From fc96ad03c9c787275c7cea6a551fb8df6f1bcd78 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Fri, 31 Jul 2026 07:53:51 -0700 Subject: [PATCH 1/2] RU-T52 Expo 56, Livekit 2.10 and Gluestack v5 Update --- .gitignore | 1 + .vscode/settings.json | 11 +- app.config.ts | 6 +- babel.config.js | 10 +- global.css | 704 ++- jest-setup.ts | 2 + jest.config.js | 8 +- metro.config.js | 4 +- package.json | 166 +- .../@gluestack-ui+actionsheet+0.2.53.patch | 66 - patches/@gluestack-ui+core+5.0.15.patch | 32 + postcss.config.mjs | 5 + react-native-css-env.d.ts | 3 + src/app/(app)/__tests__/calls.test.tsx | 13 +- src/app/(app)/__tests__/index.test.tsx | 17 +- src/app/(app)/_layout.tsx | 20 +- src/app/(app)/calls.tsx | 2 +- src/app/(app)/index.tsx | 16 +- src/app/(app)/weather-alerts.tsx | 2 +- src/app/_layout.tsx | 11 +- src/app/call/[id].tsx | 6 +- src/app/call/__tests__/[id].security.test.tsx | 25 +- src/app/call/__tests__/[id].test.tsx | 10 +- .../call/new/__tests__/what3words.test.tsx | 1 + src/app/login/__tests__/login-form.test.tsx | 1 + src/app/maps/search.tsx | 2 +- src/app/routes/active.tsx | 2 +- src/app/routes/history/[planId].tsx | 4 +- src/app/routes/history/instance/[id].tsx | 6 +- src/app/routes/poi/[id].tsx | 2 +- src/app/routes/start.tsx | 6 +- src/app/routes/stop/[id].tsx | 2 +- .../__tests__/video-feed-card.test.tsx | 1 + .../__tests__/video-feed-tab-content.test.tsx | 1 + .../__tests__/call-notes-modal-new.test.tsx | 4 +- .../close-call-bottom-sheet.test.tsx | 1 + .../dispatch-selection-basic.test.tsx | 1 + .../dispatch-selection-modal.test.tsx | 1 + src/components/calls/call-card.tsx | 4 +- src/components/calls/call-notes-modal.tsx | 2 +- .../__tests__/check-in-bottom-sheet.test.tsx | 1 + .../__tests__/check-in-timer-card.test.tsx | 1 + src/components/common/loading.tsx | 2 +- src/components/contacts/contact-card.tsx | 2 +- .../contacts/contact-details-sheet.tsx | 4 +- .../incident-command-tab-panel.test.tsx | 1 + .../incident-command-tab-panel.tsx | 12 +- .../__tests__/livekit-bottom-sheet.test.tsx | 1 + .../maps/__tests__/full-screen-map.test.tsx | 1 + .../maps/__tests__/pin-actions.test.tsx | 1 + src/components/maps/map-view.web.tsx | 34 + src/components/maps/static-map.tsx | 12 +- .../__tests__/note-details-sheet.test.tsx | 1 + src/components/notes/note-card.tsx | 2 +- .../notifications/NotificationDetail.tsx | 47 +- .../notifications/NotificationInbox.tsx | 23 +- .../__tests__/protocol-details-sheet.test.tsx | 1 + src/components/protocols/protocol-card.tsx | 2 +- .../__tests__/role-assignment-item.test.tsx | 1 + .../role-user-selection-modal.test.tsx | 1 + .../__tests__/roles-bottom-sheet.test.tsx | 1 + src/components/routes/poi-card.tsx | 2 +- src/components/routes/route-card.tsx | 2 +- src/components/routes/stop-card.tsx | 2 +- .../login-info-bottom-sheet-simple.test.tsx | 3 +- .../login-info-bottom-sheet.test.tsx | 3 +- .../server-url-bottom-sheet-simple.test.tsx | 1 + .../__tests__/status-bottom-sheet.test.tsx | 12 +- src/components/status/status-bottom-sheet.tsx | 28 +- .../ui/__tests__/actionsheet.test.tsx | 5 +- .../ui/__tests__/bottom-sheet.test.tsx | 1 + .../ui/__tests__/shared-tabs.test.tsx | 1 + src/components/ui/accordion/index.tsx | 74 +- src/components/ui/actionsheet/index.tsx | 184 +- src/components/ui/alert-dialog/index.tsx | 99 +- src/components/ui/alert/index.tsx | 77 +- src/components/ui/avatar/index.tsx | 14 +- src/components/ui/badge/index.tsx | 67 +- src/components/ui/bottomsheet/index.tsx | 52 +- src/components/ui/box/index.tsx | 2 +- src/components/ui/box/index.web.tsx | 8 +- src/components/ui/box/styles.tsx | 3 +- src/components/ui/button/index.tsx | 29 +- src/components/ui/card/index.tsx | 2 +- src/components/ui/card/index.web.tsx | 8 +- src/components/ui/card/styles.tsx | 3 +- src/components/ui/center/index.tsx | 2 +- src/components/ui/center/index.web.tsx | 8 +- src/components/ui/center/styles.tsx | 3 +- src/components/ui/checkbox/index.tsx | 113 +- src/components/ui/divider/index.tsx | 20 +- src/components/ui/drawer/index.tsx | 124 +- src/components/ui/fab/index.tsx | 37 +- src/components/ui/focus-aware-status-bar.tsx | 20 +- src/components/ui/form-control/index.tsx | 97 +- .../ui/gluestack-ui-provider/config.ts | 309 - .../ui/gluestack-ui-provider/index.tsx | 27 +- .../ui/gluestack-ui-provider/index.web.tsx | 48 +- src/components/ui/grid/index.tsx | 159 +- src/components/ui/grid/index.web.tsx | 10 +- src/components/ui/grid/styles.tsx | 4 +- src/components/ui/heading/index.tsx | 24 +- src/components/ui/heading/index.web.tsx | 22 +- src/components/ui/heading/styles.tsx | 3 +- src/components/ui/hstack/index.tsx | 2 +- src/components/ui/hstack/index.web.tsx | 8 +- src/components/ui/hstack/styles.tsx | 3 +- .../__tests__/html-renderer.test.tsx | 1 + src/components/ui/html-renderer/index.web.tsx | 3 - src/components/ui/icon/index.tsx | 27 +- src/components/ui/icon/index.web.tsx | 6 +- src/components/ui/image-background/index.tsx | 8 +- src/components/ui/image.tsx | 6 +- src/components/ui/image/index.tsx | 8 +- src/components/ui/index.tsx | 6 +- src/components/ui/input/index.tsx | 27 +- src/components/ui/link/index.tsx | 19 +- src/components/ui/menu/index.tsx | 56 +- src/components/ui/modal/index.tsx | 93 +- src/components/ui/popover/index.tsx | 90 +- src/components/ui/portal/index.tsx | 14 +- src/components/ui/pressable/index.tsx | 18 +- src/components/ui/progress/index.tsx | 11 +- src/components/ui/radio/index.tsx | 31 +- src/components/ui/select/index.tsx | 39 +- .../ui/select/select-actionsheet.tsx | 131 +- src/components/ui/skeleton/index.tsx | 2 +- src/components/ui/skeleton/index.web.tsx | 12 +- src/components/ui/skeleton/styles.tsx | 2 +- src/components/ui/slider/index.tsx | 46 +- src/components/ui/spinner/index.tsx | 17 +- src/components/ui/switch/index.tsx | 20 +- src/components/ui/text/index.tsx | 2 +- src/components/ui/text/index.web.tsx | 8 +- src/components/ui/text/styles.tsx | 3 +- src/components/ui/textarea/index.tsx | 29 +- src/components/ui/toast/index.tsx | 27 +- src/components/ui/tooltip/index.tsx | 45 +- src/components/ui/utils/dom-props.ts | 63 + .../ui/utils/use-break-point-value.ts | 104 - src/components/ui/vstack/index.tsx | 2 +- src/components/ui/vstack/index.web.tsx | 8 +- src/components/ui/vstack/styles.tsx | 3 +- .../weather-alerts/weather-alert-card.tsx | 2 +- .../__tests__/use-selected-theme.test.ts | 40 +- src/lib/hooks/use-selected-theme.tsx | 43 +- src/lib/test-utils.tsx | 7 +- .../customStatuses/customStateDetailTypes.ts | 16 +- src/stores/status/store.ts | 20 +- tailwind.config.js | 193 - tsconfig.json | 3 +- yarn.lock | 5560 +++++++---------- 152 files changed, 4193 insertions(+), 5735 deletions(-) delete mode 100644 patches/@gluestack-ui+actionsheet+0.2.53.patch create mode 100644 patches/@gluestack-ui+core+5.0.15.patch create mode 100644 postcss.config.mjs create mode 100644 react-native-css-env.d.ts delete mode 100644 src/components/ui/gluestack-ui-provider/config.ts create mode 100644 src/components/ui/utils/dom-props.ts delete mode 100644 src/components/ui/utils/use-break-point-value.ts delete mode 100644 tailwind.config.js diff --git a/.gitignore b/.gitignore index 7fda2daa..a8684c37 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ opencode.json /.dual-graph-pro .DS_Store /docs +.env.development diff --git a/.vscode/settings.json b/.vscode/settings.json index 58f69882..4fb76797 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -58,12 +58,19 @@ "statusBar.background": "#254de6", "statusBar.foreground": "#e7e7e7", "statusBarItem.hoverBackground": "#5372eb", - "statusBarItem.remoteBackground": "#254de6", + "statusBarItem.remoteBackground": "#ef708a", "statusBarItem.remoteForeground": "#e7e7e7", "titleBar.activeBackground": "#254de6", "titleBar.activeForeground": "#e7e7e7", "titleBar.inactiveBackground": "#254de699", - "titleBar.inactiveForeground": "#e7e7e799" + "titleBar.inactiveForeground": "#e7e7e799", + "activityBarTop.activeBackground": "#5372eb", + "activityBarTop.background": "#5372eb", + "activityBarTop.foreground": "#e7e7e7", + "activityBarTop.inactiveForeground": "#e7e7e799", + "commandCenter.foreground": "#e7e7e7", + "statusBar.debuggingBackground": "#254de6", + "statusBar.debuggingForeground": "#e7e7e7" }, "peacock.color": "#254de6", } diff --git a/app.config.ts b/app.config.ts index 9622edc1..d19084f1 100644 --- a/app.config.ts +++ b/app.config.ts @@ -42,7 +42,6 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ orientation: 'default', icon: './assets/icon.png', userInterfaceStyle: 'automatic', - newArchEnabled: true, updates: { fallbackToCacheTimeout: 0, }, @@ -134,6 +133,9 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ ['react-native-edge-to-edge'], 'expo-web-browser', 'expo-secure-store', + 'expo-image', + 'expo-sharing', + 'expo-status-bar', [ '@rnmapbox/maps', { @@ -179,7 +181,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ android: { extraProguardRules: '-keep class expo.modules.location.** { *; }', extraMavenRepos: ['../../node_modules/@notifee/react-native/android/libs'], - targetSdkVersion: 35, + targetSdkVersion: 36, }, ios: { deploymentTarget: '18.1', diff --git a/babel.config.js b/babel.config.js index 12fc0e5c..e0e67617 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,13 +1,10 @@ module.exports = function (api) { api.cache(true); - // Check if we're in test environment - const isTest = process.env.NODE_ENV === 'test'; - return { - presets: isTest - ? ['babel-preset-expo'] // No nativewind in test environment - : [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'], + // NativeWind v5 no longer needs a babel preset or jsxImportSource — + // styling is wired through the metro transform (react-native-css). + presets: ['babel-preset-expo'], plugins: [ [ 'module-resolver', @@ -19,7 +16,6 @@ module.exports = function (api) { '@unitools/image': '@unitools/image-expo', '@unitools/router': '@unitools/router-expo', '@unitools/link': '@unitools/link-expo', - '@tailwind.config': './tailwind.config.js', '@assets': './assets', }, extensions: ['.ios.ts', '.android.ts', '.ts', '.ios.tsx', '.android.tsx', '.tsx', '.jsx', '.js', '.json'], diff --git a/global.css b/global.css index eb9f4e72..0b211685 100644 --- a/global.css +++ b/global.css @@ -1,6 +1,699 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import 'tailwindcss/theme.css' layer(theme); +@import 'tailwindcss/preflight.css' layer(base); +@import 'tailwindcss/utilities.css'; +@import 'nativewind/theme'; + +/* Class-driven dark mode (GluestackUIProvider toggles the .dark class on web + and on the native root View). */ +@custom-variant dark (&:where(.dark, .dark *)); + +/* ─── Design tokens (light / dark) ─────────────────────────────── + 1. :root — light defaults (all platforms) + 2. @media dark :root — dark defaults; on native this follows + Appearance.getColorScheme() (the provider + sets an override for explicit modes). + 3. :root.dark / :root.light — web-only class override, beats the media + query within the same layer. */ +@layer theme { + :root { + --color-primary-0: 248 250 252; + --color-primary-50: 241 245 249; + --color-primary-100: 226 232 240; + --color-primary-200: 203 213 225; + --color-primary-300: 148 163 184; + --color-primary-400: 100 116 139; + --color-primary-500: 71 85 105; + --color-primary-600: 51 65 85; + --color-primary-700: 45 55 72; + --color-primary-800: 39 49 66; + --color-primary-900: 30 41 59; + --color-primary-950: 15 23 42; + --color-secondary-0: 254 255 255; + --color-secondary-50: 241 242 242; + --color-secondary-100: 231 232 232; + --color-secondary-200: 219 219 219; + --color-secondary-300: 175 176 176; + --color-secondary-400: 114 115 115; + --color-secondary-500: 94 95 95; + --color-secondary-600: 81 82 82; + --color-secondary-700: 63 64 64; + --color-secondary-800: 39 38 38; + --color-secondary-900: 24 23 23; + --color-secondary-950: 11 12 12; + --color-tertiary-0: 255 250 245; + --color-tertiary-50: 255 242 229; + --color-tertiary-100: 255 233 213; + --color-tertiary-200: 254 209 170; + --color-tertiary-300: 253 180 116; + --color-tertiary-400: 251 157 75; + --color-tertiary-500: 231 129 40; + --color-tertiary-600: 215 117 31; + --color-tertiary-700: 180 98 26; + --color-tertiary-800: 130 73 23; + --color-tertiary-900: 108 61 19; + --color-tertiary-950: 84 49 18; + --color-error-0: 254 233 233; + --color-error-50: 254 226 226; + --color-error-100: 254 202 202; + --color-error-200: 252 165 165; + --color-error-300: 248 113 113; + --color-error-400: 239 68 68; + --color-error-500: 230 53 53; + --color-error-600: 220 38 38; + --color-error-700: 185 28 28; + --color-error-800: 153 27 27; + --color-error-900: 127 29 29; + --color-error-950: 83 19 19; + --color-success-0: 228 255 244; + --color-success-50: 202 255 232; + --color-success-100: 162 241 192; + --color-success-200: 132 211 162; + --color-success-300: 102 181 132; + --color-success-400: 72 151 102; + --color-success-500: 52 131 82; + --color-success-600: 42 121 72; + --color-success-700: 32 111 62; + --color-success-800: 22 101 52; + --color-success-900: 20 83 45; + --color-success-950: 27 50 36; + --color-warning-0: 255 253 251; + --color-warning-50: 255 249 245; + --color-warning-100: 255 231 213; + --color-warning-200: 254 205 170; + --color-warning-300: 253 173 116; + --color-warning-400: 251 149 75; + --color-warning-500: 231 120 40; + --color-warning-600: 215 108 31; + --color-warning-700: 180 90 26; + --color-warning-800: 130 68 23; + --color-warning-900: 108 56 19; + --color-warning-950: 84 45 18; + --color-info-0: 236 248 254; + --color-info-50: 199 235 252; + --color-info-100: 162 221 250; + --color-info-200: 124 207 248; + --color-info-300: 87 194 246; + --color-info-400: 50 180 244; + --color-info-500: 13 166 242; + --color-info-600: 11 141 205; + --color-info-700: 9 115 168; + --color-info-800: 7 90 131; + --color-info-900: 5 64 93; + --color-info-950: 3 38 56; + --color-typography-0: 254 254 255; + --color-typography-50: 245 245 245; + --color-typography-100: 229 229 229; + --color-typography-200: 219 219 220; + --color-typography-300: 212 212 212; + --color-typography-400: 163 163 163; + --color-typography-500: 140 140 140; + --color-typography-600: 115 115 115; + --color-typography-700: 82 82 82; + --color-typography-800: 64 64 64; + --color-typography-900: 38 38 39; + --color-typography-950: 23 23 23; + --color-outline-0: 253 254 254; + --color-outline-50: 243 243 243; + --color-outline-100: 230 230 230; + --color-outline-200: 221 220 219; + --color-outline-300: 211 211 211; + --color-outline-400: 165 163 163; + --color-outline-500: 140 141 141; + --color-outline-600: 115 116 116; + --color-outline-700: 83 82 82; + --color-outline-800: 65 65 65; + --color-outline-900: 39 38 36; + --color-outline-950: 26 23 23; + --color-background-0: 255 255 255; + --color-background-50: 246 246 246; + --color-background-100: 242 241 241; + --color-background-200: 220 219 219; + --color-background-300: 213 212 212; + --color-background-400: 162 163 163; + --color-background-500: 142 142 142; + --color-background-600: 116 116 116; + --color-background-700: 83 82 82; + --color-background-800: 65 64 64; + --color-background-900: 39 38 37; + --color-background-950: 24 23 24; + --color-background-error: 254 241 241; + --color-background-warning: 255 244 235; + --color-background-success: 237 252 242; + --color-background-muted: 247 248 247; + --color-background-info: 235 248 254; + --color-indicator-primary: 55 55 55; + --color-indicator-info: 83 153 236; + --color-indicator-error: 185 28 28; + } + + @media (prefers-color-scheme: dark) { + :root { + --color-primary-0: 15 23 42; + --color-primary-50: 30 41 59; + --color-primary-100: 39 49 66; + --color-primary-200: 45 55 72; + --color-primary-300: 51 65 85; + --color-primary-400: 71 85 105; + --color-primary-500: 100 116 139; + --color-primary-600: 71 85 105; + --color-primary-700: 51 65 85; + --color-primary-800: 45 55 72; + --color-primary-900: 39 49 66; + --color-primary-950: 30 41 59; + --color-secondary-0: 11 12 12; + --color-secondary-50: 24 23 23; + --color-secondary-100: 39 38 38; + --color-secondary-200: 63 64 64; + --color-secondary-300: 81 82 82; + --color-secondary-400: 94 95 95; + --color-secondary-500: 114 115 115; + --color-secondary-600: 175 176 176; + --color-secondary-700: 219 219 219; + --color-secondary-800: 231 232 232; + --color-secondary-900: 241 242 242; + --color-secondary-950: 254 255 255; + --color-tertiary-0: 84 49 18; + --color-tertiary-50: 108 61 19; + --color-tertiary-100: 130 73 23; + --color-tertiary-200: 180 98 26; + --color-tertiary-300: 215 117 31; + --color-tertiary-400: 231 129 40; + --color-tertiary-500: 251 157 75; + --color-tertiary-600: 253 180 116; + --color-tertiary-700: 254 209 170; + --color-tertiary-800: 255 233 213; + --color-tertiary-900: 255 242 229; + --color-tertiary-950: 255 250 245; + --color-error-0: 83 19 19; + --color-error-50: 127 29 29; + --color-error-100: 153 27 27; + --color-error-200: 185 28 28; + --color-error-300: 220 38 38; + --color-error-400: 230 53 53; + --color-error-500: 239 68 68; + --color-error-600: 248 113 113; + --color-error-700: 252 165 165; + --color-error-800: 254 202 202; + --color-error-900: 254 226 226; + --color-error-950: 254 233 233; + --color-success-0: 27 50 36; + --color-success-50: 20 83 45; + --color-success-100: 22 101 52; + --color-success-200: 32 111 62; + --color-success-300: 42 121 72; + --color-success-400: 52 131 82; + --color-success-500: 72 151 102; + --color-success-600: 102 181 132; + --color-success-700: 132 211 162; + --color-success-800: 162 241 192; + --color-success-900: 202 255 232; + --color-success-950: 228 255 244; + --color-warning-0: 84 45 18; + --color-warning-50: 108 56 19; + --color-warning-100: 130 68 23; + --color-warning-200: 180 90 26; + --color-warning-300: 215 108 31; + --color-warning-400: 231 120 40; + --color-warning-500: 251 149 75; + --color-warning-600: 253 173 116; + --color-warning-700: 254 205 170; + --color-warning-800: 255 231 213; + --color-warning-900: 255 249 245; + --color-warning-950: 255 253 251; + --color-info-0: 3 38 56; + --color-info-50: 5 64 93; + --color-info-100: 7 90 131; + --color-info-200: 9 115 168; + --color-info-300: 11 141 205; + --color-info-400: 13 166 242; + --color-info-500: 50 180 244; + --color-info-600: 87 194 246; + --color-info-700: 124 207 248; + --color-info-800: 162 221 250; + --color-info-900: 199 235 252; + --color-info-950: 236 248 254; + --color-typography-0: 23 23 23; + --color-typography-50: 38 38 39; + --color-typography-100: 64 64 64; + --color-typography-200: 82 82 82; + --color-typography-300: 115 115 115; + --color-typography-400: 140 140 140; + --color-typography-500: 163 163 163; + --color-typography-600: 212 212 212; + --color-typography-700: 219 219 220; + --color-typography-800: 229 229 229; + --color-typography-900: 245 245 245; + --color-typography-950: 254 254 255; + --color-outline-0: 26 23 23; + --color-outline-50: 39 38 36; + --color-outline-100: 65 65 65; + --color-outline-200: 83 82 82; + --color-outline-300: 115 116 116; + --color-outline-400: 140 141 141; + --color-outline-500: 165 163 163; + --color-outline-600: 211 211 211; + --color-outline-700: 221 220 219; + --color-outline-800: 230 230 230; + --color-outline-900: 243 243 243; + --color-outline-950: 253 254 254; + --color-background-0: 18 18 18; + --color-background-50: 39 38 37; + --color-background-100: 65 64 64; + --color-background-200: 83 82 82; + --color-background-300: 116 116 116; + --color-background-400: 142 142 142; + --color-background-500: 162 163 163; + --color-background-600: 213 212 212; + --color-background-700: 220 219 219; + --color-background-800: 242 241 241; + --color-background-900: 246 246 246; + --color-background-950: 254 254 254; + --color-background-error: 66 43 43; + --color-background-warning: 65 47 35; + --color-background-success: 28 43 33; + --color-background-muted: 51 51 51; + --color-background-info: 26 40 46; + --color-indicator-primary: 247 247 247; + --color-indicator-info: 161 199 245; + --color-indicator-error: 232 70 69; + } + } + + :root.dark { + --color-primary-0: 15 23 42; + --color-primary-50: 30 41 59; + --color-primary-100: 39 49 66; + --color-primary-200: 45 55 72; + --color-primary-300: 51 65 85; + --color-primary-400: 71 85 105; + --color-primary-500: 100 116 139; + --color-primary-600: 71 85 105; + --color-primary-700: 51 65 85; + --color-primary-800: 45 55 72; + --color-primary-900: 39 49 66; + --color-primary-950: 30 41 59; + --color-secondary-0: 11 12 12; + --color-secondary-50: 24 23 23; + --color-secondary-100: 39 38 38; + --color-secondary-200: 63 64 64; + --color-secondary-300: 81 82 82; + --color-secondary-400: 94 95 95; + --color-secondary-500: 114 115 115; + --color-secondary-600: 175 176 176; + --color-secondary-700: 219 219 219; + --color-secondary-800: 231 232 232; + --color-secondary-900: 241 242 242; + --color-secondary-950: 254 255 255; + --color-tertiary-0: 84 49 18; + --color-tertiary-50: 108 61 19; + --color-tertiary-100: 130 73 23; + --color-tertiary-200: 180 98 26; + --color-tertiary-300: 215 117 31; + --color-tertiary-400: 231 129 40; + --color-tertiary-500: 251 157 75; + --color-tertiary-600: 253 180 116; + --color-tertiary-700: 254 209 170; + --color-tertiary-800: 255 233 213; + --color-tertiary-900: 255 242 229; + --color-tertiary-950: 255 250 245; + --color-error-0: 83 19 19; + --color-error-50: 127 29 29; + --color-error-100: 153 27 27; + --color-error-200: 185 28 28; + --color-error-300: 220 38 38; + --color-error-400: 230 53 53; + --color-error-500: 239 68 68; + --color-error-600: 248 113 113; + --color-error-700: 252 165 165; + --color-error-800: 254 202 202; + --color-error-900: 254 226 226; + --color-error-950: 254 233 233; + --color-success-0: 27 50 36; + --color-success-50: 20 83 45; + --color-success-100: 22 101 52; + --color-success-200: 32 111 62; + --color-success-300: 42 121 72; + --color-success-400: 52 131 82; + --color-success-500: 72 151 102; + --color-success-600: 102 181 132; + --color-success-700: 132 211 162; + --color-success-800: 162 241 192; + --color-success-900: 202 255 232; + --color-success-950: 228 255 244; + --color-warning-0: 84 45 18; + --color-warning-50: 108 56 19; + --color-warning-100: 130 68 23; + --color-warning-200: 180 90 26; + --color-warning-300: 215 108 31; + --color-warning-400: 231 120 40; + --color-warning-500: 251 149 75; + --color-warning-600: 253 173 116; + --color-warning-700: 254 205 170; + --color-warning-800: 255 231 213; + --color-warning-900: 255 249 245; + --color-warning-950: 255 253 251; + --color-info-0: 3 38 56; + --color-info-50: 5 64 93; + --color-info-100: 7 90 131; + --color-info-200: 9 115 168; + --color-info-300: 11 141 205; + --color-info-400: 13 166 242; + --color-info-500: 50 180 244; + --color-info-600: 87 194 246; + --color-info-700: 124 207 248; + --color-info-800: 162 221 250; + --color-info-900: 199 235 252; + --color-info-950: 236 248 254; + --color-typography-0: 23 23 23; + --color-typography-50: 38 38 39; + --color-typography-100: 64 64 64; + --color-typography-200: 82 82 82; + --color-typography-300: 115 115 115; + --color-typography-400: 140 140 140; + --color-typography-500: 163 163 163; + --color-typography-600: 212 212 212; + --color-typography-700: 219 219 220; + --color-typography-800: 229 229 229; + --color-typography-900: 245 245 245; + --color-typography-950: 254 254 255; + --color-outline-0: 26 23 23; + --color-outline-50: 39 38 36; + --color-outline-100: 65 65 65; + --color-outline-200: 83 82 82; + --color-outline-300: 115 116 116; + --color-outline-400: 140 141 141; + --color-outline-500: 165 163 163; + --color-outline-600: 211 211 211; + --color-outline-700: 221 220 219; + --color-outline-800: 230 230 230; + --color-outline-900: 243 243 243; + --color-outline-950: 253 254 254; + --color-background-0: 18 18 18; + --color-background-50: 39 38 37; + --color-background-100: 65 64 64; + --color-background-200: 83 82 82; + --color-background-300: 116 116 116; + --color-background-400: 142 142 142; + --color-background-500: 162 163 163; + --color-background-600: 213 212 212; + --color-background-700: 220 219 219; + --color-background-800: 242 241 241; + --color-background-900: 246 246 246; + --color-background-950: 254 254 254; + --color-background-error: 66 43 43; + --color-background-warning: 65 47 35; + --color-background-success: 28 43 33; + --color-background-muted: 51 51 51; + --color-background-info: 26 40 46; + --color-indicator-primary: 247 247 247; + --color-indicator-info: 161 199 245; + --color-indicator-error: 232 70 69; + } + + :root.light { + --color-primary-0: 248 250 252; + --color-primary-50: 241 245 249; + --color-primary-100: 226 232 240; + --color-primary-200: 203 213 225; + --color-primary-300: 148 163 184; + --color-primary-400: 100 116 139; + --color-primary-500: 71 85 105; + --color-primary-600: 51 65 85; + --color-primary-700: 45 55 72; + --color-primary-800: 39 49 66; + --color-primary-900: 30 41 59; + --color-primary-950: 15 23 42; + --color-secondary-0: 254 255 255; + --color-secondary-50: 241 242 242; + --color-secondary-100: 231 232 232; + --color-secondary-200: 219 219 219; + --color-secondary-300: 175 176 176; + --color-secondary-400: 114 115 115; + --color-secondary-500: 94 95 95; + --color-secondary-600: 81 82 82; + --color-secondary-700: 63 64 64; + --color-secondary-800: 39 38 38; + --color-secondary-900: 24 23 23; + --color-secondary-950: 11 12 12; + --color-tertiary-0: 255 250 245; + --color-tertiary-50: 255 242 229; + --color-tertiary-100: 255 233 213; + --color-tertiary-200: 254 209 170; + --color-tertiary-300: 253 180 116; + --color-tertiary-400: 251 157 75; + --color-tertiary-500: 231 129 40; + --color-tertiary-600: 215 117 31; + --color-tertiary-700: 180 98 26; + --color-tertiary-800: 130 73 23; + --color-tertiary-900: 108 61 19; + --color-tertiary-950: 84 49 18; + --color-error-0: 254 233 233; + --color-error-50: 254 226 226; + --color-error-100: 254 202 202; + --color-error-200: 252 165 165; + --color-error-300: 248 113 113; + --color-error-400: 239 68 68; + --color-error-500: 230 53 53; + --color-error-600: 220 38 38; + --color-error-700: 185 28 28; + --color-error-800: 153 27 27; + --color-error-900: 127 29 29; + --color-error-950: 83 19 19; + --color-success-0: 228 255 244; + --color-success-50: 202 255 232; + --color-success-100: 162 241 192; + --color-success-200: 132 211 162; + --color-success-300: 102 181 132; + --color-success-400: 72 151 102; + --color-success-500: 52 131 82; + --color-success-600: 42 121 72; + --color-success-700: 32 111 62; + --color-success-800: 22 101 52; + --color-success-900: 20 83 45; + --color-success-950: 27 50 36; + --color-warning-0: 255 253 251; + --color-warning-50: 255 249 245; + --color-warning-100: 255 231 213; + --color-warning-200: 254 205 170; + --color-warning-300: 253 173 116; + --color-warning-400: 251 149 75; + --color-warning-500: 231 120 40; + --color-warning-600: 215 108 31; + --color-warning-700: 180 90 26; + --color-warning-800: 130 68 23; + --color-warning-900: 108 56 19; + --color-warning-950: 84 45 18; + --color-info-0: 236 248 254; + --color-info-50: 199 235 252; + --color-info-100: 162 221 250; + --color-info-200: 124 207 248; + --color-info-300: 87 194 246; + --color-info-400: 50 180 244; + --color-info-500: 13 166 242; + --color-info-600: 11 141 205; + --color-info-700: 9 115 168; + --color-info-800: 7 90 131; + --color-info-900: 5 64 93; + --color-info-950: 3 38 56; + --color-typography-0: 254 254 255; + --color-typography-50: 245 245 245; + --color-typography-100: 229 229 229; + --color-typography-200: 219 219 220; + --color-typography-300: 212 212 212; + --color-typography-400: 163 163 163; + --color-typography-500: 140 140 140; + --color-typography-600: 115 115 115; + --color-typography-700: 82 82 82; + --color-typography-800: 64 64 64; + --color-typography-900: 38 38 39; + --color-typography-950: 23 23 23; + --color-outline-0: 253 254 254; + --color-outline-50: 243 243 243; + --color-outline-100: 230 230 230; + --color-outline-200: 221 220 219; + --color-outline-300: 211 211 211; + --color-outline-400: 165 163 163; + --color-outline-500: 140 141 141; + --color-outline-600: 115 116 116; + --color-outline-700: 83 82 82; + --color-outline-800: 65 65 65; + --color-outline-900: 39 38 36; + --color-outline-950: 26 23 23; + --color-background-0: 255 255 255; + --color-background-50: 246 246 246; + --color-background-100: 242 241 241; + --color-background-200: 220 219 219; + --color-background-300: 213 212 212; + --color-background-400: 162 163 163; + --color-background-500: 142 142 142; + --color-background-600: 116 116 116; + --color-background-700: 83 82 82; + --color-background-800: 65 64 64; + --color-background-900: 39 38 37; + --color-background-950: 24 23 24; + --color-background-error: 254 241 241; + --color-background-warning: 255 244 235; + --color-background-success: 237 252 242; + --color-background-muted: 247 248 247; + --color-background-info: 235 248 254; + --color-indicator-primary: 55 55 55; + --color-indicator-info: 83 153 236; + --color-indicator-error: 185 28 28; + } +} + +/* ─── Tailwind v4 utilities for the legacy gluestack v2 token scales ── */ +@theme inline { + --color-primary-0: rgb(var(--color-primary-0)); + --color-primary-50: rgb(var(--color-primary-50)); + --color-primary-100: rgb(var(--color-primary-100)); + --color-primary-200: rgb(var(--color-primary-200)); + --color-primary-300: rgb(var(--color-primary-300)); + --color-primary-400: rgb(var(--color-primary-400)); + --color-primary-500: rgb(var(--color-primary-500)); + --color-primary-600: rgb(var(--color-primary-600)); + --color-primary-700: rgb(var(--color-primary-700)); + --color-primary-800: rgb(var(--color-primary-800)); + --color-primary-900: rgb(var(--color-primary-900)); + --color-primary-950: rgb(var(--color-primary-950)); + --color-secondary-0: rgb(var(--color-secondary-0)); + --color-secondary-50: rgb(var(--color-secondary-50)); + --color-secondary-100: rgb(var(--color-secondary-100)); + --color-secondary-200: rgb(var(--color-secondary-200)); + --color-secondary-300: rgb(var(--color-secondary-300)); + --color-secondary-400: rgb(var(--color-secondary-400)); + --color-secondary-500: rgb(var(--color-secondary-500)); + --color-secondary-600: rgb(var(--color-secondary-600)); + --color-secondary-700: rgb(var(--color-secondary-700)); + --color-secondary-800: rgb(var(--color-secondary-800)); + --color-secondary-900: rgb(var(--color-secondary-900)); + --color-secondary-950: rgb(var(--color-secondary-950)); + --color-tertiary-0: rgb(var(--color-tertiary-0)); + --color-tertiary-50: rgb(var(--color-tertiary-50)); + --color-tertiary-100: rgb(var(--color-tertiary-100)); + --color-tertiary-200: rgb(var(--color-tertiary-200)); + --color-tertiary-300: rgb(var(--color-tertiary-300)); + --color-tertiary-400: rgb(var(--color-tertiary-400)); + --color-tertiary-500: rgb(var(--color-tertiary-500)); + --color-tertiary-600: rgb(var(--color-tertiary-600)); + --color-tertiary-700: rgb(var(--color-tertiary-700)); + --color-tertiary-800: rgb(var(--color-tertiary-800)); + --color-tertiary-900: rgb(var(--color-tertiary-900)); + --color-tertiary-950: rgb(var(--color-tertiary-950)); + --color-error-0: rgb(var(--color-error-0)); + --color-error-50: rgb(var(--color-error-50)); + --color-error-100: rgb(var(--color-error-100)); + --color-error-200: rgb(var(--color-error-200)); + --color-error-300: rgb(var(--color-error-300)); + --color-error-400: rgb(var(--color-error-400)); + --color-error-500: rgb(var(--color-error-500)); + --color-error-600: rgb(var(--color-error-600)); + --color-error-700: rgb(var(--color-error-700)); + --color-error-800: rgb(var(--color-error-800)); + --color-error-900: rgb(var(--color-error-900)); + --color-error-950: rgb(var(--color-error-950)); + --color-success-0: rgb(var(--color-success-0)); + --color-success-50: rgb(var(--color-success-50)); + --color-success-100: rgb(var(--color-success-100)); + --color-success-200: rgb(var(--color-success-200)); + --color-success-300: rgb(var(--color-success-300)); + --color-success-400: rgb(var(--color-success-400)); + --color-success-500: rgb(var(--color-success-500)); + --color-success-600: rgb(var(--color-success-600)); + --color-success-700: rgb(var(--color-success-700)); + --color-success-800: rgb(var(--color-success-800)); + --color-success-900: rgb(var(--color-success-900)); + --color-success-950: rgb(var(--color-success-950)); + --color-warning-0: rgb(var(--color-warning-0)); + --color-warning-50: rgb(var(--color-warning-50)); + --color-warning-100: rgb(var(--color-warning-100)); + --color-warning-200: rgb(var(--color-warning-200)); + --color-warning-300: rgb(var(--color-warning-300)); + --color-warning-400: rgb(var(--color-warning-400)); + --color-warning-500: rgb(var(--color-warning-500)); + --color-warning-600: rgb(var(--color-warning-600)); + --color-warning-700: rgb(var(--color-warning-700)); + --color-warning-800: rgb(var(--color-warning-800)); + --color-warning-900: rgb(var(--color-warning-900)); + --color-warning-950: rgb(var(--color-warning-950)); + --color-info-0: rgb(var(--color-info-0)); + --color-info-50: rgb(var(--color-info-50)); + --color-info-100: rgb(var(--color-info-100)); + --color-info-200: rgb(var(--color-info-200)); + --color-info-300: rgb(var(--color-info-300)); + --color-info-400: rgb(var(--color-info-400)); + --color-info-500: rgb(var(--color-info-500)); + --color-info-600: rgb(var(--color-info-600)); + --color-info-700: rgb(var(--color-info-700)); + --color-info-800: rgb(var(--color-info-800)); + --color-info-900: rgb(var(--color-info-900)); + --color-info-950: rgb(var(--color-info-950)); + --color-typography-0: rgb(var(--color-typography-0)); + --color-typography-50: rgb(var(--color-typography-50)); + --color-typography-100: rgb(var(--color-typography-100)); + --color-typography-200: rgb(var(--color-typography-200)); + --color-typography-300: rgb(var(--color-typography-300)); + --color-typography-400: rgb(var(--color-typography-400)); + --color-typography-500: rgb(var(--color-typography-500)); + --color-typography-600: rgb(var(--color-typography-600)); + --color-typography-700: rgb(var(--color-typography-700)); + --color-typography-800: rgb(var(--color-typography-800)); + --color-typography-900: rgb(var(--color-typography-900)); + --color-typography-950: rgb(var(--color-typography-950)); + --color-outline-0: rgb(var(--color-outline-0)); + --color-outline-50: rgb(var(--color-outline-50)); + --color-outline-100: rgb(var(--color-outline-100)); + --color-outline-200: rgb(var(--color-outline-200)); + --color-outline-300: rgb(var(--color-outline-300)); + --color-outline-400: rgb(var(--color-outline-400)); + --color-outline-500: rgb(var(--color-outline-500)); + --color-outline-600: rgb(var(--color-outline-600)); + --color-outline-700: rgb(var(--color-outline-700)); + --color-outline-800: rgb(var(--color-outline-800)); + --color-outline-900: rgb(var(--color-outline-900)); + --color-outline-950: rgb(var(--color-outline-950)); + --color-background-0: rgb(var(--color-background-0)); + --color-background-50: rgb(var(--color-background-50)); + --color-background-100: rgb(var(--color-background-100)); + --color-background-200: rgb(var(--color-background-200)); + --color-background-300: rgb(var(--color-background-300)); + --color-background-400: rgb(var(--color-background-400)); + --color-background-500: rgb(var(--color-background-500)); + --color-background-600: rgb(var(--color-background-600)); + --color-background-700: rgb(var(--color-background-700)); + --color-background-800: rgb(var(--color-background-800)); + --color-background-900: rgb(var(--color-background-900)); + --color-background-950: rgb(var(--color-background-950)); + --color-background-error: rgb(var(--color-background-error)); + --color-background-warning: rgb(var(--color-background-warning)); + --color-background-success: rgb(var(--color-background-success)); + --color-background-muted: rgb(var(--color-background-muted)); + --color-background-info: rgb(var(--color-background-info)); + --color-indicator-primary: rgb(var(--color-indicator-primary)); + --color-indicator-info: rgb(var(--color-indicator-info)); + --color-indicator-error: rgb(var(--color-indicator-error)); +} + +/* ─── Non-color theme extensions (ported from tailwind.config.js) ── */ +@theme { + --font-roboto: Roboto, sans-serif; + --font-weight-extrablack: 950; + --text-2xs: 10px; + --color-typography-white: #ffffff; + --color-typography-gray: #d4d4d4; + --color-typography-black: #181718; + --color-background-light: #fbfbfb; + --color-background-dark: #181719; + --shadow-hard-1: -2px 2px 8px 0px rgba(38, 38, 38, 0.2); + --shadow-hard-2: 0px 3px 10px 0px rgba(38, 38, 38, 0.2); + --shadow-hard-3: 2px 2px 8px 0px rgba(38, 38, 38, 0.2); + --shadow-hard-4: 0px -3px 10px 0px rgba(38, 38, 38, 0.2); + --shadow-hard-5: 0px 2px 10px 0px rgba(38, 38, 38, 0.1); + --shadow-soft-1: 0px 0px 10px rgba(38, 38, 38, 0.1); + --shadow-soft-2: 0px 0px 20px rgba(38, 38, 38, 0.2); + --shadow-soft-3: 0px 0px 30px rgba(38, 38, 38, 0.1); + --shadow-soft-4: 0px 0px 40px rgba(38, 38, 38, 0.1); +} /* Web-only pulse animation for user location marker on map */ @keyframes pulse-ring { @@ -14,6 +707,11 @@ } } +/* Web-only: applied to the location marker outer ring (replaces Animated.loop) */ +.pulse-ring { + animation: pulse-ring 2s ease-in-out infinite; +} + /* Web-only skeleton loading animation */ @keyframes skeleton-pulse { 0%, 100% { diff --git a/jest-setup.ts b/jest-setup.ts index 7527560f..58be9263 100644 --- a/jest-setup.ts +++ b/jest-setup.ts @@ -229,6 +229,8 @@ jest.mock('react-native-permissions', () => ({ // Mock nativewind to avoid react-native-css-interop issues jest.mock('nativewind', () => ({ + styled: jest.fn((Component: any) => Component), + vars: jest.fn((v: any) => v), cssInterop: jest.fn((Component: any) => Component), useColorScheme: jest.fn(() => ({ colorScheme: 'light', diff --git a/jest.config.js b/jest.config.js index be67fad0..69fcffff 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,11 @@ +// Must be set before babel-preset-expo transforms modules (it inlines +// EXPO_PUBLIC_* vars) — makes expo's winter runtime keep RN's fetch in tests. +process.env.EXPO_PUBLIC_USE_RN_FETCH = '1'; + module.exports = { preset: 'jest-expo', + // Reanimated v4 worklets: resolve to the non-native builds in Jest + resolver: 'react-native-worklets/jest/resolver.js', setupFilesAfterEnv: ['/jest-setup.ts'], testMatch: ['**/?(*.)+(spec|test).ts?(x)'], testPathIgnorePatterns: ['/node_modules/', '\\.\\._.*'], @@ -8,7 +14,7 @@ module.exports = { moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], moduleDirectories: ['node_modules', '/'], transformIgnorePatterns: [ - 'node_modules/(?!((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg|@legendapp/motion|@gluestack-ui|expo-audio|@aptabase/.*|@shopify/flash-list))', + 'node_modules/(?!((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|standard-navigation|@rnmapbox|@sentry/react-native|native-base|react-native-svg|@legendapp/motion|@gluestack-ui|nativewind|react-native-css|expo-audio|@aptabase/.*|@shopify/flash-list))', ], coverageReporters: ['json-summary', ['text', { file: 'coverage.txt' }], 'cobertura'], reporters: [ diff --git a/metro.config.js b/metro.config.js index 50429dac..42ae3e79 100644 --- a/metro.config.js +++ b/metro.config.js @@ -3,7 +3,7 @@ const { getSentryExpoConfig } = require('@sentry/react-native/metro'); //const { getDefaultConfig } = require('expo/metro-config'); const path = require('path'); -const { withNativeWind } = require('nativewind/metro'); +const { withNativewind } = require('nativewind/metro'); const config = getSentryExpoConfig(__dirname, { isCSSEnabled: true, @@ -97,4 +97,4 @@ config.resolver.resolveRequest = (context, moduleName, platform) => { return context.resolveRequest(context, moduleName, platform); }; -module.exports = withNativeWind(config, { input: './global.css', inlineRem: 16 }); +module.exports = withNativewind(config, { inlineRem: 16 }); diff --git a/package.json b/package.json index ad6b27e5..d1144fd2 100644 --- a/package.json +++ b/package.json @@ -54,53 +54,26 @@ "electron:build:linux": "yarn web:build && electron-builder --config electron-builder.config.js --linux" }, "dependencies": { - "@config-plugins/react-native-callkeep": "11.0.0", - "@config-plugins/react-native-webrtc": "12.0.0", - "@dev-plugins/react-query": "0.2.0", + "@config-plugins/react-native-callkeep": "14.0.1", + "@config-plugins/react-native-webrtc": "15.0.1", + "@dev-plugins/react-query": "0.4.0", "@expo/html-elements": "0.10.1", - "@expo/metro-runtime": "6.1.2", - "@gluestack-ui/accordion": "1.0.14", - "@gluestack-ui/actionsheet": "0.2.53", - "@gluestack-ui/alert": "0.1.16", - "@gluestack-ui/alert-dialog": "0.1.38", - "@gluestack-ui/avatar": "0.1.18", - "@gluestack-ui/button": "1.0.14", - "@gluestack-ui/checkbox": "0.1.39", - "@gluestack-ui/divider": "0.1.10", - "@gluestack-ui/fab": "0.1.28", - "@gluestack-ui/form-control": "0.1.19", - "@gluestack-ui/icon": "0.1.27", - "@gluestack-ui/image": "0.1.17", - "@gluestack-ui/input": "0.1.38", - "@gluestack-ui/link": "0.1.29", - "@gluestack-ui/menu": "0.2.43", - "@gluestack-ui/modal": "0.1.41", - "@gluestack-ui/nativewind-utils": "1.0.28", - "@gluestack-ui/overlay": "0.1.22", - "@gluestack-ui/popover": "0.1.49", - "@gluestack-ui/pressable": "0.1.23", - "@gluestack-ui/progress": "0.1.18", - "@gluestack-ui/radio": "0.1.40", - "@gluestack-ui/select": "0.1.31", - "@gluestack-ui/slider": "0.1.32", - "@gluestack-ui/spinner": "0.1.15", - "@gluestack-ui/switch": "0.1.29", - "@gluestack-ui/textarea": "0.1.25", - "@gluestack-ui/toast": "1.0.9", - "@gluestack-ui/tooltip": "0.1.44", + "@expo/metro-runtime": "~56.0.18", + "@gluestack-ui/core": "^5.0.15", + "@gluestack-ui/utils": "^5.0.6", "@gorhom/bottom-sheet": "5.2.13", "@hookform/resolvers": "3.9.1", "@legendapp/motion": "2.4.0", - "@livekit/react-native": "2.9.1", - "@livekit/react-native-expo-plugin": "1.0.1", - "@livekit/react-native-webrtc": "137.0.2", + "@livekit/react-native": "2.12.0", + "@livekit/react-native-expo-plugin": "1.0.2", + "@livekit/react-native-webrtc": "144.1.2", "@microsoft/signalr": "8.0.17", "@notifee/react-native": "9.1.8", "@novu/react-native": "3.11.0", - "@react-native-community/netinfo": "11.4.1", + "@react-native-community/netinfo": "12.0.1", "@rnmapbox/maps": "10.2.10", "@semantic-release/git": "10.0.1", - "@sentry/react-native": "8.10.0", + "@sentry/react-native": "~8.20.0", "@shopify/flash-list": "2.0.2", "@tanstack/react-query": "5.52.3", "app-icon-badge": "0.1.2", @@ -110,82 +83,85 @@ "buffer": "6.0.3", "countly-sdk-react-native-bridge": "26.1.0", "date-fns": "4.1.0", - "expo": "54.0.33", - "expo-application": "7.0.8", - "expo-asset": "12.0.12", - "expo-audio": "1.1.1", - "expo-auth-session": "7.0.10", + "expo": "56.0.18", + "expo-application": "~56.0.3", + "expo-asset": "~56.0.21", + "expo-audio": "~56.0.13", + "expo-auth-session": "~56.0.16", "expo-av": "16.0.8", - "expo-build-properties": "1.0.10", - "expo-clipboard": "8.0.8", - "expo-constants": "18.0.13", - "expo-crypto": "15.0.8", - "expo-dev-client": "6.0.20", - "expo-device": "8.0.10", - "expo-document-picker": "14.0.8", - "expo-file-system": "19.0.21", - "expo-font": "14.0.11", - "expo-image": "3.0.11", - "expo-image-manipulator": "14.0.8", - "expo-image-picker": "17.0.10", - "expo-keep-awake": "15.0.8", - "expo-linking": "8.0.11", - "expo-localization": "17.0.8", - "expo-location": "19.0.8", - "expo-navigation-bar": "5.0.10", - "expo-notifications": "0.32.16", - "expo-router": "6.0.23", - "expo-screen-orientation": "9.0.8", - "expo-secure-store": "15.0.8", - "expo-sharing": "14.0.8", - "expo-splash-screen": "31.0.13", - "expo-status-bar": "3.0.9", - "expo-system-ui": "6.0.9", - "expo-task-manager": "14.0.9", - "expo-web-browser": "15.0.10", + "expo-build-properties": "~56.0.24", + "expo-clipboard": "~56.0.4", + "expo-constants": "~56.0.22", + "expo-crypto": "~56.0.4", + "expo-dev-client": "~56.0.24", + "expo-device": "~56.0.4", + "expo-document-picker": "~56.0.4", + "expo-file-system": "~56.0.8", + "expo-font": "~56.0.7", + "expo-image": "~56.0.11", + "expo-image-manipulator": "~56.0.23", + "expo-image-picker": "~56.0.22", + "expo-keep-awake": "~56.0.3", + "expo-linking": "~56.0.16", + "expo-localization": "~56.0.6", + "expo-location": "~56.0.22", + "expo-navigation-bar": "~56.0.3", + "expo-notifications": "~56.0.22", + "expo-router": "~56.2.17", + "expo-screen-orientation": "~56.0.5", + "expo-secure-store": "~56.0.4", + "expo-sharing": "~56.0.23", + "expo-splash-screen": "~56.0.14", + "expo-status-bar": "~56.0.4", + "expo-system-ui": "~56.0.5", + "expo-task-manager": "~56.0.24", + "expo-web-browser": "~56.0.6", "geojson": "0.5.0", "i18next": "23.14.0", - "livekit-client": "2.15.7", + "livekit-client": "^2.20.1", "lodash": "4.17.23", "lodash.memoize": "4.1.2", "lucide-react-native": "0.475.0", "mapbox-gl": "3.18.1", "moti": "0.29.0", - "nativewind": "4.1.23", + "nativewind": "5.0.0-preview.4", "promise": "8.3.0", - "react": "19.1.0", - "react-dom": "19.1.0", + "react": "19.2.3", + "react-dom": "19.2.3", "react-error-boundary": "4.0.13", "react-hook-form": "7.53.2", "react-i18next": "15.0.3", - "react-native": "0.81.5", + "react-native": "0.85.3", "react-native-base64": "0.2.2", "react-native-ble-manager": "12.2.1", "react-native-callkeep": "github:Irfanwani/react-native-callkeep#957193d0716f1c2dfdc18e627cbff0f8a0800971", + "react-native-css": "^3.0.7", "react-native-edge-to-edge": "1.6.0", "react-native-flash-message": "0.4.2", - "react-native-gesture-handler": "2.28.0", - "react-native-keyboard-controller": "1.18.5", + "react-native-gesture-handler": "~2.31.1", + "react-native-keyboard-controller": "1.21.6", "react-native-logs": "5.3.0", "react-native-mmkv": "3.1.0", - "react-native-reanimated": "4.1.7", + "react-native-reanimated": "4.3.1", "react-native-restart": "0.0.27", - "react-native-safe-area-context": "5.6.2", - "react-native-screens": "4.16.0", - "react-native-svg": "15.12.1", + "react-native-safe-area-context": "~5.7.0", + "react-native-screens": "~4.26.0", + "react-native-svg": "15.15.4", "react-native-web": "0.21.2", - "react-native-webview": "13.15.0", - "react-native-worklets": "0.5.1", + "react-native-webview": "13.16.1", + "react-native-worklets": "0.8.3", "react-query-kit": "3.3.2", "tailwind-variants": "0.2.1", "zod": "3.23.8", "zustand": "4.5.7" }, "devDependencies": { - "@babel/core": "7.26.10", + "@babel/core": "^7.29.0", "@commitlint/cli": "19.2.2", "@commitlint/config-conventional": "19.2.2", - "@expo/config": "12.0.13", + "@expo/config": "^56.0.0", + "@react-native/jest-preset": "~0.85.3", + "@tailwindcss/postcss": "^4.2.0", "@testing-library/jest-dom": "6.5.0", "@testing-library/react-native": "12.9.0", "@types/geojson": "7946.0.16", @@ -193,7 +169,7 @@ "@types/jest": "29.5.14", "@types/lodash.memoize": "4.1.9", "@types/mapbox-gl": "3.4.1", - "@types/react": "19.1.17", + "@types/react": "~19.2.14", "@types/react-native-base64": "0.2.2", "@typescript-eslint/eslint-plugin": "8.56.0", "@typescript-eslint/parser": "8.56.0", @@ -219,7 +195,7 @@ "eslint-plugin-unused-imports": "2.0.0", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", - "jest-expo": "54.0.17", + "jest-expo": "~56.0.5", "jest-junit": "16.0.0", "lint-staged": "15.2.11", "np": "10.0.7", @@ -227,10 +203,9 @@ "postinstall-postinstall": "2.1.0", "prettier": "3.3.3", "react-native-svg-transformer": "1.5.1", - "tailwindcss": "3.4.4", - "ts-jest": "29.1.5", + "tailwindcss": "^4.2.0", "ts-node": "10.9.2", - "typescript": "5.9.3", + "typescript": "~6.0.3", "wait-on": "9.0.3" }, "repository": { @@ -253,12 +228,7 @@ "install": { "exclude": [ "eslint-config-expo", - "react-native@~0.76.6", - "react-native-reanimated@~3.16.1", - "react-native-gesture-handler@~2.20.0", - "react-native-screens@~4.4.0", - "react-native-safe-area-context@~4.12.0", - "react-native-webview@~13.12.5" + "@sentry/react-native" ] } }, @@ -266,8 +236,8 @@ "initVersion": "7.0.4" }, "resolutions": { - "@expo/metro-config": "54.0.15", "form-data": "4.0.4", - "promise": "8.3.0" + "promise": "8.3.0", + "lightningcss": "1.30.1" } } diff --git a/patches/@gluestack-ui+actionsheet+0.2.53.patch b/patches/@gluestack-ui+actionsheet+0.2.53.patch deleted file mode 100644 index 19c2505b..00000000 --- a/patches/@gluestack-ui+actionsheet+0.2.53.patch +++ /dev/null @@ -1,66 +0,0 @@ -diff --git a/node_modules/@gluestack-ui/actionsheet/lib/ActionsheetContent.jsx b/node_modules/@gluestack-ui/actionsheet/lib/ActionsheetContent.jsx -index 5055fe3..b7b0eca 100644 ---- a/node_modules/@gluestack-ui/actionsheet/lib/ActionsheetContent.jsx -+++ b/node_modules/@gluestack-ui/actionsheet/lib/ActionsheetContent.jsx -@@ -1,6 +1,6 @@ - /* eslint-disable react-native/no-inline-styles */ - import React, { forwardRef } from 'react'; --import { Animated, Dimensions, AccessibilityInfo, Platform, Keyboard, } from 'react-native'; -+import { Animated, Dimensions, AccessibilityInfo, Platform, Keyboard, useWindowDimensions, } from 'react-native'; - import { ActionsheetContext } from './context'; - import { ActionsheetContentProvider } from './ActionsheetContentContext'; - import { OverlayAnimatePresence } from './OverlayAnimatePresence'; -@@ -8,15 +8,11 @@ import { FocusScope } from '@react-native-aria/focus'; - import { mergeRefs, findNodeHandle } from '@gluestack-ui/utils'; - import { useDialog } from '@react-native-aria/dialog'; - import { usePreventScroll } from '@react-native-aria/overlays'; --//dimentions not giving proper window height on web --const windowHeight = Platform.OS === 'web' -- ? typeof window !== 'undefined' -- ? window.innerHeight -- : Dimensions.get('screen').height -- : Dimensions.get('screen').height; -+// Patched: use useWindowDimensions for reactive height (fixes landscape) - function ActionsheetContent(StyledActionsheetContent, AnimatePresence) { - return forwardRef(({ children, _experimentalContent = false, focusScope = true, ...props }, ref) => { - const { visible, handleClose, trapFocus, initialFocusRef, handleCloseBackdrop, finalFocusRef, snapPoints, preventScroll, } = React.useContext(ActionsheetContext); -+ const { height: windowHeight } = useWindowDimensions(); - usePreventScroll({ isDisabled: preventScroll }); - const pan = React.useRef(new Animated.ValueXY()).current; - const contentSheetHeight = React.useRef(0); -diff --git a/node_modules/@gluestack-ui/actionsheet/src/ActionsheetContent.tsx b/node_modules/@gluestack-ui/actionsheet/src/ActionsheetContent.tsx -index 425a859..f0ec760 100644 ---- a/node_modules/@gluestack-ui/actionsheet/src/ActionsheetContent.tsx -+++ b/node_modules/@gluestack-ui/actionsheet/src/ActionsheetContent.tsx -@@ -6,6 +6,7 @@ import { - AccessibilityInfo, - Platform, - Keyboard, -+ useWindowDimensions, - } from 'react-native'; - import { ActionsheetContext } from './context'; - import { ActionsheetContentProvider } from './ActionsheetContentContext'; -@@ -15,13 +16,7 @@ import { mergeRefs, findNodeHandle } from '@gluestack-ui/utils'; - import { useDialog } from '@react-native-aria/dialog'; - import { usePreventScroll } from '@react-native-aria/overlays'; - --//dimentions not giving proper window height on web --const windowHeight = -- Platform.OS === 'web' -- ? typeof window !== 'undefined' -- ? window.innerHeight -- : Dimensions.get('screen').height -- : Dimensions.get('screen').height; -+// Patched: use useWindowDimensions for reactive height (fixes landscape) - - function ActionsheetContent( - StyledActionsheetContent: any, -@@ -48,6 +43,8 @@ function ActionsheetContent( - preventScroll, - } = React.useContext(ActionsheetContext); - -+ const { height: windowHeight } = useWindowDimensions(); -+ - usePreventScroll({ isDisabled: preventScroll }); - - const pan = React.useRef(new Animated.ValueXY()).current; diff --git a/patches/@gluestack-ui+core+5.0.15.patch b/patches/@gluestack-ui+core+5.0.15.patch new file mode 100644 index 00000000..ab1419a2 --- /dev/null +++ b/patches/@gluestack-ui+core+5.0.15.patch @@ -0,0 +1,32 @@ +diff --git a/node_modules/@gluestack-ui/core/lib/esm/actionsheet/creator/ActionsheetContent.jsx b/node_modules/@gluestack-ui/core/lib/esm/actionsheet/creator/ActionsheetContent.jsx +index 30962f7..d024fbf 100644 +--- a/node_modules/@gluestack-ui/core/lib/esm/actionsheet/creator/ActionsheetContent.jsx ++++ b/node_modules/@gluestack-ui/core/lib/esm/actionsheet/creator/ActionsheetContent.jsx +@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) { + }; + /* eslint-disable react-native/no-inline-styles */ + import React, { forwardRef } from 'react'; +-import { Animated, Dimensions, AccessibilityInfo, Platform, Keyboard, } from 'react-native'; ++import { Animated, Dimensions, AccessibilityInfo, Platform, Keyboard, useWindowDimensions, } from 'react-native'; + import { ActionsheetContext } from './context'; + import { ActionsheetContentProvider } from './ActionsheetContentContext'; + import { OverlayAnimatePresence } from './OverlayAnimatePresence'; +@@ -19,16 +19,12 @@ import { FocusScope } from '@gluestack-ui/utils/aria'; + import { mergeRefs, findNodeHandle } from '@gluestack-ui/utils/common'; + import { useDialog } from '@gluestack-ui/utils/aria'; + import { usePreventScroll } from '../../overlay/aria'; +-//dimentions not giving proper window height on web +-const windowHeight = Platform.OS === 'web' +- ? typeof window !== 'undefined' +- ? window.innerHeight +- : Dimensions.get('screen').height +- : Dimensions.get('screen').height; ++// Patched: use useWindowDimensions for reactive height (fixes landscape) + function ActionsheetContent(StyledActionsheetContent, AnimatePresence) { + return forwardRef((_a, ref) => { + var { children, _experimentalContent = false, focusScope = true } = _a, props = __rest(_a, ["children", "_experimentalContent", "focusScope"]); + const { visible, handleClose, trapFocus, initialFocusRef, handleCloseBackdrop, finalFocusRef, snapPoints, preventScroll, } = React.useContext(ActionsheetContext); ++ const { height: windowHeight } = useWindowDimensions(); + usePreventScroll({ isDisabled: preventScroll }); + const pan = React.useRef(new Animated.ValueXY()).current; + const contentSheetHeight = React.useRef(0); diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 00000000..a34a3d56 --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; diff --git a/react-native-css-env.d.ts b/react-native-css-env.d.ts new file mode 100644 index 00000000..82aac401 --- /dev/null +++ b/react-native-css-env.d.ts @@ -0,0 +1,3 @@ +/// + +// NOTE: This file should not be edited and should be committed with your source code. It is generated by react-native-css. If you need to move or disable this file, please see the documentation. diff --git a/src/app/(app)/__tests__/calls.test.tsx b/src/app/(app)/__tests__/calls.test.tsx index a8d5f3da..da6b63f9 100644 --- a/src/app/(app)/__tests__/calls.test.tsx +++ b/src/app/(app)/__tests__/calls.test.tsx @@ -22,6 +22,11 @@ jest.mock('react-native', () => ({ // Mock expo-router jest.mock('expo-router', () => ({ + useFocusEffect: jest.fn((callback: () => void) => { + const React = require('react'); + React.useEffect(callback, []); + }), + useIsFocused: jest.fn(() => true), router: { push: jest.fn(), }, @@ -187,6 +192,7 @@ jest.mock('expo-navigation-bar', () => ({ })); jest.mock('nativewind', () => ({ + styled: jest.fn((Component: any) => Component), useColorScheme: jest.fn(() => ({ colorScheme: 'light' })), })); @@ -200,13 +206,6 @@ jest.mock('@/components/ui/focus-aware-status-bar', () => ({ })); // Mock useFocusEffect and useIsFocused -jest.mock('@react-navigation/native', () => ({ - useFocusEffect: jest.fn((callback: () => void) => { - const React = require('react'); - React.useEffect(callback, []); - }), - useIsFocused: jest.fn(() => true), -})); import CallsScreen from '../calls'; diff --git a/src/app/(app)/__tests__/index.test.tsx b/src/app/(app)/__tests__/index.test.tsx index 86d16dba..bd45d781 100644 --- a/src/app/(app)/__tests__/index.test.tsx +++ b/src/app/(app)/__tests__/index.test.tsx @@ -1,5 +1,6 @@ // Mock nativewind before any imports jest.mock('nativewind', () => ({ + styled: jest.fn((Component: any) => Component), cssInterop: jest.fn((Component: any) => Component), useColorScheme: jest.fn(() => ({ colorScheme: 'light', @@ -30,15 +31,6 @@ jest.mock('@/services/location'); jest.mock('@/hooks/use-map-signalr-updates', () => ({ useMapSignalRUpdates: jest.fn(), })); -jest.mock('@react-navigation/native', () => ({ - useIsFocused: jest.fn(() => true), - useNavigation: jest.fn(() => ({ - navigate: jest.fn(), - push: jest.fn(), - replace: jest.fn(), - goBack: jest.fn(), - })), -})); jest.mock('@/api/mapping/mapping', () => ({ getMapDataAndMarkers: jest.fn().mockResolvedValue({ Data: { MapMakerInfos: [] } @@ -60,6 +52,13 @@ jest.mock('@rnmapbox/maps', () => ({ }, })); jest.mock('expo-router', () => ({ + useIsFocused: jest.fn(() => true), + useNavigation: jest.fn(() => ({ + navigate: jest.fn(), + push: jest.fn(), + replace: jest.fn(), + goBack: jest.fn(), + })), Stack: { Screen: ({ children, ...props }: any) => children, }, diff --git a/src/app/(app)/_layout.tsx b/src/app/(app)/_layout.tsx index b6196818..1128f635 100644 --- a/src/app/(app)/_layout.tsx +++ b/src/app/(app)/_layout.tsx @@ -7,7 +7,7 @@ import { Redirect, SplashScreen, Tabs } from 'expo-router'; import { CloudAlert, Contact, ListTree, Map, Megaphone, Menu, Navigation, Notebook, Settings } from 'lucide-react-native'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { ActivityIndicator, Platform, StyleSheet, useWindowDimensions } from 'react-native'; +import { ActivityIndicator, type ColorValue, Platform, StyleSheet, useWindowDimensions } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { NotificationButton } from '@/components/notifications/NotificationButton'; @@ -368,14 +368,14 @@ export default function TabLayout() { const handleCloseNotifications = useCallback(() => setIsNotificationsOpen(false), []); // Memoize per-screen tab bar icon renderers to prevent new functions every render - const mapIcon = useCallback(({ color }: { color: string }) => , []); - const callsIcon = useCallback(({ color }: { color: string }) => , []); - const contactsIcon = useCallback(({ color }: { color: string }) => , []); - const notesIcon = useCallback(({ color }: { color: string }) => , []); - const routesIcon = useCallback(({ color }: { color: string }) => , []); - const weatherAlertsIcon = useCallback(({ color }: { color: string }) => , []); - const protocolsIcon = useCallback(({ color }: { color: string }) => , []); - const settingsIcon = useCallback(({ color }: { color: string }) => , []); + const mapIcon = useCallback(({ color }: { color: ColorValue }) => , []); + const callsIcon = useCallback(({ color }: { color: ColorValue }) => , []); + const contactsIcon = useCallback(({ color }: { color: ColorValue }) => , []); + const notesIcon = useCallback(({ color }: { color: ColorValue }) => , []); + const routesIcon = useCallback(({ color }: { color: ColorValue }) => , []); + const weatherAlertsIcon = useCallback(({ color }: { color: ColorValue }) => , []); + const protocolsIcon = useCallback(({ color }: { color: ColorValue }) => , []); + const settingsIcon = useCallback(({ color }: { color: ColorValue }) => , []); // Memoize header left/right renders const headerLeftMap = useCallback(() => , [isLandscape]); @@ -619,7 +619,7 @@ const styles = StyleSheet.create({ backgroundColor: Platform.OS === 'web' ? '#ffffff' : undefined, }, loadingOverlay: { - ...StyleSheet.absoluteFillObject, + ...(StyleSheet.absoluteFill as object), justifyContent: 'center', alignItems: 'center', backgroundColor: Platform.OS === 'web' ? '#ffffff' : 'rgba(255,255,255,0.95)', diff --git a/src/app/(app)/calls.tsx b/src/app/(app)/calls.tsx index 62492e9c..9cfcf31b 100644 --- a/src/app/(app)/calls.tsx +++ b/src/app/(app)/calls.tsx @@ -1,4 +1,4 @@ -import { useFocusEffect } from '@react-navigation/native'; +import { useFocusEffect } from 'expo-router'; import { router } from 'expo-router'; import { PlusIcon, RefreshCcwDotIcon, Search, X } from 'lucide-react-native'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; diff --git a/src/app/(app)/index.tsx b/src/app/(app)/index.tsx index fdcc6b33..851d4c72 100644 --- a/src/app/(app)/index.tsx +++ b/src/app/(app)/index.tsx @@ -570,7 +570,7 @@ function MapContent() { {locationLatitude != null && locationLongitude != null ? ( - + {locationHeading != null ? ( @@ -745,18 +745,10 @@ const styles = StyleSheet.create({ alignItems: 'center', // elevation and shadow properties are handled by themedStyles }, - // Web-only CSS pulse animation (replaces Animated.loop which falls back to JS driver on web) + // Web-only CSS pulse animation (replaces Animated.loop which falls back to JS driver on web). + // Applied via the global `.pulse-ring` CSS class — react-native-web rejects + // `animationName` as an inline style property. markerPulseWeb: { // No JS-driven transform on web — the outer ring animates via CSS instead } as any, - markerOuterRingPulseWeb: - Platform.OS === 'web' - ? { - // @ts-ignore — web-only CSS animation properties - animationName: 'pulse-ring', - animationDuration: '2s', - animationIterationCount: 'infinite', - animationTimingFunction: 'ease-in-out', - } - : ({} as any), }); diff --git a/src/app/(app)/weather-alerts.tsx b/src/app/(app)/weather-alerts.tsx index f707e054..4f5ab76c 100644 --- a/src/app/(app)/weather-alerts.tsx +++ b/src/app/(app)/weather-alerts.tsx @@ -1,4 +1,4 @@ -import { useFocusEffect } from '@react-navigation/native'; +import { useFocusEffect } from 'expo-router'; import { router } from 'expo-router'; import { CloudOff, RefreshCcwDotIcon, Search, X } from 'lucide-react-native'; import React, { useCallback, useMemo, useState } from 'react'; diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index 8a045ee4..5b57ce0d 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -9,15 +9,15 @@ import { Env } from '@env'; import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; import { registerGlobals } from '@livekit/react-native'; import notifee from '@notifee/react-native'; -import { createNavigationContainerRef, DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; import type { EventHint } from '@sentry/core'; import type { ErrorEvent, StackFrame } from '@sentry/react-native'; import * as Sentry from '@sentry/react-native'; import { isRunningInExpoGo } from 'expo'; -import { Stack, useNavigationContainerRef } from 'expo-router'; +import { DarkTheme, DefaultTheme, Stack, ThemeProvider, useNavigationContainerRef } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; +import { useColorScheme } from 'nativewind'; import React, { useEffect } from 'react'; -import { LogBox, Platform, useColorScheme } from 'react-native'; +import { LogBox, Platform } from 'react-native'; import FlashMessage from 'react-native-flash-message'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { KeyboardProvider } from 'react-native-keyboard-controller'; @@ -39,7 +39,6 @@ import { uuidv4 } from '@/lib/utils'; import { appInitializationService } from '@/services/app-initialization.service'; export { ErrorBoundary } from 'expo-router'; -export const navigationRef = createNavigationContainerRef(); export const unstable_settings = { initialRouteName: '(app)', @@ -202,7 +201,9 @@ function RootLayout() { } function Providers({ children }: { children: React.ReactNode }) { - const colorScheme = useColorScheme(); + // nativewind's hook (not react-native's) so manually selected themes are + // reflected on web, where Appearance only tracks the system preference. + const { colorScheme } = useColorScheme(); const renderContent = () => ( diff --git a/src/app/call/[id].tsx b/src/app/call/[id].tsx index 90f319a4..45410ba9 100644 --- a/src/app/call/[id].tsx +++ b/src/app/call/[id].tsx @@ -353,7 +353,7 @@ export default function CallDetail() { title: t('call_detail.tabs.info'), icon: , content: ( - + {t('call_detail.priority')} @@ -558,7 +558,7 @@ export default function CallDetail() { /> {/* Header */} - + {call.Name} ({call.Number}) @@ -604,7 +604,7 @@ export default function CallDetail() { ) : null} {/* Action Buttons */} - +