From 4bf4b2121945948dd7fb80a15c9c95aa9534be44 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 14 Sep 2026 16:11:28 -0300 Subject: [PATCH 001/120] chore: replace react-native-restart with expo reloadAppAsync --- app/externalModules.d.ts | 1 - app/views/LanguageView/index.tsx | 4 ++-- ios/Podfile.lock | 6 ------ package.json | 1 - pnpm-lock.yaml | 14 -------------- 5 files changed, 2 insertions(+), 24 deletions(-) diff --git a/app/externalModules.d.ts b/app/externalModules.d.ts index 4935ca2295f..caada7163d1 100644 --- a/app/externalModules.d.ts +++ b/app/externalModules.d.ts @@ -1,6 +1,5 @@ declare module 'remove-markdown'; declare module 'react-native-mime-types'; -declare module 'react-native-restart'; declare module 'react-native-math-view'; declare module '@env' { export const RUNNING_E2E_TESTS: string; diff --git a/app/views/LanguageView/index.tsx b/app/views/LanguageView/index.tsx index e2bc2c6157f..20f171eb830 100644 --- a/app/views/LanguageView/index.tsx +++ b/app/views/LanguageView/index.tsx @@ -1,6 +1,6 @@ +import { reloadAppAsync } from 'expo'; import { useLayoutEffect } from 'react'; import { FlatList } from 'react-native'; -import RNRestart from 'react-native-restart'; import { useDispatch } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; @@ -53,7 +53,7 @@ const LanguageView = () => { await Promise.all([changeLanguage(language), new Promise(resolve => setTimeout(resolve, 300))]); if (shouldRestart) { - await RNRestart.Restart(); + await reloadAppAsync('Language RTL change'); } else { dispatch(appStart({ root: RootEnum.ROOT_INSIDE })); } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e3964b3b425..4b1c5a010a7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2231,8 +2231,6 @@ PODS: - Yoga - react-native-netinfo (11.4.1): - React-Core - - react-native-restart (0.0.22): - - React-Core - react-native-safe-area-context (5.6.2): - boost - DoubleConversion @@ -3618,7 +3616,6 @@ DEPENDENCIES: - react-native-external-keyboard (from `../node_modules/react-native-external-keyboard`) - react-native-keyboard-controller (from `../node_modules/react-native-keyboard-controller`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - - react-native-restart (from `../node_modules/react-native-restart`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - "react-native-slider (from `../node_modules/@react-native-community/slider`)" - react-native-webrtc (from `../node_modules/react-native-webrtc`) @@ -3856,8 +3853,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-keyboard-controller" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" - react-native-restart: - :path: "../node_modules/react-native-restart" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" react-native-slider: @@ -4069,7 +4064,6 @@ SPEC CHECKSUMS: react-native-external-keyboard: db51d3a78d68c1f87fef41c67353a7c0ddbe51dd react-native-keyboard-controller: c4ca61f44d66c2f8987a7e67e9b78e80dc965c45 react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 - react-native-restart: f6f591aeb40194c41b9b5013901f00e6cf7d0f29 react-native-safe-area-context: 0a3b034bb63a5b684dd2f5fffd3c90ef6ed41ee8 react-native-slider: 6201419b3e3f7c6b7cf2068e0c01274fa86a1da5 react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74 diff --git a/package.json b/package.json index c5731eec7c6..e7c7b059c60 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,6 @@ "react-native-picker-select": "9.0.1", "react-native-popover-view": "5.1.7", "react-native-reanimated": "4.1.3", - "react-native-restart": "0.0.22", "react-native-safe-area-context": "^5.5.2", "react-native-screens": "~4.17.1", "react-native-skeleton-placeholder": "5.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e23de4c7370..e8f03c2a726 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -282,9 +282,6 @@ importers: react-native-reanimated: specifier: 4.1.3 version: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-restart: - specifier: 0.0.22 - version: 0.0.22(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-safe-area-context: specifier: ^5.5.2 version: 5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -6617,12 +6614,6 @@ packages: react-native: '*' react-native-worklets: '>=0.5.0' - react-native-restart@0.0.22: - resolution: {integrity: sha512-XwCqAMAKsO8yCM3xACPFKvkDQZe41lcavOuO0gUEu803IuTLtciualCq/qs83ryRDCDh1jkXYRqFjsGjLMCN3Q==} - peerDependencies: - react: '*' - react-native: '*' - react-native-safe-area-context@5.6.2: resolution: {integrity: sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==} peerDependencies: @@ -15133,11 +15124,6 @@ snapshots: react-native-worklets: 0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) semver: 7.7.2 - react-native-restart@0.0.22(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): - dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 From b36586c87637d13a54ba01a1d75f9451f1578626 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 14 Sep 2026 16:12:44 -0300 Subject: [PATCH 002/120] chore: replace react-native-background-timer with setInterval --- app/lib/methods/videoConfTimer.ts | 12 ++++-------- ios/Podfile.lock | 6 ------ package.json | 2 -- pnpm-lock.yaml | 20 -------------------- 4 files changed, 4 insertions(+), 36 deletions(-) diff --git a/app/lib/methods/videoConfTimer.ts b/app/lib/methods/videoConfTimer.ts index 741204d06f0..a1ba213a962 100644 --- a/app/lib/methods/videoConfTimer.ts +++ b/app/lib/methods/videoConfTimer.ts @@ -1,18 +1,15 @@ -import BackgroundTimer from 'react-native-background-timer'; - import { updateJitsiTimeout } from '../services/restApi'; -let interval: number | null = null; +let interval: ReturnType | null = null; export const initVideoConfTimer = (rid: string): void => { if (rid) { updateJitsiTimeout(rid).catch((e: unknown) => console.log(e)); if (interval) { - BackgroundTimer.clearInterval(interval); - BackgroundTimer.stopBackgroundTimer(); + clearInterval(interval); interval = null; } - interval = BackgroundTimer.setInterval(() => { + interval = setInterval(() => { updateJitsiTimeout(rid).catch((e: unknown) => console.log(e)); }, 10000); } @@ -20,8 +17,7 @@ export const initVideoConfTimer = (rid: string): void => { export const endVideoConfTimer = (): void => { if (interval) { - BackgroundTimer.clearInterval(interval); + clearInterval(interval); interval = null; - BackgroundTimer.stopBackgroundTimer(); } }; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4b1c5a010a7..c71f63e5585 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2112,8 +2112,6 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-background-timer (2.4.1): - - React-Core - react-native-cameraroll (7.10.2): - boost - DoubleConversion @@ -3610,7 +3608,6 @@ DEPENDENCIES: - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - react-native-a11y-order (from `../node_modules/react-native-a11y-order`) - - react-native-background-timer (from `../node_modules/react-native-background-timer`) - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)" - "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)" - react-native-external-keyboard (from `../node_modules/react-native-external-keyboard`) @@ -3841,8 +3838,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" react-native-a11y-order: :path: "../node_modules/react-native-a11y-order" - react-native-background-timer: - :path: "../node_modules/react-native-background-timer" react-native-cameraroll: :path: "../node_modules/@react-native-camera-roll/camera-roll" react-native-cookies: @@ -4058,7 +4053,6 @@ SPEC CHECKSUMS: React-Mapbuffer: 017336879e2e0fb7537bbc08c24f34e2384c9260 React-microtasksnativemodule: 63ee6730cec233feab9cdcc0c100dc28a12e4165 react-native-a11y-order: 20b33ff538af60d8b8a7f7284115a7febc9034d4 - react-native-background-timer: 4638ae3bee00320753647900b21260b10587b6f7 react-native-cameraroll: ae31ba7ee17d31c5ee05d7bb248742e3af482783 react-native-cookies: d648ab7025833b977c0b19e142503034f5f29411 react-native-external-keyboard: db51d3a78d68c1f87fef41c67353a7c0ddbe51dd diff --git a/package.json b/package.json index e7c7b059c60..a134e484617 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,6 @@ "react-native": "0.81.5", "react-native-a11y-order": "^0.4.0", "react-native-animatable": "1.3.3", - "react-native-background-timer": "2.4.1", "react-native-bootsplash": "^6.3.8", "react-native-callkeep": "4.3.16", "react-native-console-time-polyfill": "1.2.3", @@ -185,7 +184,6 @@ "@types/lodash": "^4.14.188", "@types/node": "25.0.3", "@types/react": "~19.1.0", - "@types/react-native-background-timer": "^2.0.2", "@types/semver": "7.3.13", "@types/ua-parser-js": "^0.7.36", "@types/url-parse": "^1.4.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e8f03c2a726..b83a8b3429e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -207,9 +207,6 @@ importers: react-native-animatable: specifier: 1.3.3 version: 1.3.3 - react-native-background-timer: - specifier: 2.4.1 - version: 2.4.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) react-native-bootsplash: specifier: ^6.3.8 version: 6.3.11(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -439,9 +436,6 @@ importers: '@types/react': specifier: ~19.1.0 version: 19.1.17 - '@types/react-native-background-timer': - specifier: ^2.0.2 - version: 2.0.2 '@types/semver': specifier: 7.3.13 version: 7.3.13 @@ -2903,9 +2897,6 @@ packages: '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - '@types/react-native-background-timer@2.0.2': - resolution: {integrity: sha512-cMAep0M5yqUHjiiRPvGiviqiJYdI45KSjbI5ufsIFSQGFwHwrHJC/8yawNhy0G3Gix6fufWLsEj6jC5niUNHiQ==} - '@types/react@17.0.90': resolution: {integrity: sha512-P9beVR/x06U9rCJzSxtENnOr4BrbJ6VrsrDTc+73TtHv9XHhryXKbjGRB+6oooB2r0G/pQkD/S4dHo/7jUfwFw==} @@ -6451,11 +6442,6 @@ packages: react-native-animatable@1.3.3: resolution: {integrity: sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==} - react-native-background-timer@2.4.1: - resolution: {integrity: sha512-TE4Kiy7jUyv+hugxDxitzu38sW1NqjCk4uE5IgU2WevLv7sZacaBc6PZKOShNRPGirLl1NWkaG3LDEkdb9Um5g==} - peerDependencies: - react-native: '>=0.47.0' - react-native-bootsplash@6.3.11: resolution: {integrity: sha512-VA102LDBPyqMJzRtIb5H6MWsKSSpjzW49e34E2kpOivmkYllLlOpkpB/Q6Yvrt4u3EbkgOxB5gerJERhvcO4Tg==} hasBin: true @@ -10897,8 +10883,6 @@ snapshots: '@types/prop-types@15.7.15': {} - '@types/react-native-background-timer@2.0.2': {} - '@types/react@17.0.90': dependencies: '@types/prop-types': 15.7.15 @@ -14950,10 +14934,6 @@ snapshots: dependencies: prop-types: 15.8.1 - react-native-background-timer@2.4.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): - dependencies: - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-bootsplash@6.3.11(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: '@expo/config-plugins': 10.1.2 From 4b1f58bc510ef3fcc3c138ec976ca54e1c9b02c8 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 14 Sep 2026 16:14:36 -0300 Subject: [PATCH 003/120] chore: bump react-native-localize to 3.7.0 --- __mocks__/react-native-localize.js | 2 +- app/i18n/index.ts | 2 +- ios/Podfile.lock | 30 ++++++++++++++++++++++++++++-- package.json | 2 +- pnpm-lock.yaml | 23 +++++++++++++++++------ 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/__mocks__/react-native-localize.js b/__mocks__/react-native-localize.js index 2800ee9024e..94e4c6a2551 100644 --- a/__mocks__/react-native-localize.js +++ b/__mocks__/react-native-localize.js @@ -1,2 +1,2 @@ export const initialConstants = null; -export const findBestAvailableLanguage = () => null; +export const findBestLanguageTag = () => null; diff --git a/app/i18n/index.ts b/app/i18n/index.ts index 91189b48d42..0015c925882 100644 --- a/app/i18n/index.ts +++ b/app/i18n/index.ts @@ -182,7 +182,7 @@ export const setLanguage = (l: string) => { i18n.translations = { en: translations.en?.() }; const defaultLanguage = { languageTag: 'en', isRTL: false }; const availableLanguages = Object.keys(translations); -const { languageTag } = RNLocalize.findBestAvailableLanguage(availableLanguages) || defaultLanguage; +const { languageTag } = RNLocalize.findBestLanguageTag(availableLanguages) || defaultLanguage; // @ts-ignore i18n.isTranslated = (text?: string) => text in englishJson; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c71f63e5585..ebdb78c97e3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3187,8 +3187,34 @@ PODS: - SocketRocket - TOCropViewController (~> 2.8.0) - Yoga - - RNLocalize (2.1.1): + - RNLocalize (3.7.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga - RNReanimated (4.1.3): - boost - DoubleConversion @@ -4107,7 +4133,7 @@ SPEC CHECKSUMS: RNFileViewer: f9424017fa643c115c1444e11292e84fb16ddd68 RNGestureHandler: b8d2e75c2e88fc2a1f6be3b3beeeed80b88fa37d RNImageCropPicker: 5432d75a3bc7209dc8b6ab189fcfe19bd6339fb9 - RNLocalize: ca86348d88b9a89da0e700af58d428ab3f343c4e + RNLocalize: c46dd4008dc8990a098fcafd6ee051122a1ccfda RNReanimated: e1690cdd7f215cfb96a3b7986b81889867dfdb4f RNScreens: ccfcc2f7d9c0d458b7fc41b3f4f0bea054602b3a RNSVG: 94a1be05fab4043354bcf7104f0f9b0e2231ef05 diff --git a/package.json b/package.json index a134e484617..8e4d4a39565 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "react-native-katex": "git+https://github.com/RocketChat/react-native-katex.git", "react-native-keyboard-controller": "1.18.5", "react-native-linear-gradient": "2.6.2", - "react-native-localize": "2.1.1", + "react-native-localize": "3.7.0", "react-native-math-view": "3.9.5", "react-native-mime-types": "2.3.0", "react-native-mmkv": "^4.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b83a8b3429e..c58dd5c9806 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -250,8 +250,8 @@ importers: specifier: 2.6.2 version: 2.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-localize: - specifier: 2.1.1 - version: 2.1.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + specifier: 3.7.0 + version: 3.7.0(@expo/config-plugins@54.0.4)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-math-view: specifier: 3.9.5 version: 3.9.5(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) @@ -6543,10 +6543,18 @@ packages: react: '*' react-native: '*' - react-native-localize@2.1.1: - resolution: {integrity: sha512-+uyz2/b0vyLq19fcb7r1qU1gqmzbp3aC6EMTvOVXwfBuiN+aJXR/fDdFOJJ8D7+bLccKeuS2zBDrazh+ZayX/g==} + react-native-localize@3.7.0: + resolution: {integrity: sha512-6Ohx+zZzycC6zhNVBGM/u1U+O6Ww29YIFseeyXqsKcO/pTfjLcdE40IUJF4SVVwrdh00IMJwy90HjLGUaeqK7Q==} peerDependencies: - react-native: '>=0.60.0' + '@expo/config-plugins': '*' + react: '*' + react-native: '*' + react-native-macos: '*' + peerDependenciesMeta: + '@expo/config-plugins': + optional: true + react-native-macos: + optional: true react-native-math-view@3.9.5: resolution: {integrity: sha512-UJxrisNafszfqIW+utoSDylb72SkZ92cKz1IfE5Dm0s+uIaHxOxepF2DdRbktAV8c0FEFllnXfErcGdh8sfIBw==} @@ -15044,9 +15052,12 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-localize@2.1.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + react-native-localize@3.7.0(@expo/config-plugins@54.0.4)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: + react: 19.1.0 react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + optionalDependencies: + '@expo/config-plugins': 54.0.4 react-native-math-view@3.9.5(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): dependencies: From 22c118219a825fb18dcb47611875551b69a8050c Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 14 Sep 2026 16:16:41 -0300 Subject: [PATCH 004/120] chore: replace react-native-file-viewer with expo-quick-look --- app/lib/methods/helpers/fileDownload.ts | 7 ++----- ios/Podfile.lock | 12 +++++------ jest.setup.js | 4 ++-- package.json | 2 +- pnpm-lock.yaml | 28 ++++++++++++++----------- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/app/lib/methods/helpers/fileDownload.ts b/app/lib/methods/helpers/fileDownload.ts index 6cbf3fcac8c..3eb13342a5a 100644 --- a/app/lib/methods/helpers/fileDownload.ts +++ b/app/lib/methods/helpers/fileDownload.ts @@ -1,5 +1,5 @@ import * as FileSystem from 'expo-file-system/legacy'; -import FileViewer from 'react-native-file-viewer'; +import ExpoQuickLook from '@magrinj/expo-quick-look'; import { LISTENER } from '~/containers/Toast'; import { type IAttachment } from '~/definitions'; @@ -38,10 +38,7 @@ export const fileDownloadAndPreview = async (url: string, attachment: IAttachmen } } - await FileViewer.open(file, { - showOpenWithDialog: true, - showAppsSuggestions: true - }); + await ExpoQuickLook.previewFile({ uri: file }); } catch (e) { EventEmitter.emit(LISTENER, { message: i18n.t('Error_Download_file') }); } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ebdb78c97e3..65503ee5460 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -129,6 +129,8 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga + - ExpoQuickLook (0.4.0): + - ExpoModulesCore - ExpoSystemUI (6.0.9): - ExpoModulesCore - ExpoVideoThumbnails (10.0.8): @@ -3095,8 +3097,6 @@ PODS: - FirebaseCoreExtension - React-Core - RNFBApp - - RNFileViewer (2.1.4): - - React-Core - RNGestureHandler (2.28.0): - boost - DoubleConversion @@ -3586,6 +3586,7 @@ DEPENDENCIES: - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - ExpoLocalAuthentication (from `../node_modules/expo-local-authentication/ios`) - ExpoModulesCore (from `../node_modules/expo-modules-core`) + - "ExpoQuickLook (from `../node_modules/@magrinj/expo-quick-look/ios`)" - ExpoSystemUI (from `../node_modules/expo-system-ui/ios`) - ExpoVideoThumbnails (from `../node_modules/expo-video-thumbnails/ios`) - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) @@ -3685,7 +3686,6 @@ DEPENDENCIES: - "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)" - "RNFBApp (from `../node_modules/@react-native-firebase/app`)" - "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)" - - RNFileViewer (from `../node_modules/react-native-file-viewer`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`) - RNLocalize (from `../node_modules/react-native-localize`) @@ -3773,6 +3773,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-local-authentication/ios" ExpoModulesCore: :path: "../node_modules/expo-modules-core" + ExpoQuickLook: + :path: "../node_modules/@magrinj/expo-quick-look/ios" ExpoSystemUI: :path: "../node_modules/expo-system-ui/ios" ExpoVideoThumbnails: @@ -3966,8 +3968,6 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-firebase/app" RNFBCrashlytics: :path: "../node_modules/@react-native-firebase/crashlytics" - RNFileViewer: - :path: "../node_modules/react-native-file-viewer" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" RNImageCropPicker: @@ -4013,6 +4013,7 @@ SPEC CHECKSUMS: ExpoKeepAwake: 55f75eca6499bb9e4231ebad6f3e9cb8f99c0296 ExpoLocalAuthentication: 8a31808565da7af926dd9b595e98594d8b1553b6 ExpoModulesCore: 91a57f1d109cf53fe58d44fcf6b68a777561549a + ExpoQuickLook: 7ac65cd559a74619ea41e13df20e9d7a61e79ea6 ExpoSystemUI: 2ad325f361a2fcd96a464e8574e19935c461c9cc ExpoVideoThumbnails: 503a79271416c8723f04b55ea4737282513ebe4f ExpoWebBrowser: 17b064c621789e41d4816c95c93f429b84971f52 @@ -4130,7 +4131,6 @@ SPEC CHECKSUMS: RNFBAnalytics: 2e8b8ffcd2bb3d59a43ecbe09571c73c56edef7a RNFBApp: a448037d2df74af9d374a0b765be12ff1e844dc0 RNFBCrashlytics: c3bb5533f9957eddc88f3ea383583309a4ce9f89 - RNFileViewer: f9424017fa643c115c1444e11292e84fb16ddd68 RNGestureHandler: b8d2e75c2e88fc2a1f6be3b3beeeed80b88fa37d RNImageCropPicker: 5432d75a3bc7209dc8b6ab189fcfe19bd6339fb9 RNLocalize: c46dd4008dc8990a098fcafd6ee051122a1ccfda diff --git a/jest.setup.js b/jest.setup.js index c5815af3787..4243cbabb12 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -49,8 +49,8 @@ jest.mock('react-native-reanimated', () => { jest.mock('@react-native-clipboard/clipboard', () => mockClipboard); -jest.mock('react-native-file-viewer', () => ({ - open: jest.fn(() => null) +jest.mock('@magrinj/expo-quick-look', () => ({ + previewFile: jest.fn(() => Promise.resolve()) })); jest.mock('react-native-incall-manager', () => ({ diff --git a/package.json b/package.json index 8e4d4a39565..b8b3ac24c10 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@expo/vector-icons": "^15.0.3", "@hookform/resolvers": "^2.9.10", "@lodev09/react-native-true-sheet": "~3.7.0", + "@magrinj/expo-quick-look": "0.4.0", "@nozbe/watermelondb": "^0.28.1-0", "@react-native-async-storage/async-storage": "2.2.0", "@react-native-camera-roll/camera-roll": "^7.10.0", @@ -98,7 +99,6 @@ "react-native-easy-grid": "0.2.2", "react-native-easy-toast": "2.3.0", "react-native-external-keyboard": "^0.9.0", - "react-native-file-viewer": "2.1.4", "react-native-gesture-handler": "~2.28.0", "react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker#47092e8c90550a54d45fe307f7cb2a24c9535ed5", "react-native-incall-manager": "^4.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c58dd5c9806..0b122ee8a96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ importers: '@lodev09/react-native-true-sheet': specifier: ~3.7.0 version: 3.7.3(261280a80c394a7dcf8d9609d62c37ca) + '@magrinj/expo-quick-look': + specifier: 0.4.0 + version: 0.4.0(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@nozbe/watermelondb': specifier: ^0.28.1-0 version: 0.28.1-0 @@ -228,9 +231,6 @@ importers: react-native-external-keyboard: specifier: ^0.9.0 version: 0.9.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-file-viewer: - specifier: 2.1.4 - version: 2.1.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) react-native-gesture-handler: specifier: ~2.28.0 version: 2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -2019,6 +2019,13 @@ packages: react-native-worklets: optional: true + '@magrinj/expo-quick-look@0.4.0': + resolution: {integrity: sha512-7otvYguE9gjhXqvQGrTKmJIRDY/t4o3bx2tfMcUZDb4hQprLuoRbzmtxDpyNCVkGmIXZKTeg7YxGMVa1BGpbGA==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -6493,11 +6500,6 @@ packages: react: '*' react-native: '*' - react-native-file-viewer@2.1.4: - resolution: {integrity: sha512-G3ko9lmqxT+lWhsDNy2K3Jes6xSMsUvlYwuwnRCNk2wC6hgYMeoeaiwDt8R3CdON781hB6Ej1eu3ir1QATtHXg==} - peerDependencies: - react-native: '>=0.47' - react-native-gesture-handler@2.28.0: resolution: {integrity: sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==} peerDependencies: @@ -9912,6 +9914,12 @@ snapshots: react-native-reanimated: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-worklets: 0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@magrinj/expo-quick-look@0.4.0(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + dependencies: + expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.8.0 @@ -15008,10 +15016,6 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-file-viewer@2.1.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): - dependencies: - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: '@egjs/hammerjs': 2.0.17 From 3230b78416071331677ece443b26eaac9013b42d Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 15 Sep 2026 10:36:55 -0300 Subject: [PATCH 005/120] chore: migrate from expo-av to expo-audio and expo-video (#6719) --- app/containers/AudioPlayer/Seek.test.tsx | 44 +++ app/containers/AudioPlayer/Seek.tsx | 21 +- app/containers/AudioPlayer/index.test.tsx | 84 +++--- app/containers/AudioPlayer/index.tsx | 37 +-- .../MessageComposer/MessageComposer.test.tsx | 34 ++- .../components/Buttons/MicOrSendButton.tsx | 31 ++- .../components/RecordAudio/Duration.tsx | 28 +- .../components/RecordAudio/RecordAudio.tsx | 64 ++--- app/containers/Ringer/index.tsx | 33 +-- .../__snapshots__/index.test.tsx.snap | 16 ++ app/containers/VideoPlayer/index.stories.tsx | 20 ++ app/containers/VideoPlayer/index.test.tsx | 90 +++++++ app/containers/VideoPlayer/index.tsx | 71 +++++ app/containers/VideoPlayer/styles.ts | 7 + app/i18n/locales/ar.json | 2 + app/i18n/locales/bn-IN.json | 2 + app/i18n/locales/cs.json | 2 + app/i18n/locales/de.json | 2 + app/i18n/locales/en.json | 3 +- app/i18n/locales/es.json | 2 + app/i18n/locales/fi.json | 2 + app/i18n/locales/fr.json | 2 + app/i18n/locales/hi-IN.json | 2 + app/i18n/locales/hu.json | 2 + app/i18n/locales/it.json | 2 + app/i18n/locales/ja.json | 2 + app/i18n/locales/nl.json | 2 + app/i18n/locales/nn.json | 2 + app/i18n/locales/no.json | 3 +- app/i18n/locales/pt-BR.json | 3 +- app/i18n/locales/pt-PT.json | 2 + app/i18n/locales/ru.json | 2 + app/i18n/locales/sl-SI.json | 2 + app/i18n/locales/sv.json | 2 + app/i18n/locales/ta-IN.json | 2 + app/i18n/locales/te-IN.json | 2 + app/i18n/locales/tr.json | 2 + app/i18n/locales/zh-CN.json | 2 + app/i18n/locales/zh-TW.json | 2 + app/lib/constants/audio.ts | 61 ++--- app/lib/methods/AudioManager.test.ts | 255 ++++++++++++++++++ app/lib/methods/AudioManager.ts | 146 +++++++--- app/lib/native/NativeVoip.ts | 2 +- .../services/voip/playCallEndedSound.test.ts | 164 +++++------ app/lib/services/voip/playCallEndedSound.ts | 24 +- app/sagas/__tests__/deepLinking.test.ts | 2 +- app/views/AttachmentView.test.tsx | 5 - app/views/AttachmentView.tsx | 41 +-- .../components/Dialpad/DialpadContext.tsx | 33 +-- app/views/CallView/index.tsx | 26 +- app/views/ShareView/Preview.tsx | 41 ++- ios/Podfile.lock | 19 +- jest.setup.js | 151 +++-------- package.json | 3 +- patches/expo-audio+0.4.9.patch | 24 ++ pnpm-lock.yaml | 32 ++- 56 files changed, 1117 insertions(+), 545 deletions(-) create mode 100644 app/containers/AudioPlayer/Seek.test.tsx create mode 100644 app/containers/VideoPlayer/__snapshots__/index.test.tsx.snap create mode 100644 app/containers/VideoPlayer/index.stories.tsx create mode 100644 app/containers/VideoPlayer/index.test.tsx create mode 100644 app/containers/VideoPlayer/index.tsx create mode 100644 app/containers/VideoPlayer/styles.ts create mode 100644 app/lib/methods/AudioManager.test.ts create mode 100644 patches/expo-audio+0.4.9.patch diff --git a/app/containers/AudioPlayer/Seek.test.tsx b/app/containers/AudioPlayer/Seek.test.tsx new file mode 100644 index 00000000000..ce7668c55a7 --- /dev/null +++ b/app/containers/AudioPlayer/Seek.test.tsx @@ -0,0 +1,44 @@ +import { act, render, renderHook } from '@testing-library/react-native'; +import { Gesture, State } from 'react-native-gesture-handler'; +import { fireGestureHandler } from 'react-native-gesture-handler/jest-utils'; +import { useSharedValue } from 'react-native-reanimated'; + +import Seek from './Seek'; + +jest.mock('~/theme', () => ({ useTheme: () => ({ colors: {} }) })); + +const setup = () => { + const pan = jest.spyOn(Gesture, 'Pan'); + const { result } = renderHook(() => ({ currentTime: useSharedValue(30), duration: useSharedValue(120) })); + const { currentTime, duration } = result.current; + const onChangeTime = jest.fn(); + render(); + return { gesture: pan.mock.results[0].value, currentTime, onChangeTime }; +}; + +afterEach(() => jest.restoreAllMocks()); + +test('a tap that never starts dragging preserves the paused position', () => { + const { gesture, currentTime, onChangeTime } = setup(); + act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.FAILED }])); + expect(currentTime.value).toBe(30); + expect(onChangeTime).not.toHaveBeenCalled(); +}); + +test('a cancelled drag restores its starting position without seeking', () => { + const { gesture, currentTime, onChangeTime } = setup(); + act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.ACTIVE }, { state: State.CANCELLED }])); + expect(currentTime.value).toBe(30); + expect(onChangeTime).not.toHaveBeenCalled(); + + currentTime.value = 45; + act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.FAILED }])); + expect(currentTime.value).toBe(45); +}); + +test('a completed drag seeks in seconds', () => { + const { gesture, onChangeTime } = setup(); + act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.ACTIVE }, { state: State.END }])); + expect(onChangeTime).toHaveBeenCalledTimes(1); + expect(onChangeTime).toHaveBeenCalledWith(30); +}); diff --git a/app/containers/AudioPlayer/Seek.tsx b/app/containers/AudioPlayer/Seek.tsx index 929f0b8bc78..ebef805872b 100644 --- a/app/containers/AudioPlayer/Seek.tsx +++ b/app/containers/AudioPlayer/Seek.tsx @@ -20,7 +20,7 @@ interface ISeek { duration: SharedValue; currentTime: SharedValue; loaded: boolean; - onChangeTime: (time: number) => Promise; + onChangeTime: (time: number) => void; } function clamp(value: number, min: number, max: number) { @@ -49,6 +49,8 @@ const Seek = ({ currentTime, duration, loaded = false, onChangeTime }: ISeek) => const scale = useSharedValue(1); const isPanning = useSharedValue(false); const contextX = useSharedValue(0); + const savedTranslateX = useSharedValue(0); + const savedCurrentTime = useSharedValue(0); const styleLine = useAnimatedStyle(() => ({ width: translateX.value @@ -69,23 +71,24 @@ const Seek = ({ currentTime, duration, loaded = false, onChangeTime }: ISeek) => .onStart(() => { isPanning.value = true; contextX.value = translateX.value; + savedTranslateX.value = translateX.value; + savedCurrentTime.value = currentTime.value; scale.value = withTiming(1.3, { duration: 150 }); }) .onUpdate(event => { const newX = contextX.value + event.translationX; translateX.value = clamp(newX, 0, maxWidth.value); }) - .onEnd(() => { - scheduleOnRN(onChangeTime, Math.round(currentTime.value * 1000)); - }) .onFinalize((_, didSucceed) => { - if (isPanning.value && !didSucceed) { - translateX.value = contextX.value; - currentTime.value = (contextX.value * duration.value) / maxWidth.value || 0; - } - + if (!isPanning.value) return; isPanning.value = false; scale.value = withTiming(1, { duration: 150 }); + if (didSucceed) { + scheduleOnRN(onChangeTime, currentTime.value); + } else { + translateX.value = savedTranslateX.value; + currentTime.value = savedCurrentTime.value; + } }); useDerivedValue(() => { diff --git a/app/containers/AudioPlayer/index.test.tsx b/app/containers/AudioPlayer/index.test.tsx index e4166f42a8d..1f28c6a3d2f 100644 --- a/app/containers/AudioPlayer/index.test.tsx +++ b/app/containers/AudioPlayer/index.test.tsx @@ -13,12 +13,12 @@ jest.mock('~/lib/methods/AudioManager', () => ({ default: { loadAudio: jest.fn(), setOnPlaybackStatusUpdate: jest.fn(), - setRateAsync: jest.fn(), + setRate: jest.fn(), addAudioRendered: jest.fn(), removeAudioRendered: jest.fn(), pauseAudio: jest.fn(), playAudio: jest.fn(), - setPositionAsync: jest.fn() + setPositionAsync: jest.fn(() => Promise.resolve()) } })); @@ -40,10 +40,10 @@ jest.mock('~/theme', () => ({ })); const mockTaskCancel = jest.fn(); -let capturedFocusCb: Function | undefined; -let capturedBlurCb: Function | undefined; +let capturedFocusCb: (() => void) | undefined; +let capturedBlurCb: (() => void) | undefined; let mockUnsubscribe: jest.Mock; -let capturedAudioFocusedCb: Function | undefined; +let capturedAudioFocusedCb: ((audioFocused: string) => void) | undefined; const defaultProps = { fileUri: 'file:///audio.mp3', @@ -67,7 +67,7 @@ describe('AudioPlayer', () => { capturedFocusCb = undefined; capturedBlurCb = undefined; mockUnsubscribe = jest.fn(); - const mockAddListener = jest.fn((event: string, cb: Function) => { + const mockAddListener = jest.fn((event: string, cb: () => void) => { if (event === 'focus') capturedFocusCb = cb; if (event === 'blur') capturedBlurCb = cb; return mockUnsubscribe; @@ -75,7 +75,7 @@ describe('AudioPlayer', () => { (useNavigation as jest.Mock).mockReturnValue({ navigate: jest.fn(), addListener: mockAddListener }); capturedAudioFocusedCb = undefined; - (emitter.on as jest.Mock).mockImplementation((event: string, cb: Function) => { + (emitter.on as jest.Mock).mockImplementation((event: string, cb: (audioFocused: string) => void) => { if (event === 'audioFocused') capturedAudioFocusedCb = cb; }); }); @@ -102,7 +102,7 @@ describe('AudioPlayer', () => { await waitFor(() => { expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalledWith('mock-audio-key', expect.any(Function)); - expect(AudioManager.setRateAsync).toHaveBeenCalledWith('mock-audio-key', 1.0); + expect(AudioManager.setRate).toHaveBeenCalledWith('mock-audio-key', 1.0); }); }); @@ -168,7 +168,7 @@ describe('AudioPlayer', () => { expect(() => getByTestId('play-button')).not.toThrow(); }); - it('skips setOnPlaybackStatusUpdate and setRateAsync (with loaded key) when loadAudio throws', async () => { + it('skips setOnPlaybackStatusUpdate and setRate (with loaded key) when loadAudio throws', async () => { (AudioManager.loadAudio as jest.Mock).mockRejectedValue(new Error('load failed')); render(); @@ -176,7 +176,7 @@ describe('AudioPlayer', () => { await act(async () => {}); expect(AudioManager.setOnPlaybackStatusUpdate).not.toHaveBeenCalled(); - expect(AudioManager.setRateAsync).not.toHaveBeenCalledWith('mock-audio-key', expect.any(Number)); + expect(AudioManager.setRate).not.toHaveBeenCalledWith('mock-audio-key', expect.any(Number)); }); }); @@ -273,7 +273,7 @@ describe('AudioPlayer', () => { await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; - act(() => statusCb({ isLoaded: true, isPlaying: true })); + act(() => statusCb({ isLoaded: true, playing: true })); fireEvent.press(getByTestId('play-button')); @@ -299,7 +299,7 @@ describe('AudioPlayer', () => { await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; - act(() => statusCb({ isLoaded: true, isPlaying: true })); + act(() => statusCb({ isLoaded: true, playing: true })); await waitFor(() => expect(getByTestId('play-button').props.accessibilityLabel).toBe('Pause')); }); @@ -310,10 +310,10 @@ describe('AudioPlayer', () => { await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; - act(() => statusCb({ isLoaded: true, isPlaying: true })); + act(() => statusCb({ isLoaded: true, playing: true })); await waitFor(() => expect(getByTestId('play-button').props.accessibilityLabel).toBe('Pause')); - act(() => statusCb({ isLoaded: true, isPlaying: false })); + act(() => statusCb({ isLoaded: true, playing: false })); await waitFor(() => expect(getByTestId('play-button').props.accessibilityLabel).toBe('Play')); }); @@ -324,13 +324,13 @@ describe('AudioPlayer', () => { const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; const currentTimeValue = (useSharedValue as jest.Mock).mock.results[1].value as { value: number }; - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 30000, positionMillis: 1000 })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 30, currentTime: 1 })); expect(currentTimeValue.value).toBe(1); - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 30000, positionMillis: 2000 })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 30, currentTime: 2 })); expect(currentTimeValue.value).toBe(2); - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 30000, positionMillis: 3000 })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 30, currentTime: 3 })); expect(currentTimeValue.value).toBe(3); }); }); @@ -356,7 +356,7 @@ describe('AudioPlayer', () => { await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; - act(() => statusCb({ isLoaded: true, isPlaying: true })); + act(() => statusCb({ isLoaded: true, playing: true })); await waitFor(() => expect(queryByTestId('playback-speed')).not.toBeNull()); }); @@ -383,7 +383,7 @@ describe('AudioPlayer', () => { await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; - act(() => statusCb({ isLoaded: true, isPlaying: true })); + act(() => statusCb({ isLoaded: true, playing: true })); await waitFor(() => expect(activateKeepAwake).toHaveBeenCalled()); }); @@ -466,24 +466,24 @@ describe('AudioPlayer', () => { }); describe('onPlaybackStatusUpdate callback chain', () => { - it('sets paused to false (audioState: playing) when status has isPlaying: true', async () => { + it('sets paused to false (audioState: playing) when status has playing: true', async () => { const { queryByTestId } = render(); await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; - act(() => statusCb({ isLoaded: true, isPlaying: true })); + act(() => statusCb({ isLoaded: true, playing: true })); await waitFor(() => expect(queryByTestId('playback-speed')).not.toBeNull()); }); - it('keeps paused true when status has isPlaying: false', async () => { + it('keeps paused true when status has playing: false', async () => { const { queryByTestId } = render(); await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; - act(() => statusCb({ isLoaded: true, isPlaying: false })); + act(() => statusCb({ isLoaded: true, playing: false })); await act(async () => {}); @@ -500,13 +500,13 @@ describe('AudioPlayer', () => { const durationValue = sharedValueMock.mock.results[0].value as { value: number }; const currentTimeValue = sharedValueMock.mock.results[1].value as { value: number }; - act(() => statusCb({ isLoaded: true, durationMillis: 10000, positionMillis: 3000, isPlaying: false })); + act(() => statusCb({ isLoaded: true, duration: 10, currentTime: 3, playing: false })); expect(durationValue.value).toBe(10); expect(currentTimeValue.value).toBe(3); }); - it('does not update currentTime.value when positionMillis exceeds durationMillis', async () => { + it('does not update currentTime.value when currentTime exceeds duration', async () => { render(); await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); @@ -514,14 +514,14 @@ describe('AudioPlayer', () => { const currentTimeValue = (useSharedValue as jest.Mock).mock.results[1].value as { value: number }; - act(() => statusCb({ isLoaded: true, durationMillis: 5000, positionMillis: 2000, isPlaying: false })); + act(() => statusCb({ isLoaded: true, duration: 5, currentTime: 2, playing: false })); expect(currentTimeValue.value).toBe(2); - act(() => statusCb({ isLoaded: true, durationMillis: 5000, positionMillis: 9000, isPlaying: false })); + act(() => statusCb({ isLoaded: true, duration: 5, currentTime: 9, playing: false })); expect(currentTimeValue.value).toBe(2); }); - it('does not update shared values when durationMillis is absent', async () => { + it('does not update shared values when duration is absent', async () => { render(); await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); @@ -529,7 +529,7 @@ describe('AudioPlayer', () => { const durationValue = (useSharedValue as jest.Mock).mock.results[0].value as { value: number }; - act(() => statusCb({ isLoaded: true, isPlaying: false })); + act(() => statusCb({ isLoaded: true, playing: false })); expect(durationValue.value).toBe(0); }); @@ -542,9 +542,9 @@ describe('AudioPlayer', () => { const currentTimeValue = (useSharedValue as jest.Mock).mock.results[1].value as { value: number }; - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 5000, positionMillis: 4000 })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 5, currentTime: 4 })); - act(() => statusCb({ isLoaded: true, isPlaying: false, didJustFinish: true })); + act(() => statusCb({ isLoaded: true, playing: false, didJustFinish: true, currentTime: 5, duration: 5 })); await waitFor(() => { expect(queryByTestId('playback-speed')).toBeNull(); @@ -560,16 +560,16 @@ describe('AudioPlayer', () => { const currentTimeValue = (useSharedValue as jest.Mock).mock.results[1].value as { value: number }; - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 10000, positionMillis: 0 })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 10, currentTime: 0 })); expect(currentTimeValue.value).toBe(0); - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 10000, positionMillis: 2500 })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 10, currentTime: 2.5 })); expect(currentTimeValue.value).toBe(2.5); - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 10000, positionMillis: 5000 })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 10, currentTime: 5 })); expect(currentTimeValue.value).toBe(5); - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 10000, positionMillis: 7500 })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 10, currentTime: 7.5 })); expect(currentTimeValue.value).toBe(7.5); }); @@ -580,7 +580,7 @@ describe('AudioPlayer', () => { const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; const durationValue = (useSharedValue as jest.Mock).mock.results[0].value as { value: number }; - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 10000, positionMillis: 0 })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 10, currentTime: 0 })); expect(durationValue.value).toBe(10); }); @@ -592,7 +592,7 @@ describe('AudioPlayer', () => { const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; const currentTimeValue = (useSharedValue as jest.Mock).mock.results[1].value as { value: number }; - act(() => statusCb({ isLoaded: true, isPlaying: false, durationMillis: 10000, positionMillis: 3500 })); + act(() => statusCb({ isLoaded: true, playing: false, duration: 10, currentTime: 3.5 })); expect(currentTimeValue.value).toBe(3.5); }); @@ -604,19 +604,19 @@ describe('AudioPlayer', () => { const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; const currentTimeValue = (useSharedValue as jest.Mock).mock.results[1].value as { value: number }; - act(() => statusCb({ isLoaded: true, isPlaying: true, durationMillis: 10000, positionMillis: 7777 })); - act(() => statusCb({ isLoaded: true, isPlaying: false, didJustFinish: true })); + act(() => statusCb({ isLoaded: true, playing: true, duration: 10, currentTime: 7.777 })); + act(() => statusCb({ isLoaded: true, playing: false, didJustFinish: true, currentTime: 10, duration: 10 })); expect(currentTimeValue.value).toBe(0); }); - it('does not crash when status is null', async () => { + it('does not crash or update state when status is not loaded', async () => { render(); await waitFor(() => expect(AudioManager.setOnPlaybackStatusUpdate).toHaveBeenCalled()); const statusCb = (AudioManager.setOnPlaybackStatusUpdate as jest.Mock).mock.calls[0][1] as Function; - act(() => statusCb(null)); + act(() => statusCb({ isLoaded: false })); await act(async () => {}); }); @@ -634,7 +634,7 @@ describe('AudioPlayer', () => { await waitFor(() => expect(AudioManager.playAudio).toHaveBeenCalledWith('mock-audio-key')); act(() => { - statusCb({ isLoaded: true, isPlaying: true, positionMillis: 5000, durationMillis: 30000 }); + statusCb({ isLoaded: true, playing: true, currentTime: 5, duration: 30 }); }); expect(currentTimeValue.value).toBe(5); diff --git a/app/containers/AudioPlayer/index.tsx b/app/containers/AudioPlayer/index.tsx index 544de5850b9..442ae6e3c5b 100644 --- a/app/containers/AudioPlayer/index.tsx +++ b/app/containers/AudioPlayer/index.tsx @@ -1,9 +1,9 @@ import { useEffect, useRef, useState } from 'react'; import { InteractionManager, View } from 'react-native'; -import { type AVPlaybackStatus } from 'expo-av'; import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake'; import { useSharedValue } from 'react-native-reanimated'; import { useNavigation } from '@react-navigation/native'; +import type { AudioStatus } from 'expo-audio'; import { useTheme } from '~/theme'; import styles from './styles'; @@ -16,6 +16,7 @@ import { type TDownloadState } from '~/lib/methods/handleMediaDownload'; import { emitter } from '~/lib/methods/helpers/emitter'; import { type TAudioState } from './types'; import { useUserPreferences } from '~/lib/methods/userPreferences'; +import log from '~/lib/methods/helpers/log'; interface IAudioPlayerProps { fileUri: string; @@ -47,7 +48,7 @@ const AudioPlayer = ({ const audioUri = useRef(''); const navigation = useNavigation(); - const onPlaybackStatusUpdate = (status: AVPlaybackStatus) => { + const onPlaybackStatusUpdate = (status: AudioStatus): void => { if (status) { onPlaying(status); handlePlaybackStatusUpdate(status); @@ -55,37 +56,37 @@ const AudioPlayer = ({ } }; - const onPlaying = (data: AVPlaybackStatus) => { - if (data.isLoaded && data.isPlaying) { + const onPlaying = (data: AudioStatus): void => { + if (data.isLoaded && data.playing) { setPaused(false); } else { setPaused(true); } }; - const handlePlaybackStatusUpdate = (data: AVPlaybackStatus) => { - if (data.isLoaded && data.durationMillis) { - const durationSeconds = data.durationMillis / 1000; - duration.value = durationSeconds > 0 ? durationSeconds : 0; - const currentSecond = data.positionMillis / 1000; - if (currentSecond <= durationSeconds) { - currentTime.value = currentSecond; - } + const handlePlaybackStatusUpdate = (data: AudioStatus): void => { + if (!data.isLoaded) return; + const durationSeconds = data.duration || 0; + duration.value = durationSeconds > 0 ? durationSeconds : 0; + const currentSecond = data.currentTime || 0; + if (currentSecond <= durationSeconds) { + currentTime.value = currentSecond; } }; - const onEnd = (data: AVPlaybackStatus) => { + const onEnd = (data: AudioStatus) => { if (data.isLoaded && data.didJustFinish) { try { setPaused(true); currentTime.value = 0; - } catch { - // do nothing + AudioManager.setPositionAsync(audioUri.current, 0).catch(log); + } catch (error) { + log(error); } } }; - const setPosition = async (time: number) => { + const setPosition = async (time: number): Promise => { await AudioManager.setPositionAsync(audioUri.current, time); }; @@ -102,7 +103,7 @@ const AudioPlayer = ({ }; useEffect(() => { - AudioManager.setRateAsync(audioUri.current, playbackSpeed); + AudioManager.setRate(audioUri.current, playbackSpeed); }, [playbackSpeed]); const onPress = () => { @@ -121,7 +122,7 @@ const AudioPlayer = ({ try { audioUri.current = await AudioManager.loadAudio({ msgId, rid, uri: fileUri }); AudioManager.setOnPlaybackStatusUpdate(audioUri.current, onPlaybackStatusUpdate); - AudioManager.setRateAsync(audioUri.current, playbackSpeed); + AudioManager.setRate(audioUri.current, playbackSpeed); } catch { // do nothing } diff --git a/app/containers/MessageComposer/MessageComposer.test.tsx b/app/containers/MessageComposer/MessageComposer.test.tsx index e470df6bc2c..a1f7acfcec1 100644 --- a/app/containers/MessageComposer/MessageComposer.test.tsx +++ b/app/containers/MessageComposer/MessageComposer.test.tsx @@ -1,6 +1,7 @@ import { useEffect, type ReactElement, type RefObject } from 'react'; -import { act, render, screen, fireEvent, waitFor, userEvent } from '@testing-library/react-native'; +import { act, render, renderHook, screen, fireEvent, waitFor, userEvent } from '@testing-library/react-native'; import { Provider } from 'react-redux'; +import { getRecordingPermissionsAsync, requestRecordingPermissionsAsync, useAudioRecorder, PermissionStatus } from 'expo-audio'; import { MessageComposerContainer } from './MessageComposerContainer'; import { ComposerAttachments } from './components/Attachments/ComposerAttachments'; @@ -23,6 +24,10 @@ import { useMessageComposerApi } from './context'; import { type IMessageComposerRef } from './interfaces'; import { sendFileMessage } from '~/lib/methods/sendFileMessage'; import { runSlashCommand } from '~/lib/services/restApi'; +import log from '~/lib/methods/helpers/log'; +import { RECORDING_SETTINGS } from '~/lib/constants/audio'; + +jest.mock('~/lib/methods/helpers/log', () => ({ __esModule: true, default: jest.fn() })); jest.useFakeTimers(); @@ -734,6 +739,33 @@ describe('MessageComposer', () => { }); describe('Audio', () => { + test('requests undetermined microphone permission before recording', async () => { + jest.mocked(getRecordingPermissionsAsync).mockResolvedValueOnce({ + status: PermissionStatus.UNDETERMINED, + granted: false, + canAskAgain: true, + expires: 'never' + }); + jest.mocked(requestRecordingPermissionsAsync).mockClear(); + render(); + await user.press(screen.getByTestId('message-composer-send-audio')); + expect(requestRecordingPermissionsAsync).toHaveBeenCalled(); + expect(screen.queryByTestId('message-composer-send-audio')).not.toBeOnTheScreen(); + }); + + test('exits recording when recorder preparation fails', async () => { + const error = new Error('Failed to prepare recorder'); + const { result } = renderHook(() => useAudioRecorder(RECORDING_SETTINGS)); + const recorder = result.current; + jest.mocked(recorder.prepareToRecordAsync).mockRejectedValueOnce(error); + jest.mocked(useAudioRecorder).mockReturnValueOnce(recorder); + render(); + await user.press(screen.getByTestId('message-composer-send-audio')); + await waitFor(() => expect(log).toHaveBeenCalledWith(error)); + expect(screen.getByTestId('message-composer-send-audio')).toBeOnTheScreen(); + expect(recorder.record).not.toHaveBeenCalled(); + }); + test('tap record', async () => { render(); expect(screen.getByTestId('message-composer-send-audio')).toBeOnTheScreen(); diff --git a/app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx b/app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx index cfc4a5051cd..24b34428161 100644 --- a/app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx +++ b/app/containers/MessageComposer/components/Buttons/MicOrSendButton.tsx @@ -1,11 +1,11 @@ -import { Audio } from 'expo-av'; +import { requestRecordingPermissionsAsync, getRecordingPermissionsAsync, PermissionStatus } from 'expo-audio'; import { useContext, type ReactElement } from 'react'; import { Alert } from 'react-native'; -import { PermissionStatus } from 'expo-camera'; import i18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { openAppSettings } from '~/lib/methods/helpers/openAppSettings'; +import log from '~/lib/methods/helpers/log'; import { useTheme } from '~/theme'; import { useComposerRid, useComposerSharing } from '~/containers/MessageComposer/ComposerStore'; import { @@ -28,16 +28,27 @@ export const MicOrSendButton = (): ReactElement | null => { const { colors } = useTheme(); const { setRecordingAudio } = useMessageComposerApi(); - const requestPermissionAndStartToRecordAudio = () => - Audio.requestPermissionsAsync() - .then(({ granted }) => setRecordingAudio(granted)) - .catch(() => {}); + const requestPermissionAndStartToRecordAudio = async () => { + try { + const { granted } = await requestRecordingPermissionsAsync(); + setRecordingAudio(granted); + } catch (error) { + log(error); + setRecordingAudio(false); + } + }; const startRecording = async () => { - const { status, granted, canAskAgain } = await Audio.getPermissionsAsync(); - if (granted) return setRecordingAudio(true); - if (status === PermissionStatus.UNDETERMINED) return requestPermissionAndStartToRecordAudio(); - if (canAskAgain) return requestPermissionAndStartToRecordAudio(); + try { + const { status, granted, canAskAgain } = await getRecordingPermissionsAsync(); + if (granted) return setRecordingAudio(true); + if (status === PermissionStatus.UNDETERMINED) return requestPermissionAndStartToRecordAudio(); + if (canAskAgain) return requestPermissionAndStartToRecordAudio(); + } catch (error) { + log(error); + setRecordingAudio(false); + return; + } Alert.alert( i18n.t('Microphone_access_needed_to_record_audio'), diff --git a/app/containers/MessageComposer/components/RecordAudio/Duration.tsx b/app/containers/MessageComposer/components/RecordAudio/Duration.tsx index d8396723cb4..9390b653311 100644 --- a/app/containers/MessageComposer/components/RecordAudio/Duration.tsx +++ b/app/containers/MessageComposer/components/RecordAudio/Duration.tsx @@ -1,32 +1,24 @@ -import { forwardRef, useImperativeHandle, useState } from 'react'; +import { useState } from 'react'; import { type FontVariant, Text } from 'react-native'; -import { type Audio } from 'expo-av'; +import { useAudioRecorderState, type AudioRecorder } from 'expo-audio'; import sharedStyles from '~/views/Styles'; import { useTheme } from '~/theme'; import { formatTime } from './utils'; -export interface IDurationRef { - onRecordingStatusUpdate: (status: Audio.RecordingStatus) => void; -} - -export const Duration = forwardRef((_, ref) => { +export const Duration = ({ audioRecorder }: { audioRecorder: AudioRecorder }) => { const [styles] = useStyle(); + const recorderState = useAudioRecorderState(audioRecorder); const [duration, setDuration] = useState('00:00'); - useImperativeHandle(ref, () => ({ - onRecordingStatusUpdate - })); - - const onRecordingStatusUpdate = (status: Audio.RecordingStatus) => { - if (!status.isRecording) { - return; - } - setDuration(formatTime(Math.floor(status.durationMillis / 1000))); - }; + // Adjust state during render (instead of an effect) to sync the timer with the recorder state + const nextDuration = recorderState.isRecording ? formatTime(Math.floor(recorderState.durationMillis / 1000)) : duration; + if (nextDuration !== duration) { + setDuration(nextDuration); + } return {duration}; -}); +}; function useStyle() { const { colors } = useTheme(); diff --git a/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx b/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx index 773fab2203f..25c6712dcfa 100644 --- a/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx +++ b/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx @@ -1,6 +1,6 @@ import { View, Text } from 'react-native'; -import { useEffect, useRef, useState, type ReactElement } from 'react'; -import { Audio } from 'expo-av'; +import { useEffect, useState, type ReactElement } from 'react'; +import { requestRecordingPermissionsAsync, setAudioModeAsync, useAudioRecorder } from 'expo-audio'; import { getInfoAsync } from 'expo-file-system/legacy'; import { useKeepAwake } from 'expo-keep-awake'; import { shallowEqual } from 'react-redux'; @@ -12,22 +12,21 @@ import sharedStyles from '~/views/Styles'; import { ReviewButton } from './ReviewButton'; import { useMessageComposerApi } from '~/containers/MessageComposer/context'; import { sendFileMessage } from '~/lib/methods/sendFileMessage'; -import { RECORDING_EXTENSION, RECORDING_MODE, RECORDING_SETTINGS } from '~/lib/constants/audio'; +import { RECORDING_EXTENSION, RECORDING_SETTINGS, RECORDING_MODE } from '~/lib/constants/audio'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import log from '~/lib/methods/helpers/log'; import { type IUpload } from '~/definitions'; import { useComposerRid, useComposerTmid } from '~/containers/MessageComposer/ComposerStore'; import { useCanUploadFile } from '~/containers/MessageComposer/hooks'; -import { Duration, type IDurationRef } from './Duration'; +import { Duration } from './Duration'; import AudioPlayer from '~/containers/AudioPlayer'; import { CancelButton } from './CancelButton'; import i18n from '~/i18n'; export const RecordAudio = (): ReactElement | null => { const [styles, colors] = useStyle(); - const recordingRef = useRef(null); - const durationRef = useRef({} as IDurationRef); - const numberOfTriesRef = useRef(0); + const audioRecorder = useAudioRecorder(RECORDING_SETTINGS); + const [status, setStatus] = useState<'recording' | 'reviewing'>('recording'); const { setRecordingAudio } = useMessageComposerApi(); const rid = useComposerRid(); @@ -40,33 +39,26 @@ export const RecordAudio = (): ReactElement | null => { useEffect(() => { const record = async () => { try { - await Audio.setAudioModeAsync(RECORDING_MODE); - recordingRef.current = new Audio.Recording(); - await recordingRef.current.prepareToRecordAsync(RECORDING_SETTINGS); - recordingRef.current.setOnRecordingStatusUpdate(durationRef.current.onRecordingStatusUpdate); - await recordingRef.current.startAsync(); - } catch (error: any) { - // error only occurs on iOS devices - if (error?.code === 'E_AUDIO_RECORDERNOTCREATED') { - if (numberOfTriesRef.current <= 5) { - recordingRef.current = null; - numberOfTriesRef.current += 1; - setTimeout(() => { - record(); - }, 100); - } else { - console.error(error); - } - } else { - console.error(error); + const permissions = await requestRecordingPermissionsAsync(); + if (!permissions.granted) { + setRecordingAudio(false); + return; } + + await setAudioModeAsync(RECORDING_MODE); + await audioRecorder.prepareToRecordAsync(); + audioRecorder.record(); + } catch (error) { + log(error); + setRecordingAudio(false); } }; + record(); return () => { try { - recordingRef.current?.stopAndUnloadAsync(); + audioRecorder.stop(); } catch { // Do nothing } @@ -75,7 +67,7 @@ export const RecordAudio = (): ReactElement | null => { const cancelRecording = async () => { try { - await recordingRef.current?.stopAndUnloadAsync(); + await audioRecorder.stop(); } catch { // Do nothing } finally { @@ -85,7 +77,7 @@ export const RecordAudio = (): ReactElement | null => { const goReview = async () => { try { - await recordingRef.current?.stopAndUnloadAsync(); + await audioRecorder.stop(); setStatus('reviewing'); } catch { // Do nothing @@ -94,16 +86,14 @@ export const RecordAudio = (): ReactElement | null => { const sendAudio = async () => { try { - if (!rid) return; - setRecordingAudio(false); - const fileURI = recordingRef.current?.getURI(); - const fileData = await getInfoAsync(fileURI as string); + if (!rid || !audioRecorder.uri) return; + const fileData = await getInfoAsync(audioRecorder.uri); const fileInfo = { name: `${Date.now()}${RECORDING_EXTENSION}`, mime: 'audio/aac', type: 'audio/aac', store: 'Uploads', - path: fileURI, + path: fileData.uri, size: fileData.exists ? fileData.size : null } as IUpload; @@ -112,7 +102,9 @@ export const RecordAudio = (): ReactElement | null => { await sendFileMessage(rid, fileInfo, tmid, server, user); } } + setRecordingAudio(false); } catch (e) { + setRecordingAudio(false); log(e); } }; @@ -125,7 +117,7 @@ export const RecordAudio = (): ReactElement | null => { return ( - + @@ -146,7 +138,7 @@ export const RecordAudio = (): ReactElement | null => { - + diff --git a/app/containers/Ringer/index.tsx b/app/containers/Ringer/index.tsx index 19dad9435cd..59174edc05b 100644 --- a/app/containers/Ringer/index.tsx +++ b/app/containers/Ringer/index.tsx @@ -1,5 +1,7 @@ -import { Audio } from 'expo-av'; -import { useEffect, useRef, memo } from 'react'; +import { createAudioPlayer } from 'expo-audio'; +import { useEffect, memo } from 'react'; + +import log from '~/lib/methods/helpers/log'; export enum ERingerSounds { DIALTONE = 'dialtone', @@ -7,25 +9,24 @@ export enum ERingerSounds { } const Ringer = memo(({ ringer }: { ringer: ERingerSounds }) => { - const sound = useRef(new Audio.Sound()); - useEffect(() => { - const loadAndPlay = async () => { + const soundFile = ringer === ERingerSounds.DIALTONE ? require('./dialtone.mp3') : require('./ringtone.mp3'); + const player = createAudioPlayer(soundFile); + try { + player.loop = true; + player.play(); + } catch (error) { + log(error); + } + + return () => { try { - const soundFile = ringer === ERingerSounds.DIALTONE ? require(`./dialtone.mp3`) : require(`./ringtone.mp3`); - await sound.current.loadAsync(soundFile); - await sound.current.playAsync(); - await sound.current.setIsLoopingAsync(true); + player.pause(); + player.release(); } catch (error) { - console.error('Error loading sound:', error); + log(error); } }; - - loadAndPlay(); - - return () => { - sound.current?.unloadAsync(); - }; }, []); return null; diff --git a/app/containers/VideoPlayer/__snapshots__/index.test.tsx.snap b/app/containers/VideoPlayer/__snapshots__/index.test.tsx.snap new file mode 100644 index 00000000000..4e2d0c7e633 --- /dev/null +++ b/app/containers/VideoPlayer/__snapshots__/index.test.tsx.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Story Snapshots: Basic should match snapshot 1`] = ` + +`; diff --git a/app/containers/VideoPlayer/index.stories.tsx b/app/containers/VideoPlayer/index.stories.tsx new file mode 100644 index 00000000000..fa4ee1ef025 --- /dev/null +++ b/app/containers/VideoPlayer/index.stories.tsx @@ -0,0 +1,20 @@ +import { type ReactElement } from 'react'; + +import VideoPlayer from '.'; + +export default { + title: 'Video Player' +}; + +const attachment = { + title_link: 'https://open.rocket.chat/video.mp4', + video_url: 'https://open.rocket.chat/video.mp4' +}; + +const user = { id: 'user-id', token: 'token' }; + +const setLoading = () => undefined; + +export const Basic = (): ReactElement => ( + +); diff --git a/app/containers/VideoPlayer/index.test.tsx b/app/containers/VideoPlayer/index.test.tsx new file mode 100644 index 00000000000..12e94566684 --- /dev/null +++ b/app/containers/VideoPlayer/index.test.tsx @@ -0,0 +1,90 @@ +import { render } from '@testing-library/react-native'; +import { Alert } from 'react-native'; + +import VideoPlayer from '.'; +import { useAppNavigation } from '~/lib/hooks/navigation'; +import { generateSnapshots } from '~/.rnstorybook/generateSnapshots'; +import * as stories from './index.stories'; + +jest.mock('expo', () => ({ + useEventListener: jest.fn() +})); + +jest.mock('~/i18n', () => ({ + __esModule: true, + default: { + t: (key: string) => key + } +})); + +jest.mock('~/lib/methods/helpers', () => ({ + formatAttachmentUrl: (url: string) => url, + encodeAttachmentUrl: (url: string) => url +})); + +jest.mock('~/lib/hooks/navigation', () => ({ + useAppNavigation: jest.fn(() => ({ + addListener: jest.fn(() => jest.fn()), + goBack: jest.fn() + })) +})); + +const mockUseEventListener = require('expo').useEventListener as jest.Mock; + +const mockUseAppNavigation = useAppNavigation as jest.Mock; + +let statusCallback: ((event: { status: string }) => void) | undefined; + +const baseProps = { + attachment: { + title_link: 'https://open.rocket.chat/video.mp4', + video_url: 'https://open.rocket.chat/video.mp4' + }, + user: { id: 'user-id', token: 'token' }, + baseUrl: 'https://open.rocket.chat' +}; + +describe('VideoPlayer', () => { + beforeEach(() => { + jest.clearAllMocks(); + statusCallback = undefined; + mockUseAppNavigation.mockReturnValue({ + addListener: jest.fn(() => jest.fn()), + goBack: jest.fn() + }); + mockUseEventListener.mockImplementation((_player: unknown, _event: string, cb: (event: { status: string }) => void) => { + statusCallback = cb; + }); + }); + + it('hides the loading indicator when the video is ready to play', () => { + const setLoading = jest.fn(); + render(); + + statusCallback?.({ status: 'readyToPlay' }); + + expect(setLoading).toHaveBeenCalledWith(false); + }); + + it('alerts and navigates back only once when the video fails to load', () => { + const setLoading = jest.fn(); + const goBack = jest.fn(); + mockUseAppNavigation.mockReturnValue({ + addListener: jest.fn(() => jest.fn()), + goBack + }); + const alertSpy = jest.spyOn(Alert, 'alert').mockImplementation(() => undefined); + + render(); + + statusCallback?.({ status: 'error' }); + statusCallback?.({ status: 'error' }); + + expect(alertSpy).toHaveBeenCalledTimes(1); + expect(goBack).toHaveBeenCalledTimes(1); + + alertSpy.mockRestore(); + }); +}); + +generateSnapshots(stories); diff --git a/app/containers/VideoPlayer/index.tsx b/app/containers/VideoPlayer/index.tsx new file mode 100644 index 00000000000..4d156ff367f --- /dev/null +++ b/app/containers/VideoPlayer/index.tsx @@ -0,0 +1,71 @@ +import { useVideoPlayer, VideoView } from 'expo-video'; +import { useEffect, useRef, type Dispatch, type SetStateAction, type ReactElement } from 'react'; +import { View } from 'react-native'; +import { useEventListener } from 'expo'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { styles } from './styles'; +import { useAppNavigation } from '~/lib/hooks/navigation'; +import I18n from '~/i18n'; +import { type IAttachment } from '~/definitions'; +import { formatAttachmentUrl, encodeAttachmentUrl } from '~/lib/methods/helpers'; +import { showErrorAlert } from '~/lib/methods/helpers/info'; + +interface IVideoPlayerProps { + attachment: IAttachment; + user: { id: string; token: string }; + baseUrl: string; + setLoading: Dispatch>; +} + +const VideoPlayer = ({ attachment, user, baseUrl, setLoading }: IVideoPlayerProps): ReactElement => { + const navigation = useAppNavigation(); + const hasHandledErrorRef = useRef(false); + const { bottom } = useSafeAreaInsets(); + + const url = formatAttachmentUrl(attachment.title_link || attachment.video_url, user.id, user.token, baseUrl); + const uri = encodeAttachmentUrl(url); + + const player = useVideoPlayer(uri, player => { + player.play(); + }); + + useEventListener(player, 'statusChange', ({ status }) => { + if (status === 'readyToPlay') { + setLoading(false); + } else if (status === 'error' && !hasHandledErrorRef.current) { + hasHandledErrorRef.current = true; + setLoading(false); + showErrorAlert(I18n.t('There_was_an_error_while_playing_video'), I18n.t('Error')); + navigation.goBack(); + } + }); + + useEffect(() => { + hasHandledErrorRef.current = false; + }, [uri]); + + useEffect(() => { + const blurSub = navigation.addListener('blur', () => { + player.pause(); + }); + return () => { + blurSub(); + }; + }, [navigation, player]); + + return ( + + + + ); +}; + +export default VideoPlayer; diff --git a/app/containers/VideoPlayer/styles.ts b/app/containers/VideoPlayer/styles.ts new file mode 100644 index 00000000000..c445de8903d --- /dev/null +++ b/app/containers/VideoPlayer/styles.ts @@ -0,0 +1,7 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + flex: 1 + } +}); diff --git a/app/i18n/locales/ar.json b/app/i18n/locales/ar.json index 874571d310e..cdbeaa547a1 100644 --- a/app/i18n/locales/ar.json +++ b/app/i18n/locales/ar.json @@ -209,6 +209,7 @@ "Enter_E2EE_Password_description": "أدخل كلمة مرور E2EE لعرض وإرسال الرسائل المشفرة.\n\nيجب إدخال كلمة المرور على كل جهاز.", "Enter_manually": "أدخل يدويًا", "Enter_username_or_number": "أدخل اسم المستخدم أو الرقم", + "Error": "خطأ", "Error_incorrect_password": "كلمة المرور غير صحيحة", "Error_prefix": "خطأ: {{message}}", "Error_uploading": "خطأ في الرفع", @@ -614,6 +615,7 @@ "The_user_wont_be_able_to_type_in_roomName": "المستخدم لن يتمكن من الكتابة في {{roomName}}", "Theme": "سمة", "There_was_an_error_while_action": "حدث خطأ أثناء {{action}}!", + "There_was_an_error_while_playing_video": "حدث خطأ أثناء تشغيل الفيديو.", "This_room_is_blocked": "هذه الغرفة محظورة", "This_room_is_read_only": "هذه الغرفة للقراءة فقط", "This_will_clear_all_your_offline_data": "سيؤدي هذا إلى محو جميع بياناتك في وضع عدم الاتصال.", diff --git a/app/i18n/locales/bn-IN.json b/app/i18n/locales/bn-IN.json index 17c242b47a3..9d6021a1d35 100644 --- a/app/i18n/locales/bn-IN.json +++ b/app/i18n/locales/bn-IN.json @@ -310,6 +310,7 @@ "Enter_E2EE_Password_description": "আপনার ই2ইইই পাসওয়ার্ড প্রবেশ করান গোপন বার্তা দেখতে এবং পাঠানোর জন্য।\n\nপ্রতিটি যন্ত্রে পাসওয়ার্ড প্রবেশ করতে হবে।", "Enter_manually": "ম্যানুয়ালি লিখুন", "Enter_username_or_number": "ব্যবহারকারীর নাম বা নম্বর লিখুন", + "Error": "ত্রুটি", "Error_Download_file": "ফাইল ডাউনলোড করতে ত্রুটি", "Error_incorrect_password": "ভুল পাসওয়ার্ড", "Error_prefix": "ত্রুটি: {{message}}", @@ -835,6 +836,7 @@ "The_user_wont_be_able_to_type_in_roomName": "ব্যবহারকারী প্রতিষ্ঠানগুলি {{roomName}} টাইপ করতে পারবেন না", "Theme": "থিম", "There_was_an_error_while_action": "{{action}} সময়ে একটি ত্রুটি ছিল!", + "There_was_an_error_while_playing_video": "ভিডিও চালানোর সময় একটি ত্রুটি ঘটেছে।", "This_room_is_blocked": "এই রুমটি অবরোধিত", "This_room_is_read_only": "এই রুমটি শুধুমাত্র পড়া যায়", "This_will_clear_all_your_offline_data": "এটি আপনার সমস্ত অফলাইন ডেটা মুছে ফেলবে।", diff --git a/app/i18n/locales/cs.json b/app/i18n/locales/cs.json index 957e5cea9e4..b85835d83c7 100644 --- a/app/i18n/locales/cs.json +++ b/app/i18n/locales/cs.json @@ -329,6 +329,7 @@ "Enter_Your_E2E_Password": "Zadejte heslo E2E", "Enter_Your_Encryption_Password_desc1": "To vám umožní přístup k vašim zašifrovaným soukromým skupinám a přímým zprávám.", "Enter_Your_Encryption_Password_desc2": "Ke kódování/dekódování zpráv musíte zadat heslo na každém místě, kde používáte chat.", + "Error": "Chyba", "Error_Download_file": "Chyba při stahování souboru", "Error_incorrect_password": "Nesprávné heslo", "Error_prefix": "Chyba: {{message}}", @@ -898,6 +899,7 @@ "The_user_wont_be_able_to_type_in_roomName": "Uživatel nebude moci psát v {{roomName}}", "Theme": "Téma", "There_was_an_error_while_action": "Při {{action}} došlo k chybě!", + "There_was_an_error_while_playing_video": "Při přehrávání videa došlo k chybě.", "This_room_is_blocked": "Tato místnost je zablokována", "This_room_is_read_only": "Tato místnost je pouze pro čtení", "This_will_clear_all_your_offline_data": "Tímto vymažete všechna vaše offline data.", diff --git a/app/i18n/locales/de.json b/app/i18n/locales/de.json index 4af39fdeab6..162d248ddd7 100644 --- a/app/i18n/locales/de.json +++ b/app/i18n/locales/de.json @@ -304,6 +304,7 @@ "Enter_E2EE_Password_description": "Geben Sie Ihr E2EE-Passwort ein, um verschlüsselte Nachrichten anzuzeigen und zu senden.\n\nDas Passwort muss auf jedem Gerät eingegeben werden.", "Enter_manually": "Manuell eingeben", "Enter_username_or_number": "Benutzername oder Nummer eingeben", + "Error": "Fehler", "Error_Download_file": "Fehler beim Herunterladen der Datei", "Error_incorrect_password": "Falsches Passwort", "Error_prefix": "Fehler: {{message}}", @@ -827,6 +828,7 @@ "The_user_wont_be_able_to_type_in_roomName": "Dem Nutzer wird es nicht möglich sein in {{roomName}} zu schreiben", "Theme": "Erscheinungsbild", "There_was_an_error_while_action": "Während {{action}} ist ein Fehler aufgetreten!", + "There_was_an_error_while_playing_video": "Beim Abspielen des Videos ist ein Fehler aufgetreten.", "This_room_is_blocked": "Dieser Room ist gesperrt", "This_room_is_read_only": "Dieser Room kann nur gelesen werden", "This_will_clear_all_your_offline_data": "Dies wird Ihre Offline-Daten löschen.", diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 4a0809c15a1..20f09b55c0b 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -350,9 +350,9 @@ "Enter_manually": "Enter manually", "Enter_the_code": "Enter the code we just emailed you.", "Enter_username_or_number": "Enter username or number", + "Error": "Error", "Error_Download_file": "Error while downloading file", "Error_incorrect_password": "Incorrect password", - "Error_play_video": "There was an error while playing this video", "Error_prefix": "Error: {{message}}", "Error_uploading": "Error uploading", "error-action-not-allowed": "{{action}} is not allowed", @@ -924,6 +924,7 @@ "The_user_wont_be_able_to_type_in_roomName": "The user won't be able to type in {{roomName}}", "Theme": "Theme", "There_was_an_error_while_action": "There was an error while {{action}}!", + "There_was_an_error_while_playing_video": "There was an error while playing the video.", "This_room_is_blocked": "This room is blocked", "This_room_is_read_only": "This room is read only", "This_will_clear_all_your_offline_data": "This will clear all your offline data.", diff --git a/app/i18n/locales/es.json b/app/i18n/locales/es.json index f590403f9e8..fa0a06ceb55 100644 --- a/app/i18n/locales/es.json +++ b/app/i18n/locales/es.json @@ -174,6 +174,7 @@ "Enter_E2EE_Password_description": "Ingrese su contraseña E2EE para ver y enviar mensajes cifrados.\n\nLa contraseña debe ingresarse en cada dispositivo.", "Enter_manually": "Ingresar manualmente", "Enter_username_or_number": "Introduzca usuario o número", + "Error": "Error", "Error_incorrect_password": "Contraseña incorrecta", "Error_prefix": "Error: {{mensaje}}", "Error_uploading": "Error en la subida", @@ -459,6 +460,7 @@ "The_autocomplete_options_are_available_above_the_input_composer": "Las opciones de autocompletado están disponibles arriba del compositor de entrada.", "Theme": "Tema", "There_was_an_error_while_action": "¡Ha habido un error mientras {{action}}!", + "There_was_an_error_while_playing_video": "Se produjo un error al reproducir el vídeo.", "This_room_is_blocked": "La sala está bloqueada", "This_room_is_read_only": "Esta sala es de sólo lectura", "Thread": "Hilo", diff --git a/app/i18n/locales/fi.json b/app/i18n/locales/fi.json index 19a11f6f096..f36f557d3ba 100644 --- a/app/i18n/locales/fi.json +++ b/app/i18n/locales/fi.json @@ -289,6 +289,7 @@ "Enter_E2EE_Password_description": "Syötä E2EE-salasanasi, jotta voit tarkastella ja lähettää salattuja viestejä.\n\nSalasana on syötettävä jokaisella laitteella.", "Enter_manually": "Syötä manuaalisesti", "Enter_username_or_number": "Anna käyttäjänimi tai numero", + "Error": "Virhe", "Error_Download_file": "Virhe ladattaessa tiedostoa", "Error_incorrect_password": "Virheellinen salasana", "Error_prefix": "Virhe: {{message}}", @@ -802,6 +803,7 @@ "The_user_wont_be_able_to_type_in_roomName": "Käyttäjä ei voi kirjoittaa huoneessa {{roomName}}", "Theme": "Teema", "There_was_an_error_while_action": "Virhe toiminnon {{action}} aikana!", + "There_was_an_error_while_playing_video": "Videon toiston aikana tapahtui virhe.", "This_room_is_blocked": "Huone on estetty", "This_room_is_read_only": "Huone on vain luku -tilassa", "This_will_clear_all_your_offline_data": "Tämä poistaa kaikki offline-tietosi.", diff --git a/app/i18n/locales/fr.json b/app/i18n/locales/fr.json index ae37eb4740f..ddb8e006735 100644 --- a/app/i18n/locales/fr.json +++ b/app/i18n/locales/fr.json @@ -257,6 +257,7 @@ "Enter_E2EE_Password_description": "Entrez votre mot de passe E2EE pour consulter et envoyer des messages chiffrés.\n\nLe mot de passe doit être saisi sur chaque appareil.", "Enter_manually": "Saisir manuellement", "Enter_username_or_number": "Saisissez un nom d’utilisateur ou un numéro", + "Error": "Erreur", "Error_Download_file": "Erreur lors du téléchargement du fichier", "Error_incorrect_password": "Mot de passe incorrect", "Error_prefix": "Erreur : {{message}}", @@ -735,6 +736,7 @@ "The_user_wont_be_able_to_type_in_roomName": "L'utilisateur ne pourra pas écrire dans {{roomName}}", "Theme": "Thème", "There_was_an_error_while_action": "Une erreur s'est produite lors de {{action}} !", + "There_was_an_error_while_playing_video": "Une erreur s'est produite lors de la lecture de la vidéo.", "This_room_is_blocked": "Ce salon est bloqué", "This_room_is_read_only": "Ce salon est en lecture seule", "This_will_clear_all_your_offline_data": "Cela effacera toutes vos données hors ligne.", diff --git a/app/i18n/locales/hi-IN.json b/app/i18n/locales/hi-IN.json index 75c383b403f..3ceefcbd1d2 100644 --- a/app/i18n/locales/hi-IN.json +++ b/app/i18n/locales/hi-IN.json @@ -310,6 +310,7 @@ "Enter_E2EE_Password_description": "एन्क्रिप्टेड संदेश देखने और भेजने के लिए अपना E2EE पासवर्ड दर्ज करें।\n\nप्रत्येक डिवाइस पर पासवर्ड दर्ज करना आवश्यक है।", "Enter_manually": "मैन्युअल रूप से दर्ज करें", "Enter_username_or_number": "उपयोगकर्ता नाम या नंबर दर्ज करें", + "Error": "त्रुटि", "Error_Download_file": "फ़ाइल डाउनलोड करते समय त्रुटि", "Error_incorrect_password": "गलत पासवर्ड", "Error_prefix": "त्रुटि: {{message}}", @@ -835,6 +836,7 @@ "The_user_wont_be_able_to_type_in_roomName": "उपयोगकर्ता {{roomName}} में टाइप नहीं कर सकता है", "Theme": "थीम", "There_was_an_error_while_action": "{{action}} करते समय एक त्रुटि हुई थी!", + "There_was_an_error_while_playing_video": "वीडियो चलाते समय एक त्रुटि हुई।", "This_room_is_blocked": "यह कमरा ब्लॉक किया गया है", "This_room_is_read_only": "यह कमरा केवल पठन के लिए है", "This_will_clear_all_your_offline_data": "यह आपके सभी ऑफ़लाइन डेटा को हटा देगा।", diff --git a/app/i18n/locales/hu.json b/app/i18n/locales/hu.json index 1c274001778..70261c37561 100644 --- a/app/i18n/locales/hu.json +++ b/app/i18n/locales/hu.json @@ -310,6 +310,7 @@ "Enter_E2EE_Password_description": "Adja meg az E2EE jelszavát a titkosított üzenetek megtekintéséhez és küldéséhez.\n\nA jelszót minden eszközön meg kell adni.", "Enter_manually": "Kézi bevitel", "Enter_username_or_number": "Adjon meg felhasználónevet vagy számot", + "Error": "Hiba", "Error_Download_file": "Hiba a fájl letöltése közben", "Error_incorrect_password": "Helytelen jelszó", "Error_prefix": "Hiba: {{üzenet}}", @@ -836,6 +837,7 @@ "The_user_wont_be_able_to_type_in_roomName": "A felhasználó nem tud írni itt: {{roomName}}", "Theme": "Téma", "There_was_an_error_while_action": "Hiba történt a {{action}}!", + "There_was_an_error_while_playing_video": "Hiba történt a videó lejátszása közben.", "This_room_is_blocked": "Ez a szoba tiltva van", "This_room_is_read_only": "Ez a szoba csak olvasható", "This_will_clear_all_your_offline_data": "Ez törli az összes offline adatot.", diff --git a/app/i18n/locales/it.json b/app/i18n/locales/it.json index 20f08388d0f..8f7c7534bfb 100644 --- a/app/i18n/locales/it.json +++ b/app/i18n/locales/it.json @@ -233,6 +233,7 @@ "Enter_E2EE_Password_description": "Inserisci la tua password E2EE per visualizzare e inviare messaggi crittografati.\n\nLa password deve essere inserita su ciascun dispositivo.", "Enter_manually": "Inserisci manualmente", "Enter_username_or_number": "Inserisci nome utente o numero", + "Error": "Errore", "Error_incorrect_password": "Password errata", "Error_prefix": "Errore: {{message}}", "Error_uploading": "Errore nel caricamento di", @@ -648,6 +649,7 @@ "The_user_wont_be_able_to_type_in_roomName": "L'utente non potrà scrivere in {{roomName}}", "Theme": "Tema", "There_was_an_error_while_action": "Si è verificato un errore nel {{action}}!", + "There_was_an_error_while_playing_video": "Si è verificato un errore durante la riproduzione del video.", "This_room_is_blocked": "Questa stanza è bloccata", "This_room_is_read_only": "Questa stanza è in sola lettura", "This_will_clear_all_your_offline_data": "Questo cancellerà tutti i tuoi dati offline.", diff --git a/app/i18n/locales/ja.json b/app/i18n/locales/ja.json index 41cd0fd4c3a..40a9cf29fd4 100644 --- a/app/i18n/locales/ja.json +++ b/app/i18n/locales/ja.json @@ -208,6 +208,7 @@ "Enter_E2EE_Password_description": "E2EEパスワードを入力して、暗号化されたメッセージを表示および送信してください。\n\n各デバイスでパスワードを入力する必要があります。", "Enter_manually": "手動で入力", "Enter_username_or_number": "ユーザー名または番号を入力", + "Error": "エラー", "Error_incorrect_password": "パスワードが違います。", "Error_prefix": "エラー: {{message}}", "Error_uploading": "アップロードエラー", @@ -546,6 +547,7 @@ "The_autocomplete_options_are_available_above_the_input_composer": "オートコンプリートのオプションは入力コンポーザーの上にあります。", "Theme": "テーマ", "There_was_an_error_while_action": "{{action}}の最中にエラーが発生しました!", + "There_was_an_error_while_playing_video": "動画の再生中にエラーが発生しました。", "This_room_is_blocked": "このルームはブロックされています。", "This_room_is_read_only": "このルームは読み取り専用です。", "This_will_clear_all_your_offline_data": "オフラインデータをすべて削除します。", diff --git a/app/i18n/locales/nl.json b/app/i18n/locales/nl.json index d1801286da3..c127f34c9d2 100644 --- a/app/i18n/locales/nl.json +++ b/app/i18n/locales/nl.json @@ -257,6 +257,7 @@ "Enter_E2EE_Password_description": "Voer je E2EE-wachtwoord in om versleutelde berichten te bekijken en te verzenden.\n\nHet wachtwoord moet op elk apparaat worden ingevoerd.", "Enter_manually": "Handmatig invoeren", "Enter_username_or_number": "Voer een gebruikersnaam of nummer in", + "Error": "Fout", "Error_Download_file": "Fout tijdens het downloaden van bestand", "Error_incorrect_password": "Onjuist wachtwoord", "Error_prefix": "Fout: {{message}}", @@ -735,6 +736,7 @@ "The_user_wont_be_able_to_type_in_roomName": "De gebruiker zal in {{roomName}} niet kunnen typen", "Theme": "Thema", "There_was_an_error_while_action": "Er is een fout opgetreden tijdens {{action}}!", + "There_was_an_error_while_playing_video": "Er is een fout opgetreden tijdens het afspelen van de video.", "This_room_is_blocked": "Deze kamer is geblokkeerd", "This_room_is_read_only": "Deze kamer is alleen-lezen", "This_will_clear_all_your_offline_data": "Hiermee worden al jouw offline gegevens gewist.", diff --git a/app/i18n/locales/nn.json b/app/i18n/locales/nn.json index 88bcc574c4f..ddb9e2c1bcb 100644 --- a/app/i18n/locales/nn.json +++ b/app/i18n/locales/nn.json @@ -174,6 +174,7 @@ "Enter_manually": "Skriv inn manuelt", "Enter_the_code": "Skriv inn koden vi nettopp sendte deg på e-post.", "Enter_username_or_number": "Skriv inn brukarnamn eller nummer", + "Error": "Feil", "error-action-not-allowed": "{{action}} er ikke tillatt", "error-avatar-invalid-url": "Ugyldig avatar URL: {{url}}", "error-email-send-failed": "Feil ved å prøve å sende e-post: {{message}}", @@ -414,6 +415,7 @@ "The_maximum_number_of_users_has_been_reached": "Maksimalt antall brukere er nådd.", "The_room_does_not_exist": "Rommet eksisterer ikke eller du har ikke tilgang", "Theme": "Tema", + "There_was_an_error_while_playing_video": "Det oppstod ein feil under avspeling av videoen.", "This_room_is_blocked": "Dette rommet er blokkert", "This_room_is_read_only": "Dette rommet er kun skrivebeskyttet", "Thread_message": "trådmelding {{msg}}", diff --git a/app/i18n/locales/no.json b/app/i18n/locales/no.json index b9a643c907c..f0aa31b332a 100644 --- a/app/i18n/locales/no.json +++ b/app/i18n/locales/no.json @@ -335,8 +335,8 @@ "Enter_manually": "Skriv inn manuelt", "Enter_the_code": "Skriv inn koden vi nettopp sendte deg på e-post.", "Enter_username_or_number": "Skriv inn brukernavn eller nummer", + "Error": "Feil", "Error_Download_file": "Feil under nedlasting av fil", - "Error_play_video": "Det oppsto en feil under avspilling av denne videoen", "Error_uploading": "Feil ved opplasting", "error-action-not-allowed": "{{action}} er ikke tillat", "error-avatar-invalid-url": "Ugyldig avatar URL: {{url}}", @@ -882,6 +882,7 @@ "The_user_wont_be_able_to_type_in_roomName": "Brukeren vil ikke kunne skrive inn {{roomName}}", "Theme": "Tema", "There_was_an_error_while_action": "Det oppsto en feil mens {{action}}!", + "There_was_an_error_while_playing_video": "Det oppstod en feil under avspilling av videoen.", "This_room_is_blocked": "Dette rommet er blokkert", "This_room_is_read_only": "Dette rommet er skrivebeskyttet", "This_will_clear_all_your_offline_data": "Dette vil slette alle dine frakoblede data.", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 6e6322cde1f..1af1d2a4788 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -339,9 +339,9 @@ "Enter_manually": "Inserir manualmente", "Enter_the_code": "Insira o código que acabamos de enviar por e-mail.", "Enter_username_or_number": "Digite o usuário ou número", + "Error": "Erro", "Error_Download_file": "Erro ao baixar o arquivo", "Error_incorrect_password": "Senha incorreta", - "Error_play_video": "Houve um erro ao reproduzir esse vídeo", "Error_prefix": "Erro: {{message}}", "Error_uploading": "Erro subindo", "error-action-not-allowed": "{{action}} não é permitido", @@ -907,6 +907,7 @@ "The_user_wont_be_able_to_type_in_roomName": "O usuário não poderá digitar em {{roomName}}", "Theme": "Tema", "There_was_an_error_while_action": "Aconteceu um erro {{action}}!", + "There_was_an_error_while_playing_video": "Ocorreu um erro ao reproduzir o vídeo.", "This_room_is_blocked": "Esta sala está bloqueada", "This_room_is_read_only": "Esta sala é somente leitura", "This_will_clear_all_your_offline_data": "Isto limpará todos os seus dados offline.", diff --git a/app/i18n/locales/pt-PT.json b/app/i18n/locales/pt-PT.json index d7d39e650a8..033e4036b2c 100644 --- a/app/i18n/locales/pt-PT.json +++ b/app/i18n/locales/pt-PT.json @@ -204,6 +204,7 @@ "Enter_E2EE_Password_description": "Introduza a sua palavra-passe E2EE para ver e enviar mensagens encriptadas.\n\nA palavra-passe precisa ser introduzida em cada dispositivo.", "Enter_manually": "Inserir manualmente", "Enter_username_or_number": "Introduza o nome de utilizador ou número", + "Error": "Erro", "Error_incorrect_password": "Palavra-passe incorreta", "Error_prefix": "Erro: {{message}}", "Error_uploading": "Erro ao fazer o envio", @@ -530,6 +531,7 @@ "Terms_of_Service": " Termos do Serviço ", "The_autocomplete_options_are_available_above_the_input_composer": "As opções de preenchimento automático estão disponíveis acima do compositor de entrada.", "There_was_an_error_while_action": "Houve um erro enquanto {{action}}!", + "There_was_an_error_while_playing_video": "Ocorreu um erro ao reproduzir o vídeo.", "This_room_is_blocked": "Esta sala está bloqueada", "This_room_is_read_only": "Esta sala é apenas de leitura", "Thread_message": "mensagem do tópico {{msg}}", diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json index ad350745b44..11cce560077 100644 --- a/app/i18n/locales/ru.json +++ b/app/i18n/locales/ru.json @@ -280,6 +280,7 @@ "Enter_E2EE_Password_description": "Введите ваш E2EE пароль, чтобы просматривать и отправлять зашифрованные сообщения.\n\nПароль необходимо вводить на каждом устройстве.", "Enter_manually": "Ввести вручную", "Enter_username_or_number": "Введите имя пользователя или номер", + "Error": "Ошибка", "Error_Download_file": "Ошибка при скачивании файла", "Error_incorrect_password": "Неверный пароль", "Error_prefix": "Ошибка: {{сообщение}}", @@ -779,6 +780,7 @@ "The_user_wont_be_able_to_type_in_roomName": "Пользователь не сможет писать сообщения в {{roomName}}", "Theme": "Тема", "There_was_an_error_while_action": "Произошла ошибка в процессе {{action}}!", + "There_was_an_error_while_playing_video": "Произошла ошибка при воспроизведении видео.", "This_room_is_blocked": "Этот чат заблокирован", "This_room_is_read_only": "Этот чат доступен только для чтения", "This_will_clear_all_your_offline_data": "Это очистит все ваши офлайн данные.", diff --git a/app/i18n/locales/sl-SI.json b/app/i18n/locales/sl-SI.json index a7817e3d830..efcefab0cf0 100644 --- a/app/i18n/locales/sl-SI.json +++ b/app/i18n/locales/sl-SI.json @@ -267,6 +267,7 @@ "Enter_E2EE_Password_description": "Vnesite svoje geslo E2EE za ogled in pošiljanje šifriranih sporočil.\n\nGeslo je treba vnesti na vsaki napravi.", "Enter_manually": "Vnesi ročno", "Enter_username_or_number": "Vnesite uporabniško ime ali številko", + "Error": "Napaka", "Error_Download_file": "Napaka med prenosom datoteke", "Error_incorrect_password": "Napačno geslo", "Error_prefix": "Napaka: {{message}}", @@ -749,6 +750,7 @@ "The_user_wont_be_able_to_type_in_roomName": "Uporabnik ne bo mogel vtipkavati v {{roomName}}", "Theme": "Tema", "There_was_an_error_while_action": "Med {{action}} je prišlo do napake!", + "There_was_an_error_while_playing_video": "Med predvajanjem videa je prišlo do napake.", "This_room_is_blocked": "Ta soba je blokirana", "This_room_is_read_only": "Ta soba se samo bere", "This_will_clear_all_your_offline_data": "To bo zbrisalo vse vaše podatke brez povezave.", diff --git a/app/i18n/locales/sv.json b/app/i18n/locales/sv.json index ab2f2b6c0b3..0f671eca4f7 100644 --- a/app/i18n/locales/sv.json +++ b/app/i18n/locales/sv.json @@ -289,6 +289,7 @@ "Enter_E2EE_Password_description": "Ange ditt E2EE-lösenord för att visa och skicka krypterade meddelanden.\n\nLösenordet måste anges på varje enhet.", "Enter_manually": "Ange manuellt", "Enter_username_or_number": "Ange användarnamn eller nummer", + "Error": "Fel", "Error_Download_file": "Fel vid nedladdning av fil", "Error_incorrect_password": "Felaktigt lösenord", "Error_prefix": "Fel: {{message}}", @@ -801,6 +802,7 @@ "The_user_wont_be_able_to_type_in_roomName": "Användaren kan inte ange {{roomName}}", "Theme": "Tema", "There_was_an_error_while_action": "Ett fel inträffade vid {{action}}.", + "There_was_an_error_while_playing_video": "Ett fel uppstod när videon spelades upp.", "This_room_is_blocked": "Rummet är blockerat", "This_room_is_read_only": "Rummet är skrivskyddat", "This_will_clear_all_your_offline_data": "Med den här åtgärden rensas alla dina offlinedata.", diff --git a/app/i18n/locales/ta-IN.json b/app/i18n/locales/ta-IN.json index 14a0b567050..c7223617b79 100644 --- a/app/i18n/locales/ta-IN.json +++ b/app/i18n/locales/ta-IN.json @@ -310,6 +310,7 @@ "Enter_E2EE_Password_description": "உங்கள் கடவுச்சொல்லைப் பதிவு செய்து குறியாக்கச் செய்திகளைப் பார்க்கவும் மற்றும் அனுப்பவும். \n\nஒவ்வொரு சாதனத்திலும் கடவுச்சொல்லை உள்ளிட வேண்டும்.", "Enter_manually": "கைமுறையாக உள்ளிடவும்", "Enter_username_or_number": "பயனர் பெயர் அல்லது எண்ணை உள்ளிடவும்", + "Error": "பிழை", "Error_Download_file": "கோப்பு பதிவிறக்கம் செய்யும் போது பிழை", "Error_incorrect_password": "தவறான கடவுச்சொல்", "Error_prefix": "பிழை: {{message}}", @@ -835,6 +836,7 @@ "The_user_wont_be_able_to_type_in_roomName": "பயனர் {{roomName}} இல்லை என்றார்", "Theme": "வானம்", "There_was_an_error_while_action": "{{action}} போன்ற நடவடிக்கையில் பிழை ஏற்பட்டது", + "There_was_an_error_while_playing_video": "வீடியோவை இயக்கும்போது ஒரு பிழை ஏற்பட்டது.", "This_room_is_blocked": "இந்த அறை தடைக்கப்பட்டுள்ளது", "This_room_is_read_only": "இந்த அறை மட்டுமே படிக்கக்கூடியது", "This_will_clear_all_your_offline_data": "இது உங்களுடைய அனுமதிகள் அனைத்து ஆஃப்லைன் தரவையும் அழிக்கும்.", diff --git a/app/i18n/locales/te-IN.json b/app/i18n/locales/te-IN.json index 661e4f51b98..85195c0a19e 100644 --- a/app/i18n/locales/te-IN.json +++ b/app/i18n/locales/te-IN.json @@ -309,6 +309,7 @@ "Enter_E2EE_Password_description": "ఎన్‌క్రిప్ట్ చేయబడిన సందేశాలను వీక్షించడానికి మరియు పంపడానికి మీ E2EE పాస్‌వర్డ్‌ని నమోదు చెయ్యండి.\n\nప్రతీ పరికరంలో పాస్‌వర్డ్‌ని నమోదు చేయాల్సి ఉంటుంది.", "Enter_manually": "మాన్యువల్‌గా నమోదు చేయండి", "Enter_username_or_number": "వినియోగదారు పేరు లేదా నంబర్‌ను నమోదు చేయండి", + "Error": "లోపం", "Error_Download_file": "ఫైల్ డౌన్‌లోడ్ చేస్తున్నప్పుడు తప్పిబాటు", "Error_incorrect_password": "తప్పు పాస్‌వర్డ్", "Error_prefix": "తప్పు: {{message}}", @@ -834,6 +835,7 @@ "The_user_wont_be_able_to_type_in_roomName": "వాడు {{roomName}}లో టైప్ చేయగలరు", "Theme": "థీమ్", "There_was_an_error_while_action": "{{action}} ప్రవృత్తి ప్రారంభించడానికి ఒక లోపం ఉంది!", + "There_was_an_error_while_playing_video": "వీడియోను ప్లే చేస్తున్నప్పుడు ఒక లోపం సంభవించింది.", "This_room_is_blocked": "ఈ గది నిషేధించబడింది", "This_room_is_read_only": "ఈ గది కంటెంట్ చదవడానికి మాత్రమే", "This_will_clear_all_your_offline_data": "ఇది మీ ఆఫ్‌లైన్ డేటాను అన్ని క్లియర్ చేస్తుంది.", diff --git a/app/i18n/locales/tr.json b/app/i18n/locales/tr.json index dd2dc95dbce..5ba75c7bc19 100644 --- a/app/i18n/locales/tr.json +++ b/app/i18n/locales/tr.json @@ -222,6 +222,7 @@ "Enter_E2EE_Password_description": "Şifreli mesajları görüntülemek ve göndermek için E2EE şifrenizi girin.\n\nHer cihazda şifre girişi yapılmalıdır.", "Enter_manually": "Manuel olarak gir", "Enter_username_or_number": "Kullanıcı adı veya numara girin", + "Error": "Hata", "Error_incorrect_password": "Yanlış parola", "Error_prefix": "Hata: {{message}}", "Error_uploading": "Yükleme hatası", @@ -631,6 +632,7 @@ "The_user_wont_be_able_to_type_in_roomName": "Kullanıcı {{roomName}} içinde yazamayacak", "Theme": "Tema", "There_was_an_error_while_action": "{{action}} sırasında bir hata oluştu!", + "There_was_an_error_while_playing_video": "Video oynatılırken bir hata oluştu.", "This_room_is_blocked": "Bu oda engellendi", "This_room_is_read_only": "Bu oda yazma kısıtlı", "This_will_clear_all_your_offline_data": "Bu, tüm çevrimdışı verilerinizi temizleyecektir.", diff --git a/app/i18n/locales/zh-CN.json b/app/i18n/locales/zh-CN.json index 63d65844017..0826aeadeaa 100644 --- a/app/i18n/locales/zh-CN.json +++ b/app/i18n/locales/zh-CN.json @@ -218,6 +218,7 @@ "Enter_E2EE_Password_description": "输入您的E2EE密码以查看和发送加密消息。\n\n每台设备都需要输入密码。", "Enter_manually": "手动输入", "Enter_username_or_number": "输入用户名或号码", + "Error": "错误", "Error_incorrect_password": "密码错误", "Error_prefix": "错误:{{message}}", "Error_uploading": "错误上传", @@ -603,6 +604,7 @@ "The_user_wont_be_able_to_type_in_roomName": "此用户将无法在 {{roomName}} 中输入", "Theme": "布景主题", "There_was_an_error_while_action": "{{action}}出现错误!", + "There_was_an_error_while_playing_video": "播放视频时发生错误。", "This_room_is_blocked": "这个聊天室被锁了", "This_room_is_read_only": "这个聊天室是只读的", "This_will_clear_all_your_offline_data": "这将清除您的所有离线资料。", diff --git a/app/i18n/locales/zh-TW.json b/app/i18n/locales/zh-TW.json index 4c5ae9b4029..a6ff5ba72b2 100644 --- a/app/i18n/locales/zh-TW.json +++ b/app/i18n/locales/zh-TW.json @@ -224,6 +224,7 @@ "Enter_E2EE_Password_description": "輸入您的E2EE密碼以查看和發送加密訊息。\n\n每個設備都需要輸入密碼。", "Enter_manually": "手動輸入", "Enter_username_or_number": "輸入使用者名稱或號碼", + "Error": "錯誤", "Error_incorrect_password": "密碼錯誤", "Error_prefix": "錯誤:{{message}}", "Error_uploading": "錯誤上傳", @@ -631,6 +632,7 @@ "The_user_wont_be_able_to_type_in_roomName": "此使用者將無法在 {{roomName}} 中輸入", "Theme": "佈景主題", "There_was_an_error_while_action": "{{action}}出現錯誤!", + "There_was_an_error_while_playing_video": "播放影片時發生錯誤。", "This_room_is_blocked": "這個聊天室已被鎖定", "This_room_is_read_only": "這個聊天室是唯讀的", "This_will_clear_all_your_offline_data": "這將清除您的所有離線資料。", diff --git a/app/lib/constants/audio.ts b/app/lib/constants/audio.ts index f0c506c34de..bfb77daeb4a 100644 --- a/app/lib/constants/audio.ts +++ b/app/lib/constants/audio.ts @@ -1,48 +1,41 @@ -import { Audio, type AudioMode, InterruptionModeAndroid, InterruptionModeIOS } from 'expo-av'; -import { type RecordingOptions } from 'expo-av/build/Audio'; +import { isIOS } from '../methods/helpers'; +import { AudioQuality, IOSOutputFormat, type RecordingOptions, type AudioMode } from 'expo-audio'; export const RECORDING_EXTENSION = '.aac'; + export const RECORDING_SETTINGS: RecordingOptions = { + extension: RECORDING_EXTENSION, + sampleRate: 44100, + numberOfChannels: 2, + bitRate: isIOS ? 64000 : 128000, android: { - // Settings related to audio encoding. - extension: RECORDING_EXTENSION, - outputFormat: Audio.AndroidOutputFormat.AAC_ADTS, - audioEncoder: Audio.AndroidAudioEncoder.AAC, - // Settings related to audio quality. - sampleRate: Audio.RecordingOptionsPresets.LOW_QUALITY.android.sampleRate, - numberOfChannels: Audio.RecordingOptionsPresets.LOW_QUALITY.android.numberOfChannels, - bitRate: Audio.RecordingOptionsPresets.LOW_QUALITY.android.bitRate + outputFormat: 'aac_adts', + audioEncoder: 'aac' }, ios: { - // Settings related to audio encoding. - extension: RECORDING_EXTENSION, - audioQuality: Audio.IOSAudioQuality.MEDIUM, - outputFormat: Audio.IOSOutputFormat.MPEG4AAC, - // Settings related to audio quality. - sampleRate: Audio.RecordingOptionsPresets.LOW_QUALITY.ios.sampleRate, - numberOfChannels: Audio.RecordingOptionsPresets.LOW_QUALITY.ios.numberOfChannels, - bitRate: Audio.RecordingOptionsPresets.LOW_QUALITY.ios.bitRate + audioQuality: AudioQuality.MEDIUM, + outputFormat: IOSOutputFormat.MPEG4AAC, + linearPCMBitDepth: 16, + linearPCMIsBigEndian: false, + linearPCMIsFloat: false }, - web: {}, - keepAudioActiveHint: true + web: {} }; export const RECORDING_MODE: AudioMode = { - allowsRecordingIOS: true, - playsInSilentModeIOS: true, - staysActiveInBackground: true, - shouldDuckAndroid: true, - playThroughEarpieceAndroid: false, - interruptionModeIOS: InterruptionModeIOS.DoNotMix, - interruptionModeAndroid: InterruptionModeAndroid.DoNotMix + playsInSilentMode: true, + shouldPlayInBackground: true, + allowsRecording: true, + shouldRouteThroughEarpiece: false, + interruptionMode: 'doNotMix', + interruptionModeAndroid: 'doNotMix' }; export const AUDIO_MODE: AudioMode = { - allowsRecordingIOS: false, - playsInSilentModeIOS: true, - staysActiveInBackground: true, - shouldDuckAndroid: true, - playThroughEarpieceAndroid: false, - interruptionModeIOS: InterruptionModeIOS.DoNotMix, - interruptionModeAndroid: InterruptionModeAndroid.DoNotMix + playsInSilentMode: true, + shouldPlayInBackground: true, + allowsRecording: false, + shouldRouteThroughEarpiece: false, + interruptionMode: 'doNotMix', + interruptionModeAndroid: 'doNotMix' }; diff --git a/app/lib/methods/AudioManager.test.ts b/app/lib/methods/AudioManager.test.ts new file mode 100644 index 00000000000..9996b0b1eaf --- /dev/null +++ b/app/lib/methods/AudioManager.test.ts @@ -0,0 +1,255 @@ +jest.mock('../database/services/Message', () => ({ + getMessageById: jest.fn(() => Promise.resolve(null)) +})); + +jest.mock('./getFilePathAudio', () => ({ + getFilePathAudio: jest.fn(() => 'file://x') +})); + +import type AudioManagerInstance from './AudioManager'; +import type { AudioStatus } from 'expo-audio'; +import type { TMessageModel } from '~/definitions'; + +interface IAudioManagerSetup { + AudioManager: typeof AudioManagerInstance; + players: any[]; +} + +const setup = (): IAudioManagerSetup => { + jest.resetModules(); + const players: any[] = []; + const createAudioPlayer = require('expo-audio').createAudioPlayer as jest.Mock; + createAudioPlayer.mockImplementation(() => { + const player = { + play: jest.fn(), + pause: jest.fn(), + stop: jest.fn(), + replay: jest.fn(), + setPlaying: jest.fn(), + setLooping: jest.fn(), + setMuted: jest.fn(), + setVolume: jest.fn(), + shouldCorrectPitch: false, + setPlaybackRate: jest.fn(function (this: { shouldCorrectPitch: boolean }) { + return this.shouldCorrectPitch; + }), + seekTo: jest.fn(), + release: jest.fn(), + remove: jest.fn(), + addListener: jest.fn(() => ({ remove: jest.fn() })), + removeListener: jest.fn(), + getStatusAsync: jest.fn(() => Promise.resolve()), + playing: false, + looping: false, + muted: false, + volume: 1, + playbackRate: 1, + currentTime: 0, + duration: 0, + loaded: false, + progress: 0, + buffering: false, + keepAlive: false + }; + players.push(player); + return player; + }); + const AudioManager = require('./AudioManager').default; + return { AudioManager, players }; +}; + +describe('AudioManager', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('loadAudio caches the player keyed by msgId/rid/uri', async () => { + const { AudioManager, players } = setup(); + const key1 = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + const key2 = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + + expect(key1).toBe(key2); + expect(players).toHaveLength(1); + }); + + it('loadAudio returns different keys for different uris', async () => { + const { AudioManager, players } = setup(); + const key1 = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + const key2 = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://b.mp3' }); + + expect(key1).not.toBe(key2); + expect(players).toHaveLength(2); + }); + + it('unloadRoomAudios releases only players for the given rid', async () => { + const { AudioManager, players } = setup(); + await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + await AudioManager.loadAudio({ rid: 'room-2', uri: 'file://b.mp3' }); + const [player1, player2] = players; + + await AudioManager.unloadRoomAudios('room-1'); + + expect(player1.release).toHaveBeenCalled(); + expect(player2.release).not.toHaveBeenCalled(); + }); + + it('unloadRoomAudios matches by exact rid segment, not substring', async () => { + const { AudioManager, players } = setup(); + await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://room-10/x.mp3' }); + await AudioManager.loadAudio({ rid: 'room-10', uri: 'file://x.mp3' }); + const [playerSubstring, playerExact] = players; + + await AudioManager.unloadRoomAudios('room-1'); + + expect(playerSubstring.release).toHaveBeenCalled(); + expect(playerExact.release).not.toHaveBeenCalled(); + }); + + it('onEnd releases the player when didJustFinish is true', async () => { + const { AudioManager, players } = setup(); + const key = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + AudioManager.addAudioRendered(key); + + await AudioManager.onEnd(key, { isLoaded: true, didJustFinish: true, currentTime: 5, duration: 5 } as AudioStatus); + + expect(players[0].release).toHaveBeenCalled(); + }); + + it('setPositionAsync seeks the player', async () => { + const { AudioManager, players } = setup(); + const key = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + + await AudioManager.setPositionAsync(key, 10); + + expect(players[0].seekTo).toHaveBeenCalledWith(10); + }); + + // On iOS setPlaybackRate maps to AVPlayer.rate, and a non-zero rate starts playback + it('setRate does not touch an idle player, so loading never starts playback', async () => { + const { AudioManager, players } = setup(); + const key = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + + AudioManager.setRate(key, 1); + + expect(players[0].setPlaybackRate).not.toHaveBeenCalled(); + expect(players[0].play).not.toHaveBeenCalled(); + }); + + it('playAudio applies the rate stored while the player was idle', async () => { + const { AudioManager, players } = setup(); + const key = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + AudioManager.setRate(key, 1.5); + + await AudioManager.playAudio(key); + + expect(players[0].setPlaybackRate).toHaveBeenCalledWith(1.5, 'medium'); + expect(players[0].setPlaybackRate).toHaveLastReturnedWith(true); + expect(players[0].play).toHaveBeenCalled(); + }); + + it('setRate applies immediately to the audio currently playing', async () => { + const { AudioManager, players } = setup(); + const key = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + await AudioManager.playAudio(key); + players[0].setPlaybackRate.mockClear(); + + AudioManager.setRate(key, 2); + + expect(players[0].setPlaybackRate).toHaveBeenCalledWith(2, 'medium'); + expect(players[0].setPlaybackRate).toHaveLastReturnedWith(true); + }); + + it('setRate leaves other loaded audios untouched while one is playing', async () => { + const { AudioManager, players } = setup(); + const playingKey = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + const idleKey = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://b.mp3' }); + await AudioManager.playAudio(playingKey); + + AudioManager.setRate(idleKey, 2); + + expect(players[1].setPlaybackRate).not.toHaveBeenCalled(); + expect(players[1].play).not.toHaveBeenCalled(); + }); + + it('a recreated player seeks to the saved position before the rate is applied', async () => { + const { AudioManager, players } = setup(); + const key = await AudioManager.loadAudio({ rid: 'room-1', uri: 'file://a.mp3' }); + AudioManager.setRate(key, 1.5); + await AudioManager.setPositionAsync(key, 7); + await AudioManager.onEnd(key, { isLoaded: true, didJustFinish: true, currentTime: 5, duration: 5 } as AudioStatus); + + await AudioManager.playAudio(key); + + const recreated = players[1]; + const [seekOrder] = recreated.seekTo.mock.invocationCallOrder; + const [rateOrder] = recreated.setPlaybackRate.mock.invocationCallOrder; + const [playOrder] = recreated.play.mock.invocationCallOrder; + expect(recreated.setPlaybackRate).toHaveBeenCalledWith(1.5, 'medium'); + expect(recreated.setPlaybackRate).toHaveLastReturnedWith(true); + expect(seekOrder).toBeLessThan(rateOrder); + expect(rateOrder).toBeLessThan(playOrder); + }); + + it('recreates the next completed audio when replaying a sequence', async () => { + const { AudioManager, players } = setup(); + const firstKey = AudioManager.loadAudio({ msgId: 'a', rid: 'room', uri: 'file://x' }); + const nextKey = AudioManager.loadAudio({ msgId: 'b', rid: 'room', uri: 'file://x' }); + const nextMessage = { id: 'b', attachments: [{ audio_url: '/x', audio_type: 'audio/mp3' }] } as TMessageModel; + jest + .spyOn(AudioManager, 'getNextAudioMessage') + .mockImplementation(msgId => Promise.resolve(msgId === 'a' ? nextMessage : null)); + const finished = { isLoaded: true, didJustFinish: true } as AudioStatus; + + await AudioManager.playAudio(firstKey); + await AudioManager.onEnd(firstKey, finished); + expect(players[1].play).toHaveBeenCalledTimes(1); + await AudioManager.onEnd(nextKey, finished); + + await AudioManager.playAudio(firstKey); + await AudioManager.onEnd(firstKey, finished); + + expect(players).toHaveLength(4); + expect(players[3].play).toHaveBeenCalledTimes(1); + + await AudioManager.onEnd(nextKey, finished); + AudioManager.removeAudioRendered(nextKey); + await AudioManager.playAudio(firstKey); + await AudioManager.onEnd(firstKey, finished); + expect(players).toHaveLength(5); + }); + + it('room cleanup removes completed audio state and releases live players only in that room', async () => { + const { AudioManager, players } = setup(); + const completedKey = AudioManager.loadAudio({ msgId: 'a', rid: 'room', uri: 'file://x' }); + const liveKey = AudioManager.loadAudio({ msgId: 'b', rid: 'room', uri: 'file://x' }); + const otherKey = AudioManager.loadAudio({ msgId: 'c', rid: 'other', uri: 'file://x' }); + const callback = jest.fn(); + AudioManager.setOnPlaybackStatusUpdate(completedKey, callback); + AudioManager.setRate(completedKey, 2); + await AudioManager.onEnd(completedKey, { isLoaded: true, didJustFinish: true } as AudioStatus); + + await AudioManager.unloadRoomAudios('room'); + + expect(players[0].release).toHaveBeenCalledTimes(1); + expect(players[1].release).toHaveBeenCalledTimes(1); + expect(players[2].release).not.toHaveBeenCalled(); + for (const field of [ + 'audioQueue', + 'audioUris', + 'audioPositions', + 'audioRates', + 'audioSubscriptions', + 'audioCallbacks', + 'audioMeta' + ] as const) { + expect(AudioManager[field]).not.toHaveProperty([completedKey]); + expect(AudioManager[field]).not.toHaveProperty([liveKey]); + } + expect(AudioManager['audioUris'][otherKey]).toBe('file://x'); + expect(AudioManager['audioMeta'][otherKey]).toEqual({ msgId: 'c', rid: 'other' }); + jest.spyOn(AudioManager, 'getNextAudioMessage').mockResolvedValue({ id: 'a', attachments: [{}] } as TMessageModel); + const playAudio = jest.spyOn(AudioManager, 'playAudio'); + await AudioManager.playNextAudioInSequence(liveKey); + expect(playAudio).not.toHaveBeenCalled(); + }); +}); diff --git a/app/lib/methods/AudioManager.ts b/app/lib/methods/AudioManager.ts index fbd2a388147..9972160441c 100644 --- a/app/lib/methods/AudioManager.ts +++ b/app/lib/methods/AudioManager.ts @@ -1,4 +1,4 @@ -import { type AVPlaybackStatus, Audio } from 'expo-av'; +import { createAudioPlayer, setAudioModeAsync, type AudioPlayer, type AudioStatus } from 'expo-audio'; import { Q } from '@nozbe/watermelondb'; import dayjs from '../dayjs'; @@ -6,18 +6,31 @@ import { getMessageById } from '../database/services/Message'; import database from '../database'; import { getFilePathAudio } from './getFilePathAudio'; import { type TMessageModel } from '~/definitions'; -import { AUDIO_MODE } from '../constants/audio'; import { emitter } from './helpers'; +import log from './helpers/log'; +import { AUDIO_MODE } from '../constants/audio'; const getAudioKey = ({ msgId, rid, uri }: { msgId?: string; rid: string; uri: string }) => `${msgId}-${rid}-${uri}`; class AudioManagerClass { - private audioQueue: { [audioKey: string]: Audio.Sound }; + private audioQueue: { [audioKey: string]: AudioPlayer }; + private audioUris: { [audioKey: string]: string }; + private audioPositions: { [audioKey: string]: number }; + private audioRates: { [audioKey: string]: number }; + private audioSubscriptions: { [audioKey: string]: () => void }; + private audioCallbacks: { [audioKey: string]: (status: AudioStatus) => void }; + private audioMeta: { [audioKey: string]: { msgId?: string; rid: string } }; private audioPlaying: string; private audiosRendered: Set; constructor() { this.audioQueue = {}; + this.audioUris = {}; + this.audioPositions = {}; + this.audioRates = {}; + this.audioSubscriptions = {}; + this.audioCallbacks = {}; + this.audioMeta = {}; this.audioPlaying = ''; this.audiosRendered = new Set(); } @@ -30,72 +43,119 @@ class AudioManagerClass { this.audiosRendered.delete(audioKey); }; - async loadAudio({ msgId, rid, uri }: { rid: string; msgId?: string; uri: string }): Promise { + loadAudio({ msgId, rid, uri }: { rid: string; msgId?: string; uri: string }): string { const audioKey = getAudioKey({ msgId, rid, uri }); this.addAudioRendered(audioKey); + this.audioUris[audioKey] = uri; + this.audioMeta[audioKey] = { msgId, rid }; if (this.audioQueue[audioKey]) { return audioKey; } - const { sound } = await Audio.Sound.createAsync({ uri }, { androidImplementation: 'MediaPlayer' }); + + const sound = createAudioPlayer({ uri }); this.audioQueue[audioKey] = sound; return audioKey; } async playAudio(audioKey: string) { if (this.audioPlaying) { - await this.pauseAudio(); + this.pauseAudio(); + } + + // If player was released, recreate it + if (!this.audioQueue[audioKey] && this.audioUris[audioKey]) { + const sound = createAudioPlayer({ uri: this.audioUris[audioKey] }); + this.audioQueue[audioKey] = sound; + + if (this.audioPositions[audioKey] !== undefined) { + await sound.seekTo(this.audioPositions[audioKey]); + } + + // Re-register the callback if it exists + if (this.audioCallbacks[audioKey]) { + const sub = sound.addListener('playbackStatusUpdate', status => { + this.onPlaybackStatusUpdate(audioKey, status, this.audioCallbacks[audioKey]); + }); + if (sub) this.audioSubscriptions[audioKey] = () => sub.remove?.(); + } + } + + try { + await setAudioModeAsync(AUDIO_MODE); + this.audioPlaying = audioKey; + this.setRate(audioKey, this.audioRates[audioKey]); + this.audioQueue[audioKey]?.play(); + emitter.emit('audioFocused', audioKey); + } catch (error) { + log(error); } - await Audio.setAudioModeAsync(AUDIO_MODE); - await this.audioQueue[audioKey]?.playAsync(); - this.audioPlaying = audioKey; - emitter.emit('audioFocused', audioKey); } - async pauseAudio() { + pauseAudio(): void { if (this.audioPlaying) { - await this.audioQueue[this.audioPlaying]?.pauseAsync(); + this.audioQueue[this.audioPlaying]?.pause(); this.audioPlaying = ''; } } async setPositionAsync(audioKey: string, time: number) { + this.audioPositions[audioKey] = time; try { - await this.audioQueue[audioKey]?.setPositionAsync(time); - } catch { - // Do nothing + await this.audioQueue[audioKey]?.seekTo(time); + } catch (error) { + log(error); } } - async setRateAsync(audioKey: string, value = 1.0) { + setRate(audioKey: string, value = 1.0): void { + this.audioRates[audioKey] = value; + if (!audioKey || this.audioPlaying !== audioKey) { + return; + } + try { - await this.audioQueue[audioKey].setRateAsync(value, true); - } catch { - // Do nothing + const player = this.audioQueue[audioKey]; + if (!player) return; + player.shouldCorrectPitch = true; + player.setPlaybackRate(value, 'medium'); + } catch (error) { + log(error); } } - onPlaybackStatusUpdate(audioKey: string, status: AVPlaybackStatus, callback: (status: AVPlaybackStatus) => void) { + onPlaybackStatusUpdate(audioKey: string, status: AudioStatus, callback: (status: AudioStatus) => void): void { if (status) { callback(status); this.onEnd(audioKey, status); } } - setOnPlaybackStatusUpdate(audioKey: string, callback: (status: AVPlaybackStatus) => void): void { - return this.audioQueue[audioKey]?.setOnPlaybackStatusUpdate(status => - this.onPlaybackStatusUpdate(audioKey, status, callback) - ); + setOnPlaybackStatusUpdate(audioKey: string, callback: (status: AudioStatus) => void): void { + this.audioCallbacks[audioKey] = callback; + this.audioSubscriptions[audioKey]?.(); + const sub = this.audioQueue[audioKey]?.addListener('playbackStatusUpdate', status => { + this.onPlaybackStatusUpdate(audioKey, status, callback); + }); + if (sub) this.audioSubscriptions[audioKey] = () => sub.remove?.(); } - async onEnd(audioKey: string, status: AVPlaybackStatus) { + async onEnd(audioKey: string, status: AudioStatus): Promise { + if (!this.audioQueue[audioKey]) { + return; + } + if (status.isLoaded && status.didJustFinish) { try { - await this.audioQueue[audioKey]?.stopAsync(); + this.audioSubscriptions[audioKey]?.(); + delete this.audioSubscriptions[audioKey]; + this.audioQueue[audioKey].release(); + delete this.audioQueue[audioKey]; + this.audioPositions[audioKey] = 0; this.audioPlaying = ''; emitter.emit('audioFocused', ''); await this.playNextAudioInSequence(audioKey); - } catch { - // do nothing + } catch (error) { + log(error); } } } @@ -127,7 +187,6 @@ class AudioManagerClass { } else { whereClause.push(Q.where('rid', rid), Q.where('tmid', null)); } - const [message] = await db .get('messages') .query(...whereClause) @@ -143,7 +202,7 @@ class AudioManagerClass { const nextMessage = await this.getNextAudioMessage(msgId, rid); if (nextMessage && nextMessage.attachments) { const nextAudioInSeqKey = this.getNextAudioKey({ message: nextMessage, rid }); - if (nextAudioInSeqKey && this.audioQueue?.[nextAudioInSeqKey] && this.audiosRendered.has(nextAudioInSeqKey)) { + if (nextAudioInSeqKey && this.audioUris[nextAudioInSeqKey] && this.audiosRendered.has(nextAudioInSeqKey)) { await this.playAudio(nextAudioInSeqKey); } } @@ -153,20 +212,23 @@ class AudioManagerClass { if (!rid) { return; } - const regExp = new RegExp(rid); - const roomAudioKeysLoaded = Object.keys(this.audioQueue).filter(audioKey => regExp.test(audioKey)); + const roomAudioKeysLoaded = Object.keys(this.audioMeta).filter(audioKey => this.audioMeta[audioKey].rid === rid); const roomAudiosLoaded = roomAudioKeysLoaded.map(key => this.audioQueue[key]); try { - await Promise.all( - roomAudiosLoaded.map(async audio => { - await audio?.stopAsync(); - await audio?.unloadAsync(); - }) - ); - } catch { - // Do nothing - } - roomAudioKeysLoaded.forEach(key => delete this.audioQueue[key]); + await Promise.all(roomAudiosLoaded.map(audio => audio?.release())); + } catch (error) { + log(error); + } + roomAudioKeysLoaded.forEach(key => { + this.audioSubscriptions[key]?.(); + delete this.audioSubscriptions[key]; + delete this.audioCallbacks[key]; + delete this.audioQueue[key]; + delete this.audioUris[key]; + delete this.audioPositions[key]; + delete this.audioRates[key]; + delete this.audioMeta[key]; + }); this.audioPlaying = ''; } } diff --git a/app/lib/native/NativeVoip.ts b/app/lib/native/NativeVoip.ts index 4aa6636b53d..e79e64861df 100644 --- a/app/lib/native/NativeVoip.ts +++ b/app/lib/native/NativeVoip.ts @@ -74,7 +74,7 @@ export interface Spec extends TurboModule { /** * Plays the outgoing-call ringback (dialtone) on the voice-communication audio path so it * follows the active comm device (earpiece/speaker/BT) instead of the music stream. - * Idempotent: a second call while playing is a no-op. iOS no-op (CallKit + AVAudioSession handle routing for expo-av). + * Idempotent: a second call while playing is a no-op. iOS no-op (CallKit + AVAudioSession handle routing for expo-audio). */ startRingback(): Promise; diff --git a/app/lib/services/voip/playCallEndedSound.test.ts b/app/lib/services/voip/playCallEndedSound.test.ts index 577fefa1697..cbaae6f71cc 100644 --- a/app/lib/services/voip/playCallEndedSound.test.ts +++ b/app/lib/services/voip/playCallEndedSound.test.ts @@ -6,156 +6,140 @@ jest.mock('~/lib/methods/helpers/log', () => ({ default: (...args: unknown[]) => mockLog(...args) })); -// Mock expo-av at the test boundary, matching the style used in the VoIP services directory. -const mockLoadAsync = jest.fn(() => Promise.resolve()); -const mockPlayAsync = jest.fn(() => Promise.resolve()); -const mockUnloadAsync = jest.fn(() => Promise.resolve()); -type MockPlaybackStatus = { isLoaded: boolean; didJustFinish?: boolean }; -let capturedPlaybackStatusUpdate: ((status: MockPlaybackStatus) => void) | null = null; - -const mockSetOnPlaybackStatusUpdate = jest.fn((cb: (status: MockPlaybackStatus) => void) => { - capturedPlaybackStatusUpdate = cb; -}); +// Access the global mock from jest.setup.js - it provides a consistent mock player +// eslint-disable-next-line @typescript-eslint/no-var-requires +const expoAudio = require('expo-audio'); -const mockSoundInstance = { - loadAsync: mockLoadAsync, - playAsync: mockPlayAsync, - unloadAsync: mockUnloadAsync, - setOnPlaybackStatusUpdate: mockSetOnPlaybackStatusUpdate -}; - -jest.mock('expo-av', () => ({ - Audio: { - Sound: jest.fn(() => mockSoundInstance) - } -})); +const mockCreateAudioPlayer = expoAudio.createAudioPlayer as jest.Mock; + +const getActualPlayer = () => mockCreateAudioPlayer.mock.results[0].value; beforeEach(() => { jest.clearAllMocks(); - capturedPlaybackStatusUpdate = null; resetPlayCallEndedSoundForTesting(); mockLog.mockClear(); + mockCreateAudioPlayer.mockClear(); }); describe('playCallEndedSound', () => { - it('triggers playback when invoked', async () => { - await playCallEndedSound(); + it('creates a player and starts playback when invoked', () => { + playCallEndedSound(); - expect(mockLoadAsync).toHaveBeenCalledTimes(1); - expect(mockPlayAsync).toHaveBeenCalledTimes(1); + expect(mockCreateAudioPlayer).toHaveBeenCalledTimes(1); + expect(getActualPlayer().play).toHaveBeenCalledTimes(1); }); - it('registers a playback status callback', async () => { - await playCallEndedSound(); + it('registers a playback status listener', () => { + playCallEndedSound(); - expect(mockSetOnPlaybackStatusUpdate).toHaveBeenCalledTimes(1); + expect(getActualPlayer().addListener).toHaveBeenCalledWith('playbackStatusUpdate', expect.any(Function)); }); - it('unloads the player when didJustFinish fires', async () => { - await playCallEndedSound(); + it('removes the player when didJustFinish fires', () => { + playCallEndedSound(); + + const actualPlayer = getActualPlayer(); + const statusCallback = actualPlayer.addListener.mock.calls.find( + ([event]: [string, unknown]) => event === 'playbackStatusUpdate' + )?.[1]; + expect(statusCallback).toBeDefined(); - expect(capturedPlaybackStatusUpdate).not.toBeNull(); - capturedPlaybackStatusUpdate!({ isLoaded: true, didJustFinish: true }); + statusCallback({ isLoaded: true, didJustFinish: true }); - expect(mockUnloadAsync).toHaveBeenCalledTimes(1); + expect(actualPlayer.release).toHaveBeenCalledTimes(1); }); - it('does not unload when didJustFinish is false', async () => { - await playCallEndedSound(); + it('does not remove when didJustFinish is false', () => { + playCallEndedSound(); - capturedPlaybackStatusUpdate!({ isLoaded: true, didJustFinish: false }); + const actualPlayer = getActualPlayer(); + const addListenerCalls = actualPlayer.addListener.mock.calls; + const statusCallback = addListenerCalls.find(([event]: [string, unknown]) => event === 'playbackStatusUpdate')?.[1]; + statusCallback?.({ isLoaded: true, didJustFinish: false }); - expect(mockUnloadAsync).not.toHaveBeenCalled(); + expect(actualPlayer.release).not.toHaveBeenCalled(); }); - it('double-invocation while first is in flight does not produce overlapping playback', async () => { - // First invocation — loadAsync resolves immediately in mock but we can test - // the coalescing: a second call while the first play is underway must not call - // loadAsync/playAsync a second time. - let resolveLoad!: () => void; - mockLoadAsync.mockImplementationOnce( - () => - new Promise(res => { - resolveLoad = res; - }) - ); - - // Start first invocation (hangs at loadAsync) - const first = playCallEndedSound(); + it('double-invocation while first is in flight does not produce overlapping playback', () => { + // Start first invocation + playCallEndedSound(); // Second invocation while first is still loading — must be a no-op - const second = playCallEndedSound(); + playCallEndedSound(); - // Resolve the first load - resolveLoad(); - await first; - await second; - - // loadAsync and playAsync must each have been called exactly once - expect(mockLoadAsync).toHaveBeenCalledTimes(1); - expect(mockPlayAsync).toHaveBeenCalledTimes(1); + // createAudioPlayer and play must each have been called exactly once (coalescing) + expect(mockCreateAudioPlayer).toHaveBeenCalledTimes(1); + expect(getActualPlayer().play).toHaveBeenCalledTimes(1); }); - it('allows a new invocation after the previous cue completes', async () => { - await playCallEndedSound(); + it('allows a new invocation after the previous cue completes', () => { + playCallEndedSound(); + // Simulate completion - capturedPlaybackStatusUpdate!({ isLoaded: true, didJustFinish: true }); + const actualPlayer = getActualPlayer(); + const addListenerCalls = actualPlayer.addListener.mock.calls; + const statusCallback = addListenerCalls.find(([event]: [string, unknown]) => event === 'playbackStatusUpdate')?.[1]; + statusCallback?.({ isLoaded: true, didJustFinish: true }); // Reset mocks to count fresh calls - mockLoadAsync.mockClear(); - mockPlayAsync.mockClear(); - mockSetOnPlaybackStatusUpdate.mockClear(); - capturedPlaybackStatusUpdate = null; + mockCreateAudioPlayer.mockClear(); - await playCallEndedSound(); + playCallEndedSound(); - expect(mockLoadAsync).toHaveBeenCalledTimes(1); - expect(mockPlayAsync).toHaveBeenCalledTimes(1); + expect(mockCreateAudioPlayer).toHaveBeenCalledTimes(1); + expect(getActualPlayer().play).toHaveBeenCalledTimes(1); }); - it('watchdog releases the lock if didJustFinish never fires', async () => { + it('watchdog releases the lock if didJustFinish never fires', () => { jest.useFakeTimers(); try { - await playCallEndedSound(); + playCallEndedSound(); + + const actualPlayer = getActualPlayer(); // didJustFinish never fires; lock would be permanent without the watchdog. - expect(mockUnloadAsync).not.toHaveBeenCalled(); + expect(actualPlayer.release).not.toHaveBeenCalled(); jest.advanceTimersByTime(5000); - // Watchdog forces unload + lock release. - expect(mockUnloadAsync).toHaveBeenCalledTimes(1); + // Watchdog forces remove + lock release. + expect(actualPlayer.release).toHaveBeenCalledTimes(1); // Subsequent invocation is allowed (lock cleared). - mockLoadAsync.mockClear(); - mockPlayAsync.mockClear(); - await playCallEndedSound(); - expect(mockLoadAsync).toHaveBeenCalledTimes(1); - expect(mockPlayAsync).toHaveBeenCalledTimes(1); + mockCreateAudioPlayer.mockClear(); + playCallEndedSound(); + expect(mockCreateAudioPlayer).toHaveBeenCalledTimes(1); + expect(getActualPlayer().play).toHaveBeenCalledTimes(1); } finally { jest.useRealTimers(); } }); - it('didJustFinish clears the watchdog so it does not double-unload', async () => { + it('didJustFinish clears the watchdog so it does not double-remove', () => { jest.useFakeTimers(); try { - await playCallEndedSound(); - capturedPlaybackStatusUpdate!({ isLoaded: true, didJustFinish: true }); - expect(mockUnloadAsync).toHaveBeenCalledTimes(1); + playCallEndedSound(); + + const actualPlayer = getActualPlayer(); + const addListenerCalls = actualPlayer.addListener.mock.calls; + const statusCallback = addListenerCalls.find(([event]: [string, unknown]) => event === 'playbackStatusUpdate')?.[1]; + statusCallback?.({ isLoaded: true, didJustFinish: true }); + expect(actualPlayer.release).toHaveBeenCalledTimes(1); // Watchdog must not fire after natural completion. jest.advanceTimersByTime(10000); - expect(mockUnloadAsync).toHaveBeenCalledTimes(1); + expect(actualPlayer.release).toHaveBeenCalledTimes(1); } finally { jest.useRealTimers(); } }); - it('calls log when loadAsync throws', async () => { + it('calls log when createAudioPlayer throws', () => { const err = new Error('E_LOAD_FAILED'); - mockLoadAsync.mockRejectedValueOnce(err); + mockCreateAudioPlayer.mockImplementationOnce(() => { + throw err; + }); - await playCallEndedSound(); + playCallEndedSound(); expect(mockLog).toHaveBeenCalledWith(err); }); diff --git a/app/lib/services/voip/playCallEndedSound.ts b/app/lib/services/voip/playCallEndedSound.ts index a7625e8df6d..c1d6276ca7d 100644 --- a/app/lib/services/voip/playCallEndedSound.ts +++ b/app/lib/services/voip/playCallEndedSound.ts @@ -1,10 +1,11 @@ -import { Audio } from 'expo-av'; +import { createAudioPlayer } from 'expo-audio'; import log from '~/lib/methods/helpers/log'; // Module-scoped state so it survives React tree unmounts and is safe to call // fire-and-forget from any termination path. let isPlaying = false; +let currentPlayer: ReturnType | null = null; let watchdogTimer: ReturnType | null = null; // Cue is ~1.5s; 5s is generous slack before we assume the player is wedged. @@ -12,6 +13,7 @@ const WATCHDOG_MS = 5000; function releaseLock(): void { isPlaying = false; + currentPlayer = null; if (watchdogTimer != null) { clearTimeout(watchdogTimer); watchdogTimer = null; @@ -28,7 +30,7 @@ function releaseLock(): void { * the player or the audio session is interrupted indefinitely), so future * cues aren't permanently blocked. */ -export async function playCallEndedSound(): Promise { +export function playCallEndedSound(): void { if (isPlaying) { return; } @@ -36,26 +38,22 @@ export async function playCallEndedSound(): Promise { isPlaying = true; try { - const sound = new Audio.Sound(); + currentPlayer = createAudioPlayer(require('~/containers/Ringer/call-ended.mp3')); - sound.setOnPlaybackStatusUpdate(status => { + currentPlayer.addListener('playbackStatusUpdate', status => { if (status.isLoaded && status.didJustFinish) { + const player = currentPlayer; releaseLock(); - sound.unloadAsync().catch(() => { - // best-effort unload - }); + player?.release(); } }); - // eslint-disable-next-line @typescript-eslint/no-require-imports - await sound.loadAsync(require('~/containers/Ringer/call-ended.mp3')); - await sound.playAsync(); + currentPlayer.play(); watchdogTimer = setTimeout(() => { + const player = currentPlayer; releaseLock(); - sound.unloadAsync().catch(() => { - // best-effort unload - }); + player?.release(); }, WATCHDOG_MS); } catch (error) { // Never throw — this is fire-and-forget diff --git a/app/sagas/__tests__/deepLinking.test.ts b/app/sagas/__tests__/deepLinking.test.ts index dfbcd55c3b7..be257131f23 100644 --- a/app/sagas/__tests__/deepLinking.test.ts +++ b/app/sagas/__tests__/deepLinking.test.ts @@ -78,7 +78,7 @@ jest.mock('i18n-js', () => ({ default: { t: (k: string) => k } })); -jest.mock('../../lib/methods/helpers/info', () => ({ +jest.mock('~/lib/methods/helpers/info', () => ({ showConfirmationAlert: jest.fn(({ onPress }: { onPress: () => void }) => onPress()) })); diff --git a/app/views/AttachmentView.test.tsx b/app/views/AttachmentView.test.tsx index 701b0cb9375..d44fed8c0d1 100644 --- a/app/views/AttachmentView.test.tsx +++ b/app/views/AttachmentView.test.tsx @@ -18,11 +18,6 @@ jest.mock('react-native-safe-area-context', () => ({ useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }) })); -jest.mock('expo-av', () => ({ - ResizeMode: { CONTAIN: 'contain' }, - Video: () => null -})); - jest.mock('expo-file-system/legacy', () => ({ deleteAsync: jest.fn() })); diff --git a/app/views/AttachmentView.tsx b/app/views/AttachmentView.tsx index 52e3368c460..5c821df148e 100644 --- a/app/views/AttachmentView.tsx +++ b/app/views/AttachmentView.tsx @@ -1,7 +1,6 @@ import { CameraRoll } from '@react-native-camera-roll/camera-roll'; import { useHeaderHeight } from '@react-navigation/elements'; -import { ResizeMode, Video } from 'expo-av'; -import { useCallback, useLayoutEffect, useRef, useState, type Dispatch, type SetStateAction, type ReactElement } from 'react'; +import { useCallback, useLayoutEffect, useState, type Dispatch, type SetStateAction, type ReactElement } from 'react'; import { PermissionsAndroid, useWindowDimensions, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { shallowEqual } from 'react-redux'; @@ -12,13 +11,14 @@ import RCActivityIndicator from '../containers/ActivityIndicator'; import AltTextLabel from '../containers/AltTextLabel'; import * as HeaderButton from '../containers/Header/components/HeaderButton'; import { ImageViewer } from '../containers/ImageViewer'; +import VideoPlayer from '../containers/VideoPlayer'; import { LISTENER } from '../containers/Toast'; import { type IAttachment } from '../definitions'; import I18n from '../i18n'; import { useAltTextSupported } from '../lib/hooks/useAltTextSupported'; import { useAppSelector } from '../lib/hooks/useAppSelector'; import { useAppNavigation, useAppRoute } from '../lib/hooks/navigation'; -import { encodeAttachmentUrl, formatAttachmentUrl, isAndroid, showErrorAlert } from '../lib/methods/helpers'; +import { encodeAttachmentUrl, formatAttachmentUrl, isAndroid } from '../lib/methods/helpers'; import { fileDownload } from '../lib/methods/helpers/fileDownload'; import EventEmitter from '../lib/methods/helpers/events'; import { getUserSelector } from '../selectors/login'; @@ -35,11 +35,9 @@ const RenderContent = ({ attachment: IAttachment; altText?: string; }) => { - const videoRef = useRef ); } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 65503ee5460..a6270f6acc9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -33,9 +33,6 @@ PODS: - DoubleConversion (1.1.6) - EXApplication (7.0.8): - ExpoModulesCore - - EXAV (16.0.8): - - ExpoModulesCore - - ReactCommon/turbomodule/core - EXConstants (18.0.12): - ExpoModulesCore - EXNotifications (0.32.15): @@ -75,6 +72,8 @@ PODS: - ExpoModulesCore - ExpoAsset (12.0.12): - ExpoModulesCore + - ExpoAudio (0.4.9): + - ExpoModulesCore - ExpoCamera (17.0.10): - ExpoModulesCore - ZXingObjC/OneD @@ -133,6 +132,8 @@ PODS: - ExpoModulesCore - ExpoSystemUI (6.0.9): - ExpoModulesCore + - ExpoVideo (3.0.16): + - ExpoModulesCore - ExpoVideoThumbnails (10.0.8): - ExpoModulesCore - ExpoWebBrowser (15.0.10): @@ -3570,12 +3571,12 @@ DEPENDENCIES: - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXApplication (from `../node_modules/expo-application/ios`) - - EXAV (from `../node_modules/expo-av/ios`) - EXConstants (from `../node_modules/expo-constants/ios`) - EXNotifications (from `../node_modules/expo-notifications/ios`) - Expo (from `../node_modules/expo`) - ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`) - ExpoAsset (from `../node_modules/expo-asset/ios`) + - ExpoAudio (from `../node_modules/expo-audio/ios`) - ExpoCamera (from `../node_modules/expo-camera/ios`) - ExpoDevice (from `../node_modules/expo-device/ios`) - ExpoDocumentPicker (from `../node_modules/expo-document-picker/ios`) @@ -3588,6 +3589,7 @@ DEPENDENCIES: - ExpoModulesCore (from `../node_modules/expo-modules-core`) - "ExpoQuickLook (from `../node_modules/@magrinj/expo-quick-look/ios`)" - ExpoSystemUI (from `../node_modules/expo-system-ui/ios`) + - ExpoVideo (from `../node_modules/expo-video/ios`) - ExpoVideoThumbnails (from `../node_modules/expo-video-thumbnails/ios`) - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) @@ -3741,8 +3743,6 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXApplication: :path: "../node_modules/expo-application/ios" - EXAV: - :path: "../node_modules/expo-av/ios" EXConstants: :path: "../node_modules/expo-constants/ios" EXNotifications: @@ -3753,6 +3753,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-apple-authentication/ios" ExpoAsset: :path: "../node_modules/expo-asset/ios" + ExpoAudio: + :path: "../node_modules/expo-audio/ios" ExpoCamera: :path: "../node_modules/expo-camera/ios" ExpoDevice: @@ -3777,6 +3779,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@magrinj/expo-quick-look/ios" ExpoSystemUI: :path: "../node_modules/expo-system-ui/ios" + ExpoVideo: + :path: "../node_modules/expo-video/ios" ExpoVideoThumbnails: :path: "../node_modules/expo-video-thumbnails/ios" ExpoWebBrowser: @@ -3997,12 +4001,12 @@ SPEC CHECKSUMS: BVLinearGradient: 7815a70ab485b7b155186dd0cc836363e0288cad DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb EXApplication: 1e98d4b1dccdf30627f92917f4b2c5a53c330e5f - EXAV: b60fcf142fae6684d295bc28cd7cfcb3335570ea EXConstants: 805f35b1b295c542ca6acce836f21a1f9ee104d5 EXNotifications: 983f04ad4ad879b181179e326bf220541e478386 Expo: 6118272de314ed3d4bc74de3b1229b8f5b324ef1 ExpoAppleAuthentication: 9413ae9a5e631a424cc04c1b13b0694dbab7d594 ExpoAsset: f867e55ceb428aab99e1e8c082b5aee7c159ea18 + ExpoAudio: d98f922b17a945cd33f8462d086278dbd810c537 ExpoCamera: 6a326deb45ba840749652e4c15198317aa78497e ExpoDevice: 6327c3c200816795708885adf540d26ecab83d1a ExpoDocumentPicker: 7cd9e71a0f66fb19eb0a586d6f26eee1284692e0 @@ -4015,6 +4019,7 @@ SPEC CHECKSUMS: ExpoModulesCore: 91a57f1d109cf53fe58d44fcf6b68a777561549a ExpoQuickLook: 7ac65cd559a74619ea41e13df20e9d7a61e79ea6 ExpoSystemUI: 2ad325f361a2fcd96a464e8574e19935c461c9cc + ExpoVideo: 8d9e8a1acd0c4c210e538b4355e34ae8ff5e04f7 ExpoVideoThumbnails: 503a79271416c8723f04b55ea4737282513ebe4f ExpoWebBrowser: 17b064c621789e41d4816c95c93f429b84971f52 fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 diff --git a/jest.setup.js b/jest.setup.js index 4243cbabb12..69aa7056068 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -90,121 +90,54 @@ jest.mock('expo-font', () => ({ __esModule: true })); -jest.mock('expo-av', () => { - const InterruptionModeAndroid = { - DoNotMix: 1, - DuckOthers: 2 - }; - const InterruptionModeIOS = { - DoNotMix: 1, - DuckOthers: 2, - MixWithOthers: 3 +jest.mock('expo-audio', () => { + const recorderState = { + canRecord: true, + isRecording: false, + durationMillis: 0, + mediaServicesDidReset: false, + url: null }; return { - Audio: { - getPermissionsAsync: jest.fn(() => Promise.resolve({ status: 'granted', granted: true, canAskAgain: true })), - requestPermissionsAsync: jest.fn(() => Promise.resolve({ status: 'granted', granted: true, canAskAgain: true })), - setAudioModeAsync: jest.fn(() => Promise.resolve()), - Recording: jest.fn(() => ({ - prepareToRecordAsync: jest.fn(() => Promise.resolve()), - startAsync: jest.fn(() => Promise.resolve()), - stopAndUnloadAsync: jest.fn(() => Promise.resolve()), - setOnRecordingStatusUpdate: jest.fn(), - getStatusAsync: jest.fn(() => Promise.resolve()) - })), - Sound: { - createAsync: jest.fn(() => - Promise.resolve({ - sound: { - setOnPlaybackStatusUpdate: jest.fn(), - playAsync: jest.fn(() => Promise.resolve()), - pauseAsync: jest.fn(() => Promise.resolve()), - stopAsync: jest.fn(() => Promise.resolve()), - unloadAsync: jest.fn(() => Promise.resolve()), - getStatusAsync: jest.fn(() => Promise.resolve()), - setPositionAsync: jest.fn(() => Promise.resolve()) - }, - status: {} - }) - ), - create: jest.fn(() => ({ - setOnPlaybackStatusUpdate: jest.fn(), - playAsync: jest.fn(() => Promise.resolve()), - pauseAsync: jest.fn(() => Promise.resolve()), - stopAsync: jest.fn(() => Promise.resolve()), - unloadAsync: jest.fn(() => Promise.resolve()), - getStatusAsync: jest.fn(() => Promise.resolve()), - setPositionAsync: jest.fn(() => Promise.resolve()), - loadAsync: jest.fn(() => Promise.resolve()) - })) - }, - RecordingStatus: { - StatusDict: {} - }, - AudioStatus: { - StatusDict: {} - }, - AndroidOutputFormat: { - AAC_ADTS: 0 - }, - AndroidAudioEncoder: { - AAC: 0 - }, - IOSAudioQuality: { - LOW: 0, - MEDIUM: 1, - HIGH: 2 - }, - IOSOutputFormat: { - MPEG4AAC: 0 - }, - RecordingOptionsPresets: { - LOW_QUALITY: { - android: { - extension: '.aac', - outputFormat: 0, - audioEncoder: 0, - sampleRate: 16000, - numberOfChannels: 1, - bitRate: 64000 - }, - ios: { - extension: '.aac', - audioQuality: 1, - outputFormat: 0, - sampleRate: 16000, - numberOfChannels: 1, - bitRate: 64000 - }, - web: {} - }, - HIGH_QUALITY: { - android: { - extension: '.aac', - outputFormat: 0, - audioEncoder: 0, - sampleRate: 48000, - numberOfChannels: 2, - bitRate: 128000 - }, - ios: { - extension: '.aac', - audioQuality: 1, - outputFormat: 0, - sampleRate: 48000, - numberOfChannels: 2, - bitRate: 128000 - }, - web: {} - } - } - }, - InterruptionModeAndroid, - InterruptionModeIOS + PermissionStatus: { GRANTED: 'granted', DENIED: 'denied', UNDETERMINED: 'undetermined' }, + AudioQuality: { MEDIUM: 0x40 }, + IOSOutputFormat: { MPEG4AAC: 'aac ' }, + createAudioPlayer: jest.fn(() => ({ + play: jest.fn(), + pause: jest.fn(), + setPlaybackRate: jest.fn(), + seekTo: jest.fn(() => Promise.resolve()), + release: jest.fn(), + addListener: jest.fn(() => ({ remove: jest.fn() })), + playing: false, + loop: false, + isLoaded: false, + shouldCorrectPitch: false + })), + setAudioModeAsync: jest.fn(() => Promise.resolve()), + requestRecordingPermissionsAsync: jest.fn(() => Promise.resolve({ status: 'granted', granted: true, canAskAgain: true })), + getRecordingPermissionsAsync: jest.fn(() => Promise.resolve({ status: 'granted', granted: true, canAskAgain: true })), + useAudioRecorder: jest.fn(() => ({ + uri: null, + stop: jest.fn(() => Promise.resolve()), + record: jest.fn(), + prepareToRecordAsync: jest.fn(() => Promise.resolve()), + getStatus: jest.fn(() => ({ ...recorderState })) + })), + useAudioRecorderState: jest.fn(() => ({ ...recorderState })) }; }); +jest.mock('expo-video', () => ({ + useVideoPlayer: jest.fn(() => ({ + play: jest.fn(), + pause: jest.fn(), + addListener: jest.fn(() => ({ remove: jest.fn() })) + })), + VideoView: jest.fn(() => null) +})); + jest.mock('./app/lib/methods/search', () => ({ search: () => [] })); diff --git a/package.json b/package.json index b8b3ac24c10..9a4243c12a2 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "eslint-import-resolver-typescript": "^4.4.4", "expo": "^54.0.0", "expo-apple-authentication": "~8.0.8", - "expo-av": "~16.0.8", + "expo-audio": "~0.4.9", "expo-camera": "~17.0.10", "expo-device": "^8.0.10", "expo-document-picker": "~14.0.8", @@ -77,6 +77,7 @@ "expo-notifications": "~0.32.15", "expo-status-bar": "~3.0.9", "expo-system-ui": "~6.0.9", + "expo-video": "~3.0.16", "expo-video-thumbnails": "~10.0.8", "expo-web-browser": "~15.0.10", "hoist-non-react-statics": "3.3.2", diff --git a/patches/expo-audio+0.4.9.patch b/patches/expo-audio+0.4.9.patch new file mode 100644 index 00000000000..7b6f7a2961e --- /dev/null +++ b/patches/expo-audio+0.4.9.patch @@ -0,0 +1,24 @@ +diff --git a/node_modules/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt b/node_modules/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt +index c9fd366..5c74acf 100644 +--- a/node_modules/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt ++++ b/node_modules/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt +@@ -26,6 +26,7 @@ import androidx.media3.datasource.okhttp.OkHttpDataSource + import androidx.media3.exoplayer.dash.DashMediaSource + import androidx.media3.exoplayer.hls.HlsMediaSource + import androidx.media3.exoplayer.smoothstreaming.SsMediaSource ++import androidx.media3.extractor.DefaultExtractorsFactory + import androidx.media3.exoplayer.source.MediaSource + import androidx.media3.exoplayer.source.ProgressiveMediaSource + import expo.modules.interfaces.permissions.Permissions +@@ -553,7 +554,10 @@ class AudioModule : Module() { + CONTENT_TYPE_SS -> SsMediaSource.Factory(factory) + CONTENT_TYPE_DASH -> DashMediaSource.Factory(factory) + CONTENT_TYPE_HLS -> HlsMediaSource.Factory(factory) +- CONTENT_TYPE_OTHER -> ProgressiveMediaSource.Factory(factory) ++ CONTENT_TYPE_OTHER -> ProgressiveMediaSource.Factory( ++ factory, ++ DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true) ++ ) + else -> throw IllegalStateException("Unsupported type: $type") + }.createMediaSource(mediaItem) + } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b122ee8a96..2153d88cd26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,9 +129,9 @@ importers: expo-apple-authentication: specifier: ~8.0.8 version: 8.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) - expo-av: - specifier: ~16.0.8 - version: 16.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-audio: + specifier: ~0.4.9 + version: 0.4.9(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-camera: specifier: ~17.0.10 version: 17.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -165,6 +165,9 @@ importers: expo-system-ui: specifier: ~6.0.9 version: 6.0.9(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + expo-video: + specifier: ~3.0.16 + version: 3.0.16(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-video-thumbnails: specifier: ~10.0.8 version: 10.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) @@ -4473,16 +4476,12 @@ packages: react: '*' react-native: '*' - expo-av@16.0.8: - resolution: {integrity: sha512-cmVPftGR/ca7XBgs7R6ky36lF3OC0/MM/lpgX/yXqfv0jASTsh7AYX9JxHCwFmF+Z6JEB1vne9FDx4GiLcGreQ==} + expo-audio@0.4.9: + resolution: {integrity: sha512-J4mMYEt2mqRqqwmSsXFylMGlrNWa+MbCzGl1IZBs+smvPAMJ3Ni8fNplzCQ0I9RnRzygKhRwJNpnAVL+n4MuyA==} peerDependencies: expo: '*' react: '*' react-native: '*' - react-native-web: '*' - peerDependenciesMeta: - react-native-web: - optional: true expo-camera@17.0.10: resolution: {integrity: sha512-w1RBw83mAGVk4BPPwNrCZyFop0VLiVSRE3c2V9onWbdFwonpRhzmB4drygG8YOUTl1H3wQvALJHyMPTbgsK1Jg==} @@ -4593,6 +4592,13 @@ packages: peerDependencies: expo: '*' + expo-video@3.0.16: + resolution: {integrity: sha512-H1HlxcHGomZItqisGfW3YL/G9BHtNBfVSimDJcLuyxyU87wFnV8loO9tCjuhufkfh/aTa2sW5BYAjLjg9DvnBQ==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + expo-web-browser@15.0.10: resolution: {integrity: sha512-fvDhW4bhmXAeWFNFiInmsGCK83PAqAcQaFyp/3pE/jbdKmFKoRCWr46uZGIfN4msLK/OODhaQ/+US7GSJNDHJg==} peerDependencies: @@ -12561,7 +12567,7 @@ snapshots: transitivePeerDependencies: - supports-color - expo-av@16.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-audio@0.4.9(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react: 19.1.0 @@ -12674,6 +12680,12 @@ snapshots: dependencies: expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-video@3.0.16(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + dependencies: + expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo-web-browser@15.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): dependencies: expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) From 6978e70154bb9042f0cee686dbbc07652c5534ea Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Tue, 15 Sep 2026 15:30:20 -0300 Subject: [PATCH 006/120] chore: bump react-native-mmkv and react-native-nitro-modules --- android/app/build.gradle | 2 +- ios/Podfile.lock | 22 +++++++++++----------- ios/RocketChat.xcodeproj/project.pbxproj | 4 ++++ metro.config.js | 1 - package.json | 4 ++-- pnpm-lock.yaml | 22 +++++++++++----------- 6 files changed, 29 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index d282923fc23..708ee991747 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -142,7 +142,7 @@ dependencies { implementation project(':watermelondb-jsi') implementation "com.google.code.gson:gson:2.8.9" - implementation "io.github.zhongwuzw:mmkv:2.2.4" + implementation "io.github.zhongwuzw:mmkv:2.4.1" implementation "com.github.bumptech.glide:glide:${rootProject.ext.glideVersion}" implementation 'com.facebook.soloader:soloader:0.10.4' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a6270f6acc9..100f970b291 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -248,9 +248,9 @@ PODS: - libwebp/sharpyuv (1.5.0) - libwebp/webp (1.5.0): - libwebp/sharpyuv - - MMKV (2.2.4): - - MMKVCore (~> 2.2.4) - - MMKVCore (2.2.4) + - MMKV (2.4.0): + - MMKVCore (~> 2.4.0) + - MMKVCore (2.4.0) - MobileCrypto (0.4.0): - boost - DoubleConversion @@ -284,14 +284,14 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) - - NitroMmkv (4.1.2): + - NitroMmkv (4.3.2): - boost - DoubleConversion - fast_float - fmt - glog - hermes-engine - - MMKVCore (= 2.2.4) + - MMKVCore (= 2.4.0) - NitroModules - RCT-Folly - RCT-Folly/Fabric @@ -315,7 +315,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - NitroModules (0.33.9): + - NitroModules (0.37.1): - boost - DoubleConversion - fast_float @@ -4043,12 +4043,12 @@ SPEC CHECKSUMS: libavif: 5f8e715bea24debec477006f21ef9e95432e254d libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 - MMKV: 1a8e7dbce7f9cad02c52e1b1091d07bd843aefaf - MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df + MMKV: 86859fdfa2b0b21db1fd6e48788474a6416a2c77 + MMKVCore: 3d16ce9f7d411e135020915fde98a056859a1efa MobileCrypto: cbe4be11a33b5888e77861de9346fa5d3fccc8ac nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - NitroMmkv: 4a2d747ce97a4ae740b3cb3a3853366813faf831 - NitroModules: 18e41c298d86fc753fc754969fa9f84044e70711 + NitroMmkv: 5d6c3a5cca0e47be9a4dd3f66262dbad0799c087 + NitroModules: f2a165630c12aed5793d6b42b9002c6a649ea04e PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 @@ -4122,7 +4122,7 @@ SPEC CHECKSUMS: React-timing: c39eeb992274aeaeb9f4666dc97a36a31d33fe94 React-utils: 2f9ba0088251788ad66aa1855ff99ed2424024d2 ReactAppDependencyProvider: 1bcd3527ac0390a1c898c114f81ff954be35ed79 - ReactCodegen: 7fadc30654a54b3a0d62df08f55e2211e25486ee + ReactCodegen: e0a2012cda55dfd0d2e626ba5d11929c9ff5986c ReactCommon: 6d0fa86a4510730da7c72560e0ced14258292ab9 ReactNativeIncallManager: dccd3e7499caa3bb73d3acfedf4fb0360f1a87d5 RNBootSplash: 7fcc9a58ae343aeb1a1dd49f9030832fe432c544 diff --git a/ios/RocketChat.xcodeproj/project.pbxproj b/ios/RocketChat.xcodeproj/project.pbxproj index a37be2d721d..7f2c42f4612 100644 --- a/ios/RocketChat.xcodeproj/project.pbxproj +++ b/ios/RocketChat.xcodeproj/project.pbxproj @@ -1285,6 +1285,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/MMKV/MMKV_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", @@ -1319,6 +1320,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MMKV_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", @@ -1442,6 +1444,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/MMKV/MMKV_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", @@ -1476,6 +1479,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MMKV_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", diff --git a/metro.config.js b/metro.config.js index 5b1d7cb76a9..bd22faf1ff1 100644 --- a/metro.config.js +++ b/metro.config.js @@ -14,7 +14,6 @@ const config = { }, resolver: { // When running E2E tests, prioritize .mock.ts files for app code - // Note: react-native-mmkv's internal mock file is disabled via patch-package sourceExts: process.env.RUNNING_E2E_TESTS === 'true' ? ['mock.ts', ...sourceExts] : sourceExts } }; diff --git a/package.json b/package.json index 9a4243c12a2..7f58059d646 100644 --- a/package.json +++ b/package.json @@ -109,9 +109,9 @@ "react-native-localize": "3.7.0", "react-native-math-view": "3.9.5", "react-native-mime-types": "2.3.0", - "react-native-mmkv": "^4.1.2", + "react-native-mmkv": "^4.3.2", "react-native-modal": "13.0.1", - "react-native-nitro-modules": "^0.33.9", + "react-native-nitro-modules": "^0.37.1", "react-native-notifier": "1.6.1", "react-native-picker-select": "9.0.1", "react-native-popover-view": "5.1.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2153d88cd26..4c9f438dcbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -262,14 +262,14 @@ importers: specifier: 2.3.0 version: 2.3.0 react-native-mmkv: - specifier: ^4.1.2 - version: 4.1.2(react-native-nitro-modules@0.33.9(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ^4.3.2 + version: 4.3.2(react-native-nitro-modules@0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-modal: specifier: 13.0.1 version: 13.0.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-nitro-modules: - specifier: ^0.33.9 - version: 0.33.9(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ^0.37.1 + version: 0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-notifier: specifier: 1.6.1 version: 1.6.1(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -6574,8 +6574,8 @@ packages: resolution: {integrity: sha512-9l/kkT1QM0i0xAKkOADkP6jIMhqiS+R/eSKBS/lsUmuMYMqboClyrwTFFZwUcaVIN0SpeH4wOKhYTqCnFgRsEw==} engines: {node: '>= 0.6'} - react-native-mmkv@4.1.2: - resolution: {integrity: sha512-6LHb2DQBXuo96Aues13EugmlWw/HAYuh3KoJoQNrC4JsBwn3J3KiRYAg2mCm5Je0VYq2YsmbgZG7XJwX/WFYZA==} + react-native-mmkv@4.3.2: + resolution: {integrity: sha512-49OAyfkg0/TMWiWELZN6VuVQPZPhizwL4DTmp8b7B1md3dB/s3LH3mGfC3T+lp9W0y/rqxZMEnotLFTIbOAenQ==} peerDependencies: react: '*' react-native: '*' @@ -6587,8 +6587,8 @@ packages: react: '*' react-native: '>=0.65.0' - react-native-nitro-modules@0.33.9: - resolution: {integrity: sha512-BM9C5mCGYYjrc8CDWZZ0anLWU/knH2xaEuFzvzogKTOW6fzgS6mmsCdM3ty+AhImJNSYwK19DLrHaqwnrrwEzw==} + react-native-nitro-modules@0.37.1: + resolution: {integrity: sha512-KpW6EQVQ/bfegpCGxN9Be+ndvsfj56t4IESEvCASu9gWpJa/NDfHpIeIwCOvZQ3eXmLEj6YD4wCLqcC1FJPr2w==} peerDependencies: react: '*' react-native: '*' @@ -15088,11 +15088,11 @@ snapshots: dependencies: mime-db: 1.37.0 - react-native-mmkv@4.1.2(react-native-nitro-modules@0.33.9(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-mmkv@4.3.2(react-native-nitro-modules@0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-nitro-modules: 0.33.9(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-nitro-modules: 0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-modal@13.0.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: @@ -15101,7 +15101,7 @@ snapshots: react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) react-native-animatable: 1.3.3 - react-native-nitro-modules@0.33.9(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-nitro-modules@0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) From f96be07ce638ff54515a4b65db253b77956eb250 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Tue, 15 Sep 2026 15:30:55 -0300 Subject: [PATCH 007/120] chore: drop dead react-native-mmkv patch and tighten its jest mock --- __mocks__/react-native-mmkv.js | 275 +++--------------------- app/lib/methods/userPreferences.test.ts | 156 ++++++++++++-- patches/react-native-mmkv+4.1.2.patch | 26 --- 3 files changed, 164 insertions(+), 293 deletions(-) delete mode 100644 patches/react-native-mmkv+4.1.2.patch diff --git a/__mocks__/react-native-mmkv.js b/__mocks__/react-native-mmkv.js index c0e1cedee61..4ca7eabb782 100644 --- a/__mocks__/react-native-mmkv.js +++ b/__mocks__/react-native-mmkv.js @@ -1,27 +1,20 @@ -// Mock for react-native-mmkv -const { useState, useEffect, useRef, useMemo, useCallback } = require('react'); +// Mock for react-native-mmkv. +// Covers only the surface app/lib/methods/userPreferences.ts uses: createMMKV, useMMKVString, +// and the instance methods it calls. Keep this in sync with that module — a method that exists +// here but not on the real MMKV instance makes Jest pass while the device fails. +const { useState, useEffect, useCallback } = require('react'); // Shared storage between instances with the same id const storageInstances = new Map(); -let defaultInstance = null; -export const Mode = { - SINGLE_PROCESS: 'single-process', - MULTI_PROCESS: 'multi-process' -}; - -// MMKV Instance class class MMKVInstance { constructor(config = {}) { - const { id = 'mmkv.default', path, encryptionKey, mode, readOnly } = config; + const { id = 'mmkv.default', path, encryptionKey, mode } = config; this.id = id; this.path = path; this.encryptionKey = encryptionKey; this.mode = mode; - this.isReadOnly = readOnly || false; - this._size = 0; - // Share storage between instances with the same id if (!storageInstances.has(this.id)) { storageInstances.set(this.id, { storage: new Map(), @@ -34,16 +27,8 @@ class MMKVInstance { this.listeners = instance.listeners; } - get size() { - return this._size; - } - set(key, value) { - if (this.isReadOnly) { - throw new Error('Cannot set value in read-only instance'); - } this.storage.set(key, value); - this._size = this.storage.size; this.notifyListeners(key); } @@ -62,22 +47,13 @@ class MMKVInstance { return typeof value === 'boolean' ? value : undefined; } - getBuffer(key) { - const value = this.storage.get(key); - return value instanceof ArrayBuffer ? value : undefined; - } - contains(key) { return this.storage.has(key); } remove(key) { - if (this.isReadOnly) { - throw new Error('Cannot remove value in read-only instance'); - } const deleted = this.storage.delete(key); if (deleted) { - this._size = this.storage.size; this.notifyListeners(key); } return deleted; @@ -88,39 +64,11 @@ class MMKVInstance { } clearAll() { - if (this.isReadOnly) { - throw new Error('Cannot clear read-only instance'); - } this.storage.clear(); - this._size = 0; - // Notify about clear (pass undefined to indicate clear all) + // The real listener contract passes undefined to mean "everything changed" this.notifyListeners(undefined); } - recrypt(key) { - this.encryptionKey = key; - } - - trim() { - // No-op for mock - } - - importAllFrom(other) { - if (this.isReadOnly) { - throw new Error('Cannot import values into read-only instance'); - } - let count = 0; - for (const [key, value] of other.storage.entries()) { - this.storage.set(key, value); - count++; - } - this._size = this.storage.size; - if (count > 0) { - this.notifyListeners(undefined); - } - return count; - } - addOnValueChangedListener(callback) { this.listeners.push(callback); return { @@ -144,202 +92,37 @@ class MMKVInstance { } } -// Factory function to create MMKV instances export function createMMKV(configuration) { return new MMKVInstance(configuration); } -// Get default instance -function getDefaultMMKVInstance() { - if (defaultInstance == null) { - defaultInstance = createMMKV(); - } - return defaultInstance; -} - -// Top-level functions -export function existsMMKV(id) { - return storageInstances.has(id); -} - -export function deleteMMKV(id) { - const instance = storageInstances.get(id); - instance?.storage.clear(); - instance?.listeners.splice(0); - - const deleted = storageInstances.delete(id); - if (defaultInstance?.id === id) { - defaultInstance = null; - } - return deleted; -} +export function useMMKVString(key, instance) { + const [value, setValue] = useState(() => instance.getString(key)); -// Helper function for configuration comparison -function isConfigurationEqual(left, right) { - if (left == null || right == null) { - return left == null && right == null; - } - - return ( - left.encryptionKey === right.encryptionKey && - left.id === right.id && - left.path === right.path && - left.mode === right.mode && - left.readOnly === right.readOnly + const set = useCallback( + v => { + const newValue = typeof v === 'function' ? v(instance.getString(key)) : v; + if (newValue === undefined) { + instance.remove(key); + return; + } + if (typeof newValue !== 'string') { + throw new Error(`MMKV: Type ${typeof newValue} is not supported!`); + } + instance.set(key, newValue); + }, + [key, instance] ); -} - -/** - * Use the default, shared MMKV instance or a custom instance with configuration - */ -export function useMMKV(configuration) { - const configurationRef = useRef(); - const instanceRef = useRef(); - - if (configuration == null) { - return getDefaultMMKVInstance(); - } - - if (!instanceRef.current || !isConfigurationEqual(configurationRef.current, configuration)) { - configurationRef.current = configuration; - instanceRef.current = createMMKV(configuration); - } - - return instanceRef.current; -} - -/** - * Listen for changes in the given MMKV storage instance - */ -export function useMMKVListener(valueChangedListener, instance) { - const ref = useRef(valueChangedListener); - const mmkv = instance ?? getDefaultMMKVInstance(); useEffect(() => { - ref.current = valueChangedListener; - }, [valueChangedListener]); - - useEffect(() => { - const listener = mmkv.addOnValueChangedListener(changedKey => { - ref.current(changedKey); + setValue(instance.getString(key)); + const listener = instance.addOnValueChangedListener(changedKey => { + if (changedKey === undefined || changedKey === key) { + setValue(instance.getString(key)); + } }); return () => listener.remove(); - }, [mmkv]); -} + }, [key, instance]); -/** - * Get a list of all keys that exist in the given MMKV instance - */ -export function useMMKVKeys(instance) { - const mmkv = instance ?? getDefaultMMKVInstance(); - const [allKeys, setKeys] = useState(() => mmkv.getAllKeys()); - - useEffect(() => { - setKeys(mmkv.getAllKeys()); - }, [mmkv]); - - useMMKVListener(key => { - if (key === undefined) { - setKeys(() => mmkv.getAllKeys()); - return; - } - - const currentlyHasKey = allKeys.includes(key); - const hasKey = mmkv.contains(key); - if (hasKey !== currentlyHasKey) { - setKeys(() => mmkv.getAllKeys()); - } - }, mmkv); - - return allKeys; + return [value, set]; } - -/** - * Create a custom MMKV hook for a specific type - */ -function createMMKVHook(getter, onSet) { - return (key, instance) => { - const mmkv = instance ?? getDefaultMMKVInstance(); - - const [bump, setBump] = useState(0); - const value = useMemo(() => getter(mmkv, key), [mmkv, key, bump]); - - const set = useCallback( - v => { - const newValue = typeof v === 'function' ? v(getter(mmkv, key)) : v; - if (onSet) { - onSet(mmkv, key, newValue); - return; - } - switch (typeof newValue) { - case 'number': - case 'string': - case 'boolean': - mmkv.set(key, newValue); - break; - case 'undefined': - mmkv.remove(key); - break; - case 'object': - if (newValue instanceof ArrayBuffer) { - mmkv.set(key, newValue); - break; - } else { - throw new Error(`MMKV: Type object (${newValue}) is not supported!`); - } - default: - throw new Error(`MMKV: Type ${typeof newValue} is not supported!`); - } - }, - [key, mmkv, onSet] - ); - - useEffect(() => { - const listener = mmkv.addOnValueChangedListener(changedKey => { - if (changedKey === undefined || changedKey === key) { - setBump(b => b + 1); - } - }); - return () => listener.remove(); - }, [key, mmkv]); - - return [value, set]; - }; -} - -/** - * Use the string value of the given key from the given MMKV storage instance - */ -export const useMMKVString = createMMKVHook((instance, key) => instance.getString(key)); - -/** - * Use the number value of the given key from the given MMKV storage instance - */ -export const useMMKVNumber = createMMKVHook((instance, key) => instance.getNumber(key)); - -/** - * Use the boolean value of the given key from the given MMKV storage instance - */ -export const useMMKVBoolean = createMMKVHook((instance, key) => instance.getBoolean(key)); - -/** - * Use the object value (JSON stringified) of the given key from the given MMKV storage instance - */ -export const useMMKVObject = createMMKVHook( - (instance, key) => { - const stored = instance.getString(key); - return stored ? JSON.parse(stored) : undefined; - }, - (mmkv, key, value) => { - if (value === undefined) { - mmkv.remove(key); - return; - } - mmkv.set(key, JSON.stringify(value)); - } -); - -/** - * Use the buffer value of the given key from the given MMKV storage instance - */ -export const useMMKVBuffer = createMMKVHook((instance, key) => instance.getBuffer(key)); diff --git a/app/lib/methods/userPreferences.test.ts b/app/lib/methods/userPreferences.test.ts index e0a9aaf8bdb..c613b4084d5 100644 --- a/app/lib/methods/userPreferences.test.ts +++ b/app/lib/methods/userPreferences.test.ts @@ -1,37 +1,151 @@ -import userPreferences from './userPreferences'; +import { act, renderHook } from '@testing-library/react-native'; + +import userPreferences, { useUserPreferences } from './userPreferences'; describe('UserPreferences', () => { beforeEach(() => userPreferences.clearAll()); - it('getBool returns false for stored false', () => { - userPreferences.setBool('k', false); - expect(userPreferences.getBool('k')).toBe(false); + describe('string', () => { + it('round-trips a stored string', () => { + userPreferences.setString('k', 'value'); + expect(userPreferences.getString('k')).toBe('value'); + }); + + it('getString returns null for unset key', () => { + expect(userPreferences.getString('missing')).toBeNull(); + }); + + it('getString returns null for an empty string', () => { + userPreferences.setString('k', ''); + expect(userPreferences.getString('k')).toBeNull(); + }); }); - it('getBool parses JSON-string stored boolean', () => { - userPreferences.setString('k', JSON.stringify(false)); - expect(userPreferences.getBool('k')).toBe(false); - userPreferences.setString('k', JSON.stringify(true)); - expect(userPreferences.getBool('k')).toBe(true); + describe('bool', () => { + it('getBool returns false for stored false', () => { + userPreferences.setBool('k', false); + expect(userPreferences.getBool('k')).toBe(false); + }); + + it('getBool parses JSON-string stored boolean', () => { + userPreferences.setString('k', JSON.stringify(false)); + expect(userPreferences.getBool('k')).toBe(false); + userPreferences.setString('k', JSON.stringify(true)); + expect(userPreferences.getBool('k')).toBe(true); + }); + + it('getBool returns null for non-boolean or invalid JSON strings', () => { + for (const value of ['1', 'null', '{}', 'not-json']) { + userPreferences.setString('k', value); + expect(userPreferences.getBool('k')).toBeNull(); + } + }); + + it('getBool returns null for unset key', () => { + expect(userPreferences.getBool('missing')).toBeNull(); + }); }); - it('getBool returns null for non-boolean or invalid JSON strings', () => { - for (const value of ['1', 'null', '{}', 'not-json']) { - userPreferences.setString('k', value); - expect(userPreferences.getBool('k')).toBeNull(); - } + describe('number', () => { + it('getNumber returns 0 for stored 0', () => { + userPreferences.setNumber('k', 0); + expect(userPreferences.getNumber('k')).toBe(0); + }); + + it('getNumber returns null for unset key', () => { + expect(userPreferences.getNumber('missing')).toBeNull(); + }); }); - it('getBool returns null for unset key', () => { - expect(userPreferences.getBool('missing')).toBeNull(); + describe('map', () => { + it('round-trips a stored object', () => { + userPreferences.setMap('k', { sort: 'activity', group: true }); + expect(userPreferences.getMap('k')).toEqual({ sort: 'activity', group: true }); + }); + + it('getMap returns null for unset key', () => { + expect(userPreferences.getMap('missing')).toBeNull(); + }); + + it('getMap returns null when the stored string is not JSON', () => { + userPreferences.setString('k', 'not-json'); + expect(userPreferences.getMap('k')).toBeNull(); + }); }); - it('getNumber returns 0 for stored 0', () => { - userPreferences.setNumber('k', 0); - expect(userPreferences.getNumber('k')).toBe(0); + describe('key management', () => { + it('contains reports whether a key is stored', () => { + expect(userPreferences.contains('k')).toBe(false); + userPreferences.setString('k', 'value'); + expect(userPreferences.contains('k')).toBe(true); + }); + + it('removeItem drops a single key', () => { + userPreferences.setString('keep', 'a'); + userPreferences.setString('drop', 'b'); + userPreferences.removeItem('drop'); + expect(userPreferences.contains('drop')).toBe(false); + expect(userPreferences.getString('keep')).toBe('a'); + }); + + it('getAllKeys lists every stored key', () => { + userPreferences.setString('a', '1'); + userPreferences.setBool('b', true); + userPreferences.setNumber('c', 2); + expect(userPreferences.getAllKeys().sort()).toEqual(['a', 'b', 'c']); + }); + + it('clearAll empties the storage', () => { + userPreferences.setString('a', '1'); + userPreferences.clearAll(); + expect(userPreferences.getAllKeys()).toEqual([]); + }); }); - it('getNumber returns null for unset key', () => { - expect(userPreferences.getNumber('missing')).toBeNull(); + describe('useUserPreferences', () => { + it('returns the default value when nothing is stored', () => { + const { result } = renderHook(() => useUserPreferences('k', 'fallback')); + expect(result.current[0]).toBe('fallback'); + }); + + it('returns undefined when nothing is stored and no default is given', () => { + const { result } = renderHook(() => useUserPreferences('k')); + expect(result.current[0]).toBeUndefined(); + }); + + it('reads back a string written through the setter', () => { + const { result } = renderHook(() => useUserPreferences('k', 'fallback')); + + act(() => result.current[1]('written')); + + expect(result.current[0]).toBe('written'); + expect(userPreferences.getString('k')).toBe('written'); + }); + + it('round-trips a non-string value as JSON', () => { + const { result } = renderHook(() => useUserPreferences<{ sort: string }>('k', { sort: 'alphabetical' })); + + act(() => result.current[1]({ sort: 'activity' })); + + expect(result.current[0]).toEqual({ sort: 'activity' }); + expect(userPreferences.getMap('k')).toEqual({ sort: 'activity' }); + }); + + it('falls back to the default when the stored string is not valid JSON', () => { + userPreferences.setString('k', 'not-json'); + const { result } = renderHook(() => useUserPreferences<{ sort: string }>('k', { sort: 'alphabetical' })); + + expect(result.current[0]).toEqual({ sort: 'alphabetical' }); + }); + + it('clears the stored value when set to undefined', () => { + userPreferences.setString('k', 'written'); + const { result } = renderHook(() => useUserPreferences('k', 'fallback')); + + act(() => result.current[1](undefined)); + + expect(userPreferences.contains('k')).toBe(false); + expect(result.current[0]).toBe('fallback'); + }); }); }); diff --git a/patches/react-native-mmkv+4.1.2.patch b/patches/react-native-mmkv+4.1.2.patch deleted file mode 100644 index e11f0299747..00000000000 --- a/patches/react-native-mmkv+4.1.2.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/node_modules/react-native-mmkv/src/createMMKV/createMMKV.ts b/node_modules/react-native-mmkv/src/createMMKV/createMMKV.ts -index d5142f9..5da521f 100644 ---- a/node_modules/react-native-mmkv/src/createMMKV/createMMKV.ts -+++ b/node_modules/react-native-mmkv/src/createMMKV/createMMKV.ts -@@ -2,15 +2,15 @@ import type { MMKV } from '../specs/MMKV.nitro' - import type { Configuration } from '../specs/MMKVFactory.nitro' - import { Platform } from 'react-native' - import { addMemoryWarningListener } from '../addMemoryWarningListener/addMemoryWarningListener' --import { isTest } from '../isTest' --import { createMockMMKV } from './createMockMMKV' -+// import { isTest } from '../isTest' -+// import { createMockMMKV } from './createMockMMKV' // Disabled via patch-package for E2E tests - import { getMMKVFactory, getPlatformContext } from '../getMMKVFactory' - - export function createMMKV(configuration?: Configuration): MMKV { -- if (isTest()) { -- // In a test environment, we mock the MMKV instance. -- return createMockMMKV(configuration) -- } -+ //if (isTest()) { -+ // In a test environment, we mock the MMKV instance. -+ // return createMockMMKV(configuration) -+ //} - - const factory = getMMKVFactory() - From 559b2f79aa49e452e8d0a1ce0504d58169e81603 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 15 Sep 2026 17:55:58 -0300 Subject: [PATCH 008/120] chore: RN 0.86 / Expo 57 dependencies, toolchain and patches (#7693) * chore: upgrade dependencies and patches to RN 0.86 / Expo 57 * chore: align upgrade toolchain floors and CI checks * chore: update Jest toolchain for RN 0.86 and Expo 57 --- .github/actions/build-ios/action.yml | 5 - .github/actions/upload-ios/action.yml | 5 - .github/workflows/e2e-build-ios.yml | 5 - .github/workflows/eslint.yml | 8 +- .github/workflows/maestro-ios.yml | 6 - .github/workflows/organize_translations.yml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- jest.config.js | 2 + jest.preset.js | 2 - jest.setup.js | 16 +- package.json | 73 +- ...12.0.12.patch => expo-asset+57.0.17.patch} | 15 +- ...io+0.4.9.patch => expo-audio+57.0.5.patch} | 4 +- ...21.patch => expo-file-system+57.0.7.patch} | 10 +- ...t+14.0.10.patch => expo-font+57.0.4.patch} | 0 patches/react-native+0.81.5.patch | 38 - patches/react-native+0.86.3.patch | 24 + ...react-native-gesture-handler+2.32.0.patch} | 31 +- pnpm-lock.yaml | 4132 ++++++++++------- 19 files changed, 2580 insertions(+), 1800 deletions(-) rename patches/{expo-asset+12.0.12.patch => expo-asset+57.0.17.patch} (68%) rename patches/{expo-audio+0.4.9.patch => expo-audio+57.0.5.patch} (94%) rename patches/{expo-file-system+19.0.21.patch => expo-file-system+57.0.7.patch} (80%) rename patches/{expo-font+14.0.10.patch => expo-font+57.0.4.patch} (100%) delete mode 100644 patches/react-native+0.81.5.patch create mode 100644 patches/react-native+0.86.3.patch rename patches/{react-native-gesture-handler+2.28.0.patch => react-native-gesture-handler+2.32.0.patch} (54%) diff --git a/.github/actions/build-ios/action.yml b/.github/actions/build-ios/action.yml index ddb2133bd5d..d9e6e064f42 100644 --- a/.github/actions/build-ios/action.yml +++ b/.github/actions/build-ios/action.yml @@ -41,11 +41,6 @@ runs: sudo rm -rf ~/Library/Developer/CoreSimulator/Caches/* shell: bash - - name: Set up Xcode - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 - with: - xcode-version: '26.2.0' - - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 with: diff --git a/.github/actions/upload-ios/action.yml b/.github/actions/upload-ios/action.yml index 781851ac8d3..ef727a14c4d 100644 --- a/.github/actions/upload-ios/action.yml +++ b/.github/actions/upload-ios/action.yml @@ -61,11 +61,6 @@ runs: restore-keys: | pods-v1- - - name: Set up Xcode - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 - with: - xcode-version: '26.2.0' - - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 with: diff --git a/.github/workflows/e2e-build-ios.yml b/.github/workflows/e2e-build-ios.yml index f290a6d4228..6bfdd887904 100644 --- a/.github/workflows/e2e-build-ios.yml +++ b/.github/workflows/e2e-build-ios.yml @@ -65,11 +65,6 @@ jobs: ~/.maestro/deps key: maestro-${{ runner.os }}-${{ env.MAESTRO_VERSION }} - - name: Set up Xcode - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 - with: - xcode-version: '26.2.0' - - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 with: diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 2372337e384..198f70d4188 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -19,10 +19,14 @@ jobs: uses: ./.github/actions/setup-node - name: Run Oxlint - run: pnpm lint + run: pnpm exec oxlint + + - name: Run TypeScript + continue-on-error: true + run: pnpm exec tsc - name: Run Tests - run: pnpm test --runInBand + run: pnpm test - name: Validate sniffler test-map freshness run: node .github/scripts/validate-test-map.js diff --git a/.github/workflows/maestro-ios.yml b/.github/workflows/maestro-ios.yml index 3cf48779f52..c87463cc0e8 100644 --- a/.github/workflows/maestro-ios.yml +++ b/.github/workflows/maestro-ios.yml @@ -22,12 +22,6 @@ jobs: - name: Checkout Repo uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Set up Xcode - # Match the Xcode version e2e-build-ios.yml builds with - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 - with: - xcode-version: '26.2.0' - - name: Configure Simulator run: | defaults write com.apple.iphonesimulator ConnectHardwareKeyboard -bool false diff --git a/.github/workflows/organize_translations.yml b/.github/workflows/organize_translations.yml index 4e2dad51c2a..12cb600686b 100644 --- a/.github/workflows/organize_translations.yml +++ b/.github/workflows/organize_translations.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: 18 + node-version: 22 - name: Run script to organize JSON keys run: node scripts/organize-translations.js diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 42f0071e3f1..bc79d447cc6 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/jest.config.js b/jest.config.js index d8ca39be13d..e9ef674db8f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,8 @@ const getAliasConfig = require('./config/import-aliases'); module.exports = { + maxWorkers: 2, + workerIdleMemoryLimit: '512MB', modulePathIgnorePatterns: ['/.*worktrees/'], testPathIgnorePatterns: [ 'e2e', diff --git a/jest.preset.js b/jest.preset.js index 32b28ff7b71..92c0a951b5e 100644 --- a/jest.preset.js +++ b/jest.preset.js @@ -1,7 +1,5 @@ -const jestRN = 'react-native'; const jestExpo = require('jest-expo/jest-preset.js'); module.exports = { - ...jestRN, ...jestExpo }; diff --git a/jest.setup.js b/jest.setup.js index 69aa7056068..9ebbf508fb7 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -18,22 +18,8 @@ jest.mock('react-native-safe-area-context', () => { const loadAsyncMock = jest.spyOn(Image, 'loadAsync'); loadAsyncMock.mockImplementation(() => Promise.resolve({ width: 200, height: 300 })); -jest.mock('react-native-worklets', () => ({ - RuntimeKind: { ReactNative: 1, UI: 2, Worker: 3 }, - getRuntimeKind: jest.fn(() => 1), - createSerializable: jest.fn(value => value), - isWorkletFunction: jest.fn(() => false), - runOnUI: jest.fn(fn => fn), - runOnJS: jest.fn(fn => fn), - makeShareable: jest.fn(value => value), - callMicrotasks: jest.fn(), - executeOnUIRuntimeSync: jest.fn(fn => fn()), - serializableMappingCache: { get: jest.fn(), set: jest.fn(), has: jest.fn(() => false), delete: jest.fn() }, - scheduleOnRN: jest.fn((fn, ...args) => fn(...args)) -})); +jest.mock('react-native-worklets', () => jest.requireActual('react-native-worklets/lib/module/mock')); -// @ts-ignore -global.__reanimatedWorkletInit = () => {}; jest.mock('react-native-reanimated', () => { const actual = jest.requireActual('react-native-reanimated/mock'); return { diff --git a/package.json b/package.json index 7f58059d646..05b6aca3168 100644 --- a/package.json +++ b/package.json @@ -35,10 +35,10 @@ "@react-native-async-storage/async-storage": "2.2.0", "@react-native-camera-roll/camera-roll": "^7.10.0", "@react-native-clipboard/clipboard": "^1.13.2", - "@react-native-community/datetimepicker": "8.4.4", + "@react-native-community/datetimepicker": "9.1.0", "@react-native-community/hooks": "^100.1.0", - "@react-native-community/netinfo": "^11.3.1", - "@react-native-community/slider": "5.0.1", + "@react-native-community/netinfo": "^12.0.1", + "@react-native-community/slider": "5.2.0", "@react-native-cookies/cookies": "6.2.1", "@react-native-firebase/analytics": "^21.12.2", "@react-native-firebase/app": "^21.12.2", @@ -63,23 +63,23 @@ "dequal": "2.0.3", "ejson": "2.2.3", "eslint-import-resolver-typescript": "^4.4.4", - "expo": "^54.0.0", - "expo-apple-authentication": "~8.0.8", - "expo-audio": "~0.4.9", - "expo-camera": "~17.0.10", - "expo-device": "^8.0.10", - "expo-document-picker": "~14.0.8", - "expo-file-system": "~19.0.21", - "expo-haptics": "~15.0.8", - "expo-image": "~3.0.11", - "expo-keep-awake": "~15.0.8", - "expo-local-authentication": "~17.0.8", - "expo-notifications": "~0.32.15", - "expo-status-bar": "~3.0.9", - "expo-system-ui": "~6.0.9", - "expo-video": "~3.0.16", - "expo-video-thumbnails": "~10.0.8", - "expo-web-browser": "~15.0.10", + "expo": "^57.0.0", + "expo-apple-authentication": "~57.0.2", + "expo-audio": "~57.0.5", + "expo-camera": "~57.0.5", + "expo-device": "^57.0.2", + "expo-document-picker": "~57.0.2", + "expo-file-system": "~57.0.7", + "expo-haptics": "~57.0.3", + "expo-image": "~57.0.5", + "expo-keep-awake": "~57.0.2", + "expo-local-authentication": "~57.0.3", + "expo-notifications": "~57.0.19", + "expo-status-bar": "~57.0.1", + "expo-system-ui": "~57.0.4", + "expo-video": "~57.0.4", + "expo-video-thumbnails": "~57.0.2", + "expo-web-browser": "~57.0.3", "hoist-non-react-statics": "3.3.2", "i18n-js": "3.9.2", "js-base64": "3.6.1", @@ -88,9 +88,9 @@ "lodash": "4.17.21", "mitt": "3.0.1", "pretty-bytes": "5.6.0", - "react": "19.1.0", + "react": "19.2.3", "react-hook-form": "7.34.2", - "react-native": "0.81.5", + "react-native": "0.86.3", "react-native-a11y-order": "^0.4.0", "react-native-animatable": "1.3.3", "react-native-bootsplash": "^6.3.8", @@ -100,7 +100,7 @@ "react-native-easy-grid": "0.2.2", "react-native-easy-toast": "2.3.0", "react-native-external-keyboard": "^0.9.0", - "react-native-gesture-handler": "~2.28.0", + "react-native-gesture-handler": "~2.32.0", "react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker#47092e8c90550a54d45fe307f7cb2a24c9535ed5", "react-native-incall-manager": "^4.2.1", "react-native-katex": "git+https://github.com/RocketChat/react-native-katex.git", @@ -115,15 +115,15 @@ "react-native-notifier": "1.6.1", "react-native-picker-select": "9.0.1", "react-native-popover-view": "5.1.7", - "react-native-reanimated": "4.1.3", + "react-native-reanimated": "4.5.1", "react-native-safe-area-context": "^5.5.2", - "react-native-screens": "~4.17.1", + "react-native-screens": "~4.26.2", "react-native-skeleton-placeholder": "5.2.4", "react-native-svg": "^15.12.1", "react-native-url-polyfill": "2.0.0", "react-native-webrtc": "124.0.7", "react-native-webview": "13.16.1", - "react-native-worklets": "^0.6.1", + "react-native-worklets": "^0.10.1", "react-redux": "8.0.5", "reanimated-tab-view": "^0.3.0", "redux": "4.2.0", @@ -146,7 +146,10 @@ "pnpm": { "overrides": { "ua-parser-js": "1.0.2", - "jpeg-js": "0.4.4" + "jpeg-js": "0.4.4", + "typescript": "7.0.2", + "react-native-keyboard-controller": "1.18.5", + "react-native-nitro-modules": "^0.37.1" }, "onlyBuiltDependencies": [ "@bugsnag/cli", @@ -159,7 +162,7 @@ ] }, "devDependencies": { - "@babel/core": "~7.25.9", + "@babel/core": "~7.29.7", "@babel/plugin-proposal-decorators": "~7.25.9", "@babel/plugin-transform-named-capturing-groups-regex": "~7.25.9", "@babel/preset-env": "~7.25.9", @@ -171,7 +174,7 @@ "@react-native-community/cli-platform-android": "20.0.0", "@react-native-community/cli-platform-ios": "20.0.0", "@react-native/babel-preset": "0.81.5", - "@react-native/metro-config": "0.81.5", + "@react-native/metro-config": "0.86.3", "@react-native/typescript-config": "0.81.5", "@storybook/react": "~9.0.9", "@storybook/react-native": "~9.0.9", @@ -184,7 +187,7 @@ "@types/jsrsasign": "^10.5.8", "@types/lodash": "^4.14.188", "@types/node": "25.0.3", - "@types/react": "~19.1.0", + "@types/react": "~19.2.18", "@types/semver": "7.3.13", "@types/ua-parser-js": "^0.7.36", "@types/url-parse": "^1.4.8", @@ -193,19 +196,19 @@ "babel-plugin-module-resolver": "^5.0.3", "babel-plugin-react-compiler": "1.0.0", "babel-plugin-transform-remove-console": "^6.9.4", - "babel-preset-expo": "~54.0.9", + "babel-preset-expo": "~57.0.12", "emojibase-data": "17.0.0", "eslint-plugin-react-native": "~5.0.0", "fast-glob": "3.3.3", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-cli": "^29.7.0", - "jest-expo": "~54.0.16", + "jest-expo": "~57.0.5", "oxfmt": "^0.60.0", "oxlint": "^1.75.0", "oxlint-plugin-complexity": "^2.1.8", "patch-package": "~8.0.1", - "react-dom": "19.1.0", + "react-dom": "19.2.3", "react-native-dotenv": "3.4.8", "sniffler": "0.4.0", "storybook": "~9.0.9", @@ -213,7 +216,7 @@ }, "snyk": true, "engines": { - "node": ">=18" + "node": ">=22.13.0" }, "codegenConfig": { "name": "SSLPinning", @@ -224,6 +227,6 @@ } }, "volta": { - "node": "24.13.1" + "node": "22.13.0" } } diff --git a/patches/expo-asset+12.0.12.patch b/patches/expo-asset+57.0.17.patch similarity index 68% rename from patches/expo-asset+12.0.12.patch rename to patches/expo-asset+57.0.17.patch index dd415ccd507..1a7de8cc921 100644 --- a/patches/expo-asset+12.0.12.patch +++ b/patches/expo-asset+57.0.17.patch @@ -1,24 +1,21 @@ diff --git a/node_modules/expo-asset/android/src/main/java/expo/modules/asset/AssetModule.kt b/node_modules/expo-asset/android/src/main/java/expo/modules/asset/AssetModule.kt -index 9f1e2c6..d4c49cd 100644 +index cff5e41..a57e8a8 100644 --- a/node_modules/expo-asset/android/src/main/java/expo/modules/asset/AssetModule.kt +++ b/node_modules/expo-asset/android/src/main/java/expo/modules/asset/AssetModule.kt -@@ -3,6 +3,7 @@ package expo.modules.asset +@@ -2,6 +2,7 @@ package expo.modules.asset + import android.content.Context import android.net.Uri - import android.util.Log +import android.os.Build - import expo.modules.interfaces.filesystem.Permission + import android.util.Log import expo.modules.kotlin.AppContext import expo.modules.kotlin.exception.CodedException -@@ -80,6 +81,13 @@ class AssetModule : Module() { +@@ -83,6 +84,10 @@ class AssetModule : Module() { Name("ExpoAsset") AsyncFunction("downloadAsync") Coroutine { uri: URI, md5Hash: String?, type: String -> -+ // If the scheme is null, it's a raw asset (e.g., "custom.ttf"). -+ // Returning early prevents Expo from trying to "cache" a local native file. -+ // Only for Android 8 and early + if (uri.scheme == null && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) { -+ return@Coroutine uri ++ return@Coroutine uri + } + if (uri.scheme == "file" && !uri.toString().startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) { diff --git a/patches/expo-audio+0.4.9.patch b/patches/expo-audio+57.0.5.patch similarity index 94% rename from patches/expo-audio+0.4.9.patch rename to patches/expo-audio+57.0.5.patch index 7b6f7a2961e..0d7210ec4b5 100644 --- a/patches/expo-audio+0.4.9.patch +++ b/patches/expo-audio+57.0.5.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt b/node_modules/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt -index c9fd366..5c74acf 100644 +index 992239b..df504ce 100644 --- a/node_modules/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt +++ b/node_modules/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt @@ -26,6 +26,7 @@ import androidx.media3.datasource.okhttp.OkHttpDataSource @@ -10,7 +10,7 @@ index c9fd366..5c74acf 100644 import androidx.media3.exoplayer.source.MediaSource import androidx.media3.exoplayer.source.ProgressiveMediaSource import expo.modules.interfaces.permissions.Permissions -@@ -553,7 +554,10 @@ class AudioModule : Module() { +@@ -931,7 +932,10 @@ class AudioModule : Module() { CONTENT_TYPE_SS -> SsMediaSource.Factory(factory) CONTENT_TYPE_DASH -> DashMediaSource.Factory(factory) CONTENT_TYPE_HLS -> HlsMediaSource.Factory(factory) diff --git a/patches/expo-file-system+19.0.21.patch b/patches/expo-file-system+57.0.7.patch similarity index 80% rename from patches/expo-file-system+19.0.21.patch rename to patches/expo-file-system+57.0.7.patch index 8633be6f991..37a07aee726 100644 --- a/patches/expo-file-system+19.0.21.patch +++ b/patches/expo-file-system+57.0.7.patch @@ -1,11 +1,11 @@ diff --git a/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/legacy/FileSystemLegacyModule.kt b/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/legacy/FileSystemLegacyModule.kt -index f6d43bf..197bebf 100644 +index 4626dac..a17bc35 100644 --- a/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/legacy/FileSystemLegacyModule.kt +++ b/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/legacy/FileSystemLegacyModule.kt -@@ -80,11 +80,20 @@ fun slashifyFilePath(path: String?): String? { - open class FileSystemLegacyModule : Module() { - private val context: Context - get() = appContext.reactContext ?: throw Exceptions.AppContextLost() +@@ -83,11 +83,20 @@ open class FileSystemLegacyModule : Module() { + get() = appContext.persistentFilesDirectory + private val cacheDirectory: File + get() = appContext.cacheDirectory - private var client: OkHttpClient? = null private var dirPermissionsRequest: Promise? = null private val taskHandlers: MutableMap = HashMap() diff --git a/patches/expo-font+14.0.10.patch b/patches/expo-font+57.0.4.patch similarity index 100% rename from patches/expo-font+14.0.10.patch rename to patches/expo-font+57.0.4.patch diff --git a/patches/react-native+0.81.5.patch b/patches/react-native+0.81.5.patch deleted file mode 100644 index 8c17708ee96..00000000000 --- a/patches/react-native+0.81.5.patch +++ /dev/null @@ -1,38 +0,0 @@ -diff --git a/node_modules/react-native/React/Views/RCTViewManager.m b/node_modules/react-native/React/Views/RCTViewManager.m -index f017cb8..f6aaafb 100644 ---- a/node_modules/react-native/React/Views/RCTViewManager.m -+++ b/node_modules/react-native/React/Views/RCTViewManager.m -@@ -225,7 +225,9 @@ RCT_CUSTOM_VIEW_PROPERTY(accessibilityRole, UIAccessibilityTraits, RCTView) - { - UIAccessibilityTraits accessibilityRoleTraits = - json ? [RCTConvert UIAccessibilityTraits:json] : UIAccessibilityTraitNone; -- if (view.reactAccessibilityElement.accessibilityRoleTraits != accessibilityRoleTraits) { -+ NSString *roleString = json ? [RCTConvert NSString:json] : nil; -+ if (view.reactAccessibilityElement.accessibilityRoleTraits != accessibilityRoleTraits || view.reactAccessibilityElement.accessibilityRole != roleString) { -+ - view.accessibilityRoleTraits = accessibilityRoleTraits; - view.reactAccessibilityElement.accessibilityRole = json ? [RCTConvert NSString:json] : nil; - [self updateAccessibilityTraitsForRole:view withDefaultView:defaultView]; -@@ -235,7 +237,8 @@ RCT_CUSTOM_VIEW_PROPERTY(accessibilityRole, UIAccessibilityTraits, RCTView) - RCT_CUSTOM_VIEW_PROPERTY(role, UIAccessibilityTraits, RCTView) - { - UIAccessibilityTraits roleTraits = json ? [RCTConvert UIAccessibilityTraits:json] : UIAccessibilityTraitNone; -- if (view.reactAccessibilityElement.roleTraits != roleTraits) { -+ NSString *roleString = json ? [RCTConvert NSString:json] : nil; -+ if (view.reactAccessibilityElement.roleTraits != roleTraits || view.reactAccessibilityElement.role != roleString) { - view.roleTraits = roleTraits; - view.reactAccessibilityElement.role = json ? [RCTConvert NSString:json] : nil; - [self updateAccessibilityTraitsForRole:view withDefaultView:defaultView]; -diff --git a/node_modules/react-native/scripts/cocoapods/new_architecture.rb b/node_modules/react-native/scripts/cocoapods/new_architecture.rb -index 7d8f8e6..d2852a2 100644 ---- a/node_modules/react-native/scripts/cocoapods/new_architecture.rb -+++ b/node_modules/react-native/scripts/cocoapods/new_architecture.rb -@@ -163,7 +163,7 @@ class NewArchitectureHelper - .uniq{ |p| p.path } - .map{ |p| p.path } - -- excluded_info_plist = ["/Pods", "Tests", "metainternal", ".bundle", "build/", "DerivedData/"] -+ excluded_info_plist = ["/Pods", "Tests", "metainternal", ".bundle", "build/", "DerivedData/", ".xcframework", ".framework"] - projectPaths.each do |projectPath| - projectFolderPath = File.dirname(projectPath) - infoPlistFiles = `find #{projectFolderPath} -name "Info.plist"` diff --git a/patches/react-native+0.86.3.patch b/patches/react-native+0.86.3.patch new file mode 100644 index 00000000000..b0da19c9af6 --- /dev/null +++ b/patches/react-native+0.86.3.patch @@ -0,0 +1,24 @@ +diff --git a/node_modules/react-native/React/Views/RCTViewManager.m b/node_modules/react-native/React/Views/RCTViewManager.m +index fa1e0bc..82d9393 100644 +--- a/node_modules/react-native/React/Views/RCTViewManager.m ++++ b/node_modules/react-native/React/Views/RCTViewManager.m +@@ -223,7 +223,8 @@ - (RCTShadowView *)shadowView + { + UIAccessibilityTraits accessibilityRoleTraits = + json ? [RCTConvert UIAccessibilityTraits:json] : UIAccessibilityTraitNone; +- if (view.reactAccessibilityElement.accessibilityRoleTraits != accessibilityRoleTraits) { ++ NSString *roleString = json ? [RCTConvert NSString:json] : nil; ++ if (view.reactAccessibilityElement.accessibilityRoleTraits != accessibilityRoleTraits || view.reactAccessibilityElement.accessibilityRole != roleString) { + view.accessibilityRoleTraits = accessibilityRoleTraits; + view.reactAccessibilityElement.accessibilityRole = json ? [RCTConvert NSString:json] : nil; + [self updateAccessibilityTraitsForRole:view withDefaultView:defaultView]; +@@ -233,7 +234,8 @@ - (RCTShadowView *)shadowView + RCT_CUSTOM_VIEW_PROPERTY(role, UIAccessibilityTraits, RCTView) + { + UIAccessibilityTraits roleTraits = json ? [RCTConvert UIAccessibilityTraits:json] : UIAccessibilityTraitNone; +- if (view.reactAccessibilityElement.roleTraits != roleTraits) { ++ NSString *roleString = json ? [RCTConvert NSString:json] : nil; ++ if (view.reactAccessibilityElement.roleTraits != roleTraits || view.reactAccessibilityElement.role != roleString) { + view.roleTraits = roleTraits; + view.reactAccessibilityElement.role = json ? [RCTConvert NSString:json] : nil; + [self updateAccessibilityTraitsForRole:view withDefaultView:defaultView]; diff --git a/patches/react-native-gesture-handler+2.28.0.patch b/patches/react-native-gesture-handler+2.32.0.patch similarity index 54% rename from patches/react-native-gesture-handler+2.28.0.patch rename to patches/react-native-gesture-handler+2.32.0.patch index 309449cc7c5..ba5d8c8056f 100644 --- a/patches/react-native-gesture-handler+2.28.0.patch +++ b/patches/react-native-gesture-handler+2.32.0.patch @@ -1,8 +1,35 @@ +diff --git a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +index e4114c4..41544da 100644 +--- a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt ++++ b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +@@ -504,10 +504,22 @@ class RNGestureHandlerButtonViewManager : + } + + override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { ++ if (isActivationKey(keyCode) && !isChildTouched() && !context.isScreenReaderOn()) { ++ findGestureHandlerRootView()?.activateNativeHandlers(this) ++ return true ++ } + receivedKeyEvent = true + return super.onKeyUp(keyCode, event) + } + ++ private fun isActivationKey(keyCode: Int) = when (keyCode) { ++ KeyEvent.KEYCODE_DPAD_CENTER, ++ KeyEvent.KEYCODE_ENTER, ++ KeyEvent.KEYCODE_NUMPAD_ENTER, ++ KeyEvent.KEYCODE_SPACE -> true ++ else -> false ++ } ++ + override fun performClick(): Boolean { + // don't preform click when a child button is pressed (mainly to prevent sound effect of + // a parent button from playing) diff --git a/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm b/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm -index 2296c39..0a872ba 100644 +index 6b9fa9c..6d2ccea 100644 --- a/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm +++ b/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm -@@ -89,6 +89,52 @@ +@@ -83,6 +83,52 @@ - (BOOL)shouldHandleTouch:(RNGHUIView *)view } #if !TARGET_OS_OSX diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c9f438dcbd..5007156765b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,9 @@ settings: overrides: ua-parser-js: 1.0.2 jpeg-js: 0.4.4 + typescript: 7.0.2 + react-native-keyboard-controller: 1.18.5 + react-native-nitro-modules: ^0.37.1 importers: @@ -17,73 +20,73 @@ importers: version: 8.4.0 '@expo/vector-icons': specifier: ^15.0.3 - version: 15.0.3(expo-font@14.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 15.0.3(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@hookform/resolvers': specifier: ^2.9.10 - version: 2.9.11(react-hook-form@7.34.2(react@19.1.0)) + version: 2.9.11(react-hook-form@7.34.2(react@19.2.3)) '@lodev09/react-native-true-sheet': specifier: ~3.7.0 - version: 3.7.3(261280a80c394a7dcf8d9609d62c37ca) + version: 3.7.3(b217ab5bd6345e9949a4a395c898a850) '@magrinj/expo-quick-look': specifier: 0.4.0 - version: 0.4.0(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 0.4.0(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@nozbe/watermelondb': specifier: ^0.28.1-0 version: 0.28.1-0 '@react-native-async-storage/async-storage': specifier: 2.2.0 - version: 2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) '@react-native-camera-roll/camera-roll': specifier: ^7.10.0 - version: 7.10.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 7.10.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) '@react-native-clipboard/clipboard': specifier: ^1.13.2 - version: 1.16.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 1.16.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-native-community/datetimepicker': - specifier: 8.4.4 - version: 8.4.4(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: 9.1.0 + version: 9.1.0(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-native-community/hooks': specifier: ^100.1.0 - version: 100.1.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 100.1.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-native-community/netinfo': - specifier: ^11.3.1 - version: 11.4.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + specifier: ^12.0.1 + version: 12.0.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-native-community/slider': - specifier: 5.0.1 - version: 5.0.1 + specifier: 5.2.0 + version: 5.2.0 '@react-native-cookies/cookies': specifier: 6.2.1 - version: 6.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 6.2.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) '@react-native-firebase/analytics': specifier: ^21.12.2 - version: 21.14.0(@react-native-firebase/app@21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)))(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) + version: 21.14.0(@react-native-firebase/app@21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)) '@react-native-firebase/app': specifier: ^21.12.2 - version: 21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)))(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-native-firebase/crashlytics': specifier: ^21.12.2 - version: 21.14.0(7f31d425d6e3bb1976dd842bb1e41e49) + version: 21.14.0(@react-native-firebase/app@21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo@57.0.23) '@react-native-masked-view/masked-view': specifier: ^0.3.1 - version: 0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-native-picker/picker': specifier: 2.11.4 - version: 2.11.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 2.11.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-native/codegen': specifier: ^0.80.0 - version: 0.80.2(@babel/core@7.25.9) + version: 0.80.2(@babel/core@7.29.7) '@react-navigation/drawer': specifier: ^7.12.2 - version: 7.12.2(1791ab06baac4955f4dd8054e3baaac6) + version: 7.12.2(568a64fb44ad70e6f0b55611752f41f8) '@react-navigation/elements': specifier: ^2.9.25 - version: 2.9.25(@react-native-masked-view/masked-view@0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) '@react-navigation/native': specifier: ^7.3.3 - version: 7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': specifier: ^7.17.5 - version: 7.17.5(5d5d774b9c7e992721e68b3dbd486565) + version: 7.17.5(b9b07f1e05b03fd68f5bb004ad12b8b4) '@rocket.chat/media-signaling': specifier: 1.0.0-rc.1 version: 1.0.0-rc.1 @@ -92,7 +95,7 @@ importers: version: 0.31.36 '@rocket.chat/mobile-crypto': specifier: RocketChat/rocket.chat-mobile-crypto#main - version: https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/69a0a250dd7c6ff0808eb659d7202be1cae7fa1c(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/69a0a250dd7c6ff0808eb659d7202be1cae7fa1c(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@rocket.chat/sdk': specifier: RocketChat/Rocket.Chat.js.SDK#ecf4fedcf8ab6121e62a6b95bb48823afd805afd version: https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/ecf4fedcf8ab6121e62a6b95bb48823afd805afd @@ -101,7 +104,7 @@ importers: version: 0.39.0(@rocket.chat/icons@0.47.0)(@types/node@25.0.3)(typescript@7.0.2) '@zoontek/react-native-navigation-bar': specifier: ^1.1.1 - version: 1.1.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 1.1.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) axios: specifier: 0.30.3 version: 0.30.3 @@ -124,56 +127,56 @@ importers: specifier: ^4.4.4 version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@8.57.1) expo: - specifier: ^54.0.0 - version: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ^57.0.0 + version: 57.0.23(92e84404446a2d5101115c437b8fd679) expo-apple-authentication: - specifier: ~8.0.8 - version: 8.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + specifier: ~57.0.2 + version: 57.0.2(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) expo-audio: - specifier: ~0.4.9 - version: 0.4.9(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~57.0.5 + version: 57.0.5(expo-asset@57.0.17(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) expo-camera: - specifier: ~17.0.10 - version: 17.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~57.0.5 + version: 57.0.5(@types/emscripten@1.41.6)(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) expo-device: - specifier: ^8.0.10 - version: 8.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) + specifier: ^57.0.2 + version: 57.0.2(expo@57.0.23) expo-document-picker: - specifier: ~14.0.8 - version: 14.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) + specifier: ~57.0.2 + version: 57.0.2(expo@57.0.23) expo-file-system: - specifier: ~19.0.21 - version: 19.0.21(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + specifier: ~57.0.7 + version: 57.0.7(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) expo-haptics: - specifier: ~15.0.8 - version: 15.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) + specifier: ~57.0.3 + version: 57.0.3(expo@57.0.23) expo-image: - specifier: ~3.0.11 - version: 3.0.11(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~57.0.5 + version: 57.0.5(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) expo-keep-awake: - specifier: ~15.0.8 - version: 15.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react@19.1.0) + specifier: ~57.0.2 + version: 57.0.2(expo@57.0.23)(react@19.2.3) expo-local-authentication: - specifier: ~17.0.8 - version: 17.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) + specifier: ~57.0.3 + version: 57.0.3(expo@57.0.23) expo-notifications: - specifier: ~0.32.15 - version: 0.32.15(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~57.0.19 + version: 57.0.19(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2) expo-status-bar: - specifier: ~3.0.9 - version: 3.0.9(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~57.0.1 + version: 57.0.1(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) expo-system-ui: - specifier: ~6.0.9 - version: 6.0.9(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + specifier: ~57.0.4 + version: 57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) expo-video: - specifier: ~3.0.16 - version: 3.0.16(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~57.0.4 + version: 57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) expo-video-thumbnails: - specifier: ~10.0.8 - version: 10.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) + specifier: ~57.0.2 + version: 57.0.2(expo@57.0.23) expo-web-browser: - specifier: ~15.0.10 - version: 15.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + specifier: ~57.0.3 + version: 57.0.3(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) hoist-non-react-statics: specifier: 3.3.2 version: 3.3.2 @@ -199,119 +202,119 @@ importers: specifier: 5.6.0 version: 5.6.0 react: - specifier: 19.1.0 - version: 19.1.0 + specifier: 19.2.3 + version: 19.2.3 react-hook-form: specifier: 7.34.2 - version: 7.34.2(react@19.1.0) + version: 7.34.2(react@19.2.3) react-native: - specifier: 0.81.5 - version: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + specifier: 0.86.3 + version: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-a11y-order: specifier: ^0.4.0 - version: 0.4.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 0.4.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-animatable: specifier: 1.3.3 version: 1.3.3 react-native-bootsplash: specifier: ^6.3.8 - version: 6.3.11(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 6.3.11(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-callkeep: specifier: 4.3.16 - version: 4.3.16(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 4.3.16(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) react-native-console-time-polyfill: specifier: 1.2.3 - version: 1.2.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 1.2.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-device-info: specifier: 11.1.0 - version: 11.1.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 11.1.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) react-native-easy-grid: specifier: 0.2.2 version: 0.2.2 react-native-easy-toast: specifier: 2.3.0 - version: 2.3.0(prop-types@15.8.1)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 2.3.0(prop-types@15.8.1)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) react-native-external-keyboard: specifier: ^0.9.0 - version: 0.9.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 0.9.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-gesture-handler: - specifier: ~2.28.0 - version: 2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~2.32.0 + version: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-image-crop-picker: specifier: RocketChat/react-native-image-crop-picker#47092e8c90550a54d45fe307f7cb2a24c9535ed5 - version: https://codeload.github.com/RocketChat/react-native-image-crop-picker/tar.gz/47092e8c90550a54d45fe307f7cb2a24c9535ed5(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: https://codeload.github.com/RocketChat/react-native-image-crop-picker/tar.gz/47092e8c90550a54d45fe307f7cb2a24c9535ed5(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-incall-manager: specifier: ^4.2.1 - version: 4.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 4.2.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) react-native-katex: specifier: git+https://github.com/RocketChat/react-native-katex.git - version: https://codeload.github.com/RocketChat/react-native-katex/tar.gz/37e579804fe238732d8a4b06dec073610ab062b1(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: https://codeload.github.com/RocketChat/react-native-katex/tar.gz/37e579804fe238732d8a4b06dec073610ab062b1(react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-keyboard-controller: specifier: 1.18.5 - version: 1.18.5(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 1.18.5(react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-linear-gradient: specifier: 2.6.2 - version: 2.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-localize: specifier: 3.7.0 - version: 3.7.0(@expo/config-plugins@54.0.4)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 3.7.0(@expo/config-plugins@57.0.9(typescript@7.0.2))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-math-view: specifier: 3.9.5 - version: 3.9.5(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 3.9.5(react-native-svg@15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) react-native-mime-types: specifier: 2.3.0 version: 2.3.0 react-native-mmkv: specifier: ^4.3.2 - version: 4.3.2(react-native-nitro-modules@0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 4.3.2(react-native-nitro-modules@0.37.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-modal: specifier: 13.0.1 - version: 13.0.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 13.0.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-nitro-modules: specifier: ^0.37.1 - version: 0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 0.37.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-notifier: specifier: 1.6.1 - version: 1.6.1(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 1.6.1(react-native-gesture-handler@2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-picker-select: specifier: 9.0.1 - version: 9.0.1(@react-native-picker/picker@2.11.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) + version: 9.0.1(@react-native-picker/picker@2.11.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)) react-native-popover-view: specifier: 5.1.7 version: 5.1.7 react-native-reanimated: - specifier: 4.1.3 - version: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: 4.5.1 + version: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: specifier: ^5.5.2 - version: 5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-screens: - specifier: ~4.17.1 - version: 4.17.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~4.26.2 + version: 4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-skeleton-placeholder: specifier: 5.2.4 - version: 5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-linear-gradient@2.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-svg: specifier: ^15.12.1 - version: 15.12.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-url-polyfill: specifier: 2.0.0 - version: 2.0.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 2.0.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) react-native-webrtc: specifier: 124.0.7 - version: 124.0.7(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + version: 124.0.7(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) react-native-webview: specifier: 13.16.1 - version: 13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-worklets: - specifier: ^0.6.1 - version: 0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ^0.10.1 + version: 0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-redux: specifier: 8.0.5 - version: 8.0.5(@types/react@19.1.17)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(redux@4.2.0) + version: 8.0.5(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(redux@4.2.0) reanimated-tab-view: specifier: ^0.3.0 - version: 0.3.0(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 0.3.0(641d271507e632f6b7f49ee76b960c3d) redux: specifier: 4.2.0 version: 4.2.0 @@ -347,10 +350,10 @@ importers: version: 1.5.10 use-debounce: specifier: 9.0.4 - version: 9.0.4(react@19.1.0) + version: 9.0.4(react@19.2.3) use-deep-compare-effect: specifier: 1.6.1 - version: 1.6.1(react@19.1.0) + version: 1.6.1(react@19.2.3) xregexp: specifier: 5.0.2 version: 5.0.2 @@ -359,20 +362,20 @@ importers: version: 0.32.11 zustand: specifier: ^5.0.10 - version: 5.0.12(@types/react@19.1.17)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) + version: 5.0.12(@types/react@19.2.18)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: '@babel/core': - specifier: ~7.25.9 - version: 7.25.9 + specifier: ~7.29.7 + version: 7.29.7 '@babel/plugin-proposal-decorators': specifier: ~7.25.9 - version: 7.25.9(@babel/core@7.25.9) + version: 7.25.9(@babel/core@7.29.7) '@babel/plugin-transform-named-capturing-groups-regex': specifier: ~7.25.9 - version: 7.25.9(@babel/core@7.25.9) + version: 7.25.9(@babel/core@7.29.7) '@babel/preset-env': specifier: ~7.25.9 - version: 7.25.9(@babel/core@7.25.9) + version: 7.25.9(@babel/core@7.29.7) '@babel/runtime': specifier: ~7.25.9 version: 7.25.9 @@ -384,7 +387,7 @@ importers: version: 2.3.3 '@gorhom/bottom-sheet': specifier: ^5 - version: 5.2.8(@types/react@19.1.17)(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 5.2.8(9928f68f03a282f377b2614cb23deb76) '@react-native-community/cli': specifier: 20.0.0 version: 20.0.0(typescript@7.0.2) @@ -396,22 +399,22 @@ importers: version: 20.0.0 '@react-native/babel-preset': specifier: 0.81.5 - version: 0.81.5(@babel/core@7.25.9) + version: 0.81.5(@babel/core@7.29.7) '@react-native/metro-config': - specifier: 0.81.5 - version: 0.81.5(@babel/core@7.25.9) + specifier: 0.86.3 + version: 0.86.3(@babel/core@7.29.7) '@react-native/typescript-config': specifier: 0.81.5 version: 0.81.5 '@storybook/react': specifier: ~9.0.9 - version: 9.0.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) + version: 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) '@storybook/react-native': specifier: ~9.0.9 - version: 9.0.18(b3c380f041753120db9b4b839e8df819) + version: 9.0.18(03fc32bb656def7817eec6786e023d85) '@testing-library/react-native': specifier: ^13.2.0 - version: 13.3.3(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) + version: 13.3.3(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) '@types/bytebuffer': specifier: ~5.0.49 version: 5.0.49 @@ -437,8 +440,8 @@ importers: specifier: 25.0.3 version: 25.0.3 '@types/react': - specifier: ~19.1.0 - version: 19.1.17 + specifier: ~19.2.18 + version: 19.2.18 '@types/semver': specifier: 7.3.13 version: 7.3.13 @@ -450,10 +453,10 @@ importers: version: 1.4.11 babel-jest: specifier: ~29.7.0 - version: 29.7.0(@babel/core@7.25.9) + version: 29.7.0(@babel/core@7.29.7) babel-loader: specifier: ~9.1.3 - version: 9.1.3(@babel/core@7.25.9)(webpack@5.106.2(esbuild@0.25.12)) + version: 9.1.3(@babel/core@7.29.7)(webpack@5.106.2(esbuild@0.25.12)) babel-plugin-module-resolver: specifier: ^5.0.3 version: 5.0.3 @@ -464,8 +467,8 @@ importers: specifier: ^6.9.4 version: 6.9.4 babel-preset-expo: - specifier: ~54.0.9 - version: 54.0.9(@babel/core@7.25.9)(@babel/runtime@7.25.9)(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-refresh@0.14.2) + specifier: ~57.0.12 + version: 57.0.12(@babel/core@7.29.7)(@babel/runtime@7.25.9)(expo@57.0.23)(react-refresh@0.14.2) emojibase-data: specifier: 17.0.0 version: 17.0.0(emojibase@17.0.0) @@ -485,8 +488,8 @@ importers: specifier: ^29.7.0 version: 29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0) jest-expo: - specifier: ~54.0.16 - version: 54.0.16(@babel/core@7.25.9)(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~57.0.5 + version: 57.0.5(@babel/core@7.29.7)(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(expo@57.0.23)(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) oxfmt: specifier: ^0.60.0 version: 0.60.0 @@ -500,8 +503,8 @@ importers: specifier: ~8.0.1 version: 8.0.1 react-dom: - specifier: 19.1.0 - version: 19.1.0(react@19.1.0) + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) react-native-dotenv: specifier: 3.4.8 version: 3.4.8(@babel/runtime@7.25.9) @@ -517,14 +520,6 @@ importers: packages: - '@0no-co/graphql.web@1.2.0': - resolution: {integrity: sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==} - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 - peerDependenciesMeta: - graphql: - optional: true - '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} @@ -539,26 +534,50 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.0': resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + '@babel/core@7.25.9': resolution: {integrity: sha512-WYvQviPw+Qyib0v92AwNIrdLISTp7RfDkM7bPqBvpbnhY4wq8HvHBZREVdYDXk98C8BkOIVnHAY3yvj7AVISxQ==} engines: {node: '>=6.9.0'} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.28.6': resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} engines: {node: '>=6.9.0'} @@ -580,6 +599,10 @@ packages: resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.28.5': resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} @@ -588,12 +611,22 @@ packages: resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.28.6': resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} @@ -602,6 +635,10 @@ packages: resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.27.1': resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} engines: {node: '>=6.9.0'} @@ -622,14 +659,26 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + '@babel/helper-wrap-function@7.28.6': resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} engines: {node: '>=6.9.0'} @@ -638,6 +687,10 @@ packages: resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.25.9': resolution: {integrity: sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==} engines: {node: '>=6.9.0'} @@ -647,6 +700,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} engines: {node: '>=6.9.0'} @@ -767,6 +825,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: @@ -1085,6 +1149,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.29.7': + resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-pure-annotations@7.27.1': resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==} engines: {node: '>=6.9.0'} @@ -1180,12 +1250,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - '@babel/preset-react@7.28.5': - resolution: {integrity: sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/preset-typescript@7.28.5': resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} engines: {node: '>=6.9.0'} @@ -1212,14 +1276,26 @@ packages: resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.29.0': resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1484,8 +1560,8 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@expo/cli@54.0.20': - resolution: {integrity: sha512-cwsXmhftvS0p9NNYOhXGnicBAZl9puWwRt19Qq5eQ6njLnaj8WvcR+kDZyADtgZxBsZiyVlrKXvnjt43HXywQA==} + '@expo/cli@57.0.25': + resolution: {integrity: sha512-G2FJfwClIPNPhH2sJlIFAkhQLYaKNGzSarIQUl6sVA8oGdrmBnhRvbtTzxDzjS6WzO2aDX8DY7y1aNbCFILodg==} hasBin: true peerDependencies: expo: '*' @@ -1497,29 +1573,29 @@ packages: react-native: optional: true - '@expo/code-signing-certificates@0.0.5': - resolution: {integrity: sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==} + '@expo/code-signing-certificates@0.0.6': + resolution: {integrity: sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==} '@expo/config-plugins@10.1.2': resolution: {integrity: sha512-IMYCxBOcnuFStuK0Ay+FzEIBKrwW8OVUMc65+v0+i7YFIIe8aL342l7T4F8lR4oCfhXn7d6M5QPgXvjtc/gAcw==} - '@expo/config-plugins@54.0.4': - resolution: {integrity: sha512-g2yXGICdoOw5i3LkQSDxl2Q5AlQCrG7oniu0pCPPO+UxGb7He4AFqSvPSy8HpRUj55io17hT62FTjYRD+d6j3Q==} + '@expo/config-plugins@57.0.9': + resolution: {integrity: sha512-hHgfL1avkCdEvDSw7IwlKwRYYNgcxzbNNMIk6W6lTkJpY0MajinAfeJUS0J+wPCsjUfGbVqOJM+XhPaO5ulUxg==} '@expo/config-types@53.0.5': resolution: {integrity: sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g==} - '@expo/config-types@54.0.10': - resolution: {integrity: sha512-/J16SC2an1LdtCZ67xhSkGXpALYUVUNyZws7v+PVsFZxClYehDSoKLqyRaGkpHlYrCc08bS0RF5E0JV6g50psA==} + '@expo/config-types@57.0.2': + resolution: {integrity: sha512-ewW08OonrcRIsRKIlFvvcmmafE5zemb1ocu3HkNwtVPyRtj2w42pZCAkMIROYpcVBaPnc3mDT9UZDzwXWC3i6g==} - '@expo/config@12.0.13': - resolution: {integrity: sha512-Cu52arBa4vSaupIWsF0h7F/Cg//N374nYb7HAxV0I4KceKA7x2UXpYaHOL7EEYYvp7tZdThBjvGpVmr8ScIvaQ==} + '@expo/config@57.0.9': + resolution: {integrity: sha512-dmzlKraIFxa7wLwV6K7WzI8jp6QZpW6Mc5mGjLimJUFjzh4uQdYaT3m3plEutM5yxBoBEwqzks7l+I/ljCbxAQ==} '@expo/devcert@1.2.1': resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} - '@expo/devtools@0.1.8': - resolution: {integrity: sha512-SVLxbuanDjJPgc0sy3EfXUMLb/tXzp6XIHkhtPVmTWJAp+FOr6+5SeiCfJrCzZFet0Ifyke2vX3sFcKwEvCXwQ==} + '@expo/devtools@57.0.1': + resolution: {integrity: sha512-GyUf+wFNkbttaX0jR7MZa9bm77U0IrLg6d2AjpxdyoXw/w4abHoXG0oFufwLMgP9zLTd5+Ct4X/ffNUTnlzZgg==} peerDependencies: react: '*' react-native: '*' @@ -1529,59 +1605,115 @@ packages: react-native: optional: true - '@expo/env@2.0.8': - resolution: {integrity: sha512-5VQD6GT8HIMRaSaB5JFtOXuvfDVU80YtZIuUT/GDhUF782usIXY13Tn3IdDz1Tm/lqA9qnRZQ1BF4t7LlvdJPA==} + '@expo/dom-webview@57.0.1': + resolution: {integrity: sha512-lAKsME4SAq+8sf56oN0DX5TBYyruupoRxbWbD2xf9RnKY8y6x8eb9LCE5pxSN0qyWdqnp+0wmyWzDkKboThKAw==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + '@expo/env@2.4.3': + resolution: {integrity: sha512-M1NXeZCA1mkMkYOyIe7PlyRX0/jqFtMoJgyblnlq/vpCRfmueFT7RnGSQG8uEFDF5WHOFGijAQ3fogPh3/n5Ng==} + engines: {node: '>=20.12.0'} + + '@expo/expo-modules-macros-plugin@0.6.1': + resolution: {integrity: sha512-cpsLZE4rqkc1Y3eZTkxB98jrqY1YXgetmtxFt8q89jBRmk3quRuk1BZo+VcnCSObZardjg99r1k5xijEMONFGA==} - '@expo/fingerprint@0.15.4': - resolution: {integrity: sha512-eYlxcrGdR2/j2M6pEDXo9zU9KXXF1vhP+V+Tl+lyY+bU8lnzrN6c637mz6Ye3em2ANy8hhUR03Raf8VsT9Ogng==} + '@expo/fingerprint@0.20.13': + resolution: {integrity: sha512-h1x+w+JpNRu88a/5SMZRzJs9YDGR/f7R8ApCr7OdenhqmJ00hFxQMzvxajDLL+1P2Y2g/ND0xksusqvBqWSW5Q==} hasBin: true - '@expo/image-utils@0.8.8': - resolution: {integrity: sha512-HHHaG4J4nKjTtVa1GG9PCh763xlETScfEyNxxOvfTRr8IKPJckjTyqSLEtdJoFNJ1vqiABEjW7tqGhqGibZLeA==} + '@expo/image-utils@0.11.5': + resolution: {integrity: sha512-KPQBTpmpAfy/Vu9y4wPW808/qtZxjYmyJg8cm2QCPAupp+qEWA3b5zmk0ulOwQ9OgeHxuCPgUqWgkwHFo7UsrQ==} - '@expo/json-file@10.0.8': - resolution: {integrity: sha512-9LOTh1PgKizD1VXfGQ88LtDH0lRwq9lsTb4aichWTWSWqy3Ugfkhfm3BhzBIkJJfQQ5iJu3m/BoRlEIjoCGcnQ==} + '@expo/inline-modules@0.1.7': + resolution: {integrity: sha512-Bz/khd1gIJqDkje7t5ejD5e9jFbm4xEJzWSwRKadRo6gruepbw1xJ3Eb+e58OS8fY1ZNQYjzZbspnuph67sN0g==} + + '@expo/json-file@11.0.1': + resolution: {integrity: sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ==} '@expo/json-file@9.1.5': resolution: {integrity: sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==} - '@expo/metro-config@54.0.12': - resolution: {integrity: sha512-Xhv1z/ak/cuJWeLxlnWr2u22q2AM/klASbjpP5eE34y91lGWa2NUwrFWoS830MhJ6kuAqtGdoQhwyPa3TES7sA==} + '@expo/local-build-cache-provider@57.0.8': + resolution: {integrity: sha512-SEdE0pAQrr90bRh3MNR0ZuwoIBw390cYdFgbn7Vk0Mtm9EHaBfP7kYj+2hXnXZJgOEm3/JMtfoD3rV7rWA9FGg==} + + '@expo/log-box@57.0.4': + resolution: {integrity: sha512-IxwS9s1L2muj8mj8AQSuiy7u8OFJdc02NRFo2me/Tj6DiaeG5SREqmpBE4rQpR2cadqSg5jl8Qab8Cjie616dg==} + peerDependencies: + '@expo/dom-webview': ^57.0.1 + expo: '*' + react: '*' + react-native: '*' + + '@expo/metro-config@57.0.12': + resolution: {integrity: sha512-S62Lrq35HZqBFD55423pmWb8PjaiR/W02zQC1uECBmw1vTN8WZaFz4TJ0i21EeJzwfebMb9MLxL8JZ102Z6VbA==} peerDependencies: expo: '*' peerDependenciesMeta: expo: optional: true - '@expo/metro@54.2.0': - resolution: {integrity: sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w==} + '@expo/metro-file-map@57.0.3': + resolution: {integrity: sha512-1OXy+uPYY5uc7Tm4VBsd2NRn+3wHhqeqNuEO/Xo4kmYgv8FjYgUAc+bUXON9FpC2ikcLn4EVlGM9ce2exx9Mlg==} + + '@expo/metro@56.0.2': + resolution: {integrity: sha512-Ld5AeYMCCDa8bLeWhfuLbZFFjlV3f6ORqyPz2glGh6RltIngMuLf9BTC2yvHFjkKuGxL5SynijmA8xmNNWn5iA==} - '@expo/osascript@2.3.8': - resolution: {integrity: sha512-/TuOZvSG7Nn0I8c+FcEaoHeBO07yu6vwDgk7rZVvAXoeAK5rkA09jRyjYsZo+0tMEFaToBeywA6pj50Mb3ny9w==} + '@expo/osascript@2.7.1': + resolution: {integrity: sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==} engines: {node: '>=12'} - '@expo/package-manager@1.9.9': - resolution: {integrity: sha512-Nv5THOwXzPprMJwbnXU01iXSrCp3vJqly9M4EJ2GkKko9Ifer2ucpg7x6OUsE09/lw+npaoUnHMXwkw7gcKxlg==} + '@expo/package-manager@1.13.1': + resolution: {integrity: sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg==} '@expo/plist@0.3.5': resolution: {integrity: sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g==} - '@expo/plist@0.4.8': - resolution: {integrity: sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ==} + '@expo/plist@0.8.1': + resolution: {integrity: sha512-3gTReGIUm0oRaMClsAJYxBnVPCl6fVpsl8HS+DTVxDhW4GyVyxg9E/Znm3BvcHtUJ51RJJI14pC1wvrNilCRHw==} + + '@expo/prebuild-config@57.0.16': + resolution: {integrity: sha512-QA8oMZ7y70kjOzw+abhRl9uYC8Nu/abeUTHr0YVxb3pgIC14gc7w543bYtI+5gEDI3q55jKUViIauC76QvZN1w==} + + '@expo/require-utils@57.0.5': + resolution: {integrity: sha512-kTAXj9lDFEIPMsbAOGCGbjBbMF0oi7CqkYM79KOX0DDD9wSwXmlKL1z2h8OwsrBf7mbOo2DjlRvZu4BEjrIxGw==} + peerDependencies: + typescript: 7.0.2 + peerDependenciesMeta: + typescript: + optional: true - '@expo/prebuild-config@54.0.8': - resolution: {integrity: sha512-EA7N4dloty2t5Rde+HP0IEE+nkAQiu4A/+QGZGT9mFnZ5KKjPPkqSyYcRvP5bhQE10D+tvz6X0ngZpulbMdbsg==} + '@expo/router-server@57.0.10': + resolution: {integrity: sha512-8rGh6N5di7jzu7UC1SuIA1TWmVd+vBwOHwC7/1ww+FA6CcxcWyQlcPl39h2+Jpnul78CVzWpIjOcdKsZsp2zXw==} peerDependencies: + '@expo/metro-runtime': ^57.0.15 expo: '*' + expo-constants: ^57.0.18 + expo-font: ^57.0.4 + expo-router: '*' + expo-server: ^57.0.3 + react: '*' + react-dom: '*' + react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 + peerDependenciesMeta: + '@expo/metro-runtime': + optional: true + expo-router: + optional: true + react-dom: + optional: true + react-server-dom-webpack: + optional: true - '@expo/schema-utils@0.1.8': - resolution: {integrity: sha512-9I6ZqvnAvKKDiO+ZF8BpQQFYWXOJvTAL5L/227RUbWG1OVZDInFifzCBiqAZ3b67NRfeAgpgvbA7rejsqhY62A==} + '@expo/schema-utils@57.0.2': + resolution: {integrity: sha512-fMu/jyN0l1Wzv7XkeWR4IYCx1M8ryui3FdBNGrWwbRgJ7EhxXxK8E2jxP2W3pbgUwUY0V3hG8+GyfCZwny+Lxw==} '@expo/sdk-runtime-versions@1.0.0': resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} - '@expo/spawn-async@1.7.2': - resolution: {integrity: sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==} + '@expo/spawn-async@1.8.0': + resolution: {integrity: sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw==} engines: {node: '>=12'} '@expo/sudo-prompt@9.3.2': @@ -1594,11 +1726,13 @@ packages: react: '*' react-native: '*' - '@expo/ws-tunnel@1.0.6': - resolution: {integrity: sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==} + '@expo/ws-tunnel@2.0.0': + resolution: {integrity: sha512-j+JfTRdCk820J9dU0sA2SqshQIKFOMo7ED84w9MJFcebfbNQgsLztEY/SABDkGnjatrW4xGqnUhVRxSBVyCkXw==} + peerDependencies: + ws: ^8.0.0 - '@expo/xcpretty@4.3.2': - resolution: {integrity: sha512-ReZxZ8pdnoI3tP/dNnJdnmAk7uLT4FjsKDGW7YeDdvdOMz2XCQSmSCM9IWlrXuWtMF9zeSB6WJtEhCQ41gQOfw==} + '@expo/xcpretty@4.4.5': + resolution: {integrity: sha512-J3eL4n4h5QTwfD0SIz8OIk6/+sOL/hFZAMacgCM07UNlxBQfJipEpIC2AQxvGkbYeStByJ0TVhQAJo+DeNgaSQ==} hasBin: true '@firebase/analytics-compat@0.2.17': @@ -1865,9 +1999,6 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead - '@ide/backoff@1.0.0': - resolution: {integrity: sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==} - '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -1889,10 +2020,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - '@isaacs/ttlcache@1.4.1': resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} engines: {node: '>=12'} @@ -1990,6 +2117,9 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -2410,8 +2540,8 @@ packages: engines: {node: '>=18'} hasBin: true - '@react-native-community/datetimepicker@8.4.4': - resolution: {integrity: sha512-bc4ZixEHxZC9/qf5gbdYvIJiLZ5CLmEsC3j+Yhe1D1KC/3QhaIfGDVdUcid0PdlSoGOSEq4VlB93AWyetEyBSQ==} + '@react-native-community/datetimepicker@9.1.0': + resolution: {integrity: sha512-eadbnk+I2vxvW30iTAsm/qlCnMMAadkifIMYNEB2lzhxN/SvlKc7S2V4k5DyrwjdCbqdcMk3t9K6fnUMcAV34w==} peerDependencies: expo: '>=52.0.0' react: '*' @@ -2429,13 +2559,14 @@ packages: react: '>=18.0.0' react-native: '>=0.70' - '@react-native-community/netinfo@11.4.1': - resolution: {integrity: sha512-B0BYAkghz3Q2V09BF88RA601XursIEA111tnc2JOaN7axJWmNefmfjZqw/KdSxKZp7CZUuPpjBmz/WCR9uaHYg==} + '@react-native-community/netinfo@12.0.1': + resolution: {integrity: sha512-P/3caXIvfYSJG8AWJVefukg+ZGRPs+M4Lp3pNJtgcTYoJxCjWrKQGNnCkj/Cz//zWa/avGed0i/wzm0T8vV2IQ==} peerDependencies: + react: '*' react-native: '>=0.59' - '@react-native-community/slider@5.0.1': - resolution: {integrity: sha512-K3JRWkIW4wQ79YJ6+BPZzp1SamoikxfPRw7Yw4B4PElEQmqZFrmH9M5LxvIo460/3QSrZF/wCgi3qizJt7g/iw==} + '@react-native-community/slider@5.2.0': + resolution: {integrity: sha512-484sH8aWEaSjxaZ7HT3YZ8CKDcNes2synko1vdEz5DFEdvKAduxKJTj22L/qBMD7rtIkfbX69DMzWDAGbOAV6w==} '@react-native-cookies/cookies@6.2.1': resolution: {integrity: sha512-D17wCA0DXJkGJIxkL74Qs9sZ3sA+c+kCoGmXVknW7bVw/W+Vv1m/7mWTNi9DLBZSRddhzYw8SU0aJapIaM/g5w==} @@ -2479,20 +2610,30 @@ packages: react: '*' react-native: '*' - '@react-native/assets-registry@0.81.5': - resolution: {integrity: sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w==} - engines: {node: '>= 20.19.4'} + '@react-native/assets-registry@0.86.3': + resolution: {integrity: sha512-TDhgCZA4wjJg84d5A9swiOQYPIWSKEEVdg9IwMFZDupQzW/F3QoLUrfAJOcalgqTDA9/buTB8awhE3Whwg6u9Q==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} '@react-native/babel-plugin-codegen@0.81.5': resolution: {integrity: sha512-oF71cIH6je3fSLi6VPjjC3Sgyyn57JLHXs+mHWc9MoCiJJcM4nqsS5J38zv1XQ8d3zOW2JtHro+LF0tagj2bfQ==} engines: {node: '>= 20.19.4'} + '@react-native/babel-plugin-codegen@0.86.3': + resolution: {integrity: sha512-O6Xza4JBGPIU8J7YbKTyBoYL4thpy8jMW/oaLDWdAyOwYHKIjK47pAL5HUEbOe2bWz2PEKjbYRF2ApkJv1ottQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/babel-preset@0.81.5': resolution: {integrity: sha512-UoI/x/5tCmi+pZ3c1+Ypr1DaRMDLI3y+Q70pVLLVgrnC3DHsHRIbHcCHIeG/IJvoeFqFM2sTdhSOLJrf8lOPrA==} engines: {node: '>= 20.19.4'} peerDependencies: '@babel/core': '*' + '@react-native/babel-preset@0.86.3': + resolution: {integrity: sha512-/eqs/Hy9RZRcjdcs4wj3Cqmxvtb3NM5g+Uuh1RIvsjynMO8PRsrVWWLWgBcZL/jYUo+ogXd2NFB0W8L5Bg89xw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + '@react-native/codegen@0.80.2': resolution: {integrity: sha512-eYad9ex9/RS6oFbbpu6LxsczktbhfJbJlTvtRlcWLJjJbFTeNr5Q7CgBT2/m5VtpxnJ/0YdmZ9vdazsJ2yp9kw==} engines: {node: '>=18'} @@ -2505,60 +2646,76 @@ packages: peerDependencies: '@babel/core': '*' - '@react-native/community-cli-plugin@0.81.5': - resolution: {integrity: sha512-yWRlmEOtcyvSZ4+OvqPabt+NS36vg0K/WADTQLhrYrm9qdZSuXmq8PmdJWz/68wAqKQ+4KTILiq2kjRQwnyhQw==} - engines: {node: '>= 20.19.4'} + '@react-native/codegen@0.86.3': + resolution: {integrity: sha512-Ux4jHi0fh+bdtVEcL0gaPLbY56V+SvFUDl/8sRAE1jdb4k+o7fT/4Nc29yz4X+qfjstkSqObQTMBGhdzxH9JvA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + + '@react-native/community-cli-plugin@0.86.3': + resolution: {integrity: sha512-qSDL9LQc5mZSZPNczT95WU9YQuPzxBklgON9vLhhqfI0yWIwKInqFx88dQ/uiEXBtf0yossthaQIqA4Ml6bF6g==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} peerDependencies: '@react-native-community/cli': '*' - '@react-native/metro-config': '*' + '@react-native/metro-config': 0.86.3 peerDependenciesMeta: '@react-native-community/cli': optional: true '@react-native/metro-config': optional: true - '@react-native/debugger-frontend@0.81.5': - resolution: {integrity: sha512-bnd9FSdWKx2ncklOetCgrlwqSGhMHP2zOxObJbOWXoj7GHEmih4MKarBo5/a8gX8EfA1EwRATdfNBQ81DY+h+w==} - engines: {node: '>= 20.19.4'} + '@react-native/debugger-frontend@0.86.3': + resolution: {integrity: sha512-TQmeofQ0PcuylhhlleOeuzHYZfbrgm3gayXzowqUEzgRisTm1D40/J3ggqs7XkQi5HP5ZA3n8dHmKL9vIzPcsw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/dev-middleware@0.81.5': - resolution: {integrity: sha512-WfPfZzboYgo/TUtysuD5xyANzzfka8Ebni6RIb2wDxhb56ERi7qDrE4xGhtPsjCL4pQBXSVxyIlCy0d8I6EgGA==} - engines: {node: '>= 20.19.4'} + '@react-native/debugger-shell@0.86.3': + resolution: {integrity: sha512-O4ds+J7xZfxkbih9T+cAGegBdvKSPKYJm/lDgC9CpEjFMkmzWTpVLU3Qsv9sqZuo58z+sGhIcJfPsCFFWHpqbQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/gradle-plugin@0.81.5': - resolution: {integrity: sha512-hORRlNBj+ReNMLo9jme3yQ6JQf4GZpVEBLxmTXGGlIL78MAezDZr5/uq9dwElSbcGmLEgeiax6e174Fie6qPLg==} - engines: {node: '>= 20.19.4'} + '@react-native/dev-middleware@0.86.3': + resolution: {integrity: sha512-LiEPTqTg/63bYUnrPyHLfjTDCNhA/+CUqI1+DsA9tYyewtSbULd5awsva6SgE10I+2iMhgKXS3ymkhU/kSCrGA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/js-polyfills@0.81.5': - resolution: {integrity: sha512-fB7M1CMOCIUudTRuj7kzxIBTVw2KXnsgbQ6+4cbqSxo8NmRRhA0Ul4ZUzZj3rFd3VznTL4Brmocv1oiN0bWZ8w==} - engines: {node: '>= 20.19.4'} + '@react-native/gradle-plugin@0.86.3': + resolution: {integrity: sha512-lxmx0GqLEWRIpZfpYFXlYVIs3ENQwaW6Vmp6oi29l2GoQJ1wZfFZRdMimDWlGEk8LKfHar3QH3iaPMkTcK9lEQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/metro-babel-transformer@0.81.5': - resolution: {integrity: sha512-Vwm6gJ3VlP+QKAEU98v1dwZKqbUcIYP47K614SktA9dYDOtw+rEBjyzvNf69S4YG5JRvDmzw36E9zxtcg6ABOw==} + '@react-native/jest-preset@0.86.3': + resolution: {integrity: sha512-oMmBwXUpTgJvGlcXQQPuiQLykoM8jsWYp6RwdJ2NWyIWzJcGp1rmSr3XV7istoIcUt0wdOVLtQs0A0nDm9ND+A==} engines: {node: '>= 20.19.4'} + peerDependencies: + react: ^19.2.3 + + '@react-native/js-polyfills@0.86.3': + resolution: {integrity: sha512-eYIJ0es967+tePBFQDnl/gidVFxLns3fnbiK6rxscQrGodvuUO6hwxpQnfNynJ8MjbbndImXihXjcnJdc7SzJg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/metro-babel-transformer@0.86.3': + resolution: {integrity: sha512-0nlwVkG0uT9o72nrGTKUz/IqDnCkM4zE0ihe4r+OJcxH93yBdDm58AXJ3EXZeSjv3osHEFXU7ceajdZiS34mTw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} peerDependencies: '@babel/core': '*' - '@react-native/metro-config@0.81.5': - resolution: {integrity: sha512-3Q0jQt5Zcen4+udkE0XQIS8VmI+vx3sWl5R2o36vHkg8eXpiQjvz/jY0sZmC8ahailiEWEscFklQzhTmizhdPQ==} - engines: {node: '>= 20.19.4'} + '@react-native/metro-config@0.86.3': + resolution: {integrity: sha512-qdzDMepV2xdUhsO4XC7idnnbt8K6+Hd59AjeK9y4KS86fxSQq5oL0TAbqUuajDGR7beNQoYTuS6AowN/zP0oaQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} '@react-native/normalize-color@2.1.0': resolution: {integrity: sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==} - '@react-native/normalize-colors@0.81.5': - resolution: {integrity: sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==} + '@react-native/normalize-colors@0.86.3': + resolution: {integrity: sha512-Cv3CDkprb67GrzuaS9BGbBJC/6G4lIw3nyKOHRKTqTTum4bn37y5+R0Z04L8mcbQN85eEohNrRwb7IOM4j6uvg==} '@react-native/typescript-config@0.81.5': resolution: {integrity: sha512-NeCecPmlW+fcwFKzDzT1GcEQmJSE6tLz9Fg6wGjKL1l7pqUzpQIQg1iF3OovHOlyfPiB98+XRHnIBvlTSJ5R0w==} - '@react-native/virtualized-lists@0.81.5': - resolution: {integrity: sha512-UVXgV/db25OPIvwZySeToXD/9sKKhOdkcWmmf4Jh8iBZuyfML+/5CasaZ1E7Lqg6g3uqVQq75NqIwkYmORJMPw==} - engines: {node: '>= 20.19.4'} + '@react-native/virtualized-lists@0.86.3': + resolution: {integrity: sha512-1j44NEyNn05Ut40vHAmoSWbsIcybFkMAOBTwQt1PrESyfSS+qBoyU1LGIogNva0VIa0rQyEC5PzbA7R4/7Nhyw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} peerDependencies: - '@types/react': ^19.1.0 + '@types/react': ^19.2.0 react: '*' - react-native: '*' + react-native: 0.86.3 peerDependenciesMeta: '@types/react': optional: true @@ -2759,7 +2916,7 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta storybook: ^9.0.18 - typescript: '>= 4.9.x' + typescript: 7.0.2 peerDependenciesMeta: typescript: optional: true @@ -2771,7 +2928,7 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta storybook: ^9.1.17 - typescript: '>= 4.9.x' + typescript: 7.0.2 peerDependenciesMeta: typescript: optional: true @@ -2836,6 +2993,9 @@ packages: '@types/ejson@2.2.2': resolution: {integrity: sha512-4DEXy6yKJVr1DQx4xy+y7NV9mK6p1vgikvtbOITWgp2Prs6yhffqZ8XOjEtojX2pzC7B1KF9kcQe9Bv52E9K2A==} + '@types/emscripten@1.41.6': + resolution: {integrity: sha512-uN+9i8bFT5CUcZfyIEYDrSueACEyKGbUs5kC/72DGlZZoinh84sJfVV0i8UOJD1asdzkvLPBRrKs41kZ8MdEXg==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -2907,11 +3067,14 @@ packages: '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/react-test-renderer@19.3.0': + resolution: {integrity: sha512-TX4p2D4Z2oigM3SXU7+QyLSUnSR0vR1S1Pfxm/qFL9nHQ3jclgi6ioGoWQmlIr1DeKRVmPJrmNlhgM0JlWlLUg==} + '@types/react@17.0.90': resolution: {integrity: sha512-P9beVR/x06U9rCJzSxtENnOr4BrbJ6VrsrDTc+73TtHv9XHhryXKbjGRB+6oooB2r0G/pQkD/S4dHo/7jUfwFw==} - '@types/react@19.1.17': - resolution: {integrity: sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==} + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} '@types/scheduler@0.16.8': resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} @@ -3170,14 +3333,6 @@ packages: cpu: [x64] os: [win32] - '@urql/core@5.2.0': - resolution: {integrity: sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==} - - '@urql/exchange-retry@1.3.2': - resolution: {integrity: sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg==} - peerDependencies: - '@urql/core': ^5.0.0 - '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -3275,6 +3430,10 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-globals@7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} @@ -3306,6 +3465,11 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agent-cli-detector@0.1.7: + resolution: {integrity: sha512-d8OWDVdZMgjhLUT9ZPgSv/BdFFF9pVuscC0JdUSz3bjwE15gcp6u/o0/JooM2yyAWC49KThFhXlgTXRa9B7yng==} + engines: {node: '>=18.18'} + hasBin: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -3367,9 +3531,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -3431,9 +3592,6 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - assert@2.1.0: - resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -3528,6 +3686,12 @@ packages: babel-plugin-syntax-hermes-parser@0.29.1: resolution: {integrity: sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA==} + babel-plugin-syntax-hermes-parser@0.36.0: + resolution: {integrity: sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==} + + babel-plugin-syntax-hermes-parser@0.36.1: + resolution: {integrity: sha512-ycduwJbvdvIMmVvlAZqGggS+pm5Eu4Bk9pcV9Sm2Z4PJNRVsKkv0g7vHj+LeuC1gHTeF67sJXFOq61IlqCa2hA==} + babel-plugin-transform-flow-enums@0.0.2: resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} @@ -3539,17 +3703,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0 || ^8.0.0-0 - babel-preset-expo@54.0.9: - resolution: {integrity: sha512-8J6hRdgEC2eJobjoft6mKJ294cLxmi3khCUy2JJQp4htOYYkllSLUq6vudWJkTJiIuGdVR4bR6xuz2EvJLWHNg==} + babel-preset-expo@57.0.12: + resolution: {integrity: sha512-rrZqJPYbVevy7rqibJZQFd3AsleGslJ63vmB3Qs8b5poOO5AHaehUGbZHjb0HoOZO59d86BEifYbq0/ncoM2UQ==} peerDependencies: '@babel/runtime': ^7.20.0 expo: '*' + expo-widgets: ^57.0.19 react-refresh: '>=0.14.0 <1.0.0' peerDependenciesMeta: '@babel/runtime': optional: true expo: optional: true + expo-widgets: + optional: true babel-preset-jest@29.6.3: resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} @@ -3563,6 +3730,13 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + barcode-detector@3.2.2: + resolution: {integrity: sha512-/4QOrrNrCRmDSBWiiP4aC72dnkuXUEdcFRidHgbPRXUpy82XcCMvJssI6fEs6JT42LS7DvBVZO70qasJbYKyrA==} + bare-events@2.8.2: resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} peerDependencies: @@ -3643,6 +3817,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -3734,10 +3912,6 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - chrome-launcher@0.15.2: resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} engines: {node: '>=12.13.0'} @@ -3747,8 +3921,8 @@ packages: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} - chromium-edge-launcher@0.2.0: - resolution: {integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==} + chromium-edge-launcher@0.3.0: + resolution: {integrity: sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==} ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} @@ -3853,10 +4027,6 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -3902,6 +4072,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -3919,7 +4093,7 @@ packages: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} peerDependencies: - typescript: '>=4.9.5' + typescript: 7.0.2 peerDependenciesMeta: typescript: optional: true @@ -3933,10 +4107,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypto-random-string@2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} - engines: {node: '>=8'} - css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} @@ -4108,6 +4278,9 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dnssd-advertise@1.1.6: + resolution: {integrity: sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -4140,14 +4313,6 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - dotenv-expand@11.0.7: - resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} - engines: {node: '>=12'} - - dotenv@16.4.7: - resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} - engines: {node: '>=12'} - dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} @@ -4214,10 +4379,6 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} - env-editor@0.4.2: - resolution: {integrity: sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==} - engines: {node: '>=8'} - env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -4439,9 +4600,6 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - exec-async@2.2.0: - resolution: {integrity: sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==} - execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -4458,33 +4616,35 @@ packages: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - expo-apple-authentication@8.0.8: - resolution: {integrity: sha512-TwCHWXYR1kS0zaeV7QZKLWYluxsvqL31LFJubzK30njZqeWoWO89HZ8nZVaeXbFV1LrArKsze4BmMb+94wS0AQ==} + expo-apple-authentication@57.0.2: + resolution: {integrity: sha512-00KnlyRceRKski+ljZz8q8JhJZ4D3ZScU+Y0RqS6ElJ8GaCWfd7Qh5tMr47yIU/dKDrOnkzkFUM+6vyHS5HI7Q==} peerDependencies: expo: '*' + react: '*' react-native: '*' - expo-application@7.0.8: - resolution: {integrity: sha512-qFGyxk7VJbrNOQWBbE09XUuGuvkOgFS9QfToaK2FdagM2aQ+x3CvGV2DuVgl/l4ZxPgIf3b/MNh9xHpwSwn74Q==} + expo-application@57.0.3: + resolution: {integrity: sha512-t/CxeLio0NjY9/cOak1tQp62/hAUjzJxmhR4rVUR7K5bb9YoEtfl1ZekTn+Iuh+dYOMYzF0l72ODMSmY3CR1NA==} peerDependencies: expo: '*' - expo-asset@12.0.12: - resolution: {integrity: sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ==} + expo-asset@57.0.17: + resolution: {integrity: sha512-qSdWZBSfEg06o3GJMwcj36tw0vXkB6eYjqkQistWmyJkHxhUOKnsKJnev6OraXxLdoI+KOd1+qgp/ENx8gcqYA==} peerDependencies: expo: '*' react: '*' react-native: '*' - expo-audio@0.4.9: - resolution: {integrity: sha512-J4mMYEt2mqRqqwmSsXFylMGlrNWa+MbCzGl1IZBs+smvPAMJ3Ni8fNplzCQ0I9RnRzygKhRwJNpnAVL+n4MuyA==} + expo-audio@57.0.5: + resolution: {integrity: sha512-39fX5JC5zDvELB4/eDJPj2XAYRN0W9+K2DfDWm5N3aC5WZ7HRrhupWtT6hbH09NlfYpsRSqYOwEZnocFM42ynQ==} peerDependencies: expo: '*' + expo-asset: '*' react: '*' react-native: '*' - expo-camera@17.0.10: - resolution: {integrity: sha512-w1RBw83mAGVk4BPPwNrCZyFop0VLiVSRE3c2V9onWbdFwonpRhzmB4drygG8YOUTl1H3wQvALJHyMPTbgsK1Jg==} + expo-camera@57.0.5: + resolution: {integrity: sha512-0JaUXfDWMytmqm4pVKesZfLTSjAumfS2UGhnc1M4tqJ0qATCS0jxc7KjaoyKEzWJY+FEc8GlPCAGprS/7tNWZQ==} peerDependencies: expo: '*' react: '*' @@ -4494,42 +4654,42 @@ packages: react-native-web: optional: true - expo-constants@18.0.12: - resolution: {integrity: sha512-WzcKYMVNRRu4NcSzfIVRD5aUQFnSpTZgXFrlWmm19xJoDa4S3/PQNi6PNTBRc49xz9h8FT7HMxRKaC8lr0gflA==} + expo-constants@57.0.18: + resolution: {integrity: sha512-cLDwDOniPfuCZPHNY++cAY2ZkK97u5aobbBvilPK2LLmaWxR4ZpD31LWUIzUDLrwMPxLjmh1uURnbeYEf0Mivw==} peerDependencies: expo: '*' react-native: '*' - expo-device@8.0.10: - resolution: {integrity: sha512-jd5BxjaF7382JkDMaC+P04aXXknB2UhWaVx5WiQKA05ugm/8GH5uaz9P9ckWdMKZGQVVEOC8MHaUADoT26KmFA==} + expo-device@57.0.2: + resolution: {integrity: sha512-1A0/FICR3CgrH43FeiDQP3l5eHDmiMoh4EjXfhfoHHkMgdBQdyD0kwDWskiGZlQ3E0Nd0LHkCFH6vJ1ofm/jow==} peerDependencies: expo: '*' - expo-document-picker@14.0.8: - resolution: {integrity: sha512-3tyQKpPqWWFlI8p9RiMX1+T1Zge5mEKeBuXWp1h8PEItFMUDSiOJbQ112sfdC6Hxt8wSxreV9bCRl/NgBdt+fA==} + expo-document-picker@57.0.2: + resolution: {integrity: sha512-vVBbBNg0piMJUIxjB+DR6JaZZpcSl90ZUoywLA/EgNHTfRkZW+9axP97UxM2a0Zm0W6WfH3cLzzGAEfP9OKGwg==} peerDependencies: expo: '*' - expo-file-system@19.0.21: - resolution: {integrity: sha512-s3DlrDdiscBHtab/6W1osrjGL+C2bvoInPJD7sOwmxfJ5Woynv2oc+Fz1/xVXaE/V7HE/+xrHC/H45tu6lZzzg==} + expo-file-system@57.0.7: + resolution: {integrity: sha512-1vkZRadOsIputFDBcclqBa1pO3iXOO6iIsdqJipQWw4V5TSnWgmRTuOF7ro8IzrVue+PHbum++KCG3Tawem4cg==} peerDependencies: expo: '*' react-native: '*' - expo-font@14.0.10: - resolution: {integrity: sha512-UqyNaaLKRpj4pKAP4HZSLnuDQqueaO5tB1c/NWu5vh1/LF9ulItyyg2kF/IpeOp0DeOLk0GY0HrIXaKUMrwB+Q==} + expo-font@57.0.4: + resolution: {integrity: sha512-7WOMC2oA6xCfOm8bbofqWn2bIqyWFm7YLEnpc/gA1MJH0Ll0TfT/3+CqRkoW2eNRWMS8sxrz5WukUmSvmNquHA==} peerDependencies: expo: '*' react: '*' react-native: '*' - expo-haptics@15.0.8: - resolution: {integrity: sha512-lftutojy8Qs8zaDzzjwM3gKHFZ8bOOEZDCkmh2Ddpe95Ra6kt2izeOfOfKuP/QEh0MZ1j9TfqippyHdRd1ZM9g==} + expo-haptics@57.0.3: + resolution: {integrity: sha512-qucUXtwtJdtECKSQK5D6TQBc/0SxKj7s5wQ4mrfSxhZsHrS9a4rcxINl/gbruMaY9spTXStco32ST4pV0vXmxw==} peerDependencies: expo: '*' - expo-image@3.0.11: - resolution: {integrity: sha512-4TudfUCLgYgENv+f48omnU8tjS2S0Pd9EaON5/s1ZUBRwZ7K8acEr4NfvLPSaeXvxW24iLAiyQ7sV7BXQH3RoA==} + expo-image@57.0.5: + resolution: {integrity: sha512-jOm5lofaIKXhQ7i0Hz3I5JuNULw/aghmpIPZXddwQcEtw8+wUhlozq3M7LbsOrcsBEapcG4jFs85DsfuFoz1tQ==} peerDependencies: expo: '*' react: '*' @@ -4539,46 +4699,56 @@ packages: react-native-web: optional: true - expo-keep-awake@15.0.8: - resolution: {integrity: sha512-YK9M1VrnoH1vLJiQzChZgzDvVimVoriibiDIFLbQMpjYBnvyfUeHJcin/Gx1a+XgupNXy92EQJLgI/9ZuXajYQ==} + expo-keep-awake@57.0.2: + resolution: {integrity: sha512-GqgH746wtJImmsbyHmCQoi7cOslKuWQBbJHXr53S35Bpf5UvxCAztvnitUEd7D0QvFN2rvkOhylbZpgZZfBLlQ==} peerDependencies: expo: '*' react: '*' - expo-local-authentication@17.0.8: - resolution: {integrity: sha512-Q5fXHhu6w3pVPlFCibU72SYIAN+9wX7QpFn9h49IUqs0Equ44QgswtGrxeh7fdnDqJrrYGPet5iBzjnE70uolA==} + expo-local-authentication@57.0.3: + resolution: {integrity: sha512-1oQCBKz8JOpai+Zp+Z2RkBFIi2CHHWLSJk/pjvT9VEyf8nPFslo4kt1ZlQvtJ4rupbKNCmxqKfYr8Mlo8nJEug==} peerDependencies: expo: '*' - expo-modules-autolinking@3.0.23: - resolution: {integrity: sha512-YZnaE0G+52xftjH5nsIRaWsoVBY38SQCECclpdgLisdbRY/6Mzo7ndokjauOv3mpFmzMZACHyJNu1YSAffQwTg==} + expo-modules-autolinking@57.0.13: + resolution: {integrity: sha512-Hj5NjZRlfccazhKab+wK19leRVUr1Y/PUERaRvegIbUSkq4GVxKq6lU9QHlKQyC16MdD/VS4iwsiLivB4o7lvQ==} hasBin: true - expo-modules-core@3.0.29: - resolution: {integrity: sha512-LzipcjGqk8gvkrOUf7O2mejNWugPkf3lmd9GkqL9WuNyeN2fRwU0Dn77e3ZUKI3k6sI+DNwjkq4Nu9fNN9WS7Q==} + expo-modules-core@57.0.18: + resolution: {integrity: sha512-ovB7C+GVQTlKAOIsOIHJVVXN9Mn0ljf2SYBS+afrRaGqq+HZEn1D3o+HReIVeH1NjCn/xDBb7I4HjldHs6luZA==} peerDependencies: react: '*' react-native: '*' + react-native-worklets: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 + peerDependenciesMeta: + react-native-worklets: + optional: true + + expo-modules-jsi@57.1.0: + resolution: {integrity: sha512-a5ckeHfnbYfonhcHGkM0EU/a0Keh+/OufXy/HyFS+spr5Ib0N4Oh1ZsSYFQhp5xdOUFkVofKRhfH+VmcccIIpA==} + peerDependencies: + react-native: '*' - expo-notifications@0.32.15: - resolution: {integrity: sha512-gnJcauheC2S0Wl0RuJaFkaBRVzCG011j5hlG0TEbsuOCPBuB/F30YEk8yurK8Psv+zHkVfeiJ5AC+nL0LWk0WA==} + expo-notifications@57.0.19: + resolution: {integrity: sha512-JW/usGEwOxWzN8s0L7+j3FAoB39GtaZtpUGye2lPvcqYSkvewpZYkrC16P1JaCP09YRx0Q+qXPx5NUoUU8NsBA==} peerDependencies: expo: '*' react: '*' react-native: '*' - expo-server@1.0.5: - resolution: {integrity: sha512-IGR++flYH70rhLyeXF0Phle56/k4cee87WeQ4mamS+MkVAVP+dDlOHf2nN06Z9Y2KhU0Gp1k+y61KkghF7HdhA==} + expo-server@57.0.3: + resolution: {integrity: sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==} engines: {node: '>=20.16.0'} - expo-status-bar@3.0.9: - resolution: {integrity: sha512-xyYyVg6V1/SSOZWh4Ni3U129XHCnFHBTcUo0dhWtFDrZbNp/duw5AGsQfb2sVeU0gxWHXSY1+5F0jnKYC7WuOw==} + expo-status-bar@57.0.1: + resolution: {integrity: sha512-Xwaq1gAoVRWx5dPG5VhT5RSbnI9OilhZnO5qoPBnUaBAa5VzRzfdS8q0/bsPt0jR2DKLtGuP0bQ6efMJ4RIMDg==} peerDependencies: + expo: '*' react: '*' react-native: '*' - expo-system-ui@6.0.9: - resolution: {integrity: sha512-eQTYGzw1V4RYiYHL9xDLYID3Wsec2aZS+ypEssmF64D38aDrqbDgz1a2MSlHLQp2jHXSs3FvojhZ9FVela1Zcg==} + expo-system-ui@57.0.4: + resolution: {integrity: sha512-z//rxQJzhTNA1PcKrUtWmrd21jAS3nEDu1b0n8B/7LTTR933OcsZYP1HnbaZifuo2ZH/h7nce2Ow61WZ1Ig2bg==} peerDependencies: expo: '*' react-native: '*' @@ -4587,38 +4757,44 @@ packages: react-native-web: optional: true - expo-video-thumbnails@10.0.8: - resolution: {integrity: sha512-nPUtP7ERLf5DY5V2A6gquRP5rP3Uvq6+FVkDwG9R3KKhFeTYkWZ5Ce1iQ7Yt5qDNQqcUcgEqmRpGCbJmn9ckKA==} + expo-video-thumbnails@57.0.2: + resolution: {integrity: sha512-9nqx9nzjdFuSweJL/WsCaDoZOpd3lzOswudvoBsHUZvx0xfHJ+iGo3fND2u58P1CBT7qVjEiVm0wMBZKB6Z14g==} peerDependencies: expo: '*' - expo-video@3.0.16: - resolution: {integrity: sha512-H1HlxcHGomZItqisGfW3YL/G9BHtNBfVSimDJcLuyxyU87wFnV8loO9tCjuhufkfh/aTa2sW5BYAjLjg9DvnBQ==} + expo-video@57.0.4: + resolution: {integrity: sha512-4YCWG1IJzUNpLX0xoussK0VOG524qgUiMbwJzrgpDuPUzOgR3ZqXSZxKsy3HlYLiuWdzIQzwfpbtSNim9F9D3Q==} peerDependencies: expo: '*' react: '*' react-native: '*' - expo-web-browser@15.0.10: - resolution: {integrity: sha512-fvDhW4bhmXAeWFNFiInmsGCK83PAqAcQaFyp/3pE/jbdKmFKoRCWr46uZGIfN4msLK/OODhaQ/+US7GSJNDHJg==} + expo-web-browser@57.0.3: + resolution: {integrity: sha512-+ecvhyS/PdWkpv4ai6FF1Trd6Y/4kn/eQ3O86mie2ecU3w+I8bKZh13kppY/jw1uSL7yikt03L5W/iiHojrFwQ==} peerDependencies: expo: '*' react-native: '*' - expo@54.0.30: - resolution: {integrity: sha512-6q+aFfKL0SpT8prfdpR3V8HcN51ov0mCGuwQTzyuk6eeO9rg7a7LWbgPv9rEVXGZEuyULstL8LGNwHqusand7Q==} + expo@57.0.23: + resolution: {integrity: sha512-/aBLZFTD7+BNHWV/q5XbNRFqMhW/CrHsYeq8CcS/iGz6lJHYwEMm4XFcdoaeliuMdo6ZjNrsTHkBIdGB8VMHTg==} hasBin: true peerDependencies: '@expo/dom-webview': '*' '@expo/metro-runtime': '*' react: '*' + react-dom: '*' react-native: '*' + react-native-web: '*' react-native-webview: '*' peerDependenciesMeta: '@expo/dom-webview': optional: true '@expo/metro-runtime': optional: true + react-dom: + optional: true + react-native-web: + optional: true react-native-webview: optional: true @@ -4655,6 +4831,11 @@ packages: resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} engines: {node: '>=0.8.0'} + fb-dotslash@0.5.8: + resolution: {integrity: sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==} + engines: {node: '>=20'} + hasBin: true + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -4667,6 +4848,9 @@ packages: picomatch: optional: true + fetch-nodeshim@0.4.10: + resolution: {integrity: sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==} + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -4758,10 +4942,6 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} - freeport-async@2.0.0: - resolution: {integrity: sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==} - engines: {node: '>=8'} - fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -4874,10 +5054,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - global-dirs@0.1.1: - resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} - engines: {node: '>=4'} - globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -4943,14 +5119,23 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + hermes-compiler@250829098.0.17: + resolution: {integrity: sha512-qG1PXzTEtriF6oQLZF3vyHhSMxOdW5h2TqqLri0rdpstPustd2fSvRZQMVAPdlhgFwBfYnj3OUZtiO6LjYsEFw==} + hermes-estree@0.28.1: resolution: {integrity: sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ==} hermes-estree@0.29.1: resolution: {integrity: sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==} - hermes-estree@0.32.0: - resolution: {integrity: sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==} + hermes-estree@0.35.0: + resolution: {integrity: sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==} + + hermes-estree@0.36.0: + resolution: {integrity: sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==} + + hermes-estree@0.36.1: + resolution: {integrity: sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==} hermes-parser@0.28.1: resolution: {integrity: sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg==} @@ -4958,8 +5143,14 @@ packages: hermes-parser@0.29.1: resolution: {integrity: sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==} - hermes-parser@0.32.0: - resolution: {integrity: sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==} + hermes-parser@0.35.0: + resolution: {integrity: sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==} + + hermes-parser@0.36.0: + resolution: {integrity: sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==} + + hermes-parser@0.36.1: + resolution: {integrity: sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==} hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -5030,11 +5221,6 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - image-size@1.2.1: - resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} - engines: {node: '>=16.x'} - hasBin: true - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -5159,10 +5345,6 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} - is-nan@1.3.2: - resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} - engines: {node: '>= 0.4'} - is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} @@ -5333,14 +5515,17 @@ packages: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-expo@54.0.16: - resolution: {integrity: sha512-wPV5dddlNMORNSA7ZjEjePA+ztks3G5iKCOHLIauURnKQPTscnaat5juXPboK1Bv2I+c/RDfkt4uZtAmXdlu/g==} + jest-expo@57.0.5: + resolution: {integrity: sha512-U08fwjaDBwnnbKnZcOA0WQbHfKqOWTCGku+IdCaekrLZmfbbZr3P1WqEsXHo6ntidQ3DmW+TT6G1aOp+ChYfKA==} hasBin: true peerDependencies: + '@react-native/jest-preset': ^0.86.3 expo: '*' react-native: '*' - react-server-dom-webpack: ~19.0.3 || ~19.1.4 || ~19.2.3 + react-server-dom-webpack: ~19.0.4 || ~19.1.5 || ~19.2.4 peerDependenciesMeta: + expo: + optional: true react-server-dom-webpack: optional: true @@ -5537,8 +5722,8 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - lan-network@0.1.7: - resolution: {integrity: sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ==} + lan-network@0.2.1: + resolution: {integrity: sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==} hasBin: true launch-editor@2.12.0: @@ -5763,62 +5948,120 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - metro-babel-transformer@0.83.3: - resolution: {integrity: sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==} - engines: {node: '>=20.19.4'} + metro-babel-transformer@0.84.5: + resolution: {integrity: sha512-2WbHILKMiJUzfdjmGOQOqU1bWi9//gqiclc/tkk/AIsrrVw3efhZ1uhkOwMTxUEPOzqoo091H0olLmVZH5FHGQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-babel-transformer@0.84.6: + resolution: {integrity: sha512-B1ozl6KxFHbQjXZ2U8WiTPWXylqiD3V4EXyV5r0fqETOrqIrJlBvbVTD82UxIN+B3Vpe4l8G1tb15ocd2RyOQA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache-key@0.84.5: + resolution: {integrity: sha512-3dPB2TnvGjjf0/9O7AXVQURKXuQNauTZE7WpTGTlR017Gh/B5y0m/2wcqxfveUguHSpu89KhVxCAlr2k/H7uhQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache-key@0.84.6: + resolution: {integrity: sha512-6tyXt1BZ/3U183XV48oif7Nm65TE+pTo0Vs31u4FxMgqiYQR/SiSr3RoXn3pmy4pd280ZmLmct0x3jZpu0pV4A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache@0.84.5: + resolution: {integrity: sha512-WHS0n2OxQqtwEjSeQFPePNrMvEFhmQcUQM9cRJMHByWoi/GMWFBEWOf7hVkAM/0KRutAXNbDlSu/cZB6CyxgQQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache@0.84.6: + resolution: {integrity: sha512-KBJVpb02oKNO+jlWQ1Aax7cd11YP0MClDaMgx6C4MhyZ9g95J/dC1Bo6sCzXaU9L9h/w5cQfT5zDkGW5sOj99A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-config@0.84.5: + resolution: {integrity: sha512-zie+uN6oohscowi2S7ByU+wUw6CrT4ZxW9uAbONOObSxx86RGmnIAmjXHLkfmcdYoY7jzOPEbqcI6oeVmqyBQA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-cache-key@0.83.3: - resolution: {integrity: sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==} - engines: {node: '>=20.19.4'} + metro-config@0.84.6: + resolution: {integrity: sha512-cD2mLEofcuV26kxSV4hfV1Pbuh6igdUhYQriO8ZVeJ7HyZfq4/wqHV0gSR7fEO8NUp9odr60/Kzm5YakmghWwQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-cache@0.83.3: - resolution: {integrity: sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==} - engines: {node: '>=20.19.4'} + metro-core@0.84.5: + resolution: {integrity: sha512-xwm605hCi5Y6eJTTb8ZWo6pkUcoBEIyiQOfkZh5GwtDwUrP9SNhTQZhzJHrBCwwxlf3Ptl/pxWJgQ1rsNYMnrA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-config@0.83.3: - resolution: {integrity: sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==} - engines: {node: '>=20.19.4'} + metro-core@0.84.6: + resolution: {integrity: sha512-ZqYskM8+f3PFMosBDJd8DLqRvT1ltRGhWL/ZfTpBvf4SoadO30VVrdhAoHFVGz3G1CJpWa3fgZH7nnnX/ybllA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-core@0.83.3: - resolution: {integrity: sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==} - engines: {node: '>=20.19.4'} + metro-file-map@0.84.5: + resolution: {integrity: sha512-mlm/JL8toSbSc2akpKIGmzvrVRSCgZ5vkbycI34oMLoOnLGuLyC8WTyVJ6P0hZG/usDaGwZSl/s9BCRriqjGJA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-file-map@0.83.3: - resolution: {integrity: sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==} - engines: {node: '>=20.19.4'} + metro-file-map@0.84.6: + resolution: {integrity: sha512-ov9VywWBsHPt54Zc7/DxWvqjTwxuZvhfeyHg8ZAMGKtf2PcH1wS3EnoBH7In53Rspk/eXVWAO5+avxunPn081w==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-minify-terser@0.83.3: - resolution: {integrity: sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==} - engines: {node: '>=20.19.4'} + metro-minify-terser@0.84.5: + resolution: {integrity: sha512-BJoFwCEDsYnagPqarayInv2+diCDNDdLlaof/p6s9w4gh+gc9HXYM+pDvsKGKKUumpZswNF3Z/ftTMqKl/5IBg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-resolver@0.83.3: - resolution: {integrity: sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==} - engines: {node: '>=20.19.4'} + metro-minify-terser@0.84.6: + resolution: {integrity: sha512-SqhB/Kxrw0XfyW0k8mEscqX/CQopm6Fp3EpdSIPzvKfGgQp9bTMahg9PAdVsXUmIWwErbmFw1VqB+VFBOKksUw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-runtime@0.83.3: - resolution: {integrity: sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==} - engines: {node: '>=20.19.4'} + metro-resolver@0.84.5: + resolution: {integrity: sha512-VSSnepg1k6LyCwtb6eirWdAWlpKwBG8Rdtsr1mU38rMelFyWgh3/QuMSiZIZAIjwg/fsa8GhW5/FO54CAUPCEA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-source-map@0.83.3: - resolution: {integrity: sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==} - engines: {node: '>=20.19.4'} + metro-resolver@0.84.6: + resolution: {integrity: sha512-PplpGc/OCLxgKeLNsLQO/VHsYDIvkcGGr3zNT7UanHCGRqhxKv245o+naV8cRI4/pcLqyc+9j+J0AJbT3Kp5Sg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-symbolicate@0.83.3: - resolution: {integrity: sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==} - engines: {node: '>=20.19.4'} + metro-runtime@0.84.5: + resolution: {integrity: sha512-U1m2+d1Pr+JO2/iVXBB2OfXXityz7tqwIorxfrT15IEgaHvpJBq/OHiqnOWPKJbUl3JcxjcdviZZOKk85oK4Qg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-runtime@0.84.6: + resolution: {integrity: sha512-47EYO/DOai0PA3GoAyDpyXOgHNMwbl/Z9dQlQineDLHkpD42xOwMrIx1crT0+n6F+aGoEyxZmeFwfMzaIqVqKQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-source-map@0.84.5: + resolution: {integrity: sha512-2BtV5L9uPc49F13Gn5wiP6bX/EncqzqTIk2VL/0F/96Vo0YEOjluT/qktQjFODfqGFsucwnh5mPEAl/2jVEfeg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-source-map@0.84.6: + resolution: {integrity: sha512-4p/EplRbgNxhdqA6kAj1AmIq9qqYXYSAjgxuZ73rYA+p037xDDrxK8CyDKNfbn1k6aMuVt8LV0fpt7NHUTKiZw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-symbolicate@0.84.5: + resolution: {integrity: sha512-rQ40zYDAkaWBN9yvjUuAD0ZpzBMZSoKyGYXnb5JrfbKjun7fTvfoLHL3KXFYenBTYZkQtlp4cKSCv/1utxFyOw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} hasBin: true - metro-transform-plugins@0.83.3: - resolution: {integrity: sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==} - engines: {node: '>=20.19.4'} + metro-symbolicate@0.84.6: + resolution: {integrity: sha512-wJFyyF5ysbVyoYVWGL6GwVMiNnGllwDU2gF096376fl0fc8IFUlADSyNlswj31K9og2sOilXe/FdyWFPczPUYg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + + metro-transform-plugins@0.84.5: + resolution: {integrity: sha512-+InaSVGaOyt0DyRo4Y/zIdPI6CZwnbNho5LAL23tgmuGwv7fyfkF7kKfPjZcfxXBcoYdTLLFnCfCH/dHSiCqNg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-transform-plugins@0.84.6: + resolution: {integrity: sha512-pROPMFaj25Y9+3LlLqpBRz/7B/2YPnfEQz/z3juYxkh3FGQ+3c/Qh4E+khPSTdwTw0M6LitLYHMqaHMbQmuBJw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-transform-worker@0.84.5: + resolution: {integrity: sha512-ui1Z8x4s5RL36gMmKLaMMO7O9NNDHNdthEZSCDQHAau3JcAsTaFOK6I+2q4I/kW5u8hSEjJk9L45TXSVJw6g1A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro-transform-worker@0.83.3: - resolution: {integrity: sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==} - engines: {node: '>=20.19.4'} + metro-transform-worker@0.84.6: + resolution: {integrity: sha512-xLTVrOENaHR9DiuhnHS+zcle0rKsCy2CpXSIx7xD+zhJs+qVRNUyV7Z1zIt/0P/d0cSGVx846R+dIjHEudhcww==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - metro@0.83.3: - resolution: {integrity: sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==} - engines: {node: '>=20.19.4'} + metro@0.84.5: + resolution: {integrity: sha512-r1liLkyFZMVSEMNjU1CJU5pRzs3NdkxHqXS60O25c0rCIqAR+cGk7rPydw/g0WAIKVXojIBIF45yYBPagJGcgw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + + metro@0.84.6: + resolution: {integrity: sha512-ty/tx/imE5Ph2gvPU788Ckim6RAC2tyZxLz2hWRgC9U7/fGKV7qY/slADy0qHPEw9cpUgc4BfmV3oUsYwm7oeg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} hasBin: true mhchemparser@4.2.1: @@ -5844,6 +6087,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -5874,6 +6121,10 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5896,10 +6147,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@3.1.0: - resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} - engines: {node: '>= 18'} - mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} @@ -5923,12 +6170,12 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + multitars@1.0.2: + resolution: {integrity: sha512-6GwVw5eLi9sThdtlS4PKwC7yRLaf45pYhIEzKBHdKxi+YOXGKFX8acIniH+Uh/+k9mS2lQOupTccjoe5r0/1IQ==} + mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoclone@0.2.1: resolution: {integrity: sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==} @@ -5937,6 +6184,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -5956,12 +6208,13 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - nested-error-stacks@2.0.1: - resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} - nocache@3.0.4: resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} engines: {node: '>=12.0.0'} @@ -6014,9 +6267,13 @@ packages: nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} - ob1@0.83.3: - resolution: {integrity: sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==} - engines: {node: '>=20.19.4'} + ob1@0.84.5: + resolution: {integrity: sha512-aH9RkoZc7w/90HBamFxTw8ZLFr05wXS+iOnvmrgo53Ep8Pyrm5FieQSaPIVROkfFVQISeD/zo92fes26TOwe+A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + ob1@0.84.6: + resolution: {integrity: sha512-+s+6zjd0X68hfAcn92NsEPXnSYOF3O7exw/Fj4UfRTB+UL+Tdjdkzu5/4WquXlzqgvNm8FWuuW6/0Yd8S2rYxw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -6026,10 +6283,6 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} - object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -6250,10 +6503,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@3.0.1: - resolution: {integrity: sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==} - engines: {node: '>=10'} - picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} @@ -6294,8 +6543,8 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} prebuild-install@7.1.3: @@ -6373,10 +6622,6 @@ packages: pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - qrcode-terminal@0.11.0: - resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} - hasBin: true - qs@6.14.1: resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} @@ -6394,9 +6639,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - queue@6.0.2: - resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} - randexp@0.5.3: resolution: {integrity: sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==} engines: {node: '>=4'} @@ -6416,10 +6658,10 @@ packages: react-devtools-core@6.1.5: resolution: {integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==} - react-dom@19.1.0: - resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: - react: ^19.1.0 + react: ^19.2.3 react-freeze@1.0.4: resolution: {integrity: sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==} @@ -6445,6 +6687,9 @@ packages: react-is@19.1.0: resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==} + react-is@19.3.0: + resolution: {integrity: sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==} + react-native-a11y-order@0.4.0: resolution: {integrity: sha512-qtx+cvHWcCKu2UukjuKLGAwMqTFWQG3LxblR7E2QBOjLlt204kk33vZHotGW+U7jvE+aikL7t/DcNNCwj05Kiw==} engines: {node: '>= 16.0.0'} @@ -6506,8 +6751,8 @@ packages: react: '*' react-native: '*' - react-native-gesture-handler@2.28.0: - resolution: {integrity: sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==} + react-native-gesture-handler@2.32.0: + resolution: {integrity: sha512-uYIMOKlKENORq2SABE+jIjbPU+h5I/sQKcq2v16zRq848nwEp1fWRVwML4QWqijc8UcXJC25o54S8GQd4Mf2OA==} peerDependencies: react: '*' react-native: '*' @@ -6530,6 +6775,12 @@ packages: react: '*' react-native: '*' + react-native-is-edge-to-edge@1.3.1: + resolution: {integrity: sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==} + peerDependencies: + react: '*' + react-native: '*' + react-native-katex@https://codeload.github.com/RocketChat/react-native-katex/tar.gz/37e579804fe238732d8a4b06dec073610ab062b1: resolution: {gitHosted: true, tarball: https://codeload.github.com/RocketChat/react-native-katex/tar.gz/37e579804fe238732d8a4b06dec073610ab062b1} version: 1.3.0 @@ -6579,7 +6830,7 @@ packages: peerDependencies: react: '*' react-native: '*' - react-native-nitro-modules: '*' + react-native-nitro-modules: ^0.37.1 react-native-modal@13.0.1: resolution: {integrity: sha512-UB+mjmUtf+miaG/sDhOikRfBOv0gJdBU2ZE1HtFWp6UixW9jCk/bhGdHUgmZljbPpp0RaO/6YiMmQSSK3kkMaw==} @@ -6608,22 +6859,21 @@ packages: react-native-popover-view@5.1.7: resolution: {integrity: sha512-Xa+zKYp9x62UNezT8o9SnqQaBQEHdGwGHZnFnA2bsXLneVRvvlBopNwMNj+p7N65qnPf3mmzWj+57ThtM1qFuw==} - react-native-reanimated@4.1.3: - resolution: {integrity: sha512-GP8wsi1u3nqvC1fMab/m8gfFwFyldawElCcUSBJQgfrXeLmsPPUOpDw44lbLeCpcwUuLa05WTVePdTEwCLTUZg==} + react-native-reanimated@4.5.1: + resolution: {integrity: sha512-RnMvtDuR+68ig864gAvZCOdZehqhC5rFmMo0kn+ARfgVSTvFeF6IFLBVgMPUu0KwihaapEyW24WRi6nEyy1kSA==} peerDependencies: - '@babel/core': ^7.0.0-0 react: '*' - react-native: '*' - react-native-worklets: '>=0.5.0' + react-native: 0.83 - 0.86 + react-native-worklets: 0.10.x - react-native-safe-area-context@5.6.2: - resolution: {integrity: sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==} + react-native-safe-area-context@5.7.0: + resolution: {integrity: sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==} peerDependencies: react: '*' react-native: '*' - react-native-screens@4.17.1: - resolution: {integrity: sha512-hGArs1kzsokvwxq98vluGlprUw3Q95zEjvZ3U2q28FmvLy25e6jxMclEkgxNtJ0GVJ2gWcFRTXON0EIVvUEd+A==} + react-native-screens@4.26.2: + resolution: {integrity: sha512-2XnWsZToKj76trGtEZzx5ELD/qOICFEprEeUntImmitQFVUkea27fiWdUSITArI356Y1qynpXZINW+Unbhky/A==} peerDependencies: react: '*' react-native: '*' @@ -6636,8 +6886,8 @@ packages: react-native: '>=0.50.1' react-native-linear-gradient: ^2.5.6 - react-native-svg@15.12.1: - resolution: {integrity: sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==} + react-native-svg@15.15.4: + resolution: {integrity: sha512-boT/vIRgj6zZKBpfTPJJiYWMbZE9duBMOwPK6kCSTgxsS947IFMOq9OgIFkpWZTB7t229H24pDRkh3W9ZK/J1A==} peerDependencies: react: '*' react-native: '*' @@ -6658,21 +6908,25 @@ packages: react: '*' react-native: '*' - react-native-worklets@0.6.1: - resolution: {integrity: sha512-URca8l7c7Uog7gv4mcg9KILdJlnbvwdS5yfXQYf5TDkD2W1VY1sduEKrD+sA3lUPXH/TG1vmXAvNxCNwPMYgGg==} + react-native-worklets@0.10.1: + resolution: {integrity: sha512-62mRM19bDpfpdI8HLkEErcdOsrAPDtE9lA/sw+5lLRpzBHNhxaoj9QyY2KjXqUmirelxkX4zuPGTC3VdA0feJA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': '*' + '@react-native/metro-config': '*' react: '*' - react-native: '*' + react-native: 0.83 - 0.86 - react-native@0.81.5: - resolution: {integrity: sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==} - engines: {node: '>= 20.19.4'} + react-native@0.86.3: + resolution: {integrity: sha512-JR5s3bM9ezud+Mw24GlNXNfthqPIKwrQgPPJcam+L97t2sKjjEavhCzBn+fyqZZRcM5+XlhYxpTxVkK7e1n38Q==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} hasBin: true peerDependencies: - '@types/react': ^19.1.0 - react: ^19.1.0 + '@react-native/jest-preset': 0.86.3 + '@types/react': ^19.1.1 + react: ^19.2.3 peerDependenciesMeta: + '@react-native/jest-preset': + optional: true '@types/react': optional: true @@ -6701,13 +6955,13 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react-test-renderer@19.1.0: - resolution: {integrity: sha512-jXkSl3CpvPYEF+p/eGDLB4sPoDX8pKkYvRl9+rR8HxLY0X04vW7hCm1/0zHoUSjPZ3bDa+wXWNTDVIw/R8aDVw==} + react-test-renderer@19.2.3: + resolution: {integrity: sha512-TMR1LnSFiWZMJkCgNf5ATSvAheTT2NvKIwiVwdBPHxjBI7n/JbWd4gaZ16DVd9foAXdvDz+sB5yxZTwMjPRxpw==} peerDependencies: - react: ^19.1.0 + react: ^19.2.3 - react@19.1.0: - resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} read-pkg-up@7.0.1: @@ -6798,10 +7052,6 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - requireg@0.2.2: - resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==} - engines: {node: '>= 4.0.0'} - requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -6823,10 +7073,6 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve-global@1.0.0: - resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} - engines: {node: '>=8'} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -6842,9 +7088,6 @@ packages: engines: {node: '>= 0.4'} hasBin: true - resolve@1.7.1: - resolution: {integrity: sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==} - restore-cursor@2.0.0: resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} engines: {node: '>=4'} @@ -6894,6 +7137,11 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sandbox-cli-detector@0.2.0: + resolution: {integrity: sha512-4lyHX0ZU0AZKwjgZ1InxZAa3PNpyEb8rOQ+Zss1ReYmhNzW0Q+h1zE5nvniXN0HaAWZaZE1zgVNEirb0R7LmNg==} + engines: {node: '>=18.18'} + hasBin: true + sax@1.4.3: resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} @@ -6901,8 +7149,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} schema-utils@4.3.3: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} @@ -6921,11 +7169,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -7235,11 +7478,6 @@ packages: structured-headers@0.4.1: resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - superstruct@2.0.2: resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} engines: {node: '>=14.0.0'} @@ -7271,6 +7509,10 @@ packages: resolution: {integrity: sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==} engines: {node: '>=8.0.0'} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + tapable@2.3.3: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} @@ -7288,15 +7530,6 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - tar@7.5.2: - resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} - engines: {node: '>=18'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - temp-dir@2.0.0: - resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} - engines: {node: '>=8'} - terminal-link@2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} @@ -7332,13 +7565,6 @@ packages: text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - throat@5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} @@ -7392,6 +7618,9 @@ packages: toposort@2.0.2: resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + toqr@0.1.1: + resolution: {integrity: sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -7412,9 +7641,6 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -7456,6 +7682,10 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} + type-fest@5.9.0: + resolution: {integrity: sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==} + engines: {node: '>=20'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -7503,7 +7733,7 @@ packages: resolution: {integrity: sha512-eLIKd0KHZtSvbsA+FYwX+Y0ZBt0BwVGz3GgODQX+6GfGL4DOzKW02LEx62oUZg6vCQX1BL5xyiPXAIdW+Hc51g==} hasBin: true peerDependencies: - typescript: '>=4.8.0 <5.10.0' + typescript: 7.0.2 typical@4.0.0: resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} @@ -7524,10 +7754,6 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@6.22.0: - resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==} - engines: {node: '>=18.17'} - unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -7544,10 +7770,6 @@ packages: resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} engines: {node: '>=4'} - unique-string@2.0.0: - resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} - engines: {node: '>=8'} - universal-websocket-client@1.0.3: resolution: {integrity: sha512-ZQF2hSasp4IM7Y3E+IAkA40c6kclsfhpligALvnuecEnU1dTJQZ5XlgXWLt3C8+uizDw5x48kCMBDPnLg/EVzA==} @@ -7699,6 +7921,9 @@ packages: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} engines: {node: '>=12'} + whatwg-url-minimum@0.1.2: + resolution: {integrity: sha512-XPEm0XFQWNVG292lII1PrRRJl3sItrs7CettZ4ncYxuDVpLyy+NwlGyut2hXI0JswcJUxeCH+CyOJK0ZzAXD6A==} + whatwg-url-without-unicode@8.0.0-3: resolution: {integrity: sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==} engines: {node: '>=10'} @@ -7734,9 +7959,6 @@ packages: wicked-good-xpath@1.3.0: resolution: {integrity: sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==} - wonka@6.3.5: - resolution: {integrity: sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==} - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -7846,10 +8068,6 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -7887,6 +8105,9 @@ packages: resolution: {integrity: sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==} engines: {node: '>=10'} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zustand@5.0.12: resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==} engines: {node: '>=12.20.0'} @@ -7905,9 +8126,12 @@ packages: use-sync-external-store: optional: true -snapshots: + zxing-wasm@3.1.3: + resolution: {integrity: sha512-3lC9BJk4fR5ZJxcGjb0hVnDFOW7KpLXHIebiBmVd4FDRQZVeObztoTKxRUPxlWwSgxrMRONK0u50hBD1aTYEKg==} + peerDependencies: + '@types/emscripten': '>=1.39.6' - '@0no-co/graphql.web@1.2.0': {} +snapshots: '@adobe/css-tools@4.4.4': {} @@ -7926,8 +8150,16 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.29.7': {} + '@babel/core@7.25.9': dependencies: '@ampproject/remapping': 2.3.0 @@ -7948,6 +8180,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.29.1': dependencies: '@babel/parser': 7.29.2 @@ -7956,10 +8208,22 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.29.0 + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.8 + '@babel/helper-compilation-targets@7.28.6': dependencies: '@babel/compat-data': 7.29.0 @@ -7968,29 +8232,37 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.25.9)': + '@babel/helper-compilation-targets@7.29.7': dependencies: - '@babel/core': 7.25.9 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.25.9) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.7) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/traverse': 7.29.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.25.9)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.25.9)': + '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 @@ -8001,6 +8273,8 @@ snapshots: '@babel/helper-globals@7.28.0': {} + '@babel/helper-globals@7.29.7': {} + '@babel/helper-member-expression-to-functions@7.28.5': dependencies: '@babel/traverse': 7.29.0 @@ -8015,6 +8289,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.28.6(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 @@ -8024,24 +8305,44 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.27.1': dependencies: '@babel/types': 7.29.0 '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.25.9)': + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.28.6(@babel/core@7.25.9)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/traverse': 7.29.0 @@ -8057,10 +8358,16 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-option@7.29.7': {} + '@babel/helper-wrap-function@7.28.6': dependencies: '@babel/template': 7.28.6 @@ -8074,6 +8381,11 @@ snapshots: '@babel/template': 7.28.6 '@babel/types': 7.29.0 + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + '@babel/highlight@7.25.9': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -8085,102 +8397,126 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.25.9)': + '@babel/parser@7.29.8': dependencies: - '@babel/core': 7.25.9 + '@babel/types': 7.29.8 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.25.9) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-decorators@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-proposal-decorators@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.25.9) + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.9)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.25.9)': + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.25.9)': @@ -8188,226 +8524,296 @@ snapshots: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.25.9)': dependencies: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.25.9)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.25.9) + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.25.9)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.25.9) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.7) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.25.9) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.25.9) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.7) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/template': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.25.9)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.25.9) + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -8420,328 +8826,335 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.25.9)': + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.25.9) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.25.9) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.25.9) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.25.9)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.25.9) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.25.9)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.25.9) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.7) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.25.9)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.25.9)': + '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.25.9) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.25.9) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.25.9) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.29.7) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-spread@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.25.9)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.25.9) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.25.9) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.25.9)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.25.9)': + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.25.9(@babel/core@7.25.9)': + '@babel/preset-env@7.25.9(@babel/core@7.29.7)': dependencies: '@babel/compat-data': 7.29.0 - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.9) - '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.25.9) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.25.9) - '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.25.9) - '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.25.9) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.25.9) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.25.9) - '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.25.9) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.25.9) - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.25.9) - babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.9) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.25.9) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.29.7) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.29.7) core-js-compat: 3.48.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.25.9)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.28.6 '@babel/types': 7.29.0 esutils: 2.0.3 - '@babel/preset-react@7.28.5(@babel/core@7.25.9)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.25.9) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.25.9) - transitivePeerDependencies: - - supports-color - - '@babel/preset-typescript@7.28.5(@babel/core@7.25.9)': - dependencies: - '@babel/core': 7.25.9 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.25.9) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.7) transitivePeerDependencies: - supports-color @@ -8765,6 +9178,12 @@ snapshots: '@babel/parser': 7.29.2 '@babel/types': 7.29.0 + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@babel/traverse@7.29.0': dependencies: '@babel/code-frame': 7.29.0 @@ -8777,11 +9196,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@bcoe/v8-coverage@0.2.3': {} '@bugsnag/cli@3.5.1': @@ -8986,32 +9422,34 @@ snapshots: '@eslint/js@8.57.1': {} - '@expo/cli@54.0.20(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))': + '@expo/cli@57.0.25(@expo/dom-webview@57.0.1)(expo-constants@57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo@57.0.23)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2)': dependencies: - '@0no-co/graphql.web': 1.2.0 - '@expo/code-signing-certificates': 0.0.5 - '@expo/config': 12.0.13 - '@expo/config-plugins': 54.0.4 + '@expo/code-signing-certificates': 0.0.6 + '@expo/config': 57.0.9(typescript@7.0.2) + '@expo/config-plugins': 57.0.9(typescript@7.0.2) '@expo/devcert': 1.2.1 - '@expo/env': 2.0.8 - '@expo/image-utils': 0.8.8 - '@expo/json-file': 10.0.8 - '@expo/metro': 54.2.0 - '@expo/metro-config': 54.0.12(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) - '@expo/osascript': 2.3.8 - '@expo/package-manager': 1.9.9 - '@expo/plist': 0.4.8 - '@expo/prebuild-config': 54.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) - '@expo/schema-utils': 0.1.8 - '@expo/spawn-async': 1.7.2 - '@expo/ws-tunnel': 1.0.6 - '@expo/xcpretty': 4.3.2 - '@react-native/dev-middleware': 0.81.5 - '@urql/core': 5.2.0 - '@urql/exchange-retry': 1.3.2(@urql/core@5.2.0) + '@expo/env': 2.4.3 + '@expo/image-utils': 0.11.5(typescript@7.0.2) + '@expo/inline-modules': 0.1.7(typescript@7.0.2) + '@expo/json-file': 11.0.1 + '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@expo/metro': 56.0.2 + '@expo/metro-config': 57.0.12(expo@57.0.23)(typescript@7.0.2) + '@expo/metro-file-map': 57.0.3 + '@expo/osascript': 2.7.1 + '@expo/package-manager': 1.13.1 + '@expo/plist': 0.8.1 + '@expo/prebuild-config': 57.0.16(typescript@7.0.2) + '@expo/require-utils': 57.0.5(typescript@7.0.2) + '@expo/router-server': 57.0.10(expo-constants@57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo-server@57.0.3)(expo@57.0.23)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@expo/schema-utils': 57.0.2 + '@expo/spawn-async': 1.8.0 + '@expo/ws-tunnel': 2.0.0(ws@8.18.3) + '@expo/xcpretty': 4.4.5 + '@react-native/dev-middleware': 0.86.3 accepts: 1.3.8 + agent-cli-detector: 0.1.7 arg: 5.0.2 - better-opn: 3.0.2 bplist-creator: 0.1.0 bplist-parser: 0.3.2 chalk: 4.1.2 @@ -9019,51 +9457,51 @@ snapshots: compression: 1.8.1 connect: 3.7.0 debug: 4.4.3 - env-editor: 0.4.2 - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-server: 1.0.5 - freeport-async: 2.0.0 + dnssd-advertise: 1.1.6 + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo-server: 57.0.3 + fetch-nodeshim: 0.4.10 getenv: 2.0.0 glob: 13.0.0 - lan-network: 0.1.7 - minimatch: 9.0.5 + lan-network: 0.2.1 + multitars: 1.0.2 node-forge: 1.3.3 npm-package-arg: 11.0.3 ora: 3.4.0 - picomatch: 3.0.1 - pretty-bytes: 5.6.0 + picomatch: 4.0.5 pretty-format: 29.7.0 progress: 2.0.3 prompts: 2.4.2 - qrcode-terminal: 0.11.0 - require-from-string: 2.0.2 - requireg: 0.2.2 - resolve: 1.22.11 resolve-from: 5.0.0 - resolve.exports: 2.0.3 + sandbox-cli-detector: 0.2.0 semver: 7.7.4 send: 0.19.2 slugify: 1.6.6 - source-map-support: 0.5.21 stacktrace-parser: 0.1.11 structured-headers: 0.4.1 - tar: 7.5.2 terminal-link: 2.1.1 - undici: 6.22.0 + toqr: 0.1.1 wrap-ansi: 7.0.0 ws: 8.18.3 + zod: 3.25.76 optionalDependencies: - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) transitivePeerDependencies: + - '@expo/dom-webview' + - '@expo/metro-runtime' - bufferutil - - graphql + - expo-constants + - expo-font + - react + - react-dom + - react-server-dom-webpack - supports-color + - typescript - utf-8-validate - '@expo/code-signing-certificates@0.0.5': + '@expo/code-signing-certificates@0.0.6': dependencies: node-forge: 1.3.3 - nullthrows: 1.1.1 '@expo/config-plugins@10.1.2': dependencies: @@ -9084,46 +9522,44 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/config-plugins@54.0.4': + '@expo/config-plugins@57.0.9(typescript@7.0.2)': dependencies: - '@expo/config-types': 54.0.10 - '@expo/json-file': 10.0.8 - '@expo/plist': 0.4.8 + '@expo/config-types': 57.0.2 + '@expo/json-file': 11.0.1 + '@expo/plist': 0.8.1 + '@expo/require-utils': 57.0.5(typescript@7.0.2) '@expo/sdk-runtime-versions': 1.0.0 chalk: 4.1.2 debug: 4.4.3 getenv: 2.0.0 glob: 13.0.0 - resolve-from: 5.0.0 semver: 7.7.4 - slash: 3.0.0 slugify: 1.6.6 xcode: 3.0.1 xml2js: 0.6.0 transitivePeerDependencies: - supports-color + - typescript '@expo/config-types@53.0.5': {} - '@expo/config-types@54.0.10': {} + '@expo/config-types@57.0.2': {} - '@expo/config@12.0.13': + '@expo/config@57.0.9(typescript@7.0.2)': dependencies: - '@babel/code-frame': 7.10.4 - '@expo/config-plugins': 54.0.4 - '@expo/config-types': 54.0.10 - '@expo/json-file': 10.0.8 + '@expo/config-plugins': 57.0.9(typescript@7.0.2) + '@expo/config-types': 57.0.2 + '@expo/json-file': 11.0.1 + '@expo/require-utils': 57.0.5(typescript@7.0.2) deepmerge: 4.3.1 getenv: 2.0.0 glob: 13.0.0 - require-from-string: 2.0.2 - resolve-from: 5.0.0 resolve-workspace-root: 2.0.0 semver: 7.7.4 slugify: 1.6.6 - sucrase: 3.35.1 transitivePeerDependencies: - supports-color + - typescript '@expo/devcert@1.2.1': dependencies: @@ -9132,55 +9568,68 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/devtools@0.1.8(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@expo/devtools@57.0.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: chalk: 4.1.2 optionalDependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@expo/env@2.0.8': + '@expo/dom-webview@57.0.1(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': + dependencies: + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + + '@expo/env@2.4.3': dependencies: chalk: 4.1.2 debug: 4.4.3 - dotenv: 16.4.7 - dotenv-expand: 11.0.7 getenv: 2.0.0 transitivePeerDependencies: - supports-color - '@expo/fingerprint@0.15.4': + '@expo/expo-modules-macros-plugin@0.6.1': {} + + '@expo/fingerprint@0.20.13': dependencies: - '@expo/spawn-async': 1.7.2 + '@expo/env': 2.4.3 + '@expo/spawn-async': 1.8.0 arg: 5.0.2 chalk: 4.1.2 debug: 4.4.3 getenv: 2.0.0 glob: 13.0.0 ignore: 5.3.2 - minimatch: 9.0.5 - p-limit: 3.1.0 + minimatch: 10.2.6 resolve-from: 5.0.0 semver: 7.7.4 transitivePeerDependencies: - supports-color - '@expo/image-utils@0.8.8': + '@expo/image-utils@0.11.5(typescript@7.0.2)': dependencies: - '@expo/spawn-async': 1.7.2 + '@expo/require-utils': 57.0.5(typescript@7.0.2) + '@expo/spawn-async': 1.8.0 chalk: 4.1.2 getenv: 2.0.0 jimp-compact: 0.16.1 parse-png: 2.1.0 - resolve-from: 5.0.0 - resolve-global: 1.0.0 semver: 7.7.4 - temp-dir: 2.0.0 - unique-string: 2.0.0 + transitivePeerDependencies: + - supports-color + - typescript - '@expo/json-file@10.0.8': + '@expo/inline-modules@0.1.7(typescript@7.0.2)': dependencies: - '@babel/code-frame': 7.10.4 + '@expo/config-plugins': 57.0.9(typescript@7.0.2) + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/json-file@11.0.1': + dependencies: + '@babel/code-frame': 7.29.0 json5: 2.2.3 '@expo/json-file@9.1.5': @@ -9188,66 +9637,96 @@ snapshots: '@babel/code-frame': 7.10.4 json5: 2.2.3 - '@expo/metro-config@54.0.12(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))': + '@expo/local-build-cache-provider@57.0.8(typescript@7.0.2)': + dependencies: + '@expo/config': 57.0.9(typescript@7.0.2) + chalk: 4.1.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/log-box@57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': + dependencies: + '@expo/dom-webview': 57.0.1(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + anser: 1.4.10 + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + stacktrace-parser: 0.1.11 + + '@expo/metro-config@57.0.12(expo@57.0.23)(typescript@7.0.2)': dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.25.9 '@babel/generator': 7.29.1 - '@expo/config': 12.0.13 - '@expo/env': 2.0.8 - '@expo/json-file': 10.0.8 - '@expo/metro': 54.2.0 - '@expo/spawn-async': 1.7.2 + '@expo/config': 57.0.9(typescript@7.0.2) + '@expo/env': 2.4.3 + '@expo/json-file': 11.0.1 + '@expo/metro': 56.0.2 + '@expo/require-utils': 57.0.5(typescript@7.0.2) + '@expo/spawn-async': 1.8.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 browserslist: 4.28.1 chalk: 4.1.2 debug: 4.4.3 - dotenv: 16.4.7 - dotenv-expand: 11.0.7 getenv: 2.0.0 glob: 13.0.0 - hermes-parser: 0.29.1 + hermes-parser: 0.36.1 jsc-safe-url: 0.2.4 lightningcss: 1.30.2 - minimatch: 9.0.5 - postcss: 8.4.49 + picomatch: 4.0.5 + postcss: 8.5.28 resolve-from: 5.0.0 optionalDependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) transitivePeerDependencies: - bufferutil - supports-color + - typescript - utf-8-validate - '@expo/metro@54.2.0': - dependencies: - metro: 0.83.3 - metro-babel-transformer: 0.83.3 - metro-cache: 0.83.3 - metro-cache-key: 0.83.3 - metro-config: 0.83.3 - metro-core: 0.83.3 - metro-file-map: 0.83.3 - metro-minify-terser: 0.83.3 - metro-resolver: 0.83.3 - metro-runtime: 0.83.3 - metro-source-map: 0.83.3 - metro-symbolicate: 0.83.3 - metro-transform-plugins: 0.83.3 - metro-transform-worker: 0.83.3 + '@expo/metro-file-map@57.0.3': + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + '@expo/metro@56.0.2': + dependencies: + metro: 0.84.5 + metro-babel-transformer: 0.84.5 + metro-cache: 0.84.5 + metro-cache-key: 0.84.5 + metro-config: 0.84.5 + metro-core: 0.84.5 + metro-file-map: 0.84.5 + metro-minify-terser: 0.84.5 + metro-resolver: 0.84.5 + metro-runtime: 0.84.5 + metro-source-map: 0.84.5 + metro-symbolicate: 0.84.5 + metro-transform-plugins: 0.84.5 + metro-transform-worker: 0.84.5 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@expo/osascript@2.3.8': + '@expo/osascript@2.7.1': dependencies: - '@expo/spawn-async': 1.7.2 - exec-async: 2.2.0 + '@expo/spawn-async': 1.8.0 - '@expo/package-manager@1.9.9': + '@expo/package-manager@1.13.1': dependencies: - '@expo/json-file': 10.0.8 - '@expo/spawn-async': 1.7.2 + '@expo/json-file': 11.0.1 + '@expo/spawn-async': 1.8.0 chalk: 4.1.2 npm-package-arg: 11.0.3 ora: 3.4.0 @@ -9259,51 +9738,75 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 - '@expo/plist@0.4.8': + '@expo/plist@0.8.1': dependencies: '@xmldom/xmldom': 0.8.11 base64-js: 1.5.1 xmlbuilder: 15.1.1 - '@expo/prebuild-config@54.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))': + '@expo/prebuild-config@57.0.16(typescript@7.0.2)': dependencies: - '@expo/config': 12.0.13 - '@expo/config-plugins': 54.0.4 - '@expo/config-types': 54.0.10 - '@expo/image-utils': 0.8.8 - '@expo/json-file': 10.0.8 - '@react-native/normalize-colors': 0.81.5 + '@expo/config': 57.0.9(typescript@7.0.2) + '@expo/config-plugins': 57.0.9(typescript@7.0.2) + '@expo/config-types': 57.0.2 + '@expo/image-utils': 0.11.5(typescript@7.0.2) + '@expo/json-file': 11.0.1 + '@react-native/normalize-colors': 0.86.3 debug: 4.4.3 - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-modules-autolinking: 57.0.13(typescript@7.0.2) resolve-from: 5.0.0 semver: 7.7.4 - xml2js: 0.6.0 transitivePeerDependencies: - supports-color + - typescript - '@expo/schema-utils@0.1.8': {} + '@expo/require-utils@57.0.5(typescript@7.0.2)': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/core': 7.25.9 + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.25.9) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - supports-color + + '@expo/router-server@57.0.10(expo-constants@57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo-server@57.0.3)(expo@57.0.23)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + debug: 4.4.3 + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo-constants: 57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) + expo-font: 57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + expo-server: 57.0.3 + react: 19.2.3 + optionalDependencies: + react-dom: 19.2.3(react@19.2.3) + transitivePeerDependencies: + - supports-color + + '@expo/schema-utils@57.0.2': {} '@expo/sdk-runtime-versions@1.0.0': {} - '@expo/spawn-async@1.7.2': + '@expo/spawn-async@1.8.0': dependencies: cross-spawn: 7.0.6 '@expo/sudo-prompt@9.3.2': {} - '@expo/vector-icons@15.0.3(expo-font@14.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@expo/vector-icons@15.0.3(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - expo-font: 14.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo-font: 57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@expo/ws-tunnel@1.0.6': {} + '@expo/ws-tunnel@2.0.0(ws@8.18.3)': + dependencies: + ws: 8.18.3 - '@expo/xcpretty@4.3.2': + '@expo/xcpretty@4.4.5': dependencies: - '@babel/code-frame': 7.10.4 + '@babel/code-frame': 7.29.0 chalk: 4.1.2 - find-up: 5.0.0 js-yaml: 4.1.1 '@firebase/analytics-compat@0.2.17(@firebase/app-compat@0.2.50)(@firebase/app@0.11.1)': @@ -9370,10 +9873,10 @@ snapshots: idb: 7.1.1 tslib: 2.8.1 - '@firebase/auth-compat@0.5.18(@firebase/app-compat@0.2.50)(@firebase/app-types@0.9.3)(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)))': + '@firebase/auth-compat@0.5.18(@firebase/app-compat@0.2.50)(@firebase/app-types@0.9.3)(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))': dependencies: '@firebase/app-compat': 0.2.50 - '@firebase/auth': 1.9.0(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))) + '@firebase/auth': 1.9.0(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))) '@firebase/auth-types': 0.13.0(@firebase/app-types@0.9.3)(@firebase/util@1.10.3) '@firebase/component': 0.6.12 '@firebase/util': 1.10.3 @@ -9390,7 +9893,7 @@ snapshots: '@firebase/app-types': 0.9.3 '@firebase/util': 1.10.3 - '@firebase/auth@1.9.0(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)))': + '@firebase/auth@1.9.0(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))': dependencies: '@firebase/app': 0.11.1 '@firebase/component': 0.6.12 @@ -9398,7 +9901,7 @@ snapshots: '@firebase/util': 1.10.3 tslib: 2.8.1 optionalDependencies: - '@react-native-async-storage/async-storage': 2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + '@react-native-async-storage/async-storage': 2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) '@firebase/component@0.6.12': dependencies: @@ -9626,22 +10129,22 @@ snapshots: '@firebase/webchannel-wrapper@1.0.3': {} - '@gorhom/bottom-sheet@5.2.8(@types/react@19.1.17)(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@gorhom/bottom-sheet@5.2.8(9928f68f03a282f377b2614cb23deb76)': dependencies: - '@gorhom/portal': 1.0.14(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@gorhom/portal': 1.0.14(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) invariant: 2.2.4 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.18 - '@gorhom/portal@1.0.14(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@gorhom/portal@1.0.14(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: nanoid: 3.3.11 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) '@grpc/grpc-js@1.9.15': dependencies: @@ -9661,9 +10164,9 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 - '@hookform/resolvers@2.9.11(react-hook-form@7.34.2(react@19.1.0))': + '@hookform/resolvers@2.9.11(react-hook-form@7.34.2(react@19.2.3))': dependencies: - react-hook-form: 7.34.2(react@19.1.0) + react-hook-form: 7.34.2(react@19.2.3) '@humanwhocodes/config-array@0.13.0': dependencies: @@ -9677,8 +10180,6 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} - '@ide/backoff@1.0.0': {} - '@inquirer/external-editor@1.0.3(@types/node@25.0.3)': dependencies: chardet: 2.1.1 @@ -9701,10 +10202,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.2 - '@isaacs/ttlcache@1.4.1': {} '@istanbuljs/load-nyc-config@1.1.0': @@ -9896,6 +10393,11 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/source-map@0.3.11': @@ -9910,21 +10412,21 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@lodev09/react-native-true-sheet@3.7.3(261280a80c394a7dcf8d9609d62c37ca)': + '@lodev09/react-native-true-sheet@3.7.3(b217ab5bd6345e9949a4a395c898a850)': dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - '@gorhom/bottom-sheet': 5.2.8(@types/react@19.1.17)(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@react-navigation/native': 7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-worklets: 0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@gorhom/bottom-sheet': 5.2.8(9928f68f03a282f377b2614cb23deb76) + '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-worklets: 0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - '@magrinj/expo-quick-look@0.4.0(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@magrinj/expo-quick-look@0.4.0(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) '@napi-rs/wasm-runtime@0.2.12': dependencies: @@ -10103,19 +10605,19 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))': + '@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))': dependencies: merge-options: 3.0.4 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@react-native-camera-roll/camera-roll@7.10.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))': + '@react-native-camera-roll/camera-roll@7.10.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))': dependencies: - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@react-native-clipboard/clipboard@1.16.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-native-clipboard/clipboard@1.16.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) '@react-native-community/cli-clean@20.0.0': dependencies: @@ -10273,134 +10775,181 @@ snapshots: - typescript - utf-8-validate - '@react-native-community/datetimepicker@8.4.4(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-native-community/datetimepicker@9.1.0(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: invariant: 2.2.4 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) - '@react-native-community/hooks@100.1.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-native-community/hooks@100.1.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@react-native-community/netinfo@11.4.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))': + '@react-native-community/netinfo@12.0.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@react-native-community/slider@5.0.1': {} + '@react-native-community/slider@5.2.0': {} - '@react-native-cookies/cookies@6.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))': + '@react-native-cookies/cookies@6.2.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))': dependencies: invariant: 2.2.4 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@react-native-firebase/analytics@21.14.0(@react-native-firebase/app@21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)))(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))': + '@react-native-firebase/analytics@21.14.0(@react-native-firebase/app@21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))': dependencies: - '@react-native-firebase/app': 21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)))(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-native-firebase/app': 21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) superstruct: 2.0.2 - '@react-native-firebase/app@21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)))(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-native-firebase/app@21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - firebase: 11.3.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + firebase: 11.3.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) transitivePeerDependencies: - '@react-native-async-storage/async-storage' - '@react-native-firebase/crashlytics@21.14.0(7f31d425d6e3bb1976dd842bb1e41e49)': + '@react-native-firebase/crashlytics@21.14.0(@react-native-firebase/app@21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo@57.0.23)': dependencies: - '@react-native-firebase/app': 21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)))(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-native-firebase/app': 21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) stacktrace-js: 2.0.2 optionalDependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) - '@react-native-masked-view/masked-view@0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@react-native-picker/picker@2.11.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-native-picker/picker@2.11.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@react-native/assets-registry@0.81.5': {} + '@react-native/assets-registry@0.86.3': {} - '@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.25.9)': + '@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.29.7)': dependencies: '@babel/traverse': 7.29.0 - '@react-native/codegen': 0.81.5(@babel/core@7.25.9) + '@react-native/codegen': 0.81.5(@babel/core@7.29.7) transitivePeerDependencies: - '@babel/core' - supports-color - '@react-native/babel-preset@0.81.5(@babel/core@7.25.9)': + '@react-native/babel-plugin-codegen@0.86.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.25.9) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.9) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.9) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.25.9) - '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.25.9) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.25.9) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.25.9) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.25.9) - '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.25.9) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.25.9) + '@babel/traverse': 7.29.8 + '@react-native/codegen': 0.86.3(@babel/core@7.29.7) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@react-native/babel-preset@0.81.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.7) '@babel/template': 7.28.6 - '@react-native/babel-plugin-codegen': 0.81.5(@babel/core@7.25.9) + '@react-native/babel-plugin-codegen': 0.81.5(@babel/core@7.29.7) babel-plugin-syntax-hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.25.9) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) + react-refresh: 0.14.2 + transitivePeerDependencies: + - supports-color + + '@react-native/babel-preset@0.86.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.7) + '@react-native/babel-plugin-codegen': 0.86.3(@babel/core@7.29.7) + babel-plugin-syntax-hermes-parser: 0.36.0 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) react-refresh: 0.14.2 transitivePeerDependencies: - supports-color - '@react-native/codegen@0.80.2(@babel/core@7.25.9)': + '@react-native/codegen@0.80.2(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 glob: 7.2.3 hermes-parser: 0.28.1 invariant: 2.2.4 nullthrows: 1.1.1 yargs: 17.7.2 - '@react-native/codegen@0.81.5(@babel/core@7.25.9)': + '@react-native/codegen@0.81.5(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 '@babel/parser': 7.29.2 glob: 7.2.3 hermes-parser: 0.29.1 @@ -10408,62 +10957,93 @@ snapshots: nullthrows: 1.1.1 yargs: 17.7.2 - '@react-native/community-cli-plugin@0.81.5(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))': + '@react-native/codegen@0.86.3(@babel/core@7.29.7)': dependencies: - '@react-native/dev-middleware': 0.81.5 + '@babel/core': 7.29.7 + '@babel/parser': 7.29.2 + hermes-parser: 0.36.0 + invariant: 2.2.4 + nullthrows: 1.1.1 + tinyglobby: 0.2.15 + yargs: 17.7.2 + + '@react-native/community-cli-plugin@0.86.3(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))': + dependencies: + '@react-native/dev-middleware': 0.86.3 debug: 4.4.3 invariant: 2.2.4 - metro: 0.83.3 - metro-config: 0.83.3 - metro-core: 0.83.3 + metro: 0.84.6 + metro-config: 0.84.6 + metro-core: 0.84.6 semver: 7.5.2 optionalDependencies: '@react-native-community/cli': 20.0.0(typescript@7.0.2) - '@react-native/metro-config': 0.81.5(@babel/core@7.25.9) + '@react-native/metro-config': 0.86.3(@babel/core@7.29.7) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@react-native/debugger-frontend@0.81.5': {} + '@react-native/debugger-frontend@0.86.3': {} - '@react-native/dev-middleware@0.81.5': + '@react-native/debugger-shell@0.86.3': + dependencies: + cross-spawn: 7.0.6 + debug: 4.4.3 + fb-dotslash: 0.5.8 + transitivePeerDependencies: + - supports-color + + '@react-native/dev-middleware@0.86.3': dependencies: '@isaacs/ttlcache': 1.4.1 - '@react-native/debugger-frontend': 0.81.5 + '@react-native/debugger-frontend': 0.86.3 + '@react-native/debugger-shell': 0.86.3 chrome-launcher: 0.15.2 - chromium-edge-launcher: 0.2.0 + chromium-edge-launcher: 0.3.0 connect: 3.7.0 debug: 4.4.3 invariant: 2.2.4 nullthrows: 1.1.1 open: 7.4.2 serve-static: 1.16.3 - ws: 6.2.3 + ws: 7.5.10 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@react-native/gradle-plugin@0.81.5': {} + '@react-native/gradle-plugin@0.86.3': {} - '@react-native/js-polyfills@0.81.5': {} + '@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3)': + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@react-native/js-polyfills': 0.86.3 + babel-jest: 29.7.0(@babel/core@7.29.7) + jest-environment-node: 29.7.0 + react: 19.2.3 + regenerator-runtime: 0.13.11 + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@react-native/js-polyfills@0.86.3': {} - '@react-native/metro-babel-transformer@0.81.5(@babel/core@7.25.9)': + '@react-native/metro-babel-transformer@0.86.3(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.25.9 - '@react-native/babel-preset': 0.81.5(@babel/core@7.25.9) - hermes-parser: 0.29.1 + '@babel/core': 7.29.7 + '@react-native/babel-preset': 0.86.3(@babel/core@7.29.7) + hermes-parser: 0.36.0 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - '@react-native/metro-config@0.81.5(@babel/core@7.25.9)': + '@react-native/metro-config@0.86.3(@babel/core@7.29.7)': dependencies: - '@react-native/js-polyfills': 0.81.5 - '@react-native/metro-babel-transformer': 0.81.5(@babel/core@7.25.9) - metro-config: 0.83.3 - metro-runtime: 0.83.3 + '@react-native/js-polyfills': 0.86.3 + '@react-native/metro-babel-transformer': 0.86.3(@babel/core@7.29.7) + metro-config: 0.84.6 + metro-runtime: 0.84.6 transitivePeerDependencies: - '@babel/core' - bufferutil @@ -10472,83 +11052,83 @@ snapshots: '@react-native/normalize-color@2.1.0': {} - '@react-native/normalize-colors@0.81.5': {} + '@react-native/normalize-colors@0.86.3': {} '@react-native/typescript-config@0.81.5': {} - '@react-native/virtualized-lists@0.81.5(@types/react@19.1.17)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-native/virtualized-lists@0.86.3(@types/react@19.2.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.18 - '@react-navigation/core@7.21.1(react@19.1.0)': + '@react-navigation/core@7.21.1(react@19.2.3)': dependencies: '@react-navigation/routers': 7.6.0 escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 nanoid: 3.3.11 query-string: 7.1.3 - react: 19.1.0 + react: 19.2.3 react-is: 19.1.0 - use-latest-callback: 0.2.6(react@19.1.0) - use-sync-external-store: 1.6.0(react@19.1.0) + use-latest-callback: 0.2.6(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/drawer@7.12.2(1791ab06baac4955f4dd8054e3baaac6)': + '@react-navigation/drawer@7.12.2(568a64fb44ad70e6f0b55611752f41f8)': dependencies: - '@react-navigation/elements': 2.9.25(@react-native-masked-view/masked-view@0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@react-navigation/native': 7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/elements': 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) + '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-drawer-layout: 4.2.5(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-screens: 4.17.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - use-latest-callback: 0.2.6(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-drawer-layout: 4.2.5(641d271507e632f6b7f49ee76b960c3d) + react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + use-latest-callback: 0.2.6(react@19.2.3) transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/elements@2.9.25(@react-native-masked-view/masked-view@0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-navigation/elements@2.9.25(94b93b3be2a5aeeeacd03698514a6eb6)': dependencies: - '@react-navigation/native': 7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - use-latest-callback: 0.2.6(react@19.1.0) - use-sync-external-store: 1.6.0(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + use-latest-callback: 0.2.6(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) optionalDependencies: - '@react-native-masked-view/masked-view': 0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - '@react-navigation/native-stack@7.17.5(5d5d774b9c7e992721e68b3dbd486565)': + '@react-navigation/native-stack@7.17.5(b9b07f1e05b03fd68f5bb004ad12b8b4)': dependencies: - '@react-navigation/elements': 2.9.25(@react-native-masked-view/masked-view@0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@react-navigation/native': 7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-navigation/elements': 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) + '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-screens: 4.17.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) sf-symbols-typescript: 2.2.0 warn-once: 0.1.1 transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/native@7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@react-navigation/native@7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - '@react-navigation/core': 7.21.1(react@19.1.0) + '@react-navigation/core': 7.21.1(react@19.2.3) escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 nanoid: 3.3.11 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) standard-navigation: 0.0.7 - use-latest-callback: 0.2.6(react@19.1.0) + use-latest-callback: 0.2.6(react@19.2.3) '@react-navigation/routers@7.6.0': dependencies: @@ -10592,10 +11172,10 @@ snapshots: dependencies: tldts: 6.1.86 - '@rocket.chat/mobile-crypto@https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/69a0a250dd7c6ff0808eb659d7202be1cae7fa1c(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@rocket.chat/mobile-crypto@https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/69a0a250dd7c6ff0808eb659d7202be1cae7fa1c(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) '@rocket.chat/sdk@https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/ecf4fedcf8ab6121e62a6b95bb48823afd805afd': dependencies: @@ -10643,34 +11223,34 @@ snapshots: '@storybook/global@5.0.0': {} - '@storybook/react-dom-shim@9.0.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))': + '@storybook/react-dom-shim@9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))': dependencies: - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4) - '@storybook/react-dom-shim@9.1.17(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))': + '@storybook/react-dom-shim@9.1.17(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))': dependencies: - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4) - '@storybook/react-native-theming@9.1.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@storybook/react-native-theming@9.1.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: polished: 4.3.1 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - '@storybook/react-native-ui-common@9.1.4(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2)': + '@storybook/react-native-ui-common@9.1.4(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2)': dependencies: - '@storybook/react': 9.1.17(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) - '@storybook/react-native-theming': 9.1.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@storybook/react': 9.1.17(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) + '@storybook/react-native-theming': 9.1.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) es-toolkit: 1.43.0 fuse.js: 7.1.0 memoizerific: 1.11.3 polished: 4.3.1 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) store2: 2.14.4 storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4) ts-dedent: 2.2.0 @@ -10678,51 +11258,51 @@ snapshots: - react-dom - typescript - '@storybook/react-native-ui@9.1.4(ebecaf8f8fcf1df6c542e15e1e29e0ad)': + '@storybook/react-native-ui@9.1.4(ec70a72c944b256777781574cc6aeff6)': dependencies: - '@gorhom/bottom-sheet': 5.2.8(@types/react@19.1.17)(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@storybook/react': 9.1.17(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) - '@storybook/react-native-theming': 9.1.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@storybook/react-native-ui-common': 9.1.4(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) + '@gorhom/bottom-sheet': 5.2.8(9928f68f03a282f377b2614cb23deb76) + '@storybook/react': 9.1.17(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) + '@storybook/react-native-theming': 9.1.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@storybook/react-native-ui-common': 9.1.4(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) es-toolkit: 1.43.0 fuse.js: 7.1.0 memoizerific: 1.11.3 polished: 4.3.1 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-svg: 15.12.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-svg: 15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) store2: 2.14.4 storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4) transitivePeerDependencies: - react-dom - typescript - '@storybook/react-native@9.0.18(b3c380f041753120db9b4b839e8df819)': + '@storybook/react-native@9.0.18(03fc32bb656def7817eec6786e023d85)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react': 9.0.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) - '@storybook/react-native-theming': 9.1.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@storybook/react-native-ui': 9.1.4(ebecaf8f8fcf1df6c542e15e1e29e0ad) - '@storybook/react-native-ui-common': 9.1.4(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) + '@storybook/react': 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) + '@storybook/react-native-theming': 9.1.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@storybook/react-native-ui': 9.1.4(ec70a72c944b256777781574cc6aeff6) + '@storybook/react-native-ui-common': 9.1.4(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) commander: 8.3.0 dedent: 1.6.0(babel-plugin-macros@3.1.0) deepmerge: 4.3.1 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-url-polyfill: 2.0.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-url-polyfill: 2.0.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) setimmediate: 1.0.5 storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4) type-fest: 2.19.0 util: 0.12.5 ws: 8.18.3 optionalDependencies: - '@gorhom/bottom-sheet': 5.2.8(@types/react@19.1.17)(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@gorhom/bottom-sheet': 5.2.8(9928f68f03a282f377b2614cb23deb76) + react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - babel-plugin-macros - bufferutil @@ -10731,29 +11311,29 @@ snapshots: - typescript - utf-8-validate - '@storybook/react@9.0.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2)': + '@storybook/react@9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 9.0.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4)) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + '@storybook/react-dom-shim': 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4)) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4) optionalDependencies: typescript: 7.0.2 - '@storybook/react@9.1.17(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2)': + '@storybook/react@9.1.17(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 9.1.17(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4)) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + '@storybook/react-dom-shim': 9.1.17(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4)) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4) optionalDependencies: typescript: 7.0.2 '@testing-library/dom@10.4.1': dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 '@babel/runtime': 7.25.9 '@types/aria-query': 5.0.4 aria-query: 5.3.0 @@ -10771,14 +11351,14 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': + '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: jest-matcher-utils: 30.2.0 picocolors: 1.1.1 pretty-format: 30.2.0 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-test-renderer: 19.1.0(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-test-renderer: 19.2.3(react@19.2.3) redent: 3.0.0 optionalDependencies: jest: 29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0) @@ -10831,6 +11411,8 @@ snapshots: '@types/ejson@2.2.2': {} + '@types/emscripten@1.41.6': {} + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -10853,9 +11435,9 @@ snapshots: dependencies: '@types/unist': 2.0.11 - '@types/hoist-non-react-statics@3.3.7(@types/react@19.1.17)': + '@types/hoist-non-react-statics@3.3.7(@types/react@19.2.18)': dependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.18 hoist-non-react-statics: 3.3.2 '@types/i18n-js@3.8.3': {} @@ -10905,13 +11487,17 @@ snapshots: '@types/prop-types@15.7.15': {} + '@types/react-test-renderer@19.3.0': + dependencies: + '@types/react': 19.2.18 + '@types/react@17.0.90': dependencies: '@types/prop-types': 15.7.15 '@types/scheduler': 0.16.8 csstype: 3.2.3 - '@types/react@19.1.17': + '@types/react@19.2.18': dependencies: csstype: 3.2.3 @@ -11058,18 +11644,6 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@urql/core@5.2.0': - dependencies: - '@0no-co/graphql.web': 1.2.0 - wonka: 6.3.5 - transitivePeerDependencies: - - graphql - - '@urql/exchange-retry@1.3.2(@urql/core@5.2.0)': - dependencies: - '@urql/core': 5.2.0 - wonka: 6.3.5 - '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.3 @@ -11180,10 +11754,10 @@ snapshots: '@yarnpkg/lockfile@1.1.0': {} - '@zoontek/react-native-navigation-bar@1.1.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@zoontek/react-native-navigation-bar@1.1.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) abab@2.0.6: {} @@ -11196,6 +11770,11 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + acorn-globals@7.0.1: dependencies: acorn: 8.16.0 @@ -11223,6 +11802,8 @@ snapshots: agent-base@7.1.4: {} + agent-cli-detector@0.1.7: {} + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -11278,8 +11859,6 @@ snapshots: ansi-styles@6.2.3: {} - any-promise@1.3.0: {} - anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -11365,14 +11944,6 @@ snapshots: asap@2.0.6: {} - assert@2.1.0: - dependencies: - call-bind: 1.0.8 - is-nan: 1.3.2 - object-is: 1.1.6 - object.assign: 4.1.7 - util: 0.12.5 - assertion-error@2.0.1: {} ast-types@0.16.1: @@ -11415,9 +11986,22 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@9.1.3(@babel/core@7.25.9)(webpack@5.106.2(esbuild@0.25.12)): + babel-jest@29.7.0(@babel/core@7.29.7): dependencies: - '@babel/core': 7.25.9 + '@babel/core': 7.29.7 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.29.7) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-loader@9.1.3(@babel/core@7.29.7)(webpack@5.106.2(esbuild@0.25.12)): + dependencies: + '@babel/core': 7.29.7 find-cache-dir: 4.0.0 schema-utils: 4.3.3 webpack: 5.106.2(esbuild@0.25.12) @@ -11454,35 +12038,35 @@ snapshots: reselect: 4.1.8 resolve: 1.22.11 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.25.9): + babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.29.7): dependencies: '@babel/compat-data': 7.29.0 - '@babel/core': 7.25.9 - '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.7) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.25.9): + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.29.7): dependencies: - '@babel/core': 7.25.9 - '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.7) core-js-compat: 3.48.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.25.9): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7): dependencies: - '@babel/core': 7.25.9 - '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.7) core-js-compat: 3.48.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.25.9): + babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.29.7): dependencies: - '@babel/core': 7.25.9 - '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.25.9) + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.7) transitivePeerDependencies: - supports-color @@ -11496,9 +12080,17 @@ snapshots: dependencies: hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.25.9): + babel-plugin-syntax-hermes-parser@0.36.0: dependencies: - '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.25.9) + hermes-parser: 0.36.0 + + babel-plugin-syntax-hermes-parser@0.36.1: + dependencies: + hermes-parser: 0.36.1 + + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.7): + dependencies: + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.29.7) transitivePeerDependencies: - '@babel/core' @@ -11523,34 +12115,73 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.9) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.25.9) - babel-preset-expo@54.0.9(@babel/core@7.25.9)(@babel/runtime@7.25.9)(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-refresh@0.14.2): - dependencies: - '@babel/helper-module-imports': 7.28.6 - '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.25.9) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.25.9) - '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.25.9) - '@babel/preset-react': 7.28.5(@babel/core@7.25.9) - '@babel/preset-typescript': 7.28.5(@babel/core@7.25.9) - '@react-native/babel-preset': 0.81.5(@babel/core@7.25.9) + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) + + babel-preset-expo@57.0.12(@babel/core@7.29.7)(@babel/runtime@7.25.9)(expo@57.0.23)(react-refresh@0.14.2): + dependencies: + '@babel/generator': 7.29.8 + '@babel/helper-module-imports': 7.29.7 + '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.29.7) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.7) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) + '@react-native/babel-plugin-codegen': 0.86.3(@babel/core@7.29.7) babel-plugin-react-compiler: 1.0.0 babel-plugin-react-native-web: 0.21.2 - babel-plugin-syntax-hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.25.9) + babel-plugin-syntax-hermes-parser: 0.36.1 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) debug: 4.4.3 react-refresh: 0.14.2 - resolve-from: 5.0.0 optionalDependencies: '@babel/runtime': 7.25.9 - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) transitivePeerDependencies: - '@babel/core' - supports-color @@ -11561,10 +12192,24 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.25.9) + babel-preset-jest@29.6.3(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + badgin@1.2.3: {} balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + + barcode-detector@3.2.2(@types/emscripten@1.41.6): + dependencies: + zxing-wasm: 3.1.3(@types/emscripten@1.41.6) + transitivePeerDependencies: + - '@types/emscripten' + bare-events@2.8.2: {} bare-fs@4.5.2: @@ -11658,6 +12303,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -11748,8 +12397,6 @@ snapshots: chownr@1.1.4: {} - chownr@3.0.0: {} - chrome-launcher@0.15.2: dependencies: '@types/node': 25.0.3 @@ -11761,14 +12408,13 @@ snapshots: chrome-trace-event@1.0.4: {} - chromium-edge-launcher@0.2.0: + chromium-edge-launcher@0.3.0: dependencies: '@types/node': 25.0.3 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 mkdirp: 1.0.4 - rimraf: 3.0.2 transitivePeerDependencies: - supports-color @@ -11864,8 +12510,6 @@ snapshots: commander@2.20.3: {} - commander@4.1.1: {} - commander@7.2.0: {} commander@8.3.0: {} @@ -11917,6 +12561,8 @@ snapshots: content-type@1.0.5: {} + content-type@2.1.0: {} + convert-source-map@2.0.0: {} core-js-compat@3.48.0: @@ -11964,8 +12610,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypto-random-string@2.0.0: {} - css-select@5.2.2: dependencies: boolbase: 1.0.0 @@ -12079,6 +12723,7 @@ snapshots: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 + optional: true delayed-stream@1.0.0: {} @@ -12102,6 +12747,8 @@ snapshots: diff-sequences@29.6.3: {} + dnssd-advertise@1.1.6: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -12133,15 +12780,9 @@ snapshots: domutils@3.2.2: dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - - dotenv-expand@11.0.7: - dependencies: - dotenv: 16.6.1 - - dotenv@16.4.7: {} + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 dotenv@16.6.1: {} @@ -12190,8 +12831,6 @@ snapshots: entities@6.0.1: {} - env-editor@0.4.2: {} - env-paths@2.2.1: {} envinfo@7.21.0: {} @@ -12522,8 +13161,6 @@ snapshots: events@3.3.0: {} - exec-async@2.2.0: {} - execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -12548,182 +13185,202 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - expo-apple-authentication@8.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + expo-apple-authentication@57.0.2(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - expo-application@7.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): + expo-application@57.0.3(expo@57.0.23): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) - expo-asset@12.0.12(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-asset@57.0.17(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2): dependencies: - '@expo/image-utils': 0.8.8 - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-constants: 18.0.12(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + '@expo/image-utils': 0.11.5(typescript@7.0.2) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo-constants: 57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) transitivePeerDependencies: - supports-color + - typescript - expo-audio@0.4.9(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-audio@57.0.5(expo-asset@57.0.17(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo-asset: 57.0.17(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - expo-camera@17.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-camera@57.0.5(@types/emscripten@1.41.6)(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - invariant: 2.2.4 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + barcode-detector: 3.2.2(@types/emscripten@1.41.6) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + transitivePeerDependencies: + - '@types/emscripten' - expo-constants@18.0.12(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + expo-constants@57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - '@expo/config': 12.0.13 - '@expo/env': 2.0.8 - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + '@expo/env': 2.4.3 + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) transitivePeerDependencies: - supports-color - expo-device@8.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): + expo-device@57.0.2(expo@57.0.23): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) ua-parser-js: 1.0.2 - expo-document-picker@14.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): + expo-document-picker@57.0.2(expo@57.0.23): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) - expo-file-system@19.0.21(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + expo-file-system@57.0.7(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - expo-font@14.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) fontfaceobserver: 2.3.0 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - expo-haptics@15.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): + expo-haptics@57.0.3(expo@57.0.23): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) - expo-image@3.0.11(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-image@57.0.5(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + sf-symbols-typescript: 2.2.0 - expo-keep-awake@15.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react@19.1.0): + expo-keep-awake@57.0.2(expo@57.0.23)(react@19.2.3): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react: 19.1.0 + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react: 19.2.3 - expo-local-authentication@17.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): + expo-local-authentication@57.0.3(expo@57.0.23): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) invariant: 2.2.4 - expo-modules-autolinking@3.0.23: + expo-modules-autolinking@57.0.13(typescript@7.0.2): dependencies: - '@expo/spawn-async': 1.7.2 + '@expo/require-utils': 57.0.5(typescript@7.0.2) + '@expo/spawn-async': 1.8.0 chalk: 4.1.2 commander: 7.2.0 - require-from-string: 2.0.2 - resolve-from: 5.0.0 + transitivePeerDependencies: + - supports-color + - typescript - expo-modules-core@3.0.29(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-modules-core@57.0.18(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: + '@expo/expo-modules-macros-plugin': 0.6.1 + expo-modules-jsi: 57.1.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) invariant: 2.2.4 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + optionalDependencies: + react-native-worklets: 0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + + expo-modules-jsi@57.1.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): + dependencies: + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - expo-notifications@0.32.15(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-notifications@57.0.19(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2): dependencies: - '@expo/image-utils': 0.8.8 - '@ide/backoff': 1.0.0 + '@expo/image-utils': 0.11.5(typescript@7.0.2) abort-controller: 3.0.0 - assert: 2.1.0 badgin: 1.2.3 - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-application: 7.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) - expo-constants: 18.0.12(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo-application: 57.0.3(expo@57.0.23) + expo-constants: 57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) transitivePeerDependencies: - supports-color + - typescript - expo-server@1.0.5: {} + expo-server@57.0.3: {} - expo-status-bar@3.0.9(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-status-bar@57.0.1(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - expo-system-ui@6.0.9(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + expo-system-ui@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - '@react-native/normalize-colors': 0.81.5 + '@react-native/normalize-colors': 0.86.3 debug: 4.4.3 - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) transitivePeerDependencies: - supports-color - expo-video-thumbnails@10.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): + expo-video-thumbnails@57.0.2(expo@57.0.23): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) - expo-video@3.0.16(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-video@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - expo-web-browser@15.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + expo-web-browser@57.0.3(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo@57.0.23(92e84404446a2d5101115c437b8fd679): dependencies: '@babel/runtime': 7.25.9 - '@expo/cli': 54.0.20(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) - '@expo/config': 12.0.13 - '@expo/config-plugins': 54.0.4 - '@expo/devtools': 0.1.8(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - '@expo/fingerprint': 0.15.4 - '@expo/metro': 54.2.0 - '@expo/metro-config': 54.0.12(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) - '@expo/vector-icons': 15.0.3(expo-font@14.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@expo/cli': 57.0.25(@expo/dom-webview@57.0.1)(expo-constants@57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo@57.0.23)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + '@expo/config': 57.0.9(typescript@7.0.2) + '@expo/config-plugins': 57.0.9(typescript@7.0.2) + '@expo/devtools': 57.0.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@expo/fingerprint': 0.20.13 + '@expo/local-build-cache-provider': 57.0.8(typescript@7.0.2) + '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@expo/metro': 56.0.2 + '@expo/metro-config': 57.0.12(expo@57.0.23)(typescript@7.0.2) '@ungap/structured-clone': 1.3.0 - babel-preset-expo: 54.0.9(@babel/core@7.25.9)(@babel/runtime@7.25.9)(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-refresh@0.14.2) - expo-asset: 12.0.12(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-constants: 18.0.12(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) - expo-file-system: 19.0.21(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)) - expo-font: 14.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-keep-awake: 15.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react@19.1.0) - expo-modules-autolinking: 3.0.23 - expo-modules-core: 3.0.29(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + babel-preset-expo: 57.0.12(@babel/core@7.29.7)(@babel/runtime@7.25.9)(expo@57.0.23)(react-refresh@0.14.2) + expo-asset: 57.0.17(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2) + expo-constants: 57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) + expo-file-system: 57.0.7(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) + expo-font: 57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + expo-keep-awake: 57.0.2(expo@57.0.23)(react@19.2.3) + expo-modules-autolinking: 57.0.13(typescript@7.0.2) + expo-modules-core: 57.0.18(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) pretty-format: 29.7.0 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-refresh: 0.14.2 - whatwg-url-without-unicode: 8.0.0-3 + whatwg-url-minimum: 0.1.2 optionalDependencies: - react-native-webview: 13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@expo/dom-webview': 57.0.1(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-dom: 19.2.3(react@19.2.3) + react-native-webview: 13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - '@babel/core' - bufferutil - expo-router - - graphql + - expo-widgets + - react-native-worklets + - react-server-dom-webpack - supports-color + - typescript - utf-8-validate exponential-backoff@3.1.3: {} @@ -12758,6 +13415,8 @@ snapshots: dependencies: websocket-driver: 0.7.4 + fb-dotslash@0.5.8: {} + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -12766,6 +13425,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fetch-nodeshim@0.4.10: {} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -12828,7 +13489,7 @@ snapshots: dependencies: micromatch: 4.0.8 - firebase@11.3.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))): + firebase@11.3.1(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))): dependencies: '@firebase/analytics': 0.10.11(@firebase/app@0.11.1) '@firebase/analytics-compat': 0.2.17(@firebase/app-compat@0.2.50)(@firebase/app@0.11.1) @@ -12837,8 +13498,8 @@ snapshots: '@firebase/app-check-compat': 0.3.18(@firebase/app-compat@0.2.50)(@firebase/app@0.11.1) '@firebase/app-compat': 0.2.50 '@firebase/app-types': 0.9.3 - '@firebase/auth': 1.9.0(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))) - '@firebase/auth-compat': 0.5.18(@firebase/app-compat@0.2.50)(@firebase/app-types@0.9.3)(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))) + '@firebase/auth': 1.9.0(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))) + '@firebase/auth-compat': 0.5.18(@firebase/app-compat@0.2.50)(@firebase/app-types@0.9.3)(@firebase/app@0.11.1)(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))) '@firebase/data-connect': 0.3.0(@firebase/app@0.11.1) '@firebase/database': 1.0.12 '@firebase/database-compat': 2.0.3 @@ -12900,8 +13561,6 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 - freeport-async@2.0.0: {} - fresh@0.5.2: {} fs-constants@1.0.0: {} @@ -13030,10 +13689,6 @@ snapshots: minipass: 4.2.8 path-scurry: 1.11.1 - global-dirs@0.1.1: - dependencies: - ini: 1.3.8 - globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -13098,11 +13753,17 @@ snapshots: he@1.2.0: {} + hermes-compiler@250829098.0.17: {} + hermes-estree@0.28.1: {} hermes-estree@0.29.1: {} - hermes-estree@0.32.0: {} + hermes-estree@0.35.0: {} + + hermes-estree@0.36.0: {} + + hermes-estree@0.36.1: {} hermes-parser@0.28.1: dependencies: @@ -13112,9 +13773,17 @@ snapshots: dependencies: hermes-estree: 0.29.1 - hermes-parser@0.32.0: + hermes-parser@0.35.0: + dependencies: + hermes-estree: 0.35.0 + + hermes-parser@0.36.0: + dependencies: + hermes-estree: 0.36.0 + + hermes-parser@0.36.1: dependencies: - hermes-estree: 0.32.0 + hermes-estree: 0.36.1 hoist-non-react-statics@3.3.2: dependencies: @@ -13190,10 +13859,6 @@ snapshots: ignore@5.3.2: {} - image-size@1.2.1: - dependencies: - queue: 6.0.2 - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -13339,11 +14004,6 @@ snapshots: is-map@2.0.3: optional: true - is-nan@1.3.2: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - is-negative-zero@2.0.3: optional: true @@ -13601,24 +14261,24 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - jest-expo@54.0.16(@babel/core@7.25.9)(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + jest-expo@57.0.5(@babel/core@7.29.7)(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(expo@57.0.23)(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - '@expo/config': 12.0.13 - '@expo/json-file': 10.0.8 '@jest/create-cache-key-function': 29.7.0 '@jest/globals': 29.7.0 - babel-jest: 29.7.0(@babel/core@7.25.9) - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-native/jest-preset': 0.86.3(@babel/core@7.29.7)(react@19.2.3) + babel-jest: 29.7.0(@babel/core@7.29.7) jest-environment-jsdom: 29.7.0 jest-snapshot: 29.7.0 jest-watch-select-projects: 2.0.0 jest-watch-typeahead: 2.2.1(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0)) json5: 2.2.3 lodash: 4.17.21 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-test-renderer: 19.1.0(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-test-renderer: 19.2.3(react@19.2.3) server-only: 0.0.1 stacktrace-js: 2.0.2 + optionalDependencies: + expo: 57.0.23(92e84404446a2d5101115c437b8fd679) transitivePeerDependencies: - '@babel/core' - bufferutil @@ -13970,7 +14630,7 @@ snapshots: kleur@3.0.3: {} - lan-network@0.1.7: {} + lan-network@0.2.1: {} launch-editor@2.12.0: dependencies: @@ -14153,50 +14813,109 @@ snapshots: merge2@1.4.1: {} - metro-babel-transformer@0.83.3: + metro-babel-transformer@0.84.5: + dependencies: + '@babel/core': 7.25.9 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.35.0 + metro-cache-key: 0.84.5 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-babel-transformer@0.84.6: dependencies: '@babel/core': 7.25.9 flow-enums-runtime: 0.0.6 - hermes-parser: 0.32.0 + hermes-parser: 0.35.0 + metro-cache-key: 0.84.6 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - metro-cache-key@0.83.3: + metro-cache-key@0.84.5: dependencies: flow-enums-runtime: 0.0.6 - metro-cache@0.83.3: + metro-cache-key@0.84.6: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache@0.84.5: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.84.5 + transitivePeerDependencies: + - supports-color + + metro-cache@0.84.6: dependencies: exponential-backoff: 3.1.3 flow-enums-runtime: 0.0.6 https-proxy-agent: 7.0.6 - metro-core: 0.83.3 + metro-core: 0.84.6 + transitivePeerDependencies: + - supports-color + + metro-config@0.84.5: + dependencies: + connect: 3.7.0 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.84.5 + metro-cache: 0.84.5 + metro-core: 0.84.5 + metro-runtime: 0.84.5 + yaml: 2.8.2 transitivePeerDependencies: + - bufferutil - supports-color + - utf-8-validate - metro-config@0.83.3: + metro-config@0.84.6: dependencies: connect: 3.7.0 flow-enums-runtime: 0.0.6 jest-validate: 29.7.0 - metro: 0.83.3 - metro-cache: 0.83.3 - metro-core: 0.83.3 - metro-runtime: 0.83.3 + metro: 0.84.6 + metro-cache: 0.84.6 + metro-core: 0.84.6 + metro-runtime: 0.84.6 yaml: 2.8.2 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro-core@0.83.3: + metro-core@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.84.5 + + metro-core@0.84.6: dependencies: flow-enums-runtime: 0.0.6 lodash.throttle: 4.1.1 - metro-resolver: 0.83.3 + metro-resolver: 0.84.6 + + metro-file-map@0.84.5: + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color - metro-file-map@0.83.3: + metro-file-map@0.84.6: dependencies: debug: 4.4.3 fb-watchman: 2.0.2 @@ -14210,47 +14929,96 @@ snapshots: transitivePeerDependencies: - supports-color - metro-minify-terser@0.83.3: + metro-minify-terser@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.44.1 + + metro-minify-terser@0.84.6: dependencies: flow-enums-runtime: 0.0.6 terser: 5.44.1 - metro-resolver@0.83.3: + metro-resolver@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-resolver@0.84.6: dependencies: flow-enums-runtime: 0.0.6 - metro-runtime@0.83.3: + metro-runtime@0.84.5: + dependencies: + '@babel/runtime': 7.25.9 + flow-enums-runtime: 0.0.6 + + metro-runtime@0.84.6: dependencies: '@babel/runtime': 7.25.9 flow-enums-runtime: 0.0.6 - metro-source-map@0.83.3: + metro-source-map@0.84.5: dependencies: '@babel/traverse': 7.29.0 - '@babel/traverse--for-generate-function-map': '@babel/traverse@7.29.0' '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-symbolicate: 0.83.3 + metro-symbolicate: 0.84.5 nullthrows: 1.1.1 - ob1: 0.83.3 + ob1: 0.84.5 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-symbolicate@0.83.3: + metro-source-map@0.84.6: + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.84.6 + nullthrows: 1.1.1 + ob1: 0.84.6 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.84.5 + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.84.6: dependencies: flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-source-map: 0.83.3 + metro-source-map: 0.84.6 nullthrows: 1.1.1 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-transform-plugins@0.83.3: + metro-transform-plugins@0.84.5: + dependencies: + '@babel/core': 7.25.9 + '@babel/generator': 7.29.1 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.84.6: dependencies: '@babel/core': 7.25.9 '@babel/generator': 7.29.1 @@ -14261,27 +15029,47 @@ snapshots: transitivePeerDependencies: - supports-color - metro-transform-worker@0.83.3: + metro-transform-worker@0.84.5: + dependencies: + '@babel/core': 7.25.9 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + flow-enums-runtime: 0.0.6 + metro: 0.84.5 + metro-babel-transformer: 0.84.5 + metro-cache: 0.84.5 + metro-cache-key: 0.84.5 + metro-minify-terser: 0.84.5 + metro-source-map: 0.84.5 + metro-transform-plugins: 0.84.5 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-transform-worker@0.84.6: dependencies: '@babel/core': 7.25.9 '@babel/generator': 7.29.1 '@babel/parser': 7.29.2 '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 - metro: 0.83.3 - metro-babel-transformer: 0.83.3 - metro-cache: 0.83.3 - metro-cache-key: 0.83.3 - metro-minify-terser: 0.83.3 - metro-source-map: 0.83.3 - metro-transform-plugins: 0.83.3 + metro: 0.84.6 + metro-babel-transformer: 0.84.6 + metro-cache: 0.84.6 + metro-cache-key: 0.84.6 + metro-minify-terser: 0.84.6 + metro-source-map: 0.84.6 + metro-transform-plugins: 0.84.6 nullthrows: 1.1.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro@0.83.3: + metro@0.84.5: dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.25.9 @@ -14290,33 +15078,76 @@ snapshots: '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - accepts: 1.3.8 - chalk: 4.1.2 + accepts: 2.0.0 ci-info: 2.0.0 connect: 3.7.0 debug: 4.4.3 error-stack-parser: 2.1.4 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 - hermes-parser: 0.32.0 - image-size: 1.2.1 + hermes-parser: 0.35.0 invariant: 2.2.4 jest-worker: 29.7.0 jsc-safe-url: 0.2.4 lodash.throttle: 4.1.1 - metro-babel-transformer: 0.83.3 - metro-cache: 0.83.3 - metro-cache-key: 0.83.3 - metro-config: 0.83.3 - metro-core: 0.83.3 - metro-file-map: 0.83.3 - metro-resolver: 0.83.3 - metro-runtime: 0.83.3 - metro-source-map: 0.83.3 - metro-symbolicate: 0.83.3 - metro-transform-plugins: 0.83.3 - metro-transform-worker: 0.83.3 - mime-types: 2.1.35 + metro-babel-transformer: 0.84.5 + metro-cache: 0.84.5 + metro-cache-key: 0.84.5 + metro-config: 0.84.5 + metro-core: 0.84.5 + metro-file-map: 0.84.5 + metro-resolver: 0.84.5 + metro-runtime: 0.84.5 + metro-source-map: 0.84.5 + metro-symbolicate: 0.84.5 + metro-transform-plugins: 0.84.5 + metro-transform-worker: 0.84.5 + mime-types: 3.0.2 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.10 + yargs: 17.7.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro@0.84.6: + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/core': 7.25.9 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + accepts: 2.0.0 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.3 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.35.0 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.84.6 + metro-cache: 0.84.6 + metro-cache-key: 0.84.6 + metro-config: 0.84.6 + metro-core: 0.84.6 + metro-file-map: 0.84.6 + metro-resolver: 0.84.6 + metro-runtime: 0.84.6 + metro-source-map: 0.84.6 + metro-symbolicate: 0.84.6 + metro-transform-plugins: 0.84.6 + metro-transform-worker: 0.84.6 + mime-types: 3.0.2 nullthrows: 1.1.1 serialize-error: 2.1.0 source-map: 0.5.7 @@ -14345,6 +15176,10 @@ snapshots: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mime@2.6.0: {} @@ -14361,6 +15196,10 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.12 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -14379,10 +15218,6 @@ snapshots: minipass@7.1.2: {} - minizlib@3.1.0: - dependencies: - minipass: 7.1.2 - mitt@3.0.1: {} mj-context-menu@0.6.1: {} @@ -14397,18 +15232,16 @@ snapshots: ms@2.1.3: {} - mute-stream@0.0.8: {} + multitars@1.0.2: {} - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 + mute-stream@0.0.8: {} nanoclone@0.2.1: {} nanoid@3.3.11: {} + nanoid@3.3.19: {} + napi-build-utils@2.0.0: {} napi-postinstall@0.3.4: {} @@ -14419,9 +15252,11 @@ snapshots: negotiator@0.6.4: {} - neo-async@2.6.2: {} + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 - nested-error-stacks@2.0.1: {} + neo-async@2.6.2: {} nocache@3.0.4: {} @@ -14472,7 +15307,11 @@ snapshots: nwsapi@2.2.23: {} - ob1@0.83.3: + ob1@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + + ob1@0.84.6: dependencies: flow-enums-runtime: 0.0.6 @@ -14480,11 +15319,6 @@ snapshots: object-inspect@1.13.4: {} - object-is@1.1.6: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - object-keys@1.1.1: {} object.assign@4.1.7: @@ -14495,6 +15329,7 @@ snapshots: es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 + optional: true object.fromentries@2.0.8: dependencies: @@ -14748,8 +15583,6 @@ snapshots: picomatch@2.3.1: {} - picomatch@3.0.1: {} - picomatch@4.0.3: {} picomatch@4.0.5: {} @@ -14782,9 +15615,9 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss@8.4.49: + postcss@8.5.28: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.19 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -14880,8 +15713,6 @@ snapshots: pure-rand@6.1.0: {} - qrcode-terminal@0.11.0: {} - qs@6.14.1: dependencies: side-channel: 1.1.0 @@ -14899,10 +15730,6 @@ snapshots: queue-microtask@1.2.3: {} - queue@6.0.2: - dependencies: - inherits: 2.0.4 - randexp@0.5.3: dependencies: drange: 1.1.1 @@ -14932,18 +15759,18 @@ snapshots: - bufferutil - utf-8-validate - react-dom@19.1.0(react@19.1.0): + react-dom@19.2.3(react@19.2.3): dependencies: - react: 19.1.0 - scheduler: 0.26.0 + react: 19.2.3 + scheduler: 0.27.0 - react-freeze@1.0.4(react@19.1.0): + react-freeze@1.0.4(react@19.2.3): dependencies: - react: 19.1.0 + react: 19.2.3 - react-hook-form@7.34.2(react@19.1.0): + react-hook-form@7.34.2(react@19.2.3): dependencies: - react: 19.1.0 + react: 19.2.3 react-is@16.13.1: {} @@ -14953,16 +15780,18 @@ snapshots: react-is@19.1.0: {} - react-native-a11y-order@0.4.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-is@19.3.0: {} + + react-native-a11y-order@0.4.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-animatable@1.3.3: dependencies: prop-types: 15.8.1 - react-native-bootsplash@6.3.11(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-bootsplash@6.3.11(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: '@expo/config-plugins': 10.1.2 '@react-native-community/cli-config-android': 18.0.1 @@ -14974,9 +15803,9 @@ snapshots: node-html-parser: 7.0.1 picocolors: 1.1.1 prettier: 3.7.4 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-is-edge-to-edge: 1.2.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) sharp: 0.32.6 ts-dedent: 2.2.0 xml-formatter: 3.6.7 @@ -14986,135 +15815,141 @@ snapshots: - react-native-b4a - supports-color - react-native-callkeep@4.3.16(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + react-native-callkeep@4.3.16(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-console-time-polyfill@1.2.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-console-time-polyfill@1.2.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-device-info@11.1.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + react-native-device-info@11.1.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-dotenv@3.4.8(@babel/runtime@7.25.9): dependencies: '@babel/runtime': 7.25.9 dotenv: 16.6.1 - react-native-drawer-layout@4.2.5(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-drawer-layout@4.2.5(641d271507e632f6b7f49ee76b960c3d): dependencies: color: 4.2.3 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - use-latest-callback: 0.2.6(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + use-latest-callback: 0.2.6(react@19.2.3) react-native-easy-grid@0.2.2: dependencies: lodash: 4.17.21 - react-native-easy-toast@2.3.0(prop-types@15.8.1)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + react-native-easy-toast@2.3.0(prop-types@15.8.1)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: deprecated-react-native-prop-types: 2.3.0 prop-types: 15.8.1 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-external-keyboard@0.9.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-external-keyboard@0.9.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-gesture-handler@2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: '@egjs/hammerjs': 2.0.17 + '@types/react-test-renderer': 19.3.0 hoist-non-react-statics: 3.3.2 invariant: 2.2.4 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + + react-native-image-crop-picker@https://codeload.github.com/RocketChat/react-native-image-crop-picker/tar.gz/47092e8c90550a54d45fe307f7cb2a24c9535ed5(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-image-crop-picker@https://codeload.github.com/RocketChat/react-native-image-crop-picker/tar.gz/47092e8c90550a54d45fe307f7cb2a24c9535ed5(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-incall-manager@4.2.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-incall-manager@4.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + react-native-is-edge-to-edge@1.2.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-is-edge-to-edge@1.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-is-edge-to-edge@1.3.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-katex@https://codeload.github.com/RocketChat/react-native-katex/tar.gz/37e579804fe238732d8a4b06dec073610ab062b1(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-katex@https://codeload.github.com/RocketChat/react-native-katex/tar.gz/37e579804fe238732d8a4b06dec073610ab062b1(react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-webview: 13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-webview: 13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-keyboard-controller@1.18.5(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-keyboard-controller@1.18.5(react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-is-edge-to-edge: 1.3.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-linear-gradient@2.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-linear-gradient@2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-localize@3.7.0(@expo/config-plugins@54.0.4)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-localize@3.7.0(@expo/config-plugins@57.0.9(typescript@7.0.2))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - '@expo/config-plugins': 54.0.4 + '@expo/config-plugins': 57.0.9(typescript@7.0.2) - react-native-math-view@3.9.5(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + react-native-math-view@3.9.5(react-native-svg@15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: hast-util-from-selector: 2.0.1 lodash: 4.17.21 mathjax-full: 3.2.2 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-svg: 15.12.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-svg: 15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) transformation-matrix: 2.16.1 react-native-mime-types@2.3.0: dependencies: mime-db: 1.37.0 - react-native-mmkv@4.3.2(react-native-nitro-modules@0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-mmkv@4.3.2(react-native-nitro-modules@0.37.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-nitro-modules: 0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-nitro-modules: 0.37.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-modal@13.0.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-modal@13.0.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: prop-types: 15.8.1 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-animatable: 1.3.3 - react-native-nitro-modules@0.37.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-nitro-modules@0.37.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-notifier@1.6.1(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-notifier@1.6.1(react-native-gesture-handler@2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-picker-select@9.0.1(@react-native-picker/picker@2.11.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): + react-native-picker-select@9.0.1(@react-native-picker/picker@2.11.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)): dependencies: - '@react-native-picker/picker': 2.11.4(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-native-picker/picker': 2.11.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) lodash.isequal: 4.5.0 react-native-popover-view@5.1.7: @@ -15122,121 +15957,121 @@ snapshots: deprecated-react-native-prop-types: 2.3.0 prop-types: 15.8.1 - react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - '@babel/core': 7.25.9 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-worklets: 0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - semver: 7.7.2 + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-is-edge-to-edge: 1.3.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-worklets: 0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + semver: 7.7.4 - react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-safe-area-context@5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-screens@4.17.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-screens@4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.0 - react-freeze: 1.0.4(react@19.1.0) - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-freeze: 1.0.4(react@19.2.3) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) warn-once: 0.1.1 - react-native-skeleton-placeholder@5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-linear-gradient@2.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-skeleton-placeholder@5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - '@react-native-masked-view/masked-view': 0.3.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-linear-gradient: 2.6.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-linear-gradient: 2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-svg@15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: css-select: 5.2.2 css-tree: 1.1.3 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) warn-once: 0.1.1 - react-native-url-polyfill@2.0.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + react-native-url-polyfill@2.0.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) whatwg-url-without-unicode: 8.0.0-3 - react-native-webrtc@124.0.7(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0)): + react-native-webrtc@124.0.7(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: base64-js: 1.5.1 debug: 4.3.4 event-target-shim: 6.0.2 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) transitivePeerDependencies: - supports-color - react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: escape-string-regexp: 4.0.0 invariant: 2.2.4 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - - react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): - dependencies: - '@babel/core': 7.25.9 - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.25.9) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.25.9) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.25.9) - '@babel/preset-typescript': 7.28.5(@babel/core@7.25.9) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + + react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.7) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) + '@babel/types': 7.29.8 + '@react-native/metro-config': 0.86.3(@babel/core@7.29.7) convert-source-map: 2.0.0 - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - semver: 7.7.2 + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + semver: 7.7.4 transitivePeerDependencies: - supports-color - react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0): + react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3): dependencies: - '@jest/create-cache-key-function': 29.7.0 - '@react-native/assets-registry': 0.81.5 - '@react-native/codegen': 0.81.5(@babel/core@7.25.9) - '@react-native/community-cli-plugin': 0.81.5(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9)) - '@react-native/gradle-plugin': 0.81.5 - '@react-native/js-polyfills': 0.81.5 - '@react-native/normalize-colors': 0.81.5 - '@react-native/virtualized-lists': 0.81.5(@types/react@19.1.17)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-native/assets-registry': 0.86.3 + '@react-native/codegen': 0.86.3(@babel/core@7.29.7) + '@react-native/community-cli-plugin': 0.86.3(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.86.3(@babel/core@7.29.7)) + '@react-native/gradle-plugin': 0.86.3 + '@react-native/js-polyfills': 0.86.3 + '@react-native/normalize-colors': 0.86.3 + '@react-native/virtualized-lists': 0.86.3(@types/react@19.2.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 - babel-jest: 29.7.0(@babel/core@7.25.9) - babel-plugin-syntax-hermes-parser: 0.29.1 + babel-plugin-syntax-hermes-parser: 0.36.0 base64-js: 1.5.1 commander: 12.1.0 flow-enums-runtime: 0.0.6 - glob: 7.2.3 + hermes-compiler: 250829098.0.17 invariant: 2.2.4 - jest-environment-node: 29.7.0 memoize-one: 5.2.1 - metro-runtime: 0.83.3 - metro-source-map: 0.83.3 + metro-runtime: 0.84.6 + metro-source-map: 0.84.6 nullthrows: 1.1.1 pretty-format: 29.7.0 promise: 8.3.0 - react: 19.1.0 + react: 19.2.3 react-devtools-core: 6.1.5 react-refresh: 0.14.2 regenerator-runtime: 0.13.11 - scheduler: 0.26.0 + scheduler: 0.27.0 semver: 7.5.2 stacktrace-parser: 0.1.11 + tinyglobby: 0.2.15 whatwg-fetch: 3.6.20 - ws: 6.2.3 + ws: 7.5.10 yargs: 17.7.2 optionalDependencies: - '@types/react': 19.1.17 + '@react-native/jest-preset': 0.86.3(@babel/core@7.29.7)(react@19.2.3) + '@types/react': 19.2.18 transitivePeerDependencies: - '@babel/core' - '@react-native-community/cli' @@ -15245,30 +16080,30 @@ snapshots: - supports-color - utf-8-validate - react-redux@8.0.5(@types/react@19.1.17)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(redux@4.2.0): + react-redux@8.0.5(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(redux@4.2.0): dependencies: '@babel/runtime': 7.25.9 - '@types/hoist-non-react-statics': 3.3.7(@types/react@19.1.17) + '@types/hoist-non-react-statics': 3.3.7(@types/react@19.2.18) '@types/use-sync-external-store': 0.0.3 hoist-non-react-statics: 3.3.2 - react: 19.1.0 + react: 19.2.3 react-is: 18.3.1 - use-sync-external-store: 1.6.0(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.2.3) optionalDependencies: - '@types/react': 19.1.17 - react-dom: 19.1.0(react@19.1.0) - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + '@types/react': 19.2.18 + react-dom: 19.2.3(react@19.2.3) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) redux: 4.2.0 react-refresh@0.14.2: {} - react-test-renderer@19.1.0(react@19.1.0): + react-test-renderer@19.2.3(react@19.2.3): dependencies: - react: 19.1.0 - react-is: 19.1.0 - scheduler: 0.26.0 + react: 19.2.3 + react-is: 19.3.0 + scheduler: 0.27.0 - react@19.1.0: {} + react@19.2.3: {} read-pkg-up@7.0.1: dependencies: @@ -15289,12 +16124,12 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - reanimated-tab-view@0.3.0(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + reanimated-tab-view@0.3.0(641d271507e632f6b7f49ee76b960c3d): dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.3(@babel/core@7.25.9)(react-native-worklets@0.6.1(@babel/core@7.25.9)(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) recast@0.23.11: dependencies: @@ -15379,12 +16214,6 @@ snapshots: require-main-filename@2.0.0: {} - requireg@0.2.2: - dependencies: - nested-error-stacks: 2.0.1 - rc: 1.2.8 - resolve: 1.7.1 - requires-port@1.0.0: {} reselect@4.0.0: {} @@ -15399,10 +16228,6 @@ snapshots: resolve-from@5.0.0: {} - resolve-global@1.0.0: - dependencies: - global-dirs: 0.1.1 - resolve-pkg-maps@1.0.0: {} resolve-workspace-root@2.0.0: {} @@ -15415,10 +16240,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@1.7.1: - dependencies: - path-parse: 1.0.7 - restore-cursor@2.0.0: dependencies: onetime: 2.0.1 @@ -15472,13 +16293,15 @@ snapshots: safer-buffer@2.1.2: {} + sandbox-cli-detector@0.2.0: {} + sax@1.4.3: {} saxes@6.0.0: dependencies: xmlchars: 2.2.0 - scheduler@0.26.0: {} + scheduler@0.27.0: {} schema-utils@4.3.3: dependencies: @@ -15495,8 +16318,6 @@ snapshots: dependencies: lru-cache: 6.0.0 - semver@7.7.2: {} - semver@7.7.4: {} send@0.19.2: @@ -15861,16 +16682,6 @@ snapshots: structured-headers@0.4.1: {} - sucrase@3.35.1: - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.15 - ts-interface-checker: 0.1.13 - superstruct@2.0.2: {} supports-color@5.5.0: @@ -15901,6 +16712,8 @@ snapshots: typical: 5.2.0 wordwrapjs: 4.0.1 + tagged-tag@1.0.0: {} + tapable@2.3.3: {} tar-fs@2.1.4: @@ -15939,16 +16752,6 @@ snapshots: - bare-abort-controller - react-native-b4a - tar@7.5.2: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.1.0 - yallist: 5.0.0 - - temp-dir@2.0.0: {} - terminal-link@2.1.1: dependencies: ansi-escapes: 4.3.2 @@ -15985,14 +16788,6 @@ snapshots: text-table@0.2.0: {} - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - throat@5.0.0: {} through@2.3.8: {} @@ -16030,6 +16825,8 @@ snapshots: toposort@2.0.2: {} + toqr@0.1.1: {} + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -16049,8 +16846,6 @@ snapshots: ts-dedent@2.2.0: {} - ts-interface-checker@0.1.13: {} - tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -16083,6 +16878,10 @@ snapshots: type-fest@2.19.0: {} + type-fest@5.9.0: + dependencies: + tagged-tag: 1.0.0 + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -16198,8 +16997,6 @@ snapshots: undici-types@7.16.0: {} - undici@6.22.0: {} - unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: @@ -16211,10 +17008,6 @@ snapshots: unicode-property-aliases-ecmascript@2.2.0: {} - unique-string@2.0.0: - dependencies: - crypto-random-string: 2.0.0 - universal-websocket-client@1.0.3: dependencies: ws: 8.18.3 @@ -16269,24 +17062,24 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - use-debounce@9.0.4(react@19.1.0): + use-debounce@9.0.4(react@19.2.3): dependencies: - react: 19.1.0 + react: 19.2.3 - use-deep-compare-effect@1.6.1(react@19.1.0): + use-deep-compare-effect@1.6.1(react@19.2.3): dependencies: '@babel/runtime': 7.25.9 '@types/react': 17.0.90 dequal: 2.0.3 - react: 19.1.0 + react: 19.2.3 - use-latest-callback@0.2.6(react@19.1.0): + use-latest-callback@0.2.6(react@19.2.3): dependencies: - react: 19.1.0 + react: 19.2.3 - use-sync-external-store@1.6.0(react@19.1.0): + use-sync-external-store@1.6.0(react@19.2.3): dependencies: - react: 19.1.0 + react: 19.2.3 util-deprecate@1.0.2: {} @@ -16393,6 +17186,8 @@ snapshots: whatwg-mimetype@3.0.0: {} + whatwg-url-minimum@0.1.2: {} + whatwg-url-without-unicode@8.0.0-3: dependencies: buffer: 5.7.1 @@ -16456,8 +17251,6 @@ snapshots: wicked-good-xpath@1.3.0: {} - wonka@6.3.5: {} - word-wrap@1.2.5: {} wordwrapjs@4.0.1: @@ -16534,8 +17327,6 @@ snapshots: yallist@4.0.0: {} - yallist@5.0.0: {} - yaml@1.10.2: optional: true @@ -16586,8 +17377,15 @@ snapshots: property-expr: 2.0.6 toposort: 2.0.2 - zustand@5.0.12(@types/react@19.1.17)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)): + zod@3.25.76: {} + + zustand@5.0.12(@types/react@19.2.18)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): optionalDependencies: - '@types/react': 19.1.17 - react: 19.1.0 - use-sync-external-store: 1.6.0(react@19.1.0) + '@types/react': 19.2.18 + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) + + zxing-wasm@3.1.3(@types/emscripten@1.41.6): + dependencies: + '@types/emscripten': 1.41.6 + type-fest: 5.9.0 From 521901437bb2cc9e2909f9dcfbacff54bc3db29d Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 15 Sep 2026 18:42:24 -0300 Subject: [PATCH 009/120] fix: adapt app code to React Native 0.86 and Expo 57 (#7694) * fix: adapt app code to React Native 0.86 and Expo 57 * test: await scheduled gesture and floating date callbacks --- app/containers/ActionSheet/styles.ts | 2 +- app/containers/AudioPlayer/Seek.test.tsx | 14 +- .../Avatar/__snapshots__/Avatar.test.tsx.snap | 54 + .../Chip/__snapshots__/Chip.test.tsx.snap | 9 + .../__snapshots__/DirectoryItem.test.tsx.snap | 21 + .../NotifierComponent.test.tsx.snap | 12 + app/containers/Loading/index.tsx | 2 +- .../LoginServices/LoginServices.test.tsx | 8 +- .../__snapshots__/FilterHeader.test.tsx.snap | 8 +- .../__snapshots__/PeerItem.test.tsx.snap | 6 + .../__snapshots__/PeerList.test.tsx.snap | 9 + .../__snapshots__/SelectedPeer.test.tsx.snap | 12 + .../__snapshots__/ReactionsList.test.tsx.snap | 36 + .../__snapshots__/RoomItem.test.tsx.snap | 150 +++ .../__snapshots__/SearchBox.test.tsx.snap | 4 +- .../__snapshots__/ServerItem.test.tsx.snap | 24 + app/containers/StatusBar.tsx | 7 +- .../__snapshots__/TextInput.test.tsx.snap | 46 +- .../__snapshots__/UiKitModal.test.tsx.snap | 42 +- app/containers/VideoPlayer/index.tsx | 2 +- .../__snapshots__/Markdown.test.tsx.snap | 15 + .../message/components/Attachments/Reply.tsx | 2 +- .../__snapshots__/Message.test.tsx.snap | 1083 +++++++++++++++++ .../__snapshots__/leaves.test.tsx.snap | 6 + app/lib/methods/helpers/theme.ts | 2 +- .../MasterDetailStack/ModalContainer.tsx | 2 +- .../__snapshots__/index.test.tsx.snap | 42 + .../__snapshots__/Dialpad.test.tsx.snap | 16 +- .../__snapshots__/CallerInfo.test.tsx.snap | 6 + .../__snapshots__/Item.test.tsx.snap | 24 + .../__snapshots__/Item.test.tsx.snap | 12 + .../ServersHistoryItem.test.tsx.snap | 24 + .../RoomInfoViewAvatar.test.tsx.snap | 6 + .../List/hooks/useFloatingDate.test.ts | 47 +- app/views/ShareView/Preview.tsx | 2 +- .../__snapshots__/Item.test.tsx.snap | 36 + jest.setup.js | 7 + 37 files changed, 1705 insertions(+), 95 deletions(-) diff --git a/app/containers/ActionSheet/styles.ts b/app/containers/ActionSheet/styles.ts index be6d260b9e3..ecbb9be14e3 100644 --- a/app/containers/ActionSheet/styles.ts +++ b/app/containers/ActionSheet/styles.ts @@ -39,7 +39,7 @@ export default StyleSheet.create({ margin: 8 }, backdrop: { - ...StyleSheet.absoluteFillObject + ...StyleSheet.absoluteFill }, button: { marginHorizontal: 16, diff --git a/app/containers/AudioPlayer/Seek.test.tsx b/app/containers/AudioPlayer/Seek.test.tsx index ce7668c55a7..7ba004704f3 100644 --- a/app/containers/AudioPlayer/Seek.test.tsx +++ b/app/containers/AudioPlayer/Seek.test.tsx @@ -18,27 +18,27 @@ const setup = () => { afterEach(() => jest.restoreAllMocks()); -test('a tap that never starts dragging preserves the paused position', () => { +test('a tap that never starts dragging preserves the paused position', async () => { const { gesture, currentTime, onChangeTime } = setup(); - act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.FAILED }])); + await act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.FAILED }])); expect(currentTime.value).toBe(30); expect(onChangeTime).not.toHaveBeenCalled(); }); -test('a cancelled drag restores its starting position without seeking', () => { +test('a cancelled drag restores its starting position without seeking', async () => { const { gesture, currentTime, onChangeTime } = setup(); - act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.ACTIVE }, { state: State.CANCELLED }])); + await act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.ACTIVE }, { state: State.CANCELLED }])); expect(currentTime.value).toBe(30); expect(onChangeTime).not.toHaveBeenCalled(); currentTime.value = 45; - act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.FAILED }])); + await act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.FAILED }])); expect(currentTime.value).toBe(45); }); -test('a completed drag seeks in seconds', () => { +test('a completed drag seeks in seconds', async () => { const { gesture, onChangeTime } = setup(); - act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.ACTIVE }, { state: State.END }])); + await act(() => fireGestureHandler(gesture, [{ state: State.BEGAN }, { state: State.ACTIVE }, { state: State.END }])); expect(onChangeTime).toHaveBeenCalledTimes(1); expect(onChangeTime).toHaveBeenCalledWith(30); }); diff --git a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap index ecb8f0874b4..24c8f2149b2 100644 --- a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap +++ b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap @@ -34,6 +34,7 @@ exports[`Story Snapshots: AvatarBase64 should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -51,6 +52,8 @@ exports[`Story Snapshots: AvatarBase64 should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -91,6 +94,7 @@ exports[`Story Snapshots: AvatarExternalProviderUrl should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -108,6 +112,8 @@ exports[`Story Snapshots: AvatarExternalProviderUrl should match snapshot 1`] = "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -148,6 +154,7 @@ exports[`Story Snapshots: AvatarPath should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -165,6 +172,8 @@ exports[`Story Snapshots: AvatarPath should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -205,6 +214,7 @@ exports[`Story Snapshots: AvatarRoomId should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -222,6 +232,8 @@ exports[`Story Snapshots: AvatarRoomId should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -262,6 +274,7 @@ exports[`Story Snapshots: AvatarText should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -279,6 +292,8 @@ exports[`Story Snapshots: AvatarText should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -319,6 +334,7 @@ exports[`Story Snapshots: AvatarUrl should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -336,6 +352,8 @@ exports[`Story Snapshots: AvatarUrl should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -376,6 +394,7 @@ exports[`Story Snapshots: Channel should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -393,6 +412,8 @@ exports[`Story Snapshots: Channel should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -433,6 +454,7 @@ exports[`Story Snapshots: Children should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -450,6 +472,8 @@ exports[`Story Snapshots: Children should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -529,6 +553,7 @@ exports[`Story Snapshots: CustomBorderRadius should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -546,6 +571,8 @@ exports[`Story Snapshots: CustomBorderRadius should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -588,6 +615,7 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -605,6 +633,8 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -645,6 +675,7 @@ exports[`Story Snapshots: Direct should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -662,6 +693,8 @@ exports[`Story Snapshots: Direct should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -712,6 +745,7 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -726,6 +760,8 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -767,6 +803,7 @@ exports[`Story Snapshots: RoomAvatarExternalProviderUrl should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -784,6 +821,8 @@ exports[`Story Snapshots: RoomAvatarExternalProviderUrl should match snapshot 1` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -824,6 +863,7 @@ exports[`Story Snapshots: Static should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -841,6 +881,8 @@ exports[`Story Snapshots: Static should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -961,6 +1003,7 @@ exports[`Story Snapshots: Touchable should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -978,6 +1021,8 @@ exports[`Story Snapshots: Touchable should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -1021,6 +1066,7 @@ exports[`Story Snapshots: WithETag should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1038,6 +1084,8 @@ exports[`Story Snapshots: WithETag should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -1078,6 +1126,7 @@ exports[`Story Snapshots: WithoutETag should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1095,6 +1144,8 @@ exports[`Story Snapshots: WithoutETag should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> @@ -1135,6 +1186,7 @@ exports[`Story Snapshots: WrongServer should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1152,6 +1204,8 @@ exports[`Story Snapshots: WrongServer should match snapshot 1`] = ` "width": 56, } } + symbolSize={null} + symbolWeight={null} transition={null} width={56} /> diff --git a/app/containers/Chip/__snapshots__/Chip.test.tsx.snap b/app/containers/Chip/__snapshots__/Chip.test.tsx.snap index 724d28a710d..e2eef08552c 100644 --- a/app/containers/Chip/__snapshots__/Chip.test.tsx.snap +++ b/app/containers/Chip/__snapshots__/Chip.test.tsx.snap @@ -208,6 +208,7 @@ exports[`Story Snapshots: ChipText should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -225,6 +226,8 @@ exports[`Story Snapshots: ChipText should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> @@ -396,6 +399,7 @@ exports[`Story Snapshots: ChipWithShortText should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -413,6 +417,8 @@ exports[`Story Snapshots: ChipWithShortText should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> @@ -820,6 +826,7 @@ exports[`Story Snapshots: ChipWithoutIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -837,6 +844,8 @@ exports[`Story Snapshots: ChipWithoutIcon should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> diff --git a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap index 02fd544278f..8f7883c8591 100644 --- a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap +++ b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap @@ -149,6 +149,7 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -166,6 +167,8 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -438,6 +441,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -455,6 +459,8 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -727,6 +733,7 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -744,6 +751,8 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -985,6 +994,7 @@ exports[`Story Snapshots: LongRoomName should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1002,6 +1012,8 @@ exports[`Story Snapshots: LongRoomName should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -1490,6 +1502,7 @@ exports[`Story Snapshots: TeamMain should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1507,6 +1520,8 @@ exports[`Story Snapshots: TeamMain should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -1779,6 +1794,7 @@ exports[`Story Snapshots: WithRightLabel should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1796,6 +1812,8 @@ exports[`Story Snapshots: WithRightLabel should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -2090,6 +2108,7 @@ exports[`Story Snapshots: WithoutDescription should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -2107,6 +2126,8 @@ exports[`Story Snapshots: WithoutDescription should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> diff --git a/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap b/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap index c8346155834..22b27242232 100644 --- a/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap +++ b/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap @@ -154,6 +154,7 @@ exports[`Story Snapshots: ChannelMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -171,6 +172,8 @@ exports[`Story Snapshots: ChannelMessage should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -506,6 +509,7 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -523,6 +527,8 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -858,6 +864,7 @@ exports[`Story Snapshots: WithBlackTheme should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -875,6 +882,8 @@ exports[`Story Snapshots: WithBlackTheme should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -1210,6 +1219,7 @@ exports[`Story Snapshots: WithDarkTheme should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1227,6 +1237,8 @@ exports[`Story Snapshots: WithDarkTheme should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> diff --git a/app/containers/Loading/index.tsx b/app/containers/Loading/index.tsx index 9d579450bd8..4e356e0acdf 100644 --- a/app/containers/Loading/index.tsx +++ b/app/containers/Loading/index.tsx @@ -110,7 +110,7 @@ const Loading = (): ReactElement | null => { ({})); -jest.mock('react-native/Libraries/Linking/Linking', () => ({ - openURL: jest.fn() -})); +jest.mock('react-native/Libraries/Linking/Linking', () => { + const linking = { openURL: jest.fn() }; + + return { ...linking, default: linking }; +}); const SERVER = 'https://demo.rocket.chat'; diff --git a/app/containers/NewMediaCall/__snapshots__/FilterHeader.test.tsx.snap b/app/containers/NewMediaCall/__snapshots__/FilterHeader.test.tsx.snap index 1b2a83f0383..41809dd0ebf 100644 --- a/app/containers/NewMediaCall/__snapshots__/FilterHeader.test.tsx.snap +++ b/app/containers/NewMediaCall/__snapshots__/FilterHeader.test.tsx.snap @@ -40,13 +40,13 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` } > @@ -452,6 +455,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -469,6 +473,8 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> diff --git a/app/containers/NewMediaCall/__snapshots__/PeerList.test.tsx.snap b/app/containers/NewMediaCall/__snapshots__/PeerList.test.tsx.snap index 3647f8288b7..59381fb0ffc 100644 --- a/app/containers/NewMediaCall/__snapshots__/PeerList.test.tsx.snap +++ b/app/containers/NewMediaCall/__snapshots__/PeerList.test.tsx.snap @@ -314,6 +314,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -331,6 +332,8 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> @@ -522,6 +525,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -539,6 +543,8 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> @@ -730,6 +736,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -747,6 +754,8 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> diff --git a/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap b/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap index 1da0008460a..c2250ce3384 100644 --- a/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap +++ b/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap @@ -97,6 +97,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -114,6 +115,8 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> @@ -511,6 +514,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -528,6 +532,8 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> @@ -762,6 +768,7 @@ exports[`Story Snapshots: LongUsername should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -779,6 +786,8 @@ exports[`Story Snapshots: LongUsername should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> @@ -1202,6 +1211,7 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1219,6 +1229,8 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` "width": 28, } } + symbolSize={null} + symbolWeight={null} transition={null} width={28} /> diff --git a/app/containers/ReactionsList/__snapshots__/ReactionsList.test.tsx.snap b/app/containers/ReactionsList/__snapshots__/ReactionsList.test.tsx.snap index 7c2f1d2d9f9..9ee970be89c 100644 --- a/app/containers/ReactionsList/__snapshots__/ReactionsList.test.tsx.snap +++ b/app/containers/ReactionsList/__snapshots__/ReactionsList.test.tsx.snap @@ -160,6 +160,7 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -173,6 +174,8 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` "width": 24, } } + symbolSize={null} + symbolWeight={null} transition={null} width={24} /> @@ -262,6 +265,7 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -275,6 +279,8 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` "width": 24, } } + symbolSize={null} + symbolWeight={null} transition={null} width={24} /> @@ -364,6 +370,7 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -377,6 +384,8 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` "width": 24, } } + symbolSize={null} + symbolWeight={null} transition={null} width={24} /> @@ -691,6 +700,7 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -704,6 +714,8 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -791,6 +803,7 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -804,6 +817,8 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -891,6 +906,7 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -904,6 +920,8 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -1289,6 +1307,7 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -1302,6 +1321,8 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` "width": 24, } } + symbolSize={null} + symbolWeight={null} transition={null} width={24} /> @@ -1391,6 +1412,7 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -1404,6 +1426,8 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` "width": 24, } } + symbolSize={null} + symbolWeight={null} transition={null} width={24} /> @@ -1493,6 +1517,7 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -1506,6 +1531,8 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` "width": 24, } } + symbolSize={null} + symbolWeight={null} transition={null} width={24} /> @@ -1820,6 +1847,7 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -1833,6 +1861,8 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -1920,6 +1950,7 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -1933,6 +1964,8 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -2020,6 +2053,7 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -2033,6 +2067,8 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> diff --git a/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap b/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap index 9743e9e9ebc..cd35cf933d4 100644 --- a/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap +++ b/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap @@ -485,6 +485,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -502,6 +503,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -1139,6 +1142,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1156,6 +1160,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -1837,6 +1843,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1854,6 +1861,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -2535,6 +2544,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -2552,6 +2562,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -3233,6 +3245,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -3250,6 +3263,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -3931,6 +3946,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -3948,6 +3964,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -4629,6 +4647,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -4646,6 +4665,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -5327,6 +5348,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -5344,6 +5366,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -6025,6 +6049,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -6042,6 +6067,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -6723,6 +6750,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -6740,6 +6768,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -7421,6 +7451,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -7438,6 +7469,8 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -8123,6 +8156,7 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -8140,6 +8174,8 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -8760,6 +8796,7 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -8777,6 +8814,8 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -9458,6 +9497,7 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -9475,6 +9515,8 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -10156,6 +10198,7 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -10173,6 +10216,8 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -14716,6 +14761,7 @@ exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -14733,6 +14779,8 @@ exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -15377,6 +15425,7 @@ exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -15394,6 +15443,8 @@ exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -16043,6 +16094,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -16060,6 +16112,8 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -16713,6 +16767,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -16730,6 +16785,8 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -17383,6 +17440,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -17400,6 +17458,8 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -18053,6 +18113,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -18070,6 +18131,8 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -18723,6 +18786,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -18740,6 +18804,8 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -19458,6 +19524,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -19475,6 +19542,8 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -20193,6 +20262,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -20210,6 +20280,8 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -20933,6 +21005,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -20950,6 +21023,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -21566,6 +21641,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -21583,6 +21659,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -22199,6 +22277,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -22216,6 +22295,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -22832,6 +22913,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -22849,6 +22931,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -23465,6 +23549,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -23482,6 +23567,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -24098,6 +24185,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -24115,6 +24203,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -24731,6 +24821,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -24748,6 +24839,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -25364,6 +25457,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -25381,6 +25475,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -25997,6 +26093,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -26014,6 +26111,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -26630,6 +26729,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -26647,6 +26747,8 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -27268,6 +27370,7 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -27285,6 +27388,8 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -27940,6 +28045,7 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -27957,6 +28063,8 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -28650,6 +28758,7 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -28667,6 +28776,8 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -29322,6 +29433,7 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -29339,6 +29451,8 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -30036,6 +30150,7 @@ exports[`Story Snapshots: Touch should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -30053,6 +30168,8 @@ exports[`Story Snapshots: Touch should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -30673,6 +30790,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -30690,6 +30808,8 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -31314,6 +31434,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -31331,6 +31452,8 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -31947,6 +32070,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -31964,6 +32088,8 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -32580,6 +32706,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -32597,6 +32724,8 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -33213,6 +33342,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -33230,6 +33360,8 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -33846,6 +33978,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -33863,6 +33996,8 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -34479,6 +34614,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -34496,6 +34632,8 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -35112,6 +35250,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -35129,6 +35268,8 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -35745,6 +35886,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -35762,6 +35904,8 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -36383,6 +36527,7 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -36400,6 +36545,8 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> @@ -37024,6 +37171,7 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -37041,6 +37189,8 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` "width": 48, } } + symbolSize={null} + symbolWeight={null} transition={null} width={48} /> diff --git a/app/containers/SearchBox/__snapshots__/SearchBox.test.tsx.snap b/app/containers/SearchBox/__snapshots__/SearchBox.test.tsx.snap index fbc1de51897..a72a4351e98 100644 --- a/app/containers/SearchBox/__snapshots__/SearchBox.test.tsx.snap +++ b/app/containers/SearchBox/__snapshots__/SearchBox.test.tsx.snap @@ -10,13 +10,13 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` testID="searchbox" > @@ -361,6 +364,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` }, ] } + sfEffect={null} source={ [ { @@ -375,6 +379,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -598,6 +604,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` }, ] } + sfEffect={null} source={ [ { @@ -612,6 +619,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -966,6 +975,7 @@ exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` }, ] } + sfEffect={null} source={ [ { @@ -980,6 +990,8 @@ exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -1331,6 +1343,7 @@ exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` }, ] } + sfEffect={null} source={ [ { @@ -1345,6 +1358,8 @@ exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -1575,6 +1590,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` }, ] } + sfEffect={null} source={ [ { @@ -1589,6 +1605,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -1812,6 +1830,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` }, ] } + sfEffect={null} source={ [ { @@ -1826,6 +1845,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -2049,6 +2070,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` }, ] } + sfEffect={null} source={ [ { @@ -2063,6 +2085,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> diff --git a/app/containers/StatusBar.tsx b/app/containers/StatusBar.tsx index d096c2b9248..451bbc9f5c3 100644 --- a/app/containers/StatusBar.tsx +++ b/app/containers/StatusBar.tsx @@ -4,18 +4,17 @@ import { useTheme } from '../theme'; interface IStatusBar { barStyle?: 'light' | 'dark'; - backgroundColor?: string; } -const StatusBar = ({ barStyle, backgroundColor }: IStatusBar) => { - const { theme, colors } = useTheme(); +const StatusBar = ({ barStyle }: IStatusBar) => { + const { theme } = useTheme(); if (!barStyle) { barStyle = 'light'; if (theme === 'light') { barStyle = 'dark'; } } - return ; + return ; }; export default StatusBar; diff --git a/app/containers/TextInput/__snapshots__/TextInput.test.tsx.snap b/app/containers/TextInput/__snapshots__/TextInput.test.tsx.snap index f556661b7db..41064e56d07 100644 --- a/app/containers/TextInput/__snapshots__/TextInput.test.tsx.snap +++ b/app/containers/TextInput/__snapshots__/TextInput.test.tsx.snap @@ -9,13 +9,13 @@ exports[`Story Snapshots: Icons should match snapshot 1`] = ` } > @@ -4155,13 +4158,13 @@ exports[`Story Snapshots: ModalInputWithAddField should match snapshot 1`] = ` First field diff --git a/app/containers/VideoPlayer/index.tsx b/app/containers/VideoPlayer/index.tsx index 4d156ff367f..b8877b4553d 100644 --- a/app/containers/VideoPlayer/index.tsx +++ b/app/containers/VideoPlayer/index.tsx @@ -61,7 +61,7 @@ const VideoPlayer = ({ attachment, user, baseUrl, setLoading }: IVideoPlayerProp style={styles.container} contentFit='contain' nativeControls - allowsFullscreen + fullscreenOptions={{ enable: true }} allowsPictureInPicture /> diff --git a/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap b/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap index 5c067a788e9..a77bc0df4ee 100644 --- a/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap +++ b/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap @@ -805,6 +805,7 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -818,6 +819,8 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -866,6 +869,7 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -879,6 +883,8 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -927,6 +933,7 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -940,6 +947,8 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -1034,6 +1043,7 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -1047,6 +1057,8 @@ exports[`Story Snapshots: Emoji should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -4156,6 +4168,7 @@ exports[`Story Snapshots: Image should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -4170,6 +4183,8 @@ exports[`Story Snapshots: Image should match snapshot 1`] = ` "width": 300, } } + symbolSize={null} + symbolWeight={null} transition={null} width={300} /> diff --git a/app/containers/message/components/Attachments/Reply.tsx b/app/containers/message/components/Attachments/Reply.tsx index 264dff3de55..15fe63b2433 100644 --- a/app/containers/message/components/Attachments/Reply.tsx +++ b/app/containers/message/components/Attachments/Reply.tsx @@ -36,7 +36,7 @@ const styles = StyleSheet.create({ paddingLeft: 8 }, backdrop: { - ...StyleSheet.absoluteFillObject + ...StyleSheet.absoluteFill }, authorContainer: { flex: 1, diff --git a/app/containers/message/components/__tests__/__snapshots__/Message.test.tsx.snap b/app/containers/message/components/__tests__/__snapshots__/Message.test.tsx.snap index 404c7f13697..180d7b86dc5 100644 --- a/app/containers/message/components/__tests__/__snapshots__/Message.test.tsx.snap +++ b/app/containers/message/components/__tests__/__snapshots__/Message.test.tsx.snap @@ -224,6 +224,7 @@ exports[`Story Snapshots: Archived should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -241,6 +242,8 @@ exports[`Story Snapshots: Archived should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -679,6 +682,7 @@ exports[`Story Snapshots: ArchivedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -696,6 +700,8 @@ exports[`Story Snapshots: ArchivedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -1142,6 +1148,7 @@ exports[`Story Snapshots: AttachmentWithTextAndLink should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1159,6 +1166,8 @@ exports[`Story Snapshots: AttachmentWithTextAndLink should match snapshot 1`] = "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -1945,6 +1954,7 @@ exports[`Story Snapshots: AttachmentWithTextAndLinkLargeFont should match snapsh onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1962,6 +1972,8 @@ exports[`Story Snapshots: AttachmentWithTextAndLinkLargeFont should match snapsh "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -2748,6 +2760,7 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -2765,6 +2778,8 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -3198,6 +3213,7 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -3215,6 +3231,8 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -4103,6 +4121,7 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -4117,6 +4136,8 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -4561,6 +4582,7 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -4575,6 +4597,8 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -5022,6 +5046,7 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -5039,6 +5064,8 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -5472,6 +5499,7 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -5489,6 +5517,8 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -5935,6 +5965,7 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -5952,6 +5983,8 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -6385,6 +6418,7 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -6402,6 +6436,8 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -6848,6 +6884,7 @@ exports[`Story Snapshots: BlockQuote should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -6865,6 +6902,8 @@ exports[`Story Snapshots: BlockQuote should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -7330,6 +7369,7 @@ Testing block quote." onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -7347,6 +7387,8 @@ Testing block quote." "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -7878,6 +7920,7 @@ exports[`Story Snapshots: BlockQuoteLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -7895,6 +7938,8 @@ exports[`Story Snapshots: BlockQuoteLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -8360,6 +8405,7 @@ Testing block quote." onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -8377,6 +8423,8 @@ Testing block quote." "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -8908,6 +8956,7 @@ exports[`Story Snapshots: Broadcast should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -8925,6 +8974,8 @@ exports[`Story Snapshots: Broadcast should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -9494,6 +9545,7 @@ exports[`Story Snapshots: BroadcastLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -9511,6 +9563,8 @@ exports[`Story Snapshots: BroadcastLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -10080,6 +10134,7 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -10097,6 +10152,8 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -10685,6 +10742,7 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -10702,6 +10760,8 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -11529,6 +11589,7 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -11546,6 +11607,8 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -12134,6 +12197,7 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -12151,6 +12215,8 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -12978,6 +13044,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -12995,6 +13062,8 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -13585,6 +13654,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -13602,6 +13672,8 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -14585,6 +14657,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -14602,6 +14675,8 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -15192,6 +15267,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -15209,6 +15285,8 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -16192,6 +16270,7 @@ exports[`Story Snapshots: ColoredAttachments should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -16209,6 +16288,8 @@ exports[`Story Snapshots: ColoredAttachments should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -17464,6 +17545,7 @@ exports[`Story Snapshots: ColoredAttachmentsLargeFont should match snapshot 1`] onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -17481,6 +17563,8 @@ exports[`Story Snapshots: ColoredAttachmentsLargeFont should match snapshot 1`] "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -18736,6 +18820,7 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -18753,6 +18838,8 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -19568,6 +19655,7 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -19585,6 +19673,8 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -20698,6 +20788,7 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -20715,6 +20806,8 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -21530,6 +21623,7 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -21547,6 +21641,8 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -22660,6 +22756,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -22677,6 +22774,8 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -23178,6 +23277,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -23195,6 +23295,8 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -23887,6 +23989,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -23904,6 +24007,8 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -24414,6 +24519,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -24431,6 +24537,8 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -24877,6 +24985,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -24894,6 +25003,8 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -25395,6 +25506,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -25412,6 +25524,8 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -26104,6 +26218,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -26121,6 +26236,8 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -26631,6 +26748,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -26648,6 +26766,8 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -27094,6 +27214,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -27111,6 +27232,8 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -27670,6 +27793,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -27687,6 +27811,8 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -28248,6 +28374,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -28265,6 +28392,8 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -28826,6 +28955,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -28843,6 +28973,8 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -29417,6 +29549,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -29434,6 +29567,8 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -29993,6 +30128,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -30010,6 +30146,8 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -30571,6 +30709,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -30588,6 +30727,8 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -31149,6 +31290,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -31166,6 +31308,8 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -31740,6 +31884,7 @@ exports[`Story Snapshots: Edited should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -31757,6 +31902,8 @@ exports[`Story Snapshots: Edited should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -32487,6 +32634,7 @@ exports[`Story Snapshots: EditedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -32504,6 +32652,8 @@ exports[`Story Snapshots: EditedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -33235,6 +33385,7 @@ exports[`Story Snapshots: Editing should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -33252,6 +33403,8 @@ exports[`Story Snapshots: Editing should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -33699,6 +33852,7 @@ exports[`Story Snapshots: EditingLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -33716,6 +33870,8 @@ exports[`Story Snapshots: EditingLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -34162,6 +34318,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -34179,6 +34336,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -34611,6 +34770,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -34628,6 +34788,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -35038,6 +35200,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -35055,6 +35218,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -35236,6 +35401,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -35249,6 +35415,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -35280,6 +35448,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -35293,6 +35462,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -35324,6 +35495,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -35337,6 +35509,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -35575,6 +35749,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -35592,6 +35767,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -35773,6 +35950,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -35786,6 +35964,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -36024,6 +36204,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -36041,6 +36222,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -36244,6 +36427,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -36257,6 +36441,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -36495,6 +36681,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -36512,6 +36699,8 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -37024,6 +37213,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -37041,6 +37231,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -37473,6 +37665,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -37490,6 +37683,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -37900,6 +38095,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -37917,6 +38113,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -38098,6 +38296,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -38111,6 +38310,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -38142,6 +38343,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -38155,6 +38357,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -38186,6 +38390,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -38199,6 +38404,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -38437,6 +38644,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -38454,6 +38662,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -38635,6 +38845,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -38648,6 +38859,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -38886,6 +39099,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -38903,6 +39117,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -39106,6 +39322,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -39119,6 +39336,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -39357,6 +39576,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -39374,6 +39594,8 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -39886,6 +40108,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -39903,6 +40126,8 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -40616,6 +40841,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -40633,6 +40859,8 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -41072,6 +41300,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -41085,6 +41314,8 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -41678,6 +41909,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -41695,6 +41927,8 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -41968,6 +42202,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -41985,6 +42220,8 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -42456,6 +42693,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -42473,6 +42711,8 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -42859,6 +43099,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -42876,6 +43117,8 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -43415,6 +43658,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -43432,6 +43676,8 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -44218,6 +44464,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -44235,6 +44482,8 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -44948,6 +45197,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -44965,6 +45215,8 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -45404,6 +45656,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -45417,6 +45670,8 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -46010,6 +46265,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -46027,6 +46283,8 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -46300,6 +46558,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -46317,6 +46576,8 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -46788,6 +47049,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -46805,6 +47067,8 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -47191,6 +47455,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -47208,6 +47473,8 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -47747,6 +48014,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -47764,6 +48032,8 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -48469,6 +48739,7 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -48486,6 +48757,8 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -48911,6 +49184,7 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -48928,6 +49202,8 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -49366,6 +49642,7 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -49383,6 +49660,8 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -49808,6 +50087,7 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -49825,6 +50105,8 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -50344,6 +50626,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -50361,6 +50644,8 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -50939,6 +51224,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -50956,6 +51242,8 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -51534,6 +51822,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -51551,6 +51840,8 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -52129,6 +52420,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -52146,6 +52438,8 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -53272,6 +53566,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -53289,6 +53584,8 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -53867,6 +54164,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -53884,6 +54182,8 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -54462,6 +54762,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -54479,6 +54780,8 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -55057,6 +55360,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -55074,6 +55378,8 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -56187,6 +56493,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -56204,6 +56511,8 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -56956,6 +57265,7 @@ exports[`Story Snapshots: FullName should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -56973,6 +57283,8 @@ exports[`Story Snapshots: FullName should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -57419,6 +57731,7 @@ exports[`Story Snapshots: FullNameLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -57436,6 +57749,8 @@ exports[`Story Snapshots: FullNameLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -57882,6 +58197,7 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -57899,6 +58215,8 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -58332,6 +58650,7 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -58349,6 +58668,8 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -59206,6 +59527,7 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -59223,6 +59545,8 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -59669,6 +59993,7 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -59686,6 +60011,8 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -60119,6 +60446,7 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -60136,6 +60464,8 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -60993,6 +61323,7 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -61010,6 +61341,8 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -61461,6 +61794,7 @@ exports[`Story Snapshots: Ignored should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -61478,6 +61812,8 @@ exports[`Story Snapshots: Ignored should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -61776,6 +62112,7 @@ exports[`Story Snapshots: IgnoredLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -61793,6 +62130,8 @@ exports[`Story Snapshots: IgnoredLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -62086,6 +62425,7 @@ exports[`Story Snapshots: InlineKatex should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -62103,6 +62443,8 @@ exports[`Story Snapshots: InlineKatex should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -62535,6 +62877,7 @@ exports[`Story Snapshots: InlineKatexLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -62552,6 +62895,8 @@ exports[`Story Snapshots: InlineKatexLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -62976,6 +63321,7 @@ exports[`Story Snapshots: JitsiCall should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -62993,6 +63339,8 @@ exports[`Story Snapshots: JitsiCall should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -63501,6 +63849,7 @@ exports[`Story Snapshots: JitsiCallLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -63518,6 +63867,8 @@ exports[`Story Snapshots: JitsiCallLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -64034,6 +64385,7 @@ exports[`Story Snapshots: Katex should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -64051,6 +64403,8 @@ exports[`Story Snapshots: Katex should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -64445,6 +64799,7 @@ exports[`Story Snapshots: KatexArray should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -64462,6 +64817,8 @@ exports[`Story Snapshots: KatexArray should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -64856,6 +65213,7 @@ exports[`Story Snapshots: KatexArrayLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -64873,6 +65231,8 @@ exports[`Story Snapshots: KatexArrayLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -65267,6 +65627,7 @@ exports[`Story Snapshots: KatexLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -65284,6 +65645,8 @@ exports[`Story Snapshots: KatexLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -65680,6 +66043,7 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -65697,6 +66061,8 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -66330,6 +66696,7 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -66347,6 +66714,8 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -66928,6 +67297,7 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -66945,6 +67315,8 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -67701,6 +68073,7 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -67718,6 +68091,8 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -68351,6 +68726,7 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -68368,6 +68744,8 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -68949,6 +69327,7 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -68966,6 +69345,8 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -69720,6 +70101,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -69737,6 +70119,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -70170,6 +70554,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -70187,6 +70572,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -70656,6 +71043,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -70673,6 +71061,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -71141,6 +71531,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -71158,6 +71549,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -71544,6 +71937,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -71561,6 +71955,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -72067,6 +72463,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -72084,6 +72481,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -72548,6 +72947,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -72565,6 +72965,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -72981,6 +73383,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -72998,6 +73401,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -73555,6 +73960,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -73572,6 +73978,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -74129,6 +74537,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -74146,6 +74555,8 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -74797,6 +75208,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -74814,6 +75226,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -75247,6 +75661,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -75264,6 +75679,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -75733,6 +76150,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -75750,6 +76168,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -76218,6 +76638,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -76235,6 +76656,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -76621,6 +77044,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -76638,6 +77062,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -77144,6 +77570,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -77161,6 +77588,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -77625,6 +78054,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -77642,6 +78072,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -78058,6 +78490,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -78075,6 +78508,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -78632,6 +79067,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -78649,6 +79085,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -79206,6 +79644,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -79223,6 +79662,8 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -79874,6 +80315,7 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -79891,6 +80333,8 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -80521,6 +80965,7 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -80538,6 +80983,8 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -81160,6 +81607,7 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -81177,6 +81625,8 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -81807,6 +82257,7 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -81824,6 +82275,8 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -82446,6 +82899,7 @@ exports[`Story Snapshots: Message should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -82463,6 +82917,8 @@ exports[`Story Snapshots: Message should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -82842,6 +83298,7 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -82859,6 +83316,8 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -83602,6 +84061,7 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -83619,6 +84079,8 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -84410,6 +84872,7 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -84427,6 +84890,8 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -85224,6 +85689,7 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -85241,6 +85707,8 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -85929,6 +86397,7 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -85946,6 +86415,8 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -86844,6 +87315,7 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -86861,6 +87333,8 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -87568,6 +88042,7 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -87585,6 +88060,8 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -88305,6 +88782,7 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -88322,6 +88800,8 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -89029,6 +89509,7 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -89046,6 +89527,8 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -89766,6 +90249,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -89783,6 +90267,8 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -90397,6 +90883,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -90414,6 +90901,8 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -90750,6 +91239,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -90763,6 +91253,8 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -90967,6 +91459,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -90980,6 +91473,8 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -91350,6 +91845,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -91367,6 +91863,8 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -92158,6 +92656,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -92175,6 +92674,8 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -92591,6 +93092,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -92604,6 +93106,8 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -93040,6 +93544,7 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -93057,6 +93562,8 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -93658,6 +94165,7 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -93675,6 +94183,8 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -94383,6 +94893,7 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -94400,6 +94911,8 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -95174,6 +95687,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -95191,6 +95705,8 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -95792,6 +96308,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -95809,6 +96326,8 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -96517,6 +97036,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -96534,6 +97054,8 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -97295,6 +97817,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -97312,6 +97835,8 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -98109,6 +98634,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -98126,6 +98652,8 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -98814,6 +99342,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -98831,6 +99360,8 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -99729,6 +100260,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -99746,6 +100278,8 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -100360,6 +100894,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -100377,6 +100912,8 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -100713,6 +101250,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -100726,6 +101264,8 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -101035,6 +101575,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -101052,6 +101593,8 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -101843,6 +102386,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -101860,6 +102404,8 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -102276,6 +102822,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -102289,6 +102836,8 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -102726,6 +103275,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -102743,6 +103293,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -103662,6 +104214,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -103679,6 +104232,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -104091,6 +104646,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -104108,6 +104664,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -104520,6 +105078,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -104537,6 +105096,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -104949,6 +105510,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -104966,6 +105528,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -105378,6 +105942,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -105395,6 +105960,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -105807,6 +106374,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -105824,6 +106392,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -106236,6 +106806,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -106253,6 +106824,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -106665,6 +107238,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -106682,6 +107256,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -107094,6 +107670,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -107111,6 +107688,8 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -107409,6 +107988,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -107426,6 +108006,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -108345,6 +108927,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -108362,6 +108945,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -108774,6 +109359,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -108791,6 +109377,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -109203,6 +109791,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -109220,6 +109809,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -109632,6 +110223,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -109649,6 +110241,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -110061,6 +110655,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -110078,6 +110673,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -110490,6 +111087,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -110507,6 +111105,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -110919,6 +111519,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -110936,6 +111537,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -111348,6 +111951,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -111365,6 +111969,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -111777,6 +112383,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -111794,6 +112401,8 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -112091,6 +112700,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -112108,6 +112718,8 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -112460,6 +113072,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -112477,6 +113090,8 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -113144,6 +113759,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -113161,6 +113777,8 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -113513,6 +114131,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -113530,6 +114149,8 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -114197,6 +114818,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -114214,6 +114836,8 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -114566,6 +115190,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -114583,6 +115208,8 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -115263,6 +115890,7 @@ exports[`Story Snapshots: Pinned should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -115280,6 +115908,8 @@ exports[`Story Snapshots: Pinned should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -115998,6 +116628,7 @@ exports[`Story Snapshots: PinnedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -116015,6 +116646,8 @@ exports[`Story Snapshots: PinnedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -116733,6 +117366,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -116750,6 +117384,8 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -117155,6 +117791,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -117168,6 +117805,8 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -117739,6 +118378,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -117756,6 +118396,8 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -118055,6 +118697,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -118068,6 +118711,8 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -118183,6 +118828,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -118196,6 +118842,8 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -118311,6 +118959,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -118324,6 +118973,8 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -119226,6 +119877,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -119243,6 +119895,8 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -119648,6 +120302,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -119661,6 +120316,8 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -120232,6 +120889,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -120249,6 +120907,8 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -120548,6 +121208,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -120561,6 +121222,8 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -120676,6 +121339,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -120689,6 +121353,8 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -120804,6 +121470,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -120817,6 +121484,8 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` "width": 19, } } + symbolSize={null} + symbolWeight={null} transition={null} width={19} /> @@ -121720,6 +122389,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -121737,6 +122407,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -122533,6 +123205,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -122550,6 +123223,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -122839,6 +123514,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -122856,6 +123532,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -123145,6 +123823,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -123162,6 +123841,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -123460,6 +124141,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -123477,6 +124159,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -124273,6 +124957,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -124290,6 +124975,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -124579,6 +125266,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -124596,6 +125284,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -124885,6 +125575,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -124902,6 +125593,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -125327,6 +126020,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -125344,6 +126038,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -125633,6 +126329,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -125650,6 +126347,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -125939,6 +126638,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -125956,6 +126656,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -126245,6 +126947,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -126262,6 +126965,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -126687,6 +127392,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -126704,6 +127410,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -126993,6 +127701,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -127010,6 +127719,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -127299,6 +128010,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -127316,6 +128028,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -127605,6 +128319,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -127622,6 +128337,8 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -127919,6 +128636,7 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -127936,6 +128654,8 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -128445,6 +129165,7 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -128462,6 +129183,8 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -129209,6 +129932,7 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -129226,6 +129950,8 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -129735,6 +130461,7 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -129752,6 +130479,8 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -130499,6 +131228,7 @@ exports[`Story Snapshots: StaticAvatar should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -130516,6 +131246,8 @@ exports[`Story Snapshots: StaticAvatar should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -130962,6 +131694,7 @@ exports[`Story Snapshots: StaticAvatarLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -130979,6 +131712,8 @@ exports[`Story Snapshots: StaticAvatarLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -131351,6 +132086,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -131368,6 +132104,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -131591,6 +132329,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -131608,6 +132347,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -131831,6 +132572,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -131848,6 +132590,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -132074,6 +132818,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -132091,6 +132836,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -132314,6 +133061,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -132331,6 +133079,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -132554,6 +133304,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -132571,6 +133322,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -132794,6 +133547,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -132811,6 +133565,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -133034,6 +133790,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -133051,6 +133808,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -133274,6 +134033,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -133291,6 +134051,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -133514,6 +134276,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -133531,6 +134294,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -133754,6 +134519,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -133771,6 +134537,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -133994,6 +134762,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -134011,6 +134780,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -134234,6 +135005,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -134251,6 +135023,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -134474,6 +135248,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -134491,6 +135266,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -134714,6 +135491,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -134731,6 +135509,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -134954,6 +135734,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -134971,6 +135752,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -135194,6 +135977,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -135211,6 +135995,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -135434,6 +136220,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -135451,6 +136238,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -135674,6 +136463,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -135691,6 +136481,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -135914,6 +136706,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -135931,6 +136724,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -136154,6 +136949,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -136171,6 +136967,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -136394,6 +137192,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -136411,6 +137210,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -136634,6 +137435,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -136651,6 +137453,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -136874,6 +137678,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -136891,6 +137696,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -137114,6 +137921,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -137131,6 +137939,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -137354,6 +138164,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -137371,6 +138182,8 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` "width": 20, } } + symbolSize={null} + symbolWeight={null} transition={null} width={20} /> @@ -137582,6 +138395,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -137599,6 +138413,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -137822,6 +138638,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -137839,6 +138656,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -138062,6 +138881,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -138079,6 +138899,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -138305,6 +139127,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -138322,6 +139145,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -138545,6 +139370,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -138562,6 +139388,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -138785,6 +139613,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -138802,6 +139631,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -139025,6 +139856,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -139042,6 +139874,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -139265,6 +140099,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -139282,6 +140117,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -139505,6 +140342,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -139522,6 +140360,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -139745,6 +140585,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -139762,6 +140603,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -139985,6 +140828,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -140002,6 +140846,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -140225,6 +141071,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -140242,6 +141089,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -140465,6 +141314,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -140482,6 +141332,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -140705,6 +141557,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -140722,6 +141575,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -140945,6 +141800,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -140962,6 +141818,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -141185,6 +142043,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -141202,6 +142061,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -141425,6 +142286,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -141442,6 +142304,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -141665,6 +142529,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -141682,6 +142547,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -141905,6 +142772,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -141922,6 +142790,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -142145,6 +143015,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -142162,6 +143033,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -142385,6 +143258,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -142402,6 +143276,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -142625,6 +143501,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -142642,6 +143519,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -142865,6 +143744,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -142882,6 +143762,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -143105,6 +143987,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -143122,6 +144005,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -143345,6 +144230,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -143362,6 +144248,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -143585,6 +144473,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -143602,6 +144491,8 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` "width": 26, } } + symbolSize={null} + symbolWeight={null} transition={null} width={26} /> @@ -143879,6 +144770,7 @@ exports[`Story Snapshots: Temp should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -143896,6 +144788,8 @@ exports[`Story Snapshots: Temp should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -144338,6 +145232,7 @@ exports[`Story Snapshots: TempLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -144355,6 +145250,8 @@ exports[`Story Snapshots: TempLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -144805,6 +145702,7 @@ exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -144822,6 +145720,8 @@ exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -145154,6 +146054,7 @@ exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -145171,6 +146072,8 @@ exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` "width": 80, } } + symbolSize={null} + symbolWeight={null} transition={null} width={80} /> @@ -145488,6 +146391,7 @@ exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -145505,6 +146409,8 @@ exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -145837,6 +146743,7 @@ exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -145854,6 +146761,8 @@ exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] "width": 80, } } + symbolSize={null} + symbolWeight={null} transition={null} width={80} /> @@ -146171,6 +147080,7 @@ exports[`Story Snapshots: TimeFormat should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -146188,6 +147098,8 @@ exports[`Story Snapshots: TimeFormat should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -146634,6 +147546,7 @@ exports[`Story Snapshots: TimeFormatLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -146651,6 +147564,8 @@ exports[`Story Snapshots: TimeFormatLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -147097,6 +148012,7 @@ exports[`Story Snapshots: Translated should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -147114,6 +148030,8 @@ exports[`Story Snapshots: Translated should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -147595,6 +148513,7 @@ exports[`Story Snapshots: TranslatedLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -147612,6 +148531,8 @@ exports[`Story Snapshots: TranslatedLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -148093,6 +149014,7 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdown should match snapshot onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -148110,6 +149032,8 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdown should match snapshot "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -149390,6 +150314,7 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdownLargeFont should match onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -149407,6 +150332,8 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdownLargeFont should match "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -150687,6 +151614,7 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -150704,6 +151632,8 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -151279,6 +152209,7 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -151296,6 +152227,8 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -151521,6 +152454,7 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -151534,6 +152468,8 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -152140,6 +153076,7 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -152157,6 +153094,8 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -152628,6 +153567,7 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -152645,6 +153585,8 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -153066,6 +154008,7 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -153083,6 +154026,8 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -153554,6 +154499,7 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -153571,6 +154517,8 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -153992,6 +154940,7 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -154009,6 +154958,8 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -154584,6 +155535,7 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -154601,6 +155553,8 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -154826,6 +155780,7 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -154839,6 +155794,8 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -155445,6 +156402,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -155462,6 +156420,8 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -155814,6 +156774,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -155831,6 +156792,8 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -156498,6 +157461,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -156515,6 +157479,8 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -156867,6 +157833,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -156884,6 +157851,8 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -157551,6 +158520,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -157568,6 +158538,8 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -157920,6 +158892,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -157937,6 +158910,8 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -158617,6 +159592,7 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -158634,6 +159610,8 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -159089,6 +160067,7 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -159106,6 +160085,8 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -159574,6 +160555,7 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -159591,6 +160573,8 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -160046,6 +161030,7 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -160063,6 +161048,8 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -160531,6 +161518,7 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -160548,6 +161536,8 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -160783,6 +161773,7 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -160796,6 +161787,8 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -162650,6 +163643,7 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -162663,6 +163657,8 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -163387,6 +164383,7 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -163404,6 +164401,8 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -163639,6 +164638,7 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -163652,6 +164652,8 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -165482,6 +166484,7 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -165495,6 +166498,8 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -166219,6 +167224,7 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -166236,6 +167242,8 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -166612,6 +167620,7 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -166625,6 +167634,8 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -167013,6 +168024,7 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -167026,6 +168038,8 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -167267,6 +168281,7 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -167280,6 +168295,8 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -167540,6 +168557,7 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -167557,6 +168575,8 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -167933,6 +168953,7 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -167946,6 +168967,8 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -168334,6 +169357,7 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -168347,6 +169371,8 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -168607,6 +169633,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -168624,6 +169651,8 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -168859,6 +169888,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -168872,6 +169902,8 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -169273,6 +170305,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -169290,6 +170323,8 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -169591,6 +170626,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -169604,6 +170640,8 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -169865,6 +170903,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -169878,6 +170917,8 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -170440,6 +171481,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -170457,6 +171499,8 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -171220,6 +172264,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -171237,6 +172282,8 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -171472,6 +172519,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -171485,6 +172533,8 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -171886,6 +172936,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -171903,6 +172954,8 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -172892,6 +173945,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -172909,6 +173963,8 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -173672,6 +174728,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -173689,6 +174746,8 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -173924,6 +174983,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -173937,6 +174997,8 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -174338,6 +175400,7 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -174355,6 +175418,8 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -174590,6 +175655,7 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -174603,6 +175669,8 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -175304,6 +176372,7 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -175321,6 +176390,8 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -176029,6 +177100,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -176046,6 +177118,8 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -176281,6 +177355,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -176294,6 +177369,8 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` "width": 15, } } + symbolSize={null} + symbolWeight={null} transition={null} width={15} /> @@ -176653,6 +177730,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -176670,6 +177748,8 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> @@ -177172,6 +178252,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -177189,6 +178270,8 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` "width": 46.800000000000004, } } + symbolSize={null} + symbolWeight={null} transition={null} width={46.800000000000004} /> diff --git a/app/containers/message/components/stories/__tests__/__snapshots__/leaves.test.tsx.snap b/app/containers/message/components/stories/__tests__/__snapshots__/leaves.test.tsx.snap index 4dbc206d95b..7fbe6b02650 100644 --- a/app/containers/message/components/stories/__tests__/__snapshots__/leaves.test.tsx.snap +++ b/app/containers/message/components/stories/__tests__/__snapshots__/leaves.test.tsx.snap @@ -706,6 +706,7 @@ exports[`Story Snapshots: EmojiCustom should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -719,6 +720,8 @@ exports[`Story Snapshots: EmojiCustom should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -1337,6 +1340,7 @@ exports[`Story Snapshots: MessageAvatar should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1354,6 +1358,8 @@ exports[`Story Snapshots: MessageAvatar should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> diff --git a/app/lib/methods/helpers/theme.ts b/app/lib/methods/helpers/theme.ts index cf41f3740ca..42f045e6107 100644 --- a/app/lib/methods/helpers/theme.ts +++ b/app/lib/methods/helpers/theme.ts @@ -22,7 +22,7 @@ export const initialTheme = (): IThemePreference => { export const defaultTheme = (): TThemeMode => { const systemTheme = Appearance.getColorScheme(); - if (systemTheme) { + if (systemTheme === 'light' || systemTheme === 'dark') { return systemTheme; } return 'light'; diff --git a/app/stacks/MasterDetailStack/ModalContainer.tsx b/app/stacks/MasterDetailStack/ModalContainer.tsx index 5e1759d4231..691d4e08bd4 100644 --- a/app/stacks/MasterDetailStack/ModalContainer.tsx +++ b/app/stacks/MasterDetailStack/ModalContainer.tsx @@ -25,7 +25,7 @@ const styles = StyleSheet.create({ justifyContent: 'center' }, backdrop: { - ...StyleSheet.absoluteFillObject + ...StyleSheet.absoluteFill } }); diff --git a/app/views/CallView/__snapshots__/index.test.tsx.snap b/app/views/CallView/__snapshots__/index.test.tsx.snap index a29f9ff832c..09ca015dd9a 100644 --- a/app/views/CallView/__snapshots__/index.test.tsx.snap +++ b/app/views/CallView/__snapshots__/index.test.tsx.snap @@ -122,6 +122,7 @@ exports[`Story Snapshots: ConnectedCall should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -139,6 +140,8 @@ exports[`Story Snapshots: ConnectedCall should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -1017,6 +1020,7 @@ exports[`Story Snapshots: ConnectingCall should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1034,6 +1038,8 @@ exports[`Story Snapshots: ConnectingCall should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -1918,6 +1924,7 @@ exports[`Story Snapshots: MutedAndOnHold should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1935,6 +1942,8 @@ exports[`Story Snapshots: MutedAndOnHold should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -2813,6 +2822,7 @@ exports[`Story Snapshots: MutedCall should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -2830,6 +2840,8 @@ exports[`Story Snapshots: MutedCall should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -3708,6 +3720,7 @@ exports[`Story Snapshots: OnHoldCall should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -3725,6 +3738,8 @@ exports[`Story Snapshots: OnHoldCall should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -4603,6 +4618,7 @@ exports[`Story Snapshots: SpeakerOn should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -4620,6 +4636,8 @@ exports[`Story Snapshots: SpeakerOn should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -5481,6 +5499,7 @@ exports[`Story Snapshots: TabletConnectedCall should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -5498,6 +5517,8 @@ exports[`Story Snapshots: TabletConnectedCall should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -6348,6 +6369,7 @@ exports[`Story Snapshots: TabletConnectingCall should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -6365,6 +6387,8 @@ exports[`Story Snapshots: TabletConnectingCall should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -7221,6 +7245,7 @@ exports[`Story Snapshots: TabletMutedAndOnHold should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -7238,6 +7263,8 @@ exports[`Story Snapshots: TabletMutedAndOnHold should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -8088,6 +8115,7 @@ exports[`Story Snapshots: TabletMutedCall should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -8105,6 +8133,8 @@ exports[`Story Snapshots: TabletMutedCall should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -8955,6 +8985,7 @@ exports[`Story Snapshots: TabletOnHoldCall should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -8972,6 +9003,8 @@ exports[`Story Snapshots: TabletOnHoldCall should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -9822,6 +9855,7 @@ exports[`Story Snapshots: TabletSpeakerOn should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -9839,6 +9873,8 @@ exports[`Story Snapshots: TabletSpeakerOn should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -10689,6 +10725,7 @@ exports[`Story Snapshots: TabletWithDialpad should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -10706,6 +10743,8 @@ exports[`Story Snapshots: TabletWithDialpad should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -11573,6 +11612,7 @@ exports[`Story Snapshots: WithDialpad should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -11590,6 +11630,8 @@ exports[`Story Snapshots: WithDialpad should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> diff --git a/app/views/CallView/components/Dialpad/__snapshots__/Dialpad.test.tsx.snap b/app/views/CallView/components/Dialpad/__snapshots__/Dialpad.test.tsx.snap index 25a50d663a9..414681cf33c 100644 --- a/app/views/CallView/components/Dialpad/__snapshots__/Dialpad.test.tsx.snap +++ b/app/views/CallView/components/Dialpad/__snapshots__/Dialpad.test.tsx.snap @@ -26,13 +26,13 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` } > @@ -252,6 +255,7 @@ exports[`Story Snapshots: UsernameOnly should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -269,6 +273,8 @@ exports[`Story Snapshots: UsernameOnly should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> diff --git a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap index 1dedcc55ec4..2260fa4e792 100644 --- a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap +++ b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap @@ -139,6 +139,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -156,6 +157,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -537,6 +540,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -554,6 +558,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -935,6 +941,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -952,6 +959,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -1335,6 +1344,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1352,6 +1362,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -1733,6 +1745,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1750,6 +1763,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -2151,6 +2166,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -2168,6 +2184,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -2536,6 +2554,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -2553,6 +2572,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -2921,6 +2942,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -2938,6 +2960,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> diff --git a/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap b/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap index 530b9e1be5f..6b68f09c5e0 100644 --- a/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap +++ b/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap @@ -95,6 +95,7 @@ exports[`NewMessageView Item should match snapshot when hasMediaCallPermission i onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -112,6 +113,8 @@ exports[`NewMessageView Item should match snapshot when hasMediaCallPermission i "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -259,6 +262,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -276,6 +280,8 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -485,6 +491,7 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -502,6 +509,8 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> @@ -711,6 +720,7 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -728,6 +738,8 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` "width": 30, } } + symbolSize={null} + symbolWeight={null} transition={null} width={30} /> diff --git a/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap b/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap index de1d3b1e0a2..de5a8f4f41f 100644 --- a/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap +++ b/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap @@ -244,6 +244,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -258,6 +259,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -565,6 +568,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -579,6 +583,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -886,6 +892,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -900,6 +907,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -1212,6 +1221,7 @@ exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -1226,6 +1236,8 @@ exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -1533,6 +1545,7 @@ exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -1547,6 +1560,8 @@ exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -1859,6 +1874,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -1873,6 +1889,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -2180,6 +2198,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -2194,6 +2213,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> @@ -2501,6 +2522,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` onLoadStart={[Function]} onProgress={[Function]} placeholder={[]} + sfEffect={null} source={ [ { @@ -2515,6 +2537,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 44, } } + symbolSize={null} + symbolWeight={null} transition={null} width={44} /> diff --git a/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap b/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap index 510bdb3df62..b6b2cbca761 100644 --- a/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap +++ b/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap @@ -46,6 +46,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -63,6 +64,8 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> @@ -116,6 +119,7 @@ exports[`Story Snapshots: WithEdit should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -133,6 +137,8 @@ exports[`Story Snapshots: WithEdit should match snapshot 1`] = ` "width": 120, } } + symbolSize={null} + symbolWeight={null} transition={null} width={120} /> diff --git a/app/views/RoomView/List/hooks/useFloatingDate.test.ts b/app/views/RoomView/List/hooks/useFloatingDate.test.ts index 2f30c9937d1..dcecd388afd 100644 --- a/app/views/RoomView/List/hooks/useFloatingDate.test.ts +++ b/app/views/RoomView/List/hooks/useFloatingDate.test.ts @@ -118,7 +118,16 @@ describe('useFloatingDate', () => { const fadeOut = { type: 'timing', toValue: 0, duration: 300 }; const animationsOf = (result: any) => (result.current.opacity as any).animations; - const settle = (ms: number) => act(() => jest.advanceTimersByTime(ms)); + const scroll = (callback: () => void) => + act(() => { + callback(); + jest.runAllTicks(); + }); + const settle = (ms: number) => + act(() => { + jest.advanceTimersByTime(ms); + jest.runAllTicks(); + }); it('keeps viewabilityConfigCallbackPairs identity stable across updates', () => { const { result, rerender } = renderHook(() => useFloatingDate()); @@ -140,7 +149,7 @@ describe('useFloatingDate', () => { it('fades in when the user starts dragging', () => { const { result } = renderHook(() => useFloatingDate()); - act(() => result.current.scrollEvents.onBeginDrag()); + scroll(() => result.current.scrollEvents.onBeginDrag()); expect(animationsOf(result)).toEqual([fadeIn]); expect(result.current.opacity.value).toBe(1); }); @@ -150,10 +159,10 @@ describe('useFloatingDate', () => { // crept towards 1 without arriving. it('issues the fade in once across a momentum event storm', () => { const { result } = renderHook(() => useFloatingDate()); - act(() => result.current.scrollEvents.onBeginDrag()); + scroll(() => result.current.scrollEvents.onBeginDrag()); for (let i = 0; i < 20; i++) { - act(() => result.current.scrollEvents.onMomentumEnd()); - act(() => result.current.scrollEvents.onMomentumBegin()); + scroll(() => result.current.scrollEvents.onMomentumEnd()); + scroll(() => result.current.scrollEvents.onMomentumBegin()); } expect(animationsOf(result)).toEqual([fadeIn]); expect(result.current.opacity.value).toBe(1); @@ -161,8 +170,8 @@ describe('useFloatingDate', () => { it('holds at full opacity until the settle delay elapses', () => { const { result } = renderHook(() => useFloatingDate()); - act(() => result.current.scrollEvents.onBeginDrag()); - act(() => result.current.scrollEvents.onEndDrag()); + scroll(() => result.current.scrollEvents.onBeginDrag()); + scroll(() => result.current.scrollEvents.onEndDrag()); settle(999); expect(animationsOf(result)).toEqual([fadeIn]); settle(1); @@ -172,11 +181,11 @@ describe('useFloatingDate', () => { it('re-arms the settle delay so the fling that follows a drag keeps the pill up', () => { const { result } = renderHook(() => useFloatingDate()); - act(() => result.current.scrollEvents.onBeginDrag()); - act(() => result.current.scrollEvents.onEndDrag()); + scroll(() => result.current.scrollEvents.onBeginDrag()); + scroll(() => result.current.scrollEvents.onEndDrag()); settle(500); - act(() => result.current.scrollEvents.onMomentumBegin()); - act(() => result.current.scrollEvents.onMomentumEnd()); + scroll(() => result.current.scrollEvents.onMomentumBegin()); + scroll(() => result.current.scrollEvents.onMomentumEnd()); settle(999); expect(animationsOf(result)).toEqual([fadeIn]); settle(1); @@ -185,10 +194,10 @@ describe('useFloatingDate', () => { it('cancels the pending fade out when scrolling resumes', () => { const { result } = renderHook(() => useFloatingDate()); - act(() => result.current.scrollEvents.onBeginDrag()); - act(() => result.current.scrollEvents.onEndDrag()); + scroll(() => result.current.scrollEvents.onBeginDrag()); + scroll(() => result.current.scrollEvents.onEndDrag()); settle(500); - act(() => result.current.scrollEvents.onBeginDrag()); + scroll(() => result.current.scrollEvents.onBeginDrag()); settle(5000); expect(animationsOf(result)).toEqual([fadeIn]); expect(result.current.opacity.value).toBe(1); @@ -196,18 +205,18 @@ describe('useFloatingDate', () => { it('fades in again after a completed fade out', () => { const { result } = renderHook(() => useFloatingDate()); - act(() => result.current.scrollEvents.onBeginDrag()); - act(() => result.current.scrollEvents.onEndDrag()); + scroll(() => result.current.scrollEvents.onBeginDrag()); + scroll(() => result.current.scrollEvents.onEndDrag()); settle(HIDE_DELAY_MS); - act(() => result.current.scrollEvents.onBeginDrag()); + scroll(() => result.current.scrollEvents.onBeginDrag()); expect(animationsOf(result)).toEqual([fadeIn, fadeOut, fadeIn]); expect(result.current.opacity.value).toBe(1); }); it('drops the pending fade out on unmount', () => { const { result, unmount } = renderHook(() => useFloatingDate()); - act(() => result.current.scrollEvents.onBeginDrag()); - act(() => result.current.scrollEvents.onEndDrag()); + scroll(() => result.current.scrollEvents.onBeginDrag()); + scroll(() => result.current.scrollEvents.onEndDrag()); unmount(); settle(5000); expect(animationsOf(result)).toEqual([fadeIn]); diff --git a/app/views/ShareView/Preview.tsx b/app/views/ShareView/Preview.tsx index 4be35d248e2..370f0bc31ef 100644 --- a/app/views/ShareView/Preview.tsx +++ b/app/views/ShareView/Preview.tsx @@ -37,7 +37,7 @@ const VideoPreview = memo(({ uri, width, height }: { uri: string; width?: number style={{ width: width || '100%', height: height || '100%' }} contentFit='contain' nativeControls - allowsFullscreen + fullscreenOptions={{ enable: true }} /> ); }); diff --git a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap index 367af2db8e6..9366619b1ec 100644 --- a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap +++ b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap @@ -139,6 +139,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -156,6 +157,8 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -678,6 +681,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -695,6 +699,8 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -1217,6 +1223,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1234,6 +1241,8 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -1743,6 +1752,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -1760,6 +1770,8 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -2302,6 +2314,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -2319,6 +2332,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -2825,6 +2840,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -2842,6 +2858,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -3348,6 +3366,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -3365,6 +3384,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -3871,6 +3892,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -3888,6 +3910,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -4394,6 +4418,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -4411,6 +4436,8 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -4937,6 +4964,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -4954,6 +4982,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -5463,6 +5493,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -5480,6 +5511,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> @@ -5989,6 +6022,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` onProgress={[Function]} placeholder={[]} priority="high" + sfEffect={null} source={ [ { @@ -6006,6 +6040,8 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` "width": 36, } } + symbolSize={null} + symbolWeight={null} transition={null} width={36} /> diff --git a/jest.setup.js b/jest.setup.js index 9ebbf508fb7..c82820c93b7 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -4,6 +4,13 @@ import { Image } from 'expo-image'; jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage); +jest.mock('./app/lib/methods/handleMediaDownload', () => ({ + ...jest.requireActual('./app/lib/methods/handleMediaDownload'), + getMediaCache: jest.fn(() => Promise.resolve({ exists: false })), + downloadMediaFile: jest.fn(() => Promise.resolve('')), + isDownloadActive: jest.fn(() => false) +})); + jest.mock('react-native-safe-area-context', () => { const inset = { top: 0, right: 0, bottom: 0, left: 0 }; return { From 998957d7ec4300ba8df6bce759ca7090ec06ccca Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 16 Sep 2026 09:50:05 -0300 Subject: [PATCH 010/120] fix(ios): support React Native 0.86 and Expo 57 (#7696) * fix(ios): support React Native 0.86 and Expo 57 * test: fix jest failures under Node 22 Resolve Expo's lazy fetch global in jest setup so suites that fully mock react-native no longer crash when jest enumerates globals at teardown. Restore real timers only after Testing Library unmounts, so immediates scheduled with fake timers are cancelled with the same clock. * fix(android): adopt Expo 57 autolinking plugin and drop jcenter from patched libraries Expo 57 removed scripts/autolinking.gradle in favour of the expo-autolinking-settings and expo-root-project Gradle plugins. Gradle 9 removed jcenter(), which @react-native-cookies/cookies and react-native-math-view still declared. Bump the iOS Pods cache key so runners stop restoring Local Podspecs generated for the previous React Native version. * fix(android): build with React Native 0.86 codegen and Expo 57 prebuilt modules Bump @react-native/codegen, babel-preset and typescript-config to 0.86.3 so libraries stop resolving the stale 0.80 codegen. Build the four patched Expo packages from source, since Expo 57 ships prebuilt Android artifacts that ignore patch-package changes. Patch camera-roll to drop string literal unions from its spec, keyboard-controller for the non-null onConfigurationChanged signature, math-view to drop the removed UIManagerModuleListener import, and expo-font for the nullable loadedFonts field. Declare androidx.localbroadcastmanager explicitly and resolve the WRITE_EXTERNAL_STORAGE maxSdkVersion manifest conflict. * fix: drop undefined request headers rejected by Expo fetch Expo 57 installs its native fetch as the global and rejects header entries whose value is undefined. getServerInfo sent X-Auth-Token and X-User-Id as undefined before login, so every deep link login fell back to the Add workspace screen and all E2E flows failed. * test(e2e): wait for LoginView transition before keyboard navigation * fix(login): keep focus on the password field when submitting with a hardware keyboard * fix(ios): compile the release bundle with the hermesc matching the bundled Hermes runtime * fix(ios): expose room header testIDs to accessibility tree An accessible wrapper around the room header hides its children from XCUITest on React Native 0.86, so Maestro could not find the header title or the header button. Move the accessibility label and role to the pressable header, which already exposes its children. * fix(ios): expose rooms list header testIDs to accessibility tree Same cause as the room header: an accessible wrapper inside the native stack header hides its children from XCUITest, so Maestro could not find the servers list button. * fix(ios): bump react-native-linear-gradient to 2.8.3 to stop zero-size layer crash on mention autocomplete * fix(ios): address upgrade review feedback * fix(ios): exclude Android keyboard flow change --- .github/actions/build-ios/action.yml | 4 +- .github/actions/upload-ios/action.yml | 4 +- CLAUDE.md | 2 +- README.md | 2 +- app/containers/RoomHeader/RoomHeader.tsx | 13 +- .../__snapshots__/RoomHeader.test.tsx.snap | 183 ++- .../methods/helpers/__tests__/fetch.test.ts | 29 + app/lib/methods/helpers/fetch.ts | 11 +- app/views/LoginView/UserForm.tsx | 3 +- app/views/RoomsListView/components/Header.tsx | 8 +- app/views/ShareView/ShareView.test.tsx | 8 +- ios/AppDelegate.swift | 7 +- ios/Podfile | 15 +- ios/Podfile.lock | 1169 +++++++++++------ ios/RocketChat.xcodeproj/project.pbxproj | 62 +- jest.setup.js | 3 + package.json | 8 +- ...ative-camera-roll+camera-roll+7.10.2.patch | 26 + pnpm-lock.yaml | 192 +-- 19 files changed, 1026 insertions(+), 723 deletions(-) create mode 100644 app/lib/methods/helpers/__tests__/fetch.test.ts create mode 100644 patches/@react-native-camera-roll+camera-roll+7.10.2.patch diff --git a/.github/actions/build-ios/action.yml b/.github/actions/build-ios/action.yml index d9e6e064f42..01728c71c7b 100644 --- a/.github/actions/build-ios/action.yml +++ b/.github/actions/build-ios/action.yml @@ -53,9 +53,9 @@ runs: path: | ~/.pods ios/Pods - key: pods-v1-{{ checksum "ios/Podfile.lock" }} + key: pods-v2-${{ hashFiles('ios/Podfile.lock') }} restore-keys: | - pods-v1- + pods-v2- - name: pod install run: | diff --git a/.github/actions/upload-ios/action.yml b/.github/actions/upload-ios/action.yml index ef727a14c4d..bfb05673825 100644 --- a/.github/actions/upload-ios/action.yml +++ b/.github/actions/upload-ios/action.yml @@ -57,9 +57,9 @@ runs: path: | ~/.pods ios/Pods - key: pods-v1-{{ checksum "ios/Podfile.lock" }} + key: pods-v2-${{ hashFiles('ios/Podfile.lock') }} restore-keys: | - pods-v1- + pods-v2- - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 diff --git a/CLAUDE.md b/CLAUDE.md index 048929f381f..6391983df5b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -Rocket.Chat React Native mobile client. Single-package React Native app (not a monorepo) using pnpm. Supports iOS 13.4+ and Android 6.0+. +Rocket.Chat React Native mobile client. Single-package React Native app (not a monorepo) using pnpm. Supports iOS 16.4+ and Android 6.0+. Read CONTEXT.md. diff --git a/README.md b/README.md index ed7fb227d16..3ce08b95d7f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Rocket.Chat Mobile - **Supported server versions:** 0.70.0+ -- **Supported iOS versions**: 13.4+ +- **Supported iOS versions**: 16.4+ - **Supported Android versions**: 6.0+ ## Download diff --git a/app/containers/RoomHeader/RoomHeader.tsx b/app/containers/RoomHeader/RoomHeader.tsx index 9b702283284..06b833796c9 100644 --- a/app/containers/RoomHeader/RoomHeader.tsx +++ b/app/containers/RoomHeader/RoomHeader.tsx @@ -233,11 +233,14 @@ const Header = ({ enableA11yFocus={autoFocusHeader && !disabled} focusable={!disabled} canBeFocused={!disabled} - style={[styles.container, { opacity: disabled ? 0.5 : 1, height: 36.9 * fontScale }]} - accessible - accessibilityLabel={accessibilityLabel} - accessibilityRole='header'> - + style={[styles.container, { opacity: disabled ? 0.5 : 1, height: 36.9 * fontScale }]}> + {tmid ? null : ( { + const globalFetch = jest.fn().mockResolvedValue({} as Response); + + beforeEach(() => { + globalFetch.mockClear(); + globalThis.fetch = globalFetch; + }); + + it('drops headers whose value is undefined or null', async () => { + await fetchWithHeaders('https://example.com/api/info', { + method: 'GET', + headers: { 'Content-Type': 'application/json', 'X-Auth-Token': undefined, 'X-User-Id': null as unknown as undefined } + }); + + const [, options] = globalFetch.mock.calls[0]; + expect(options.headers).toEqual({ 'Content-Type': 'application/json', ...headers }); + }); + + it('keeps defined caller headers alongside the default ones', async () => { + await fetchWithHeaders('https://example.com/api/info', { + headers: { 'X-Auth-Token': 'token', 'X-User-Id': 'id' } + }); + + const [, options] = globalFetch.mock.calls[0]; + expect(options.headers).toEqual({ 'X-Auth-Token': 'token', 'X-User-Id': 'id', ...headers }); + }); +}); diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index d15a050e8fc..33e68813f70 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -39,11 +39,14 @@ export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; RocketChatSettings.customHeaders = headers; +const withoutEmptyValues = (requestHeaders: CustomHeaders): Record => + Object.fromEntries(Object.entries(requestHeaders).filter((entry): entry is [string, string] => typeof entry[1] === 'string')); + export default (url: string, options: IOptions = {}): Promise => { - let customOptions = { ...options, headers: RocketChatSettings.customHeaders }; - if (options && options.headers) { - customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } }; - } + const customOptions = { + ...options, + headers: withoutEmptyValues({ ...options.headers, ...RocketChatSettings.customHeaders }) + }; // TODO: Check if this really works and if anyone else has complained about this problem. // if (RocketChat.controller) { // // @ts-ignore diff --git a/app/views/LoginView/UserForm.tsx b/app/views/LoginView/UserForm.tsx index 90eba5b34ce..f979dfc87f2 100644 --- a/app/views/LoginView/UserForm.tsx +++ b/app/views/LoginView/UserForm.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { Keyboard, Text, View, Alert } from 'react-native'; +import { Text, View, Alert, Keyboard } from 'react-native'; import { useDispatch } from 'react-redux'; import { type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; @@ -111,6 +111,7 @@ const UserForm = () => { placeholder={Accounts_EmailOrUsernamePlaceholder} keyboardType='email-address' returnKeyType='next' + submitBehavior='submit' onSubmitEditing={() => setFocus('password')} testID='login-view-email' textContentType='username' diff --git a/app/views/RoomsListView/components/Header.tsx b/app/views/RoomsListView/components/Header.tsx index a54635c4bf6..288e581f621 100644 --- a/app/views/RoomsListView/components/Header.tsx +++ b/app/views/RoomsListView/components/Header.tsx @@ -64,8 +64,12 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string) subtitle = server?.replace(/(^\w+:|^)\/\//, ''); } return ( - - + + {serverName} diff --git a/app/views/ShareView/ShareView.test.tsx b/app/views/ShareView/ShareView.test.tsx index 0582229fe97..854aee35e4e 100644 --- a/app/views/ShareView/ShareView.test.tsx +++ b/app/views/ShareView/ShareView.test.tsx @@ -154,11 +154,11 @@ const makeInstance = ({ return shareView; }; -describe('ShareView', () => { - afterEach(() => { - jest.useRealTimers(); - }); +afterEach(() => { + jest.useRealTimers(); +}); +describe('ShareView', () => { beforeEach(() => { jest.clearAllMocks(); mockGetSubscriptionByRoomId.mockResolvedValue({ diff --git a/ios/AppDelegate.swift b/ios/AppDelegate.swift index 2699b2c7fba..636a2acedf2 100644 --- a/ios/AppDelegate.swift +++ b/ios/AppDelegate.swift @@ -1,4 +1,4 @@ -import Expo +public import Expo import React import ReactAppDependencyProvider import Firebase @@ -38,12 +38,11 @@ public class AppDelegate: ExpoAppDelegate { } let delegate = ReactNativeDelegate() - let factory = RCTReactNativeFactory(delegate: delegate) + let factory = ExpoReactNativeFactory(delegate: delegate) delegate.dependencyProvider = RCTAppDependencyProvider() reactNativeDelegate = delegate reactNativeFactory = factory - bindReactNativeFactory(factory) #if os(iOS) || os(tvOS) window = UIWindow(frame: UIScreen.main.bounds) @@ -89,7 +88,7 @@ public class AppDelegate: ExpoAppDelegate { } } -class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { +class ReactNativeDelegate: ExpoReactNativeFactoryDelegate { override func sourceURL(for bridge: RCTBridge) -> URL? { self.bundleURL() } diff --git a/ios/Podfile b/ios/Podfile index 4b0ef129eca..028fecd8aba 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,7 +1,11 @@ require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") -platform :ios, '15.1' +ENV['RCT_USE_PREBUILT_RNCORE'] = '0' +ENV['RCT_USE_RN_DEP'] = '0' +ENV['RCT_HERMES_V1_ENABLED'] = '0' + +platform :ios, '16.4' install! 'cocoapods', :deterministic_uuids => false # Pin the generated workspace name so `react-native run-ios` selects it and @@ -78,6 +82,15 @@ post_install do |installer| end end + if ENV['RCT_HERMES_V1_ENABLED'] == '0' + installer.aggregate_targets.each do |aggregate_target| + aggregate_target.xcconfigs.each do |config_name, xcconfig| + xcconfig.attributes['HERMES_CLI_PATH'] = '$(PODS_ROOT)/hermes-engine/destroot/bin/hermesc' + xcconfig.save_as(aggregate_target.xcconfig_path(config_name)) + end + end + end + bitcode_strip_path = `xcrun --find bitcode_strip`.chop! def strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path) framework_path = File.join(Dir.pwd, framework_relative_path) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 100f970b291..07bd8c959b7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -28,19 +28,18 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - BVLinearGradient (2.6.2): + - BVLinearGradient (2.8.3): - React-Core - DoubleConversion (1.1.6) - - EXApplication (7.0.8): + - EXApplication (57.0.3): - ExpoModulesCore - - EXConstants (18.0.12): + - EXConstants (57.0.18): - ExpoModulesCore - - EXNotifications (0.32.15): - - ExpoModulesCore - - Expo (54.0.30): + - Expo (57.0.23): - boost - DoubleConversion - ExpoModulesCore + - ExpoModulesJSI - fast_float - fmt - glog @@ -68,40 +67,47 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - ExpoAppleAuthentication (8.0.8): + - ExpoAppleAuthentication (57.0.2): - ExpoModulesCore - - ExpoAsset (12.0.12): + - ExpoAsset (57.0.17): - ExpoModulesCore - - ExpoAudio (0.4.9): + - ExpoAudio (57.0.5): - ExpoModulesCore - - ExpoCamera (17.0.10): + - ExpoCamera (57.0.5): - ExpoModulesCore + - ExpoCameraBarcodeScanning (57.0.5): + - ExpoCamera - ZXingObjC/OneD - ZXingObjC/PDF417 - - ExpoDevice (8.0.10): + - ExpoDevice (57.0.2): + - ExpoModulesCore + - ExpoDocumentPicker (57.0.2): - ExpoModulesCore - - ExpoDocumentPicker (14.0.8): + - ExpoDomWebView (57.0.1): - ExpoModulesCore - - ExpoFileSystem (19.0.21): + - ExpoFileSystem (57.0.7): - ExpoModulesCore - - ExpoFont (14.0.10): + - ExpoFont (57.0.4): - ExpoModulesCore - - ExpoHaptics (15.0.8): + - ExpoHaptics (57.0.3): - ExpoModulesCore - - ExpoImage (3.0.11): + - ExpoImage (57.0.5): - ExpoModulesCore - libavif/libdav1d - SDWebImage (~> 5.21.0) - SDWebImageAVIFCoder (~> 0.11.0) - SDWebImageSVGCoder (~> 1.7.0) - SDWebImageWebPCoder (~> 0.14.6) - - ExpoKeepAwake (15.0.8): + - ExpoKeepAwake (57.0.2): - ExpoModulesCore - - ExpoLocalAuthentication (17.0.8): + - ExpoLocalAuthentication (57.0.3): - ExpoModulesCore - - ExpoModulesCore (3.0.29): + - ExpoLogBox (57.0.4): + - React-Core + - ExpoModulesCore (57.0.18): - boost - DoubleConversion + - ExpoModulesJSI - fast_float - fmt - glog @@ -128,18 +134,31 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga + - ExpoModulesJSI (57.1.0): + - React-Core + - ReactCommon + - ExpoModulesWorklets (57.0.18): + - ExpoModulesCore + - ExpoModulesJSI + - ExpoModulesWorkletsAdapter (57.0.18): + - ExpoModulesCore + - ExpoModulesJSI + - ExpoModulesWorklets + - RNWorklets + - ExpoNotifications (57.0.19): + - ExpoModulesCore - ExpoQuickLook (0.4.0): - ExpoModulesCore - - ExpoSystemUI (6.0.9): + - ExpoSystemUI (57.0.4): - ExpoModulesCore - - ExpoVideo (3.0.16): + - ExpoVideo (57.0.4): - ExpoModulesCore - - ExpoVideoThumbnails (10.0.8): + - ExpoVideoThumbnails (57.0.2): - ExpoModulesCore - - ExpoWebBrowser (15.0.10): + - ExpoWebBrowser (57.0.3): - ExpoModulesCore - fast_float (8.0.0) - - FBLazyVector (0.81.5) + - FBLazyVector (0.86.3) - Firebase/AnalyticsWithoutAdIdSupport (11.11.0): - Firebase/CoreOnly - FirebaseAnalytics/WithoutAdIdSupport (~> 11.11.0) @@ -189,7 +208,7 @@ PODS: - GoogleUtilities/UserDefaults (~> 8.0) - nanopb (~> 3.30910.0) - PromisesSwift (~> 2.1) - - fmt (11.0.2) + - fmt (12.1.0) - glog (0.3.5) - GoogleAppMeasurement/WithoutAdIdSupport (11.11.0): - GoogleUtilities/AppDelegateSwizzler (~> 8.0) @@ -227,9 +246,9 @@ PODS: - GoogleUtilities/UserDefaults (8.1.0): - GoogleUtilities/Logger - GoogleUtilities/Privacy - - hermes-engine (0.81.5): - - hermes-engine/Pre-built (= 0.81.5) - - hermes-engine/Pre-built (0.81.5) + - hermes-engine (0.17.0): + - hermes-engine/Pre-built (= 0.17.0) + - hermes-engine/Pre-built (0.17.0) - JitsiWebRTC (124.0.2) - libavif/core (1.0.0) - libavif/libdav1d (1.0.0): @@ -351,42 +370,45 @@ PODS: - boost - DoubleConversion - fast_float (= 8.0.0) - - fmt (= 11.0.2) + - fmt (= 12.1.0) - glog - RCT-Folly/Default (= 2024.11.18.00) - RCT-Folly/Default (2024.11.18.00): - boost - DoubleConversion - fast_float (= 8.0.0) - - fmt (= 11.0.2) + - fmt (= 12.1.0) - glog - RCT-Folly/Fabric (2024.11.18.00): - boost - DoubleConversion - fast_float (= 8.0.0) - - fmt (= 11.0.2) - - glog - - RCTDeprecation (0.81.5) - - RCTRequired (0.81.5) - - RCTTypeSafety (0.81.5): - - FBLazyVector (= 0.81.5) - - RCTRequired (= 0.81.5) - - React-Core (= 0.81.5) - - React (0.81.5): - - React-Core (= 0.81.5) - - React-Core/DevSupport (= 0.81.5) - - React-Core/RCTWebSocket (= 0.81.5) - - React-RCTActionSheet (= 0.81.5) - - React-RCTAnimation (= 0.81.5) - - React-RCTBlob (= 0.81.5) - - React-RCTImage (= 0.81.5) - - React-RCTLinking (= 0.81.5) - - React-RCTNetwork (= 0.81.5) - - React-RCTSettings (= 0.81.5) - - React-RCTText (= 0.81.5) - - React-RCTVibration (= 0.81.5) - - React-callinvoker (0.81.5) - - React-Core (0.81.5): + - fmt (= 12.1.0) + - glog + - RCTDeprecation (0.86.3) + - RCTRequired (0.86.3) + - RCTSwiftUI (0.86.3) + - RCTSwiftUIWrapper (0.86.3): + - RCTSwiftUI + - RCTTypeSafety (0.86.3): + - FBLazyVector (= 0.86.3) + - RCTRequired (= 0.86.3) + - React-Core (= 0.86.3) + - React (0.86.3): + - React-Core (= 0.86.3) + - React-Core/DevSupport (= 0.86.3) + - React-Core/RCTWebSocket (= 0.86.3) + - React-RCTActionSheet (= 0.86.3) + - React-RCTAnimation (= 0.86.3) + - React-RCTBlob (= 0.86.3) + - React-RCTImage (= 0.86.3) + - React-RCTLinking (= 0.86.3) + - React-RCTNetwork (= 0.86.3) + - React-RCTSettings (= 0.86.3) + - React-RCTText (= 0.86.3) + - React-RCTVibration (= 0.86.3) + - React-callinvoker (0.86.3) + - React-Core (0.86.3): - boost - DoubleConversion - fast_float @@ -396,7 +418,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - RCTDeprecation - - React-Core/Default (= 0.81.5) + - React-Core/Default (= 0.86.3) - React-cxxreact - React-featureflags - React-hermes @@ -411,7 +433,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/CoreModulesHeaders (0.81.5): + - React-Core/CoreModulesHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -436,7 +458,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/Default (0.81.5): + - React-Core/Default (0.86.3): - boost - DoubleConversion - fast_float @@ -460,7 +482,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/DevSupport (0.81.5): + - React-Core/DevSupport (0.86.3): - boost - DoubleConversion - fast_float @@ -470,8 +492,8 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - RCTDeprecation - - React-Core/Default (= 0.81.5) - - React-Core/RCTWebSocket (= 0.81.5) + - React-Core/Default (= 0.86.3) + - React-Core/RCTWebSocket (= 0.86.3) - React-cxxreact - React-featureflags - React-hermes @@ -486,7 +508,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTActionSheetHeaders (0.81.5): + - React-Core/RCTActionSheetHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -511,7 +533,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTAnimationHeaders (0.81.5): + - React-Core/RCTAnimationHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -536,7 +558,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTBlobHeaders (0.81.5): + - React-Core/RCTBlobHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -561,7 +583,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTImageHeaders (0.81.5): + - React-Core/RCTImageHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -586,7 +608,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTLinkingHeaders (0.81.5): + - React-Core/RCTLinkingHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -611,7 +633,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTNetworkHeaders (0.81.5): + - React-Core/RCTNetworkHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -636,7 +658,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTSettingsHeaders (0.81.5): + - React-Core/RCTSettingsHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -661,7 +683,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTTextHeaders (0.81.5): + - React-Core/RCTTextHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -686,7 +708,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTVibrationHeaders (0.81.5): + - React-Core/RCTVibrationHeaders (0.86.3): - boost - DoubleConversion - fast_float @@ -711,7 +733,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTWebSocket (0.81.5): + - React-Core/RCTWebSocket (0.86.3): - boost - DoubleConversion - fast_float @@ -721,7 +743,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - RCTDeprecation - - React-Core/Default (= 0.81.5) + - React-Core/Default (= 0.86.3) - React-cxxreact - React-featureflags - React-hermes @@ -736,7 +758,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-CoreModules (0.81.5): + - React-CoreModules (0.86.3): - boost - DoubleConversion - fast_float @@ -744,20 +766,23 @@ PODS: - glog - RCT-Folly - RCT-Folly/Fabric - - RCTTypeSafety (= 0.81.5) - - React-Core/CoreModulesHeaders (= 0.81.5) - - React-jsi (= 0.81.5) + - RCTTypeSafety (= 0.86.3) + - React-Core/CoreModulesHeaders (= 0.86.3) + - React-debug + - React-featureflags + - React-jsi (= 0.86.3) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - React-NativeModulesApple - React-RCTBlob - React-RCTFBReactNativeSpec - - React-RCTImage (= 0.81.5) + - React-RCTImage (= 0.86.3) - React-runtimeexecutor + - React-utils - ReactCommon - SocketRocket - - React-cxxreact (0.81.5): + - React-cxxreact (0.86.3): - boost - DoubleConversion - fast_float @@ -766,19 +791,22 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-callinvoker (= 0.81.5) - - React-debug (= 0.81.5) - - React-jsi (= 0.81.5) + - React-callinvoker (= 0.86.3) + - React-debug (= 0.86.3) + - React-jsi (= 0.86.3) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - - React-logger (= 0.81.5) - - React-perflogger (= 0.81.5) + - React-logger (= 0.86.3) + - React-perflogger (= 0.86.3) - React-runtimeexecutor - - React-timing (= 0.81.5) + - React-timing (= 0.86.3) + - React-utils - SocketRocket - - React-debug (0.81.5) - - React-defaultsnativemodule (0.81.5): + - React-debug (0.86.3): + - React-debug/redbox (= 0.86.3) + - React-debug/redbox (0.86.3) + - React-defaultsnativemodule (0.86.3): - boost - DoubleConversion - fast_float @@ -788,14 +816,21 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - React-domnativemodule + - React-Fabric/animated + - React-featureflags - React-featureflagsnativemodule - React-idlecallbacksnativemodule + - React-intersectionobservernativemodule - React-jsi - React-jsiexecutor - React-microtasksnativemodule + - React-mutationobservernativemodule - React-RCTFBReactNativeSpec + - React-viewtransitionnativemodule + - React-webperformancenativemodule - SocketRocket - - React-domnativemodule (0.81.5): + - Yoga + - React-domnativemodule (0.86.3): - boost - DoubleConversion - fast_float @@ -815,7 +850,77 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-Fabric (0.81.5): + - React-Fabric (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/animated (= 0.86.3) + - React-Fabric/animationbackend (= 0.86.3) + - React-Fabric/animations (= 0.86.3) + - React-Fabric/attributedstring (= 0.86.3) + - React-Fabric/bridging (= 0.86.3) + - React-Fabric/componentregistry (= 0.86.3) + - React-Fabric/componentregistrynative (= 0.86.3) + - React-Fabric/components (= 0.86.3) + - React-Fabric/consistency (= 0.86.3) + - React-Fabric/core (= 0.86.3) + - React-Fabric/dom (= 0.86.3) + - React-Fabric/imagemanager (= 0.86.3) + - React-Fabric/leakchecker (= 0.86.3) + - React-Fabric/mounting (= 0.86.3) + - React-Fabric/observers (= 0.86.3) + - React-Fabric/scheduler (= 0.86.3) + - React-Fabric/telemetry (= 0.86.3) + - React-Fabric/uimanager (= 0.86.3) + - React-Fabric/viewtransition (= 0.86.3) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/animated (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/animationbackend + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/animationbackend (0.86.3): - boost - DoubleConversion - fast_float @@ -829,23 +934,6 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/animations (= 0.81.5) - - React-Fabric/attributedstring (= 0.81.5) - - React-Fabric/bridging (= 0.81.5) - - React-Fabric/componentregistry (= 0.81.5) - - React-Fabric/componentregistrynative (= 0.81.5) - - React-Fabric/components (= 0.81.5) - - React-Fabric/consistency (= 0.81.5) - - React-Fabric/core (= 0.81.5) - - React-Fabric/dom (= 0.81.5) - - React-Fabric/imagemanager (= 0.81.5) - - React-Fabric/leakchecker (= 0.81.5) - - React-Fabric/mounting (= 0.81.5) - - React-Fabric/observers (= 0.81.5) - - React-Fabric/scheduler (= 0.81.5) - - React-Fabric/telemetry (= 0.81.5) - - React-Fabric/templateprocessor (= 0.81.5) - - React-Fabric/uimanager (= 0.81.5) - React-featureflags - React-graphics - React-jsi @@ -857,7 +945,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/animations (0.81.5): + - React-Fabric/animations (0.86.3): - boost - DoubleConversion - fast_float @@ -882,7 +970,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/attributedstring (0.81.5): + - React-Fabric/attributedstring (0.86.3): - boost - DoubleConversion - fast_float @@ -907,7 +995,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/bridging (0.81.5): + - React-Fabric/bridging (0.86.3): - boost - DoubleConversion - fast_float @@ -932,7 +1020,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/componentregistry (0.81.5): + - React-Fabric/componentregistry (0.86.3): - boost - DoubleConversion - fast_float @@ -957,7 +1045,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/componentregistrynative (0.81.5): + - React-Fabric/componentregistrynative (0.86.3): - boost - DoubleConversion - fast_float @@ -982,7 +1070,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components (0.81.5): + - React-Fabric/components (0.86.3): - boost - DoubleConversion - fast_float @@ -996,10 +1084,10 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/components/legacyviewmanagerinterop (= 0.81.5) - - React-Fabric/components/root (= 0.81.5) - - React-Fabric/components/scrollview (= 0.81.5) - - React-Fabric/components/view (= 0.81.5) + - React-Fabric/components/legacyviewmanagerinterop (= 0.86.3) + - React-Fabric/components/root (= 0.86.3) + - React-Fabric/components/scrollview (= 0.86.3) + - React-Fabric/components/view (= 0.86.3) - React-featureflags - React-graphics - React-jsi @@ -1011,7 +1099,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components/legacyviewmanagerinterop (0.81.5): + - React-Fabric/components/legacyviewmanagerinterop (0.86.3): - boost - DoubleConversion - fast_float @@ -1036,7 +1124,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components/root (0.81.5): + - React-Fabric/components/root (0.86.3): - boost - DoubleConversion - fast_float @@ -1061,7 +1149,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components/scrollview (0.81.5): + - React-Fabric/components/scrollview (0.86.3): - boost - DoubleConversion - fast_float @@ -1086,7 +1174,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components/view (0.81.5): + - React-Fabric/components/view (0.86.3): - boost - DoubleConversion - fast_float @@ -1113,7 +1201,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-Fabric/consistency (0.81.5): + - React-Fabric/consistency (0.86.3): - boost - DoubleConversion - fast_float @@ -1138,7 +1226,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/core (0.81.5): + - React-Fabric/core (0.86.3): - boost - DoubleConversion - fast_float @@ -1163,7 +1251,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/dom (0.81.5): + - React-Fabric/dom (0.86.3): - boost - DoubleConversion - fast_float @@ -1188,7 +1276,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/imagemanager (0.81.5): + - React-Fabric/imagemanager (0.86.3): - boost - DoubleConversion - fast_float @@ -1213,7 +1301,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/leakchecker (0.81.5): + - React-Fabric/leakchecker (0.86.3): - boost - DoubleConversion - fast_float @@ -1238,7 +1326,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/mounting (0.81.5): + - React-Fabric/mounting (0.86.3): - boost - DoubleConversion - fast_float @@ -1256,6 +1344,7 @@ PODS: - React-graphics - React-jsi - React-jsiexecutor + - React-jsinspectortracing - React-logger - React-rendererdebug - React-runtimeexecutor @@ -1263,7 +1352,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/observers (0.81.5): + - React-Fabric/observers (0.86.3): - boost - DoubleConversion - fast_float @@ -1277,7 +1366,9 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/observers/events (= 0.81.5) + - React-Fabric/observers/events (= 0.86.3) + - React-Fabric/observers/intersection (= 0.86.3) + - React-Fabric/observers/mutation (= 0.86.3) - React-featureflags - React-graphics - React-jsi @@ -1289,7 +1380,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/observers/events (0.81.5): + - React-Fabric/observers/events (0.86.3): - boost - DoubleConversion - fast_float @@ -1314,7 +1405,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/scheduler (0.81.5): + - React-Fabric/observers/intersection (0.86.3): - boost - DoubleConversion - fast_float @@ -1328,12 +1419,65 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/observers/mutation (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/scheduler (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/animationbackend - React-Fabric/observers/events + - React-Fabric/viewtransition - React-featureflags - React-graphics - React-jsi - React-jsiexecutor - React-logger + - React-performancecdpmetrics - React-performancetimeline - React-rendererdebug - React-runtimeexecutor @@ -1341,7 +1485,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/telemetry (0.81.5): + - React-Fabric/telemetry (0.86.3): - boost - DoubleConversion - fast_float @@ -1366,7 +1510,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/templateprocessor (0.81.5): + - React-Fabric/uimanager (0.86.3): - boost - DoubleConversion - fast_float @@ -1380,18 +1524,20 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-Fabric/uimanager/consistency (= 0.86.3) - React-featureflags - React-graphics - React-jsi - React-jsiexecutor - React-logger + - React-rendererconsistency - React-rendererdebug - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/uimanager (0.81.5): + - React-Fabric/uimanager/consistency (0.86.3): - boost - DoubleConversion - fast_float @@ -1405,7 +1551,6 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/uimanager/consistency (= 0.81.5) - React-featureflags - React-graphics - React-jsi @@ -1418,7 +1563,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/uimanager/consistency (0.81.5): + - React-Fabric/viewtransition (0.86.3): - boost - DoubleConversion - fast_float @@ -1437,14 +1582,13 @@ PODS: - React-jsi - React-jsiexecutor - React-logger - - React-rendererconsistency - React-rendererdebug - React-runtimeexecutor - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-FabricComponents (0.81.5): + - React-FabricComponents (0.86.3): - boost - DoubleConversion - fast_float @@ -1459,8 +1603,8 @@ PODS: - React-cxxreact - React-debug - React-Fabric - - React-FabricComponents/components (= 0.81.5) - - React-FabricComponents/textlayoutmanager (= 0.81.5) + - React-FabricComponents/components (= 0.86.3) + - React-FabricComponents/textlayoutmanager (= 0.86.3) - React-featureflags - React-graphics - React-jsi @@ -1473,7 +1617,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components (0.81.5): + - React-FabricComponents/components (0.86.3): - boost - DoubleConversion - fast_float @@ -1488,17 +1632,17 @@ PODS: - React-cxxreact - React-debug - React-Fabric - - React-FabricComponents/components/inputaccessory (= 0.81.5) - - React-FabricComponents/components/iostextinput (= 0.81.5) - - React-FabricComponents/components/modal (= 0.81.5) - - React-FabricComponents/components/rncore (= 0.81.5) - - React-FabricComponents/components/safeareaview (= 0.81.5) - - React-FabricComponents/components/scrollview (= 0.81.5) - - React-FabricComponents/components/switch (= 0.81.5) - - React-FabricComponents/components/text (= 0.81.5) - - React-FabricComponents/components/textinput (= 0.81.5) - - React-FabricComponents/components/unimplementedview (= 0.81.5) - - React-FabricComponents/components/virtualview (= 0.81.5) + - React-FabricComponents/components/inputaccessory (= 0.86.3) + - React-FabricComponents/components/iostextinput (= 0.86.3) + - React-FabricComponents/components/modal (= 0.86.3) + - React-FabricComponents/components/rncore (= 0.86.3) + - React-FabricComponents/components/safeareaview (= 0.86.3) + - React-FabricComponents/components/scrollview (= 0.86.3) + - React-FabricComponents/components/switch (= 0.86.3) + - React-FabricComponents/components/text (= 0.86.3) + - React-FabricComponents/components/textinput (= 0.86.3) + - React-FabricComponents/components/unimplementedview (= 0.86.3) + - React-FabricComponents/components/virtualview (= 0.86.3) - React-featureflags - React-graphics - React-jsi @@ -1511,7 +1655,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/inputaccessory (0.81.5): + - React-FabricComponents/components/inputaccessory (0.86.3): - boost - DoubleConversion - fast_float @@ -1538,7 +1682,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/iostextinput (0.81.5): + - React-FabricComponents/components/iostextinput (0.86.3): - boost - DoubleConversion - fast_float @@ -1565,7 +1709,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/modal (0.81.5): + - React-FabricComponents/components/modal (0.86.3): - boost - DoubleConversion - fast_float @@ -1592,7 +1736,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/rncore (0.81.5): + - React-FabricComponents/components/rncore (0.86.3): - boost - DoubleConversion - fast_float @@ -1619,7 +1763,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/safeareaview (0.81.5): + - React-FabricComponents/components/safeareaview (0.86.3): - boost - DoubleConversion - fast_float @@ -1646,7 +1790,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/scrollview (0.81.5): + - React-FabricComponents/components/scrollview (0.86.3): - boost - DoubleConversion - fast_float @@ -1673,7 +1817,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/switch (0.81.5): + - React-FabricComponents/components/switch (0.86.3): - boost - DoubleConversion - fast_float @@ -1700,7 +1844,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/text (0.81.5): + - React-FabricComponents/components/text (0.86.3): - boost - DoubleConversion - fast_float @@ -1727,7 +1871,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/textinput (0.81.5): + - React-FabricComponents/components/textinput (0.86.3): - boost - DoubleConversion - fast_float @@ -1754,7 +1898,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/unimplementedview (0.81.5): + - React-FabricComponents/components/unimplementedview (0.86.3): - boost - DoubleConversion - fast_float @@ -1781,7 +1925,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/virtualview (0.81.5): + - React-FabricComponents/components/virtualview (0.86.3): - boost - DoubleConversion - fast_float @@ -1808,7 +1952,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/textlayoutmanager (0.81.5): + - React-FabricComponents/textlayoutmanager (0.86.3): - boost - DoubleConversion - fast_float @@ -1835,7 +1979,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricImage (0.81.5): + - React-FabricImage (0.86.3): - boost - DoubleConversion - fast_float @@ -1844,21 +1988,21 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - RCTRequired (= 0.81.5) - - RCTTypeSafety (= 0.81.5) + - RCTRequired (= 0.86.3) + - RCTTypeSafety (= 0.86.3) - React-Fabric - React-featureflags - React-graphics - React-ImageManager - React-jsi - - React-jsiexecutor (= 0.81.5) + - React-jsiexecutor (= 0.86.3) - React-logger - React-rendererdebug - React-utils - ReactCommon - SocketRocket - Yoga - - React-featureflags (0.81.5): + - React-featureflags (0.86.3): - boost - DoubleConversion - fast_float @@ -1867,7 +2011,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - SocketRocket - - React-featureflagsnativemodule (0.81.5): + - React-featureflagsnativemodule (0.86.3): - boost - DoubleConversion - fast_float @@ -1882,7 +2026,7 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - SocketRocket - - React-graphics (0.81.5): + - React-graphics (0.86.3): - boost - DoubleConversion - fast_float @@ -1891,11 +2035,13 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric + - React-featureflags - React-jsi - React-jsiexecutor + - React-rendererdebug - React-utils - SocketRocket - - React-hermes (0.81.5): + - React-hermes (0.86.3): - boost - DoubleConversion - fast_float @@ -1904,16 +2050,18 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-cxxreact (= 0.81.5) + - React-cxxreact (= 0.86.3) - React-jsi - - React-jsiexecutor (= 0.81.5) + - React-jsiexecutor (= 0.86.3) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - - React-perflogger (= 0.81.5) + - React-jsitooling + - React-oscompat + - React-perflogger (= 0.86.3) - React-runtimeexecutor - SocketRocket - - React-idlecallbacksnativemodule (0.81.5): + - React-idlecallbacksnativemodule (0.86.3): - boost - DoubleConversion - fast_float @@ -1929,7 +2077,7 @@ PODS: - React-runtimescheduler - ReactCommon/turbomodule/core - SocketRocket - - React-ImageManager (0.81.5): + - React-ImageManager (0.86.3): - boost - DoubleConversion - fast_float @@ -1944,7 +2092,28 @@ PODS: - React-rendererdebug - React-utils - SocketRocket - - React-jserrorhandler (0.81.5): + - React-intersectionobservernativemodule (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-cxxreact + - React-Fabric + - React-Fabric/bridging + - React-graphics + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - React-runtimescheduler + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-jserrorhandler (0.86.3): - boost - DoubleConversion - fast_float @@ -1959,7 +2128,7 @@ PODS: - React-jsi - ReactCommon/turbomodule/bridging - SocketRocket - - React-jsi (0.81.5): + - React-jsi (0.86.3): - boost - DoubleConversion - fast_float @@ -1969,7 +2138,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - SocketRocket - - React-jsiexecutor (0.81.5): + - React-jsiexecutor (0.86.3): - boost - DoubleConversion - fast_float @@ -1978,15 +2147,19 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-cxxreact (= 0.81.5) - - React-jsi (= 0.81.5) + - React-cxxreact + - React-debug + - React-jserrorhandler + - React-jsi - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - - React-perflogger (= 0.81.5) + - React-jsitooling + - React-perflogger - React-runtimeexecutor + - React-utils - SocketRocket - - React-jsinspector (0.81.5): + - React-jsinspector (0.86.3): - boost - DoubleConversion - fast_float @@ -2001,10 +2174,11 @@ PODS: - React-jsinspectornetwork - React-jsinspectortracing - React-oscompat - - React-perflogger (= 0.81.5) + - React-perflogger (= 0.86.3) - React-runtimeexecutor + - React-utils - SocketRocket - - React-jsinspectorcdp (0.81.5): + - React-jsinspectorcdp (0.86.3): - boost - DoubleConversion - fast_float @@ -2013,7 +2187,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - SocketRocket - - React-jsinspectornetwork (0.81.5): + - React-jsinspectornetwork (0.86.3): - boost - DoubleConversion - fast_float @@ -2021,40 +2195,44 @@ PODS: - glog - RCT-Folly - RCT-Folly/Fabric - - React-featureflags - React-jsinspectorcdp - - React-performancetimeline - - React-timing - SocketRocket - - React-jsinspectortracing (0.81.5): + - React-jsinspectortracing (0.86.3): - boost - DoubleConversion - fast_float - fmt - glog + - hermes-engine - RCT-Folly - RCT-Folly/Fabric + - React-jsi + - React-jsinspectornetwork - React-oscompat - React-timing + - React-utils - SocketRocket - - React-jsitooling (0.81.5): + - React-jsitooling (0.86.3): - boost - DoubleConversion - fast_float - fmt - glog + - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-cxxreact (= 0.81.5) - - React-jsi (= 0.81.5) + - React-cxxreact (= 0.86.3) + - React-debug + - React-jsi (= 0.86.3) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - React-runtimeexecutor + - React-utils - SocketRocket - - React-jsitracing (0.81.5): + - React-jsitracing (0.86.3): - React-jsi - - React-logger (0.81.5): + - React-logger (0.86.3): - boost - DoubleConversion - fast_float @@ -2063,7 +2241,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - SocketRocket - - React-Mapbuffer (0.81.5): + - React-Mapbuffer (0.86.3): - boost - DoubleConversion - fast_float @@ -2073,7 +2251,21 @@ PODS: - RCT-Folly/Fabric - React-debug - SocketRocket - - React-microtasksnativemodule (0.81.5): + - React-microtasksnativemodule (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - SocketRocket + - React-mutationobservernativemodule (0.86.3): - boost - DoubleConversion - fast_float @@ -2082,11 +2274,18 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric + - React-cxxreact + - React-Fabric + - React-Fabric/bridging + - React-Fabric/observers/mutation + - React-featureflags - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec + - React-runtimeexecutor - ReactCommon/turbomodule/core - SocketRocket + - Yoga - react-native-a11y-order (0.4.0): - boost - DoubleConversion @@ -2230,9 +2429,35 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-netinfo (11.4.1): + - react-native-netinfo (12.0.1): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety - React-Core - - react-native-safe-area-context (5.6.2): + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - react-native-safe-area-context (5.7.0): - boost - DoubleConversion - fast_float @@ -2250,8 +2475,8 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-safe-area-context/common (= 5.6.2) - - react-native-safe-area-context/fabric (= 5.6.2) + - react-native-safe-area-context/common (= 5.7.0) + - react-native-safe-area-context/fabric (= 5.7.0) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -2262,7 +2487,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-safe-area-context/common (5.6.2): + - react-native-safe-area-context/common (5.7.0): - boost - DoubleConversion - fast_float @@ -2290,7 +2515,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-safe-area-context/fabric (5.6.2): + - react-native-safe-area-context/fabric (5.7.0): - boost - DoubleConversion - fast_float @@ -2319,7 +2544,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-slider (5.0.1): + - react-native-slider (5.2.0): - boost - DoubleConversion - fast_float @@ -2337,7 +2562,7 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-slider/common (= 5.0.1) + - react-native-slider/common (= 5.2.0) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -2348,7 +2573,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-slider/common (5.0.1): + - react-native-slider/common (5.2.0): - boost - DoubleConversion - fast_float @@ -2407,7 +2632,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-NativeModulesApple (0.81.5): + - React-NativeModulesApple (0.86.3): - boost - DoubleConversion - fast_float @@ -2419,6 +2644,7 @@ PODS: - React-callinvoker - React-Core - React-cxxreact + - React-debug - React-featureflags - React-jsi - React-jsinspector @@ -2427,8 +2653,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - SocketRocket - - React-oscompat (0.81.5) - - React-perflogger (0.81.5): + - React-networking (0.86.3): - boost - DoubleConversion - fast_float @@ -2436,8 +2661,36 @@ PODS: - glog - RCT-Folly - RCT-Folly/Fabric + - React-jsinspectornetwork + - React-jsinspectortracing + - React-performancetimeline + - React-timing + - SocketRocket + - React-oscompat (0.86.3) + - React-perflogger (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - SocketRocket + - React-performancecdpmetrics (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-jsi + - React-performancetimeline + - React-runtimeexecutor + - React-timing - SocketRocket - - React-performancetimeline (0.81.5): + - React-performancetimeline (0.86.3): - boost - DoubleConversion - fast_float @@ -2446,13 +2699,14 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - React-featureflags + - React-jsinspector - React-jsinspectortracing - React-perflogger - React-timing - SocketRocket - - React-RCTActionSheet (0.81.5): - - React-Core/RCTActionSheetHeaders (= 0.81.5) - - React-RCTAnimation (0.81.5): + - React-RCTActionSheet (0.86.3): + - React-Core/RCTActionSheetHeaders (= 0.86.3) + - React-RCTAnimation (0.86.3): - boost - DoubleConversion - fast_float @@ -2462,13 +2716,14 @@ PODS: - RCT-Folly/Fabric - RCTTypeSafety - React-Core/RCTAnimationHeaders + - React-debug - React-featureflags - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - SocketRocket - - React-RCTAppDelegate (0.81.5): + - React-RCTAppDelegate (0.86.3): - boost - DoubleConversion - fast_float @@ -2502,7 +2757,7 @@ PODS: - React-utils - ReactCommon - SocketRocket - - React-RCTBlob (0.81.5): + - React-RCTBlob (0.86.3): - boost - DoubleConversion - fast_float @@ -2521,7 +2776,7 @@ PODS: - React-RCTNetwork - ReactCommon - SocketRocket - - React-RCTFabric (0.81.5): + - React-RCTFabric (0.86.3): - boost - DoubleConversion - fast_float @@ -2530,6 +2785,7 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric + - RCTSwiftUIWrapper - React-Core - React-debug - React-Fabric @@ -2541,8 +2797,9 @@ PODS: - React-jsi - React-jsinspector - React-jsinspectorcdp - - React-jsinspectornetwork - React-jsinspectortracing + - React-networking + - React-performancecdpmetrics - React-performancetimeline - React-RCTAnimation - React-RCTFBReactNativeSpec @@ -2556,7 +2813,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-RCTFBReactNativeSpec (0.81.5): + - React-RCTFBReactNativeSpec (0.86.3): - boost - DoubleConversion - fast_float @@ -2570,10 +2827,10 @@ PODS: - React-Core - React-jsi - React-NativeModulesApple - - React-RCTFBReactNativeSpec/components (= 0.81.5) + - React-RCTFBReactNativeSpec/components (= 0.86.3) - ReactCommon - SocketRocket - - React-RCTFBReactNativeSpec/components (0.81.5): + - React-RCTFBReactNativeSpec/components (0.86.3): - boost - DoubleConversion - fast_float @@ -2596,7 +2853,7 @@ PODS: - ReactCommon - SocketRocket - Yoga - - React-RCTImage (0.81.5): + - React-RCTImage (0.86.3): - boost - DoubleConversion - fast_float @@ -2612,14 +2869,14 @@ PODS: - React-RCTNetwork - ReactCommon - SocketRocket - - React-RCTLinking (0.81.5): - - React-Core/RCTLinkingHeaders (= 0.81.5) - - React-jsi (= 0.81.5) + - React-RCTLinking (0.86.3): + - React-Core/RCTLinkingHeaders (= 0.86.3) + - React-jsi (= 0.86.3) - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - ReactCommon/turbomodule/core (= 0.81.5) - - React-RCTNetwork (0.81.5): + - ReactCommon/turbomodule/core (= 0.86.3) + - React-RCTNetwork (0.86.3): - boost - DoubleConversion - fast_float @@ -2629,15 +2886,17 @@ PODS: - RCT-Folly/Fabric - RCTTypeSafety - React-Core/RCTNetworkHeaders + - React-debug - React-featureflags - React-jsi - React-jsinspectorcdp - React-jsinspectornetwork - React-NativeModulesApple + - React-networking - React-RCTFBReactNativeSpec - ReactCommon - SocketRocket - - React-RCTRuntime (0.81.5): + - React-RCTRuntime (0.86.3): - boost - DoubleConversion - fast_float @@ -2647,6 +2906,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - React-Core + - React-debug - React-jsi - React-jsinspector - React-jsinspectorcdp @@ -2656,8 +2916,9 @@ PODS: - React-RuntimeCore - React-runtimeexecutor - React-RuntimeHermes + - React-utils - SocketRocket - - React-RCTSettings (0.81.5): + - React-RCTSettings (0.86.3): - boost - DoubleConversion - fast_float @@ -2672,10 +2933,10 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon - SocketRocket - - React-RCTText (0.81.5): - - React-Core/RCTTextHeaders (= 0.81.5) + - React-RCTText (0.86.3): + - React-Core/RCTTextHeaders (= 0.86.3) - Yoga - - React-RCTVibration (0.81.5): + - React-RCTVibration (0.86.3): - boost - DoubleConversion - fast_float @@ -2689,11 +2950,11 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon - SocketRocket - - React-rendererconsistency (0.81.5) - - React-renderercss (0.81.5): + - React-rendererconsistency (0.86.3) + - React-renderercss (0.86.3): - React-debug - React-utils - - React-rendererdebug (0.81.5): + - React-rendererdebug (0.86.3): - boost - DoubleConversion - fast_float @@ -2703,7 +2964,7 @@ PODS: - RCT-Folly/Fabric - React-debug - SocketRocket - - React-RuntimeApple (0.81.5): + - React-RuntimeApple (0.86.3): - boost - DoubleConversion - fast_float @@ -2732,7 +2993,7 @@ PODS: - React-runtimescheduler - React-utils - SocketRocket - - React-RuntimeCore (0.81.5): + - React-RuntimeCore (0.86.3): - boost - DoubleConversion - fast_float @@ -2754,7 +3015,7 @@ PODS: - React-runtimescheduler - React-utils - SocketRocket - - React-runtimeexecutor (0.81.5): + - React-runtimeexecutor (0.86.3): - boost - DoubleConversion - fast_float @@ -2764,10 +3025,10 @@ PODS: - RCT-Folly/Fabric - React-debug - React-featureflags - - React-jsi (= 0.81.5) + - React-jsi (= 0.86.3) - React-utils - SocketRocket - - React-RuntimeHermes (0.81.5): + - React-RuntimeHermes (0.86.3): - boost - DoubleConversion - fast_float @@ -2788,7 +3049,7 @@ PODS: - React-runtimeexecutor - React-utils - SocketRocket - - React-runtimescheduler (0.81.5): + - React-runtimescheduler (0.86.3): - boost - DoubleConversion - fast_float @@ -2810,9 +3071,9 @@ PODS: - React-timing - React-utils - SocketRocket - - React-timing (0.81.5): + - React-timing (0.86.3): - React-debug - - React-utils (0.81.5): + - React-utils (0.86.3): - boost - DoubleConversion - fast_float @@ -2822,11 +3083,46 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - React-debug - - React-jsi (= 0.81.5) + - React-jsi (= 0.86.3) + - SocketRocket + - React-viewtransitionnativemodule (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-Fabric + - React-Fabric/bridging + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core - SocketRocket - - ReactAppDependencyProvider (0.81.5): + - Yoga + - React-webperformancenativemodule (0.86.3): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-cxxreact + - React-jsi + - React-jsiexecutor + - React-performancetimeline + - React-RCTFBReactNativeSpec + - React-runtimeexecutor + - ReactCommon/turbomodule/core + - SocketRocket + - ReactAppDependencyProvider (0.86.3): - ReactCodegen - - ReactCodegen (0.81.5): + - ReactCodegen (0.86.3): - boost - DoubleConversion - fast_float @@ -2852,7 +3148,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - SocketRocket - - ReactCommon (0.81.5): + - ReactCommon (0.86.3): - boost - DoubleConversion - fast_float @@ -2860,9 +3156,9 @@ PODS: - glog - RCT-Folly - RCT-Folly/Fabric - - ReactCommon/turbomodule (= 0.81.5) + - ReactCommon/turbomodule (= 0.86.3) - SocketRocket - - ReactCommon/turbomodule (0.81.5): + - ReactCommon/turbomodule (0.86.3): - boost - DoubleConversion - fast_float @@ -2871,15 +3167,15 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-callinvoker (= 0.81.5) - - React-cxxreact (= 0.81.5) - - React-jsi (= 0.81.5) - - React-logger (= 0.81.5) - - React-perflogger (= 0.81.5) - - ReactCommon/turbomodule/bridging (= 0.81.5) - - ReactCommon/turbomodule/core (= 0.81.5) + - React-callinvoker (= 0.86.3) + - React-cxxreact (= 0.86.3) + - React-jsi (= 0.86.3) + - React-logger (= 0.86.3) + - React-perflogger (= 0.86.3) + - ReactCommon/turbomodule/bridging (= 0.86.3) + - ReactCommon/turbomodule/core (= 0.86.3) - SocketRocket - - ReactCommon/turbomodule/bridging (0.81.5): + - ReactCommon/turbomodule/bridging (0.86.3): - boost - DoubleConversion - fast_float @@ -2888,13 +3184,13 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-callinvoker (= 0.81.5) - - React-cxxreact (= 0.81.5) - - React-jsi (= 0.81.5) - - React-logger (= 0.81.5) - - React-perflogger (= 0.81.5) + - React-callinvoker (= 0.86.3) + - React-cxxreact (= 0.86.3) + - React-jsi (= 0.86.3) + - React-logger (= 0.86.3) + - React-perflogger (= 0.86.3) - SocketRocket - - ReactCommon/turbomodule/core (0.81.5): + - ReactCommon/turbomodule/core (0.86.3): - boost - DoubleConversion - fast_float @@ -2903,14 +3199,14 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-callinvoker (= 0.81.5) - - React-cxxreact (= 0.81.5) - - React-debug (= 0.81.5) - - React-featureflags (= 0.81.5) - - React-jsi (= 0.81.5) - - React-logger (= 0.81.5) - - React-perflogger (= 0.81.5) - - React-utils (= 0.81.5) + - React-callinvoker (= 0.86.3) + - React-cxxreact (= 0.86.3) + - React-debug (= 0.86.3) + - React-featureflags (= 0.86.3) + - React-jsi (= 0.86.3) + - React-logger (= 0.86.3) + - React-perflogger (= 0.86.3) + - React-utils (= 0.86.3) - SocketRocket - ReactNativeIncallManager (4.2.1): - React-Core @@ -3056,7 +3352,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNDateTimePicker (8.4.4): + - RNDateTimePicker (9.1.0): - boost - DoubleConversion - fast_float @@ -3098,7 +3394,7 @@ PODS: - FirebaseCoreExtension - React-Core - RNFBApp - - RNGestureHandler (2.28.0): + - RNGestureHandler (2.32.0): - boost - DoubleConversion - fast_float @@ -3216,7 +3512,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNReanimated (4.1.3): + - RNReanimated (4.5.1): - boost - DoubleConversion - fast_float @@ -3243,11 +3539,13 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated (= 4.1.3) + - RNReanimated/apple (= 4.5.1) + - RNReanimated/common (= 4.5.1) + - RNReanimated/view (= 4.5.1) - RNWorklets - SocketRocket - Yoga - - RNReanimated/reanimated (4.1.3): + - RNReanimated/apple (4.5.1): - boost - DoubleConversion - fast_float @@ -3274,11 +3572,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated/apple (= 4.1.3) - RNWorklets - SocketRocket - Yoga - - RNReanimated/reanimated/apple (4.1.3): + - RNReanimated/common (4.5.1): - boost - DoubleConversion - fast_float @@ -3308,7 +3605,37 @@ PODS: - RNWorklets - SocketRocket - Yoga - - RNScreens (4.17.1): + - RNReanimated/view (4.5.1): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNWorklets + - SocketRocket + - Yoga + - RNScreens (4.26.2): - boost - DoubleConversion - fast_float @@ -3335,10 +3662,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 4.17.1) + - RNScreens/common (= 4.26.2) - SocketRocket - Yoga - - RNScreens/common (4.17.1): + - RNScreens/common (4.26.2): - boost - DoubleConversion - fast_float @@ -3367,7 +3694,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNSVG (15.12.1): + - RNSVG (15.15.4): - boost - DoubleConversion - fast_float @@ -3393,10 +3720,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNSVG/common (= 15.12.1) + - RNSVG/common (= 15.15.4) - SocketRocket - Yoga - - RNSVG/common (15.12.1): + - RNSVG/common (15.15.4): - boost - DoubleConversion - fast_float @@ -3452,7 +3779,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNWorklets (0.6.1): + - RNWorklets (0.10.1): - boost - DoubleConversion - fast_float @@ -3479,10 +3806,11 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNWorklets/worklets (= 0.6.1) + - RNWorklets/apple (= 0.10.1) + - RNWorklets/common (= 0.10.1) - SocketRocket - Yoga - - RNWorklets/worklets (0.6.1): + - RNWorklets/apple (0.10.1): - boost - DoubleConversion - fast_float @@ -3509,10 +3837,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNWorklets/worklets/apple (= 0.6.1) - SocketRocket - Yoga - - RNWorklets/worklets/apple (0.6.1): + - RNWorklets/common (0.10.1): - boost - DoubleConversion - fast_float @@ -3572,21 +3899,27 @@ DEPENDENCIES: - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXApplication (from `../node_modules/expo-application/ios`) - EXConstants (from `../node_modules/expo-constants/ios`) - - EXNotifications (from `../node_modules/expo-notifications/ios`) - Expo (from `../node_modules/expo`) - ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`) - ExpoAsset (from `../node_modules/expo-asset/ios`) - ExpoAudio (from `../node_modules/expo-audio/ios`) - ExpoCamera (from `../node_modules/expo-camera/ios`) + - ExpoCameraBarcodeScanning (from `../node_modules/expo-camera/ios`) - ExpoDevice (from `../node_modules/expo-device/ios`) - ExpoDocumentPicker (from `../node_modules/expo-document-picker/ios`) + - "ExpoDomWebView (from `../node_modules/@expo/dom-webview/ios`)" - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) - ExpoFont (from `../node_modules/expo-font/ios`) - ExpoHaptics (from `../node_modules/expo-haptics/ios`) - ExpoImage (from `../node_modules/expo-image/ios`) - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - ExpoLocalAuthentication (from `../node_modules/expo-local-authentication/ios`) + - "ExpoLogBox (from `../node_modules/@expo/log-box`)" - ExpoModulesCore (from `../node_modules/expo-modules-core`) + - ExpoModulesJSI (from `../node_modules/expo-modules-jsi/apple`) + - ExpoModulesWorklets (from `../node_modules/expo-modules-core`) + - ExpoModulesWorkletsAdapter (from `../node_modules/expo-modules-core`) + - ExpoNotifications (from `../node_modules/expo-notifications/ios`) - "ExpoQuickLook (from `../node_modules/@magrinj/expo-quick-look/ios`)" - ExpoSystemUI (from `../node_modules/expo-system-ui/ios`) - ExpoVideo (from `../node_modules/expo-video/ios`) @@ -3605,6 +3938,8 @@ DEPENDENCIES: - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - RCTRequired (from `../node_modules/react-native/Libraries/Required`) + - RCTSwiftUI (from `../node_modules/react-native/ReactApple/RCTSwiftUI`) + - RCTSwiftUIWrapper (from `../node_modules/react-native/ReactApple/RCTSwiftUIWrapper`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - React (from `../node_modules/react-native/`) - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) @@ -3624,6 +3959,7 @@ DEPENDENCIES: - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) - React-idlecallbacksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-intersectionobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver`) - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) @@ -3636,6 +3972,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - React-mutationobservernativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/mutationobserver`) - react-native-a11y-order (from `../node_modules/react-native-a11y-order`) - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)" - "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)" @@ -3647,8 +3984,10 @@ DEPENDENCIES: - react-native-webrtc (from `../node_modules/react-native-webrtc`) - react-native-webview (from `../node_modules/react-native-webview`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-networking (from `../node_modules/react-native/ReactCommon/react/networking`) - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancecdpmetrics (from `../node_modules/react-native/ReactCommon/react/performance/cdpmetrics`) - React-performancetimeline (from `../node_modules/react-native/ReactCommon/react/performance/timeline`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) @@ -3673,8 +4012,10 @@ DEPENDENCIES: - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-timing (from `../node_modules/react-native/ReactCommon/react/timing`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - - ReactAppDependencyProvider (from `build/generated/ios`) - - ReactCodegen (from `build/generated/ios`) + - React-viewtransitionnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/viewtransition`) + - React-webperformancenativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/webperformance`) + - ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`) + - ReactCodegen (from `build/generated/ios/ReactCodegen`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - ReactNativeIncallManager (from `../node_modules/react-native-incall-manager`) - RNBootSplash (from `../node_modules/react-native-bootsplash`) @@ -3745,8 +4086,6 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-application/ios" EXConstants: :path: "../node_modules/expo-constants/ios" - EXNotifications: - :path: "../node_modules/expo-notifications/ios" Expo: :path: "../node_modules/expo" ExpoAppleAuthentication: @@ -3757,10 +4096,14 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-audio/ios" ExpoCamera: :path: "../node_modules/expo-camera/ios" + ExpoCameraBarcodeScanning: + :path: "../node_modules/expo-camera/ios" ExpoDevice: :path: "../node_modules/expo-device/ios" ExpoDocumentPicker: :path: "../node_modules/expo-document-picker/ios" + ExpoDomWebView: + :path: "../node_modules/@expo/dom-webview/ios" ExpoFileSystem: :path: "../node_modules/expo-file-system/ios" ExpoFont: @@ -3773,8 +4116,18 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-keep-awake/ios" ExpoLocalAuthentication: :path: "../node_modules/expo-local-authentication/ios" + ExpoLogBox: + :path: "../node_modules/@expo/log-box" ExpoModulesCore: :path: "../node_modules/expo-modules-core" + ExpoModulesJSI: + :path: "../node_modules/expo-modules-jsi/apple" + ExpoModulesWorklets: + :path: "../node_modules/expo-modules-core" + ExpoModulesWorkletsAdapter: + :path: "../node_modules/expo-modules-core" + ExpoNotifications: + :path: "../node_modules/expo-notifications/ios" ExpoQuickLook: :path: "../node_modules/@magrinj/expo-quick-look/ios" ExpoSystemUI: @@ -3795,7 +4148,7 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" - :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 + :tag: hermes-v0.17.0 MobileCrypto: :path: "../node_modules/@rocket.chat/mobile-crypto" NitroMmkv: @@ -3808,6 +4161,10 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: :path: "../node_modules/react-native/Libraries/Required" + RCTSwiftUI: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUI" + RCTSwiftUIWrapper: + :path: "../node_modules/react-native/ReactApple/RCTSwiftUIWrapper" RCTTypeSafety: :path: "../node_modules/react-native/Libraries/TypeSafety" React: @@ -3844,6 +4201,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" React-ImageManager: :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-intersectionobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/intersectionobserver" React-jserrorhandler: :path: "../node_modules/react-native/ReactCommon/jserrorhandler" React-jsi: @@ -3868,6 +4227,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + React-mutationobservernativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/mutationobserver" react-native-a11y-order: :path: "../node_modules/react-native-a11y-order" react-native-cameraroll: @@ -3890,10 +4251,14 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-webview" React-NativeModulesApple: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-networking: + :path: "../node_modules/react-native/ReactCommon/react/networking" React-oscompat: :path: "../node_modules/react-native/ReactCommon/oscompat" React-perflogger: :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-performancecdpmetrics: + :path: "../node_modules/react-native/ReactCommon/react/performance/cdpmetrics" React-performancetimeline: :path: "../node_modules/react-native/ReactCommon/react/performance/timeline" React-RCTActionSheet: @@ -3942,10 +4307,14 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/timing" React-utils: :path: "../node_modules/react-native/ReactCommon/react/utils" + React-viewtransitionnativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/viewtransition" + React-webperformancenativemodule: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/webperformance" ReactAppDependencyProvider: - :path: build/generated/ios + :path: build/generated/ios/ReactAppDependencyProvider ReactCodegen: - :path: build/generated/ios + :path: build/generated/ios/ReactCodegen ReactCommon: :path: "../node_modules/react-native/ReactCommon" ReactNativeIncallManager: @@ -3998,32 +4367,38 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 BugsnagReactNative: 2078dec76e54db9f6e687743af45dc3673cb32ab - BVLinearGradient: 7815a70ab485b7b155186dd0cc836363e0288cad + BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb - EXApplication: 1e98d4b1dccdf30627f92917f4b2c5a53c330e5f - EXConstants: 805f35b1b295c542ca6acce836f21a1f9ee104d5 - EXNotifications: 983f04ad4ad879b181179e326bf220541e478386 - Expo: 6118272de314ed3d4bc74de3b1229b8f5b324ef1 - ExpoAppleAuthentication: 9413ae9a5e631a424cc04c1b13b0694dbab7d594 - ExpoAsset: f867e55ceb428aab99e1e8c082b5aee7c159ea18 - ExpoAudio: d98f922b17a945cd33f8462d086278dbd810c537 - ExpoCamera: 6a326deb45ba840749652e4c15198317aa78497e - ExpoDevice: 6327c3c200816795708885adf540d26ecab83d1a - ExpoDocumentPicker: 7cd9e71a0f66fb19eb0a586d6f26eee1284692e0 - ExpoFileSystem: 858a44267a3e6e9057e0888ad7c7cfbf55d52063 - ExpoFont: 35ac6191ed86bbf56b3ebd2d9154eda9fad5b509 - ExpoHaptics: d3a6375d8dcc3a1083d003bc2298ff654fafb536 - ExpoImage: 686f972bff29525733aa13357f6691dc90aa03d8 - ExpoKeepAwake: 55f75eca6499bb9e4231ebad6f3e9cb8f99c0296 - ExpoLocalAuthentication: 8a31808565da7af926dd9b595e98594d8b1553b6 - ExpoModulesCore: 91a57f1d109cf53fe58d44fcf6b68a777561549a + EXApplication: a727f05eb8592f0bc764f6ab8d51eacc158652ec + EXConstants: 8948a340eb14bf2bdaaa0e21a63cb9343860b87a + Expo: 5958a8d1e01ac4630df9d05171059b291828a93b + ExpoAppleAuthentication: 16c2955d33ec7315e5a99d17fb5d62ed33cdb5b9 + ExpoAsset: b3999ffb3d4049b6c870708c60ea2209ace9f170 + ExpoAudio: edc9230b788be6f65238a53907d5c176b57a7ce0 + ExpoCamera: c4c16b83b6db376e5a0e538fe2a04deafbb5b6d8 + ExpoCameraBarcodeScanning: 69275ab8ecd76f338da22df825c59405bcf36472 + ExpoDevice: 5c638e530304a51992263ab5c1cab726f992b9dc + ExpoDocumentPicker: fe03a24855e39af0f9b5aee17e7eb4813f6e44a6 + ExpoDomWebView: beaec034e51bd028428b98bb1f2633ab79c6d095 + ExpoFileSystem: 28778f942725170102f7c6cf4dee05a526ddacd0 + ExpoFont: f8198a952ba2ff53c9b393b5269ea435f232cfc3 + ExpoHaptics: 32efd02d88953d30a5c2ff796c0fce692e7bcd2e + ExpoImage: abe60c6c68d08d837b2c51fb9e8b6c6e320b9125 + ExpoKeepAwake: 72af0b3be3615e59bc819d26061ff4e06b155ee0 + ExpoLocalAuthentication: 1e4aef156a3a1f953f8278b8d8309f6118919640 + ExpoLogBox: 80957303325cd4439137765c90a87d7f22aaf2ff + ExpoModulesCore: cfff5545e4ce059bbfba80e56bd1771d9a620ed1 + ExpoModulesJSI: 21c947f0cc559401c86995dad7a0b1112c7fd656 + ExpoModulesWorklets: 986cf72c18400743869b4642d243115f831e404d + ExpoModulesWorkletsAdapter: ae4dd675418c2bbdf802ae74f7f78442e7010ab7 + ExpoNotifications: 098f80e610228da37ee98c0828931337cf5b1106 ExpoQuickLook: 7ac65cd559a74619ea41e13df20e9d7a61e79ea6 - ExpoSystemUI: 2ad325f361a2fcd96a464e8574e19935c461c9cc - ExpoVideo: 8d9e8a1acd0c4c210e538b4355e34ae8ff5e04f7 - ExpoVideoThumbnails: 503a79271416c8723f04b55ea4737282513ebe4f - ExpoWebBrowser: 17b064c621789e41d4816c95c93f429b84971f52 + ExpoSystemUI: 05ee8d4aaba1b6af330f3621e19b39ff9f21e718 + ExpoVideo: 56ed2863b981dd5ae946d8ef23ecfcae855863a0 + ExpoVideoThumbnails: da5d471c170bf4110f681b6649524eace9c95b5b + ExpoWebBrowser: a4d7c382116fbe33c1ec7f60293f02b7e97042e4 fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 - FBLazyVector: 5beb8028d5a2e75dd9634917f23e23d3a061d2aa + FBLazyVector: 94a9e5b949d0f65bbc1a741f8a7c72499da32567 Firebase: 6a8f201c61eda24e98f1ce2b44b1b9c2caf525cc FirebaseAnalytics: acfa848bf81e1a4dbf60ef1f0eddd7328fe6673e FirebaseCore: 2321536f9c423b1f857e047a82b8a42abc6d9e2c @@ -4033,12 +4408,12 @@ SPEC CHECKSUMS: FirebaseInstallations: 781e0e37aa0e1c92b44d00e739aba79ad31b2dba FirebaseRemoteConfigInterop: 1c6135e8a094cc6368949f5faeeca7ee8948b8aa FirebaseSessions: f5c6bfeb66a7202deaf33352017bb6365e395820 - fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd - glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 + fmt: 530618a01105dae0fa3a2f27c81ae11fa8f67eac + glog: 9c7bd858f1402a8e680d1637276210773c093b76 GoogleAppMeasurement: 8a82b93a6400c8e6551c0bcd66a9177f2e067aed GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 - hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172 + hermes-engine: 517aaf93c2fbacc66266024672ddb34b20ed8679 JitsiWebRTC: b47805ab5668be38e7ee60e2258f49badfe8e1d0 libavif: 5f8e715bea24debec477006f21ef9e95432e254d libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f @@ -4051,79 +4426,87 @@ SPEC CHECKSUMS: NitroModules: f2a165630c12aed5793d6b42b9002c6a649ea04e PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 - RCTDeprecation: 5eb1d2eeff5fb91151e8a8eef45b6c7658b6c897 - RCTRequired: cebcf9442fc296c9b89ac791dfd463021d9f6f23 - RCTTypeSafety: b99aa872829ee18f6e777e0ef55852521c5a6788 - React: 914f8695f9bf38e6418228c2ffb70021e559f92f - React-callinvoker: 23cd4e33928608bd0cc35357597568b8b9a5f068 - React-Core: 6a0a97598e9455348113bfe4c573fe8edac34469 - React-CoreModules: a88a6ca48b668401b9780e272e2a607e70f9f955 - React-cxxreact: 06265fd7e8d5c3b6b49e00d328ef76e5f1ae9c8b - React-debug: 29aed758c756956a51b4560223edbd15191ca4c5 - React-defaultsnativemodule: c406bf7cd78036efffb7dec9df469257a1bca58c - React-domnativemodule: 925ea5ff8cb05c68e910057e6349e5898cce00f3 - React-Fabric: 13130d0a70f17e913865b04673ee64603d6c42fe - React-FabricComponents: 1f01ea24a1314bf9abcac4743bb7ad8791336be6 - React-FabricImage: f364dc54fcf8b0ef77192674a009aa4f65b34d75 - React-featureflags: 32217ac18a8c216fc571044186fb04164af72772 - React-featureflagsnativemodule: 9c552bb908a7434baa846002ee1752a77b1a5520 - React-graphics: 3034a698e46e947f74a443e761f1feef742e9d71 - React-hermes: a852be3ab9e1f515e46ba3ea9f48c31d4a9df437 - React-idlecallbacksnativemodule: c43fe1f2221b0548cc366bf15f88efb3b3221bbf - React-ImageManager: 7efd7b19cdfaa3a82482e9e6ac0b56606a3ec271 - React-jserrorhandler: 597057d0b9d158c03e02aa376a4a95f64f46a910 - React-jsi: 7b53959aea60909ac6bbe4dd0bdec6c10d7dc597 - React-jsiexecutor: 19938072af05ade148474bac41e0324a2d733f44 - React-jsinspector: eb6bb244a75cbd56f32767daf2efdb344e2ff10c - React-jsinspectorcdp: 727f37537e9c7ab22b6b86c802d879efae5e2757 - React-jsinspectornetwork: 11d47e644701c58038ef8d7f54a405ddd62b3b16 - React-jsinspectortracing: 8875637e6c65b3b9a3852b006856562e874e7a78 - React-jsitooling: b6e6a2551459a6ef9e1529df2ea981fa27ed3a91 - React-jsitracing: 879e2b2f80dd33d84175989de0a8db5d662505db - React-logger: a913317214a26565cd4c045347edf1bcacb80a3f - React-Mapbuffer: 017336879e2e0fb7537bbc08c24f34e2384c9260 - React-microtasksnativemodule: 63ee6730cec233feab9cdcc0c100dc28a12e4165 + RCT-Folly: 36c4f904fb6cd0219dcb76b94e9502d2a72fab0b + RCTDeprecation: 5268fe9d45036e52deb765d242297f1011e17333 + RCTRequired: 4ecfd325edd95cbbdc202163fb9cb2ba54ac3c9f + RCTSwiftUI: e8b9cdb0d9fbea9284db6420bb726206819a9287 + RCTSwiftUIWrapper: 823739174d403f0fc8fca2baa5c485b8121c00c4 + RCTTypeSafety: 40a9400b253020417515028803a696b35ab8042c + React: 52dce66f71097ed7a4968673d95eeb8095ded74e + React-callinvoker: fba8c8c6078156889ed4e6070ed46265345462b5 + React-Core: fb34f5a9df5c1743c550912b96f9c4f3a3ef69d8 + React-CoreModules: 0e196522b5b3e181a2e7b1e4f33f8b801ac23fd4 + React-cxxreact: 11942a287481cd18c11d9bd2c64d3b1b2b2626c2 + React-debug: 6fee06a4b91395b4dbcb20c628974edae868eb43 + React-defaultsnativemodule: 782f1b8c794bbc960cdce4b483db78d9e251cff2 + React-domnativemodule: 01e99c6243cb87447e7627399a891b41727178da + React-Fabric: 31abac546ae65a4a6b7b283df4496ff393545c86 + React-FabricComponents: 7d474b9b833c18fd349943bcf7b1c54cb9aed728 + React-FabricImage: 53aefdba02f5c6ca953cd7bab04a0d3cf8625c15 + React-featureflags: 7284e11613282ec1703cf697bf13b2744d3bf8b5 + React-featureflagsnativemodule: 40b3e8baff8d43413f89561d4987b73afcc97c2f + React-graphics: a252e749294e52fa60c2711b795f5aa7649d58b8 + React-hermes: df503e8bd7483322f10dfefe27c68f9290befbd5 + React-idlecallbacksnativemodule: e5ba08ad15e7dbbbad2d3bf7fc23bc051cfe6610 + React-ImageManager: a08e8bccc9f6ea6e43bbcb9ce324094eed9f5cb7 + React-intersectionobservernativemodule: 850839034c43eb7e7f15faab8e1fff2cd5a478f4 + React-jserrorhandler: 0b250992c1a8181a90e925834444fabbc7a21fb5 + React-jsi: 26b4997a1a030b14434e1a3b3ca28a593388a649 + React-jsiexecutor: 337306ff602647d6ad7017ee988286b69d51d513 + React-jsinspector: 70354072327bb21c23613eab53ab14381787d78f + React-jsinspectorcdp: b53e6d2c40036b4456d212e2b3f48112b2144c4e + React-jsinspectornetwork: 6f5479e9b485065597d2a00827180e35ef153c2f + React-jsinspectortracing: 8d37007dfcbd35bdae4c5feebb98aa87cec4cde1 + React-jsitooling: 6b2bc363c1eb762c303849787bdcf0cd7148da3a + React-jsitracing: db3ab1e47adee8b8a56ced09074a1cbdffd48609 + React-logger: 49084e2c68c541d938502793e71aeb3707407d5f + React-Mapbuffer: e1ff6f5e203a9e7508d74f83d82052ece710bc8a + React-microtasksnativemodule: 8f4fec3bfe2f2b2d5cf216106caac2349fc261c8 + React-mutationobservernativemodule: a44d1a1fe65bf7ec9f30036a66db8d8f0412c1ee react-native-a11y-order: 20b33ff538af60d8b8a7f7284115a7febc9034d4 react-native-cameraroll: ae31ba7ee17d31c5ee05d7bb248742e3af482783 react-native-cookies: d648ab7025833b977c0b19e142503034f5f29411 react-native-external-keyboard: db51d3a78d68c1f87fef41c67353a7c0ddbe51dd react-native-keyboard-controller: c4ca61f44d66c2f8987a7e67e9b78e80dc965c45 - react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 - react-native-safe-area-context: 0a3b034bb63a5b684dd2f5fffd3c90ef6ed41ee8 - react-native-slider: 6201419b3e3f7c6b7cf2068e0c01274fa86a1da5 + react-native-netinfo: 9fad4eedfec9840a10e73ac4591ea1158523309b + react-native-safe-area-context: eda63a662750758c1fdd7e719c9f1026c8d161cb + react-native-slider: 205c30f4f19af8469c1c24bd4e3ec74038576a67 react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74 react-native-webview: 83c663c5bdf1357d3e7c00986260cb888ea0e328 - React-NativeModulesApple: cbceb3c4cb726838c461b13802a76cefa6f3476f - React-oscompat: eb0626e8ba1a2c61673c991bf9dc21834898475d - React-perflogger: 509e1f9a3ee28df71b0a66de806ac515ce951246 - React-performancetimeline: 9ce28cce1cded27410c293283f99fe62bebdb920 - React-RCTActionSheet: 30fe8f9f8d86db4a25ff34595a658ecd837485fc - React-RCTAnimation: 3126eb1cb8e7a6ca33a52fd833d8018aa9311af1 - React-RCTAppDelegate: b03981c790aa40cf26e0f78cc0f1f2df8287ead4 - React-RCTBlob: 53c35e85c85d6bdaa55dc81a0b290d4e78431095 - React-RCTFabric: 59ad9008775f123019c508efff260594a8509791 - React-RCTFBReactNativeSpec: 82b605ab4f6f8da0a7ad88641161df5a0bafb1fb - React-RCTImage: 074b2faa71a152a456c974e118b60c9eeda94a64 - React-RCTLinking: e5ca17a4f7ae2ad7b0c0483be77e1b383ecd0a8a - React-RCTNetwork: c508d7548c9eceac30a8100a846ea00033a03366 - React-RCTRuntime: 6979568c0bc276fe785e085894f954fa15e0ec7e - React-RCTSettings: dd84c857a4fce42c1e08c1dabcda894e25af4a6e - React-RCTText: 6e4b177d047f98bccb90d6fb1ebdd3391cf8b299 - React-RCTVibration: 9572d4a06a0c92650bcc62913e50eb2a89f19fb6 - React-rendererconsistency: a7b47f8b186af64ff8509c8caec4114a2f1ae63f - React-renderercss: 9845c5063b3a2d0462ed4e4c7fc34219a5d608ed - React-rendererdebug: 3905e346c06347b86c6e49d427062cdd638a3044 - React-RuntimeApple: 97233caf2b635c40819bf5be38d818777f8229ab - React-RuntimeCore: dc41f86fcdf1fbb42a5b8388a29bf59dfa56b2f8 - React-runtimeexecutor: d16d045faaf6cd7de8d1aa8e31a51c13d8db84a4 - React-RuntimeHermes: 5a9d132554c8d6b416d794cd4ac7d927b2f88f7b - React-runtimescheduler: 689d805d43c28b8fb1ab390914e042d10e2ea2ab - React-timing: c39eeb992274aeaeb9f4666dc97a36a31d33fe94 - React-utils: 2f9ba0088251788ad66aa1855ff99ed2424024d2 - ReactAppDependencyProvider: 1bcd3527ac0390a1c898c114f81ff954be35ed79 - ReactCodegen: e0a2012cda55dfd0d2e626ba5d11929c9ff5986c - ReactCommon: 6d0fa86a4510730da7c72560e0ced14258292ab9 + React-NativeModulesApple: 750a5d07f72a9224863f7d7c30519f71aa88576e + React-networking: dc4795b8113f6029877be8abdf555cedb60d8f3c + React-oscompat: 3d37f8d54a39a05a54be863c235b939cb4108385 + React-perflogger: 5aa7c01773e8dca7c2e72b1eb42ebddb7ca83413 + React-performancecdpmetrics: c18cb2f28b3a4220c90f3b6ee5bcf375c52544be + React-performancetimeline: a9a81c04f7d4c2df48ce5e1a1c2bbfbf2e292579 + React-RCTActionSheet: 57e248e5a72bb65bb26e7afb5370ba3d411c003a + React-RCTAnimation: d7501cac9938345a51acb245ed6e911016c9414a + React-RCTAppDelegate: fd219555b51b039b0dbf58a910f0adb00eb6df8d + React-RCTBlob: 647dcdd01449e7d0f39c3a5d3862328ed4dfca83 + React-RCTFabric: 7b1c18df82a2bf7f355ad61264956b42abfcdfca + React-RCTFBReactNativeSpec: 289308a9b9c633e0250a7a7f82d403b60b9dac78 + React-RCTImage: 5880615f3360564cb78872c838ab435e013659ae + React-RCTLinking: ea77766ce56904248222754f0a5960bc6b173cbf + React-RCTNetwork: d2cf10109367dcbcadeeb36b01eeab729708ce94 + React-RCTRuntime: d566a4abd49a8e6eaf383d09c75bdb1dc1505a28 + React-RCTSettings: 058999035f72625afb32c6c32bd34074a33190f8 + React-RCTText: a09cad9bebd88d5088ef47e1dfb38fe4cf114591 + React-RCTVibration: da609d61ad6cef32ddfea86d116fd69c34fdbf34 + React-rendererconsistency: d7d521e7ac66970d051f25575c23a55355e4526d + React-renderercss: 3105f8eed9fd35b4035c61949193d1553741743a + React-rendererdebug: 119235eaa62ac4cc21e4d87cbc6f9ff8d7bdff51 + React-RuntimeApple: b58c06ee048b953b0fab67fd474d3f92fe47c403 + React-RuntimeCore: 463614d0376292c66e85f874db0dabbb8e0822e3 + React-runtimeexecutor: 3d63baa8fb9b5afd94ec56e7da49f0b49ef9de1b + React-RuntimeHermes: 3cefc114d68f49441e661b767b38730d7eb466ef + React-runtimescheduler: 0a4593b56ddab4a38ec431b2ad7d36ccb99b018d + React-timing: d0983fe4b4dd63fc660e9a9e220536747a4ba5e1 + React-utils: ca695510a0b98277cfae1a5e94274dd8fc01c877 + React-viewtransitionnativemodule: d2652188ed10f6dfbc05751607467dc53062e7ad + React-webperformancenativemodule: e59f5087f046a00242b60a8be0f471a353044f40 + ReactAppDependencyProvider: bb350d1333fd459c1aabe782088419a9b8894d3a + ReactCodegen: ead138c976dd9fc1ca8a79a297d4ccf96182531f + ReactCommon: 322cf4b51be09a3cbf5ae891c8dd9f52091c4011 ReactNativeIncallManager: dccd3e7499caa3bb73d3acfedf4fb0360f1a87d5 RNBootSplash: 7fcc9a58ae343aeb1a1dd49f9030832fe432c544 RNCallKeep: 1930a01d8caf48f018be4f2db0c9f03405c2f977 @@ -4131,19 +4514,19 @@ SPEC CHECKSUMS: RNCClipboard: e560338bf6cc4656a09ff90610b62ddc0dbdad65 RNCMaskedView: d707a83784c67099b54b37d056ababb2767ce15e RNCPicker: 35fc66f352403cdfe99d53b541f5180482ca2bc5 - RNDateTimePicker: ca1dc7e24d0b4839877f0ab619e7bca5db715289 + RNDateTimePicker: ea25d20659754bfe1014e0d5905d21dbc18ff705 RNDeviceInfo: 900bd20e1fd3bfd894e7384cc4a83880c0341bd3 RNFBAnalytics: 2e8b8ffcd2bb3d59a43ecbe09571c73c56edef7a RNFBApp: a448037d2df74af9d374a0b765be12ff1e844dc0 RNFBCrashlytics: c3bb5533f9957eddc88f3ea383583309a4ce9f89 - RNGestureHandler: b8d2e75c2e88fc2a1f6be3b3beeeed80b88fa37d + RNGestureHandler: 89c0c079dc88636fed9580bb581bd7df69ef33d0 RNImageCropPicker: 5432d75a3bc7209dc8b6ab189fcfe19bd6339fb9 RNLocalize: c46dd4008dc8990a098fcafd6ee051122a1ccfda - RNReanimated: e1690cdd7f215cfb96a3b7986b81889867dfdb4f - RNScreens: ccfcc2f7d9c0d458b7fc41b3f4f0bea054602b3a - RNSVG: 94a1be05fab4043354bcf7104f0f9b0e2231ef05 + RNReanimated: 1a67ec7c458dd04abadba7549d9d5abb24d6ec8a + RNScreens: 7a423c912ca9e107b37f54f32292186de32bfcaf + RNSVG: 1eecd65673a4735fead92da0e79441f09ec2d77a RNTrueSheet: 53f29088da313dabff8b81d7c4d52afd8e609cfa - RNWorklets: ab618bf7d1c7fd2cb793b9f0f39c3e29274b3ebf + RNWorklets: 71a002c69e25e15bd4758faccd089349bcfc82d7 SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c @@ -4152,9 +4535,9 @@ SPEC CHECKSUMS: SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 TOCropViewController: 797deaf39c90e6e9ddd848d88817f6b9a8a09888 WatermelonDB: 4c846c8cb94eef3cba90fa034d15310163226703 - Yoga: 1e91d83a5286cfd3b725eade59274c92270540d4 + Yoga: 9690ea6ce9c77aee633f4815cc42202c701edbd8 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: 9ef0d302aac0150cec49089c7c416db32bb9d3ec +PODFILE CHECKSUM: d705b9a75929ea25f9b7bb3ef8167fe307bd8b32 COCOAPODS: 1.15.2 diff --git a/ios/RocketChat.xcodeproj/project.pbxproj b/ios/RocketChat.xcodeproj/project.pbxproj index 7f2c42f4612..fba4a7314c7 100644 --- a/ios/RocketChat.xcodeproj/project.pbxproj +++ b/ios/RocketChat.xcodeproj/project.pbxproj @@ -1274,9 +1274,9 @@ "${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoNotifications/ExpoNotifications_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1296,6 +1296,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-timing/React-timing_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle", @@ -1309,9 +1310,9 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", @@ -1331,6 +1332,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-timing_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle", @@ -1433,9 +1435,9 @@ "${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoNotifications/ExpoNotifications_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1455,6 +1457,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-timing/React-timing_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle", @@ -1468,9 +1471,9 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", @@ -1490,6 +1493,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-timing_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle", @@ -1553,13 +1557,15 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoModulesJSI/ExpoModulesJSI.framework/ExpoModulesJSI", "${PODS_XCFRAMEWORKS_BUILD_DIR}/JitsiWebRTC/WebRTC.framework/WebRTC", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermesvm.framework/hermesvm", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExpoModulesJSI.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermesvm.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1853,7 +1859,7 @@ "$PODS_CONFIGURATION_BUILD_DIR/Firebase", ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", @@ -1929,7 +1935,7 @@ "$PODS_CONFIGURATION_BUILD_DIR/Firebase", ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( /usr/lib/swift, "$(inherited)", @@ -1983,6 +1989,7 @@ INFOPLIST_KEY_CFBundleDisplayName = Rocket.Chat; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKCompanionAppBundleIdentifier = chat.rocket.ios; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2002,7 +2009,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 8.0; + WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; @@ -2036,6 +2043,7 @@ INFOPLIST_KEY_CFBundleDisplayName = Rocket.Chat; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKCompanionAppBundleIdentifier = chat.rocket.ios; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2053,7 +2061,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 8.0; + WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; @@ -2087,7 +2095,7 @@ "$(SRCROOT)", ); INFOPLIST_FILE = NotificationService/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2140,7 +2148,7 @@ "$(SRCROOT)", ); INFOPLIST_FILE = NotificationService/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2193,7 +2201,7 @@ "$(SRCROOT)/../node_modules/react-native-mmkv/MMKV/Core/**", ); INFOPLIST_FILE = RocketChatRN/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2257,7 +2265,7 @@ "$(SRCROOT)/../node_modules/react-native-mmkv/MMKV/Core/**", ); INFOPLIST_FILE = RocketChatRN/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2348,16 +2356,25 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", "${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios", + "${PODS_CONFIGURATION_BUILD_DIR}/React-featureflags/React_featureflags.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-renderercss/React_renderercss.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = "$(inherited)"; - OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); OTHER_LDFLAGS = ( "$(inherited)", " ", ); + PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -2418,15 +2435,24 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", "${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios", + "${PODS_CONFIGURATION_BUILD_DIR}/React-featureflags/React_featureflags.framework/Headers", + "${PODS_CONFIGURATION_BUILD_DIR}/React-renderercss/React_renderercss.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CFLAGS = "$(inherited)"; - OTHER_CPLUSPLUSFLAGS = "$(inherited)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); OTHER_LDFLAGS = ( "$(inherited)", " ", ); + PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/jest.setup.js b/jest.setup.js index c82820c93b7..a438a918521 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -4,6 +4,9 @@ import { Image } from 'expo-image'; jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage); +const resolveExpoLazyFetchBeforeReactNativeMocks = () => globalThis.fetch; +resolveExpoLazyFetchBeforeReactNativeMocks(); + jest.mock('./app/lib/methods/handleMediaDownload', () => ({ ...jest.requireActual('./app/lib/methods/handleMediaDownload'), getMediaCache: jest.fn(() => Promise.resolve({ exists: false })), diff --git a/package.json b/package.json index 05b6aca3168..48fba68e228 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@react-native-firebase/crashlytics": "^21.12.2", "@react-native-masked-view/masked-view": "^0.3.1", "@react-native-picker/picker": "2.11.4", - "@react-native/codegen": "^0.80.0", + "@react-native/codegen": "0.86.3", "@react-navigation/drawer": "^7.12.2", "@react-navigation/elements": "^2.9.25", "@react-navigation/native": "^7.3.3", @@ -105,7 +105,7 @@ "react-native-incall-manager": "^4.2.1", "react-native-katex": "git+https://github.com/RocketChat/react-native-katex.git", "react-native-keyboard-controller": "1.18.5", - "react-native-linear-gradient": "2.6.2", + "react-native-linear-gradient": "2.8.3", "react-native-localize": "3.7.0", "react-native-math-view": "3.9.5", "react-native-mime-types": "2.3.0", @@ -173,9 +173,9 @@ "@react-native-community/cli": "20.0.0", "@react-native-community/cli-platform-android": "20.0.0", "@react-native-community/cli-platform-ios": "20.0.0", - "@react-native/babel-preset": "0.81.5", + "@react-native/babel-preset": "0.86.3", "@react-native/metro-config": "0.86.3", - "@react-native/typescript-config": "0.81.5", + "@react-native/typescript-config": "0.86.3", "@storybook/react": "~9.0.9", "@storybook/react-native": "~9.0.9", "@testing-library/react-native": "^13.2.0", diff --git a/patches/@react-native-camera-roll+camera-roll+7.10.2.patch b/patches/@react-native-camera-roll+camera-roll+7.10.2.patch new file mode 100644 index 00000000000..d2ea78a557f --- /dev/null +++ b/patches/@react-native-camera-roll+camera-roll+7.10.2.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/@react-native-camera-roll/camera-roll/src/NativeCameraRollModule.ts b/node_modules/@react-native-camera-roll/camera-roll/src/NativeCameraRollModule.ts +index 63e1136..634a40b 100644 +--- a/node_modules/@react-native-camera-roll/camera-roll/src/NativeCameraRollModule.ts ++++ b/node_modules/@react-native-camera-roll/camera-roll/src/NativeCameraRollModule.ts +@@ -21,8 +21,8 @@ type Album = { + id: string; + title: string; + count: number; +- type: AlbumType; +- subtype?: AlbumSubType; ++ type: string; ++ subtype?: string; + }; + + type SubTypes = +@@ -41,8 +41,8 @@ type PhotoIdentifier = { + node: { + id: string; + type: string; +- subTypes: SubTypes; +- sourceType: SourceType; ++ subTypes: string; ++ sourceType: string; + group_name: string[]; + image: { + filename: string | null; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5007156765b..ea4362fae23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,8 +73,8 @@ importers: specifier: 2.11.4 version: 2.11.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-native/codegen': - specifier: ^0.80.0 - version: 0.80.2(@babel/core@7.29.7) + specifier: 0.86.3 + version: 0.86.3(@babel/core@7.29.7) '@react-navigation/drawer': specifier: ^7.12.2 version: 7.12.2(568a64fb44ad70e6f0b55611752f41f8) @@ -253,8 +253,8 @@ importers: specifier: 1.18.5 version: 1.18.5(react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-linear-gradient: - specifier: 2.6.2 - version: 2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + specifier: 2.8.3 + version: 2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-localize: specifier: 3.7.0 version: 3.7.0(@expo/config-plugins@57.0.9(typescript@7.0.2))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -293,7 +293,7 @@ importers: version: 4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-skeleton-placeholder: specifier: 5.2.4 - version: 5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + version: 5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-svg: specifier: ^15.12.1 version: 15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -398,14 +398,14 @@ importers: specifier: 20.0.0 version: 20.0.0 '@react-native/babel-preset': - specifier: 0.81.5 - version: 0.81.5(@babel/core@7.29.7) + specifier: 0.86.3 + version: 0.86.3(@babel/core@7.29.7) '@react-native/metro-config': specifier: 0.86.3 version: 0.86.3(@babel/core@7.29.7) '@react-native/typescript-config': - specifier: 0.81.5 - version: 0.81.5 + specifier: 0.86.3 + version: 0.86.3 '@storybook/react': specifier: ~9.0.9 version: 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) @@ -1143,12 +1143,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.27.1': - resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.29.7': resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} engines: {node: '>=6.9.0'} @@ -2614,38 +2608,16 @@ packages: resolution: {integrity: sha512-TDhgCZA4wjJg84d5A9swiOQYPIWSKEEVdg9IwMFZDupQzW/F3QoLUrfAJOcalgqTDA9/buTB8awhE3Whwg6u9Q==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/babel-plugin-codegen@0.81.5': - resolution: {integrity: sha512-oF71cIH6je3fSLi6VPjjC3Sgyyn57JLHXs+mHWc9MoCiJJcM4nqsS5J38zv1XQ8d3zOW2JtHro+LF0tagj2bfQ==} - engines: {node: '>= 20.19.4'} - '@react-native/babel-plugin-codegen@0.86.3': resolution: {integrity: sha512-O6Xza4JBGPIU8J7YbKTyBoYL4thpy8jMW/oaLDWdAyOwYHKIjK47pAL5HUEbOe2bWz2PEKjbYRF2ApkJv1ottQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/babel-preset@0.81.5': - resolution: {integrity: sha512-UoI/x/5tCmi+pZ3c1+Ypr1DaRMDLI3y+Q70pVLLVgrnC3DHsHRIbHcCHIeG/IJvoeFqFM2sTdhSOLJrf8lOPrA==} - engines: {node: '>= 20.19.4'} - peerDependencies: - '@babel/core': '*' - '@react-native/babel-preset@0.86.3': resolution: {integrity: sha512-/eqs/Hy9RZRcjdcs4wj3Cqmxvtb3NM5g+Uuh1RIvsjynMO8PRsrVWWLWgBcZL/jYUo+ogXd2NFB0W8L5Bg89xw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} peerDependencies: '@babel/core': '*' - '@react-native/codegen@0.80.2': - resolution: {integrity: sha512-eYad9ex9/RS6oFbbpu6LxsczktbhfJbJlTvtRlcWLJjJbFTeNr5Q7CgBT2/m5VtpxnJ/0YdmZ9vdazsJ2yp9kw==} - engines: {node: '>=18'} - peerDependencies: - '@babel/core': '*' - - '@react-native/codegen@0.81.5': - resolution: {integrity: sha512-a2TDA03Up8lpSa9sh5VRGCQDXgCTOyDOFH+aqyinxp1HChG8uk89/G+nkJ9FPd0rqgi25eCTR16TWdS3b+fA6g==} - engines: {node: '>= 20.19.4'} - peerDependencies: - '@babel/core': '*' - '@react-native/codegen@0.86.3': resolution: {integrity: sha512-Ux4jHi0fh+bdtVEcL0gaPLbY56V+SvFUDl/8sRAE1jdb4k+o7fT/4Nc29yz4X+qfjstkSqObQTMBGhdzxH9JvA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -2706,8 +2678,8 @@ packages: '@react-native/normalize-colors@0.86.3': resolution: {integrity: sha512-Cv3CDkprb67GrzuaS9BGbBJC/6G4lIw3nyKOHRKTqTTum4bn37y5+R0Z04L8mcbQN85eEohNrRwb7IOM4j6uvg==} - '@react-native/typescript-config@0.81.5': - resolution: {integrity: sha512-NeCecPmlW+fcwFKzDzT1GcEQmJSE6tLz9Fg6wGjKL1l7pqUzpQIQg1iF3OovHOlyfPiB98+XRHnIBvlTSJ5R0w==} + '@react-native/typescript-config@0.86.3': + resolution: {integrity: sha512-HjYnjYUVP88Z/0YPGQ4ErRsEirCzccNYGuoldv6uhiK93aVnIdv7+8sbROmqcJv6BI6R+Kf0iBlN3gSG9Au+jg==} '@react-native/virtualized-lists@0.86.3': resolution: {integrity: sha512-1j44NEyNn05Ut40vHAmoSWbsIcybFkMAOBTwQt1PrESyfSS+qBoyU1LGIogNva0VIa0rQyEC5PzbA7R4/7Nhyw==} @@ -3683,9 +3655,6 @@ packages: babel-plugin-react-native-web@0.21.2: resolution: {integrity: sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==} - babel-plugin-syntax-hermes-parser@0.29.1: - resolution: {integrity: sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA==} - babel-plugin-syntax-hermes-parser@0.36.0: resolution: {integrity: sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==} @@ -5122,12 +5091,6 @@ packages: hermes-compiler@250829098.0.17: resolution: {integrity: sha512-qG1PXzTEtriF6oQLZF3vyHhSMxOdW5h2TqqLri0rdpstPustd2fSvRZQMVAPdlhgFwBfYnj3OUZtiO6LjYsEFw==} - hermes-estree@0.28.1: - resolution: {integrity: sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ==} - - hermes-estree@0.29.1: - resolution: {integrity: sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==} - hermes-estree@0.35.0: resolution: {integrity: sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==} @@ -5137,12 +5100,6 @@ packages: hermes-estree@0.36.1: resolution: {integrity: sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==} - hermes-parser@0.28.1: - resolution: {integrity: sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg==} - - hermes-parser@0.29.1: - resolution: {integrity: sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==} - hermes-parser@0.35.0: resolution: {integrity: sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==} @@ -6796,8 +6753,8 @@ packages: react-native: '*' react-native-reanimated: '>=3.0.0' - react-native-linear-gradient@2.6.2: - resolution: {integrity: sha512-Z8Xxvupsex+9BBFoSYS87bilNPWcRfRsGC0cpJk72Nxb5p2nEkGSBv73xZbEHnW2mUFvP+huYxrVvjZkr/gRjQ==} + react-native-linear-gradient@2.8.3: + resolution: {integrity: sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==} peerDependencies: react: '*' react-native: '*' @@ -8554,11 +8511,6 @@ snapshots: '@babel/core': 7.25.9 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -8947,23 +8899,12 @@ snapshots: '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.7) - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7)': dependencies: @@ -10834,14 +10775,6 @@ snapshots: '@react-native/assets-registry@0.86.3': {} - '@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.29.7)': - dependencies: - '@babel/traverse': 7.29.0 - '@react-native/codegen': 0.81.5(@babel/core@7.29.7) - transitivePeerDependencies: - - '@babel/core' - - supports-color - '@react-native/babel-plugin-codegen@0.86.3(@babel/core@7.29.7)': dependencies: '@babel/traverse': 7.29.8 @@ -10850,56 +10783,6 @@ snapshots: - '@babel/core' - supports-color - '@react-native/babel-preset@0.81.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.7) - '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.7) - '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.29.7) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.7) - '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.7) - '@babel/template': 7.28.6 - '@react-native/babel-plugin-codegen': 0.81.5(@babel/core@7.29.7) - babel-plugin-syntax-hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) - react-refresh: 0.14.2 - transitivePeerDependencies: - - supports-color - '@react-native/babel-preset@0.86.3(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -10938,29 +10821,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@react-native/codegen@0.80.2(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - glob: 7.2.3 - hermes-parser: 0.28.1 - invariant: 2.2.4 - nullthrows: 1.1.1 - yargs: 17.7.2 - - '@react-native/codegen@0.81.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.2 - glob: 7.2.3 - hermes-parser: 0.29.1 - invariant: 2.2.4 - nullthrows: 1.1.1 - yargs: 17.7.2 - '@react-native/codegen@0.86.3(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.8 hermes-parser: 0.36.0 invariant: 2.2.4 nullthrows: 1.1.1 @@ -11054,7 +10918,7 @@ snapshots: '@react-native/normalize-colors@0.86.3': {} - '@react-native/typescript-config@0.81.5': {} + '@react-native/typescript-config@0.86.3': {} '@react-native/virtualized-lists@0.86.3(@types/react@19.2.18)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: @@ -12076,10 +11940,6 @@ snapshots: babel-plugin-react-native-web@0.21.2: {} - babel-plugin-syntax-hermes-parser@0.29.1: - dependencies: - hermes-parser: 0.29.1 - babel-plugin-syntax-hermes-parser@0.36.0: dependencies: hermes-parser: 0.36.0 @@ -13755,24 +13615,12 @@ snapshots: hermes-compiler@250829098.0.17: {} - hermes-estree@0.28.1: {} - - hermes-estree@0.29.1: {} - hermes-estree@0.35.0: {} hermes-estree@0.36.0: {} hermes-estree@0.36.1: {} - hermes-parser@0.28.1: - dependencies: - hermes-estree: 0.28.1 - - hermes-parser@0.29.1: - dependencies: - hermes-estree: 0.29.1 - hermes-parser@0.35.0: dependencies: hermes-estree: 0.35.0 @@ -15898,7 +15746,7 @@ snapshots: react-native-is-edge-to-edge: 1.3.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-linear-gradient@2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + react-native-linear-gradient@2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -15977,12 +15825,12 @@ snapshots: react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) warn-once: 0.1.1 - react-native-skeleton-placeholder@5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + react-native-skeleton-placeholder@5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-linear-gradient: 2.6.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-linear-gradient: 2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-svg@15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: From 0e8a91b6219171637708090e19a2b68b2554e66c Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 16 Sep 2026 09:57:29 -0300 Subject: [PATCH 011/120] fix: update Android build for React Native 0.86 (#7695) * fix: update Android build for React Native 0.86 * fix: restore Android Gradle 9 compatibility * fix: align React Native codegen version * fix: resolve Android storage permission merge * fix: patch Android modules for React Native 0.86 * fix: restore Android local broadcast dependency * fix: build patched Expo modules from source * fix: raise iOS deployment target to 16.4 for Expo 57 * fix: materialize expo fetch global before jest mocks * fix: keep setImmediate real under jest fake timers * fix: key iOS pods cache by Podfile.lock hash * fix: allow non-modular React includes in framework modules * fix: drop undefined header values before fetch expo/fetch, installed as the global fetch on native by Expo 57, forwards header entries to the native request verbatim, so an undefined value fails the Kotlin cast to Pair and rejects the whole request. * fix: build React-Core from source instead of prebuilt * fix: match Expo import access level with generated provider * fix: adopt Expo 57 AppDelegate contract * fix: compile Hermes bytecode with the legacy compiler * fix: expose header test ids on iOS * fix: expose team channels header test id * fix: remove iOS changes from Android upgrade --- .github/actions/build-android/action.yml | 11 +------ .github/workflows/e2e-build-android.yml | 10 +----- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 4 +++ android/settings.gradle | 17 +++++++--- .../methods/helpers/__tests__/fetch.test.ts | 33 ++++++++++++------- jest.config.js | 1 + jest.setup.js | 2 ++ package.json | 12 +++++++ .../@react-native-cookies+cookies+6.2.1.patch | 20 +++++++++++ patches/expo-font+57.0.4.patch | 2 +- ...ct-native-keyboard-controller+1.18.5.patch | 26 +++++++++++++++ patches/react-native-math-view+3.9.5.patch | 32 ++++++++++++++++++ 13 files changed, 134 insertions(+), 38 deletions(-) create mode 100644 patches/@react-native-cookies+cookies+6.2.1.patch create mode 100644 patches/react-native-keyboard-controller+1.18.5.patch create mode 100644 patches/react-native-math-view+3.9.5.patch diff --git a/.github/actions/build-android/action.yml b/.github/actions/build-android/action.yml index 86c531d52c9..022d586cd6a 100644 --- a/.github/actions/build-android/action.yml +++ b/.github/actions/build-android/action.yml @@ -45,17 +45,8 @@ runs: - name: Set gradle.properties working-directory: android run: | - echo "" > ./gradle.properties - echo -e "org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8" >> ./gradle.properties - echo -e "android.useAndroidX=true" >> ./gradle.properties - echo -e "android.enableJetifier=true" >> ./gradle.properties - echo -e "android.jetifier.ignorelist=bcprov-jdk18on" >> ./gradle.properties - echo -e "reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64" >> ./gradle.properties - echo -e "newArchEnabled=true" >> ./gradle.properties - echo -e "hermesEnabled=true" >> ./gradle.properties - echo -e "edgeToEdgeEnabled=true" >> ./gradle.properties + printf '\n' >> ./gradle.properties echo -e "VERSIONCODE=${{ inputs.BUILD_VERSION }}" >> ./gradle.properties - echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties echo -e "BugsnagAPIKey=${{ inputs.BUGSNAG_KEY_OFFICIAL }}" >> ./gradle.properties echo -e "KEYSTORE=${{ inputs.KEYSTORE_OFFICIAL }}" >> ./gradle.properties echo -e 'KEYSTORE_PASSWORD=${{ inputs.KEYSTORE_OFFICIAL_PASSWORD }}' >> ./gradle.properties diff --git a/.github/workflows/e2e-build-android.yml b/.github/workflows/e2e-build-android.yml index 93ddf48c31c..89455a5af67 100644 --- a/.github/workflows/e2e-build-android.yml +++ b/.github/workflows/e2e-build-android.yml @@ -103,16 +103,8 @@ jobs: - name: Set gradle.properties working-directory: android run: | - echo "" > ./gradle.properties - echo -e "org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8" >> ./gradle.properties - echo -e "android.useAndroidX=true" >> ./gradle.properties - echo -e "android.enableJetifier=true" >> ./gradle.properties - echo -e "android.jetifier.ignorelist=bcprov-jdk18on" >> ./gradle.properties + printf '\n' >> ./gradle.properties echo -e "reactNativeArchitectures=arm64-v8a,x86_64" >> ./gradle.properties - echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties - echo -e "newArchEnabled=true" >> ./gradle.properties - echo -e "hermesEnabled=true" >> ./gradle.properties - echo -e "edgeToEdgeEnabled=true" >> ./gradle.properties echo -e "VERSIONCODE=999999" >> ./gradle.properties echo -e "KEYSTORE=release.keystore" >> ./gradle.properties echo -e 'KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_OFFICIAL_PASSWORD }}' >> ./gradle.properties diff --git a/android/app/build.gradle b/android/app/build.gradle index 708ee991747..ffbf6561738 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -144,7 +144,7 @@ dependencies { implementation "com.google.code.gson:gson:2.8.9" implementation "io.github.zhongwuzw:mmkv:2.4.1" implementation "com.github.bumptech.glide:glide:${rootProject.ext.glideVersion}" - implementation 'com.facebook.soloader:soloader:0.10.4' + implementation "androidx.localbroadcastmanager:localbroadcastmanager:1.1.0" // For SecureKeystore (EncryptedSharedPreferences) implementation 'androidx.security:security-crypto:1.1.0' diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0e9fdce9400..c6a739a6ef3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,10 @@ + diff --git a/android/settings.gradle b/android/settings.gradle index 9bb0709a8db..291937214cc 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,6 +1,14 @@ -pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } -plugins { id("com.facebook.react.settings") } -extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } +pluginManagement { + includeBuild("../node_modules/@react-native/gradle-plugin") + includeBuild("../node_modules/expo-modules-autolinking/android/expo-gradle-plugin") +} +plugins { + id("com.facebook.react.settings") + id("expo-autolinking-settings") +} +extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> + ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand) +} rootProject.name = 'RocketChatRN' include ':watermelondb-jsi' project(':watermelondb-jsi').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android-jsi') @@ -8,5 +16,4 @@ include ':app' includeBuild('../node_modules/@react-native/gradle-plugin') -apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); -useExpoModules() \ No newline at end of file +expoAutolinking.useExpoModules() diff --git a/app/lib/methods/helpers/__tests__/fetch.test.ts b/app/lib/methods/helpers/__tests__/fetch.test.ts index 54be37e9046..3b720b987bf 100644 --- a/app/lib/methods/helpers/__tests__/fetch.test.ts +++ b/app/lib/methods/helpers/__tests__/fetch.test.ts @@ -1,29 +1,38 @@ import fetchWithHeaders, { headers } from '../fetch'; -describe('fetch helper', () => { - const globalFetch = jest.fn().mockResolvedValue({} as Response); +jest.mock('react-native-device-info', () => ({ + getSystemVersion: () => '14', + getVersion: () => '4.0.0', + getBuildNumber: () => '1' +})); + +const fetchMock = jest.fn(() => Promise.resolve({} as Response)); +describe('fetch helper', () => { beforeEach(() => { - globalFetch.mockClear(); - globalThis.fetch = globalFetch; + fetchMock.mockClear(); + global.fetch = fetchMock as unknown as typeof global.fetch; }); it('drops headers whose value is undefined or null', async () => { - await fetchWithHeaders('https://example.com/api/info', { + await fetchWithHeaders('https://open.rocket.chat/api/info', { method: 'GET', headers: { 'Content-Type': 'application/json', 'X-Auth-Token': undefined, 'X-User-Id': null as unknown as undefined } }); - const [, options] = globalFetch.mock.calls[0]; - expect(options.headers).toEqual({ 'Content-Type': 'application/json', ...headers }); + expect(fetchMock).toHaveBeenCalledWith('https://open.rocket.chat/api/info', { + method: 'GET', + headers: { 'Content-Type': 'application/json', 'User-Agent': headers['User-Agent'] } + }); }); - it('keeps defined caller headers alongside the default ones', async () => { - await fetchWithHeaders('https://example.com/api/info', { - headers: { 'X-Auth-Token': 'token', 'X-User-Id': 'id' } + it('keeps headers whose value is defined', async () => { + await fetchWithHeaders('https://open.rocket.chat/api/info', { + headers: { 'X-Auth-Token': 'token', 'X-User-Id': 'userId' } }); - const [, options] = globalFetch.mock.calls[0]; - expect(options.headers).toEqual({ 'X-Auth-Token': 'token', 'X-User-Id': 'id', ...headers }); + expect(fetchMock).toHaveBeenCalledWith('https://open.rocket.chat/api/info', { + headers: { 'X-Auth-Token': 'token', 'X-User-Id': 'userId', 'User-Agent': headers['User-Agent'] } + }); }); }); diff --git a/jest.config.js b/jest.config.js index e9ef674db8f..04500372058 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,7 @@ const getAliasConfig = require('./config/import-aliases'); module.exports = { maxWorkers: 2, + fakeTimers: { doNotFake: ['setImmediate'] }, workerIdleMemoryLimit: '512MB', modulePathIgnorePatterns: ['/.*worktrees/'], testPathIgnorePatterns: [ diff --git a/jest.setup.js b/jest.setup.js index a438a918521..e4b82ae1c42 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,3 +1,5 @@ +Object.defineProperty(globalThis, 'fetch', { value: globalThis.fetch, writable: true, configurable: true }); + import mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock.js'; import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock'; import { Image } from 'expo-image'; diff --git a/package.json b/package.json index 48fba68e228..d515c5f29cb 100644 --- a/package.json +++ b/package.json @@ -215,6 +215,18 @@ "typescript": "7.0.2" }, "snyk": true, + "expo": { + "autolinking": { + "android": { + "buildFromSource": [ + "expo-asset", + "expo-audio", + "expo-file-system", + "expo-font" + ] + } + } + }, "engines": { "node": ">=22.13.0" }, diff --git a/patches/@react-native-cookies+cookies+6.2.1.patch b/patches/@react-native-cookies+cookies+6.2.1.patch new file mode 100644 index 00000000000..d0a8cee9581 --- /dev/null +++ b/patches/@react-native-cookies+cookies+6.2.1.patch @@ -0,0 +1,20 @@ +diff --git a/node_modules/@react-native-cookies/cookies/android/build.gradle b/node_modules/@react-native-cookies/cookies/android/build.gradle +index 13c7e43..626fb5a 100644 +--- a/node_modules/@react-native-cookies/cookies/android/build.gradle ++++ b/node_modules/@react-native-cookies/cookies/android/build.gradle +@@ -31,7 +31,6 @@ buildscript { + if (project == rootProject) { + repositories { + google() +- jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.3' +@@ -67,7 +66,6 @@ repositories { + url "$rootDir/../node_modules/jsc-android/dist" + } + google() +- jcenter() + } + + dependencies { diff --git a/patches/expo-font+57.0.4.patch b/patches/expo-font+57.0.4.patch index 4735b243f74..14a4f88ce5a 100644 --- a/patches/expo-font+57.0.4.patch +++ b/patches/expo-font+57.0.4.patch @@ -26,7 +26,7 @@ index 2cd51dc..db368ff 100644 + val typeface = runCatching { Typeface.createFromAsset(context.assets, candidate) }.getOrNull() + if (typeface != null) { + ReactFontManager.getInstance().setTypeface(fontFamilyName, Typeface.NORMAL, typeface) -+ loadedFonts = loadedFonts.toMutableSet().apply { add(fontFamilyName) }.toList() ++ loadedFonts = getLoadedFonts().toMutableSet().apply { add(fontFamilyName) }.toList() + return@AsyncFunction // Exit early if we successfully loaded the font + } + } diff --git a/patches/react-native-keyboard-controller+1.18.5.patch b/patches/react-native-keyboard-controller+1.18.5.patch new file mode 100644 index 00000000000..82f20514f2e --- /dev/null +++ b/patches/react-native-keyboard-controller+1.18.5.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +index e13177c..45bc4e6 100644 +--- a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt ++++ b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +@@ -91,7 +91,7 @@ class EdgeToEdgeReactViewGroup( + this.deactivate() + } + +- override fun onConfigurationChanged(newConfig: Configuration?) { ++ override fun onConfigurationChanged(newConfig: Configuration) { + this.reApplyWindowInsets() + } + // endregion +diff --git a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/background/KeyboardBackgroundViewGroup.kt b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/background/KeyboardBackgroundViewGroup.kt +index f19514f..8de0cf7 100644 +--- a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/background/KeyboardBackgroundViewGroup.kt ++++ b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/background/KeyboardBackgroundViewGroup.kt +@@ -17,7 +17,7 @@ class KeyboardBackgroundViewGroup( + } + + // theme changed +- override fun onConfigurationChanged(newConfig: Configuration?) { ++ override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + super.setBackgroundColor(reactContext.getInputMethodColor()) + } diff --git a/patches/react-native-math-view+3.9.5.patch b/patches/react-native-math-view+3.9.5.patch new file mode 100644 index 00000000000..3b809f692be --- /dev/null +++ b/patches/react-native-math-view+3.9.5.patch @@ -0,0 +1,32 @@ +diff --git a/node_modules/react-native-math-view/android/build.gradle b/node_modules/react-native-math-view/android/build.gradle +index d960a90..ddf56d7 100644 +--- a/node_modules/react-native-math-view/android/build.gradle ++++ b/node_modules/react-native-math-view/android/build.gradle +@@ -31,7 +31,6 @@ repositories { + buildscript { + repositories { + maven { url "https://maven.google.com" } +- jcenter() + google() + maven { + url 'https://maven.google.com/' +@@ -51,7 +50,6 @@ allprojects { + repositories { + mavenLocal() + maven { url "https://maven.google.com" } +- jcenter() + google() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm +diff --git a/node_modules/react-native-math-view/android/src/main/java/io/autodidact/rnmathview/SVGShadowNode.java b/node_modules/react-native-math-view/android/src/main/java/io/autodidact/rnmathview/SVGShadowNode.java +index e3719e6..76be8a4 100644 +--- a/node_modules/react-native-math-view/android/src/main/java/io/autodidact/rnmathview/SVGShadowNode.java ++++ b/node_modules/react-native-math-view/android/src/main/java/io/autodidact/rnmathview/SVGShadowNode.java +@@ -11,7 +11,6 @@ import com.facebook.react.uimanager.NativeViewHierarchyManager; + import com.facebook.react.uimanager.ReactStylesDiffMap; + import com.facebook.react.uimanager.UIBlock; + import com.facebook.react.uimanager.UIManagerModule; +-import com.facebook.react.uimanager.UIManagerModuleListener; + import com.facebook.react.uimanager.annotations.ReactProp; + import com.facebook.yoga.YogaMeasureFunction; + import com.facebook.yoga.YogaMeasureMode; From 50702636d0764a9efe5aa990583bf5ba10ad9a4e Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 16 Sep 2026 11:09:20 -0300 Subject: [PATCH 012/120] ci: make TypeScript check blocking again --- .github/workflows/eslint.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 198f70d4188..36a6c5ce89e 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -22,7 +22,6 @@ jobs: run: pnpm exec oxlint - name: Run TypeScript - continue-on-error: true run: pnpm exec tsc - name: Run Tests From a12e23b65909b3d83e20b36104d679dd0044fb6b Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 16 Sep 2026 12:08:10 -0300 Subject: [PATCH 013/120] fix: restore default submit behavior on login email field --- app/views/LoginView/UserForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/LoginView/UserForm.tsx b/app/views/LoginView/UserForm.tsx index f979dfc87f2..aab9a7b6a1d 100644 --- a/app/views/LoginView/UserForm.tsx +++ b/app/views/LoginView/UserForm.tsx @@ -111,7 +111,6 @@ const UserForm = () => { placeholder={Accounts_EmailOrUsernamePlaceholder} keyboardType='email-address' returnKeyType='next' - submitBehavior='submit' onSubmitEditing={() => setFocus('password')} testID='login-view-email' textContentType='username' From d0fcbadfed3a84ff4c281cf53d60bbf235a249ff Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 16 Sep 2026 13:38:39 -0300 Subject: [PATCH 014/120] fix: keep test identifiers on iOS accessibility elements --- app/containers/EmojiPicker/index.tsx | 12 +- .../MessageComposer.test.tsx.snap | 510 +++++++++--------- 2 files changed, 273 insertions(+), 249 deletions(-) diff --git a/app/containers/EmojiPicker/index.tsx b/app/containers/EmojiPicker/index.tsx index 52fb60422bf..dd6a875648b 100644 --- a/app/containers/EmojiPicker/index.tsx +++ b/app/containers/EmojiPicker/index.tsx @@ -49,15 +49,9 @@ const EmojiPicker = ({ ); const renderTabItem = (tab: Route, color: string) => ( - + + + ); return ( diff --git a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap index 544ac12a650..3c76e098373 100644 --- a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap +++ b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap @@ -9993,37 +9993,40 @@ exports[`MessageComposer Toolbar tap emoji 1`] = ` } } > - + -  - + {}, + ] + } + > +  + + - + -  - + {}, + ] + } + > +  + + - + -  - + {}, + ] + } + > +  + + - + -  - + {}, + ] + } + > +  + + - + -  - + {}, + ] + } + > +  + + - + -  - + {}, + ] + } + > +  + + - + -  - + {}, + ] + } + > +  + + - + -  - + {}, + ] + } + > +  + + - + -  - + {}, + ] + } + > +  + + - + -  - + {}, + ] + } + > +  + + Date: Wed, 16 Sep 2026 13:47:49 -0300 Subject: [PATCH 015/120] fix: expose test identifiers on accessible elements --- .../components/HeaderButton/HeaderButtonItem.tsx | 1 + app/containers/RoomItem/RoomItem.tsx | 1 + app/containers/RoomItem/Wrapper.tsx | 10 +++++++++- app/containers/RoomItem/interfaces.ts | 1 + app/containers/Touch.tsx | 3 +++ 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/containers/Header/components/HeaderButton/HeaderButtonItem.tsx b/app/containers/Header/components/HeaderButton/HeaderButtonItem.tsx index 7c7c0a3224b..02f0d3f97c3 100644 --- a/app/containers/Header/components/HeaderButton/HeaderButtonItem.tsx +++ b/app/containers/Header/components/HeaderButton/HeaderButtonItem.tsx @@ -58,6 +58,7 @@ const Item = memo( { +const Wrapper = ({ + accessibilityLabel, + accessibilityHint, + children, + displayMode, + testID, + ...props +}: IWrapperProps): ReactElement => { const { colors } = useTheme(); const { rowHeight, rowHeightCondensed } = useResponsiveLayout(); return ( @@ -16,6 +23,7 @@ const Wrapper = ({ accessibilityLabel, accessibilityHint, children, displayMode, style={[styles.container, { height: displayMode === DisplayMode.Condensed ? rowHeightCondensed : rowHeight }]} accessibilityLabel={accessibilityLabel} accessibilityHint={accessibilityHint} + testID={testID} accessible accessibilityRole='button'> diff --git a/app/containers/RoomItem/interfaces.ts b/app/containers/RoomItem/interfaces.ts index 23016f9df87..f269cdd7219 100644 --- a/app/containers/RoomItem/interfaces.ts +++ b/app/containers/RoomItem/interfaces.ts @@ -41,6 +41,7 @@ export interface IUpdatedAtProps { export interface IWrapperProps { accessibilityLabel: string; + testID?: string; accessibilityHint?: string; avatar: string; type: string; diff --git a/app/containers/Touch.tsx b/app/containers/Touch.tsx index ce3e0e16a29..4632f2d9dc5 100644 --- a/app/containers/Touch.tsx +++ b/app/containers/Touch.tsx @@ -37,6 +37,7 @@ const Touch = forwardRef( accessibilityHint, accessibilityActions, onAccessibilityAction, + testID, style, rectButtonStyle, disabled, @@ -85,9 +86,11 @@ const Touch = forwardRef( focusable={!disabled} canBeFocused={!disabled} style={[rectButtonStyle, marginStyles, { backgroundColor, borderRadius }]} + testID={testID} {...props} enabled={!disabled}> Date: Wed, 16 Sep 2026 13:56:26 -0300 Subject: [PATCH 016/120] fix: keep a single test identifier per element --- .../__snapshots__/DirectoryItem.test.tsx.snap | 16 +-- .../HeaderButton/HeaderButtonItem.tsx | 2 +- .../NotifierComponent.test.tsx.snap | 8 +- .../MediaCallHeader.test.tsx.snap | 24 ++-- .../Base/__snapshots__/Base.test.tsx.snap | 136 +++++++++--------- app/containers/RoomItem/RoomItem.tsx | 1 - app/containers/RoomItem/Touchable.tsx | 2 - app/containers/RoomItem/interfaces.ts | 1 - .../__snapshots__/ServerItem.test.tsx.snap | 16 +-- .../__snapshots__/TextInput.test.tsx.snap | 2 +- app/containers/Touch.tsx | 1 - .../__snapshots__/UiKitModal.test.tsx.snap | 2 +- .../__snapshots__/Item.test.tsx.snap | 16 +-- .../ServersHistoryItem.test.tsx.snap | 16 +-- .../__snapshots__/Item.test.tsx.snap | 24 ++-- .../ChangePasscodeView.test.tsx.snap | 44 +++--- .../ScreenLockedView.test.tsx.snap | 92 ++++++------ 17 files changed, 199 insertions(+), 204 deletions(-) diff --git a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap index 8f7883c8591..d5761084a08 100644 --- a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap +++ b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap @@ -70,7 +70,6 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` }, ] } - testID="directory-item-style" underlayColor="#E4E7EA" > { const { colors } = useTheme(); return ( - + diff --git a/app/containers/RoomItem/interfaces.ts b/app/containers/RoomItem/interfaces.ts index f269cdd7219..b748aee603a 100644 --- a/app/containers/RoomItem/interfaces.ts +++ b/app/containers/RoomItem/interfaces.ts @@ -143,7 +143,6 @@ export interface ILastMessageProps { export interface ITouchableProps extends IRoomItemTouchables { children: ReactElement; type: SubscriptionType; - testID: string; width: number; favorite: boolean; isRead: boolean; diff --git a/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap b/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap index 8c065cdf87f..1a4b92c41a3 100644 --- a/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap +++ b/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap @@ -58,7 +58,6 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` }, ] } - testID="server-item-https://open.rocket.chat/" underlayColor="#E4E7EA" > ( focusable={!disabled} canBeFocused={!disabled} style={[rectButtonStyle, marginStyles, { backgroundColor, borderRadius }]} - testID={testID} {...props} enabled={!disabled}> Date: Wed, 16 Sep 2026 15:09:32 -0300 Subject: [PATCH 017/120] fix: remove OrSeparator from accessibility focus chain --- .../LoginServices/__snapshots__/LoginServices.test.tsx.snap | 6 ++++-- app/containers/OrSeparator.tsx | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/containers/LoginServices/__snapshots__/LoginServices.test.tsx.snap b/app/containers/LoginServices/__snapshots__/LoginServices.test.tsx.snap index da8fdb96eda..76071c20e70 100644 --- a/app/containers/LoginServices/__snapshots__/LoginServices.test.tsx.snap +++ b/app/containers/LoginServices/__snapshots__/LoginServices.test.tsx.snap @@ -76,7 +76,7 @@ exports[`Story Snapshots: Separators should match snapshot 1`] = ` , , { const text = { color: colors.fontSecondaryInfo }; return ( - + - {I18n.t('OR')} + + {I18n.t('OR')} + ); From 6c6e7a2f5e4df7f4e8eb7d4b184f1d22cd34dd21 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 10:14:03 -0300 Subject: [PATCH 018/120] chore(ios): re-enable prebuilt React-Core and Hermes V1 (#7698) Removed the three Podfile opt-outs (RCT_USE_PREBUILT_RNCORE, RCT_USE_RN_DEP, RCT_HERMES_V1_ENABLED) and the HERMES_CLI_PATH post_install override, letting React Native 0.86 use its prebuilt React-Core/ReactNativeDependencies tarball and Hermes V1 (250829098.0.17) instead of building from source. Dropped the RCTViewManager accessibility-role patch: it only touched the Paper renderer, the app runs on Fabric, and the file no longer compiles under prebuilt Core. VoiceOver role-update behavior is covered by manual smoke testing, not this patch. RNFB pods forced to static libraries in the pre_install hook to avoid a non-modular-include failure on RNFB 21.x; the fix ships in react-native-firebase >= 26.3.0, so this can be dropped on that bump. SocketRocket now ships vendored inside the ReactNativeDependencies xcframework under a namespaced header path, so SSLPinning.mm's import changed from a flat "SRWebSocket.h" to . Copy Pods Resources phases regenerated by pod install, dropping the boost/glog/RCT-Folly privacy bundles that no longer exist as separate pods. --- ios/Libraries/SSLPinning.mm | 2 +- ios/Podfile | 15 +- ios/Podfile.lock | 2035 ++++++---------------- ios/RocketChat.xcodeproj/project.pbxproj | 42 +- patches/react-native+0.86.3.patch | 24 - 5 files changed, 576 insertions(+), 1542 deletions(-) delete mode 100644 patches/react-native+0.86.3.patch diff --git a/ios/Libraries/SSLPinning.mm b/ios/Libraries/SSLPinning.mm index 3ff922eecc5..d99bbb79015 100644 --- a/ios/Libraries/SSLPinning.mm +++ b/ios/Libraries/SSLPinning.mm @@ -9,7 +9,7 @@ #import #import "SSLPinning.h" #import "../Shared/RocketChat/MMKVBridge.h" -#import "SRWebSocket.h" +#import #import "EXSessionTaskDispatcher.h" @implementation RCTHTTPRequestHandler (Challenge) diff --git a/ios/Podfile b/ios/Podfile index 028fecd8aba..9ad0df8bc26 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,10 +1,6 @@ require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") -ENV['RCT_USE_PREBUILT_RNCORE'] = '0' -ENV['RCT_USE_RN_DEP'] = '0' -ENV['RCT_HERMES_V1_ENABLED'] = '0' - platform :ios, '16.4' install! 'cocoapods', :deterministic_uuids => false @@ -49,7 +45,7 @@ $static_framework = [ pre_install do |installer| Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} installer.pod_targets.each do |pod| - if $static_framework.include?(pod.name) + if $static_framework.include?(pod.name) || pod.name.start_with?('RNFB') def pod.build_type; Pod::BuildType.static_library # >= 1.9 end @@ -82,15 +78,6 @@ post_install do |installer| end end - if ENV['RCT_HERMES_V1_ENABLED'] == '0' - installer.aggregate_targets.each do |aggregate_target| - aggregate_target.xcconfigs.each do |config_name, xcconfig| - xcconfig.attributes['HERMES_CLI_PATH'] = '$(PODS_ROOT)/hermes-engine/destroot/bin/hermesc' - xcconfig.save_as(aggregate_target.xcconfig_path(config_name)) - end - end - end - bitcode_strip_path = `xcrun --find bitcode_strip`.chop! def strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path) framework_path = File.join(Dir.pwd, framework_relative_path) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 72e068a646e..65e167a9dd9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,17 +1,10 @@ PODS: - - boost (1.84.0) - BugsnagReactNative (8.4.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -26,29 +19,22 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - BVLinearGradient (2.8.3): - React-Core - - DoubleConversion (1.1.6) - EXApplication (57.0.3): - ExpoModulesCore - EXConstants (57.0.18): - ExpoModulesCore - Expo (57.0.23): - - boost - - DoubleConversion - ExpoModulesCore - ExpoModulesJSI - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -65,7 +51,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - ExpoAppleAuthentication (57.0.2): - ExpoModulesCore @@ -105,18 +91,12 @@ PODS: - ExpoLogBox (57.0.4): - React-Core - ExpoModulesCore (57.0.18): - - boost - - DoubleConversion - ExpoModulesJSI - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -132,7 +112,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - ExpoModulesJSI (57.1.0): - React-Core @@ -157,7 +137,6 @@ PODS: - ExpoModulesCore - ExpoWebBrowser (57.0.3): - ExpoModulesCore - - fast_float (8.0.0) - FBLazyVector (0.86.3) - Firebase/AnalyticsWithoutAdIdSupport (11.11.0): - Firebase/CoreOnly @@ -208,8 +187,6 @@ PODS: - GoogleUtilities/UserDefaults (~> 8.0) - nanopb (~> 3.30910.0) - PromisesSwift (~> 2.1) - - fmt (12.1.0) - - glog (0.3.5) - GoogleAppMeasurement/WithoutAdIdSupport (11.11.0): - GoogleUtilities/AppDelegateSwizzler (~> 8.0) - GoogleUtilities/MethodSwizzler (~> 8.0) @@ -246,9 +223,9 @@ PODS: - GoogleUtilities/UserDefaults (8.1.0): - GoogleUtilities/Logger - GoogleUtilities/Privacy - - hermes-engine (0.17.0): - - hermes-engine/Pre-built (= 0.17.0) - - hermes-engine/Pre-built (0.17.0) + - hermes-engine (250829098.0.17): + - hermes-engine/Pre-built (= 250829098.0.17) + - hermes-engine/Pre-built (250829098.0.17) - JitsiWebRTC (124.0.2) - libavif/core (1.0.0) - libavif/libdav1d (1.0.0): @@ -271,17 +248,11 @@ PODS: - MMKVCore (~> 2.4.0) - MMKVCore (2.4.0) - MobileCrypto (0.4.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -296,7 +267,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - nanopb (3.30910.0): - nanopb/decode (= 3.30910.0) @@ -304,20 +275,14 @@ PODS: - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) - NitroMmkv (4.3.2): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - MMKVCore (= 2.4.0) - NitroModules - - RCT-Folly - - RCT-Folly/Fabric - RCTRequired - RCTTypeSafety - React-callinvoker - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -332,21 +297,15 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - NitroModules (0.37.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-callinvoker - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -361,30 +320,11 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - PromisesObjC (2.4.0) - PromisesSwift (2.4.0): - PromisesObjC (= 2.4.0) - - RCT-Folly (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 8.0.0) - - fmt (= 12.1.0) - - glog - - RCT-Folly/Default (= 2024.11.18.00) - - RCT-Folly/Default (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 8.0.0) - - fmt (= 12.1.0) - - glog - - RCT-Folly/Fabric (2024.11.18.00): - - boost - - DoubleConversion - - fast_float (= 8.0.0) - - fmt (= 12.1.0) - - glog - RCTDeprecation (0.86.3) - RCTRequired (0.86.3) - RCTSwiftUI (0.86.3) @@ -409,15 +349,9 @@ PODS: - React-RCTVibration (= 0.86.3) - React-callinvoker (0.86.3) - React-Core (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default (= 0.86.3) - React-cxxreact - React-featureflags @@ -431,18 +365,14 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga + - React-Core-prebuilt (0.86.3): + - ReactNativeDependencies - React-Core/CoreModulesHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -456,18 +386,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/Default (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-cxxreact - React-featureflags - React-hermes @@ -480,18 +404,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/DevSupport (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default (= 0.86.3) - React-Core/RCTWebSocket (= 0.86.3) - React-cxxreact @@ -506,18 +424,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTActionSheetHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -531,18 +443,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTAnimationHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -556,18 +462,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTBlobHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -581,18 +481,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTImageHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -606,18 +500,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTLinkingHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -631,18 +519,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTNetworkHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -656,18 +538,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTSettingsHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -681,18 +557,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTTextHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -706,18 +576,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTVibrationHeaders (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default - React-cxxreact - React-featureflags @@ -731,18 +595,12 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-Core/RCTWebSocket (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTDeprecation + - React-Core-prebuilt - React-Core/Default (= 0.86.3) - React-cxxreact - React-featureflags @@ -756,17 +614,11 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-CoreModules (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety (= 0.86.3) + - React-Core-prebuilt - React-Core/CoreModulesHeaders (= 0.86.3) - React-debug - React-featureflags @@ -781,17 +633,11 @@ PODS: - React-runtimeexecutor - React-utils - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-cxxreact (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker (= 0.86.3) + - React-Core-prebuilt - React-debug (= 0.86.3) - React-jsi (= 0.86.3) - React-jsinspector @@ -802,19 +648,13 @@ PODS: - React-runtimeexecutor - React-timing (= 0.86.3) - React-utils - - SocketRocket + - ReactNativeDependencies - React-debug (0.86.3): - React-debug/redbox (= 0.86.3) - React-debug/redbox (0.86.3) - React-defaultsnativemodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-domnativemodule - React-Fabric/animated - React-featureflags @@ -828,17 +668,11 @@ PODS: - React-RCTFBReactNativeSpec - React-viewtransitionnativemodule - React-webperformancenativemodule - - SocketRocket + - ReactNativeDependencies - Yoga - React-domnativemodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-Fabric - React-Fabric/bridging - React-FabricComponents @@ -848,20 +682,14 @@ PODS: - React-RCTFBReactNativeSpec - React-runtimeexecutor - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-Fabric (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/animated (= 0.86.3) @@ -893,19 +721,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/animated (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/animationbackend @@ -919,19 +741,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/animationbackend (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -944,19 +760,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/animations (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -969,19 +779,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/attributedstring (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -994,19 +798,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/bridging (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1019,19 +817,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/componentregistry (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1044,19 +836,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/componentregistrynative (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1069,19 +855,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/components/legacyviewmanagerinterop (= 0.86.3) @@ -1098,19 +878,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components/legacyviewmanagerinterop (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1123,19 +897,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components/root (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1148,19 +916,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components/scrollview (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1173,19 +935,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/components/view (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1199,20 +955,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-Fabric/consistency (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1225,19 +975,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/core (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1250,19 +994,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/dom (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1275,19 +1013,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/imagemanager (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1300,19 +1032,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/leakchecker (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1325,19 +1051,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/mounting (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1351,19 +1071,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/observers (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/observers/events (= 0.86.3) @@ -1379,19 +1093,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/observers/events (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1404,19 +1112,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/observers/intersection (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1429,19 +1131,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/observers/mutation (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1454,19 +1150,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/scheduler (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/animationbackend @@ -1484,19 +1174,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/telemetry (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1509,19 +1193,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/uimanager (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric/uimanager/consistency (= 0.86.3) @@ -1536,19 +1214,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/uimanager/consistency (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1562,19 +1234,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-Fabric/viewtransition (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -1587,19 +1253,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-FabricComponents (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1615,20 +1275,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1653,20 +1307,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/inputaccessory (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1680,20 +1328,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/iostextinput (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1707,20 +1349,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/modal (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1734,20 +1370,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/rncore (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1761,20 +1391,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/safeareaview (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1788,20 +1412,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/scrollview (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1815,20 +1433,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/switch (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1842,20 +1454,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/text (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1869,20 +1475,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/textinput (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1896,20 +1496,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/unimplementedview (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1923,20 +1517,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/components/virtualview (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1950,20 +1538,14 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricComponents/textlayoutmanager (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-Fabric @@ -1977,19 +1559,13 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-FabricImage (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired (= 0.86.3) - RCTTypeSafety (= 0.86.3) + - React-Core-prebuilt - React-Fabric - React-featureflags - React-graphics @@ -2000,56 +1576,32 @@ PODS: - React-rendererdebug - React-utils - ReactCommon - - SocketRocket + - ReactNativeDependencies - Yoga - React-featureflags (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - React-Core-prebuilt + - ReactNativeDependencies - React-featureflagsnativemodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-featureflags - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-graphics (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-featureflags - React-jsi - React-jsiexecutor - React-rendererdebug - React-utils - - SocketRocket + - ReactNativeDependencies - React-hermes (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact (= 0.86.3) - React-jsi - React-jsiexecutor (= 0.86.3) @@ -2060,47 +1612,29 @@ PODS: - React-oscompat - React-perflogger (= 0.86.3) - React-runtimeexecutor - - SocketRocket + - ReactNativeDependencies - React-idlecallbacksnativemodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec - React-runtimeexecutor - React-runtimescheduler - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-ImageManager (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-Core/Default - React-debug - React-Fabric - React-graphics - React-rendererdebug - React-utils - - SocketRocket + - ReactNativeDependencies - React-intersectionobservernativemodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact - React-Fabric - React-Fabric/bridging @@ -2111,42 +1645,24 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-jserrorhandler (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags - React-jsi - ReactCommon/turbomodule/bridging - - SocketRocket + - ReactNativeDependencies - React-jsi (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - hermes-engine + - React-Core-prebuilt + - ReactNativeDependencies - React-jsiexecutor (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact - React-debug - React-jserrorhandler @@ -2158,16 +1674,10 @@ PODS: - React-perflogger - React-runtimeexecutor - React-utils - - SocketRocket + - ReactNativeDependencies - React-jsinspector (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-featureflags - React-jsi - React-jsinspectorcdp @@ -2177,50 +1687,26 @@ PODS: - React-perflogger (= 0.86.3) - React-runtimeexecutor - React-utils - - SocketRocket + - ReactNativeDependencies - React-jsinspectorcdp (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - React-Core-prebuilt + - ReactNativeDependencies - React-jsinspectornetwork (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-jsinspectorcdp - - SocketRocket + - ReactNativeDependencies - React-jsinspectortracing (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-jsi - React-jsinspectornetwork - React-oscompat - React-timing - React-utils - - SocketRocket + - ReactNativeDependencies - React-jsitooling (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact (= 0.86.3) - React-debug - React-jsi (= 0.86.3) @@ -2229,51 +1715,27 @@ PODS: - React-jsinspectortracing - React-runtimeexecutor - React-utils - - SocketRocket + - ReactNativeDependencies - React-jsitracing (0.86.3): - React-jsi - React-logger (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - React-Core-prebuilt + - ReactNativeDependencies - React-Mapbuffer (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-debug - - SocketRocket + - ReactNativeDependencies - React-microtasksnativemodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-jsi - React-jsiexecutor - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-mutationobservernativemodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact - React-Fabric - React-Fabric/bridging @@ -2284,20 +1746,14 @@ PODS: - React-RCTFBReactNativeSpec - React-runtimeexecutor - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-a11y-order (0.4.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2312,20 +1768,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-cameraroll (7.10.2): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2340,22 +1790,16 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-cookies (6.2.1): - React-Core - react-native-external-keyboard (0.9.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2370,20 +1814,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-keyboard-controller (1.18.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2399,20 +1837,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-keyboard-controller/common (1.18.5): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2427,20 +1859,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-netinfo (12.0.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2455,20 +1881,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-safe-area-context (5.7.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2485,20 +1905,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-safe-area-context/common (5.7.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2513,20 +1927,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-safe-area-context/fabric (5.7.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2542,20 +1950,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-slider (5.2.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2571,20 +1973,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-slider/common (5.2.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2599,23 +1995,17 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - react-native-webrtc (124.0.7): - JitsiWebRTC (~> 124.0.0) - React-Core - react-native-webview (13.16.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2630,19 +2020,13 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-NativeModulesApple (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker - React-Core + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -2652,69 +2036,39 @@ PODS: - React-runtimeexecutor - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - React-networking (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-jsinspectornetwork - React-jsinspectortracing - React-performancetimeline - React-timing - - SocketRocket + - ReactNativeDependencies - React-oscompat (0.86.3) - React-perflogger (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - - SocketRocket + - React-Core-prebuilt + - ReactNativeDependencies - React-performancecdpmetrics (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-jsi - React-performancetimeline - React-runtimeexecutor - React-timing - - SocketRocket + - ReactNativeDependencies - React-performancetimeline (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-featureflags - React-jsinspector - React-jsinspectortracing - React-perflogger - React-timing - - SocketRocket + - ReactNativeDependencies - React-RCTActionSheet (0.86.3): - React-Core/RCTActionSheetHeaders (= 0.86.3) - React-RCTAnimation (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTAnimationHeaders - React-debug - React-featureflags @@ -2722,19 +2076,13 @@ PODS: - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTAppDelegate (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-CoreModules - React-debug - React-defaultsnativemodule @@ -2756,16 +2104,10 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTBlob (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-Core/RCTBlobHeaders - React-Core/RCTWebSocket - React-jsi @@ -2775,18 +2117,12 @@ PODS: - React-RCTFBReactNativeSpec - React-RCTNetwork - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTFabric (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTSwiftUIWrapper - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-FabricComponents @@ -2811,37 +2147,25 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - Yoga - React-RCTFBReactNativeSpec (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec/components (= 0.86.3) - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTFBReactNativeSpec/components (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -2851,24 +2175,18 @@ PODS: - React-rendererdebug - React-utils - ReactCommon - - SocketRocket + - ReactNativeDependencies - Yoga - React-RCTImage (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTImageHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - React-RCTNetwork - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTLinking (0.86.3): - React-Core/RCTLinkingHeaders (= 0.86.3) - React-jsi (= 0.86.3) @@ -2877,14 +2195,8 @@ PODS: - ReactCommon - ReactCommon/turbomodule/core (= 0.86.3) - React-RCTNetwork (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTNetworkHeaders - React-debug - React-featureflags @@ -2895,17 +2207,11 @@ PODS: - React-networking - React-RCTFBReactNativeSpec - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTRuntime (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-Core + - React-Core-prebuilt - React-debug - React-jsi - React-jsinspector @@ -2917,63 +2223,39 @@ PODS: - React-runtimeexecutor - React-RuntimeHermes - React-utils - - SocketRocket + - ReactNativeDependencies - React-RCTSettings (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric - RCTTypeSafety + - React-Core-prebuilt - React-Core/RCTSettingsHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-RCTText (0.86.3): - React-Core/RCTTextHeaders (= 0.86.3) - Yoga - React-RCTVibration (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-Core/RCTVibrationHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - SocketRocket + - ReactNativeDependencies - React-rendererconsistency (0.86.3) - React-renderercss (0.86.3): - React-debug - React-utils - React-rendererdebug (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-debug - - SocketRocket + - ReactNativeDependencies - React-RuntimeApple (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker + - React-Core-prebuilt - React-Core/Default - React-CoreModules - React-cxxreact @@ -2992,16 +2274,10 @@ PODS: - React-RuntimeHermes - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - React-RuntimeCore (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact - React-Fabric - React-featureflags @@ -3014,29 +2290,17 @@ PODS: - React-runtimeexecutor - React-runtimescheduler - React-utils - - SocketRocket + - ReactNativeDependencies - React-runtimeexecutor (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - React-debug - React-featureflags - React-jsi (= 0.86.3) - React-utils - - SocketRocket + - ReactNativeDependencies - React-RuntimeHermes (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-featureflags - React-hermes - React-jsi @@ -3048,17 +2312,11 @@ PODS: - React-RuntimeCore - React-runtimeexecutor - React-utils - - SocketRocket + - ReactNativeDependencies - React-runtimescheduler (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker + - React-Core-prebuilt - React-cxxreact - React-debug - React-featureflags @@ -3070,30 +2328,18 @@ PODS: - React-runtimeexecutor - React-timing - React-utils - - SocketRocket + - ReactNativeDependencies - React-timing (0.86.3): - React-debug - React-utils (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-debug - React-jsi (= 0.86.3) - - SocketRocket + - ReactNativeDependencies - React-viewtransitionnativemodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-Fabric - React-Fabric/bridging - React-jsi @@ -3101,17 +2347,11 @@ PODS: - React-RCTFBReactNativeSpec - React-runtimeexecutor - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - React-webperformancenativemodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine + - React-Core-prebuilt - React-cxxreact - React-jsi - React-jsiexecutor @@ -3119,21 +2359,15 @@ PODS: - React-RCTFBReactNativeSpec - React-runtimeexecutor - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - ReactAppDependencyProvider (0.86.3): - ReactCodegen - ReactCodegen (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-FabricImage @@ -3147,59 +2381,35 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - ReactCommon (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - RCT-Folly - - RCT-Folly/Fabric + - React-Core-prebuilt - ReactCommon/turbomodule (= 0.86.3) - - SocketRocket + - ReactNativeDependencies - ReactCommon/turbomodule (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker (= 0.86.3) + - React-Core-prebuilt - React-cxxreact (= 0.86.3) - React-jsi (= 0.86.3) - React-logger (= 0.86.3) - React-perflogger (= 0.86.3) - ReactCommon/turbomodule/bridging (= 0.86.3) - ReactCommon/turbomodule/core (= 0.86.3) - - SocketRocket + - ReactNativeDependencies - ReactCommon/turbomodule/bridging (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker (= 0.86.3) + - React-Core-prebuilt - React-cxxreact (= 0.86.3) - React-jsi (= 0.86.3) - React-logger (= 0.86.3) - React-perflogger (= 0.86.3) - - SocketRocket + - ReactNativeDependencies - ReactCommon/turbomodule/core (0.86.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - React-callinvoker (= 0.86.3) + - React-Core-prebuilt - React-cxxreact (= 0.86.3) - React-debug (= 0.86.3) - React-featureflags (= 0.86.3) @@ -3207,21 +2417,16 @@ PODS: - React-logger (= 0.86.3) - React-perflogger (= 0.86.3) - React-utils (= 0.86.3) - - SocketRocket + - ReactNativeDependencies + - ReactNativeDependencies (0.86.3) - ReactNativeIncallManager (4.2.1): - React-Core - RNBootSplash (6.3.11): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3236,22 +2441,16 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNCallKeep (4.3.16): - React - RNCAsyncStorage (2.2.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3266,20 +2465,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNCClipboard (1.16.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3294,20 +2487,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNCMaskedView (0.3.2): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3322,20 +2509,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNCPicker (2.11.4): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3350,20 +2531,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNDateTimePicker (9.1.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3378,7 +2553,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNDeviceInfo (11.1.0): - React-Core @@ -3395,17 +2570,11 @@ PODS: - React-Core - RNFBApp - RNGestureHandler (2.32.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-FabricComponents @@ -3421,20 +2590,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNImageCropPicker (0.51.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3450,22 +2613,16 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - RNImageCropPicker/QBImagePickerController (= 0.51.1) - - SocketRocket - TOCropViewController (~> 2.8.0) - Yoga - RNImageCropPicker/QBImagePickerController (0.51.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3481,21 +2638,15 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - TOCropViewController (~> 2.8.0) - Yoga - RNKeychain (10.0.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3510,20 +2661,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNLocalize (3.7.0): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3538,20 +2683,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNReanimated (4.5.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3567,24 +2706,18 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - RNReanimated/apple (= 4.5.1) - RNReanimated/common (= 4.5.1) - RNReanimated/view (= 4.5.1) - RNWorklets - - SocketRocket - Yoga - RNReanimated/apple (4.5.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3600,21 +2733,15 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - RNWorklets - - SocketRocket - Yoga - RNReanimated/common (4.5.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3630,21 +2757,15 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - RNWorklets - - SocketRocket - Yoga - RNReanimated/view (4.5.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3660,21 +2781,15 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - RNWorklets - - SocketRocket - Yoga - RNScreens (4.26.2): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3690,21 +2805,15 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - RNScreens/common (= 4.26.2) - - SocketRocket - Yoga - RNScreens/common (4.26.2): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3720,20 +2829,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNSVG (15.15.4): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3748,21 +2851,15 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - RNSVG/common (= 15.15.4) - - SocketRocket - Yoga - RNSVG/common (15.15.4): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3777,20 +2874,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNTrueSheet (3.7.3): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3805,20 +2896,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNWorklets (0.10.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3834,22 +2919,16 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - ReactNativeDependencies - RNWorklets/apple (= 0.10.1) - RNWorklets/common (= 0.10.1) - - SocketRocket - Yoga - RNWorklets/apple (0.10.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3865,20 +2944,14 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - RNWorklets/common (0.10.1): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric + - hermes-engine - RCTRequired - RCTTypeSafety - React-Core + - React-Core-prebuilt - React-debug - React-Fabric - React-featureflags @@ -3894,7 +2967,7 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - SocketRocket + - ReactNativeDependencies - Yoga - SDWebImage (5.21.7): - SDWebImage/Core (= 5.21.7) @@ -3908,7 +2981,6 @@ PODS: - libwebp (~> 1.0) - SDWebImage/Core (~> 5.17) - simdjson (3.9.4) - - SocketRocket (0.7.1) - TOCropViewController (2.8.0) - WatermelonDB (0.28.1-0): - React @@ -3921,10 +2993,8 @@ PODS: - ZXingObjC/Core DEPENDENCIES: - - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - "BugsnagReactNative (from `../node_modules/@bugsnag/react-native`)" - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXApplication (from `../node_modules/expo-application/ios`) - EXConstants (from `../node_modules/expo-constants/ios`) - Expo (from `../node_modules/expo`) @@ -3953,17 +3023,13 @@ DEPENDENCIES: - ExpoVideo (from `../node_modules/expo-video/ios`) - ExpoVideoThumbnails (from `../node_modules/expo-video-thumbnails/ios`) - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) - - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - MMKV - MMKVCore - "MobileCrypto (from `../node_modules/@rocket.chat/mobile-crypto`)" - NitroMmkv (from `../node_modules/react-native-mmkv`) - NitroModules (from `../node_modules/react-native-nitro-modules`) - - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - RCTRequired (from `../node_modules/react-native/Libraries/Required`) - RCTSwiftUI (from `../node_modules/react-native/ReactApple/RCTSwiftUI`) @@ -3972,6 +3038,7 @@ DEPENDENCIES: - React (from `../node_modules/react-native/`) - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - React-Core (from `../node_modules/react-native/`) + - React-Core-prebuilt (from `../node_modules/react-native/React-Core-prebuilt.podspec`) - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) @@ -4045,6 +3112,7 @@ DEPENDENCIES: - ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`) - ReactCodegen (from `build/generated/ios/ReactCodegen`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`) - ReactNativeIncallManager (from `../node_modules/react-native-incall-manager`) - RNBootSplash (from `../node_modules/react-native-bootsplash`) - RNCallKeep (from `../node_modules/react-native-callkeep`) @@ -4067,7 +3135,6 @@ DEPENDENCIES: - "RNTrueSheet (from `../node_modules/@lodev09/react-native-true-sheet`)" - RNWorklets (from `../node_modules/react-native-worklets`) - "simdjson (from `../node_modules/@nozbe/simdjson`)" - - SocketRocket (~> 0.7.1) - "WatermelonDB (from `../node_modules/@nozbe/watermelondb`)" - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -4098,19 +3165,14 @@ SPEC REPOS: - SDWebImageAVIFCoder - SDWebImageSVGCoder - SDWebImageWebPCoder - - SocketRocket - TOCropViewController - ZXingObjC EXTERNAL SOURCES: - boost: - :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" BugsnagReactNative: :path: "../node_modules/@bugsnag/react-native" BVLinearGradient: :path: "../node_modules/react-native-linear-gradient" - DoubleConversion: - :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXApplication: :path: "../node_modules/expo-application/ios" EXConstants: @@ -4167,25 +3229,17 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-video-thumbnails/ios" ExpoWebBrowser: :path: "../node_modules/expo-web-browser/ios" - fast_float: - :podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" - fmt: - :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" - glog: - :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" - :tag: hermes-v0.17.0 + :tag: hermes-v250829098.0.17 MobileCrypto: :path: "../node_modules/@rocket.chat/mobile-crypto" NitroMmkv: :path: "../node_modules/react-native-mmkv" NitroModules: :path: "../node_modules/react-native-nitro-modules" - RCT-Folly: - :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: @@ -4202,6 +3256,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/callinvoker" React-Core: :path: "../node_modules/react-native/" + React-Core-prebuilt: + :podspec: "../node_modules/react-native/React-Core-prebuilt.podspec" React-CoreModules: :path: "../node_modules/react-native/React/CoreModules" React-cxxreact: @@ -4346,6 +3402,8 @@ EXTERNAL SOURCES: :path: build/generated/ios/ReactCodegen ReactCommon: :path: "../node_modules/react-native/ReactCommon" + ReactNativeDependencies: + :podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" ReactNativeIncallManager: :path: "../node_modules/react-native-incall-manager" RNBootSplash: @@ -4396,13 +3454,11 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - BugsnagReactNative: 2078dec76e54db9f6e687743af45dc3673cb32ab + BugsnagReactNative: 33c924422b4d4fac76459ac818500586dd53debf BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70 - DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb EXApplication: a727f05eb8592f0bc764f6ab8d51eacc158652ec EXConstants: 8948a340eb14bf2bdaaa0e21a63cb9343860b87a - Expo: 5958a8d1e01ac4630df9d05171059b291828a93b + Expo: dfda0a34acc90766241acc0ba214badf45330567 ExpoAppleAuthentication: 16c2955d33ec7315e5a99d17fb5d62ed33cdb5b9 ExpoAsset: b3999ffb3d4049b6c870708c60ea2209ace9f170 ExpoAudio: edc9230b788be6f65238a53907d5c176b57a7ce0 @@ -4418,7 +3474,7 @@ SPEC CHECKSUMS: ExpoKeepAwake: 72af0b3be3615e59bc819d26061ff4e06b155ee0 ExpoLocalAuthentication: 1e4aef156a3a1f953f8278b8d8309f6118919640 ExpoLogBox: 80957303325cd4439137765c90a87d7f22aaf2ff - ExpoModulesCore: cfff5545e4ce059bbfba80e56bd1771d9a620ed1 + ExpoModulesCore: a220d034f6873d06db8ee66af1b33fca51c65318 ExpoModulesJSI: 21c947f0cc559401c86995dad7a0b1112c7fd656 ExpoModulesWorklets: 986cf72c18400743869b4642d243115f831e404d ExpoModulesWorkletsAdapter: ae4dd675418c2bbdf802ae74f7f78442e7010ab7 @@ -4428,8 +3484,7 @@ SPEC CHECKSUMS: ExpoVideo: 56ed2863b981dd5ae946d8ef23ecfcae855863a0 ExpoVideoThumbnails: da5d471c170bf4110f681b6649524eace9c95b5b ExpoWebBrowser: a4d7c382116fbe33c1ec7f60293f02b7e97042e4 - fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 - FBLazyVector: 94a9e5b949d0f65bbc1a741f8a7c72499da32567 + FBLazyVector: 2138c6c2ab66c31bf5754dc18097fbc498a49c7f Firebase: 6a8f201c61eda24e98f1ce2b44b1b9c2caf525cc FirebaseAnalytics: acfa848bf81e1a4dbf60ef1f0eddd7328fe6673e FirebaseCore: 2321536f9c423b1f857e047a82b8a42abc6d9e2c @@ -4439,137 +3494,135 @@ SPEC CHECKSUMS: FirebaseInstallations: 781e0e37aa0e1c92b44d00e739aba79ad31b2dba FirebaseRemoteConfigInterop: 1c6135e8a094cc6368949f5faeeca7ee8948b8aa FirebaseSessions: f5c6bfeb66a7202deaf33352017bb6365e395820 - fmt: 530618a01105dae0fa3a2f27c81ae11fa8f67eac - glog: 9c7bd858f1402a8e680d1637276210773c093b76 GoogleAppMeasurement: 8a82b93a6400c8e6551c0bcd66a9177f2e067aed GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 - hermes-engine: 517aaf93c2fbacc66266024672ddb34b20ed8679 + hermes-engine: 63c5db7d529c36335fada6582fd293ae2a105d77 JitsiWebRTC: b47805ab5668be38e7ee60e2258f49badfe8e1d0 libavif: 5f8e715bea24debec477006f21ef9e95432e254d libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 MMKV: 86859fdfa2b0b21db1fd6e48788474a6416a2c77 MMKVCore: 3d16ce9f7d411e135020915fde98a056859a1efa - MobileCrypto: cbe4be11a33b5888e77861de9346fa5d3fccc8ac + MobileCrypto: 182433a43bce9ff7ac5b0a46ca09e907b5194e92 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - NitroMmkv: 5d6c3a5cca0e47be9a4dd3f66262dbad0799c087 - NitroModules: f2a165630c12aed5793d6b42b9002c6a649ea04e + NitroMmkv: 38b271a19e7e152025a6893160c98326872ea05f + NitroModules: 6232dc95a28a7a221227e618733ce0e674e8753b PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - RCT-Folly: 36c4f904fb6cd0219dcb76b94e9502d2a72fab0b - RCTDeprecation: 5268fe9d45036e52deb765d242297f1011e17333 - RCTRequired: 4ecfd325edd95cbbdc202163fb9cb2ba54ac3c9f - RCTSwiftUI: e8b9cdb0d9fbea9284db6420bb726206819a9287 - RCTSwiftUIWrapper: 823739174d403f0fc8fca2baa5c485b8121c00c4 - RCTTypeSafety: 40a9400b253020417515028803a696b35ab8042c + RCTDeprecation: 96f68ebf33f8762466b01d57d6e873c4f30efb07 + RCTRequired: 166eed8ad6d8440e73e56d09a9090366609823d2 + RCTSwiftUI: e818198d11928a78c2b2afe281d072171dab617f + RCTSwiftUIWrapper: b9457f8613a1d0c8146c08844a04154702ff8809 + RCTTypeSafety: efc19e6b92931abf0b68b064212323a258a5f019 React: 52dce66f71097ed7a4968673d95eeb8095ded74e - React-callinvoker: fba8c8c6078156889ed4e6070ed46265345462b5 - React-Core: fb34f5a9df5c1743c550912b96f9c4f3a3ef69d8 - React-CoreModules: 0e196522b5b3e181a2e7b1e4f33f8b801ac23fd4 - React-cxxreact: 11942a287481cd18c11d9bd2c64d3b1b2b2626c2 - React-debug: 6fee06a4b91395b4dbcb20c628974edae868eb43 - React-defaultsnativemodule: 782f1b8c794bbc960cdce4b483db78d9e251cff2 - React-domnativemodule: 01e99c6243cb87447e7627399a891b41727178da - React-Fabric: 31abac546ae65a4a6b7b283df4496ff393545c86 - React-FabricComponents: 7d474b9b833c18fd349943bcf7b1c54cb9aed728 - React-FabricImage: 53aefdba02f5c6ca953cd7bab04a0d3cf8625c15 - React-featureflags: 7284e11613282ec1703cf697bf13b2744d3bf8b5 - React-featureflagsnativemodule: 40b3e8baff8d43413f89561d4987b73afcc97c2f - React-graphics: a252e749294e52fa60c2711b795f5aa7649d58b8 - React-hermes: df503e8bd7483322f10dfefe27c68f9290befbd5 - React-idlecallbacksnativemodule: e5ba08ad15e7dbbbad2d3bf7fc23bc051cfe6610 - React-ImageManager: a08e8bccc9f6ea6e43bbcb9ce324094eed9f5cb7 - React-intersectionobservernativemodule: 850839034c43eb7e7f15faab8e1fff2cd5a478f4 - React-jserrorhandler: 0b250992c1a8181a90e925834444fabbc7a21fb5 - React-jsi: 26b4997a1a030b14434e1a3b3ca28a593388a649 - React-jsiexecutor: 337306ff602647d6ad7017ee988286b69d51d513 - React-jsinspector: 70354072327bb21c23613eab53ab14381787d78f - React-jsinspectorcdp: b53e6d2c40036b4456d212e2b3f48112b2144c4e - React-jsinspectornetwork: 6f5479e9b485065597d2a00827180e35ef153c2f - React-jsinspectortracing: 8d37007dfcbd35bdae4c5feebb98aa87cec4cde1 - React-jsitooling: 6b2bc363c1eb762c303849787bdcf0cd7148da3a - React-jsitracing: db3ab1e47adee8b8a56ced09074a1cbdffd48609 - React-logger: 49084e2c68c541d938502793e71aeb3707407d5f - React-Mapbuffer: e1ff6f5e203a9e7508d74f83d82052ece710bc8a - React-microtasksnativemodule: 8f4fec3bfe2f2b2d5cf216106caac2349fc261c8 - React-mutationobservernativemodule: a44d1a1fe65bf7ec9f30036a66db8d8f0412c1ee - react-native-a11y-order: 20b33ff538af60d8b8a7f7284115a7febc9034d4 - react-native-cameraroll: ae31ba7ee17d31c5ee05d7bb248742e3af482783 + React-callinvoker: afa0525ab27f4ffbb4c377f855fe9368edb099bc + React-Core: fdb67992e049f2526ade3e258c21b8404e7943a3 + React-Core-prebuilt: 9abc518a8aa7aa4fa3bbbe52bc825bcd9fb3c3ce + React-CoreModules: 456d91152aa283ddc6ae8abfcec3292af88912f6 + React-cxxreact: 73a20c0dde5ec221f98d5e4da0e71349ab48353d + React-debug: 4a1d2ef3086d88dd35f28cfb5da803584f819924 + React-defaultsnativemodule: 65c2620e6e8b3426fe227fa546425395fad38b9d + React-domnativemodule: 9fa0a7b5ed12355add4e56b6c44c18afd4a7b409 + React-Fabric: 9f83fc6b78f3717dc7c0d98ecbee5e18f1849c6a + React-FabricComponents: eacd7040d74c3c8f0f400a0f53870a24dc1c70b0 + React-FabricImage: 3e6ccf1e63427426334e239699d1989c289fe8e8 + React-featureflags: 0856ddd67965e449ad24d27b8d9b166b9904523f + React-featureflagsnativemodule: 9fd134748e9befe3766bce575570dbf6666bad1a + React-graphics: 37e133f18290451de3c05306d21c2964053b651e + React-hermes: 65cfbf62d8d31c1e69f3556ff6836a2890cf4a62 + React-idlecallbacksnativemodule: 5a6dd45a2fa8ae6373ed5dd9547e98d15a8ba61c + React-ImageManager: 9f554c1e6340161c22d6c01291d0b773167a6c36 + React-intersectionobservernativemodule: 8882f818e84fe44d8246f9e25f4a6621e7cd2d08 + React-jserrorhandler: 8c8db1ad69aed9556c4f2083c82c902901b0171e + React-jsi: 69052b2d1958c9069a143f1637f382b933917b43 + React-jsiexecutor: b9b98ea432f58ee2899a60204f090d0bc8d48313 + React-jsinspector: 5b1972a5cea8b6dd8170f88f532493a333c8d74b + React-jsinspectorcdp: ab3fcfaa5c149f8fc5d5e8274eb9bfcecf4e5cc3 + React-jsinspectornetwork: c533e6e6bdc6a7d3477a3e46886707cc6b9bd715 + React-jsinspectortracing: 0775adf8a09823fce36b21f95a3caad06c4fa168 + React-jsitooling: c9c3e7cea49b264bf1f89f998673310bc37908a2 + React-jsitracing: 6293ec5716e46c568aaf4ffe766f4c5c5e1d69e7 + React-logger: b9bf04943233973fece220497f0784eaad409b3a + React-Mapbuffer: e05fcc335c004b3b2a45889244c5c27bbbfad64c + React-microtasksnativemodule: 27f8495905481c29ac6acd0e37313b3fbfb17cf0 + React-mutationobservernativemodule: 012f3688a7e96d459e5d787264724875939f6f43 + react-native-a11y-order: c1727d69dda91472e3479e2fdea0832fa8d426ae + react-native-cameraroll: d7e55dae97acb52c4ce88a32deb02841fa15de12 react-native-cookies: d648ab7025833b977c0b19e142503034f5f29411 - react-native-external-keyboard: db51d3a78d68c1f87fef41c67353a7c0ddbe51dd - react-native-keyboard-controller: c4ca61f44d66c2f8987a7e67e9b78e80dc965c45 - react-native-netinfo: 9fad4eedfec9840a10e73ac4591ea1158523309b - react-native-safe-area-context: eda63a662750758c1fdd7e719c9f1026c8d161cb - react-native-slider: 205c30f4f19af8469c1c24bd4e3ec74038576a67 + react-native-external-keyboard: f89ea61eb77875621a1ad086769d829da8ae3fcd + react-native-keyboard-controller: c5ba8bf515b500ecb3692bc5ecc944c6be4b3290 + react-native-netinfo: a05f9b897e76ad24b53f615fed1cbb731932363d + react-native-safe-area-context: ba7283e541a5f57c6b27a7e33f2c40b8977888c0 + react-native-slider: b15258ac3d287a5951f76b11497d659ed20c0def react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74 - react-native-webview: 83c663c5bdf1357d3e7c00986260cb888ea0e328 - React-NativeModulesApple: 750a5d07f72a9224863f7d7c30519f71aa88576e - React-networking: dc4795b8113f6029877be8abdf555cedb60d8f3c - React-oscompat: 3d37f8d54a39a05a54be863c235b939cb4108385 - React-perflogger: 5aa7c01773e8dca7c2e72b1eb42ebddb7ca83413 - React-performancecdpmetrics: c18cb2f28b3a4220c90f3b6ee5bcf375c52544be - React-performancetimeline: a9a81c04f7d4c2df48ce5e1a1c2bbfbf2e292579 - React-RCTActionSheet: 57e248e5a72bb65bb26e7afb5370ba3d411c003a - React-RCTAnimation: d7501cac9938345a51acb245ed6e911016c9414a - React-RCTAppDelegate: fd219555b51b039b0dbf58a910f0adb00eb6df8d - React-RCTBlob: 647dcdd01449e7d0f39c3a5d3862328ed4dfca83 - React-RCTFabric: 7b1c18df82a2bf7f355ad61264956b42abfcdfca - React-RCTFBReactNativeSpec: 289308a9b9c633e0250a7a7f82d403b60b9dac78 - React-RCTImage: 5880615f3360564cb78872c838ab435e013659ae - React-RCTLinking: ea77766ce56904248222754f0a5960bc6b173cbf - React-RCTNetwork: d2cf10109367dcbcadeeb36b01eeab729708ce94 - React-RCTRuntime: d566a4abd49a8e6eaf383d09c75bdb1dc1505a28 - React-RCTSettings: 058999035f72625afb32c6c32bd34074a33190f8 - React-RCTText: a09cad9bebd88d5088ef47e1dfb38fe4cf114591 - React-RCTVibration: da609d61ad6cef32ddfea86d116fd69c34fdbf34 - React-rendererconsistency: d7d521e7ac66970d051f25575c23a55355e4526d - React-renderercss: 3105f8eed9fd35b4035c61949193d1553741743a - React-rendererdebug: 119235eaa62ac4cc21e4d87cbc6f9ff8d7bdff51 - React-RuntimeApple: b58c06ee048b953b0fab67fd474d3f92fe47c403 - React-RuntimeCore: 463614d0376292c66e85f874db0dabbb8e0822e3 - React-runtimeexecutor: 3d63baa8fb9b5afd94ec56e7da49f0b49ef9de1b - React-RuntimeHermes: 3cefc114d68f49441e661b767b38730d7eb466ef - React-runtimescheduler: 0a4593b56ddab4a38ec431b2ad7d36ccb99b018d - React-timing: d0983fe4b4dd63fc660e9a9e220536747a4ba5e1 - React-utils: ca695510a0b98277cfae1a5e94274dd8fc01c877 - React-viewtransitionnativemodule: d2652188ed10f6dfbc05751607467dc53062e7ad - React-webperformancenativemodule: e59f5087f046a00242b60a8be0f471a353044f40 + react-native-webview: 2a4da2e03d3ec2c731c4d1a389cb86aaa4902167 + React-NativeModulesApple: 2b658272ca69df0694cf5d54f9510aa7c33977bc + React-networking: 8ff98fbf96752706ec66ccc1d0089c1005a5be11 + React-oscompat: cea7c66df8076bd97e95885d7f584b86afffde41 + React-perflogger: 9110ba33a4aa70c708e9d50aeca040e6f2aefaf7 + React-performancecdpmetrics: 6c158ff2e7a8ecf9a4618350b93b86e83d4f3b59 + React-performancetimeline: a6c41ea140789bbeacb60a123ae34cf18abd7b9d + React-RCTActionSheet: aeb20277150454e9020517c6a2cb70410d4856d9 + React-RCTAnimation: 8453deeb01d0f284202c9670b5f7d827575b4967 + React-RCTAppDelegate: 5e9c748cc138decd9045683d8987700d824a99ff + React-RCTBlob: e6c01cae3eeb7e63a55b12f72b7dfcfd8236b4b2 + React-RCTFabric: a641da4b62d3eab2479dfc057190740bb83a5989 + React-RCTFBReactNativeSpec: 7a20b043840a40f790e47c2fc79f28f54dd8eae1 + React-RCTImage: ec8488bc119d7e5d3209f85be3ebda7e4a937c61 + React-RCTLinking: b6ff56c2f206127d49d338262418c097ea306ad0 + React-RCTNetwork: 08d71b365278511a9bb07885b4a54bdc510d99bf + React-RCTRuntime: 9b306501174b368a4ded95bc86934bffe54e1bf5 + React-RCTSettings: 372551bc9f30d26890f5c5fd569dcd15243c656c + React-RCTText: 62cff7e93aeb3c06e3634f1c59984e8c5322b542 + React-RCTVibration: 2e8c1630ae80c092f437db9638a03715f616ac03 + React-rendererconsistency: 86b56864046a6622bcbf829db78e3dcbb15462b1 + React-renderercss: a1457503aef12d882e530a9fefc9f475fc46443c + React-rendererdebug: 4ccd491bef79e3bfa6b9ac3424ebec5c1e8c795e + React-RuntimeApple: f3e01025b46b99e089b1170b81d428bdc1d4260a + React-RuntimeCore: 7db4a36cccb24f8374ccfb887c46577c11955625 + React-runtimeexecutor: 6e11e5addf6d1d1dcff16359a298c0726f0c4f31 + React-RuntimeHermes: c124599239ce984c1bafabbce975c9c5595ead53 + React-runtimescheduler: bdcc80a052783ccbf9dd6aade14072618599736e + React-timing: 9b5e7d1b657745cd88398d04b8c042debc22ac82 + React-utils: 8c00679add05bd780264773f7582582b4e9db95d + React-viewtransitionnativemodule: e89719ba504600d18f59eec5a104ffb3cac7cb80 + React-webperformancenativemodule: f94d244822b5413c8ce3fb542e41976e7a202e50 ReactAppDependencyProvider: bb350d1333fd459c1aabe782088419a9b8894d3a - ReactCodegen: ead138c976dd9fc1ca8a79a297d4ccf96182531f - ReactCommon: 322cf4b51be09a3cbf5ae891c8dd9f52091c4011 + ReactCodegen: 4acb96173b6ecf8bb9a42044614b848dcfbc4fc6 + ReactCommon: 02f6a489d5eb4ab79d7deba5cd4434e0ba351a07 + ReactNativeDependencies: 6738e602b46fd24aa453cd58267ec3bb8bf29d27 ReactNativeIncallManager: dccd3e7499caa3bb73d3acfedf4fb0360f1a87d5 - RNBootSplash: 7fcc9a58ae343aeb1a1dd49f9030832fe432c544 + RNBootSplash: 768ba7c7983ab8415dce47ac54815acd4f5ae843 RNCallKeep: 1930a01d8caf48f018be4f2db0c9f03405c2f977 - RNCAsyncStorage: fd44f4b03e007e642e98df6726737bc66e9ba609 - RNCClipboard: e560338bf6cc4656a09ff90610b62ddc0dbdad65 - RNCMaskedView: d707a83784c67099b54b37d056ababb2767ce15e - RNCPicker: 35fc66f352403cdfe99d53b541f5180482ca2bc5 - RNDateTimePicker: ea25d20659754bfe1014e0d5905d21dbc18ff705 + RNCAsyncStorage: d90e523830fa7705f8c61632479abde871bc792f + RNCClipboard: 7a7d4557bfd3370b35c99dfecd92ae7b9fc4948a + RNCMaskedView: 8069b4d4e23b3d28b4fcac70bf266be43ffc3d2e + RNCPicker: a3921be99ae291e420e3e8940e5b2b53e572c543 + RNDateTimePicker: afe0288e6c7855994f7f2617b7b2c05799fea7f3 RNDeviceInfo: 900bd20e1fd3bfd894e7384cc4a83880c0341bd3 RNFBAnalytics: 2e8b8ffcd2bb3d59a43ecbe09571c73c56edef7a RNFBApp: a448037d2df74af9d374a0b765be12ff1e844dc0 RNFBCrashlytics: c3bb5533f9957eddc88f3ea383583309a4ce9f89 - RNGestureHandler: 89c0c079dc88636fed9580bb581bd7df69ef33d0 - RNImageCropPicker: 5432d75a3bc7209dc8b6ab189fcfe19bd6339fb9 - RNKeychain: d0bdaca4688ae123909b137de08491f49a6cf6b1 - RNLocalize: c46dd4008dc8990a098fcafd6ee051122a1ccfda - RNReanimated: 1a67ec7c458dd04abadba7549d9d5abb24d6ec8a - RNScreens: 7a423c912ca9e107b37f54f32292186de32bfcaf - RNSVG: 1eecd65673a4735fead92da0e79441f09ec2d77a - RNTrueSheet: 53f29088da313dabff8b81d7c4d52afd8e609cfa - RNWorklets: 71a002c69e25e15bd4758faccd089349bcfc82d7 + RNGestureHandler: 7b07d9192bc65c6883fb37fdecc05dc6b61c814f + RNImageCropPicker: 5c9c8bef4c72d3f451b499aa0ecd90a644ea1bbd + RNKeychain: d57edf101a72acf3ae85fe4d796eed1d951d2a90 + RNLocalize: 17bce0556e2d33fe1201c8cef53b3b7786e07612 + RNReanimated: 28766837a1b96215c55c2be65b27b8903e31ef5d + RNScreens: 5b4b260e28d8263d97fddcb53e59649515797bcc + RNSVG: 68c366b996064ea99cee1e55da62b8feafb33a78 + RNTrueSheet: 93f1765e65782ef6de12fd9338061a51399bb337 + RNWorklets: 43af89f696342f91118a2f1f3a879056e2f47331 SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 simdjson: 7bb9e33d87737cec966e7b427773c67baa4458fe - SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 TOCropViewController: 797deaf39c90e6e9ddd848d88817f6b9a8a09888 WatermelonDB: 4c846c8cb94eef3cba90fa034d15310163226703 - Yoga: 9690ea6ce9c77aee633f4815cc42202c701edbd8 + Yoga: 269d501078efa6b25cd57f7f326fc3da213580be ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: d705b9a75929ea25f9b7bb3ef8167fe307bd8b32 +PODFILE CHECKSUM: b39e033fd5ceefcceb991d1d93ed4ea05c5752e0 COCOAPODS: 1.15.2 diff --git a/ios/RocketChat.xcodeproj/project.pbxproj b/ios/RocketChat.xcodeproj/project.pbxproj index fba4a7314c7..b3a5e045385 100644 --- a/ios/RocketChat.xcodeproj/project.pbxproj +++ b/ios/RocketChat.xcodeproj/project.pbxproj @@ -1288,7 +1288,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/MMKV/MMKV_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", @@ -1299,8 +1298,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-timing/React-timing_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", ); @@ -1324,7 +1321,6 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MMKV_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", @@ -1335,8 +1331,6 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-timing_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", ); @@ -1449,7 +1443,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/MMKV/MMKV_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly/RCT-Folly_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", @@ -1460,8 +1453,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-timing/React-timing_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", ); @@ -1485,7 +1476,6 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MMKV_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCT-Folly_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", @@ -1496,8 +1486,6 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-timing_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", ); @@ -1559,12 +1547,16 @@ "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", "${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoModulesJSI/ExpoModulesJSI.framework/ExpoModulesJSI", "${PODS_XCFRAMEWORKS_BUILD_DIR}/JitsiWebRTC/WebRTC.framework/WebRTC", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies", "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermesvm.framework/hermesvm", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExpoModulesJSI.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermesvm.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -2358,6 +2350,18 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios", "${PODS_CONFIGURATION_BUILD_DIR}/React-featureflags/React_featureflags.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-renderercss/React_renderercss.framework/Headers", + "${PODS_ROOT}/ReactCommon", + "${PODS_ROOT}/ReactCommon/react/nativemodule/core", + "${PODS_ROOT}/React-runtimeexecutor", + "${PODS_ROOT}/React-runtimeexecutor/platform/ios", + "${PODS_ROOT}/ReactCommon-Samples", + "${PODS_ROOT}/ReactCommon-Samples/platform/ios", + "${PODS_ROOT}/React-Fabric/react/renderer/components/view/platform/cxx", + "${PODS_ROOT}/React-NativeModulesApple", + "${PODS_ROOT}/React-graphics", + "${PODS_ROOT}/React-graphics/react/renderer/graphics/platform/ios", + "${PODS_ROOT}/React-featureflags", + "${PODS_ROOT}/React-renderercss", ); IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; @@ -2378,6 +2382,7 @@ REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; USE_HERMES = true; }; name = Debug; @@ -2437,6 +2442,18 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios", "${PODS_CONFIGURATION_BUILD_DIR}/React-featureflags/React_featureflags.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-renderercss/React_renderercss.framework/Headers", + "${PODS_ROOT}/ReactCommon", + "${PODS_ROOT}/ReactCommon/react/nativemodule/core", + "${PODS_ROOT}/React-runtimeexecutor", + "${PODS_ROOT}/React-runtimeexecutor/platform/ios", + "${PODS_ROOT}/ReactCommon-Samples", + "${PODS_ROOT}/ReactCommon-Samples/platform/ios", + "${PODS_ROOT}/React-Fabric/react/renderer/components/view/platform/cxx", + "${PODS_ROOT}/React-NativeModulesApple", + "${PODS_ROOT}/React-graphics", + "${PODS_ROOT}/React-graphics/react/renderer/graphics/platform/ios", + "${PODS_ROOT}/React-featureflags", + "${PODS_ROOT}/React-renderercss", ); IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; @@ -2456,6 +2473,7 @@ REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; USE_HERMES = true; VALIDATE_PRODUCT = YES; }; diff --git a/patches/react-native+0.86.3.patch b/patches/react-native+0.86.3.patch deleted file mode 100644 index b0da19c9af6..00000000000 --- a/patches/react-native+0.86.3.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/node_modules/react-native/React/Views/RCTViewManager.m b/node_modules/react-native/React/Views/RCTViewManager.m -index fa1e0bc..82d9393 100644 ---- a/node_modules/react-native/React/Views/RCTViewManager.m -+++ b/node_modules/react-native/React/Views/RCTViewManager.m -@@ -223,7 +223,8 @@ - (RCTShadowView *)shadowView - { - UIAccessibilityTraits accessibilityRoleTraits = - json ? [RCTConvert UIAccessibilityTraits:json] : UIAccessibilityTraitNone; -- if (view.reactAccessibilityElement.accessibilityRoleTraits != accessibilityRoleTraits) { -+ NSString *roleString = json ? [RCTConvert NSString:json] : nil; -+ if (view.reactAccessibilityElement.accessibilityRoleTraits != accessibilityRoleTraits || view.reactAccessibilityElement.accessibilityRole != roleString) { - view.accessibilityRoleTraits = accessibilityRoleTraits; - view.reactAccessibilityElement.accessibilityRole = json ? [RCTConvert NSString:json] : nil; - [self updateAccessibilityTraitsForRole:view withDefaultView:defaultView]; -@@ -233,7 +234,8 @@ - (RCTShadowView *)shadowView - RCT_CUSTOM_VIEW_PROPERTY(role, UIAccessibilityTraits, RCTView) - { - UIAccessibilityTraits roleTraits = json ? [RCTConvert UIAccessibilityTraits:json] : UIAccessibilityTraitNone; -- if (view.reactAccessibilityElement.roleTraits != roleTraits) { -+ NSString *roleString = json ? [RCTConvert NSString:json] : nil; -+ if (view.reactAccessibilityElement.roleTraits != roleTraits || view.reactAccessibilityElement.role != roleString) { - view.roleTraits = roleTraits; - view.reactAccessibilityElement.role = json ? [RCTConvert NSString:json] : nil; - [self updateAccessibilityTraitsForRole:view withDefaultView:defaultView]; From c7701551f88539678e8d85fb4fc22a754200439d Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 13:33:45 -0300 Subject: [PATCH 019/120] fix(e2e): anchor keyboard navigation login on focus (#7703) * fix(e2e): reach login email field by focus instead of Dpad count The keyboard navigation onboarding flow walked to the email field with four blind Remote Dpad Down presses. Removing the decorative OR separator from the accessibility focus chain shortened the chain by one, so the email was typed into the password field and the password into the email field, and login was never submitted. Press Down until login-view-email reports focused, bounded by times: 10, and assert focus before typing. Maestro returns normally when a repeat bound is exhausted, so without the assertion an over-long chain would fail later at the rooms-list-view wait with an unrelated error. The separator only renders when totalServices > 0, so the chain length depends on server config and no fixed count is correct. * fix(e2e): reach password and submit by focus, hide keyboard first Anchoring the email field exposed two more blind steps in the same flow. The hop to the password field assumed Enter moves focus, and the submit assumed Enter on the password field logs in. Neither had been exercised before, because the flow failed earlier. Press Down until each target reports focused, bounded and asserted like the email field. The submit loop needs the soft keyboard hidden first: with the keyboard up the Dpad presses go to the input method and never reach the login screen, so the submit button is never focused. --- .../keyboard-navigation-onboarding.yaml | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/.maestro/tests/keyboardNavigation/keyboard-navigation-onboarding.yaml b/.maestro/tests/keyboardNavigation/keyboard-navigation-onboarding.yaml index 8d48340f4ef..c40ca6d8223 100644 --- a/.maestro/tests/keyboardNavigation/keyboard-navigation-onboarding.yaml +++ b/.maestro/tests/keyboardNavigation/keyboard-navigation-onboarding.yaml @@ -31,21 +31,49 @@ tags: visible: id: 'login-view' timeout: 60000 -- pressKey: Remote Dpad Down -- waitForAnimationToEnd: - timeout: 1000 -- pressKey: Remote Dpad Down -- waitForAnimationToEnd: - timeout: 1000 -- pressKey: Remote Dpad Down -- waitForAnimationToEnd: - timeout: 1000 -- pressKey: Remote Dpad Down +- repeat: + times: 10 + while: + notVisible: + id: 'login-view-email' + focused: true + commands: + - pressKey: Remote Dpad Down + - waitForAnimationToEnd: + timeout: 1000 +- assertVisible: + id: 'login-view-email' + focused: true - inputText: ${output.user.email} - pressKey: enter -- waitForAnimationToEnd: - timeout: 1000 +- repeat: + times: 10 + while: + notVisible: + id: 'login-view-password' + focused: true + commands: + - pressKey: Remote Dpad Down + - waitForAnimationToEnd: + timeout: 1000 +- assertVisible: + id: 'login-view-password' + focused: true - inputText: ${output.user.password} +- hideKeyboard +- repeat: + times: 10 + while: + notVisible: + id: 'login-view-submit' + focused: true + commands: + - pressKey: Remote Dpad Down + - waitForAnimationToEnd: + timeout: 1000 +- assertVisible: + id: 'login-view-submit' + focused: true - pressKey: enter - extendedWaitUntil: visible: From e5b26d42d7de71911785218a246ced391788b418 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 13:51:25 -0300 Subject: [PATCH 020/120] chore(deps): bump reanimated to 4.6.0 and worklets to 0.12.2 (#7706) * chore(deps): bump reanimated to 4.6.0 and worklets to 0.12.2 Reanimated 4.6.0 requires react-native-worklets 0.12.x, so both move together. Worklets drops its caret to match reanimated's exact pin. Reanimated 4.6.0 initializes CSS event handling at import time, which the JS-only fallback used by Jest does not implement, so the mock entry point throws on load. Stubbing setCSSEventHandler at its source keeps the rest of the proxy module real. * chore(ios): update Podfile.lock for reanimated and worklets CocoaPods preserves versions already in Podfile.lock, so pod install would not have picked these up on its own. * chore(ios): bump ffi to 1.17.4 for Xcode 27 compatibility --- Gemfile.lock | 2 +- ios/Podfile.lock | 28 ++++----- jest.setup.js | 5 ++ package.json | 4 +- pnpm-lock.yaml | 158 ++++++++++++++++++++++++----------------------- 5 files changed, 102 insertions(+), 95 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a85e179d53b..7b85556bf9a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -178,7 +178,7 @@ GEM xml-simple fastlane-sirp (1.0.0) sysrandom (~> 1.0) - ffi (1.17.1) + ffi (1.17.4) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 65e167a9dd9..9c6df442d34 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2685,7 +2685,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNReanimated (4.5.1): + - RNReanimated (4.6.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2707,12 +2707,12 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNReanimated/apple (= 4.5.1) - - RNReanimated/common (= 4.5.1) - - RNReanimated/view (= 4.5.1) + - RNReanimated/apple (= 4.6.0) + - RNReanimated/common (= 4.6.0) + - RNReanimated/view (= 4.6.0) - RNWorklets - Yoga - - RNReanimated/apple (4.5.1): + - RNReanimated/apple (4.6.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2736,7 +2736,7 @@ PODS: - ReactNativeDependencies - RNWorklets - Yoga - - RNReanimated/common (4.5.1): + - RNReanimated/common (4.6.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2760,7 +2760,7 @@ PODS: - ReactNativeDependencies - RNWorklets - Yoga - - RNReanimated/view (4.5.1): + - RNReanimated/view (4.6.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2898,7 +2898,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNWorklets (0.10.1): + - RNWorklets (0.12.2): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2920,10 +2920,10 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNWorklets/apple (= 0.10.1) - - RNWorklets/common (= 0.10.1) + - RNWorklets/apple (= 0.12.2) + - RNWorklets/common (= 0.12.2) - Yoga - - RNWorklets/apple (0.10.1): + - RNWorklets/apple (0.12.2): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2946,7 +2946,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNWorklets/common (0.10.1): + - RNWorklets/common (0.12.2): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3608,11 +3608,11 @@ SPEC CHECKSUMS: RNImageCropPicker: 5c9c8bef4c72d3f451b499aa0ecd90a644ea1bbd RNKeychain: d57edf101a72acf3ae85fe4d796eed1d951d2a90 RNLocalize: 17bce0556e2d33fe1201c8cef53b3b7786e07612 - RNReanimated: 28766837a1b96215c55c2be65b27b8903e31ef5d + RNReanimated: 0c93d90acdd8b5341f8ef5d08ab13e321ef374ca RNScreens: 5b4b260e28d8263d97fddcb53e59649515797bcc RNSVG: 68c366b996064ea99cee1e55da62b8feafb33a78 RNTrueSheet: 93f1765e65782ef6de12fd9338061a51399bb337 - RNWorklets: 43af89f696342f91118a2f1f3a879056e2f47331 + RNWorklets: 5793a617e7cc6e47a8ba79f2db881176b137cbc9 SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c diff --git a/jest.setup.js b/jest.setup.js index 8512d2ceb02..300cbd57106 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -32,6 +32,11 @@ loadAsyncMock.mockImplementation(() => Promise.resolve({ width: 200, height: 300 jest.mock('react-native-worklets', () => jest.requireActual('react-native-worklets/lib/module/mock')); +jest.mock('react-native-reanimated/src/css/native/proxy', () => ({ + ...jest.requireActual('react-native-reanimated/src/css/native/proxy'), + setCSSEventHandler: jest.fn() +})); + jest.mock('react-native-reanimated', () => { const actual = jest.requireActual('react-native-reanimated/mock'); return { diff --git a/package.json b/package.json index 21975423cd6..bc3bc445ef7 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "react-native-notifier": "1.6.1", "react-native-picker-select": "9.0.1", "react-native-popover-view": "5.1.7", - "react-native-reanimated": "4.5.1", + "react-native-reanimated": "4.6.0", "react-native-safe-area-context": "^5.5.2", "react-native-screens": "~4.26.2", "react-native-skeleton-placeholder": "5.2.4", @@ -124,7 +124,7 @@ "react-native-url-polyfill": "2.0.0", "react-native-webrtc": "124.0.7", "react-native-webview": "13.16.1", - "react-native-worklets": "^0.10.1", + "react-native-worklets": "0.12.2", "react-redux": "8.0.5", "reanimated-tab-view": "^0.3.0", "redux": "4.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20b09bcb57e..7b0d05cfd78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,7 +26,7 @@ importers: version: 2.9.11(react-hook-form@7.34.2(react@19.2.3)) '@lodev09/react-native-true-sheet': specifier: ~3.7.0 - version: 3.7.3(b217ab5bd6345e9949a4a395c898a850) + version: 3.7.3(96151402182f1302dad3fc829007f172) '@magrinj/expo-quick-look': specifier: 0.4.0 version: 0.4.0(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -77,7 +77,7 @@ importers: version: 0.86.3(@babel/core@7.29.7) '@react-navigation/drawer': specifier: ^7.12.2 - version: 7.12.2(568a64fb44ad70e6f0b55611752f41f8) + version: 7.12.2(e134207243f282dc8b8f0c5dc985da80) '@react-navigation/elements': specifier: ^2.9.25 version: 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) @@ -128,7 +128,7 @@ importers: version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@8.57.1) expo: specifier: ^57.0.0 - version: 57.0.23(92e84404446a2d5101115c437b8fd679) + version: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-apple-authentication: specifier: ~57.0.2 version: 57.0.2(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -251,7 +251,7 @@ importers: version: https://codeload.github.com/RocketChat/react-native-katex/tar.gz/37e579804fe238732d8a4b06dec073610ab062b1(react-native-webview@13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-keyboard-controller: specifier: 1.18.5 - version: 1.18.5(react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + version: 1.18.5(react-native-reanimated@4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-keychain: specifier: 10.0.0 version: 10.0.0 @@ -286,8 +286,8 @@ importers: specifier: 5.1.7 version: 5.1.7 react-native-reanimated: - specifier: 4.5.1 - version: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + specifier: 4.6.0 + version: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: specifier: ^5.5.2 version: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -310,14 +310,14 @@ importers: specifier: 13.16.1 version: 13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-worklets: - specifier: ^0.10.1 - version: 0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + specifier: 0.12.2 + version: 0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-redux: specifier: 8.0.5 version: 8.0.5(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(redux@4.2.0) reanimated-tab-view: specifier: ^0.3.0 - version: 0.3.0(641d271507e632f6b7f49ee76b960c3d) + version: 0.3.0(435be115736f4209290803c40574d60f) redux: specifier: 4.2.0 version: 4.2.0 @@ -390,7 +390,7 @@ importers: version: 2.3.3 '@gorhom/bottom-sheet': specifier: ^5 - version: 5.2.8(9928f68f03a282f377b2614cb23deb76) + version: 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) '@react-native-community/cli': specifier: 20.0.0 version: 20.0.0(typescript@7.0.2) @@ -414,7 +414,7 @@ importers: version: 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) '@storybook/react-native': specifier: ~9.0.9 - version: 9.0.18(03fc32bb656def7817eec6786e023d85) + version: 9.0.18(50ef928692a2961db10db85bb365a2eb) '@testing-library/react-native': specifier: ^13.2.0 version: 13.3.3(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) @@ -6823,12 +6823,12 @@ packages: react-native-popover-view@5.1.7: resolution: {integrity: sha512-Xa+zKYp9x62UNezT8o9SnqQaBQEHdGwGHZnFnA2bsXLneVRvvlBopNwMNj+p7N65qnPf3mmzWj+57ThtM1qFuw==} - react-native-reanimated@4.5.1: - resolution: {integrity: sha512-RnMvtDuR+68ig864gAvZCOdZehqhC5rFmMo0kn+ARfgVSTvFeF6IFLBVgMPUu0KwihaapEyW24WRi6nEyy1kSA==} + react-native-reanimated@4.6.0: + resolution: {integrity: sha512-9vbQmok5BPX2J3RLPFt2UnwmK2QQoMDPDi0VB4a9JWIiEc55pdzvwEsdH91V/JXKNErqM6a9fElTBMc/1cbtBA==} peerDependencies: react: '*' - react-native: 0.83 - 0.86 - react-native-worklets: 0.10.x + react-native: 0.83 - 0.87 + react-native-worklets: 0.12.x react-native-safe-area-context@5.7.0: resolution: {integrity: sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==} @@ -6872,13 +6872,13 @@ packages: react: '*' react-native: '*' - react-native-worklets@0.10.1: - resolution: {integrity: sha512-62mRM19bDpfpdI8HLkEErcdOsrAPDtE9lA/sw+5lLRpzBHNhxaoj9QyY2KjXqUmirelxkX4zuPGTC3VdA0feJA==} + react-native-worklets@0.12.2: + resolution: {integrity: sha512-TB6Aze0WJg/U0XOu4Zfx/yV8i5i7C/lbMymUXPbq1izDy2nuzZ9ujQAMwAHM2Ygyhzi8sdRk/7FukuTTfuil9A==} peerDependencies: '@babel/core': '*' '@react-native/metro-config': '*' react: '*' - react-native: 0.83 - 0.86 + react-native: 0.83 - 0.87 react-native@0.86.3: resolution: {integrity: sha512-JR5s3bM9ezud+Mw24GlNXNfthqPIKwrQgPPJcam+L97t2sKjjEavhCzBn+fyqZZRcM5+XlhYxpTxVkK7e1n38Q==} @@ -9406,7 +9406,7 @@ snapshots: connect: 3.7.0 debug: 4.4.3 dnssd-advertise: 1.1.6 - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-server: 57.0.3 fetch-nodeshim: 0.4.10 getenv: 2.0.0 @@ -9525,7 +9525,7 @@ snapshots: '@expo/dom-webview@57.0.1(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -9597,7 +9597,7 @@ snapshots: dependencies: '@expo/dom-webview': 57.0.1(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) anser: 1.4.10 - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) stacktrace-parser: 0.1.11 @@ -9628,7 +9628,7 @@ snapshots: postcss: 8.5.28 resolve-from: 5.0.0 optionalDependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) transitivePeerDependencies: - bufferutil - supports-color @@ -9721,7 +9721,7 @@ snapshots: '@expo/router-server@57.0.10(expo-constants@57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo-server@57.0.3)(expo@57.0.23)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: debug: 4.4.3 - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-constants: 57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) expo-font: 57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) expo-server: 57.0.3 @@ -10077,14 +10077,14 @@ snapshots: '@firebase/webchannel-wrapper@1.0.3': {} - '@gorhom/bottom-sheet@5.2.8(9928f68f03a282f377b2614cb23deb76)': + '@gorhom/bottom-sheet@5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e)': dependencies: '@gorhom/portal': 1.0.14(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) invariant: 2.2.4 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) optionalDependencies: '@types/react': 19.2.18 @@ -10360,19 +10360,19 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@lodev09/react-native-true-sheet@3.7.3(b217ab5bd6345e9949a4a395c898a850)': + '@lodev09/react-native-true-sheet@3.7.3(96151402182f1302dad3fc829007f172)': dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - '@gorhom/bottom-sheet': 5.2.8(9928f68f03a282f377b2614cb23deb76) + '@gorhom/bottom-sheet': 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-worklets: 0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-worklets: 0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@magrinj/expo-quick-look@0.4.0(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -10729,7 +10729,7 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) '@react-native-community/hooks@100.1.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: @@ -10759,7 +10759,7 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) transitivePeerDependencies: - '@react-native-async-storage/async-storage' @@ -10768,7 +10768,7 @@ snapshots: '@react-native-firebase/app': 21.14.0(@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) stacktrace-js: 2.0.2 optionalDependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) '@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: @@ -10948,16 +10948,16 @@ snapshots: use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/drawer@7.12.2(568a64fb44ad70e6f0b55611752f41f8)': + '@react-navigation/drawer@7.12.2(e134207243f282dc8b8f0c5dc985da80)': dependencies: '@react-navigation/elements': 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-drawer-layout: 4.2.5(641d271507e632f6b7f49ee76b960c3d) + react-native-drawer-layout: 4.2.5(435be115736f4209290803c40574d60f) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-screens: 4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) @@ -11129,9 +11129,9 @@ snapshots: - react-dom - typescript - '@storybook/react-native-ui@9.1.4(ec70a72c944b256777781574cc6aeff6)': + '@storybook/react-native-ui@9.1.4(66b479d1a625928ae8625bb9b43592a5)': dependencies: - '@gorhom/bottom-sheet': 5.2.8(9928f68f03a282f377b2614cb23deb76) + '@gorhom/bottom-sheet': 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) '@storybook/react': 9.1.17(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) '@storybook/react-native-theming': 9.1.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@storybook/react-native-ui-common': 9.1.4(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) @@ -11142,7 +11142,7 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-svg: 15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) store2: 2.14.4 @@ -11151,12 +11151,12 @@ snapshots: - react-dom - typescript - '@storybook/react-native@9.0.18(03fc32bb656def7817eec6786e023d85)': + '@storybook/react-native@9.0.18(50ef928692a2961db10db85bb365a2eb)': dependencies: '@storybook/global': 5.0.0 '@storybook/react': 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) '@storybook/react-native-theming': 9.1.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - '@storybook/react-native-ui': 9.1.4(ec70a72c944b256777781574cc6aeff6) + '@storybook/react-native-ui': 9.1.4(66b479d1a625928ae8625bb9b43592a5) '@storybook/react-native-ui-common': 9.1.4(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) commander: 8.3.0 dedent: 1.6.0(babel-plugin-macros@3.1.0) @@ -11170,9 +11170,9 @@ snapshots: util: 0.12.5 ws: 8.18.3 optionalDependencies: - '@gorhom/bottom-sheet': 5.2.8(9928f68f03a282f377b2614cb23deb76) + '@gorhom/bottom-sheet': 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - babel-plugin-macros @@ -12048,7 +12048,7 @@ snapshots: react-refresh: 0.14.2 optionalDependencies: '@babel/runtime': 7.25.9 - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) transitivePeerDependencies: - '@babel/core' - supports-color @@ -13054,18 +13054,18 @@ snapshots: expo-apple-authentication@57.0.2(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) expo-application@57.0.3(expo@57.0.23): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-asset@57.0.17(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2): dependencies: '@expo/image-utils': 0.11.5(typescript@7.0.2) - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-constants: 57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -13075,7 +13075,7 @@ snapshots: expo-audio@57.0.5(expo-asset@57.0.17(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2))(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-asset: 57.0.17(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -13083,7 +13083,7 @@ snapshots: expo-camera@57.0.5(@types/emscripten@1.41.6)(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: barcode-detector: 3.2.2(@types/emscripten@1.41.6) - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) transitivePeerDependencies: @@ -13092,51 +13092,51 @@ snapshots: expo-constants@57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: '@expo/env': 2.4.3 - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) transitivePeerDependencies: - supports-color expo-device@57.0.2(expo@57.0.23): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) ua-parser-js: 1.0.2 expo-document-picker@57.0.2(expo@57.0.23): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-file-system@57.0.7(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) fontfaceobserver: 2.3.0 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) expo-haptics@57.0.3(expo@57.0.23): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-image@57.0.5(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) sf-symbols-typescript: 2.2.0 expo-keep-awake@57.0.2(expo@57.0.23)(react@19.2.3): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react: 19.2.3 expo-local-authentication@57.0.3(expo@57.0.23): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) invariant: 2.2.4 expo-modules-autolinking@57.0.13(typescript@7.0.2): @@ -13149,7 +13149,7 @@ snapshots: - supports-color - typescript - expo-modules-core@57.0.18(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + expo-modules-core@57.0.18(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: '@expo/expo-modules-macros-plugin': 0.6.1 expo-modules-jsi: 57.1.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) @@ -13157,7 +13157,7 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - react-native-worklets: 0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-worklets: 0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) expo-modules-jsi@57.1.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: @@ -13168,7 +13168,7 @@ snapshots: '@expo/image-utils': 0.11.5(typescript@7.0.2) abort-controller: 3.0.0 badgin: 1.2.3 - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-application: 57.0.3(expo@57.0.23) expo-constants: 57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)) react: 19.2.3 @@ -13181,7 +13181,7 @@ snapshots: expo-status-bar@57.0.1(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -13189,27 +13189,27 @@ snapshots: dependencies: '@react-native/normalize-colors': 0.86.3 debug: 4.4.3 - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) transitivePeerDependencies: - supports-color expo-video-thumbnails@57.0.2(expo@57.0.23): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) expo-video@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) expo-web-browser@57.0.3(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)): dependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - expo@57.0.23(92e84404446a2d5101115c437b8fd679): + expo@57.0.23(8132ea204d39ca81423aaa5e2384dd28): dependencies: '@babel/runtime': 7.25.9 '@expo/cli': 57.0.25(@expo/dom-webview@57.0.1)(expo-constants@57.0.18(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo@57.0.23)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@7.0.2) @@ -13229,7 +13229,7 @@ snapshots: expo-font: 57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) expo-keep-awake: 57.0.2(expo@57.0.23)(react@19.2.3) expo-modules-autolinking: 57.0.13(typescript@7.0.2) - expo-modules-core: 57.0.18(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + expo-modules-core: 57.0.18(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) pretty-format: 29.7.0 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -14133,7 +14133,7 @@ snapshots: server-only: 0.0.1 stacktrace-js: 2.0.2 optionalDependencies: - expo: 57.0.23(92e84404446a2d5101115c437b8fd679) + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) transitivePeerDependencies: - '@babel/core' - bufferutil @@ -15688,13 +15688,13 @@ snapshots: '@babel/runtime': 7.25.9 dotenv: 16.6.1 - react-native-drawer-layout@4.2.5(641d271507e632f6b7f49ee76b960c3d): + react-native-drawer-layout@4.2.5(435be115736f4209290803c40574d60f): dependencies: color: 4.2.3 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) react-native-easy-grid@0.2.2: @@ -15746,12 +15746,12 @@ snapshots: react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-webview: 13.16.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-keyboard-controller@1.18.5(react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + react-native-keyboard-controller@1.18.5(react-native-reanimated@4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-is-edge-to-edge: 1.3.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-keychain@10.0.0: {} @@ -15814,12 +15814,12 @@ snapshots: deprecated-react-native-prop-types: 2.3.0 prop-types: 15.8.1 - react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + react-native-reanimated@4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-is-edge-to-edge: 1.3.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-worklets: 0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-worklets: 0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) semver: 7.7.4 react-native-safe-area-context@5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): @@ -15870,9 +15870,10 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.7) @@ -15882,6 +15883,7 @@ snapshots: '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.7) '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.7) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 '@react-native/metro-config': 0.86.3(@babel/core@7.29.7) convert-source-map: 2.0.0 @@ -15981,12 +15983,12 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - reanimated-tab-view@0.3.0(641d271507e632f6b7f49ee76b960c3d): + reanimated-tab-view@0.3.0(435be115736f4209290803c40574d60f): dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) recast@0.23.11: dependencies: From 18d5f9d940a7606803e8360c6e3bbb0b62b06710 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 14:10:23 -0300 Subject: [PATCH 021/120] chore(deps): bump react-native-screens to 4.28.0 (#7709) --- ios/Podfile.lock | 12 ++++++------ package.json | 2 +- pnpm-lock.yaml | 22 +++++++++++----------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9c6df442d34..57fa1164bdd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2784,7 +2784,7 @@ PODS: - ReactNativeDependencies - RNWorklets - Yoga - - RNScreens (4.26.2): + - RNScreens (4.28.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2806,9 +2806,9 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNScreens/common (= 4.26.2) + - RNScreens/common (= 4.28.0) - Yoga - - RNScreens/common (4.26.2): + - RNScreens/common (4.28.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3608,11 +3608,11 @@ SPEC CHECKSUMS: RNImageCropPicker: 5c9c8bef4c72d3f451b499aa0ecd90a644ea1bbd RNKeychain: d57edf101a72acf3ae85fe4d796eed1d951d2a90 RNLocalize: 17bce0556e2d33fe1201c8cef53b3b7786e07612 - RNReanimated: 0c93d90acdd8b5341f8ef5d08ab13e321ef374ca - RNScreens: 5b4b260e28d8263d97fddcb53e59649515797bcc + RNReanimated: c57ef141c7843c2849531e7556236d7223b7db1a + RNScreens: 8708989f700bdc3dc5abc3c77aa3b5f3e73ce2b2 RNSVG: 68c366b996064ea99cee1e55da62b8feafb33a78 RNTrueSheet: 93f1765e65782ef6de12fd9338061a51399bb337 - RNWorklets: 5793a617e7cc6e47a8ba79f2db881176b137cbc9 + RNWorklets: 453d16f4cb680d5a4262b314519711e7765a80a1 SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c diff --git a/package.json b/package.json index bc3bc445ef7..8090193e30e 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "react-native-popover-view": "5.1.7", "react-native-reanimated": "4.6.0", "react-native-safe-area-context": "^5.5.2", - "react-native-screens": "~4.26.2", + "react-native-screens": "~4.28.0", "react-native-skeleton-placeholder": "5.2.4", "react-native-svg": "^15.12.1", "react-native-url-polyfill": "2.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b0d05cfd78..63bed4e9459 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,7 +77,7 @@ importers: version: 0.86.3(@babel/core@7.29.7) '@react-navigation/drawer': specifier: ^7.12.2 - version: 7.12.2(e134207243f282dc8b8f0c5dc985da80) + version: 7.12.2(73ba9f8e6c1cf66e4b24f58fe7369d0e) '@react-navigation/elements': specifier: ^2.9.25 version: 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) @@ -86,7 +86,7 @@ importers: version: 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': specifier: ^7.17.5 - version: 7.17.5(b9b07f1e05b03fd68f5bb004ad12b8b4) + version: 7.17.5(3e0f47b5bea0de66da61abc3c1812caa) '@rocket.chat/media-signaling': specifier: 1.0.0-rc.1 version: 1.0.0-rc.1 @@ -292,8 +292,8 @@ importers: specifier: ^5.5.2 version: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-screens: - specifier: ~4.26.2 - version: 4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + specifier: ~4.28.0 + version: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-skeleton-placeholder: specifier: 5.2.4 version: 5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -6836,8 +6836,8 @@ packages: react: '*' react-native: '*' - react-native-screens@4.26.2: - resolution: {integrity: sha512-2XnWsZToKj76trGtEZzx5ELD/qOICFEprEeUntImmitQFVUkea27fiWdUSITArI356Y1qynpXZINW+Unbhky/A==} + react-native-screens@4.28.0: + resolution: {integrity: sha512-0/79Z8RCibuaAHti+DJ2Dq0m6UFWh4+p1bH58K3cjiQ7IDWs5kU9tCAj4lXcJMHHonbF2mIE2BeaPSEvOpIiFg==} peerDependencies: react: '*' react-native: '*' @@ -10948,7 +10948,7 @@ snapshots: use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/drawer@7.12.2(e134207243f282dc8b8f0c5dc985da80)': + '@react-navigation/drawer@7.12.2(73ba9f8e6c1cf66e4b24f58fe7369d0e)': dependencies: '@react-navigation/elements': 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -10959,7 +10959,7 @@ snapshots: react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-screens: 4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -10976,7 +10976,7 @@ snapshots: optionalDependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - '@react-navigation/native-stack@7.17.5(b9b07f1e05b03fd68f5bb004ad12b8b4)': + '@react-navigation/native-stack@7.17.5(3e0f47b5bea0de66da61abc3c1812caa)': dependencies: '@react-navigation/elements': 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -10984,7 +10984,7 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-screens: 4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) sf-symbols-typescript: 2.2.0 warn-once: 0.1.1 transitivePeerDependencies: @@ -15827,7 +15827,7 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-screens@4.26.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + react-native-screens@4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-freeze: 1.0.4(react@19.2.3) From da561234d26d8bee865868696d2e8db2bfefd90c Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 14:20:47 -0300 Subject: [PATCH 022/120] chore(deps): bump @react-navigation family (#7710) --- package.json | 8 +- ...ch => @react-navigation+core+7.22.1.patch} | 0 pnpm-lock.yaml | 147 +++++++----------- 3 files changed, 59 insertions(+), 96 deletions(-) rename patches/{@react-navigation+core+7.21.1.patch => @react-navigation+core+7.22.1.patch} (100%) diff --git a/package.json b/package.json index 8090193e30e..e8b05e1dcf3 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,10 @@ "@react-native-masked-view/masked-view": "^0.3.1", "@react-native-picker/picker": "2.11.4", "@react-native/codegen": "0.86.3", - "@react-navigation/drawer": "^7.12.2", - "@react-navigation/elements": "^2.9.25", - "@react-navigation/native": "^7.3.3", - "@react-navigation/native-stack": "^7.17.5", + "@react-navigation/drawer": "^7.14.1", + "@react-navigation/elements": "^2.9.42", + "@react-navigation/native": "^7.4.1", + "@react-navigation/native-stack": "^7.19.1", "@rocket.chat/media-signaling": "1.0.0-rc.1", "@rocket.chat/message-parser": "0.31.36", "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto#main", diff --git a/patches/@react-navigation+core+7.21.1.patch b/patches/@react-navigation+core+7.22.1.patch similarity index 100% rename from patches/@react-navigation+core+7.21.1.patch rename to patches/@react-navigation+core+7.22.1.patch diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 63bed4e9459..9357c543078 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,7 +26,7 @@ importers: version: 2.9.11(react-hook-form@7.34.2(react@19.2.3)) '@lodev09/react-native-true-sheet': specifier: ~3.7.0 - version: 3.7.3(96151402182f1302dad3fc829007f172) + version: 3.7.3(559152e7857b125189ca42b7a151d6a4) '@magrinj/expo-quick-look': specifier: 0.4.0 version: 0.4.0(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -76,17 +76,17 @@ importers: specifier: 0.86.3 version: 0.86.3(@babel/core@7.29.7) '@react-navigation/drawer': - specifier: ^7.12.2 - version: 7.12.2(73ba9f8e6c1cf66e4b24f58fe7369d0e) + specifier: ^7.14.1 + version: 7.14.2(e24299e34c4f513bb3e57ce41b68cee3) '@react-navigation/elements': - specifier: ^2.9.25 - version: 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) + specifier: ^2.9.42 + version: 2.9.43(9ff45bd34fc277ebcc4865acd7015c6f) '@react-navigation/native': - specifier: ^7.3.3 - version: 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + specifier: ^7.4.1 + version: 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': - specifier: ^7.17.5 - version: 7.17.5(3e0f47b5bea0de66da61abc3c1812caa) + specifier: ^7.19.1 + version: 7.19.2(5323759ca43d3318f9a4b86095166ade) '@rocket.chat/media-signaling': specifier: 1.0.0-rc.1 version: 1.0.0-rc.1 @@ -2695,15 +2695,15 @@ packages: '@types/react': optional: true - '@react-navigation/core@7.21.1': - resolution: {integrity: sha512-9eOK71VFEyJjm1bP8o4Gf7YYppWPZ+RdNIjTc+WbSM7AFEH0XXEIHkkhpIBuB416sDlRZ1CRxHk2frh1HPBZqw==} + '@react-navigation/core@7.22.1': + resolution: {integrity: sha512-6JAaYM4u6RPtVMUjN5ovX5DNBdBZtDqebk7kEwlncUP6fP4P94+qEYv1bRmtxSFs7UxiZwQQs+jzTsBaW2WeyA==} peerDependencies: react: '>= 18.2.0' - '@react-navigation/drawer@7.12.2': - resolution: {integrity: sha512-LEwER8YRq6Kjxjw9vECOVgWUAiP8l2jso3XntfAvthZqYWtVD4Ms1HdAUAf4Emwp78W15PEj7m0PV2StmglkLw==} + '@react-navigation/drawer@7.14.2': + resolution: {integrity: sha512-vHDJ5TlKZ2RWW1z+Zy7k4mwuo3kLXfhO2+EpDWBTPcaeb8b/3upouKDALQviWCQ1KqyUAme+DbE8cIoGcuSQvg==} peerDependencies: - '@react-navigation/native': ^7.3.3 + '@react-navigation/native': ^7.4.1 react: '>= 18.2.0' react-native: '*' react-native-gesture-handler: '>= 2.0.0' @@ -2711,11 +2711,11 @@ packages: react-native-safe-area-context: '>= 4.0.0' react-native-screens: '>= 4.0.0' - '@react-navigation/elements@2.9.25': - resolution: {integrity: sha512-cV7Hny55aE/uge6f4UB0pbGQbFl3XjXLMW+lTBNuJs8P7a9tuf7dkkPHxxgnLIvxE+KJVI2K8CGabfDk4Ht0Sg==} + '@react-navigation/elements@2.9.43': + resolution: {integrity: sha512-XOYpRfgT9RPV2VIoXeEfTVdmXwEPpHwkezDkZVsVNEN6rTWDo07BPyLmWsvXooczyMf3jHe9AB2p/U9lJArtiQ==} peerDependencies: '@react-native-masked-view/masked-view': '>= 0.2.0' - '@react-navigation/native': ^7.3.3 + '@react-navigation/native': ^7.4.1 react: '>= 18.2.0' react-native: '*' react-native-safe-area-context: '>= 4.0.0' @@ -2723,23 +2723,23 @@ packages: '@react-native-masked-view/masked-view': optional: true - '@react-navigation/native-stack@7.17.5': - resolution: {integrity: sha512-jS67nzFlpuzNSDzAsL09hk59sNrp6zxViqKW+RzC5ia26VMxZBw2QFLBZN6rOnO3nOcpAKDpS8/Mx6vicurDfw==} + '@react-navigation/native-stack@7.19.2': + resolution: {integrity: sha512-D6hmtgpMZz1x8R0PLbfqqKh5ajEkb13DguE7X/MNNKYL4riY4zD3TrjbcesUC2EZklpQ/EoJ6h6HAhMNfCCsoQ==} peerDependencies: - '@react-navigation/native': ^7.3.3 + '@react-navigation/native': ^7.4.1 react: '>= 18.2.0' react-native: '*' react-native-safe-area-context: '>= 4.0.0' react-native-screens: '>= 4.0.0' - '@react-navigation/native@7.3.3': - resolution: {integrity: sha512-VjZngfeiyJestZPT99CKHRi3qOR6XwnEEPWb6uHF/L7fBI6RBVP842iJf61MUHRTzsWPUT+epJqdf1wm8N5YkQ==} + '@react-navigation/native@7.4.1': + resolution: {integrity: sha512-pEnu5XzAXV6RWr6A6pmZ7qLB+O6TJWMhBO25AjckMdr596UWulMPHQCtBcnOUfg6K+2h9ZUctHF2E5Xu4vcOrQ==} peerDependencies: react: '>= 18.2.0' react-native: '*' - '@react-navigation/routers@7.6.0': - resolution: {integrity: sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow==} + '@react-navigation/routers@7.6.4': + resolution: {integrity: sha512-GI7eJm8/KsZUQaYcXvEExikKurRZRgEsSzyZ7faENfi65yqJBCXjDMwyN1pF6pNW1MoLH1ErDwDivFxY6BzD3w==} '@redux-saga/core@1.4.2': resolution: {integrity: sha512-nIMLGKo6jV6Wc1sqtVQs1iqbB3Kq20udB/u9XEaZQisT6YZ0NRB8+4L6WqD/E+YziYutd27NJbG8EWUPkb7c6Q==} @@ -4169,10 +4169,6 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - decode-uri-component@0.2.2: - resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} - engines: {node: '>=0.10'} - decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -4835,10 +4831,6 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - filter-obj@1.1.0: - resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} - engines: {node: '>=0.10.0'} - finalhandler@1.1.2: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} @@ -6589,10 +6581,6 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - query-string@7.1.3: - resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} - engines: {node: '>=6'} - querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} @@ -6644,9 +6632,6 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-is@19.1.0: - resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==} - react-is@19.3.0: resolution: {integrity: sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==} @@ -6688,8 +6673,8 @@ packages: peerDependencies: '@babel/runtime': ^7.20.6 - react-native-drawer-layout@4.2.5: - resolution: {integrity: sha512-Yl82uLkXjXuq7222hWGIDsq5A6R/bsCeCEgdIxQUxAEHf00oRdDnRByLx3Fsij3qwtmYNPGrHV1NH8G8hbCbLQ==} + react-native-drawer-layout@4.2.10: + resolution: {integrity: sha512-O6TQdZ5LSm3dqnuR4rX9KPtE+9dVg7jsezEYz1l01rkQTe4fQvYZIoPu2sZFl5X2N70uhRdjnPULySVR3sBUwA==} peerDependencies: react: '>= 18.2.0' react-native: '*' @@ -7297,10 +7282,6 @@ packages: resolution: {integrity: sha512-S6ji+flMEga+1QU79NDbwZ8Ivf0S/MpupQQiIC0rTpU/ZTKgcajijJJb1OcByBQDjrXCN1/DJtGz4ZJeBMPGJw==} hasBin: true - split-on-first@1.1.0: - resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} - engines: {node: '>=6'} - sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -7331,8 +7312,10 @@ packages: resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} engines: {node: '>=6'} - standard-navigation@0.0.7: - resolution: {integrity: sha512-NCGLCNyuXrFOkGHxdNZFnpsehGtiq1oXbPhKl7ZuxFO5J//H2evqqOchmD4YwEUJnkjO4kH9Xp4hQX6hdAYCKQ==} + standard-navigation@0.0.8: + resolution: {integrity: sha512-TyVbo7INUDWtsUWDFn8RR7kwR87U0S4xHfLfbbnyeC581TmmyqQ+eM+nPw8rQTSD8QitRVcYfPaSHr/QJiUy1g==} + peerDependencies: + react: '*' statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} @@ -7365,10 +7348,6 @@ packages: streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} - strict-uri-encode@2.0.0: - resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} - engines: {node: '>=4'} - string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -10360,13 +10339,13 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@lodev09/react-native-true-sheet@3.7.3(96151402182f1302dad3fc829007f172)': + '@lodev09/react-native-true-sheet@3.7.3(559152e7857b125189ca42b7a151d6a4)': dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: '@gorhom/bottom-sheet': 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) - '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-worklets: 0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -10936,26 +10915,25 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 - '@react-navigation/core@7.21.1(react@19.2.3)': + '@react-navigation/core@7.22.1(react@19.2.3)': dependencies: - '@react-navigation/routers': 7.6.0 + '@react-navigation/routers': 7.6.4 escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 - nanoid: 3.3.11 - query-string: 7.1.3 + nanoid: 3.3.19 react: 19.2.3 - react-is: 19.1.0 + react-is: 19.3.0 use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/drawer@7.12.2(73ba9f8e6c1cf66e4b24f58fe7369d0e)': + '@react-navigation/drawer@7.14.2(e24299e34c4f513bb3e57ce41b68cee3)': dependencies: - '@react-navigation/elements': 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) - '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@react-navigation/elements': 2.9.43(9ff45bd34fc277ebcc4865acd7015c6f) + '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-drawer-layout: 4.2.5(435be115736f4209290803c40574d60f) + react-native-drawer-layout: 4.2.10(435be115736f4209290803c40574d60f) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -10964,9 +10942,9 @@ snapshots: transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/elements@2.9.25(94b93b3be2a5aeeeacd03698514a6eb6)': + '@react-navigation/elements@2.9.43(9ff45bd34fc277ebcc4865acd7015c6f)': dependencies: - '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -10976,10 +10954,10 @@ snapshots: optionalDependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - '@react-navigation/native-stack@7.17.5(3e0f47b5bea0de66da61abc3c1812caa)': + '@react-navigation/native-stack@7.19.2(5323759ca43d3318f9a4b86095166ade)': dependencies: - '@react-navigation/elements': 2.9.25(94b93b3be2a5aeeeacd03698514a6eb6) - '@react-navigation/native': 7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@react-navigation/elements': 2.9.43(9ff45bd34fc277ebcc4865acd7015c6f) + '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -10990,20 +10968,20 @@ snapshots: transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/native@7.3.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': + '@react-navigation/native@7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: - '@react-navigation/core': 7.21.1(react@19.2.3) + '@react-navigation/core': 7.22.1(react@19.2.3) escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 - nanoid: 3.3.11 + nanoid: 3.3.19 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - standard-navigation: 0.0.7 + standard-navigation: 0.0.8(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) - '@react-navigation/routers@7.6.0': + '@react-navigation/routers@7.6.4': dependencies: - nanoid: 3.3.11 + nanoid: 3.3.19 '@redux-saga/core@1.4.2': dependencies: @@ -12555,8 +12533,6 @@ snapshots: decimal.js@10.6.0: {} - decode-uri-component@0.2.2: {} - decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -13306,8 +13282,6 @@ snapshots: dependencies: to-regex-range: 5.0.1 - filter-obj@1.1.0: {} - finalhandler@1.1.2: dependencies: debug: 2.6.9 @@ -15574,13 +15548,6 @@ snapshots: quansync@0.2.11: {} - query-string@7.1.3: - dependencies: - decode-uri-component: 0.2.2 - filter-obj: 1.1.0 - split-on-first: 1.1.0 - strict-uri-encode: 2.0.0 - querystringify@2.2.0: {} queue-microtask@1.2.3: {} @@ -15633,8 +15600,6 @@ snapshots: react-is@18.3.1: {} - react-is@19.1.0: {} - react-is@19.3.0: {} react-native-a11y-order@0.4.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): @@ -15688,7 +15653,7 @@ snapshots: '@babel/runtime': 7.25.9 dotenv: 16.6.1 - react-native-drawer-layout@4.2.5(435be115736f4209290803c40574d60f): + react-native-drawer-layout@4.2.10(435be115736f4209290803c40574d60f): dependencies: color: 4.2.3 react: 19.2.3 @@ -16377,8 +16342,6 @@ snapshots: commander: 13.1.0 wicked-good-xpath: 1.3.0 - split-on-first@1.1.0: {} - sprintf-js@1.0.3: {} sql-escape-string@1.1.0: {} @@ -16410,7 +16373,9 @@ snapshots: dependencies: type-fest: 0.7.1 - standard-navigation@0.0.7: {} + standard-navigation@0.0.8(react@19.2.3): + dependencies: + react: 19.2.3 statuses@1.5.0: {} @@ -16456,8 +16421,6 @@ snapshots: - bare-abort-controller - react-native-b4a - strict-uri-encode@2.0.0: {} - string-length@4.0.2: dependencies: char-regex: 1.0.2 From 29c926fe1f6458538d51bdbfc132be478d6c9d1a Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 14:47:46 -0300 Subject: [PATCH 023/120] chore(deps): bump react-native-safe-area-context to 5.10.0 (#7711) --- ios/Podfile.lock | 12 ++++++------ package.json | 2 +- pnpm-lock.yaml | 44 ++++++++++++++++++++++---------------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 57fa1164bdd..f2a0eb6a6a2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1883,7 +1883,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context (5.7.0): + - react-native-safe-area-context (5.10.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1895,8 +1895,8 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-safe-area-context/common (= 5.7.0) - - react-native-safe-area-context/fabric (= 5.7.0) + - react-native-safe-area-context/common (= 5.10.0) + - react-native-safe-area-context/fabric (= 5.10.0) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -1907,7 +1907,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context/common (5.7.0): + - react-native-safe-area-context/common (5.10.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1929,7 +1929,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context/fabric (5.7.0): + - react-native-safe-area-context/fabric (5.10.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3553,7 +3553,7 @@ SPEC CHECKSUMS: react-native-external-keyboard: f89ea61eb77875621a1ad086769d829da8ae3fcd react-native-keyboard-controller: c5ba8bf515b500ecb3692bc5ecc944c6be4b3290 react-native-netinfo: a05f9b897e76ad24b53f615fed1cbb731932363d - react-native-safe-area-context: ba7283e541a5f57c6b27a7e33f2c40b8977888c0 + react-native-safe-area-context: 678f254e77e69986ae9d9e62721f152e73dbc76b react-native-slider: b15258ac3d287a5951f76b11497d659ed20c0def react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74 react-native-webview: 2a4da2e03d3ec2c731c4d1a389cb86aaa4902167 diff --git a/package.json b/package.json index e8b05e1dcf3..5bc0a2356ae 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "react-native-picker-select": "9.0.1", "react-native-popover-view": "5.1.7", "react-native-reanimated": "4.6.0", - "react-native-safe-area-context": "^5.5.2", + "react-native-safe-area-context": "^5.10.0", "react-native-screens": "~4.28.0", "react-native-skeleton-placeholder": "5.2.4", "react-native-svg": "^15.12.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9357c543078..737bfd93822 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,16 +77,16 @@ importers: version: 0.86.3(@babel/core@7.29.7) '@react-navigation/drawer': specifier: ^7.14.1 - version: 7.14.2(e24299e34c4f513bb3e57ce41b68cee3) + version: 7.14.2(dda132b9c8f311fd0053bf630a6001aa) '@react-navigation/elements': specifier: ^2.9.42 - version: 2.9.43(9ff45bd34fc277ebcc4865acd7015c6f) + version: 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) '@react-navigation/native': specifier: ^7.4.1 version: 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': specifier: ^7.19.1 - version: 7.19.2(5323759ca43d3318f9a4b86095166ade) + version: 7.19.2(9f93b74e18ae1df429c2a9dc7118e4c2) '@rocket.chat/media-signaling': specifier: 1.0.0-rc.1 version: 1.0.0-rc.1 @@ -289,8 +289,8 @@ importers: specifier: 4.6.0 version: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: - specifier: ^5.5.2 - version: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + specifier: ^5.10.0 + version: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-screens: specifier: ~4.28.0 version: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -414,7 +414,7 @@ importers: version: 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) '@storybook/react-native': specifier: ~9.0.9 - version: 9.0.18(50ef928692a2961db10db85bb365a2eb) + version: 9.0.18(abaa9f17cfa3ec17933d2333c1a01a26) '@testing-library/react-native': specifier: ^13.2.0 version: 13.3.3(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) @@ -6815,8 +6815,8 @@ packages: react-native: 0.83 - 0.87 react-native-worklets: 0.12.x - react-native-safe-area-context@5.7.0: - resolution: {integrity: sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==} + react-native-safe-area-context@5.10.0: + resolution: {integrity: sha512-U655+G0KItefqpTKZenr0aBNulY/49TUKSZkWGVtESlFK5qmML7BejQjPDCRU+GzC24g2KeWFJguWP5LR52dqA==} peerDependencies: react: '*' react-native: '*' @@ -10926,9 +10926,9 @@ snapshots: use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/drawer@7.14.2(e24299e34c4f513bb3e57ce41b68cee3)': + '@react-navigation/drawer@7.14.2(dda132b9c8f311fd0053bf630a6001aa)': dependencies: - '@react-navigation/elements': 2.9.43(9ff45bd34fc277ebcc4865acd7015c6f) + '@react-navigation/elements': 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 @@ -10936,32 +10936,32 @@ snapshots: react-native-drawer-layout: 4.2.10(435be115736f4209290803c40574d60f) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-screens: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/elements@2.9.43(9ff45bd34fc277ebcc4865acd7015c6f)': + '@react-navigation/elements@2.9.43(322efc84ae2b5bd2002572a8329e2a5c)': dependencies: '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) optionalDependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - '@react-navigation/native-stack@7.19.2(5323759ca43d3318f9a4b86095166ade)': + '@react-navigation/native-stack@7.19.2(9f93b74e18ae1df429c2a9dc7118e4c2)': dependencies: - '@react-navigation/elements': 2.9.43(9ff45bd34fc277ebcc4865acd7015c6f) + '@react-navigation/elements': 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-screens: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) sf-symbols-typescript: 2.2.0 warn-once: 0.1.1 @@ -11107,7 +11107,7 @@ snapshots: - react-dom - typescript - '@storybook/react-native-ui@9.1.4(66b479d1a625928ae8625bb9b43592a5)': + '@storybook/react-native-ui@9.1.4(c7e041ee7310df2b401daba6013bd73c)': dependencies: '@gorhom/bottom-sheet': 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) '@storybook/react': 9.1.17(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) @@ -11121,7 +11121,7 @@ snapshots: react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-svg: 15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) store2: 2.14.4 storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4) @@ -11129,12 +11129,12 @@ snapshots: - react-dom - typescript - '@storybook/react-native@9.0.18(50ef928692a2961db10db85bb365a2eb)': + '@storybook/react-native@9.0.18(abaa9f17cfa3ec17933d2333c1a01a26)': dependencies: '@storybook/global': 5.0.0 '@storybook/react': 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) '@storybook/react-native-theming': 9.1.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - '@storybook/react-native-ui': 9.1.4(66b479d1a625928ae8625bb9b43592a5) + '@storybook/react-native-ui': 9.1.4(c7e041ee7310df2b401daba6013bd73c) '@storybook/react-native-ui-common': 9.1.4(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) commander: 8.3.0 dedent: 1.6.0(babel-plugin-macros@3.1.0) @@ -11151,7 +11151,7 @@ snapshots: '@gorhom/bottom-sheet': 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-safe-area-context: 5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - babel-plugin-macros - bufferutil @@ -15787,7 +15787,7 @@ snapshots: react-native-worklets: 0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) semver: 7.7.4 - react-native-safe-area-context@5.7.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + react-native-safe-area-context@5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) From 5c0c03a557043a782022ccc84bd220f010941ac3 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 22 Sep 2026 10:51:38 -0300 Subject: [PATCH 024/120] fix(ios): adopt UIScene lifecycle for iOS 27 iOS 27 SDK requires scene-based app lifecycle. The app now uses Expo's ExpoAppSceneDelegate via a new SceneDelegate, with startReactNative and boot splash initialization moved out of the app delegate into the scene delegate. --- ios/AppDelegate.swift | 38 ++++++++++++++---------- ios/RocketChat.xcodeproj/project.pbxproj | 24 +++++++-------- ios/RocketChatRN/Info.plist | 30 +++++++++++++------ ios/SceneDelegate.swift | 17 +++++++++++ 4 files changed, 72 insertions(+), 37 deletions(-) create mode 100644 ios/SceneDelegate.swift diff --git a/ios/AppDelegate.swift b/ios/AppDelegate.swift index 636a2acedf2..56a299a8d4c 100644 --- a/ios/AppDelegate.swift +++ b/ios/AppDelegate.swift @@ -8,10 +8,10 @@ import PushKit @UIApplicationMain public class AppDelegate: ExpoAppDelegate { - var window: UIWindow? + public var window: UIWindow? var reactNativeDelegate: ReactNativeDelegate? - var reactNativeFactory: RCTReactNativeFactory? + public var reactNativeFactory: RCTReactNativeFactory? var watchConnection: WatchConnection? public override func application( @@ -44,21 +44,8 @@ public class AppDelegate: ExpoAppDelegate { reactNativeDelegate = delegate reactNativeFactory = factory -#if os(iOS) || os(tvOS) - window = UIWindow(frame: UIScreen.main.bounds) - factory.startReactNative( - withModuleName: "RocketChatRN", - in: window, - launchOptions: launchOptions) -#endif - let result = super.application(application, didFinishLaunchingWithOptions: launchOptions) - // Initialize boot splash - if let rootViewController = window?.rootViewController { - RNBootSplash.initWithStoryboard("LaunchScreen", rootView: rootViewController.view) - } - // Initialize SSL Pinning SSLPinning().migrate() @@ -86,6 +73,27 @@ public class AppDelegate: ExpoAppDelegate { let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler) return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result } + +#if os(iOS) || os(tvOS) + public func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + public func application( + _ application: UIApplication, + didDiscardSceneSessions sceneSessions: Set + ) {} +#endif +} + +extension AppDelegate: ExpoReactNativeFactoryProvider { + public var reactNativeFactoryModuleName: String { + "RocketChatRN" + } } class ReactNativeDelegate: ExpoReactNativeFactoryDelegate { diff --git a/ios/RocketChat.xcodeproj/project.pbxproj b/ios/RocketChat.xcodeproj/project.pbxproj index b3a5e045385..ea53c986722 100644 --- a/ios/RocketChat.xcodeproj/project.pbxproj +++ b/ios/RocketChat.xcodeproj/project.pbxproj @@ -211,6 +211,7 @@ 7AC0A1E02C000011001CAA11 /* Challenge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0A1E02C000002001CAA11 /* Challenge.mm */; }; 7AC0A1E02C000012001CAA11 /* Challenge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0A1E02C000002001CAA11 /* Challenge.mm */; }; 7ACFE7D92DDE48760090D9BC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACFE7D82DDE48760090D9BC /* AppDelegate.swift */; }; + 6F204E445C3D44699793D4F8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680FA18DB475479CAD48F334 /* SceneDelegate.swift */; }; 7AE10C0828A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 7AVR00022F5F5900002A6BDE /* VoipRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AVR00002F5F5900002A6BDE /* VoipRegion.swift */; }; 85160EB6C143E0493FE5F014 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */; }; @@ -450,6 +451,7 @@ 7AC0A1E02C000002001CAA11 /* Challenge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Challenge.mm; sourceTree = ""; }; 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 7ACFE7D82DDE48760090D9BC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 680FA18DB475479CAD48F334 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 7AE10C0528A59530003593CB /* Inter.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Inter.ttf; sourceTree = ""; }; 7AVR00002F5F5900002A6BDE /* VoipRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipRegion.swift; sourceTree = ""; }; 83F98EE33D91A93DF8E69F34 /* MediaCallsAnswerRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaCallsAnswerRequest.swift; sourceTree = ""; }; @@ -519,6 +521,7 @@ 6FA7E2362F28A9D300A1D45E /* ExternalInputModule.h */, 6FA7E2372F28A9D300A1D45E /* ExternalInputModule.m */, 7ACFE7D82DDE48760090D9BC /* AppDelegate.swift */, + 680FA18DB475479CAD48F334 /* SceneDelegate.swift */, B1E1F0032F03A00000EXT003 /* ExternalInputModule.h */, B1E1F0042F03A00000EXT004 /* ExternalInputModule.m */, 65B9A7192AFC24190088956F /* ringtone.mp3 */, @@ -1355,7 +1358,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; + shellScript = "export EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=/opt/homebrew/bin/node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; 7A55BE3C2F1131C000D8744D /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -1382,7 +1385,7 @@ inputFileListPaths = ( ); inputPaths = ( - "$TARGET_BUILD_DIR/$INFOPLIST_PATH", + $TARGET_BUILD_DIR/$INFOPLIST_PATH, ); name = "Upload source maps to Bugsnag"; outputFileListPaths = ( @@ -1391,7 +1394,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n"; + shellScript = "#SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n"; showEnvVarsInLog = 0; }; 86A998705576AFA7CE938617 /* [Expo] Configure project */ = { @@ -1727,6 +1730,7 @@ 7AAB3E24257E6A6E00707CF6 /* Date+Extensions.swift in Sources */, 7AAB3E25257E6A6E00707CF6 /* Database.swift in Sources */, 7ACFE7D92DDE48760090D9BC /* AppDelegate.swift in Sources */, + 6F204E445C3D44699793D4F8 /* SceneDelegate.swift in Sources */, 7A3704572F7DB36E009085FC /* VoipPerCallDdpRegistry.swift in Sources */, 1E9A71752B59F36E00477BA2 /* ClientSSL.swift in Sources */, A48B46DA2D3FFBD200945489 /* A11yFlowModule.m in Sources */, @@ -1848,7 +1852,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - "$PODS_CONFIGURATION_BUILD_DIR/Firebase", + $PODS_CONFIGURATION_BUILD_DIR/Firebase, ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -1924,7 +1928,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - "$PODS_CONFIGURATION_BUILD_DIR/Firebase", + $PODS_CONFIGURATION_BUILD_DIR/Firebase, ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -2374,10 +2378,7 @@ "$(inherited)", "-DRCT_REMOVE_LEGACY_ARCH=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -2465,10 +2466,7 @@ "$(inherited)", "-DRCT_REMOVE_LEGACY_ARCH=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index 372948e2af8..c5f2177e95b 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -4,11 +4,6 @@ AppGroupIdentifier group.ios.chat.rocket - bugsnag - - apiKey - apiKeyValue - CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion @@ -75,6 +70,10 @@ Give $(PRODUCT_NAME) permission to save photos NSPhotoLibraryUsageDescription Upload photos to share with other users or to change your avatar + NSUserActivityTypes + + INSendMessageIntent + RCTNewArchEnabled UIAppFonts @@ -86,6 +85,18 @@ UIApplicationSupportsMultipleScenes + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + UIBackgroundModes @@ -108,9 +119,10 @@ UIViewControllerBasedStatusBarAppearance - NSUserActivityTypes - - INSendMessageIntent - + bugsnag + + apiKey + apiKeyValue + diff --git a/ios/SceneDelegate.swift b/ios/SceneDelegate.swift new file mode 100644 index 00000000000..dead07a76e4 --- /dev/null +++ b/ios/SceneDelegate.swift @@ -0,0 +1,17 @@ +import Expo +import UIKit + +@available(iOSApplicationExtension, unavailable) +class SceneDelegate: ExpoAppSceneDelegate { + override func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + super.scene(scene, willConnectTo: session, options: connectionOptions) + + if let rootViewController = window?.rootViewController { + RNBootSplash.initWithStoryboard("LaunchScreen", rootView: rootViewController.view) + } + } +} From 2f86dc44dd345a7485acf4a0736e6388a01dc856 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 22 Sep 2026 20:23:23 -0300 Subject: [PATCH 025/120] fix(android): stop gesture-handler buttons hijacking hardware Enter/D-pad The RN 0.86 gesture-handler bump added an Android-only onKeyUp override that intercepts DPAD_CENTER/ENTER/SPACE and activates whichever button currently holds native view focus, bypassing the default performClick() path. Every composer and header icon button is a BorderlessButton, so this silently fired the wrong button (e.g. the header drawer button) on hardware Enter/D-pad and swallowed the key event elsewhere. Removed the hunk; the iOS press-handling hunk is untouched. --- .../react-native-gesture-handler+2.32.0.patch | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/patches/react-native-gesture-handler+2.32.0.patch b/patches/react-native-gesture-handler+2.32.0.patch index ba5d8c8056f..4c16a356879 100644 --- a/patches/react-native-gesture-handler+2.32.0.patch +++ b/patches/react-native-gesture-handler+2.32.0.patch @@ -1,30 +1,3 @@ -diff --git a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt -index e4114c4..41544da 100644 ---- a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt -+++ b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt -@@ -504,10 +504,22 @@ class RNGestureHandlerButtonViewManager : - } - - override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { -+ if (isActivationKey(keyCode) && !isChildTouched() && !context.isScreenReaderOn()) { -+ findGestureHandlerRootView()?.activateNativeHandlers(this) -+ return true -+ } - receivedKeyEvent = true - return super.onKeyUp(keyCode, event) - } - -+ private fun isActivationKey(keyCode: Int) = when (keyCode) { -+ KeyEvent.KEYCODE_DPAD_CENTER, -+ KeyEvent.KEYCODE_ENTER, -+ KeyEvent.KEYCODE_NUMPAD_ENTER, -+ KeyEvent.KEYCODE_SPACE -> true -+ else -> false -+ } -+ - override fun performClick(): Boolean { - // don't preform click when a child button is pressed (mainly to prevent sound effect of - // a parent button from playing) diff --git a/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm b/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm index 6b9fa9c..6d2ccea 100644 --- a/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm From c3edcf96137bbf4c87f2965c08b0fb0ccd1c86f8 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 16:48:09 -0300 Subject: [PATCH 026/120] feat(ios): adopt the system navigation bar as the stack default iOS now uses the native stack header by default instead of the JS-rendered Header, so themedHeader drops its color assignments on that platform and lets the system bar style itself. Appearance.setColorScheme is kept in sync with the in-app theme so the native bar follows light/dark/auto. Both Info.plist files gain UIDesignRequiresCompatibility to keep the pre-Liquid-Glass bar appearance. Maestro's back helper now targets the native "Back" button on iOS with retryTapIfNoChange, and the suite's tapOn/wait references to the old custom-header-back and header-back testIDs are retargeted to the shared helper or to platform-neutral waits. --- .maestro/helpers/go-back.yaml | 12 +++---- .../accessibility-and-appearance.yaml | 9 ++--- .maestro/tests/assorted/change-avatar.yaml | 6 ++-- .../tests/assorted/join-from-directory.yaml | 9 ++--- .maestro/tests/assorted/user-preferences.yaml | 15 +++----- .../tests/assorted/utils/go-to-room-list.yaml | 9 ++--- .../keyboard-navigation-components.yaml | 3 +- .maestro/tests/room/discussion.yaml | 12 +++---- .maestro/tests/room/jump-to-message.yaml | 12 +++---- .../tests/room/message-markdown-click.yaml | 12 +++---- .maestro/tests/room/room-actions.yaml | 36 +++++++------------ .maestro/tests/room/room-info.yaml | 12 +++---- .maestro/tests/room/threads.yaml | 30 ++++++---------- .maestro/tests/room/unread-badge.yaml | 16 +++------ .maestro/tests/room/utils/clear-cache.yaml | 3 +- app/lib/methods/helpers/navigation/index.ts | 28 +++++++++------ app/lib/methods/helpers/theme.ts | 4 +++ ios/RocketChatRN/Info.plist | 2 ++ ios/ShareRocketChatRN/Info.plist | 2 ++ 19 files changed, 92 insertions(+), 140 deletions(-) diff --git a/.maestro/helpers/go-back.yaml b/.maestro/helpers/go-back.yaml index 667f678c475..450c744684e 100644 --- a/.maestro/helpers/go-back.yaml +++ b/.maestro/helpers/go-back.yaml @@ -6,16 +6,16 @@ tags: --- - runFlow: when: - visible: - id: 'custom-header-back' + platform: Android commands: - tapOn: - id: 'custom-header-back' + id: 'header-back' + retryTapIfNoChange: true - runFlow: when: - visible: - id: 'header-back' + platform: iOS commands: - tapOn: - id: 'header-back' + id: 'Back' + retryTapIfNoChange: true diff --git a/.maestro/tests/assorted/accessibility-and-appearance.yaml b/.maestro/tests/assorted/accessibility-and-appearance.yaml index 04a79a29fcd..dec998f84f7 100644 --- a/.maestro/tests/assorted/accessibility-and-appearance.yaml +++ b/.maestro/tests/assorted/accessibility-and-appearance.yaml @@ -95,8 +95,7 @@ tags: timeout: 60000 # should disable mentions with @ symbol -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' - runFlow: file: './utils/go-to-accessibility-appearance.yaml' @@ -134,8 +133,7 @@ tags: timeout: 60000 # should enable rooms with # symbol -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' - runFlow: file: './utils/go-to-accessibility-appearance.yaml' @@ -169,8 +167,7 @@ tags: timeout: 60000 # should disable mentions with # symbol -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' - runFlow: file: './utils/go-to-accessibility-appearance.yaml' diff --git a/.maestro/tests/assorted/change-avatar.yaml b/.maestro/tests/assorted/change-avatar.yaml index 72285dfc884..e317392b0e8 100644 --- a/.maestro/tests/assorted/change-avatar.yaml +++ b/.maestro/tests/assorted/change-avatar.yaml @@ -56,8 +56,7 @@ tags: id: 'reset-avatar-suggestion' # should appear the discard alert when click the back icon -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: text: '.*Discard changes?.*' @@ -68,8 +67,7 @@ tags: timeout: 60000 - tapOn: text: '.*Cancel.*' -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: text: '.*Discard changes?.*' diff --git a/.maestro/tests/assorted/join-from-directory.yaml b/.maestro/tests/assorted/join-from-directory.yaml index 748345c4f5d..df89a0a7dc9 100644 --- a/.maestro/tests/assorted/join-from-directory.yaml +++ b/.maestro/tests/assorted/join-from-directory.yaml @@ -69,16 +69,14 @@ tags: visible: id: 'thread-messages-view-${output.thread}' timeout: 60000 -- tapOn: - id: 'custom-header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-view-header-threads' timeout: 60000 # should search user and navigate -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'rooms-list-view-directory' @@ -125,8 +123,7 @@ tags: timeout: 60000 # should search team and navigate -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'rooms-list-view-directory' diff --git a/.maestro/tests/assorted/user-preferences.yaml b/.maestro/tests/assorted/user-preferences.yaml index 80e026160c5..5e6baa77684 100644 --- a/.maestro/tests/assorted/user-preferences.yaml +++ b/.maestro/tests/assorted/user-preferences.yaml @@ -89,8 +89,7 @@ tags: id: push-troubleshoot-view-push-gateway-connection - assertVisible: id: push-troubleshoot-view-notification-delay -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' # should have alert in Email - assertVisible: @@ -105,8 +104,7 @@ tags: id: notification-preferences-emailNotificationMode-nothing - tapOn: id: action-sheet-handle -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' # ASCII should not be converted to emoji (Android only) - runFlow: @@ -114,8 +112,7 @@ tags: when: platform: Android commands: - - tapOn: - id: 'custom-header-back' + - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'profile-view' @@ -162,8 +159,7 @@ tags: when: platform: Android commands: - - tapOn: - id: 'header-back' + - runFlow: '../../helpers/go-back.yaml' - runFlow: file: './utils/go-to-user-preference.yaml' - extendedWaitUntil: @@ -172,8 +168,7 @@ tags: timeout: 60000 - tapOn: id: 'preferences-view-convert-ascii-to-emoji' - - tapOn: - id: 'custom-header-back' + - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'profile-view' diff --git a/.maestro/tests/assorted/utils/go-to-room-list.yaml b/.maestro/tests/assorted/utils/go-to-room-list.yaml index 6734380e054..5dc8737c39e 100644 --- a/.maestro/tests/assorted/utils/go-to-room-list.yaml +++ b/.maestro/tests/assorted/utils/go-to-room-list.yaml @@ -4,12 +4,9 @@ tags: - 'util' --- -- extendedWaitUntil: - visible: - id: 'custom-header-back' - timeout: 60000 -- tapOn: - id: 'custom-header-back' +- waitForAnimationToEnd: + timeout: 10000 +- runFlow: '../../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'accessibility-view-drawer' diff --git a/.maestro/tests/keyboardNavigation/keyboard-navigation-components.yaml b/.maestro/tests/keyboardNavigation/keyboard-navigation-components.yaml index da7b8376cad..341680e0429 100644 --- a/.maestro/tests/keyboardNavigation/keyboard-navigation-components.yaml +++ b/.maestro/tests/keyboardNavigation/keyboard-navigation-components.yaml @@ -72,8 +72,7 @@ tags: - pressKey: enter # open Toasts and Dialogs action sheet and navigate through options -- tapOn: - id: 'custom-header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'accessibility-view-list' diff --git a/.maestro/tests/room/discussion.yaml b/.maestro/tests/room/discussion.yaml index 3fc7f536218..02d95100d3c 100644 --- a/.maestro/tests/room/discussion.yaml +++ b/.maestro/tests/room/discussion.yaml @@ -99,8 +99,7 @@ tags: visible: id: 'room-view' commands: - - tapOn: - id: 'header-back' + - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'rooms-list-view-item-${output.discussionFromNewMessage}' @@ -259,20 +258,17 @@ tags: id: room-info-view-edit-button # should go back to main room -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-actions-view' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-view-messages' timeout: 60000 -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'rooms-list-view' diff --git a/.maestro/tests/room/jump-to-message.yaml b/.maestro/tests/room/jump-to-message.yaml index e6cfb9dcd03..bd2b7532612 100644 --- a/.maestro/tests/room/jump-to-message.yaml +++ b/.maestro/tests/room/jump-to-message.yaml @@ -183,8 +183,7 @@ tags: visible: id: 'message-content-202' timeout: 60000 -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' # should navigate to a thread from another room - runFlow: @@ -208,8 +207,7 @@ tags: file: './utils/expect-thread-messages.yaml' env: message: Go to jumping-thread's thread -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' # should tap on thread message from main room - extendedWaitUntil: @@ -227,8 +225,7 @@ tags: file: './utils/expect-thread-messages.yaml' env: message: 'thread message sent to main room' -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' # should tap on quote - extendedWaitUntil: @@ -252,8 +249,7 @@ tags: file: './utils/expect-thread-messages.yaml' env: message: 'quoted' -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' # should jump from search message - extendedWaitUntil: diff --git a/.maestro/tests/room/message-markdown-click.yaml b/.maestro/tests/room/message-markdown-click.yaml index f0a746f2648..d102fff2e81 100644 --- a/.maestro/tests/room/message-markdown-click.yaml +++ b/.maestro/tests/room/message-markdown-click.yaml @@ -57,8 +57,7 @@ tags: text: '.*detox-public.*' # Go back to room -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' # Wait for room view to be visible again - extendedWaitUntil: @@ -137,8 +136,7 @@ tags: text: '.*e2e_admin.*' # Go back to room -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' # Wait for room view to be visible again - extendedWaitUntil: @@ -157,8 +155,7 @@ tags: timeout: 60000 # Go back to main room -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' # Wait for room view to be visible again - extendedWaitUntil: @@ -177,8 +174,7 @@ tags: timeout: 60000 # Go back to main room -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' # Wait for room view to be visible again - extendedWaitUntil: diff --git a/.maestro/tests/room/room-actions.yaml b/.maestro/tests/room/room-actions.yaml index 52a1d7332a9..cc460b09577 100644 --- a/.maestro/tests/room/room-actions.yaml +++ b/.maestro/tests/room/room-actions.yaml @@ -93,14 +93,12 @@ tags: visible: id: 'mentioned-messages-view' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-actions-view' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' # should star and show in starred message - runFlow: @@ -149,14 +147,12 @@ tags: id: 'message-content-messageToStar' timeout: 60000 # should unstar -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-actions-view' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: message-content-messageToStar @@ -198,14 +194,12 @@ tags: notVisible: id: 'message-content-messageToStar' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-actions-view' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' # should pin a message and show in pinned - runFlow: @@ -261,8 +255,7 @@ tags: notVisible: id: 'message-content-messageToPin' timeout: 60000 -- tapOn: - id: 'custom-header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-actions-view' @@ -324,8 +317,7 @@ tags: id: 'notification-preference-view-email-alert' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-actions-view' @@ -406,8 +398,7 @@ tags: timeout: 60000 - tapOn: id: selected-users-view-submit -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: text: '.*3 members.*' @@ -440,8 +431,7 @@ tags: visible: id: 'room-members-view-item-${output.otherUser.username}' timeout: 60000 -- tapOn: - id: 'custom-header-back' +- runFlow: '../../helpers/go-back.yaml' # should filter user - tapOn: @@ -697,14 +687,12 @@ tags: visible: text: 'User has been ignored' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-actions-view' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: text: '.*Message ignored. Tap to display it.*' diff --git a/.maestro/tests/room/room-info.yaml b/.maestro/tests/room/room-info.yaml index 7859a2a7fa1..0078cc1f46f 100644 --- a/.maestro/tests/room/room-info.yaml +++ b/.maestro/tests/room/room-info.yaml @@ -141,8 +141,7 @@ tags: timeout: 60000 # should reset form -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - tapOn: id: room-info-view-edit-button - extendedWaitUntil: @@ -278,8 +277,7 @@ tags: id: 'room-info-edit-view-react-when-ro' # should change room name -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - tapOn: id: room-info-view-edit-button - evalScript: ${output.newroomname = output.room.name + 'new'} @@ -305,8 +303,7 @@ tags: visible: text: '.*Settings succesfully changed.*' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-info-view' @@ -367,8 +364,7 @@ tags: visible: text: '.*Settings succesfully changed.*' timeout: 60000 -- tapOn: - id: custom-header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-info-view' diff --git a/.maestro/tests/room/threads.yaml b/.maestro/tests/room/threads.yaml index f45f17151b4..ff880508c1f 100644 --- a/.maestro/tests/room/threads.yaml +++ b/.maestro/tests/room/threads.yaml @@ -78,8 +78,7 @@ tags: visible: id: 'message-content-replied' timeout: 60000 -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' # should navigate to thread from button - extendedWaitUntil: visible: @@ -120,8 +119,7 @@ tags: env: message: threadonly isThread: 'true' -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: notVisible: id: 'room-view-title-thread' @@ -153,8 +151,7 @@ tags: id: send-to-channel-unchecked - tapOn: id: message-composer-send -- tapOn: - id: header-back +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: notVisible: id: 'room-view-title-thread' @@ -186,8 +183,7 @@ tags: id: 'message-composer-send-to-channel' - tapOn: id: 'message-composer-send' -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: notVisible: id: 'room-view-title-thread' @@ -206,8 +202,7 @@ tags: visible: id: 'room-view-title-thread' timeout: 60000 -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' # should navigate to thread from threads view - extendedWaitUntil: @@ -230,8 +225,7 @@ tags: visible: id: 'room-view-title-thread' timeout: 60000 -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-view-messages' @@ -251,8 +245,7 @@ tags: - tapOn: id: 'message-composer-input-thread' - inputText: 'draftthread' -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -271,8 +264,7 @@ tags: - tapOn: id: 'message-composer-input-thread' - eraseText -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -292,8 +284,7 @@ tags: visible: text: '.*Add thread reply.*' timeout: 60000 -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: notVisible: id: 'room-view-title-thread' @@ -374,8 +365,7 @@ tags: text: '.*You will not be able to recover this message.*' timeout: 60000 - tapOn: 'DELETE' -- tapOn: - id: 'header-back' +- runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'room-view-title-${output.room.name}' diff --git a/.maestro/tests/room/unread-badge.yaml b/.maestro/tests/room/unread-badge.yaml index c52cfff178b..f0054edfa1b 100644 --- a/.maestro/tests/room/unread-badge.yaml +++ b/.maestro/tests/room/unread-badge.yaml @@ -34,9 +34,7 @@ tags: timeout: 60000 - tapOn: id: 'rooms-list-view-item-${output.otherUser.username}' -- tapOn: - id: header-back - retryTapIfNoChange: true +- runFlow: '../../helpers/go-back.yaml' - waitForAnimationToEnd: timeout: 5000 - extendedWaitUntil: @@ -62,9 +60,7 @@ tags: timeout: 60000 - tapOn: id: 'rooms-list-view-item-${output.otherUser.username}' -- tapOn: - id: header-back - retryTapIfNoChange: true +- runFlow: '../../helpers/go-back.yaml' - waitForAnimationToEnd: timeout: 5000 - extendedWaitUntil: @@ -91,9 +87,7 @@ tags: timeout: 60000 - tapOn: id: 'rooms-list-view-item-${output.otherUser.username}' -- tapOn: - id: header-back - retryTapIfNoChange: true +- runFlow: '../../helpers/go-back.yaml' - waitForAnimationToEnd: timeout: 5000 - extendedWaitUntil: @@ -120,9 +114,7 @@ tags: timeout: 60000 - tapOn: id: 'rooms-list-view-item-${output.otherUser.username}' -- tapOn: - id: header-back - retryTapIfNoChange: true +- runFlow: '../../helpers/go-back.yaml' - waitForAnimationToEnd: timeout: 5000 - extendedWaitUntil: diff --git a/.maestro/tests/room/utils/clear-cache.yaml b/.maestro/tests/room/utils/clear-cache.yaml index bf087ac3e01..35a30021282 100644 --- a/.maestro/tests/room/utils/clear-cache.yaml +++ b/.maestro/tests/room/utils/clear-cache.yaml @@ -8,8 +8,7 @@ tags: visible: id: 'room-view' timeout: 60000 -- tapOn: - id: 'header-back' +- runFlow: '../../../helpers/go-back.yaml' - extendedWaitUntil: visible: id: 'rooms-list-view' diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index c050358e55e..4161625b4d9 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -6,22 +6,30 @@ import { themes } from '~/lib/constants/colors'; import { type TSupportedThemes } from '~/theme'; import sharedStyles from '~/views/Styles'; import Header from '~/containers/Header'; +import { isIOS } from '~/lib/methods/helpers'; -export const defaultHeader: NativeStackNavigationOptions = { - header: (props: NativeStackHeaderProps): ReactElement => createElement(Header, props) -}; +export const defaultHeader: NativeStackNavigationOptions = isIOS + ? { + headerBackButtonDisplayMode: 'minimal' + } + : { + header: (props: NativeStackHeaderProps): ReactElement => createElement(Header, props) + }; export const drawerStyle = { width: 320 }; -export const themedHeader = (theme: TSupportedThemes): NativeStackNavigationOptions => ({ - headerStyle: { - backgroundColor: themes[theme].surfaceNeutral - }, - headerTintColor: themes[theme].fontDefault, - headerTitleStyle: { ...sharedStyles.textBold, color: themes[theme].fontTitlesLabels, fontSize: 16 } -}); +export const themedHeader = (theme: TSupportedThemes): NativeStackNavigationOptions => + isIOS + ? {} + : { + headerStyle: { + backgroundColor: themes[theme].surfaceNeutral + }, + headerTintColor: themes[theme].fontDefault, + headerTitleStyle: { ...sharedStyles.textBold, color: themes[theme].fontTitlesLabels, fontSize: 16 } + }; export const navigationTheme = (theme: TSupportedThemes) => { const defaultNavTheme = theme === 'light' ? DefaultTheme : DarkTheme; diff --git a/app/lib/methods/helpers/theme.ts b/app/lib/methods/helpers/theme.ts index 42f045e6107..99cdbaf7806 100644 --- a/app/lib/methods/helpers/theme.ts +++ b/app/lib/methods/helpers/theme.ts @@ -57,6 +57,10 @@ export const setNativeTheme = (themePreferences: IThemePreference) => { } catch (error) { // Do nothing } + } else { + Appearance.setColorScheme( + themePreferences.currentTheme === 'automatic' ? 'unspecified' : theme === 'light' ? 'light' : 'dark' + ); } SystemUI.setBackgroundColorAsync(themes[theme].surfaceNeutral); }; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index c5f2177e95b..4a4abe4efc0 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -104,6 +104,8 @@ fetch voip + UIDesignRequiresCompatibility + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist index 4149cf89cb8..2d315a3290a 100644 --- a/ios/ShareRocketChatRN/Info.plist +++ b/ios/ShareRocketChatRN/Info.plist @@ -67,6 +67,8 @@ custom.ttf Inter.ttf + UIDesignRequiresCompatibility + bugsnag apiKey From 1dd4309ec667ed2b2f64028ff5975a04a10c76e7 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 17:18:58 -0300 Subject: [PATCH 027/120] feat(ios): native room title and body focus anchor Adopt the system navigation bar title for RoomView on iPhone, showing connection state (connecting, waiting for network) alongside the room name. Add a hidden focus anchor so hardware-keyboard/VoiceOver navigation lands in the message body on room open. Scoped to iOS phones only; iPad keeps the existing RoomHeader render prop with no autofocus. --- app/containers/RoomHeader/RoomHeader.tsx | 1 - app/views/RoomView/RoomScreen.tsx | 63 +++++++------- .../components/RoomBodyFocusAnchor.tsx | 41 +++++++++ .../hooks/__tests__/useHeader.test.tsx | 84 ++++++++++++++++++- app/views/RoomView/hooks/useHeader.tsx | 15 +++- 5 files changed, 170 insertions(+), 34 deletions(-) create mode 100644 app/views/RoomView/components/RoomBodyFocusAnchor.tsx diff --git a/app/containers/RoomHeader/RoomHeader.tsx b/app/containers/RoomHeader/RoomHeader.tsx index 06b833796c9..03f8909307e 100644 --- a/app/containers/RoomHeader/RoomHeader.tsx +++ b/app/containers/RoomHeader/RoomHeader.tsx @@ -88,7 +88,6 @@ interface IRoomHeader { testID?: string; sourceType?: IOmnichannelSource; disabled?: boolean; - rightButtonsWidth?: number; abacAttributes?: ISubscription['abacAttributes']; } diff --git a/app/views/RoomView/RoomScreen.tsx b/app/views/RoomView/RoomScreen.tsx index 9f439b7ebac..633b8e7320d 100644 --- a/app/views/RoomView/RoomScreen.tsx +++ b/app/views/RoomView/RoomScreen.tsx @@ -8,6 +8,7 @@ import JoinCode from './components/JoinCode'; import { type IJoinCode, type IRoomScreenProps } from './definitions'; import { RoomProviders } from './components/RoomProviders'; import { RoomAnnouncementBanner } from './components/RoomAnnouncementBanner'; +import { RoomBodyFocusAnchor } from './components/RoomBodyFocusAnchor'; import { RoomFooter } from './components/RoomFooter/RoomFooter'; import { RoomLoadFailed } from './components/RoomLoadFailed'; import { RoomMessageActions } from './components/RoomMessageActions'; @@ -92,36 +93,38 @@ const RoomScreen = ({ route, rid, t, tmid, roomStore, ready }: IRoomScreenProps) editCancel={onEditCancel} editRequest={onEditRequest} onSendMessage={sendMessage}> - - {!tmid ? : null} - - - - - - + + + {!tmid ? : null} + + + + + + + diff --git a/app/views/RoomView/components/RoomBodyFocusAnchor.tsx b/app/views/RoomView/components/RoomBodyFocusAnchor.tsx new file mode 100644 index 00000000000..ea1b4ddf1cc --- /dev/null +++ b/app/views/RoomView/components/RoomBodyFocusAnchor.tsx @@ -0,0 +1,41 @@ +import { type ReactNode } from 'react'; +import { StyleSheet } from 'react-native'; +import { KeyboardFocusView } from 'react-native-external-keyboard'; + +import { isIOS, isTablet } from '~/lib/methods/helpers'; +import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; +import { useIsAccessibilityNavigationEnabled } from '~/lib/hooks/useIsAccessibilityNavigationEnabled'; +import { fromSubscription, useRoomStore } from '../stores/RoomStoreContext'; + +const styles = StyleSheet.create({ + anchor: { + width: 1, + height: 1 + } +}); + +interface IRoomBodyFocusAnchorProps { + children: ReactNode; +} + +export const RoomBodyFocusAnchor = ({ children }: IRoomBodyFocusAnchorProps) => { + const disabled = useRoomStore(fromSubscription(isInviteSubscription, false)); + const accessibilityNavigationEnabled = useIsAccessibilityNavigationEnabled(); + + if (!isIOS || isTablet) { + return <>{children}; + } + + return ( + <> + + {children} + + ); +}; diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index 98bb91ca015..6f1d68aeaa1 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -5,18 +5,35 @@ import { type RoomState, type RoomStore } from '~/views/RoomView/definitions'; import { useHeader } from '../useHeader'; let mockTestStore: RoomStore; +let mockIsIOS = false; +let mockConnecting = false; +let mockConnected = true; jest.mock('../useGoRoomActionsView', () => ({ useGoRoomActionsView: jest.fn(() => jest.fn()) })); jest.mock('~/views/RoomView/components/LeftButtons', () => ({ __esModule: true, default: 'LeftButtons' })); jest.mock('~/views/RoomView/components/RightButtons/RightButtons', () => ({ __esModule: true, default: 'RightButtons' })); jest.mock('~/containers/RoomHeader', () => ({ __esModule: true, default: 'RoomHeader' })); +let mockIsTablet = false; jest.mock('~/lib/methods/helpers', () => ({ getRoomTitle: jest.fn(() => 'Room Title'), - isGroupChat: jest.fn(() => false) + isGroupChat: jest.fn(() => false), + get isIOS() { + return mockIsIOS; + }, + get isTablet() { + return mockIsTablet; + } })); jest.mock('~/lib/methods/isInviteSubscription', () => ({ isInviteSubscription: jest.fn(() => false) })); +jest.mock('~/lib/hooks/useAppSelector', () => ({ + useAppSelector: (selector: (state: unknown) => unknown) => + selector({ + meteor: { connecting: mockConnecting, connected: mockConnected }, + server: { loading: false } + }) +})); const mockSetOptions = jest.fn(); const mockNavigation = { setOptions: mockSetOptions }; @@ -45,6 +62,10 @@ describe('useHeader', () => { beforeEach(() => { jest.clearAllMocks(); mockTestStore = makeRoomStore(); + mockIsIOS = false; + mockIsTablet = false; + mockConnecting = false; + mockConnected = true; }); it('sets only the headerLeft spacer and returns when rid is missing', () => { @@ -95,4 +116,65 @@ describe('useHeader', () => { expect(() => titleOptions.headerTitle()).not.toThrow(); expect(() => sideOptions.headerRight()).not.toThrow(); }); + + describe('on iOS', () => { + beforeEach(() => { + mockIsIOS = true; + }); + + it('sets a plain string title instead of the RoomHeader render prop', () => { + renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + + const titleOptions = mockSetOptions.mock.calls[1][0]; + expect(titleOptions).toEqual({ title: 'Room Title' }); + }); + + it('swaps the title to Connecting while the socket is connecting', () => { + mockConnecting = true; + + renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + + const titleOptions = mockSetOptions.mock.calls[1][0]; + expect(titleOptions).toEqual({ title: 'Connecting...' }); + }); + + it('swaps the title to Waiting_for_network when disconnected', () => { + mockConnected = false; + + renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + + const titleOptions = mockSetOptions.mock.calls[1][0]; + expect(titleOptions).toEqual({ title: 'Waiting for network...' }); + }); + + it('swaps back to the room title once connected again', () => { + mockConnecting = true; + renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + expect(mockSetOptions.mock.calls[1][0]).toEqual({ title: 'Connecting...' }); + + mockConnecting = false; + act(() => { + mockTestStore.setState({ + room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', topic: 'updated' } as RoomState['room'] + }); + }); + const lastOptions = mockSetOptions.mock.calls[mockSetOptions.mock.calls.length - 1][0]; + expect(lastOptions).toEqual({ title: 'Room Title' }); + }); + }); + + describe('on iPad', () => { + beforeEach(() => { + mockIsIOS = true; + mockIsTablet = true; + }); + + it('renders the tappable RoomHeader instead of the native string title', () => { + renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + + const titleOptions = mockSetOptions.mock.calls[1][0]; + expect(titleOptions).toHaveProperty('headerTitle'); + expect(typeof titleOptions.headerTitle).toBe('function'); + }); + }); }); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index e11e5343274..0d1d13f58c9 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -5,8 +5,10 @@ import { useShallow } from 'zustand/react/shallow'; import { useStore } from 'zustand'; import RoomHeader from '~/containers/RoomHeader'; -import { getRoomTitle, isGroupChat } from '~/lib/methods/helpers'; +import I18n from '~/i18n'; +import { getRoomTitle, isGroupChat, isIOS, isTablet } from '~/lib/methods/helpers'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; +import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; import LeftButtons from '../components/LeftButtons'; import RightButtons from '../components/RightButtons/RightButtons'; @@ -66,6 +68,9 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa ); const roomUserId = useStore(roomStore, s => s.roomUserId); const goRoomActionsView = useGoRoomActionsView(roomStore); + const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading); + const connected = useAppSelector(state => state.meteor.connected); + const useNativeBar = isIOS && !isTablet; useLayoutEffect(() => { if (!rid) { @@ -84,6 +89,12 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa return; } + if (useNativeBar) { + const title = connecting ? I18n.t('Connecting') : !connected ? I18n.t('Waiting_for_network') : headerFields.title; + navigation.setOptions({ title }); + return; + } + navigation.setOptions({ headerTitle: () => ( ) }); - }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView]); + }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView, connecting, connected, useNativeBar]); }; From 3a5436f3cacc0ec3a8287a1327aebb3405aeeb83 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 17:19:07 -0300 Subject: [PATCH 028/120] feat(ios): rooms list on the native navigation bar Move the RoomsListView header onto the system navigation bar on iPhone: native large title reflecting connection state, drawer button on the left, and search/create/directory/push-troubleshoot actions on the right with overflow into an action sheet past two visible items. Search mode keeps its existing JS header and runs before the native branch. iPad is unaffected. --- .../hooks/__tests__/useHeader.test.tsx | 190 ++++++++++++++++++ app/views/RoomsListView/hooks/useHeader.tsx | 164 +++++++++++++-- 2 files changed, 339 insertions(+), 15 deletions(-) create mode 100644 app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx new file mode 100644 index 00000000000..58ebae9d923 --- /dev/null +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -0,0 +1,190 @@ +import { renderHook } from '@testing-library/react-native'; +import { type ReactElement } from 'react'; + +import { RoomsSearchContext } from '../../contexts/RoomsSearchProvider'; +import { useHeader } from '../useHeader'; + +const mockSetOptions = jest.fn(); +const mockNavigation = { setOptions: mockSetOptions, navigate: jest.fn(), toggleDrawer: jest.fn() }; + +jest.mock('@react-navigation/native', () => ({ + useNavigation: () => mockNavigation, + useFocusEffect: jest.fn() +})); + +let mockIsIOS = true; +let mockIsTablet = false; +jest.mock('~/lib/methods/helpers', () => + Object.defineProperties( + { ...jest.requireActual('~/lib/methods/helpers') }, + { + isIOS: { get: () => mockIsIOS, configurable: true }, + isTablet: { get: () => mockIsTablet, configurable: true } + } + ) +); + +jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => false })); +jest.mock('~/lib/hooks/useIsAccessibilityNavigationEnabled', () => ({ useIsAccessibilityNavigationEnabled: () => false })); +jest.mock('~/lib/hooks/usePermissions', () => ({ usePermissions: () => [true, false, false, false, false] })); +jest.mock('~/selectors/login', () => ({ getUserSelector: () => ({ requirePasswordChange: false }) })); +const mockColors = { fontDanger: '#f00', buttonBackgroundDangerDefault: '#f00', userPresenceDisabled: '#f00' }; +jest.mock('~/theme', () => ({ useTheme: () => ({ colors: mockColors }) })); +jest.mock('~/containers/ActionSheet', () => ({ showActionSheetRef: jest.fn() })); +jest.mock('../../components/Header', () => ({ __esModule: true, default: 'RoomsListHeaderView' })); + +jest.mock('~/containers/Header/components/HeaderButton', () => { + const ReactActual = jest.requireActual('react'); + return { + Container: ({ children }: { children: unknown }) => ReactActual.createElement('Container', null, children), + Item: (props: Record) => ReactActual.createElement('Item', props), + Drawer: (props: Record) => ReactActual.createElement('Drawer', props), + More: (props: Record) => ReactActual.createElement('More', props), + BadgeWarn: () => null + }; +}); + +let mockAppState = { + supportedVersions: { status: 'supported' }, + troubleshootingNotification: { issuesWithNotifications: false }, + app: { notificationPresenceCap: false }, + meteor: { connecting: false, connected: true }, + server: { loading: false }, + login: { isFetching: false }, + rooms: { isFetching: false } +}; +jest.mock('~/lib/hooks/useAppSelector', () => ({ + useAppSelector: (selector: (state: typeof mockAppState) => unknown) => selector(mockAppState) +})); + +const mockStartSearch = jest.fn(); +const mockStopSearch = jest.fn(); +const mockSearch = jest.fn(); +const searchContextValue = { + searching: false, + searchEnabled: false, + searchResults: [], + startSearch: mockStartSearch, + stopSearch: mockStopSearch, + search: mockSearch +}; + +const renderUseHeader = () => + renderHook(() => useHeader(), { + wrapper: ({ children }: { children: ReactElement }) => ( + {children} + ) + }); + +describe('RoomsListView useHeader', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockIsIOS = true; + mockIsTablet = false; + mockAppState = { + supportedVersions: { status: 'supported' }, + troubleshootingNotification: { issuesWithNotifications: false }, + app: { notificationPresenceCap: false }, + meteor: { connecting: false, connected: true }, + server: { loading: false }, + login: { isFetching: false }, + rooms: { isFetching: false } + }; + }); + + it('sets a native string title with a large title on iPhone', () => { + renderUseHeader(); + + const options = mockSetOptions.mock.calls[0][0]; + expect(typeof options.headerTitle).toBe('string'); + expect(options.headerTitle).toBe('Chats'); + expect(options.headerLargeTitle).toBe(true); + }); + + it.each([ + [{ meteor: { connecting: true, connected: true } }, 'Connecting...'], + [{ login: { isFetching: true } }, 'Connecting...'], + [{ rooms: { isFetching: true } }, 'Updating...'], + [{ meteor: { connecting: false, connected: false } }, 'Waiting for network...'], + [{ meteor: { connecting: false, connected: false }, rooms: { isFetching: true } }, 'Waiting for network...'] + ])('swaps the native title to the connection state %j', (stateOverride, expectedTitle) => { + mockAppState = { ...mockAppState, ...stateOverride } as typeof mockAppState; + + renderUseHeader(); + + const options = mockSetOptions.mock.calls[0][0]; + expect(options.headerTitle).toBe(expectedTitle); + }); + + it('builds the right cluster in search, create priority order, overflowing push-troubleshoot and directory', () => { + mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; + + renderUseHeader(); + + const options = mockSetOptions.mock.calls[0][0]; + const rightButtons: ReactElement<{ testID: string }>[] = options.headerRight().props.children.filter(Boolean); + const testIDs = rightButtons.map(button => button.props.testID); + expect(testIDs).toEqual(['rooms-list-view-search', 'rooms-list-view-create-channel', 'rooms-list-view-more']); + }); + + it('does not render an overflow control when a single item would be hidden', () => { + renderUseHeader(); + + const options = mockSetOptions.mock.calls[0][0]; + const rightButtons: ReactElement<{ testID: string }>[] = options.headerRight().props.children.filter(Boolean); + const testIDs = rightButtons.map(button => button.props.testID); + expect(testIDs).toEqual(['rooms-list-view-search', 'rooms-list-view-create-channel', 'rooms-list-view-directory']); + }); + + it('keeps search reachable in the native cluster', () => { + renderUseHeader(); + + const options = mockSetOptions.mock.calls[0][0]; + const rightButtons: ReactElement<{ testID: string; onPress: () => void }>[] = options + .headerRight() + .props.children.filter(Boolean); + const searchButton = rightButtons.find(button => button.props.testID === 'rooms-list-view-search'); + expect(searchButton).toBeDefined(); + + searchButton?.props.onPress(); + expect(mockStartSearch).toHaveBeenCalledTimes(1); + }); + + it('lets search mode render SearchHeader with its close button ahead of the native bar', () => { + const renderWithSearch = () => + renderHook(() => useHeader(), { + wrapper: ({ children }: { children: ReactElement }) => ( + + {children} + + ) + }); + + renderWithSearch(); + + const options = mockSetOptions.mock.calls[0][0]; + expect(options.headerLargeTitle).toBeUndefined(); + expect(typeof options.headerTitle).toBe('function'); + expect(typeof options.headerLeft).toBe('function'); + }); + + it('falls back to the JS header on Android', () => { + mockIsIOS = false; + + renderUseHeader(); + + const options = mockSetOptions.mock.calls[0][0]; + expect(options.headerLargeTitle).toBeUndefined(); + expect(typeof options.headerTitle).toBe('function'); + }); + + it('falls back to the JS header on iPad', () => { + mockIsTablet = true; + + renderUseHeader(); + + const options = mockSetOptions.mock.calls[0][0]; + expect(options.headerLargeTitle).toBeUndefined(); + expect(typeof options.headerTitle).toBe('function'); + }); +}); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index cef3f006245..02a3bb160a6 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -3,19 +3,63 @@ import { useCallback, useContext, useLayoutEffect, useRef, useState } from 'reac import { InteractionManager } from 'react-native'; import { type KeyboardFocus } from 'react-native-external-keyboard'; +import { showActionSheetRef } from '~/containers/ActionSheet'; +import { type TIconsName } from '~/containers/CustomIcon'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import i18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useIsAccessibilityNavigationEnabled } from '~/lib/hooks/useIsAccessibilityNavigationEnabled'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { usePermissions } from '~/lib/hooks/usePermissions'; -import { isTablet } from '~/lib/methods/helpers'; +import { isIOS, isTablet } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { getUserSelector } from '~/selectors/login'; import { useTheme } from '~/theme'; import RoomsListHeaderView from '../components/Header'; import { RoomsSearchContext } from '../contexts/RoomsSearchProvider'; +const MAX_HEADER_RIGHT_ACTIONS = 2; + +interface IHeaderRightAction { + key: string; + present: boolean; + iconName: TIconsName; + testID: string; + accessibilityLabel: string; + color?: string; + disabled?: boolean; + onPress: () => void; +} + +const splitHeaderRightActions = (actions: IHeaderRightAction[]) => { + const present = actions.filter(action => action.present); + if (present.length <= MAX_HEADER_RIGHT_ACTIONS + 1) { + return { visible: present, overflow: [] as IHeaderRightAction[] }; + } + return { visible: present.slice(0, MAX_HEADER_RIGHT_ACTIONS), overflow: present.slice(MAX_HEADER_RIGHT_ACTIONS) }; +}; + +const useNativeBarTitle = (useNativeBar: boolean) => { + const connecting = useAppSelector(state => useNativeBar && (state.meteor.connecting || state.server.loading)); + const isLoggingIn = useAppSelector(state => useNativeBar && state.login.isFetching); + const isFetching = useAppSelector(state => useNativeBar && state.rooms.isFetching); + const connected = useAppSelector(state => !useNativeBar || state.meteor.connected); + + if (!useNativeBar) { + return ''; + } + if (connecting || isLoggingIn) { + return i18n.t('Connecting'); + } + if (!connected) { + return i18n.t('Waiting_for_network'); + } + if (isFetching) { + return i18n.t('Updating'); + } + return i18n.t('Chats'); +}; + export const useHeader = () => { const { searchEnabled, search, startSearch, stopSearch } = useContext(RoomsSearchContext); const [options, setOptions] = useState(null); @@ -83,7 +127,25 @@ export const useHeader = () => { } }, [isMasterDetail, navigation]); + const useNativeBar = isIOS && !isTablet; + const nativeBarTitle = useNativeBarTitle(useNativeBar); + useLayoutEffect(() => { + const headerLeft = () => ( + navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) + : () => navigation.toggleDrawer() + } + badge={getBadge} + disabled={disabled} + /> + ); + if (searchEnabled) { const searchOptions = { headerLeft: () => ( @@ -101,21 +163,91 @@ export const useHeader = () => { return; } + if (useNativeBar) { + const { visible, overflow } = splitHeaderRightActions([ + { + key: 'search', + present: true, + iconName: 'search', + accessibilityLabel: i18n.t('Search'), + testID: 'rooms-list-view-search', + disabled, + onPress: startSearch + }, + { + key: 'create', + present: canCreateRoom, + iconName: 'add', + accessibilityLabel: i18n.t('Create_new_channel_team_dm_discussion'), + testID: 'rooms-list-view-create-channel', + disabled, + onPress: goToNewMessage + }, + { + key: 'push-troubleshoot', + present: issuesWithNotifications, + iconName: 'notification-disabled', + accessibilityLabel: i18n.t('Troubleshooting'), + testID: 'rooms-list-view-push-troubleshoot', + color: colors.fontDanger, + onPress: navigateToPushTroubleshootView + }, + { + key: 'directory', + present: true, + iconName: 'directory', + accessibilityLabel: i18n.t('Directory'), + testID: 'rooms-list-view-directory', + disabled, + onPress: goDirectory + } + ]); + + navigation.setOptions({ + headerLargeTitle: true, + headerTitle: nativeBarTitle, + headerLeft, + headerRight: () => ( + + {[ + ...visible.map(action => ( + + )), + overflow.length >= 2 ? ( + + showActionSheetRef({ + options: overflow.map(action => ({ + title: action.accessibilityLabel, + icon: action.iconName, + testID: action.testID, + onPress: action.onPress + })) + }) + } + /> + ) : null + ]} + + ) + }); + return; + } + const options = { - headerLeft: () => ( - navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) - : () => navigation.toggleDrawer() - } - badge={getBadge} - disabled={disabled} - /> - ), + headerLeft, headerTitle: () => , headerRight: () => ( @@ -166,6 +298,8 @@ export const useHeader = () => { colors, canCreateRoom, searchEnabled, + useNativeBar, + nativeBarTitle, goDirectory, navigateToPushTroubleshootView, getBadge, From 03040e877bcbb2ae5dd12c8225b3abdf758ec377 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 17:55:22 -0300 Subject: [PATCH 029/120] feat(ios): cap room header bar items with an overflow menu --- .maestro/helpers/navigate-to-room-action.yaml | 27 +- .../RightButtons/HeaderCallButton.tsx | 73 +---- .../RightButtons/OmnichannelRightButtons.tsx | 16 +- .../OmnichannelRightButtonsLegacy.tsx | 13 + .../OmnichannelRightButtonsNative.tsx | 13 + .../RightButtons/RoomRightButtons.tsx | 156 +-------- .../RightButtons/RoomRightButtonsLegacy.tsx | 72 +++++ .../RightButtons/RoomRightButtonsNative.tsx | 145 +++++++++ .../RightButtons/ThreadRightButtons.tsx | 21 +- .../RightButtons/ThreadRightButtonsLegacy.tsx | 23 ++ .../RightButtons/ThreadRightButtonsNative.tsx | 23 ++ .../OmnichannelRightButtons.test.tsx | 24 +- .../__tests__/RoomRightButtons.test.tsx | 296 +++++++++--------- .../__tests__/ThreadRightButtons.test.tsx | 26 ++ .../RightButtons/useHeaderCallPress.ts | 65 ++++ .../RightButtons/useRoomRightButtonsData.ts | 125 ++++++++ .../useRoomRightButtonsNativeData.ts | 15 + .../__tests__/RightButtons.test.tsx | 17 +- .../__tests__/roomHeaderActions.test.ts | 48 +++ .../RoomView/helpers/roomHeaderActions.ts | 13 + 20 files changed, 837 insertions(+), 374 deletions(-) create mode 100644 app/views/RoomView/components/RightButtons/OmnichannelRightButtonsLegacy.tsx create mode 100644 app/views/RoomView/components/RightButtons/OmnichannelRightButtonsNative.tsx create mode 100644 app/views/RoomView/components/RightButtons/RoomRightButtonsLegacy.tsx create mode 100644 app/views/RoomView/components/RightButtons/RoomRightButtonsNative.tsx create mode 100644 app/views/RoomView/components/RightButtons/ThreadRightButtonsLegacy.tsx create mode 100644 app/views/RoomView/components/RightButtons/ThreadRightButtonsNative.tsx create mode 100644 app/views/RoomView/components/RightButtons/useHeaderCallPress.ts create mode 100644 app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts create mode 100644 app/views/RoomView/components/RightButtons/useRoomRightButtonsNativeData.ts create mode 100644 app/views/RoomView/helpers/__tests__/roomHeaderActions.test.ts create mode 100644 app/views/RoomView/helpers/roomHeaderActions.ts diff --git a/.maestro/helpers/navigate-to-room-action.yaml b/.maestro/helpers/navigate-to-room-action.yaml index 08a0dfeb718..dc99dce1fbc 100644 --- a/.maestro/helpers/navigate-to-room-action.yaml +++ b/.maestro/helpers/navigate-to-room-action.yaml @@ -18,12 +18,27 @@ tags: visible: id: 'room-view' timeout: 60000 -- assertVisible: - id: 'room-header' - index: 0 -- tapOn: - id: 'room-header' - index: 0 + +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'room-view-header-more' + - tapOn: + id: 'room-view-header-room-actions' + +- runFlow: + when: + platform: Android + commands: + - assertVisible: + id: 'room-header' + index: 0 + - tapOn: + id: 'room-header' + index: 0 + - extendedWaitUntil: visible: id: 'room-actions-view' diff --git a/app/views/RoomView/components/RightButtons/HeaderCallButton.tsx b/app/views/RoomView/components/RightButtons/HeaderCallButton.tsx index de7a9c68316..c884c12b757 100644 --- a/app/views/RoomView/components/RightButtons/HeaderCallButton.tsx +++ b/app/views/RoomView/components/RightButtons/HeaderCallButton.tsx @@ -1,10 +1,7 @@ -import { type ReactElement, useEffect, useRef } from 'react'; +import { type ReactElement } from 'react'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import { useVideoConf } from '~/lib/hooks/useVideoConf'; -import { useNewMediaCall } from '~/lib/hooks/useNewMediaCall'; - -const DOUBLE_TAP_WINDOW_MS = 300; +import { useHeaderCallPress } from './useHeaderCallPress'; export const HeaderCallButton = ({ rid, @@ -15,61 +12,19 @@ export const HeaderCallButton = ({ disabled: boolean; accessibilityLabel: string; }): ReactElement | null => { - const { showInitCallActionSheet, callEnabled, disabledTooltip } = useVideoConf(rid); - const { openNewMediaCall, startCallImmediate, hasMediaCallPermission, isInActiveCall } = useNewMediaCall(rid); - - const lastTapRef = useRef(0); - const pendingTimerRef = useRef | null>(null); - - useEffect( - () => () => { - if (pendingTimerRef.current) { - clearTimeout(pendingTimerRef.current); - pendingTimerRef.current = null; - } - }, - [] - ); + const { callPresent, isCallDisabled, onPressCall } = useHeaderCallPress(rid); - const handleVoipPress = () => { - const now = Date.now(); - if (pendingTimerRef.current && now - lastTapRef.current < DOUBLE_TAP_WINDOW_MS) { - clearTimeout(pendingTimerRef.current); - pendingTimerRef.current = null; - lastTapRef.current = 0; - startCallImmediate(); - return; - } - lastTapRef.current = now; - pendingTimerRef.current = setTimeout(() => { - pendingTimerRef.current = null; - openNewMediaCall(); - }, DOUBLE_TAP_WINDOW_MS); - }; - - if (hasMediaCallPermission) { - return ( - - ); - } - - if (callEnabled) { - return ( - - ); + if (!callPresent) { + return null; } - return null; + return ( + + ); }; diff --git a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx b/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx index da8d0a635b8..296acb4da2e 100644 --- a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx @@ -3,9 +3,8 @@ import { useStore } from 'zustand'; import { useNavigation } from '@react-navigation/native'; import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import i18n from '~/i18n'; -import { showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; +import { isIOS, isTablet, showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; @@ -17,6 +16,8 @@ import { useCanPlaceLivechatOnHold } from '~/views/RoomView/hooks/useCanPlaceLiv import { navigateToScreen, type TRoomStackNavigation } from '~/views/RoomView/services/navigateToScreen'; import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; import { placeLivechatOnHold } from '~/views/RoomView/services/placeLivechatOnHold'; +import { OmnichannelRightButtonsLegacy } from './OmnichannelRightButtonsLegacy'; +import { OmnichannelRightButtonsNative } from './OmnichannelRightButtonsNative'; interface IOmnichannelRightButtonsProps { rid: string; @@ -24,6 +25,7 @@ interface IOmnichannelRightButtonsProps { } export const OmnichannelRightButtons = ({ rid, roomStore }: IOmnichannelRightButtonsProps): ReactElement => { + const useNativeBar = isIOS && !isTablet; const navigation = useNavigation(); const isMasterDetail = useMasterDetail(); const { showActionSheet } = useActionSheet(); @@ -91,9 +93,9 @@ export const OmnichannelRightButtons = ({ rid, roomStore }: IOmnichannelRightBut showActionSheet({ options }); }; - return ( - - - - ); + if (useNativeBar) { + return ; + } + + return ; }; diff --git a/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsLegacy.tsx b/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsLegacy.tsx new file mode 100644 index 00000000000..f03d1bef40d --- /dev/null +++ b/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsLegacy.tsx @@ -0,0 +1,13 @@ +import { type ReactElement } from 'react'; + +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; + +interface IOmnichannelRightButtonsLegacyProps { + onShowMoreActions: () => void; +} + +export const OmnichannelRightButtonsLegacy = ({ onShowMoreActions }: IOmnichannelRightButtonsLegacyProps): ReactElement => ( + + + +); diff --git a/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsNative.tsx b/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsNative.tsx new file mode 100644 index 00000000000..5e51de957db --- /dev/null +++ b/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsNative.tsx @@ -0,0 +1,13 @@ +import { type ReactElement } from 'react'; + +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; + +interface IOmnichannelRightButtonsNativeProps { + onShowMoreActions: () => void; +} + +export const OmnichannelRightButtonsNative = ({ onShowMoreActions }: IOmnichannelRightButtonsNativeProps): ReactElement => ( + + + +); diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx index d7dfc8f6179..91189f6e57a 100644 --- a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx @@ -1,27 +1,10 @@ import { type ReactElement } from 'react'; -import { useStore } from 'zustand'; -import { useShallow } from 'zustand/react/shallow'; -import { useNavigation } from '@react-navigation/native'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import { type ISubscription, type SubscriptionType, type TSubscriptionModel, type TUserStatus } from '~/definitions'; -import i18n from '~/i18n'; -import { getRoomTitle, isGroupChat } from '~/lib/methods/helpers'; -import { events, logEvent } from '~/lib/methods/helpers/log'; -import getRoomAccessibilityLabel from '~/lib/helpers/getRoomAccessibilityLabel'; -import { useAppSelector } from '~/lib/hooks/useAppSelector'; -import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; -import { usePermissions } from '~/lib/hooks/usePermissions'; -import { useSetting } from '~/lib/hooks/useSetting'; -import { getUserSelector } from '~/selectors/login'; -import { useTheme } from '~/theme'; +import { isIOS, isTablet } from '~/lib/methods/helpers'; import { type RoomStore } from '~/views/RoomView/definitions'; -import { isSubscriptionModel } from '~/definitions/TRoom'; -import { fromSubscription } from '~/views/RoomView/stores/RoomStoreContext'; -import { useE2EEStatus } from '~/views/RoomView/hooks/useE2EEStatus'; -import { useSubscriptionUnreads } from '~/views/RoomView/hooks/useSubscriptionUnreads'; -import { navigateToScreen, type TRoomStackNavigation } from '~/views/RoomView/services/navigateToScreen'; -import { HeaderCallButton } from './HeaderCallButton'; +import { RoomRightButtonsLegacy } from './RoomRightButtonsLegacy'; +import { RoomRightButtonsNative } from './RoomRightButtonsNative'; +import { useRoomRightButtonsData } from './useRoomRightButtonsData'; interface IRoomRightButtonsProps { rid: string; @@ -29,131 +12,12 @@ interface IRoomRightButtonsProps { } export const RoomRightButtons = ({ rid, roomStore }: IRoomRightButtonsProps): ReactElement => { - const navigation = useNavigation(); - const isMasterDetail = useMasterDetail(); - const { colors } = useTheme(); + const useNativeBar = isIOS && !isTablet; + const data = useRoomRightButtonsData(rid, roomStore); - const userId = useAppSelector(state => getUserSelector(state).id); - const threadsEnabled = useSetting('Threads_enabled') as boolean; - const issuesWithNotifications = useAppSelector(state => state.troubleshootingNotification.issuesWithNotifications); + if (useNativeBar) { + return ; + } - const { t, status, roomName, roomIsGroupChat, teamMain, encrypted, disableNotifications } = useStore( - roomStore, - useShallow(s => { - const room = s.room; - return { - t: room.t as SubscriptionType, - status: fromSubscription(r => r.status, undefined)(s), - roomName: getRoomTitle(room), - roomIsGroupChat: isGroupChat(room as ISubscription), - teamMain: fromSubscription(r => !!r.teamMain, false)(s), - encrypted: fromSubscription(r => r.encrypted, undefined)(s), - disableNotifications: (room as ISubscription).disableNotifications - }; - }) - ); - const { hasE2EEWarning } = useE2EEStatus(roomStore); - const { tunread, tunreadUser, tunreadGroup, isSelfDm } = useSubscriptionUnreads(roomStore, userId); - const [canToggleEncryption] = usePermissions(['toggle-room-e2e-encryption'], rid); - - const goThreadsView = () => { - logEvent(events.ROOM_GO_THREADS); - navigateToScreen({ navigation, isMasterDetail, screen: 'ThreadMessagesView', params: { rid, t } }); - }; - - const navigateToNotificationOrPushTroubleshoot = () => { - const room = roomStore.getState().room; - if (!isSubscriptionModel(room)) { - return; - } - if (!issuesWithNotifications) { - navigateToScreen({ - navigation, - isMasterDetail, - screen: 'NotificationPrefView', - params: { rid, room: room as TSubscriptionModel } - }); - } else { - navigateToScreen({ navigation, isMasterDetail, screen: 'PushTroubleshootView' }); - } - }; - - const goSearchView = () => { - logEvent(events.ROOM_GO_SEARCH); - navigateToScreen({ - navigation, - isMasterDetail, - screen: 'SearchMessagesView', - params: isMasterDetail ? { rid, t, encrypted, showCloseModal: true } : { rid, t, encrypted } - }); - }; - - const goE2EEToggleRoomView = () => { - logEvent(events.ROOM_GO_E2EE); - navigateToScreen({ navigation, isMasterDetail, screen: 'E2EEToggleRoomView', params: { rid } }); - }; - - const threadsAccessibilityLabel = () => { - if (!tunread.length) { - return i18n.t('Threads'); - } - if (tunreadUser?.length) { - return i18n.t('Threads_dm_unread', { unread: tunreadUser?.length }); - } - if (tunreadGroup?.length) { - return i18n.t('Threads_group_unread', { unread: tunreadGroup?.length }); - } - return i18n.t('Threads_unread', { unread: tunread?.length }); - }; - - const accessibilityRoomName = - !roomIsGroupChat && t === 'd' && !!userId - ? roomName - : getRoomAccessibilityLabel({ type: t, userId, isGroupChat: roomIsGroupChat, status: status as TUserStatus, teamMain }); - - return ( - - {hasE2EEWarning ? ( - - ) : null} - {issuesWithNotifications || disableNotifications ? ( - - ) : null} - {!isSelfDm ? ( - - ) : null} - {threadsEnabled ? ( - } - disabled={hasE2EEWarning} - /> - ) : null} - - - ); + return ; }; diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtonsLegacy.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtonsLegacy.tsx new file mode 100644 index 00000000000..a4374e613e6 --- /dev/null +++ b/app/views/RoomView/components/RightButtons/RoomRightButtonsLegacy.tsx @@ -0,0 +1,72 @@ +import { type ReactElement } from 'react'; + +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; +import i18n from '~/i18n'; +import { HeaderCallButton } from './HeaderCallButton'; +import { type useRoomRightButtonsData } from './useRoomRightButtonsData'; + +interface IRoomRightButtonsLegacyProps { + rid: string; + data: ReturnType; +} + +export const RoomRightButtonsLegacy = ({ rid, data }: IRoomRightButtonsLegacyProps): ReactElement => { + const { + colors, + threadsEnabled, + issuesWithNotifications, + disableNotifications, + hasE2EEWarning, + canToggleEncryption, + isSelfDm, + tunread, + tunreadUser, + tunreadGroup, + callAccessibilityLabel, + goThreadsView, + navigateToNotificationOrPushTroubleshoot, + goSearchView, + goE2EEToggleRoomView, + threadsAccessibilityLabel + } = data; + + return ( + + {hasE2EEWarning ? ( + + ) : null} + {issuesWithNotifications || disableNotifications ? ( + + ) : null} + {!isSelfDm ? : null} + {threadsEnabled ? ( + } + disabled={hasE2EEWarning} + /> + ) : null} + + + ); +}; diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtonsNative.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtonsNative.tsx new file mode 100644 index 00000000000..20f3e9439c2 --- /dev/null +++ b/app/views/RoomView/components/RightButtons/RoomRightButtonsNative.tsx @@ -0,0 +1,145 @@ +import { type ReactElement } from 'react'; + +import { showActionSheetRef } from '~/containers/ActionSheet'; +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; +import i18n from '~/i18n'; +import { splitRoomHeaderActions, type TRoomHeaderActionKey } from '~/views/RoomView/helpers/roomHeaderActions'; +import { type RoomStore } from '~/views/RoomView/definitions'; +import { type useRoomRightButtonsData } from './useRoomRightButtonsData'; +import { useRoomRightButtonsNativeData } from './useRoomRightButtonsNativeData'; + +interface IRoomRightButtonsNativeProps { + rid: string; + roomStore: RoomStore; + data: ReturnType; +} + +export const RoomRightButtonsNative = ({ rid, roomStore, data }: IRoomRightButtonsNativeProps): ReactElement => { + const { + colors, + threadsEnabled, + issuesWithNotifications, + disableNotifications, + hasE2EEWarning, + canToggleEncryption, + isSelfDm, + tunread, + tunreadUser, + tunreadGroup, + callAccessibilityLabel, + goThreadsView, + navigateToNotificationOrPushTroubleshoot, + goSearchView, + goE2EEToggleRoomView, + threadsAccessibilityLabel + } = data; + const { callPresent, isCallDisabled, onPressCall, goRoomActionsView } = useRoomRightButtonsNativeData(rid, roomStore, isSelfDm); + + const { visibleKeys, overflowKeys } = splitRoomHeaderActions({ + threads: threadsEnabled, + call: callPresent, + encryption: hasE2EEWarning, + notifications: issuesWithNotifications || disableNotifications + }); + const isVisible = (key: TRoomHeaderActionKey) => visibleKeys.includes(key); + + const overflowOptions = [ + ...overflowKeys.map(key => { + if (key === 'threads') { + return { + title: threadsAccessibilityLabel, + icon: 'threads' as const, + testID: 'room-view-header-threads', + onPress: goThreadsView + }; + } + if (key === 'encryption') { + return { + title: i18n.t('Encrypted'), + icon: 'encrypted' as const, + testID: 'room-view-header-encryption', + enabled: canToggleEncryption, + onPress: goE2EEToggleRoomView + }; + } + if (key === 'call') { + return { + title: callAccessibilityLabel, + icon: 'phone' as const, + testID: 'room-view-header-call', + enabled: !isCallDisabled, + onPress: onPressCall + }; + } + if (key === 'notifications') { + return { + title: i18n.t('Troubleshooting'), + icon: 'notification-disabled' as const, + testID: 'room-view-push-troubleshoot', + onPress: navigateToNotificationOrPushTroubleshoot + }; + } + throw new Error(`Unhandled room header action key: ${key}`); + }), + { + title: i18n.t('Search_Messages'), + icon: 'search' as const, + testID: 'room-view-search', + enabled: !hasE2EEWarning, + onPress: goSearchView + }, + { + title: i18n.t('Actions'), + icon: 'kebab' as const, + testID: 'room-view-header-room-actions', + onPress: () => goRoomActionsView() + } + ]; + + return ( + + {isVisible('encryption') ? ( + + ) : null} + {isVisible('notifications') ? ( + + ) : null} + {isVisible('call') ? ( + + ) : null} + {isVisible('threads') ? ( + } + disabled={hasE2EEWarning} + /> + ) : null} + showActionSheetRef({ options: overflowOptions })} + testID='room-view-header-more' + /> + + ); +}; diff --git a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx b/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx index f1029284d86..1d813e74ddd 100644 --- a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx @@ -1,18 +1,20 @@ import { type ReactElement } from 'react'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import i18n from '~/i18n'; +import { isIOS, isTablet } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; import { getUserSelector } from '~/selectors/login'; import { useThreadFollowing } from '~/views/RoomView/hooks/useThreadFollowing'; +import { ThreadRightButtonsLegacy } from './ThreadRightButtonsLegacy'; +import { ThreadRightButtonsNative } from './ThreadRightButtonsNative'; interface IThreadRightButtonsProps { tmid: string; } export const ThreadRightButtons = ({ tmid }: IThreadRightButtonsProps): ReactElement => { + const useNativeBar = isIOS && !isTablet; const userId = useAppSelector(state => getUserSelector(state).id); const isFollowingThread = useThreadFollowing(tmid, userId); @@ -21,14 +23,9 @@ export const ThreadRightButtons = ({ tmid }: IThreadRightButtonsProps): ReactEle toggleFollowThread(tmid, isFollowingThread); }; - return ( - - - - ); + if (useNativeBar) { + return ; + } + + return ; }; diff --git a/app/views/RoomView/components/RightButtons/ThreadRightButtonsLegacy.tsx b/app/views/RoomView/components/RightButtons/ThreadRightButtonsLegacy.tsx new file mode 100644 index 00000000000..8aaefcf5370 --- /dev/null +++ b/app/views/RoomView/components/RightButtons/ThreadRightButtonsLegacy.tsx @@ -0,0 +1,23 @@ +import { type ReactElement } from 'react'; + +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; +import i18n from '~/i18n'; + +interface IThreadRightButtonsLegacyProps { + isFollowingThread: boolean; + onToggleFollowThread: () => void; +} + +export const ThreadRightButtonsLegacy = ({ + isFollowingThread, + onToggleFollowThread +}: IThreadRightButtonsLegacyProps): ReactElement => ( + + + +); diff --git a/app/views/RoomView/components/RightButtons/ThreadRightButtonsNative.tsx b/app/views/RoomView/components/RightButtons/ThreadRightButtonsNative.tsx new file mode 100644 index 00000000000..5281a2b54b4 --- /dev/null +++ b/app/views/RoomView/components/RightButtons/ThreadRightButtonsNative.tsx @@ -0,0 +1,23 @@ +import { type ReactElement } from 'react'; + +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; +import i18n from '~/i18n'; + +interface IThreadRightButtonsNativeProps { + isFollowingThread: boolean; + onToggleFollowThread: () => void; +} + +export const ThreadRightButtonsNative = ({ + isFollowingThread, + onToggleFollowThread +}: IThreadRightButtonsNativeProps): ReactElement => ( + + + +); diff --git a/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx index e8579d87263..06cd46f91f3 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx @@ -49,10 +49,18 @@ jest.mock('~/views/RoomView/hooks/useCanPlaceLivechatOnHold', () => ({ jest.mock('~/views/RoomView/services/closeLivechat', () => ({ closeLivechat: jest.fn() })); jest.mock('~/views/RoomView/services/placeLivechatOnHold', () => ({ placeLivechatOnHold: jest.fn() })); jest.mock('~/lib/services/restApi', () => ({ returnLivechat: jest.fn() })); +let mockIsIOS = true; +let mockIsTablet = false; jest.mock('~/lib/methods/helpers', () => ({ ...jest.requireActual('~/lib/methods/helpers'), showConfirmationAlert: jest.fn(), - showErrorAlert: jest.fn() + showErrorAlert: jest.fn(), + get isIOS() { + return mockIsIOS; + }, + get isTablet() { + return mockIsTablet; + } })); jest.mock('~/containers/Header/components/HeaderButton', () => { @@ -85,6 +93,8 @@ describe('OmnichannelRightButtons', () => { }; mockCanReturnQueue = false; mockCanPlaceLivechatOnHold = false; + mockIsIOS = true; + mockIsTablet = false; }); it('renders the kebab button', () => { @@ -187,4 +197,16 @@ describe('OmnichannelRightButtons', () => { navigation: mockNavigation }); }); + + it.each([ + ['Android', false, false], + ['iPad', true, true] + ])('offers the close option on the legacy bar (%s)', (_label, isIOSValue, isTabletValue) => { + mockIsIOS = isIOSValue; + mockIsTablet = isTabletValue; + + const options = openKebab(); + + expect(titlesOf(options)).toEqual(['Close']); + }); }); diff --git a/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx index db2d60dbb2e..8647d1b02c5 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx @@ -1,5 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react-native'; +import { showActionSheetRef } from '~/containers/ActionSheet'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { type RoomStore } from '~/views/RoomView/definitions'; import { RoomRightButtons } from '../RoomRightButtons'; @@ -20,10 +21,18 @@ jest.mock('~/lib/hooks/useMasterDetail', () => ({ jest.mock('~/theme', () => ({ useTheme: () => ({ colors: { fontDanger: '#f00' } }) })); jest.mock('~/lib/helpers/getRoomAccessibilityLabel', () => ({ __esModule: true, default: () => 'channel label' })); + +let mockIsIOS = true; +let mockIsTablet = false; jest.mock('~/lib/methods/helpers', () => ({ - ...jest.requireActual('~/lib/methods/helpers'), getRoomTitle: () => 'Room Title', - isGroupChat: () => false + isGroupChat: () => false, + get isIOS() { + return mockIsIOS; + }, + get isTablet() { + return mockIsTablet; + } })); let mockThreadsEnabled = true; @@ -68,6 +77,30 @@ jest.mock('~/lib/hooks/usePermissions', () => ({ usePermissions: () => [mockCanToggleEncryption] })); +let mockCallEnabled = false; +jest.mock('~/lib/hooks/useVideoConf', () => ({ + useVideoConf: () => ({ showInitCallActionSheet: jest.fn(), callEnabled: mockCallEnabled, disabledTooltip: false }) +})); + +let mockHasMediaCallPermission = false; +jest.mock('~/lib/hooks/useNewMediaCall', () => ({ + useNewMediaCall: () => ({ + openNewMediaCall: jest.fn(), + startCallImmediate: jest.fn(), + hasMediaCallPermission: mockHasMediaCallPermission, + isInActiveCall: false + }) +})); + +const mockGoRoomActionsView = jest.fn(); +jest.mock('~/views/RoomView/hooks/useGoRoomActionsView', () => ({ + useGoRoomActionsView: () => mockGoRoomActionsView +})); + +jest.mock('~/containers/ActionSheet', () => ({ + showActionSheetRef: jest.fn() +})); + jest.mock('~/containers/Header/components/HeaderButton', () => { const ReactActual = jest.requireActual('react'); return { @@ -114,6 +147,8 @@ describe('RoomRightButtons', () => { beforeEach(() => { jest.clearAllMocks(); mockIsMasterDetail = false; + mockIsIOS = true; + mockIsTablet = false; mockThreadsEnabled = true; mockAppState = { login: { user: { id: 'u1', username: 'user', token: 'tok' } }, @@ -123,185 +158,162 @@ describe('RoomRightButtons', () => { mockHasE2EEWarning = false; mockUnreads = { tunread: [], tunreadUser: [], tunreadGroup: [], isSelfDm: false }; mockCanToggleEncryption = false; + mockCallEnabled = true; + mockHasMediaCallPermission = false; }); - it('renders the call, threads and search buttons for a regular channel', () => { - renderRoomRightButtons(); - - expect(screen.getByTestId('room-view-header-threads')).toHaveProp('iconName', 'threads'); - expect(screen.getByTestId('room-view-search')).toHaveProp('accessibilityLabel', 'Search messages'); - expect(screen.queryByTestId('room-view-header-encryption')).not.toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-push-troubleshoot')).not.toBeOnTheScreen(); - }); - - it('labels the call button with the room accessibility name', () => { - renderRoomRightButtons(); - - expect(screen.getByTestId('header-call-button-stub')).toHaveProp('accessibilityLabel', 'Call channel label'); - }); - - it('hides the call button on a self DM', () => { - mockRoomState = { room: { rid: 'rid-1', t: 'd', name: 'user' } }; - mockUnreads = { ...mockUnreads, isSelfDm: true }; - - renderRoomRightButtons(); - - expect(screen.queryByTestId('header-call-button-stub')).not.toBeOnTheScreen(); - expect(screen.getByTestId('room-view-search')).toBeOnTheScreen(); - }); - - it('enables the encryption button and disables the others on an e2ee warning with permission', () => { - mockHasE2EEWarning = true; - mockCanToggleEncryption = true; - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - - renderRoomRightButtons(); - - expect(screen.getByTestId('room-view-header-encryption')).toHaveProp('disabled', false); - expect(screen.getByTestId('room-view-header-threads')).toHaveProp('disabled', true); - expect(screen.getByTestId('room-view-search')).toHaveProp('disabled', true); - expect(screen.getByTestId('header-call-button-stub')).toHaveProp('disabled', true); - expect(screen.getByTestId('room-view-push-troubleshoot')).toHaveProp('disabled', true); - }); - - it('disables the encryption button on an e2ee warning without permission', () => { - mockHasE2EEWarning = true; - - renderRoomRightButtons(); - - expect(screen.getByTestId('room-view-header-encryption')).toHaveProp('disabled', true); - }); - - it.each([false, true])('routes notification issues to push troubleshooting (master-detail: %s)', isMasterDetail => { - mockIsMasterDetail = isMasterDetail; - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general' } }; - - renderRoomRightButtons(); - - expect(screen.getByTestId('room-view-push-troubleshoot')).toHaveProp('color', '#f00'); - fireEvent.press(screen.getByTestId('room-view-push-troubleshoot')); - expect(mockNavigation.navigate).toHaveBeenCalledWith( - ...(isMasterDetail - ? ['ModalStackNavigator', { screen: 'PushTroubleshootView', params: undefined }] - : ['PushTroubleshootView', undefined]) - ); - }); - - it.each([false, true])('routes disabled Room notifications to preferences (master-detail: %s)', isMasterDetail => { - mockIsMasterDetail = isMasterDetail; - mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general', disableNotifications: true } }; - - renderRoomRightButtons(); + describe('on the iOS native bar', () => { + it('shows threads and call as bar items and folds search and Room Actions into the overflow menu', () => { + renderRoomRightButtons(); + + expect(screen.getByTestId('room-view-header-threads')).toHaveProp('iconName', 'threads'); + expect(screen.getByTestId('room-view-header-call')).toBeOnTheScreen(); + expect(screen.queryByTestId('room-view-search')).not.toBeOnTheScreen(); + expect(screen.getByTestId('room-view-header-more')).toBeOnTheScreen(); + + fireEvent.press(screen.getByTestId('room-view-header-more')); + const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; + expect(options.map((option: { testID: string }) => option.testID)).toEqual([ + 'room-view-search', + 'room-view-header-room-actions' + ]); + }); - expect(screen.getByTestId('room-view-push-troubleshoot')).toHaveProp('color', ''); - fireEvent.press(screen.getByTestId('room-view-push-troubleshoot')); - const params = { rid: 'rid-1', room: mockRoomState.room }; - expect(mockNavigation.navigate).toHaveBeenCalledWith( - ...(isMasterDetail ? ['ModalStackNavigator', { screen: 'NotificationPrefView', params }] : ['NotificationPrefView', params]) - ); - }); + it('demotes encryption and notifications to overflow when every warning is active', () => { + mockHasE2EEWarning = true; + mockCanToggleEncryption = true; + mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; + + renderRoomRightButtons(); + + expect(screen.getByTestId('room-view-header-threads')).toBeOnTheScreen(); + expect(screen.getByTestId('room-view-header-call')).toBeOnTheScreen(); + expect(screen.queryByTestId('room-view-header-encryption')).not.toBeOnTheScreen(); + expect(screen.queryByTestId('room-view-push-troubleshoot')).not.toBeOnTheScreen(); + + fireEvent.press(screen.getByTestId('room-view-header-more')); + const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; + expect(options.map((option: { testID: string }) => option.testID)).toEqual([ + 'room-view-header-encryption', + 'room-view-push-troubleshoot', + 'room-view-search', + 'room-view-header-room-actions' + ]); + }); - it('does not navigate from the notification button without a subscription', () => { - mockRoomState = { room: { rid: 'rid-1', t: 'c', name: 'general', disableNotifications: true } }; + it('promotes call to a bar item once threads is disabled, keeping the cap at two', () => { + mockThreadsEnabled = false; + mockHasE2EEWarning = true; + mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - renderRoomRightButtons(); + renderRoomRightButtons(); - fireEvent.press(screen.getByTestId('room-view-push-troubleshoot')); + expect(screen.queryByTestId('room-view-header-threads')).not.toBeOnTheScreen(); + expect(screen.getByTestId('room-view-header-call')).toBeOnTheScreen(); + expect(screen.getByTestId('room-view-header-encryption')).toBeOnTheScreen(); + expect(screen.queryByTestId('room-view-push-troubleshoot')).not.toBeOnTheScreen(); + }); - expect(mockNavigation.navigate).not.toHaveBeenCalled(); - }); + it('opens Room Actions from the overflow menu', () => { + renderRoomRightButtons(); - it('hides the threads button when threads are disabled', () => { - mockThreadsEnabled = false; + fireEvent.press(screen.getByTestId('room-view-header-more')); + const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; + const roomActionsOption = options.find((option: { testID: string }) => option.testID === 'room-view-header-room-actions'); + roomActionsOption.onPress(); - renderRoomRightButtons(); + expect(mockGoRoomActionsView).toHaveBeenCalledWith(); + }); - expect(screen.queryByTestId('room-view-header-threads')).not.toBeOnTheScreen(); - expect(screen.getByTestId('room-view-search')).toBeOnTheScreen(); - }); + it('hides the call bar item on a self DM', () => { + mockRoomState = { room: { rid: 'rid-1', t: 'd', name: 'user' } }; + mockUnreads = { ...mockUnreads, isSelfDm: true }; - it('labels the threads button without unreads', () => { - renderRoomRightButtons(); + renderRoomRightButtons(); - expect(screen.getByTestId('room-view-header-threads')).toHaveProp('accessibilityLabel', 'Threads'); + expect(screen.queryByTestId('room-view-header-call')).not.toBeOnTheScreen(); + }); }); - it('labels the threads button with the direct mention unread count', () => { - mockUnreads = { ...mockUnreads, tunread: ['tm-1'], tunreadUser: ['tm-1'] }; - - renderRoomRightButtons(); + describe('on Android', () => { + beforeEach(() => { + mockIsIOS = false; + }); - expect(screen.getByTestId('room-view-header-threads')).toHaveProp('accessibilityLabel', 'Threads, 1 unread, direct mention'); - }); + it('renders every present item directly with no overflow menu', () => { + mockHasE2EEWarning = true; + mockCanToggleEncryption = true; + mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - it('labels the threads button with the group mention unread count', () => { - mockUnreads = { ...mockUnreads, tunread: ['tm-1'], tunreadGroup: ['tm-1'] }; + renderRoomRightButtons(); - renderRoomRightButtons(); - - expect(screen.getByTestId('room-view-header-threads')).toHaveProp('accessibilityLabel', 'Threads, 1 unread, group mention'); + expect(screen.getByTestId('room-view-header-encryption')).toBeOnTheScreen(); + expect(screen.getByTestId('room-view-push-troubleshoot')).toBeOnTheScreen(); + expect(screen.getByTestId('room-view-header-threads')).toBeOnTheScreen(); + expect(screen.getByTestId('room-view-search')).toBeOnTheScreen(); + expect(screen.queryByTestId('room-view-header-more')).not.toBeOnTheScreen(); + }); }); - it('labels the threads button with the plain unread count', () => { - mockUnreads = { ...mockUnreads, tunread: ['tm-1', 'tm-2'] }; + describe('on iPad', () => { + beforeEach(() => { + mockIsTablet = true; + }); - renderRoomRightButtons(); + it('renders every present item directly with no overflow menu', () => { + renderRoomRightButtons(); - expect(screen.getByTestId('room-view-header-threads')).toHaveProp('accessibilityLabel', 'Threads, 2 unread'); + expect(screen.getByTestId('room-view-header-threads')).toBeOnTheScreen(); + expect(screen.getByTestId('room-view-search')).toBeOnTheScreen(); + expect(screen.queryByTestId('room-view-header-more')).not.toBeOnTheScreen(); + }); }); - it('navigates to the threads and search screens on stack mode', () => { + it.each([false, true])( + 'routes notification issues to push troubleshooting from the overflow menu (master-detail: %s)', + isMasterDetail => { + mockIsMasterDetail = isMasterDetail; + mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; + mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general' } }; + + renderRoomRightButtons(); + + fireEvent.press(screen.getByTestId('room-view-header-more')); + const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; + const notificationsOption = options.find((option: { testID: string }) => option.testID === 'room-view-push-troubleshoot'); + notificationsOption.onPress(); + expect(mockNavigation.navigate).toHaveBeenCalledWith( + ...(isMasterDetail + ? ['ModalStackNavigator', { screen: 'PushTroubleshootView', params: undefined }] + : ['PushTroubleshootView', undefined]) + ); + } + ); + + it('navigates to the threads screen', () => { mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general', encrypted: true } }; renderRoomRightButtons(); fireEvent.press(screen.getByTestId('room-view-header-threads')); expect(mockNavigation.navigate).toHaveBeenCalledWith('ThreadMessagesView', { rid: 'rid-1', t: 'c' }); - - fireEvent.press(screen.getByTestId('room-view-search')); - expect(mockNavigation.navigate).toHaveBeenCalledWith('SearchMessagesView', { rid: 'rid-1', t: 'c', encrypted: true }); - }); - - it('navigates through the modal stack on master-detail mode', () => { - mockIsMasterDetail = true; - mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general', encrypted: true } }; - - renderRoomRightButtons(); - - fireEvent.press(screen.getByTestId('room-view-header-threads')); - expect(mockNavigation.navigate).toHaveBeenCalledWith('ModalStackNavigator', { - screen: 'ThreadMessagesView', - params: { rid: 'rid-1', t: 'c' } - }); - - fireEvent.press(screen.getByTestId('room-view-search')); - expect(mockNavigation.navigate).toHaveBeenCalledWith('ModalStackNavigator', { - screen: 'SearchMessagesView', - params: { rid: 'rid-1', t: 'c', encrypted: true, showCloseModal: true } - }); }); - it.each([false, true])('offers encryption navigation while other buttons are disabled (master-detail: %s)', isMasterDetail => { - mockIsMasterDetail = isMasterDetail; + it('offers encryption navigation from the overflow menu while other bar items are disabled', () => { mockHasE2EEWarning = true; mockCanToggleEncryption = true; mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general' } }; renderRoomRightButtons(); - expect(screen.getByTestId('room-view-header-threads')).toHaveProp('disabled', true); - expect(screen.getByTestId('room-view-search')).toHaveProp('disabled', true); - expect(screen.getByTestId('room-view-push-troubleshoot')).toHaveProp('disabled', true); - expect(screen.getByTestId('room-view-header-encryption')).toHaveProp('disabled', false); + fireEvent.press(screen.getByTestId('room-view-header-more')); + const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; + const encryptionOption = options.find((option: { testID: string }) => option.testID === 'room-view-header-encryption'); + expect(encryptionOption.enabled).toBe(true); - fireEvent.press(screen.getByTestId('room-view-header-encryption')); + encryptionOption.onPress(); expect(logEvent).toHaveBeenCalledTimes(1); expect(logEvent).toHaveBeenCalledWith(events.ROOM_GO_E2EE); - expect(mockNavigation.navigate).toHaveBeenCalledWith( - ...(isMasterDetail - ? ['ModalStackNavigator', { screen: 'E2EEToggleRoomView', params: { rid: 'rid-1' } }] - : ['E2EEToggleRoomView', { rid: 'rid-1' }]) - ); + expect(mockNavigation.navigate).toHaveBeenCalledWith('E2EEToggleRoomView', { rid: 'rid-1' }); }); }); diff --git a/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx index 6e16f6df164..8b919b27ac3 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx @@ -15,6 +15,17 @@ jest.mock('~/views/RoomView/hooks/useThreadFollowing', () => ({ jest.mock('~/lib/methods/toggleFollowThread', () => ({ toggleFollowThread: jest.fn() })); +let mockIsIOS = true; +let mockIsTablet = false; +jest.mock('~/lib/methods/helpers', () => ({ + get isIOS() { + return mockIsIOS; + }, + get isTablet() { + return mockIsTablet; + } +})); + jest.mock('~/containers/Header/components/HeaderButton', () => { const ReactActual = jest.requireActual('react'); return { @@ -38,6 +49,8 @@ describe('ThreadRightButtons', () => { jest.clearAllMocks(); mockFollowersByThread = {}; mockAppState = { login: { user: { id: 'u1', username: 'user', token: 'tok' } } }; + mockIsIOS = true; + mockIsTablet = false; }); it('renders the follow button when the thread is not followed', () => { @@ -92,4 +105,17 @@ describe('ThreadRightButtons', () => { expect(toggleFollowThread).toHaveBeenCalledWith('tmid-1', true); }); + + it.each([ + ['Android', false, false], + ['iPad', true, true] + ])('follows the thread on the legacy bar (%s)', (_label, isIOSValue, isTabletValue) => { + mockIsIOS = isIOSValue; + mockIsTablet = isTabletValue; + + render(); + fireEvent.press(screen.getByTestId('room-view-header-follow')); + + expect(toggleFollowThread).toHaveBeenCalledWith('tmid-1', false); + }); }); diff --git a/app/views/RoomView/components/RightButtons/useHeaderCallPress.ts b/app/views/RoomView/components/RightButtons/useHeaderCallPress.ts new file mode 100644 index 00000000000..cefdef7f63b --- /dev/null +++ b/app/views/RoomView/components/RightButtons/useHeaderCallPress.ts @@ -0,0 +1,65 @@ +import { useEffect, useRef } from 'react'; + +import { useNewMediaCall } from '~/lib/hooks/useNewMediaCall'; +import { useVideoConf } from '~/lib/hooks/useVideoConf'; + +const DOUBLE_TAP_WINDOW_MS = 300; + +export const useHeaderCallPress = (rid: string) => { + const { showInitCallActionSheet, callEnabled, disabledTooltip } = useVideoConf(rid); + const { openNewMediaCall, startCallImmediate, hasMediaCallPermission, isInActiveCall } = useNewMediaCall(rid); + + const lastTapRef = useRef(0); + const pendingTimerRef = useRef | null>(null); + + useEffect( + () => () => { + if (pendingTimerRef.current) { + clearTimeout(pendingTimerRef.current); + pendingTimerRef.current = null; + } + }, + [] + ); + + const onPressMediaCall = () => { + const now = Date.now(); + if (pendingTimerRef.current && now - lastTapRef.current < DOUBLE_TAP_WINDOW_MS) { + clearTimeout(pendingTimerRef.current); + pendingTimerRef.current = null; + lastTapRef.current = 0; + startCallImmediate(); + return; + } + lastTapRef.current = now; + pendingTimerRef.current = setTimeout(() => { + pendingTimerRef.current = null; + openNewMediaCall(); + }, DOUBLE_TAP_WINDOW_MS); + }; + + if (hasMediaCallPermission) { + return { + callPresent: true, + hasMediaCallPermission, + isCallDisabled: isInActiveCall, + onPressCall: onPressMediaCall + }; + } + + if (callEnabled) { + return { + callPresent: true, + hasMediaCallPermission, + isCallDisabled: disabledTooltip || isInActiveCall, + onPressCall: showInitCallActionSheet + }; + } + + return { + callPresent: false, + hasMediaCallPermission, + isCallDisabled: true, + onPressCall: () => {} + }; +}; diff --git a/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts b/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts new file mode 100644 index 00000000000..2f20dd11151 --- /dev/null +++ b/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts @@ -0,0 +1,125 @@ +import { useStore } from 'zustand'; +import { useShallow } from 'zustand/react/shallow'; +import { useNavigation } from '@react-navigation/native'; + +import { type ISubscription, type SubscriptionType, type TSubscriptionModel, type TUserStatus } from '~/definitions'; +import { isSubscriptionModel } from '~/definitions/TRoom'; +import i18n from '~/i18n'; +import getRoomAccessibilityLabel from '~/lib/helpers/getRoomAccessibilityLabel'; +import { useAppSelector } from '~/lib/hooks/useAppSelector'; +import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; +import { usePermissions } from '~/lib/hooks/usePermissions'; +import { useSetting } from '~/lib/hooks/useSetting'; +import { getRoomTitle, isGroupChat } from '~/lib/methods/helpers'; +import { events, logEvent } from '~/lib/methods/helpers/log'; +import { getUserSelector } from '~/selectors/login'; +import { useTheme } from '~/theme'; +import { type RoomStore } from '~/views/RoomView/definitions'; +import { useE2EEStatus } from '~/views/RoomView/hooks/useE2EEStatus'; +import { useSubscriptionUnreads } from '~/views/RoomView/hooks/useSubscriptionUnreads'; +import { fromSubscription } from '~/views/RoomView/stores/RoomStoreContext'; +import { navigateToScreen, type TRoomStackNavigation } from '~/views/RoomView/services/navigateToScreen'; + +export const useRoomRightButtonsData = (rid: string, roomStore: RoomStore) => { + const navigation = useNavigation(); + const isMasterDetail = useMasterDetail(); + const { colors } = useTheme(); + + const userId = useAppSelector(state => getUserSelector(state).id); + const threadsEnabled = useSetting('Threads_enabled') as boolean; + const issuesWithNotifications = useAppSelector(state => state.troubleshootingNotification.issuesWithNotifications); + + const { t, status, roomName, roomIsGroupChat, teamMain, encrypted, disableNotifications } = useStore( + roomStore, + useShallow(s => { + const room = s.room; + return { + t: room.t as SubscriptionType, + status: fromSubscription(r => r.status, undefined)(s), + roomName: getRoomTitle(room), + roomIsGroupChat: isGroupChat(room as ISubscription), + teamMain: fromSubscription(r => !!r.teamMain, false)(s), + encrypted: fromSubscription(r => r.encrypted, undefined)(s), + disableNotifications: (room as ISubscription).disableNotifications + }; + }) + ); + const { hasE2EEWarning } = useE2EEStatus(roomStore); + const { tunread, tunreadUser, tunreadGroup, isSelfDm } = useSubscriptionUnreads(roomStore, userId); + const [canToggleEncryption] = usePermissions(['toggle-room-e2e-encryption'], rid); + + const goThreadsView = () => { + logEvent(events.ROOM_GO_THREADS); + navigateToScreen({ navigation, isMasterDetail, screen: 'ThreadMessagesView', params: { rid, t } }); + }; + + const navigateToNotificationOrPushTroubleshoot = () => { + const room = roomStore.getState().room; + if (!isSubscriptionModel(room)) { + return; + } + if (!issuesWithNotifications) { + navigateToScreen({ + navigation, + isMasterDetail, + screen: 'NotificationPrefView', + params: { rid, room: room as TSubscriptionModel } + }); + } else { + navigateToScreen({ navigation, isMasterDetail, screen: 'PushTroubleshootView' }); + } + }; + + const goSearchView = () => { + logEvent(events.ROOM_GO_SEARCH); + navigateToScreen({ + navigation, + isMasterDetail, + screen: 'SearchMessagesView', + params: isMasterDetail ? { rid, t, encrypted, showCloseModal: true } : { rid, t, encrypted } + }); + }; + + const goE2EEToggleRoomView = () => { + logEvent(events.ROOM_GO_E2EE); + navigateToScreen({ navigation, isMasterDetail, screen: 'E2EEToggleRoomView', params: { rid } }); + }; + + const threadsAccessibilityLabel = () => { + if (!tunread.length) { + return i18n.t('Threads'); + } + if (tunreadUser?.length) { + return i18n.t('Threads_dm_unread', { unread: tunreadUser?.length }); + } + if (tunreadGroup?.length) { + return i18n.t('Threads_group_unread', { unread: tunreadGroup?.length }); + } + return i18n.t('Threads_unread', { unread: tunread?.length }); + }; + + const accessibilityRoomName = + !roomIsGroupChat && t === 'd' && !!userId + ? roomName + : getRoomAccessibilityLabel({ type: t, userId, isGroupChat: roomIsGroupChat, status: status as TUserStatus, teamMain }); + const callAccessibilityLabel = i18n.t('Call_room_name', { roomName: accessibilityRoomName }); + + return { + colors, + threadsEnabled, + issuesWithNotifications, + disableNotifications, + hasE2EEWarning, + canToggleEncryption, + isSelfDm, + tunread, + tunreadUser, + tunreadGroup, + callAccessibilityLabel, + goThreadsView, + navigateToNotificationOrPushTroubleshoot, + goSearchView, + goE2EEToggleRoomView, + threadsAccessibilityLabel: threadsAccessibilityLabel() + }; +}; diff --git a/app/views/RoomView/components/RightButtons/useRoomRightButtonsNativeData.ts b/app/views/RoomView/components/RightButtons/useRoomRightButtonsNativeData.ts new file mode 100644 index 00000000000..59120f82b3f --- /dev/null +++ b/app/views/RoomView/components/RightButtons/useRoomRightButtonsNativeData.ts @@ -0,0 +1,15 @@ +import { type RoomStore } from '~/views/RoomView/definitions'; +import { useGoRoomActionsView } from '~/views/RoomView/hooks/useGoRoomActionsView'; +import { useHeaderCallPress } from './useHeaderCallPress'; + +export const useRoomRightButtonsNativeData = (rid: string, roomStore: RoomStore, isSelfDm: boolean) => { + const goRoomActionsView = useGoRoomActionsView(roomStore); + const { callPresent, isCallDisabled, onPressCall } = useHeaderCallPress(rid); + + return { + callPresent: !isSelfDm && callPresent, + isCallDisabled, + onPressCall, + goRoomActionsView + }; +}; diff --git a/app/views/RoomView/components/__tests__/RightButtons.test.tsx b/app/views/RoomView/components/__tests__/RightButtons.test.tsx index 8170c55440f..61b0e4401aa 100644 --- a/app/views/RoomView/components/__tests__/RightButtons.test.tsx +++ b/app/views/RoomView/components/__tests__/RightButtons.test.tsx @@ -19,7 +19,22 @@ jest.mock('~/lib/helpers/getRoomAccessibilityLabel', () => ({ __esModule: true, jest.mock('~/lib/methods/helpers', () => ({ ...jest.requireActual('~/lib/methods/helpers'), getRoomTitle: () => 'Room Title', - isGroupChat: () => false + isGroupChat: () => false, + isTablet: true +})); +jest.mock('~/lib/hooks/useVideoConf', () => ({ + useVideoConf: () => ({ showInitCallActionSheet: jest.fn(), callEnabled: false, disabledTooltip: false }) +})); +jest.mock('~/lib/hooks/useNewMediaCall', () => ({ + useNewMediaCall: () => ({ + openNewMediaCall: jest.fn(), + startCallImmediate: jest.fn(), + hasMediaCallPermission: false, + isInActiveCall: false + }) +})); +jest.mock('~/views/RoomView/hooks/useGoRoomActionsView', () => ({ + useGoRoomActionsView: () => jest.fn() })); let mockAppState = { diff --git a/app/views/RoomView/helpers/__tests__/roomHeaderActions.test.ts b/app/views/RoomView/helpers/__tests__/roomHeaderActions.test.ts new file mode 100644 index 00000000000..6b54d3f1e1c --- /dev/null +++ b/app/views/RoomView/helpers/__tests__/roomHeaderActions.test.ts @@ -0,0 +1,48 @@ +import { splitRoomHeaderActions } from '../roomHeaderActions'; + +describe('splitRoomHeaderActions', () => { + it('keeps every present item visible when at or under the cap', () => { + const { visibleKeys, overflowKeys } = splitRoomHeaderActions({ threads: true, call: true }); + + expect(visibleKeys).toEqual(['threads', 'call']); + expect(overflowKeys).toEqual([]); + }); + + it('ranks threads ahead of call so an unread badge always keeps a bar item', () => { + const { visibleKeys, overflowKeys } = splitRoomHeaderActions({ threads: true, call: true, encryption: true }); + + expect(visibleKeys).toEqual(['threads', 'call']); + expect(overflowKeys).toEqual(['encryption']); + }); + + it('demotes the lowest-ranked items to overflow when every warning is active at once', () => { + const { visibleKeys, overflowKeys } = splitRoomHeaderActions({ + threads: true, + call: true, + encryption: true, + notifications: true + }); + + expect(visibleKeys).toEqual(['threads', 'call']); + expect(overflowKeys).toEqual(['encryption', 'notifications']); + }); + + it('promotes call to the visible cluster once threads is absent', () => { + const { visibleKeys, overflowKeys } = splitRoomHeaderActions({ + threads: false, + call: true, + encryption: true, + notifications: true + }); + + expect(visibleKeys).toEqual(['call', 'encryption']); + expect(overflowKeys).toEqual(['notifications']); + }); + + it('returns nothing visible or overflowing when no conditional item is present', () => { + const { visibleKeys, overflowKeys } = splitRoomHeaderActions({}); + + expect(visibleKeys).toEqual([]); + expect(overflowKeys).toEqual([]); + }); +}); diff --git a/app/views/RoomView/helpers/roomHeaderActions.ts b/app/views/RoomView/helpers/roomHeaderActions.ts new file mode 100644 index 00000000000..dd6e63fdf46 --- /dev/null +++ b/app/views/RoomView/helpers/roomHeaderActions.ts @@ -0,0 +1,13 @@ +export const ROOM_HEADER_ACTION_PRIORITY = ['threads', 'call', 'encryption', 'notifications'] as const; +export type TRoomHeaderActionKey = (typeof ROOM_HEADER_ACTION_PRIORITY)[number]; +export const MAX_VISIBLE_ROOM_HEADER_ACTIONS = 2; + +export const splitRoomHeaderActions = ( + present: Partial> +): { visibleKeys: TRoomHeaderActionKey[]; overflowKeys: TRoomHeaderActionKey[] } => { + const presentKeys = ROOM_HEADER_ACTION_PRIORITY.filter(key => present[key]); + return { + visibleKeys: presentKeys.slice(0, MAX_VISIBLE_ROOM_HEADER_ACTIONS), + overflowKeys: presentKeys.slice(MAX_VISIBLE_ROOM_HEADER_ACTIONS) + }; +}; From 628c95320ffdce860dbf17bc88eba8322f893500 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 17:55:32 -0300 Subject: [PATCH 030/120] feat(ios): system search bar on the rooms list --- .maestro/helpers/search-room.yaml | 24 +++++-- .../hooks/__tests__/useHeader.test.tsx | 62 +++++++++++-------- app/views/RoomsListView/hooks/useHeader.tsx | 29 +++++---- 3 files changed, 71 insertions(+), 44 deletions(-) diff --git a/.maestro/helpers/search-room.yaml b/.maestro/helpers/search-room.yaml index 6314ba6f9af..a0f05199799 100644 --- a/.maestro/helpers/search-room.yaml +++ b/.maestro/helpers/search-room.yaml @@ -9,12 +9,24 @@ tags: timeout: 60000 - waitForAnimationToEnd: timeout: 5000 -- tapOn: - id: 'rooms-list-view-search' -- tapOn: - id: 'rooms-list-view-search-input' -- inputText: - text: ${ROOM} +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'magnifyingglass' + - inputText: + text: ${ROOM} +- runFlow: + when: + platform: android + commands: + - tapOn: + id: 'rooms-list-view-search' + - tapOn: + id: 'rooms-list-view-search-input' + - inputText: + text: ${ROOM} - extendedWaitUntil: visible: id: 'rooms-list-view-item-${ROOM}' diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 58ebae9d923..23c89a93f02 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -1,5 +1,5 @@ -import { renderHook } from '@testing-library/react-native'; -import { type ReactElement } from 'react'; +import { act, renderHook } from '@testing-library/react-native'; +import { type ReactElement, useState } from 'react'; import { RoomsSearchContext } from '../../contexts/RoomsSearchProvider'; import { useHeader } from '../useHeader'; @@ -116,7 +116,7 @@ describe('RoomsListView useHeader', () => { expect(options.headerTitle).toBe(expectedTitle); }); - it('builds the right cluster in search, create priority order, overflowing push-troubleshoot and directory', () => { + it('builds the right cluster in create, push-troubleshoot priority order, overflowing directory', () => { mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; renderUseHeader(); @@ -124,48 +124,56 @@ describe('RoomsListView useHeader', () => { const options = mockSetOptions.mock.calls[0][0]; const rightButtons: ReactElement<{ testID: string }>[] = options.headerRight().props.children.filter(Boolean); const testIDs = rightButtons.map(button => button.props.testID); - expect(testIDs).toEqual(['rooms-list-view-search', 'rooms-list-view-create-channel', 'rooms-list-view-more']); + expect(testIDs).toEqual(['rooms-list-view-create-channel', 'rooms-list-view-push-troubleshoot', 'rooms-list-view-directory']); }); - it('does not render an overflow control when a single item would be hidden', () => { + it('does not render an overflow control when nothing extra is present', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; const rightButtons: ReactElement<{ testID: string }>[] = options.headerRight().props.children.filter(Boolean); const testIDs = rightButtons.map(button => button.props.testID); - expect(testIDs).toEqual(['rooms-list-view-search', 'rooms-list-view-create-channel', 'rooms-list-view-directory']); + expect(testIDs).toEqual(['rooms-list-view-create-channel', 'rooms-list-view-directory']); }); - it('keeps search reachable in the native cluster', () => { + it('configures a stacked system search bar instead of a right-cluster search item', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; - const rightButtons: ReactElement<{ testID: string; onPress: () => void }>[] = options - .headerRight() - .props.children.filter(Boolean); - const searchButton = rightButtons.find(button => button.props.testID === 'rooms-list-view-search'); - expect(searchButton).toBeDefined(); + const rightButtons: ReactElement<{ testID: string }>[] = options.headerRight().props.children.filter(Boolean); + expect(rightButtons.some(button => button.props.testID === 'rooms-list-view-search')).toBe(false); + + expect(options.headerSearchBarOptions.placement).toBe('stacked'); + expect(options.headerSearchBarOptions.ref.current).toBeNull(); - searchButton?.props.onPress(); + options.headerSearchBarOptions.onFocus(); expect(mockStartSearch).toHaveBeenCalledTimes(1); + + options.headerSearchBarOptions.onChangeText({ nativeEvent: { text: 'general' } }); + expect(mockSearch).toHaveBeenCalledWith('general'); + + options.headerSearchBarOptions.onCancelButtonPress(); + expect(mockStopSearch).toHaveBeenCalledTimes(1); }); - it('lets search mode render SearchHeader with its close button ahead of the native bar', () => { - const renderWithSearch = () => - renderHook(() => useHeader(), { - wrapper: ({ children }: { children: ReactElement }) => ( - - {children} - - ) - }); + it('clears the system search bar once search stops', () => { + let setSearchEnabled: (value: boolean) => void = () => {}; + const wrapper = ({ children }: { children: ReactElement }) => { + const [searchEnabled, setter] = useState(true); + setSearchEnabled = setter; + return ( + {children} + ); + }; + + renderHook(() => useHeader(), { wrapper }); - renderWithSearch(); + const clearText = jest.fn(); + mockSetOptions.mock.calls[0][0].headerSearchBarOptions.ref.current = { clearText }; - const options = mockSetOptions.mock.calls[0][0]; - expect(options.headerLargeTitle).toBeUndefined(); - expect(typeof options.headerTitle).toBe('function'); - expect(typeof options.headerLeft).toBe('function'); + act(() => setSearchEnabled(false)); + + expect(clearText).toHaveBeenCalledTimes(1); }); it('falls back to the JS header on Android', () => { diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 02a3bb160a6..843e00ad31d 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -1,7 +1,8 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native'; -import { useCallback, useContext, useLayoutEffect, useRef, useState } from 'react'; +import { useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { InteractionManager } from 'react-native'; import { type KeyboardFocus } from 'react-native-external-keyboard'; +import { type SearchBarCommands } from 'react-native-screens'; import { showActionSheetRef } from '~/containers/ActionSheet'; import { type TIconsName } from '~/containers/CustomIcon'; @@ -65,6 +66,7 @@ export const useHeader = () => { const [options, setOptions] = useState(null); const isAccessibilityNavigationEnabled = useIsAccessibilityNavigationEnabled(); const drawerButtonRef = useRef(null); + const searchBarRef = useRef(null); const supportedVersionsStatus = useAppSelector(state => state.supportedVersions.status); const requirePasswordChange = useAppSelector(state => getUserSelector(state).requirePasswordChange); const isMasterDetail = useMasterDetail(); @@ -146,7 +148,7 @@ export const useHeader = () => { /> ); - if (searchEnabled) { + if (searchEnabled && !useNativeBar) { const searchOptions = { headerLeft: () => ( @@ -165,15 +167,6 @@ export const useHeader = () => { if (useNativeBar) { const { visible, overflow } = splitHeaderRightActions([ - { - key: 'search', - present: true, - iconName: 'search', - accessibilityLabel: i18n.t('Search'), - testID: 'rooms-list-view-search', - disabled, - onPress: startSearch - }, { key: 'create', present: canCreateRoom, @@ -207,6 +200,14 @@ export const useHeader = () => { headerLargeTitle: true, headerTitle: nativeBarTitle, headerLeft, + headerSearchBarOptions: { + ref: searchBarRef, + placement: 'stacked', + placeholder: i18n.t('Search'), + onFocus: startSearch, + onChangeText: (event: { nativeEvent: { text: string } }) => search(event.nativeEvent.text), + onCancelButtonPress: stopSearch + }, headerRight: () => ( {[ @@ -309,6 +310,12 @@ export const useHeader = () => { search ]); + useEffect(() => { + if (useNativeBar && !searchEnabled) { + searchBarRef.current?.clearText(); + } + }, [useNativeBar, searchEnabled]); + // The rooms list header persists across native-stack navigation, so autoFocus (mount-only) // won't re-fire on back-return or after the list/banner render asynchronously. Re-assert focus // on the drawer button every time the screen is focused so external-keyboard/screen-reader From 3359a5004a9e9ffcc8898ac7ab428e90bf0627ca Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 18:16:43 -0300 Subject: [PATCH 031/120] feat(ios): workspaces row in the phone drawer --- .maestro/helpers/open-servers-list.yaml | 29 +++++++++++++++ .maestro/tests/assorted/changeserver.yaml | 35 +++---------------- .maestro/tests/assorted/deeplink.yaml | 14 ++------ .maestro/tests/assorted/delete-server.yaml | 21 ++--------- .../tests/room/servers-list-small-screen.yaml | 7 +--- app/lib/methods/helpers/deviceInfo.ts | 2 ++ .../RightButtons/OmnichannelRightButtons.tsx | 5 ++- .../RightButtons/RoomRightButtons.tsx | 5 ++- .../RightButtons/ThreadRightButtons.tsx | 5 ++- .../OmnichannelRightButtons.test.tsx | 3 ++ .../__tests__/RoomRightButtons.test.tsx | 3 ++ .../__tests__/ThreadRightButtons.test.tsx | 3 ++ .../components/RoomBodyFocusAnchor.tsx | 4 +-- .../__tests__/RightButtons.test.tsx | 3 +- .../hooks/__tests__/useHeader.test.tsx | 3 ++ app/views/RoomView/hooks/useHeader.tsx | 7 ++-- .../hooks/__tests__/useHeader.test.tsx | 3 +- app/views/RoomsListView/hooks/useHeader.tsx | 14 ++++---- .../SidebarView/components/Workspaces.tsx | 35 +++++++++++++++++++ app/views/SidebarView/index.tsx | 3 ++ 20 files changed, 113 insertions(+), 91 deletions(-) create mode 100644 .maestro/helpers/open-servers-list.yaml create mode 100644 app/views/SidebarView/components/Workspaces.tsx diff --git a/.maestro/helpers/open-servers-list.yaml b/.maestro/helpers/open-servers-list.yaml new file mode 100644 index 00000000000..366cc97c2b6 --- /dev/null +++ b/.maestro/helpers/open-servers-list.yaml @@ -0,0 +1,29 @@ +appId: ${APP_ID} +name: Open servers list +tags: + - 'util' + +--- +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'rooms-list-view-sidebar' + - extendedWaitUntil: + visible: + id: 'sidebar-workspaces' + timeout: 60000 + - tapOn: + id: 'sidebar-workspaces' + +- runFlow: + when: + platform: Android + commands: + - extendedWaitUntil: + visible: + id: 'rooms-list-header-servers-list-button' + timeout: 60000 + - tapOn: + id: 'rooms-list-header-servers-list-button' diff --git a/.maestro/tests/assorted/changeserver.yaml b/.maestro/tests/assorted/changeserver.yaml index 897eabe427d..6ae06a0747b 100644 --- a/.maestro/tests/assorted/changeserver.yaml +++ b/.maestro/tests/assorted/changeserver.yaml @@ -18,12 +18,7 @@ tags: PASSWORD: ${output.user.password} # should open the servers list, have the server add button and create workspace button -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: visible: id: 'rooms-list-header-servers-list' @@ -57,12 +52,7 @@ tags: timeout: 60000 # should add server and create new user -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: visible: id: 'rooms-list-header-servers-list' @@ -107,14 +97,9 @@ tags: server: ${output.data.alternateServer} # should change back to main server -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 - waitForAnimationToEnd: timeout: 5000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: visible: id: 'rooms-list-header-servers-list' @@ -154,12 +139,7 @@ tags: server: ${output.data.server} # should test swipe to delete on alternate server -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: visible: id: 'server-item-${output.data.alternateServer}' @@ -198,12 +178,7 @@ tags: # verify alternate server is deleted - waitForAnimationToEnd: timeout: 500 -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: notVisible: id: 'server-item-${output.data.alternateServer}' diff --git a/.maestro/tests/assorted/deeplink.yaml b/.maestro/tests/assorted/deeplink.yaml index 50cd775b78e..5a4b7d0f67d 100644 --- a/.maestro/tests/assorted/deeplink.yaml +++ b/.maestro/tests/assorted/deeplink.yaml @@ -146,12 +146,7 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: visible: id: 'server-item-${output.data.alternateServer}' @@ -227,12 +222,7 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: visible: id: 'server-item-${output.data.alternateServer}' diff --git a/.maestro/tests/assorted/delete-server.yaml b/.maestro/tests/assorted/delete-server.yaml index 7e63bee1ea9..f48a289776d 100644 --- a/.maestro/tests/assorted/delete-server.yaml +++ b/.maestro/tests/assorted/delete-server.yaml @@ -22,12 +22,7 @@ tags: server: ${output.data.server} # should add server -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: visible: id: 'rooms-list-header-server-add' @@ -67,12 +62,7 @@ tags: server: ${output.data.alternateServer} # should delete server -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: visible: id: 'server-item-${output.data.server}' @@ -109,12 +99,7 @@ tags: retryTapIfNoChange: true - waitForAnimationToEnd: timeout: 300 -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - extendedWaitUntil: visible: id: 'rooms-list-header-servers-list' diff --git a/.maestro/tests/room/servers-list-small-screen.yaml b/.maestro/tests/room/servers-list-small-screen.yaml index 685822b104b..ed2c7216818 100644 --- a/.maestro/tests/room/servers-list-small-screen.yaml +++ b/.maestro/tests/room/servers-list-small-screen.yaml @@ -20,12 +20,7 @@ tags: # should fully render the Workspaces action sheet on small screens # (regression for small-screen ServersList — the Add Server button was cut off on iPhone SE) -- extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 -- tapOn: - id: 'rooms-list-header-servers-list-button' +- runFlow: '../../helpers/open-servers-list.yaml' - waitForAnimationToEnd - extendedWaitUntil: visible: diff --git a/app/lib/methods/helpers/deviceInfo.ts b/app/lib/methods/helpers/deviceInfo.ts index 9cb7ac727ef..ce331833284 100644 --- a/app/lib/methods/helpers/deviceInfo.ts +++ b/app/lib/methods/helpers/deviceInfo.ts @@ -16,3 +16,5 @@ export const supportSystemTheme = (): boolean => { // Tablet info export const isTablet = DeviceInfo.isTablet(); + +export const hasNativeHeaderBar = isIOS && !isTablet; diff --git a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx b/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx index 296acb4da2e..fe91bca5cc1 100644 --- a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx @@ -4,7 +4,7 @@ import { useNavigation } from '@react-navigation/native'; import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; import i18n from '~/i18n'; -import { isIOS, isTablet, showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar, showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; @@ -25,7 +25,6 @@ interface IOmnichannelRightButtonsProps { } export const OmnichannelRightButtons = ({ rid, roomStore }: IOmnichannelRightButtonsProps): ReactElement => { - const useNativeBar = isIOS && !isTablet; const navigation = useNavigation(); const isMasterDetail = useMasterDetail(); const { showActionSheet } = useActionSheet(); @@ -93,7 +92,7 @@ export const OmnichannelRightButtons = ({ rid, roomStore }: IOmnichannelRightBut showActionSheet({ options }); }; - if (useNativeBar) { + if (hasNativeHeaderBar) { return ; } diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx index 91189f6e57a..089c4777bd0 100644 --- a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx @@ -1,6 +1,6 @@ import { type ReactElement } from 'react'; -import { isIOS, isTablet } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import { type RoomStore } from '~/views/RoomView/definitions'; import { RoomRightButtonsLegacy } from './RoomRightButtonsLegacy'; import { RoomRightButtonsNative } from './RoomRightButtonsNative'; @@ -12,10 +12,9 @@ interface IRoomRightButtonsProps { } export const RoomRightButtons = ({ rid, roomStore }: IRoomRightButtonsProps): ReactElement => { - const useNativeBar = isIOS && !isTablet; const data = useRoomRightButtonsData(rid, roomStore); - if (useNativeBar) { + if (hasNativeHeaderBar) { return ; } diff --git a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx b/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx index 1d813e74ddd..822346c6bc5 100644 --- a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx @@ -1,6 +1,6 @@ import { type ReactElement } from 'react'; -import { isIOS, isTablet } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; @@ -14,7 +14,6 @@ interface IThreadRightButtonsProps { } export const ThreadRightButtons = ({ tmid }: IThreadRightButtonsProps): ReactElement => { - const useNativeBar = isIOS && !isTablet; const userId = useAppSelector(state => getUserSelector(state).id); const isFollowingThread = useThreadFollowing(tmid, userId); @@ -23,7 +22,7 @@ export const ThreadRightButtons = ({ tmid }: IThreadRightButtonsProps): ReactEle toggleFollowThread(tmid, isFollowingThread); }; - if (useNativeBar) { + if (hasNativeHeaderBar) { return ; } diff --git a/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx index 06cd46f91f3..29bdce2cdc7 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx @@ -60,6 +60,9 @@ jest.mock('~/lib/methods/helpers', () => ({ }, get isTablet() { return mockIsTablet; + }, + get hasNativeHeaderBar() { + return mockIsIOS && !mockIsTablet; } })); diff --git a/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx index 8647d1b02c5..dbd6d870c05 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx @@ -32,6 +32,9 @@ jest.mock('~/lib/methods/helpers', () => ({ }, get isTablet() { return mockIsTablet; + }, + get hasNativeHeaderBar() { + return mockIsIOS && !mockIsTablet; } })); diff --git a/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx index 8b919b27ac3..f82f61a5d9d 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx @@ -23,6 +23,9 @@ jest.mock('~/lib/methods/helpers', () => ({ }, get isTablet() { return mockIsTablet; + }, + get hasNativeHeaderBar() { + return mockIsIOS && !mockIsTablet; } })); diff --git a/app/views/RoomView/components/RoomBodyFocusAnchor.tsx b/app/views/RoomView/components/RoomBodyFocusAnchor.tsx index ea1b4ddf1cc..1d48596a08b 100644 --- a/app/views/RoomView/components/RoomBodyFocusAnchor.tsx +++ b/app/views/RoomView/components/RoomBodyFocusAnchor.tsx @@ -2,7 +2,7 @@ import { type ReactNode } from 'react'; import { StyleSheet } from 'react-native'; import { KeyboardFocusView } from 'react-native-external-keyboard'; -import { isIOS, isTablet } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; import { useIsAccessibilityNavigationEnabled } from '~/lib/hooks/useIsAccessibilityNavigationEnabled'; import { fromSubscription, useRoomStore } from '../stores/RoomStoreContext'; @@ -22,7 +22,7 @@ export const RoomBodyFocusAnchor = ({ children }: IRoomBodyFocusAnchorProps) => const disabled = useRoomStore(fromSubscription(isInviteSubscription, false)); const accessibilityNavigationEnabled = useIsAccessibilityNavigationEnabled(); - if (!isIOS || isTablet) { + if (!hasNativeHeaderBar) { return <>{children}; } diff --git a/app/views/RoomView/components/__tests__/RightButtons.test.tsx b/app/views/RoomView/components/__tests__/RightButtons.test.tsx index 61b0e4401aa..58ffd041956 100644 --- a/app/views/RoomView/components/__tests__/RightButtons.test.tsx +++ b/app/views/RoomView/components/__tests__/RightButtons.test.tsx @@ -20,7 +20,8 @@ jest.mock('~/lib/methods/helpers', () => ({ ...jest.requireActual('~/lib/methods/helpers'), getRoomTitle: () => 'Room Title', isGroupChat: () => false, - isTablet: true + isTablet: true, + hasNativeHeaderBar: false })); jest.mock('~/lib/hooks/useVideoConf', () => ({ useVideoConf: () => ({ showInitCallActionSheet: jest.fn(), callEnabled: false, disabledTooltip: false }) diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index 6f1d68aeaa1..7d806b2c8fc 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -22,6 +22,9 @@ jest.mock('~/lib/methods/helpers', () => ({ }, get isTablet() { return mockIsTablet; + }, + get hasNativeHeaderBar() { + return mockIsIOS && !mockIsTablet; } })); jest.mock('~/lib/methods/isInviteSubscription', () => ({ diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index 0d1d13f58c9..9e5fde1988e 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -6,7 +6,7 @@ import { useStore } from 'zustand'; import RoomHeader from '~/containers/RoomHeader'; import I18n from '~/i18n'; -import { getRoomTitle, isGroupChat, isIOS, isTablet } from '~/lib/methods/helpers'; +import { getRoomTitle, hasNativeHeaderBar, isGroupChat } from '~/lib/methods/helpers'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; @@ -70,7 +70,6 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa const goRoomActionsView = useGoRoomActionsView(roomStore); const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading); const connected = useAppSelector(state => state.meteor.connected); - const useNativeBar = isIOS && !isTablet; useLayoutEffect(() => { if (!rid) { @@ -89,7 +88,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa return; } - if (useNativeBar) { + if (hasNativeHeaderBar) { const title = connecting ? I18n.t('Connecting') : !connected ? I18n.t('Waiting_for_network') : headerFields.title; navigation.setOptions({ title }); return; @@ -116,5 +115,5 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa /> ) }); - }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView, connecting, connected, useNativeBar]); + }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView, connecting, connected]); }; diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 23c89a93f02..49930bde797 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -19,7 +19,8 @@ jest.mock('~/lib/methods/helpers', () => { ...jest.requireActual('~/lib/methods/helpers') }, { isIOS: { get: () => mockIsIOS, configurable: true }, - isTablet: { get: () => mockIsTablet, configurable: true } + isTablet: { get: () => mockIsTablet, configurable: true }, + hasNativeHeaderBar: { get: () => mockIsIOS && !mockIsTablet, configurable: true } } ) ); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 843e00ad31d..61580cea44b 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -12,7 +12,7 @@ import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useIsAccessibilityNavigationEnabled } from '~/lib/hooks/useIsAccessibilityNavigationEnabled'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { usePermissions } from '~/lib/hooks/usePermissions'; -import { isIOS, isTablet } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar, isTablet } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { getUserSelector } from '~/selectors/login'; import { useTheme } from '~/theme'; @@ -129,8 +129,7 @@ export const useHeader = () => { } }, [isMasterDetail, navigation]); - const useNativeBar = isIOS && !isTablet; - const nativeBarTitle = useNativeBarTitle(useNativeBar); + const nativeBarTitle = useNativeBarTitle(hasNativeHeaderBar); useLayoutEffect(() => { const headerLeft = () => ( @@ -148,7 +147,7 @@ export const useHeader = () => { /> ); - if (searchEnabled && !useNativeBar) { + if (searchEnabled && !hasNativeHeaderBar) { const searchOptions = { headerLeft: () => ( @@ -165,7 +164,7 @@ export const useHeader = () => { return; } - if (useNativeBar) { + if (hasNativeHeaderBar) { const { visible, overflow } = splitHeaderRightActions([ { key: 'create', @@ -299,7 +298,6 @@ export const useHeader = () => { colors, canCreateRoom, searchEnabled, - useNativeBar, nativeBarTitle, goDirectory, navigateToPushTroubleshootView, @@ -311,10 +309,10 @@ export const useHeader = () => { ]); useEffect(() => { - if (useNativeBar && !searchEnabled) { + if (hasNativeHeaderBar && !searchEnabled) { searchBarRef.current?.clearText(); } - }, [useNativeBar, searchEnabled]); + }, [searchEnabled]); // The rooms list header persists across native-stack navigation, so autoFocus (mount-only) // won't re-fire on back-return or after the list/banner render asynchronously. Re-assert focus diff --git a/app/views/SidebarView/components/Workspaces.tsx b/app/views/SidebarView/components/Workspaces.tsx new file mode 100644 index 00000000000..316918c4efb --- /dev/null +++ b/app/views/SidebarView/components/Workspaces.tsx @@ -0,0 +1,35 @@ +import { memo } from 'react'; +import { type DrawerNavigationProp } from '@react-navigation/drawer'; + +import * as List from '~/containers/List'; +import { showActionSheetRef } from '~/containers/ActionSheet'; +import { useAppSelector } from '~/lib/hooks/useAppSelector'; +import { type DrawerParamList } from '~/stacks/types'; +import ServersList from '~/views/RoomsListView/components/ServersList'; + +const formatServerHost = (server: string) => server.replace(/(^\w+:|^)\/\//, ''); + +const Workspaces = ({ navigation }: { navigation: DrawerNavigationProp }) => { + const server = useAppSelector(state => state.server.server); + + const onPress = () => { + navigation.closeDrawer(); + showActionSheetRef({ children: , enableContentPanningGesture: false }); + }; + + return ( + <> + } + onPress={onPress} + testID='sidebar-workspaces' + /> + + + ); +}; + +export default memo(Workspaces); diff --git a/app/views/SidebarView/index.tsx b/app/views/SidebarView/index.tsx index c65c6ba18ef..a6dadafcb50 100644 --- a/app/views/SidebarView/index.tsx +++ b/app/views/SidebarView/index.tsx @@ -3,11 +3,13 @@ import { type DrawerNavigationProp } from '@react-navigation/drawer'; import { ScrollView, View } from 'react-native'; import scrollPersistTaps from '~/lib/methods/helpers/scrollPersistTaps'; +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import styles from './styles'; import { type DrawerParamList } from '~/stacks/types'; import SupportedVersionsWarnItem from './components/SupportedVersionsWarnItem'; import CustomStatus from './components/CustomStatus'; import Stacks from './components/Stacks'; +import Workspaces from './components/Workspaces'; import Admin from './components/Admin'; import Profile from './components/Profile'; @@ -29,6 +31,7 @@ const SidebarView = ({ navigation }: { navigation: DrawerNavigationProp + {hasNativeHeaderBar ? : null} From 52864c64fe5555d576f73932c3850e9624c17344 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 18:16:55 -0300 Subject: [PATCH 032/120] feat(ios): system search bar across list screens --- .maestro/tests/teams/team.yaml | 27 +++++++--- app/views/CannedResponsesListView/index.tsx | 31 +++++++++-- app/views/DiscussionsView/index.tsx | 27 +++++++++- app/views/ShareListView/index.tsx | 38 ++++++++++++-- app/views/TeamChannelsView.tsx | 57 +++++++++++++++++++-- app/views/ThreadMessagesView/index.tsx | 52 +++++++++++++++++-- 6 files changed, 210 insertions(+), 22 deletions(-) diff --git a/.maestro/tests/teams/team.yaml b/.maestro/tests/teams/team.yaml index 91274b1131c..72325d23b02 100644 --- a/.maestro/tests/teams/team.yaml +++ b/.maestro/tests/teams/team.yaml @@ -46,15 +46,30 @@ tags: label: Should navigate to team channels view # Team Channels Header -- assertVisible: - id: room-header - label: Should have actions button +- runFlow: + when: + platform: android + commands: + - assertVisible: + id: room-header + label: Should have actions button - assertVisible: id: team-channels-view-create label: Should have team channels button -- assertVisible: - id: team-channels-view-search - label: Should have search button +- runFlow: + when: + platform: iOS + commands: + - assertVisible: + id: magnifyingglass + label: Should have system search bar +- runFlow: + when: + platform: android + commands: + - assertVisible: + id: team-channels-view-search + label: Should have search button # Team Channels Header Usage - tapOn: diff --git a/app/views/CannedResponsesListView/index.tsx b/app/views/CannedResponsesListView/index.tsx index fe31df31ffa..9e6b5c6e5fb 100644 --- a/app/views/CannedResponsesListView/index.tsx +++ b/app/views/CannedResponsesListView/index.tsx @@ -1,8 +1,9 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { FlatList } from 'react-native'; import { type RouteProp } from '@react-navigation/native'; import { type NativeStackNavigationOptions, type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { type SearchBarCommands } from 'react-native-screens'; import database from '~/lib/database'; import I18n from '~/i18n'; @@ -22,7 +23,7 @@ import DepartmentFilter from './DepartmentFilter'; import styles from './styles'; import { type ICannedResponse } from '~/definitions/ICannedResponse'; import { type ChatsStackParamList } from '~/stacks/types'; -import { useDebounce } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar, useDebounce } from '~/lib/methods/helpers'; import { getListCannedResponse, getDepartments } from '~/lib/services/restApi'; import { type ILivechatDepartment } from '~/definitions/ILivechatDepartment'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; @@ -69,6 +70,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView const { theme } = useTheme(); const isMasterDetail = useMasterDetail(); const { bottom } = useSafeAreaInsets(); + const searchBarRef = useRef(null); const getRoomFromDb = async () => { const { rid } = route.params; @@ -209,7 +211,30 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView await handleGetListCannedResponse({ text: searchText, department: scope, depId: departmentId, debounced: false }); }; - const getHeader = () => { + const getHeader = (): NativeStackNavigationOptions => { + if (hasNativeHeaderBar) { + return { + headerLargeTitle: true, + headerLeft: () => null, + headerTitle: I18n.t('Canned_Responses'), + headerSearchBarOptions: { + ref: searchBarRef, + placement: 'stacked', + placeholder: I18n.t('Search'), + onChangeText: (event: { nativeEvent: { text: string } }) => onChangeText(event.nativeEvent.text), + onCancelButtonPress: () => { + onChangeText(''); + searchBarRef.current?.clearText(); + } + }, + headerRight: () => ( + + + + ) + }; + } + if (isSearching) { return { headerLeft: () => ( diff --git a/app/views/DiscussionsView/index.tsx b/app/views/DiscussionsView/index.tsx index b13c0e23754..3ec429e544c 100644 --- a/app/views/DiscussionsView/index.tsx +++ b/app/views/DiscussionsView/index.tsx @@ -3,6 +3,7 @@ import { FlatList, StyleSheet } from 'react-native'; import { type NativeStackNavigationOptions, type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { type SearchBarCommands } from 'react-native-screens'; import { textInputDebounceTime } from '~/lib/constants/debounceConfig'; import { type IMessageFromServer, type TThreadModel } from '~/definitions'; @@ -10,7 +11,7 @@ import { type ChatsStackParamList } from '~/stacks/types'; import ActivityIndicator from '~/containers/ActivityIndicator'; import I18n from '~/i18n'; import log from '~/lib/methods/helpers/log'; -import { isIOS, useDebounce } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar, isIOS, useDebounce } from '~/lib/methods/helpers'; import SafeAreaView from '~/containers/SafeAreaView'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import * as List from '~/containers/List'; @@ -49,6 +50,7 @@ const DiscussionsView = () => { const total = useRef(0); const searchText = useRef(''); const offset = useRef(0); + const searchBarRef = useRef(null); const { colors } = useTheme(); @@ -95,6 +97,7 @@ const DiscussionsView = () => { setSearch([]); searchText.current = ''; offset.current = 0; + searchBarRef.current?.clearText(); }; const onSearchPress = () => { @@ -103,6 +106,28 @@ const DiscussionsView = () => { const setHeader = () => { let options: Partial; + + if (hasNativeHeaderBar) { + options = { + headerLargeTitle: true, + headerLeft: undefined, + headerTitle: I18n.t('Discussions'), + headerSearchBarOptions: { + ref: searchBarRef, + placement: 'stacked', + placeholder: I18n.t('Search'), + onFocus: onSearchPress, + onChangeText: (event: { nativeEvent: { text: string } }) => onSearchChangeText(event.nativeEvent.text), + onCancelButtonPress: onCancelSearchPress + }, + headerRight: () => null + }; + if (isMasterDetail) { + options.headerLeft = () => ; + } + return options; + } + if (isSearching) { options = { headerLeft: () => ( diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index 8944ab98fa8..4d2964d22a4 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -7,7 +7,8 @@ import * as mime from 'react-native-mime-types'; import { dequal } from 'dequal'; import { Q } from '@nozbe/watermelondb'; import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; -import { Component } from 'react'; +import { type SearchBarCommands } from 'react-native-screens'; +import { Component, createRef } from 'react'; import database from '~/lib/database'; import I18n from '~/i18n'; @@ -24,7 +25,7 @@ import { getSubscriptionSearchClause } from '~/lib/database/utils'; import styles from './styles'; import { type IApplicationState, RootEnum, type TServerModel, type TSubscriptionModel } from '~/definitions'; import { type ShareInsideStackParamList } from '~/definitions/navigationTypes'; -import { getRoomAvatar, isAndroid, isIOS } from '~/lib/methods/helpers'; +import { getRoomAvatar, hasNativeHeaderBar, isAndroid, isIOS } from '~/lib/methods/helpers'; import { shareSetParams } from '~/actions/share'; import { appStart } from '~/actions/app'; @@ -78,6 +79,8 @@ class ShareListView extends Component { private backHandler: NativeEventSubscription | undefined; + private searchBarRef = createRef(); + constructor(props: IShareListViewProps) { super(props); this.state = { @@ -145,7 +148,7 @@ class ShareListView extends Component { if (previousProps.connecting !== connecting && connecting) { this.setState({ chats: [], searchResults: [], searching: false, searchText: '' }); } - if (previousState.searching !== searching) { + if (!hasNativeHeaderBar && previousState.searching !== searching) { this.setHeader(); } } @@ -193,6 +196,30 @@ class ShareListView extends Component { const { searching } = this.state; const { navigation } = this.props; + if (hasNativeHeaderBar) { + navigation.setOptions({ + headerLargeTitle: true, + headerLeft: () => ( + + + + ), + headerTitle: I18n.t('Send_to'), + headerSearchBarOptions: this.airGappedReadOnly + ? undefined + : { + ref: this.searchBarRef, + placement: 'stacked', + placeholder: I18n.t('Search'), + onFocus: this.initSearch, + onChangeText: (event: { nativeEvent: { text: string } }) => this.search(event.nativeEvent.text), + onCancelButtonPress: this.cancelSearch + }, + headerRight: () => null + }); + return; + } + if (searching) { navigation.setOptions({ headerLeft: () => ( @@ -335,7 +362,10 @@ class ShareListView extends Component { }; cancelSearch = () => { - this.setState({ searching: false, searchResults: [], searchText: '' }, () => this.setHeader()); + this.setState({ searching: false, searchResults: [], searchText: '' }, () => { + this.setHeader(); + this.searchBarRef.current?.clearText(); + }); Keyboard.dismiss(); }; diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 43fbdd2f148..00b35003206 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -3,7 +3,8 @@ import { type NativeStackNavigationOptions } from '@react-navigation/native-stac import { Alert, FlatList, Keyboard, PixelRatio } from 'react-native'; import { connect } from 'react-redux'; import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; -import { Component } from 'react'; +import { type SearchBarCommands } from 'react-native-screens'; +import { Component, createRef } from 'react'; import { deleteRoom } from '../actions/room'; import { DisplayMode } from '../lib/constants/constantDisplayMode'; @@ -30,7 +31,15 @@ import { withTheme } from '../theme'; import { goRoom } from '../lib/methods/helpers/goRoom'; import { showErrorAlert } from '../lib/methods/helpers/info'; import log, { events, logEvent } from '../lib/methods/helpers/log'; -import { getRoomAvatar, getRoomTitle, hasPermission, debounce, isIOS, compareServerVersion } from '../lib/methods/helpers'; +import { + getRoomAvatar, + getRoomTitle, + hasPermission, + debounce, + hasNativeHeaderBar, + isIOS, + compareServerVersion +} from '../lib/methods/helpers'; import { getRoomInfo, getTeamListRoom, updateTeamRoom, removeTeamRoom } from '../lib/services/restApi'; const API_FETCH_COUNT = 25; @@ -96,6 +105,8 @@ class TeamChannelsView extends Component(); + constructor(props: ITeamChannelsViewProps) { super(props); this.teamChannels = []; @@ -220,6 +231,37 @@ class TeamChannelsView extends Component this.onSearchChangeText(event.nativeEvent.text), + onCancelButtonPress: this.onCancelSearchPress + }, + headerRight: () => ( + + {showCreate ? ( + + navigation.navigate('AddChannelTeamView', { teamId: this.teamId, rid: this.team.rid, t: this.team.t as any }) + } + /> + ) : null} + + ) + }; + navigation.setOptions(options); + return; + } + if (isSearching) { const options: NativeStackNavigationOptions = { headerLeft: () => ( @@ -261,7 +303,11 @@ class TeamChannelsView extends Component { logEvent(events.TC_SEARCH); - this.setState({ isSearching: true }, () => this.setHeader()); + this.setState({ isSearching: true }, () => { + if (!hasNativeHeaderBar) { + this.setHeader(); + } + }); }; onSearchChangeText = debounce((searchText: string) => { @@ -297,7 +343,10 @@ class TeamChannelsView extends Component { - this.setHeader(); + if (!hasNativeHeaderBar) { + this.setHeader(); + } + this.searchBarRef.current?.clearText(); } ); }; diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 978fc3e1941..16e0244493e 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -5,7 +5,8 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { type NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { type Observable, type Subscription } from 'rxjs'; import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; -import { Component } from 'react'; +import { type SearchBarCommands } from 'react-native-screens'; +import { Component, createRef } from 'react'; import { showActionSheetRef } from '~/containers/ActionSheet'; import { CustomIcon } from '~/containers/CustomIcon'; @@ -38,7 +39,7 @@ import { type TSubscriptionModel, type TThreadModel } from '~/definitions'; -import { getUidDirectMessage, debounce, isIOS } from '~/lib/methods/helpers'; +import { getUidDirectMessage, debounce, hasNativeHeaderBar, isIOS } from '~/lib/methods/helpers'; import { getSyncThreadsList, getThreadsList } from '~/lib/services/restApi'; import { toggleFollowThread as toggleFollowThreadService } from '~/lib/methods/toggleFollowThread'; import UserPreferences from '~/lib/methods/userPreferences'; @@ -80,6 +81,8 @@ class ThreadMessagesView extends Component; + private searchBarRef = createRef(); + constructor(props: IThreadMessagesViewProps) { super(props); this.mounted = false; @@ -119,6 +122,40 @@ class ThreadMessagesView extends Component this.onSearchChangeText(event.nativeEvent.text), + onCancelButtonPress: this.onCancelSearchPress + }, + headerRight: () => ( + + + currentFilter !== Filter.All ? ( + + ) : null + } + /> + + ) + }; + if (isMasterDetail) { + options.headerLeft = () => ; + } + return options; + } + if (isSearching) { return { headerLeft: () => ( @@ -380,14 +417,21 @@ class ThreadMessagesView extends Component { - this.setState({ isSearching: true }, () => this.setHeader()); + this.setState({ isSearching: true }, () => { + if (!hasNativeHeaderBar) { + this.setHeader(); + } + }); }; onCancelSearchPress = () => { this.setState({ isSearching: false, searchText: '' }, () => { const { subscription } = this.state; - this.setHeader(); + if (!hasNativeHeaderBar) { + this.setHeader(); + } this.subscribeMessages(subscription); + this.searchBarRef.current?.clearText(); }); }; From 0c8b84e6736c62a50ace252b1196658b6aa9dda1 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 19:02:41 -0300 Subject: [PATCH 033/120] test(maestro): retarget header selectors for the native iOS bar --- .maestro/helpers/open-room-actions.yaml | 27 ++++++++++++++++++ .maestro/tests/room/discussion.yaml | 17 ++--------- .maestro/tests/room/room-actions.yaml | 28 +++---------------- .maestro/tests/room/search-member.yaml | 7 +---- .../room/utils/navigate-to-info-view.yaml | 11 +------- .../room/utils/navigate-to-room-info.yaml | 11 +------- 6 files changed, 36 insertions(+), 65 deletions(-) create mode 100644 .maestro/helpers/open-room-actions.yaml diff --git a/.maestro/helpers/open-room-actions.yaml b/.maestro/helpers/open-room-actions.yaml new file mode 100644 index 00000000000..9bdecd218be --- /dev/null +++ b/.maestro/helpers/open-room-actions.yaml @@ -0,0 +1,27 @@ +appId: ${APP_ID} +name: Open room actions +tags: + - 'util' + +--- +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'room-view-header-more' + - tapOn: + id: 'room-view-header-room-actions' +- runFlow: + when: + platform: Android + commands: + - assertVisible: + id: 'room-header' + - tapOn: + id: 'room-header' + retryTapIfNoChange: true +- extendedWaitUntil: + visible: + id: 'room-actions-view' + timeout: 60000 diff --git a/.maestro/tests/room/discussion.yaml b/.maestro/tests/room/discussion.yaml index 02d95100d3c..b784f566d38 100644 --- a/.maestro/tests/room/discussion.yaml +++ b/.maestro/tests/room/discussion.yaml @@ -188,15 +188,7 @@ tags: timeout: 60000 # Check RoomActionsView render -- assertVisible: - id: room-header -- tapOn: - id: room-header - retryTapIfNoChange: true -- extendedWaitUntil: - visible: - id: 'room-actions-view' - timeout: 60000 +- runFlow: '../../helpers/open-room-actions.yaml' # should have room actions screen - assertVisible: @@ -279,12 +271,7 @@ tags: ROOM: ${output.room.name} # should navigate to DiscussionsView -- extendedWaitUntil: - visible: - id: 'room-header' - timeout: 60000 -- tapOn: - id: 'room-header' +- runFlow: '../../helpers/open-room-actions.yaml' - extendedWaitUntil: visible: id: 'room-actions-discussions' diff --git a/.maestro/tests/room/room-actions.yaml b/.maestro/tests/room/room-actions.yaml index cc460b09577..2b25bff8672 100644 --- a/.maestro/tests/room/room-actions.yaml +++ b/.maestro/tests/room/room-actions.yaml @@ -129,12 +129,7 @@ tags: notVisible: text: '.*Message Starred.*' timeout: 60000 -- tapOn: - id: 'room-header' -- extendedWaitUntil: - visible: - id: 'room-actions-view' - timeout: 60000 +- runFlow: '../../helpers/open-room-actions.yaml' - tapOn: id: 'room-actions-starred' retryTapIfNoChange: true @@ -177,12 +172,7 @@ tags: notVisible: text: '.*Message Unstarred.*' timeout: 60000 -- tapOn: - id: 'room-header' -- extendedWaitUntil: - visible: - id: 'room-actions-view' - timeout: 60000 +- runFlow: '../../helpers/open-room-actions.yaml' - tapOn: id: 'room-actions-starred' retryTapIfNoChange: true @@ -230,12 +220,7 @@ tags: visible: text: '.*Pinned a message:.*' timeout: 60000 -- tapOn: - id: 'room-header' -- extendedWaitUntil: - visible: - id: 'room-actions-view' - timeout: 60000 +- runFlow: '../../helpers/open-room-actions.yaml' - tapOn: Pinned - extendedWaitUntil: visible: @@ -705,12 +690,7 @@ tags: timeout: 60000 # should navigate to direct message -- tapOn: - id: 'room-header' -- extendedWaitUntil: - visible: - id: 'room-actions-view' - timeout: 60000 +- runFlow: '../../helpers/open-room-actions.yaml' - tapOn: id: 'room-actions-members' - extendedWaitUntil: diff --git a/.maestro/tests/room/search-member.yaml b/.maestro/tests/room/search-member.yaml index 40c7e07a35f..baeda26a88b 100644 --- a/.maestro/tests/room/search-member.yaml +++ b/.maestro/tests/room/search-member.yaml @@ -20,12 +20,7 @@ tags: file: '../../helpers/navigate-to-room.yaml' env: ROOM: 'general' -- tapOn: - id: room-header -- extendedWaitUntil: - visible: - id: 'room-actions-view' - timeout: 60000 +- runFlow: '../../helpers/open-room-actions.yaml' - tapOn: id: 'room-actions-members' - extendedWaitUntil: diff --git a/.maestro/tests/room/utils/navigate-to-info-view.yaml b/.maestro/tests/room/utils/navigate-to-info-view.yaml index c2b33f0a718..71308b4d3dd 100644 --- a/.maestro/tests/room/utils/navigate-to-info-view.yaml +++ b/.maestro/tests/room/utils/navigate-to-info-view.yaml @@ -4,16 +4,7 @@ tags: - 'util' --- -- extendedWaitUntil: - visible: - id: room-header - timeout: 60000 -- tapOn: - id: room-header -- extendedWaitUntil: - visible: - id: room-actions-view - timeout: 60000 +- runFlow: '../../../helpers/open-room-actions.yaml' - assertVisible: id: room-actions-info - tapOn: diff --git a/.maestro/tests/room/utils/navigate-to-room-info.yaml b/.maestro/tests/room/utils/navigate-to-room-info.yaml index b440852dcbe..4c419f0c9d2 100644 --- a/.maestro/tests/room/utils/navigate-to-room-info.yaml +++ b/.maestro/tests/room/utils/navigate-to-room-info.yaml @@ -8,16 +8,7 @@ tags: file: '../../../helpers/search-and-navigate-room.yaml' env: ROOM: ${ROOM} -- extendedWaitUntil: - visible: - id: 'room-header' - timeout: 60000 -- tapOn: - id: 'room-header' -- extendedWaitUntil: - visible: - id: 'room-actions-view' - timeout: 60000 +- runFlow: '../../../helpers/open-room-actions.yaml' - extendedWaitUntil: visible: id: 'room-actions-info' From af404124af71fdbdc4a4e84ef071e154fe0147d6 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 19:10:38 -0300 Subject: [PATCH 034/120] test(maestro): finish header selector retargeting for the native iOS bar --- .maestro/helpers/navigate-to-recent-room.yaml | 22 ++++-- .maestro/tests/assorted/broadcast.yaml | 7 +- .maestro/tests/assorted/join-public-room.yaml | 8 +- .../tests/e2ee/flows/reset-keys-and-send.yaml | 6 ++ .maestro/tests/room/jump-to-message.yaml | 12 +++ .maestro/tests/room/threads.yaml | 12 ++- .maestro/tests/teams/convert-team.yaml | 7 +- .maestro/tests/teams/create-team.yaml | 3 +- .maestro/tests/teams/team.yaml | 75 +++++++++++++------ 9 files changed, 102 insertions(+), 50 deletions(-) diff --git a/.maestro/helpers/navigate-to-recent-room.yaml b/.maestro/helpers/navigate-to-recent-room.yaml index fdfc1057707..c67e713d4f7 100644 --- a/.maestro/helpers/navigate-to-recent-room.yaml +++ b/.maestro/helpers/navigate-to-recent-room.yaml @@ -8,12 +8,22 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- tapOn: - id: 'rooms-list-view-search' -- extendedWaitUntil: - visible: - id: 'rooms-list-view-search-input' - timeout: 60000 +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'magnifyingglass' +- runFlow: + when: + platform: android + commands: + - tapOn: + id: 'rooms-list-view-search' + - extendedWaitUntil: + visible: + id: 'rooms-list-view-search-input' + timeout: 60000 - extendedWaitUntil: visible: id: 'rooms-list-view-item-${ROOM}' diff --git a/.maestro/tests/assorted/broadcast.yaml b/.maestro/tests/assorted/broadcast.yaml index 254c6b78b1b..4529ed56c40 100644 --- a/.maestro/tests/assorted/broadcast.yaml +++ b/.maestro/tests/assorted/broadcast.yaml @@ -79,12 +79,7 @@ tags: - extendedWaitUntil: visible: id: 'room-view-title-${output.room}' -- tapOn: - id: 'room-header' -- extendedWaitUntil: - visible: - id: 'room-actions-view' - timeout: 60000 +- runFlow: '../../helpers/open-room-actions.yaml' - tapOn: id: 'room-actions-info' - extendedWaitUntil: diff --git a/.maestro/tests/assorted/join-public-room.yaml b/.maestro/tests/assorted/join-public-room.yaml index c7f82ad1bd9..b80b517cf9c 100644 --- a/.maestro/tests/assorted/join-public-room.yaml +++ b/.maestro/tests/assorted/join-public-room.yaml @@ -25,8 +25,12 @@ tags: id: room-view # should have actions button -- assertVisible: - id: room-header +- runFlow: + when: + platform: android + commands: + - assertVisible: + id: room-header # should have join - assertVisible: diff --git a/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml b/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml index f607b8d3957..820795d67af 100644 --- a/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml +++ b/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml @@ -39,6 +39,12 @@ env: visible: text: '.*The encryption keys for the room need to be updated, another room member needs to be online for this to happen.*' timeout: 60000 +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'room-view-header-more' - extendedWaitUntil: visible: id: 'room-view-header-encryption' diff --git a/.maestro/tests/room/jump-to-message.yaml b/.maestro/tests/room/jump-to-message.yaml index bd2b7532612..2e206e14bcc 100644 --- a/.maestro/tests/room/jump-to-message.yaml +++ b/.maestro/tests/room/jump-to-message.yaml @@ -85,6 +85,12 @@ tags: file: '../../helpers/search-and-navigate-room.yaml' env: ROOM: 'jumping' +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'room-view-header-more' - extendedWaitUntil: visible: id: 'room-view-search' @@ -256,6 +262,12 @@ tags: visible: id: 'room-view-title-jumping-thread' timeout: 60000 +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'room-view-header-more' - tapOn: id: 'room-view-search' - extendedWaitUntil: diff --git a/.maestro/tests/room/threads.yaml b/.maestro/tests/room/threads.yaml index ff880508c1f..75a602141ec 100644 --- a/.maestro/tests/room/threads.yaml +++ b/.maestro/tests/room/threads.yaml @@ -29,10 +29,14 @@ tags: timeout: 60000 # should have actions button -- extendedWaitUntil: - visible: - id: 'room-header' - timeout: 60000 +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + visible: + id: 'room-header' + timeout: 60000 # should have threads button - extendedWaitUntil: diff --git a/.maestro/tests/teams/convert-team.yaml b/.maestro/tests/teams/convert-team.yaml index 9820ad29154..004241b7413 100644 --- a/.maestro/tests/teams/convert-team.yaml +++ b/.maestro/tests/teams/convert-team.yaml @@ -124,12 +124,7 @@ tags: text: 'Yes, move it!' - tapOn: text: 'Yes, move it!' -- extendedWaitUntil: - visible: - id: 'room-header' - timeout: 60000 -- tapOn: - id: 'room-header' +- runFlow: '../../helpers/open-room-actions.yaml' - extendedWaitUntil: visible: id: 'room-actions-teams' diff --git a/.maestro/tests/teams/create-team.yaml b/.maestro/tests/teams/create-team.yaml index a587cd06daf..4712adaaaad 100644 --- a/.maestro/tests/teams/create-team.yaml +++ b/.maestro/tests/teams/create-team.yaml @@ -41,8 +41,7 @@ tags: id: 'room-view-messages' - assertVisible: id: 'room-view-title-${output.teamname}' -- tapOn: - id: 'room-header' +- runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: 'room-actions-info' - tapOn: diff --git a/.maestro/tests/teams/team.yaml b/.maestro/tests/teams/team.yaml index 72325d23b02..50e959ca9b5 100644 --- a/.maestro/tests/teams/team.yaml +++ b/.maestro/tests/teams/team.yaml @@ -25,18 +25,31 @@ tags: ROOM: ${output.team} # Team Header -- assertVisible: - id: room-header +- runFlow: + when: + platform: android + commands: + - assertVisible: + id: room-header + - assertVisible: + id: room-view-search - assertVisible: id: room-view-header-call - assertVisible: id: room-view-header-threads -- assertVisible: - id: room-view-search +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'room-view-header-more' + - assertVisible: + id: 'room-view-search' + - tapOn: + id: 'action-sheet-handle' # Team Action Usage -- tapOn: - id: room-header +- runFlow: '../../helpers/open-room-actions.yaml' - tapOn: id: room-actions-teams - extendedWaitUntil: @@ -134,10 +147,13 @@ tags: id: room-view timeout: 60000 label: should navigate to room view -- assertVisible: - id: room-header -- tapOn: - id: room-header +- runFlow: + when: + platform: android + commands: + - assertVisible: + id: room-header +- runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: room-actions-teams - tapOn: @@ -166,8 +182,22 @@ tags: id: room-view-header-call - assertVisible: id: room-view-header-threads -- assertVisible: - id: room-view-search +- runFlow: + when: + platform: android + commands: + - assertVisible: + id: room-view-search +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'room-view-header-more' + - assertVisible: + id: 'room-view-search' + - tapOn: + id: 'action-sheet-handle' - runFlow: '../../helpers/go-back.yaml' # should add existing channel to team @@ -175,10 +205,13 @@ tags: file: '../../helpers/navigate-to-room.yaml' env: ROOM: ${output.team} -- assertVisible: - id: room-header -- tapOn: - id: room-header +- runFlow: + when: + platform: android + commands: + - assertVisible: + id: room-header +- runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: room-actions-teams - tapOn: @@ -217,8 +250,7 @@ tags: visible: id: room-view-title-${output.team} timeout: 60000 -- tapOn: - id: room-header +- runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: room-actions-teams - tapOn: @@ -287,12 +319,7 @@ tags: visible: id: room-view timeout: 60000 -- tapOn: - id: room-header -- extendedWaitUntil: - visible: - id: room-actions-view - timeout: 60000 +- runFlow: '../../helpers/open-room-actions.yaml' # should add users to the team - tapOn: From f2156d68c9422180b272417cd208bd81a87392ac Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 17 Sep 2026 19:18:06 -0300 Subject: [PATCH 035/120] test(maestro): harden native bar selectors after review --- .maestro/helpers/navigate-to-recent-room.yaml | 6 ----- .maestro/helpers/navigate-to-room-action.yaml | 25 +------------------ .maestro/helpers/open-room-actions.yaml | 7 ++++++ .maestro/tests/assorted/join-public-room.yaml | 6 +++++ .../tests/e2ee/flows/reset-keys-and-send.yaml | 2 ++ .maestro/tests/room/threads.yaml | 8 ++++++ .maestro/tests/teams/team.yaml | 12 +++++++++ 7 files changed, 36 insertions(+), 30 deletions(-) diff --git a/.maestro/helpers/navigate-to-recent-room.yaml b/.maestro/helpers/navigate-to-recent-room.yaml index c67e713d4f7..310e019ea2c 100644 --- a/.maestro/helpers/navigate-to-recent-room.yaml +++ b/.maestro/helpers/navigate-to-recent-room.yaml @@ -8,12 +8,6 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- runFlow: - when: - platform: iOS - commands: - - tapOn: - id: 'magnifyingglass' - runFlow: when: platform: android diff --git a/.maestro/helpers/navigate-to-room-action.yaml b/.maestro/helpers/navigate-to-room-action.yaml index dc99dce1fbc..f9367ee7fd3 100644 --- a/.maestro/helpers/navigate-to-room-action.yaml +++ b/.maestro/helpers/navigate-to-room-action.yaml @@ -19,27 +19,4 @@ tags: id: 'room-view' timeout: 60000 -- runFlow: - when: - platform: iOS - commands: - - tapOn: - id: 'room-view-header-more' - - tapOn: - id: 'room-view-header-room-actions' - -- runFlow: - when: - platform: Android - commands: - - assertVisible: - id: 'room-header' - index: 0 - - tapOn: - id: 'room-header' - index: 0 - -- extendedWaitUntil: - visible: - id: 'room-actions-view' - timeout: 60000 +- runFlow: './open-room-actions.yaml' diff --git a/.maestro/helpers/open-room-actions.yaml b/.maestro/helpers/open-room-actions.yaml index 9bdecd218be..5697cf0b288 100644 --- a/.maestro/helpers/open-room-actions.yaml +++ b/.maestro/helpers/open-room-actions.yaml @@ -10,6 +10,11 @@ tags: commands: - tapOn: id: 'room-view-header-more' + retryTapIfNoChange: true + - extendedWaitUntil: + visible: + id: 'action-sheet-handle' + timeout: 60000 - tapOn: id: 'room-view-header-room-actions' - runFlow: @@ -18,8 +23,10 @@ tags: commands: - assertVisible: id: 'room-header' + index: 0 - tapOn: id: 'room-header' + index: 0 retryTapIfNoChange: true - extendedWaitUntil: visible: diff --git a/.maestro/tests/assorted/join-public-room.yaml b/.maestro/tests/assorted/join-public-room.yaml index b80b517cf9c..845ca5f9a55 100644 --- a/.maestro/tests/assorted/join-public-room.yaml +++ b/.maestro/tests/assorted/join-public-room.yaml @@ -31,6 +31,12 @@ tags: commands: - assertVisible: id: room-header +- runFlow: + when: + platform: iOS + commands: + - assertVisible: + id: room-view-header-more # should have join - assertVisible: diff --git a/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml b/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml index 820795d67af..b11b89a3685 100644 --- a/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml +++ b/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml @@ -42,6 +42,8 @@ env: - runFlow: when: platform: iOS + notVisible: + id: 'room-view-header-encryption' commands: - tapOn: id: 'room-view-header-more' diff --git a/.maestro/tests/room/threads.yaml b/.maestro/tests/room/threads.yaml index 75a602141ec..408c6d5fffb 100644 --- a/.maestro/tests/room/threads.yaml +++ b/.maestro/tests/room/threads.yaml @@ -37,6 +37,14 @@ tags: visible: id: 'room-header' timeout: 60000 +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: + id: 'room-view-header-more' + timeout: 60000 # should have threads button - extendedWaitUntil: diff --git a/.maestro/tests/teams/team.yaml b/.maestro/tests/teams/team.yaml index 50e959ca9b5..5923f0a0356 100644 --- a/.maestro/tests/teams/team.yaml +++ b/.maestro/tests/teams/team.yaml @@ -153,6 +153,12 @@ tags: commands: - assertVisible: id: room-header +- runFlow: + when: + platform: iOS + commands: + - assertVisible: + id: room-view-header-more - runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: room-actions-teams @@ -211,6 +217,12 @@ tags: commands: - assertVisible: id: room-header +- runFlow: + when: + platform: iOS + commands: + - assertVisible: + id: room-view-header-more - runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: room-actions-teams From 59e8d3983f3adfe487078ed5d0c47091c67b77f8 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 08:26:34 -0300 Subject: [PATCH 036/120] fix(ios): inset rooms list under the native navigation bar --- app/views/RoomsListView/hooks/useHeader.tsx | 1 + app/views/RoomsListView/index.tsx | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 61580cea44b..872f62d47d2 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -199,6 +199,7 @@ export const useHeader = () => { headerLargeTitle: true, headerTitle: nativeBarTitle, headerLeft, + scrollEdgeEffects: { top: 'automatic' }, headerSearchBarOptions: { ref: searchBarRef, placement: 'stacked', diff --git a/app/views/RoomsListView/index.tsx b/app/views/RoomsListView/index.tsx index b998c8a6953..bab09c881a9 100644 --- a/app/views/RoomsListView/index.tsx +++ b/app/views/RoomsListView/index.tsx @@ -14,7 +14,15 @@ import i18n from '~/i18n'; import { MAX_SIDEBAR_WIDTH } from '~/lib/constants/tablet'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; -import { getRoomAvatar, getRoomTitle, getUidDirectMessage, isIOS, isRead, isTablet } from '~/lib/methods/helpers'; +import { + getRoomAvatar, + getRoomTitle, + getUidDirectMessage, + hasNativeHeaderBar, + isIOS, + isRead, + isTablet +} from '~/lib/methods/helpers'; import { goRoom } from '~/lib/methods/helpers/goRoom'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { getUserSelector } from '~/selectors/login'; @@ -136,6 +144,7 @@ const RoomsListView = memo(function RoomsListView() { ListHeaderComponent={ListHeader} ListFooterComponent={searching ? () => : undefined} getItemLayout={getItemLayout} + contentInsetAdjustmentBehavior={hasNativeHeaderBar ? 'automatic' : undefined} keyboardShouldPersistTaps='always' initialNumToRender={INITIAL_NUM_TO_RENDER} refreshControl={} From 40e64eab350bddaedd1e70b64b91838f04c673cc Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 09:18:05 -0300 Subject: [PATCH 037/120] fix(ios): restore translucent gray native header and collapse search bar --- app/views/RoomsListView/hooks/useHeader.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 872f62d47d2..53157af7d88 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -199,11 +199,13 @@ export const useHeader = () => { headerLargeTitle: true, headerTitle: nativeBarTitle, headerLeft, + headerStyle: { backgroundColor: `${colors.surfaceNeutral}E6` }, scrollEdgeEffects: { top: 'automatic' }, headerSearchBarOptions: { ref: searchBarRef, placement: 'stacked', placeholder: i18n.t('Search'), + hideWhenScrolling: true, onFocus: startSearch, onChangeText: (event: { nativeEvent: { text: string } }) => search(event.nativeEvent.text), onCancelButtonPress: stopSearch From d9a2bc7a625597868d4fdea55678adce5fe0a882 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 10:16:57 -0300 Subject: [PATCH 038/120] feat(ios): move rooms list search to the bottom toolbar Use automatic search bar placement so the field sits in the iOS 26 bottom toolbar, and pad the list so the last row clears it. --- app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx | 4 ++-- app/views/RoomsListView/hooks/useHeader.tsx | 4 ++-- app/views/RoomsListView/index.tsx | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 49930bde797..2e945022ffb 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -137,14 +137,14 @@ describe('RoomsListView useHeader', () => { expect(testIDs).toEqual(['rooms-list-view-create-channel', 'rooms-list-view-directory']); }); - it('configures a stacked system search bar instead of a right-cluster search item', () => { + it('configures a system search bar instead of a right-cluster search item', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; const rightButtons: ReactElement<{ testID: string }>[] = options.headerRight().props.children.filter(Boolean); expect(rightButtons.some(button => button.props.testID === 'rooms-list-view-search')).toBe(false); - expect(options.headerSearchBarOptions.placement).toBe('stacked'); + expect(options.headerSearchBarOptions.placement).toBe('automatic'); expect(options.headerSearchBarOptions.ref.current).toBeNull(); options.headerSearchBarOptions.onFocus(); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 53157af7d88..982a281d678 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -200,12 +200,12 @@ export const useHeader = () => { headerTitle: nativeBarTitle, headerLeft, headerStyle: { backgroundColor: `${colors.surfaceNeutral}E6` }, + headerLargeTitleStyle: { color: colors.fontTitlesLabels }, scrollEdgeEffects: { top: 'automatic' }, headerSearchBarOptions: { ref: searchBarRef, - placement: 'stacked', + placement: 'automatic', placeholder: i18n.t('Search'), - hideWhenScrolling: true, onFocus: startSearch, onChangeText: (event: { nativeEvent: { text: string } }) => search(event.nativeEvent.text), onCancelButtonPress: stopSearch diff --git a/app/views/RoomsListView/index.tsx b/app/views/RoomsListView/index.tsx index bab09c881a9..402b86eacdb 100644 --- a/app/views/RoomsListView/index.tsx +++ b/app/views/RoomsListView/index.tsx @@ -38,6 +38,7 @@ import { useSubscriptions } from './hooks/useSubscriptions'; import styles from './styles'; const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12; +const BOTTOM_SEARCH_TOOLBAR_HEIGHT = hasNativeHeaderBar ? 44 : 0; const RoomsListView = memo(function RoomsListView() { useHeader(); @@ -139,7 +140,7 @@ const RoomsListView = memo(function RoomsListView() { extraData={searchEnabled ? searchResults : subscriptions} keyExtractor={item => `${item.rid}-${searchEnabled}`} style={[styles.list, { backgroundColor: colors.surfaceRoom }]} - contentContainerStyle={{ paddingBottom: bottom }} + contentContainerStyle={{ paddingBottom: bottom + BOTTOM_SEARCH_TOOLBAR_HEIGHT }} renderItem={renderItem} ListHeaderComponent={ListHeader} ListFooterComponent={searching ? () => : undefined} From 143ebb3c799bd01de56f9476b0483e4c43e2aac3 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 11:32:21 -0300 Subject: [PATCH 039/120] fix(ios): make native headers opaque and drop the large title Sets an opaque surfaceNeutral background on native headers app-wide via the shared screen options, disables the large title, and themes the small title style consistently. --- app/lib/methods/helpers/navigation/index.ts | 8 +++++++- .../RoomsListView/hooks/__tests__/useHeader.test.tsx | 4 ++-- app/views/RoomsListView/hooks/useHeader.tsx | 9 +++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 4161625b4d9..42026aefbc6 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -22,7 +22,13 @@ export const drawerStyle = { export const themedHeader = (theme: TSupportedThemes): NativeStackNavigationOptions => isIOS - ? {} + ? { + headerStyle: { + backgroundColor: themes[theme].surfaceNeutral + }, + headerTransparent: false, + headerTitleStyle: { color: themes[theme].fontTitlesLabels } + } : { headerStyle: { backgroundColor: themes[theme].surfaceNeutral diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 2e945022ffb..6176b52756a 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -93,13 +93,13 @@ describe('RoomsListView useHeader', () => { }; }); - it('sets a native string title with a large title on iPhone', () => { + it('sets a native string title without a large title on iPhone', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; expect(typeof options.headerTitle).toBe('string'); expect(options.headerTitle).toBe('Chats'); - expect(options.headerLargeTitle).toBe(true); + expect(options.headerLargeTitle).toBe(false); }); it.each([ diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 982a281d678..72c47db146d 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -196,12 +196,13 @@ export const useHeader = () => { ]); navigation.setOptions({ - headerLargeTitle: true, + headerLargeTitle: false, headerTitle: nativeBarTitle, headerLeft, - headerStyle: { backgroundColor: `${colors.surfaceNeutral}E6` }, - headerLargeTitleStyle: { color: colors.fontTitlesLabels }, - scrollEdgeEffects: { top: 'automatic' }, + headerStyle: { backgroundColor: colors.surfaceNeutral }, + headerTransparent: false, + headerTitleStyle: { color: colors.fontTitlesLabels }, + scrollEdgeEffects: { top: 'hidden' }, headerSearchBarOptions: { ref: searchBarRef, placement: 'automatic', From 388d70b5f7c211a934b2b62872529856ac27a4f5 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 13:09:40 -0300 Subject: [PATCH 040/120] commit search --- app/views/RoomsListView/components/ListHeader.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/RoomsListView/components/ListHeader.tsx b/app/views/RoomsListView/components/ListHeader.tsx index d03ae4d1ff8..dfef18be5b1 100644 --- a/app/views/RoomsListView/components/ListHeader.tsx +++ b/app/views/RoomsListView/components/ListHeader.tsx @@ -23,6 +23,7 @@ const ListHeader = () => { if (searchEnabled) { return null; } + return null; const goEncryption = () => { logEvent(events.RL_GO_E2E_SAVE_PASSWORD); From b9de5738863d5f8c4e32379c373160c2355797c4 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 13:43:24 -0300 Subject: [PATCH 041/120] fix(ios): restore the legacy title headers under the native bar --- .../hooks/__tests__/useHeader.test.tsx | 49 ++----------------- app/views/RoomView/hooks/useHeader.tsx | 14 +----- app/views/RoomsListView/hooks/useHeader.tsx | 27 +--------- 3 files changed, 6 insertions(+), 84 deletions(-) diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index 7d806b2c8fc..ebf6cc8ff4f 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -6,8 +6,6 @@ import { useHeader } from '../useHeader'; let mockTestStore: RoomStore; let mockIsIOS = false; -let mockConnecting = false; -let mockConnected = true; jest.mock('../useGoRoomActionsView', () => ({ useGoRoomActionsView: jest.fn(() => jest.fn()) })); jest.mock('~/views/RoomView/components/LeftButtons', () => ({ __esModule: true, default: 'LeftButtons' })); @@ -30,13 +28,6 @@ jest.mock('~/lib/methods/helpers', () => ({ jest.mock('~/lib/methods/isInviteSubscription', () => ({ isInviteSubscription: jest.fn(() => false) })); -jest.mock('~/lib/hooks/useAppSelector', () => ({ - useAppSelector: (selector: (state: unknown) => unknown) => - selector({ - meteor: { connecting: mockConnecting, connected: mockConnected }, - server: { loading: false } - }) -})); const mockSetOptions = jest.fn(); const mockNavigation = { setOptions: mockSetOptions }; @@ -67,8 +58,6 @@ describe('useHeader', () => { mockTestStore = makeRoomStore(); mockIsIOS = false; mockIsTablet = false; - mockConnecting = false; - mockConnected = true; }); it('sets only the headerLeft spacer and returns when rid is missing', () => { @@ -125,44 +114,12 @@ describe('useHeader', () => { mockIsIOS = true; }); - it('sets a plain string title instead of the RoomHeader render prop', () => { + it('renders the tappable RoomHeader under the native header bar', () => { renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); const titleOptions = mockSetOptions.mock.calls[1][0]; - expect(titleOptions).toEqual({ title: 'Room Title' }); - }); - - it('swaps the title to Connecting while the socket is connecting', () => { - mockConnecting = true; - - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - - const titleOptions = mockSetOptions.mock.calls[1][0]; - expect(titleOptions).toEqual({ title: 'Connecting...' }); - }); - - it('swaps the title to Waiting_for_network when disconnected', () => { - mockConnected = false; - - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - - const titleOptions = mockSetOptions.mock.calls[1][0]; - expect(titleOptions).toEqual({ title: 'Waiting for network...' }); - }); - - it('swaps back to the room title once connected again', () => { - mockConnecting = true; - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - expect(mockSetOptions.mock.calls[1][0]).toEqual({ title: 'Connecting...' }); - - mockConnecting = false; - act(() => { - mockTestStore.setState({ - room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', topic: 'updated' } as RoomState['room'] - }); - }); - const lastOptions = mockSetOptions.mock.calls[mockSetOptions.mock.calls.length - 1][0]; - expect(lastOptions).toEqual({ title: 'Room Title' }); + expect(typeof titleOptions.headerTitle).toBe('function'); + expect(titleOptions.headerTitle().props.title).toBe('Room Title'); }); }); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index 9e5fde1988e..e11e5343274 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -5,10 +5,8 @@ import { useShallow } from 'zustand/react/shallow'; import { useStore } from 'zustand'; import RoomHeader from '~/containers/RoomHeader'; -import I18n from '~/i18n'; -import { getRoomTitle, hasNativeHeaderBar, isGroupChat } from '~/lib/methods/helpers'; +import { getRoomTitle, isGroupChat } from '~/lib/methods/helpers'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; -import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; import LeftButtons from '../components/LeftButtons'; import RightButtons from '../components/RightButtons/RightButtons'; @@ -68,8 +66,6 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa ); const roomUserId = useStore(roomStore, s => s.roomUserId); const goRoomActionsView = useGoRoomActionsView(roomStore); - const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading); - const connected = useAppSelector(state => state.meteor.connected); useLayoutEffect(() => { if (!rid) { @@ -88,12 +84,6 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa return; } - if (hasNativeHeaderBar) { - const title = connecting ? I18n.t('Connecting') : !connected ? I18n.t('Waiting_for_network') : headerFields.title; - navigation.setOptions({ title }); - return; - } - navigation.setOptions({ headerTitle: () => ( ) }); - }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView, connecting, connected]); + }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView]); }; diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 72c47db146d..47dff4bd4ac 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -40,27 +40,6 @@ const splitHeaderRightActions = (actions: IHeaderRightAction[]) => { return { visible: present.slice(0, MAX_HEADER_RIGHT_ACTIONS), overflow: present.slice(MAX_HEADER_RIGHT_ACTIONS) }; }; -const useNativeBarTitle = (useNativeBar: boolean) => { - const connecting = useAppSelector(state => useNativeBar && (state.meteor.connecting || state.server.loading)); - const isLoggingIn = useAppSelector(state => useNativeBar && state.login.isFetching); - const isFetching = useAppSelector(state => useNativeBar && state.rooms.isFetching); - const connected = useAppSelector(state => !useNativeBar || state.meteor.connected); - - if (!useNativeBar) { - return ''; - } - if (connecting || isLoggingIn) { - return i18n.t('Connecting'); - } - if (!connected) { - return i18n.t('Waiting_for_network'); - } - if (isFetching) { - return i18n.t('Updating'); - } - return i18n.t('Chats'); -}; - export const useHeader = () => { const { searchEnabled, search, startSearch, stopSearch } = useContext(RoomsSearchContext); const [options, setOptions] = useState(null); @@ -129,8 +108,6 @@ export const useHeader = () => { } }, [isMasterDetail, navigation]); - const nativeBarTitle = useNativeBarTitle(hasNativeHeaderBar); - useLayoutEffect(() => { const headerLeft = () => ( { navigation.setOptions({ headerLargeTitle: false, - headerTitle: nativeBarTitle, + headerTitle: () => , headerLeft, headerStyle: { backgroundColor: colors.surfaceNeutral }, headerTransparent: false, - headerTitleStyle: { color: colors.fontTitlesLabels }, scrollEdgeEffects: { top: 'hidden' }, headerSearchBarOptions: { ref: searchBarRef, @@ -302,7 +278,6 @@ export const useHeader = () => { colors, canCreateRoom, searchEnabled, - nativeBarTitle, goDirectory, navigateToPushTroubleshootView, getBadge, From 710e4d33726c354b7a3b8ecb4e92e11cefc8ef63 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 13:44:32 -0300 Subject: [PATCH 042/120] fix(ios): remove the workspaces entry from the drawer --- .../SidebarView/components/Workspaces.tsx | 35 ------------------- app/views/SidebarView/index.tsx | 3 -- 2 files changed, 38 deletions(-) delete mode 100644 app/views/SidebarView/components/Workspaces.tsx diff --git a/app/views/SidebarView/components/Workspaces.tsx b/app/views/SidebarView/components/Workspaces.tsx deleted file mode 100644 index 316918c4efb..00000000000 --- a/app/views/SidebarView/components/Workspaces.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { memo } from 'react'; -import { type DrawerNavigationProp } from '@react-navigation/drawer'; - -import * as List from '~/containers/List'; -import { showActionSheetRef } from '~/containers/ActionSheet'; -import { useAppSelector } from '~/lib/hooks/useAppSelector'; -import { type DrawerParamList } from '~/stacks/types'; -import ServersList from '~/views/RoomsListView/components/ServersList'; - -const formatServerHost = (server: string) => server.replace(/(^\w+:|^)\/\//, ''); - -const Workspaces = ({ navigation }: { navigation: DrawerNavigationProp }) => { - const server = useAppSelector(state => state.server.server); - - const onPress = () => { - navigation.closeDrawer(); - showActionSheetRef({ children: , enableContentPanningGesture: false }); - }; - - return ( - <> - } - onPress={onPress} - testID='sidebar-workspaces' - /> - - - ); -}; - -export default memo(Workspaces); diff --git a/app/views/SidebarView/index.tsx b/app/views/SidebarView/index.tsx index a6dadafcb50..c65c6ba18ef 100644 --- a/app/views/SidebarView/index.tsx +++ b/app/views/SidebarView/index.tsx @@ -3,13 +3,11 @@ import { type DrawerNavigationProp } from '@react-navigation/drawer'; import { ScrollView, View } from 'react-native'; import scrollPersistTaps from '~/lib/methods/helpers/scrollPersistTaps'; -import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import styles from './styles'; import { type DrawerParamList } from '~/stacks/types'; import SupportedVersionsWarnItem from './components/SupportedVersionsWarnItem'; import CustomStatus from './components/CustomStatus'; import Stacks from './components/Stacks'; -import Workspaces from './components/Workspaces'; import Admin from './components/Admin'; import Profile from './components/Profile'; @@ -31,7 +29,6 @@ const SidebarView = ({ navigation }: { navigation: DrawerNavigationProp - {hasNativeHeaderBar ? : null} From 349ddbbe462d28d8cac171dbc32a43930a8d2dcd Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 15:00:09 -0300 Subject: [PATCH 043/120] fix(ios): show native title with connection state text on rooms list header --- app/views/RoomsListView/hooks/useHeader.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 47dff4bd4ac..4e2fba81f98 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -52,7 +52,18 @@ export const useHeader = () => { const navigation = useNavigation(); const issuesWithNotifications = useAppSelector(state => state.troubleshootingNotification.issuesWithNotifications); const notificationPresenceCap = useAppSelector(state => state.app.notificationPresenceCap); + const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading || state.login.isFetching); + const connected = useAppSelector(state => state.meteor.connected); + const isFetchingRooms = useAppSelector(state => state.rooms.isFetching); const { colors } = useTheme(); + + const nativeHeaderTitle = connecting + ? i18n.t('Connecting') + : !connected + ? i18n.t('Waiting_for_network') + : isFetchingRooms + ? i18n.t('Updating') + : i18n.t('Chats'); const [ createPublicChannelPermission, createPrivateChannelPermission, @@ -174,7 +185,7 @@ export const useHeader = () => { navigation.setOptions({ headerLargeTitle: false, - headerTitle: () => , + headerTitle: nativeHeaderTitle, headerLeft, headerStyle: { backgroundColor: colors.surfaceNeutral }, headerTransparent: false, @@ -284,7 +295,8 @@ export const useHeader = () => { goToNewMessage, startSearch, stopSearch, - search + search, + nativeHeaderTitle ]); useEffect(() => { From bccdfdb52bdd36e93940b7cb90747f4c1f337a6d Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 16:00:59 -0300 Subject: [PATCH 044/120] chore(ios): revert xcode project churn from local build --- ios/RocketChat.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/RocketChat.xcodeproj/project.pbxproj b/ios/RocketChat.xcodeproj/project.pbxproj index ea53c986722..2040d68230c 100644 --- a/ios/RocketChat.xcodeproj/project.pbxproj +++ b/ios/RocketChat.xcodeproj/project.pbxproj @@ -1385,7 +1385,7 @@ inputFileListPaths = ( ); inputPaths = ( - $TARGET_BUILD_DIR/$INFOPLIST_PATH, + "$TARGET_BUILD_DIR/$INFOPLIST_PATH", ); name = "Upload source maps to Bugsnag"; outputFileListPaths = ( @@ -1852,7 +1852,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - $PODS_CONFIGURATION_BUILD_DIR/Firebase, + "$PODS_CONFIGURATION_BUILD_DIR/Firebase", ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.4; @@ -1928,7 +1928,7 @@ "$(inherited)", "$(SRCROOT)/../node_modules/rn-extensions-share/ios/**", "$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**", - $PODS_CONFIGURATION_BUILD_DIR/Firebase, + "$PODS_CONFIGURATION_BUILD_DIR/Firebase", ); INFOPLIST_FILE = ShareRocketChatRN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.4; From 0e8fbce4c5cc4f2cf182961288770b90eb53f6be Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 16:05:00 -0300 Subject: [PATCH 045/120] feat(ios): add headerSubtitle native-stack option via patches --- ...react-navigation+native-stack+7.19.2.patch | 116 ++++++++++++++++++ patches/react-native-screens+4.28.0.patch | 110 +++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 patches/@react-navigation+native-stack+7.19.2.patch create mode 100644 patches/react-native-screens+4.28.0.patch diff --git a/patches/@react-navigation+native-stack+7.19.2.patch b/patches/@react-navigation+native-stack+7.19.2.patch new file mode 100644 index 00000000000..e76c2a22d2c --- /dev/null +++ b/patches/@react-navigation+native-stack+7.19.2.patch @@ -0,0 +1,116 @@ +diff --git a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js +index a56cb12..28697a3 100644 +--- a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js ++++ b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js +@@ -150,6 +150,7 @@ export function useHeaderConfigProps({ + headerBlurEffect, + headerTintColor, + headerTitle, ++ headerSubtitle, + headerTitleAlign, + headerTitleStyle, + headerTransparent, +@@ -230,6 +231,12 @@ export function useHeaderConfigProps({ + tintColor, + children: titleText + }) : null; ++ if (process.env.NODE_ENV !== 'production') { ++ if (headerTitleElement != null && headerSubtitle != null) { ++ console.warn(`'headerSubtitle' is ignored when 'headerTitle' renders a custom element for the screen '${route.name}'. Remove 'headerSubtitle' or use a string 'headerTitle' instead.`); ++ } ++ } ++ const subtitleText = headerTitleElement != null ? undefined : headerSubtitle; + const supportsHeaderSearchBar = typeof isSearchBarAvailableForCurrentPlatform === 'boolean' ? isSearchBarAvailableForCurrentPlatform : + // Fallback for older versions of react-native-screens + Platform.OS === 'ios' && SearchBar != null; +@@ -357,6 +364,7 @@ export function useHeaderConfigProps({ + largeTitleFontWeight, + largeTitleHideShadow: headerLargeTitleShadowVisible === false, + title: titleText, ++ subtitle: subtitleText, + titleColor, + titleFontFamily, + titleFontSize, +diff --git a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts +index c32d1da..71ddc38 100644 +--- a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts ++++ b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts +@@ -321,6 +321,18 @@ export type NativeStackNavigationOptions = { + */ + tintColor?: string; + }) => React.ReactNode); ++ /** ++ * String to display in the header underneath the title. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerSubtitle?: string; + /** + * How to align the the header title. + * Defaults to `left` on platforms other than iOS. +diff --git a/node_modules/@react-navigation/native-stack/src/types.tsx b/node_modules/@react-navigation/native-stack/src/types.tsx +index aeb4939..f5ea8e8 100644 +--- a/node_modules/@react-navigation/native-stack/src/types.tsx ++++ b/node_modules/@react-navigation/native-stack/src/types.tsx +@@ -371,6 +371,18 @@ export type NativeStackNavigationOptions = { + */ + tintColor?: string; + }) => React.ReactNode); ++ /** ++ * String to display in the header underneath the title. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerSubtitle?: string; + /** + * How to align the the header title. + * Defaults to `left` on platforms other than iOS. +diff --git a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx +index a7e3004..c2dd78a 100644 +--- a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx ++++ b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx +@@ -211,6 +211,7 @@ export function useHeaderConfigProps({ + headerBlurEffect, + headerTintColor, + headerTitle, ++ headerSubtitle, + headerTitleAlign, + headerTitleStyle, + headerTransparent, +@@ -330,6 +331,16 @@ export function useHeaderConfigProps({ + }) + : null; + ++ if (process.env.NODE_ENV !== 'production') { ++ if (headerTitleElement != null && headerSubtitle != null) { ++ console.warn( ++ `'headerSubtitle' is ignored when 'headerTitle' renders a custom element for the screen '${route.name}'. Remove 'headerSubtitle' or use a string 'headerTitle' instead.` ++ ); ++ } ++ } ++ ++ const subtitleText = headerTitleElement != null ? undefined : headerSubtitle; ++ + const supportsHeaderSearchBar = + typeof isSearchBarAvailableForCurrentPlatform === 'boolean' + ? isSearchBarAvailableForCurrentPlatform +@@ -523,6 +534,7 @@ export function useHeaderConfigProps({ + largeTitleFontWeight, + largeTitleHideShadow: headerLargeTitleShadowVisible === false, + title: titleText, ++ subtitle: subtitleText, + titleColor, + titleFontFamily, + titleFontSize, diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch new file mode 100644 index 00000000000..9117e36e520 --- /dev/null +++ b/patches/react-native-screens+4.28.0.patch @@ -0,0 +1,110 @@ +diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h +index 8a41697..ead4691 100644 +--- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h ++++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h +@@ -23,6 +23,7 @@ + NS_ASSUME_NONNULL_BEGIN + + @property (nonatomic, retain) NSString *title; ++@property (nonatomic, retain) NSString *subtitle; + @property (nonatomic, retain) NSString *titleFontFamily; + @property (nonatomic, retain) NSNumber *titleFontSize; + @property (nonatomic, retain) NSString *titleFontWeight; +diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +index a54cb63..7a7d6b4 100644 +--- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm ++++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +@@ -507,6 +507,11 @@ RNS_IGNORE_SUPER_CALL_END + + if (shouldHide) { + navitem.title = config.title; ++#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) ++ if (@available(iOS 26.0, *)) { ++ navitem.subtitle = config.subtitle; ++ } ++#endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) + + // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items. + [navctr setNavigationBarHidden:YES animated:animated]; +@@ -648,6 +653,11 @@ RNS_IGNORE_SUPER_CALL_END + // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug). + // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments) + navitem.title = config.title; ++#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) ++ if (@available(iOS 26.0, *)) { ++ navitem.subtitle = config.subtitle; ++ } ++#endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) + navitem.leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems + withCurrentItems:navitem.leftBarButtonItems]; + navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems +@@ -1025,6 +1035,7 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) + } + + _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title); ++ _subtitle = RCTNSStringFromStringNilIfEmpty(newScreenProps.subtitle); + if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) { + _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily); + } +diff --git a/node_modules/react-native-screens/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts b/node_modules/react-native-screens/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts +index ec39792..785af97 100644 +--- a/node_modules/react-native-screens/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts ++++ b/node_modules/react-native-screens/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts +@@ -32,6 +32,7 @@ export interface NativeProps extends ViewProps { + largeTitleColor?: ColorValue | undefined; + translucent?: boolean | undefined; + title?: string | undefined; ++ subtitle?: string | undefined; + titleFontFamily?: string | undefined; + titleFontSize?: CT.Int32 | undefined; + titleFontWeight?: string | undefined; +diff --git a/node_modules/react-native-screens/lib/typescript/types.d.ts b/node_modules/react-native-screens/lib/typescript/types.d.ts +index ff37881..7a7da9a 100644 +--- a/node_modules/react-native-screens/lib/typescript/types.d.ts ++++ b/node_modules/react-native-screens/lib/typescript/types.d.ts +@@ -723,6 +723,14 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { + * String that can be displayed in the header as a fallback for `headerTitle`. + */ + title?: string | undefined; ++ /** ++ * String to display in the header underneath the title. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ subtitle?: string | undefined; + /** + * Allows for setting text color of the title. + */ +diff --git a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts +index da28c51..ef0cb5f 100644 +--- a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts ++++ b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts +@@ -66,6 +66,7 @@ export interface NativeProps extends ViewProps { + largeTitleColor?: ColorValue | undefined; + translucent?: boolean | undefined; + title?: string | undefined; ++ subtitle?: string | undefined; + titleFontFamily?: string | undefined; + titleFontSize?: CT.Int32 | undefined; + titleFontWeight?: string | undefined; +diff --git a/node_modules/react-native-screens/src/types.tsx b/node_modules/react-native-screens/src/types.tsx +index 8594b73..c137d06 100644 +--- a/node_modules/react-native-screens/src/types.tsx ++++ b/node_modules/react-native-screens/src/types.tsx +@@ -827,6 +827,14 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { + * String that can be displayed in the header as a fallback for `headerTitle`. + */ + title?: string | undefined; ++ /** ++ * String to display in the header underneath the title. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ subtitle?: string | undefined; + /** + * Allows for setting text color of the title. + */ From 5f519d2d48131e8aabd94f213cb4dae021239efc Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 16:15:19 -0300 Subject: [PATCH 046/120] fix(android): add no-op setSubtitle override to screens header config --- patches/react-native-screens+4.28.0.patch | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch index 9117e36e520..20d6784108f 100644 --- a/patches/react-native-screens+4.28.0.patch +++ b/patches/react-native-screens+4.28.0.patch @@ -1,3 +1,19 @@ +diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +index fa1b773..b2c5167 100644 +--- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt ++++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +@@ -301,6 +301,11 @@ class ScreenStackHeaderConfigViewManager : + value: String?, + ) = Unit + ++ override fun setSubtitle( ++ view: ScreenStackHeaderConfig?, ++ value: String?, ++ ) = Unit ++ + override fun setTopInsetEnabled( + config: ScreenStackHeaderConfig, + topInsetEnabled: Boolean, diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h index 8a41697..ead4691 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h From a1738d925f654b264f314df6b551c8286568d19c Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 18 Sep 2026 17:26:19 -0300 Subject: [PATCH 047/120] feat(ios): use native header subtitle for rooms list connection state Restores the develop pairing on the native bar: server name as the title and the connection-state / server-url chain as the subtitle. --- .../hooks/__tests__/useHeader.test.tsx | 23 ++++++++++------- app/views/RoomsListView/hooks/useHeader.tsx | 25 ++++++++++++------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 6176b52756a..b074980f5c2 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -50,9 +50,10 @@ let mockAppState = { troubleshootingNotification: { issuesWithNotifications: false }, app: { notificationPresenceCap: false }, meteor: { connecting: false, connected: true }, - server: { loading: false }, + server: { loading: false, server: 'https://open.rocket.chat' }, login: { isFetching: false }, - rooms: { isFetching: false } + rooms: { isFetching: false }, + settings: { Site_Name: 'Rocket.Chat' } }; jest.mock('~/lib/hooks/useAppSelector', () => ({ useAppSelector: (selector: (state: typeof mockAppState) => unknown) => selector(mockAppState) @@ -87,34 +88,38 @@ describe('RoomsListView useHeader', () => { troubleshootingNotification: { issuesWithNotifications: false }, app: { notificationPresenceCap: false }, meteor: { connecting: false, connected: true }, - server: { loading: false }, + server: { loading: false, server: 'https://open.rocket.chat' }, login: { isFetching: false }, - rooms: { isFetching: false } + rooms: { isFetching: false }, + settings: { Site_Name: 'Rocket.Chat' } }; }); - it('sets a native string title without a large title on iPhone', () => { + it('sets the server name as a native string title with the server url as subtitle on iPhone', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; expect(typeof options.headerTitle).toBe('string'); - expect(options.headerTitle).toBe('Chats'); + expect(options.headerTitle).toBe('Rocket.Chat'); + expect(options.headerSubtitle).toBe('open.rocket.chat'); expect(options.headerLargeTitle).toBe(false); }); it.each([ + [{ supportedVersions: { status: 'expired' } }, 'Cannot connect'], [{ meteor: { connecting: true, connected: true } }, 'Connecting...'], [{ login: { isFetching: true } }, 'Connecting...'], [{ rooms: { isFetching: true } }, 'Updating...'], [{ meteor: { connecting: false, connected: false } }, 'Waiting for network...'], - [{ meteor: { connecting: false, connected: false }, rooms: { isFetching: true } }, 'Waiting for network...'] - ])('swaps the native title to the connection state %j', (stateOverride, expectedTitle) => { + [{ meteor: { connecting: false, connected: false }, rooms: { isFetching: true } }, 'Updating...'] + ])('swaps the native subtitle to the connection state %j', (stateOverride, expectedSubtitle) => { mockAppState = { ...mockAppState, ...stateOverride } as typeof mockAppState; renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; - expect(options.headerTitle).toBe(expectedTitle); + expect(options.headerTitle).toBe('Rocket.Chat'); + expect(options.headerSubtitle).toBe(expectedSubtitle); }); it('builds the right cluster in create, push-troubleshoot priority order, overflowing directory', () => { diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 4e2fba81f98..a8a2d7758fa 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -55,15 +55,20 @@ export const useHeader = () => { const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading || state.login.isFetching); const connected = useAppSelector(state => state.meteor.connected); const isFetchingRooms = useAppSelector(state => state.rooms.isFetching); + const serverName = useAppSelector(state => state.settings.Site_Name as string); + const server = useAppSelector(state => state.server.server); const { colors } = useTheme(); - const nativeHeaderTitle = connecting - ? i18n.t('Connecting') - : !connected - ? i18n.t('Waiting_for_network') - : isFetchingRooms - ? i18n.t('Updating') - : i18n.t('Chats'); + const nativeHeaderSubtitle = + supportedVersionsStatus === 'expired' + ? 'Cannot connect' + : connecting + ? i18n.t('Connecting') + : isFetchingRooms + ? i18n.t('Updating') + : !connected + ? i18n.t('Waiting_for_network') + : server?.replace(/(^\w+:|^)\/\//, ''); const [ createPublicChannelPermission, createPrivateChannelPermission, @@ -185,7 +190,8 @@ export const useHeader = () => { navigation.setOptions({ headerLargeTitle: false, - headerTitle: nativeHeaderTitle, + headerTitle: serverName, + headerSubtitle: nativeHeaderSubtitle, headerLeft, headerStyle: { backgroundColor: colors.surfaceNeutral }, headerTransparent: false, @@ -296,7 +302,8 @@ export const useHeader = () => { startSearch, stopSearch, search, - nativeHeaderTitle + serverName, + nativeHeaderSubtitle ]); useEffect(() => { From 3a360f8b4d0e2b06c5524bd4d9822c2cc333f68b Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 21 Sep 2026 14:23:20 -0300 Subject: [PATCH 048/120] feat(rooms): use native iOS room headers --- .../hooks/__tests__/useHeader.test.tsx | 27 +++ .../__tests__/useNativeRoomHeader.test.ts | 104 ++++++++++++ app/views/RoomView/hooks/useHeader.tsx | 11 +- app/views/RoomView/hooks/useHeaderImage.ts | 45 +++++ .../RoomView/hooks/useNativeRoomHeader.ts | 156 ++++++++++++++++++ ios/Podfile.lock | 2 +- metro.config.js | 22 +++ tsconfig.json | 2 + 8 files changed, 364 insertions(+), 5 deletions(-) create mode 100644 app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts create mode 100644 app/views/RoomView/hooks/useHeaderImage.ts create mode 100644 app/views/RoomView/hooks/useNativeRoomHeader.ts diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index ebf6cc8ff4f..d4d23a52e7c 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -1,8 +1,13 @@ import { act, renderHook } from '@testing-library/react-native'; +import { Platform } from 'react-native'; + import { createStore } from 'zustand'; import { type RoomState, type RoomStore } from '~/views/RoomView/definitions'; import { useHeader } from '../useHeader'; +import { useNativeRoomHeader } from '../useNativeRoomHeader'; + +jest.mock('../useNativeRoomHeader', () => ({ useNativeRoomHeader: jest.fn() })); let mockTestStore: RoomStore; let mockIsIOS = false; @@ -138,3 +143,25 @@ describe('useHeader', () => { }); }); }); + +describe('native title availability', () => { + const originalVersion = Platform.Version; + const originalOS = Platform.OS; + afterEach(() => { + Object.defineProperty(Platform, 'Version', { configurable: true, value: originalVersion }); + Object.defineProperty(Platform, 'OS', { configurable: true, value: originalOS }); + }); + + it.each([ + ['ios', '26.0', true], + ['ios', '18.0', false], + ['android', 36, false] + ])('uses the appropriate title on %s %s', (os, version, native) => { + jest.clearAllMocks(); + Object.defineProperty(Platform, 'OS', { configurable: true, value: os }); + Object.defineProperty(Platform, 'Version', { configurable: true, value: version }); + renderHook(() => useHeader({ rid: 'rid-1', roomStore: makeRoomStore() })); + expect(useNativeRoomHeader).toHaveBeenCalledWith(native, expect.any(Object), undefined, null); + expect(mockSetOptions.mock.calls.some(([options]) => typeof options.headerTitle === 'function')).toBe(!native); + }); +}); diff --git a/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts new file mode 100644 index 00000000000..eea9f512ba2 --- /dev/null +++ b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts @@ -0,0 +1,104 @@ +import { act, renderHook, waitFor } from '@testing-library/react-native'; +import { Image } from 'react-native'; + +import { IconSet } from '~/containers/CustomIcon'; +import { getUserPresence } from '~/lib/methods/getUsersPresence'; +import { type IHeaderFields } from '../useHeader'; +import { useNativeRoomHeader } from '../useNativeRoomHeader'; + +const mockSetOptions = jest.fn(); +const mockNavigation = { setOptions: mockSetOptions }; +let mockState: any; + +jest.mock('@react-navigation/native', () => ({ useNavigation: () => mockNavigation })); +jest.mock('~/lib/hooks/useAppSelector', () => ({ useAppSelector: (select: (state: any) => unknown) => select(mockState) })); +jest.mock('~/containers/CustomIcon', () => ({ + IconSet: { getImageSource: jest.fn(async (name, size, color) => ({ uri: `${name}:${color}`, width: size, height: size })) }, + hasIcon: () => true +})); +jest.mock('~/lib/hooks/usePreviewFormatText', () => ({ __esModule: true, default: (text: string) => text.replaceAll('*', '') })); +jest.mock('~/lib/hooks/useUserStatusColor', () => ({ useUserStatusColor: (status: string) => status })); +jest.mock('~/lib/methods/getUsersPresence', () => ({ getUserPresence: jest.fn() })); +jest.mock('~/lib/methods/helpers/formatStatusExpiry', () => ({ formatStatusExpiry: (expiry?: string) => expiry })); +jest.mock('~/lib/methods/helpers/log', () => ({ __esModule: true, default: jest.fn() })); +jest.mock('~/theme', () => ({ useTheme: () => ({ colors: { fontTitlesLabels: 'title', fontSecondaryInfo: 'subtitle' } }) })); +jest.mock('~/i18n', () => ({ __esModule: true, default: { t: (key: string) => key } })); + +const fields: IHeaderFields = { + title: 'Room', + parentTitle: 'Parent', + teamMain: false, + subtitle: '*Topic*', + type: 'c', + isGroupChat: false, + abacAttributes: undefined, + disabled: false +}; +const latestOptions = () => mockSetOptions.mock.calls.at(-1)?.[0]; + +beforeEach(() => { + jest.clearAllMocks(); + mockState = { + meteor: { connected: true, connecting: false }, + server: { loading: false }, + settings: {}, + activeUsers: {}, + usersTyping: [] + }; +}); + +it('shows plain topic text and the room icon', async () => { + renderHook(() => useNativeRoomHeader(true, fields)); + await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('channel-public:title')); + expect(latestOptions()).toMatchObject({ headerTitle: 'Room', headerSubtitle: 'Topic', headerSubtitleImageSource: undefined }); +}); + +it('keeps the parent subtitle and moves its icon below the Thread title while users type', async () => { + mockState.usersTyping = ['Alice']; + renderHook(() => useNativeRoomHeader(true, { ...fields, title: '*Thread*', parentTitle: '*Parent*' }, 'thread')); + await waitFor(() => expect(latestOptions().headerSubtitleImageSource?.uri).toBe('channel-public:title')); + expect(latestOptions()).toMatchObject({ headerTitle: 'Thread', headerSubtitle: '*Parent*', headerTitleImageSource: undefined }); +}); + +it('clears the expiry clock during typing and updates the colored presence image', async () => { + mockState.activeUsers.user = { status: 'online', statusText: 'Working', statusExpiresAt: 'future' }; + const { rerender } = renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'd' }, undefined, 'user')); + await waitFor(() => expect(latestOptions().headerSubtitleImageSource?.uri).toBe('clock:subtitle')); + expect(latestOptions().headerTitleImageSource.uri).toBe('status-online:online'); + mockState = { ...mockState, usersTyping: ['Alice', 'Bob'], activeUsers: { user: { status: 'busy' } } }; + rerender({}); + await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('status-busy:busy')); + expect(latestOptions().headerSubtitle).toBe('Alice and Bob are_typing...'); + expect(latestOptions().headerSubtitleImageSource).toBeUndefined(); +}); + +it('requests missing direct-message presence but respects disabled broadcasting', async () => { + const { rerender } = renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'd' }, undefined, 'user')); + await act(async () => {}); + expect(getUserPresence).toHaveBeenCalledWith('user'); + jest.mocked(getUserPresence).mockClear(); + mockState.settings.Presence_broadcast_disabled = true; + rerender({}); + await act(async () => {}); + expect(getUserPresence).not.toHaveBeenCalled(); +}); + +it('does not configure or render native images when disabled', async () => { + renderHook(() => useNativeRoomHeader(false, fields)); + await act(async () => {}); + expect(mockSetOptions).not.toHaveBeenCalled(); + expect(IconSet.getImageSource).not.toHaveBeenCalled(); +}); + +it('uses supported Omnichannel app images and falls back when decoding fails', async () => { + const getSize = jest.spyOn(Image, 'getSize').mockImplementation(() => Promise.resolve({ width: 24, height: 24 })); + mockState.server.server = 'https://chat.example'; + let sourceType = { type: 'app', id: 'app-id', sidebarIcon: 'icon.png' } as IHeaderFields['sourceType']; + const { rerender } = renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'l', sourceType })); + await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toContain('get-sidebar-icon?icon=icon.png')); + getSize.mockImplementation(() => Promise.reject(new Error('Unsupported image'))); + sourceType = { ...sourceType, sidebarIcon: 'icon.svg' } as IHeaderFields['sourceType']; + rerender({}); + await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('omnichannel:offline')); + getSize.mockRestore(); +}); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index e11e5343274..4fa0f196605 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -1,5 +1,5 @@ import { useLayoutEffect } from 'react'; -import { PixelRatio, View } from 'react-native'; +import { PixelRatio, Platform, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { useShallow } from 'zustand/react/shallow'; import { useStore } from 'zustand'; @@ -14,6 +14,7 @@ import { type IRoomViewProps } from '../definitions'; import { type RoomStore } from '../definitions'; import { fromSubscription } from '../stores/RoomStoreContext'; import { useGoRoomActionsView } from './useGoRoomActionsView'; +import { useNativeRoomHeader } from './useNativeRoomHeader'; interface IUseHeaderParams { rid?: string; @@ -23,7 +24,7 @@ interface IUseHeaderParams { roomStore: RoomStore; } -interface IHeaderFields { +export interface IHeaderFields { prid?: string; title: string; parentTitle: string; @@ -66,6 +67,8 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa ); const roomUserId = useStore(roomStore, s => s.roomUserId); const goRoomActionsView = useGoRoomActionsView(roomStore); + const nativeTitle = Platform.OS === 'ios' && Number.parseInt(String(Platform.Version), 10) >= 26; + useNativeRoomHeader(!!rid && nativeTitle, headerFields, tmid, roomUserId); useLayoutEffect(() => { if (!rid) { @@ -80,7 +83,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa }, [rid, tmid, navigation, roomStore]); useLayoutEffect(() => { - if (!rid) { + if (!rid || nativeTitle) { return; } @@ -105,5 +108,5 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa /> ) }); - }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView]); + }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView, nativeTitle]); }; diff --git a/app/views/RoomView/hooks/useHeaderImage.ts b/app/views/RoomView/hooks/useHeaderImage.ts new file mode 100644 index 00000000000..f3e56648f53 --- /dev/null +++ b/app/views/RoomView/hooks/useHeaderImage.ts @@ -0,0 +1,45 @@ +import { useEffect, useState } from 'react'; +import { Image, type ImageSourcePropType } from 'react-native'; + +import { IconSet, type TIconsName } from '~/containers/CustomIcon'; +import log from '~/lib/methods/helpers/log'; + +export const useHeaderIconImage = (name: TIconsName | undefined, color: string, size: number) => { + const [image, setImage] = useState<{ name: TIconsName; color: string; source: ImageSourcePropType }>(); + useEffect(() => { + let cancelled = false; + if (name) { + IconSet.getImageSource(name, size, color) + .then(source => { + if (!cancelled && source) { + setImage({ name, color, source }); + } + }) + .catch(log); + } + return () => { + cancelled = true; + }; + }, [name, color, size]); + return image?.name === name && image?.color === color ? image?.source : undefined; +}; + +export const useHeaderRemoteImage = (uri?: string) => { + const [image, setImage] = useState<{ uri: string; width: number; height: number }>(); + useEffect(() => { + let cancelled = false; + if (uri) { + Image.getSize(uri) + .then(({ width, height }) => { + if (!cancelled) { + setImage({ uri, width, height }); + } + }) + .catch(log); + } + return () => { + cancelled = true; + }; + }, [uri]); + return image?.uri === uri ? image : undefined; +}; diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts new file mode 100644 index 00000000000..a73477736d7 --- /dev/null +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -0,0 +1,156 @@ +import { useEffect, useLayoutEffect } from 'react'; +import { shallowEqual } from 'react-redux'; +import { useNavigation } from '@react-navigation/native'; + +import { hasIcon, type TIconsName } from '~/containers/CustomIcon'; +import { STATUS_I18N_KEYS, type TUserStatus } from '~/definitions'; +import { type IActiveUser } from '~/reducers/activeUsers'; +import I18n from '~/i18n'; +import { useAppSelector } from '~/lib/hooks/useAppSelector'; +import usePreviewFormatText from '~/lib/hooks/usePreviewFormatText'; +import { useUserStatusColor } from '~/lib/hooks/useUserStatusColor'; +import { getUserPresence } from '~/lib/methods/getUsersPresence'; +import { formatStatusExpiry } from '~/lib/methods/helpers/formatStatusExpiry'; +import { useTheme } from '~/theme'; +import { type IRoomViewProps } from '../definitions'; +import { type IHeaderFields } from './useHeader'; +import { useHeaderIconImage, useHeaderRemoteImage } from './useHeaderImage'; + +const sourceIcons: Record = { + widget: 'livechat-monochromatic', + email: 'mail', + sms: 'sms', + app: 'omnichannel', + api: 'omnichannel', + other: 'omnichannel' +}; + +const getRoomIcon = (fields: IHeaderFields, isDirectMessage: boolean, status: TUserStatus): TIconsName => { + let roomIcon: TIconsName = 'channel-private'; + if (isDirectMessage) { + const statusIcon = `status-${status}`; + roomIcon = hasIcon(statusIcon) ? (statusIcon as TIconsName) : 'status-offline'; + } else if (fields.type === 'l' && !fields.prid) { + roomIcon = sourceIcons[fields.sourceType?.type ?? 'other'] ?? 'omnichannel'; + } else if (fields.abacAttributes?.length) { + roomIcon = fields.teamMain ? 'team-shield' : 'hash-shield'; + } else if (fields.teamMain) { + roomIcon = fields.type === 'p' ? 'teams-private' : 'teams'; + } else if (fields.prid) { + roomIcon = 'discussions'; + } else if (fields.type === 'c') { + roomIcon = 'channel-public'; + } else if (fields.type === 'd' && fields.isGroupChat) { + roomIcon = 'message'; + } + return roomIcon; +}; + +const getSubtitle = ({ + fields, + tmid, + usersTyping, + connected, + connecting, + activeUser +}: { + fields: IHeaderFields; + tmid?: string; + usersTyping: string[]; + connected: boolean; + connecting: boolean; + activeUser?: IActiveUser; +}) => { + if (tmid) { + return fields.parentTitle; + } + if (usersTyping.length) { + const names = usersTyping.join(usersTyping.length === 2 ? ` ${I18n.t('and')} ` : ', '); + return `${names} ${I18n.t(usersTyping.length > 1 ? 'are_typing' : 'is_typing')}...`; + } + if (fields.type === 'd') { + if (!connected || !activeUser) { + return undefined; + } + const presenceKey = STATUS_I18N_KEYS[activeUser.status]; + return activeUser.statusText || (presenceKey ? I18n.t(presenceKey) : undefined); + } + if (connecting) { + return I18n.t('Connecting'); + } + return connected ? fields.subtitle : I18n.t('Waiting_for_network'); +}; + +const useRoomHeaderPresence = (enabled: boolean, fields: IHeaderFields, roomUserId?: string | null) => { + const connected = useAppSelector(state => state.meteor.connected); + const presenceDisabled = useAppSelector(state => state.settings.Presence_broadcast_disabled); + const activeUser = useAppSelector(state => (roomUserId ? state.activeUsers[roomUserId] : undefined), shallowEqual); + const isDirectMessage = fields.type === 'd' && !fields.isGroupChat && !!roomUserId && !fields.prid; + const status: TUserStatus = presenceDisabled ? 'disabled' : !connected ? 'offline' : (activeUser?.status ?? 'loading'); + const visitorStatus = connected ? fields.visitor?.status : undefined; + const statusColor = useUserStatusColor(fields.type === 'l' ? (visitorStatus ?? 'offline') : status); + + useEffect(() => { + if (enabled && isDirectMessage && connected && status === 'loading' && roomUserId) { + getUserPresence(roomUserId); + } + }, [enabled, isDirectMessage, connected, status, roomUserId]); + + return { connected, activeUser, isDirectMessage, status, statusColor }; +}; + +const useRoomHeaderContent = ( + fields: IHeaderFields, + tmid: string | undefined, + activeUser: IActiveUser | undefined, + connected: boolean +) => { + const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading); + const usersTyping = useAppSelector(state => state.usersTyping, shallowEqual); + const typing = !tmid && usersTyping.length > 0; + const subtitle = getSubtitle({ fields, tmid, usersTyping, connected, connecting, activeUser }); + const plainTitle = usePreviewFormatText(fields.title); + const formattedSubtitle = usePreviewFormatText(subtitle ?? ''); + const hasStatusExpiry = fields.type === 'd' && connected && !!formatStatusExpiry(activeUser?.statusExpiresAt); + return { + title: tmid || fields.prid ? plainTitle : fields.title, + subtitle: tmid || typing ? subtitle : formattedSubtitle, + showClock: !tmid && !typing && !!subtitle && hasStatusExpiry + }; +}; + +export const useNativeRoomHeader = (enabled: boolean, fields: IHeaderFields, tmid?: string, roomUserId?: string | null) => { + const navigation = useNavigation(); + const { colors } = useTheme(); + const { connected, activeUser, isDirectMessage, status, statusColor } = useRoomHeaderPresence(enabled, fields, roomUserId); + + const { title, subtitle, showClock } = useRoomHeaderContent(fields, tmid, activeUser, connected); + + const roomIcon = getRoomIcon(fields, isDirectMessage, status); + const glyphImage = useHeaderIconImage( + enabled && fields.type ? roomIcon : undefined, + isDirectMessage || fields.type === 'l' ? statusColor : colors.fontTitlesLabels, + 16 + ); + const server = useAppSelector(state => state.server.server); + const source = fields.sourceType; + const remoteUri = + enabled && connected && fields.type === 'l' && source?.type === 'app' && source.id && source.sidebarIcon + ? `${server}/api/apps/public/${source.id}/get-sidebar-icon?icon=${source.sidebarIcon}` + : undefined; + const remoteImage = useHeaderRemoteImage(remoteUri); + const roomImage = remoteImage ?? glyphImage; + const clockImage = useHeaderIconImage(enabled && showClock ? 'clock' : undefined, colors.fontSecondaryInfo, 12); + const subtitleImage = tmid ? roomImage : clockImage; + + useLayoutEffect(() => { + if (enabled) { + navigation.setOptions({ + headerTitle: title, + headerSubtitle: subtitle || undefined, + headerTitleImageSource: tmid ? undefined : roomImage, + headerSubtitleImageSource: subtitleImage + }); + } + }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage]); +}; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f2a0eb6a6a2..c389682a62f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3518,7 +3518,7 @@ SPEC CHECKSUMS: React: 52dce66f71097ed7a4968673d95eeb8095ded74e React-callinvoker: afa0525ab27f4ffbb4c377f855fe9368edb099bc React-Core: fdb67992e049f2526ade3e258c21b8404e7943a3 - React-Core-prebuilt: 9abc518a8aa7aa4fa3bbbe52bc825bcd9fb3c3ce + React-Core-prebuilt: 125b040461c34b44babc54cb7f0730b2d97de6d2 React-CoreModules: 456d91152aa283ddc6ae8abfcec3292af88912f6 React-cxxreact: 73a20c0dde5ec221f98d5e4da0e71349ab48353d React-debug: 4a1d2ef3086d88dd35f28cfb5da803584f819924 diff --git a/metro.config.js b/metro.config.js index bd22faf1ff1..095f759a07c 100644 --- a/metro.config.js +++ b/metro.config.js @@ -1,3 +1,4 @@ +const fs = require('fs'); const path = require('path'); const withStorybook = require('@storybook/react-native/metro/withStorybook'); @@ -8,11 +9,32 @@ const defaultConfig = getDefaultConfig(__dirname); const sourceExts = [...defaultConfig.resolver.sourceExts, 'mjs']; +const nativeStackPath = path.join(__dirname, 'node_modules/@react-navigation/native-stack'); +const localNavigation = fs.lstatSync(nativeStackPath).isSymbolicLink(); +const localPackagePaths = [ + fs.realpathSync(nativeStackPath), + fs.realpathSync(path.join(__dirname, 'node_modules/react-native-screens')) +]; + const config = { + ...(localNavigation && { + watchFolders: localPackagePaths + }), transformer: { unstable_allowRequireContext: true }, resolver: { + ...(localNavigation && { + nodeModulesPaths: [path.join(__dirname, 'node_modules')], + resolveRequest: (context, moduleName, platform) => + context.resolveRequest( + localPackagePaths.some(directory => context.originModulePath.startsWith(`${directory}${path.sep}`)) + ? { ...context, disableHierarchicalLookup: true } + : context, + moduleName === '@react-navigation/native-stack' ? path.join(nativeStackPath, 'src/index.tsx') : moduleName, + platform + ) + }), // When running E2E tests, prioritize .mock.ts files for app code sourceExts: process.env.RUNNING_E2E_TESTS === 'true' ? ['mock.ts', ...sourceExts] : sourceExts } diff --git a/tsconfig.json b/tsconfig.json index 8bf509d9040..af8d8a336ae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "target": "esnext", "module": "esnext", "moduleResolution": "bundler", + "preserveSymlinks": true, "jsx": "react-native", "allowJs": true, "noEmit": true, @@ -17,6 +18,7 @@ "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "paths": { + "@react-navigation/native-stack": ["./node_modules/@react-navigation/native-stack/src/index.tsx"], "~/*": ["./app/*"], "~/scripts/*": ["./scripts/*"], "~/.rnstorybook/*": ["./.rnstorybook/*"] From 8b4ddd4fb60aa1f93b889adfe258fa89159a0f7e Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 21 Sep 2026 15:27:21 -0300 Subject: [PATCH 049/120] fix local navigation package resolution --- config/import-aliases.js | 3 ++- metro.config.js | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/config/import-aliases.js b/config/import-aliases.js index 0db92e7530a..67e1f028991 100644 --- a/config/import-aliases.js +++ b/config/import-aliases.js @@ -5,6 +5,7 @@ const tsconfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../tsconfig const paths = tsconfig.compilerOptions.paths; const entries = Object.entries(paths).sort(([left], [right]) => right.length - left.length); +const babelEntries = entries.filter(([, [value]]) => !value.startsWith('./node_modules/')); const stripWildcard = value => value.replace(/\*$/, ''); const stripAliasSuffix = value => stripWildcard(value).replace(/\/$/, ''); @@ -12,7 +13,7 @@ const relativeTarget = value => stripAliasSuffix(value).replace(/^\.\//, ''); const relativeTargetWithSlash = value => stripWildcard(value).replace(/^\.\//, ''); const getAliasConfig = () => ({ - babel: Object.fromEntries(entries.map(([key, [value]]) => [stripAliasSuffix(key), `./${relativeTarget(value)}`])), + babel: Object.fromEntries(babelEntries.map(([key, [value]]) => [stripAliasSuffix(key), `./${relativeTarget(value)}`])), jest: Object.fromEntries( entries.map(([key, [value]]) => [ `^${stripWildcard(key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*)$`, diff --git a/metro.config.js b/metro.config.js index 095f759a07c..67c1bf90d73 100644 --- a/metro.config.js +++ b/metro.config.js @@ -25,13 +25,21 @@ const config = { }, resolver: { ...(localNavigation && { - nodeModulesPaths: [path.join(__dirname, 'node_modules')], + nodeModulesPaths: [ + path.join(__dirname, 'node_modules'), + path.join(localPackagePaths[0], 'node_modules'), + path.join(localPackagePaths[1], 'node_modules') + ], resolveRequest: (context, moduleName, platform) => context.resolveRequest( localPackagePaths.some(directory => context.originModulePath.startsWith(`${directory}${path.sep}`)) ? { ...context, disableHierarchicalLookup: true } : context, - moduleName === '@react-navigation/native-stack' ? path.join(nativeStackPath, 'src/index.tsx') : moduleName, + moduleName === '@react-navigation/native-stack' + ? path.join(localPackagePaths[0], 'src/index.tsx') + : moduleName === 'react-native-screens' + ? path.join(localPackagePaths[1], 'src/index.tsx') + : moduleName, platform ) }), From c589eb61f1f5198a96bdb6880e3703111993c976 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 21 Sep 2026 15:36:00 -0300 Subject: [PATCH 050/120] use local libs --- ios/Podfile.lock | 2 +- package.json | 4 +- ...react-navigation+native-stack+7.19.2.patch | 116 ---------------- patches/react-native-screens+4.28.0.patch | 126 ------------------ pnpm-lock.yaml | 60 +-------- 5 files changed, 10 insertions(+), 298 deletions(-) delete mode 100644 patches/@react-navigation+native-stack+7.19.2.patch delete mode 100644 patches/react-native-screens+4.28.0.patch diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c389682a62f..f2a0eb6a6a2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3518,7 +3518,7 @@ SPEC CHECKSUMS: React: 52dce66f71097ed7a4968673d95eeb8095ded74e React-callinvoker: afa0525ab27f4ffbb4c377f855fe9368edb099bc React-Core: fdb67992e049f2526ade3e258c21b8404e7943a3 - React-Core-prebuilt: 125b040461c34b44babc54cb7f0730b2d97de6d2 + React-Core-prebuilt: 9abc518a8aa7aa4fa3bbbe52bc825bcd9fb3c3ce React-CoreModules: 456d91152aa283ddc6ae8abfcec3292af88912f6 React-cxxreact: 73a20c0dde5ec221f98d5e4da0e71349ab48353d React-debug: 4a1d2ef3086d88dd35f28cfb5da803584f819924 diff --git a/package.json b/package.json index ca9e103cd16..768e405cae9 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@react-navigation/drawer": "^7.14.1", "@react-navigation/elements": "^2.9.42", "@react-navigation/native": "^7.4.1", - "@react-navigation/native-stack": "^7.19.1", + "@react-navigation/native-stack": "link:../../../../Work/react-navigation/packages/native-stack", "@rocket.chat/media-signaling": "1.0.0-rc.1", "@rocket.chat/message-parser": "0.31.36", "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto#main", @@ -118,7 +118,7 @@ "react-native-popover-view": "5.1.7", "react-native-reanimated": "4.6.0", "react-native-safe-area-context": "^5.10.0", - "react-native-screens": "~4.28.0", + "react-native-screens": "link:../../../../Work/react-native-screens", "react-native-skeleton-placeholder": "5.2.4", "react-native-svg": "^15.12.1", "react-native-url-polyfill": "2.0.0", diff --git a/patches/@react-navigation+native-stack+7.19.2.patch b/patches/@react-navigation+native-stack+7.19.2.patch deleted file mode 100644 index e76c2a22d2c..00000000000 --- a/patches/@react-navigation+native-stack+7.19.2.patch +++ /dev/null @@ -1,116 +0,0 @@ -diff --git a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js -index a56cb12..28697a3 100644 ---- a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js -+++ b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js -@@ -150,6 +150,7 @@ export function useHeaderConfigProps({ - headerBlurEffect, - headerTintColor, - headerTitle, -+ headerSubtitle, - headerTitleAlign, - headerTitleStyle, - headerTransparent, -@@ -230,6 +231,12 @@ export function useHeaderConfigProps({ - tintColor, - children: titleText - }) : null; -+ if (process.env.NODE_ENV !== 'production') { -+ if (headerTitleElement != null && headerSubtitle != null) { -+ console.warn(`'headerSubtitle' is ignored when 'headerTitle' renders a custom element for the screen '${route.name}'. Remove 'headerSubtitle' or use a string 'headerTitle' instead.`); -+ } -+ } -+ const subtitleText = headerTitleElement != null ? undefined : headerSubtitle; - const supportsHeaderSearchBar = typeof isSearchBarAvailableForCurrentPlatform === 'boolean' ? isSearchBarAvailableForCurrentPlatform : - // Fallback for older versions of react-native-screens - Platform.OS === 'ios' && SearchBar != null; -@@ -357,6 +364,7 @@ export function useHeaderConfigProps({ - largeTitleFontWeight, - largeTitleHideShadow: headerLargeTitleShadowVisible === false, - title: titleText, -+ subtitle: subtitleText, - titleColor, - titleFontFamily, - titleFontSize, -diff --git a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts -index c32d1da..71ddc38 100644 ---- a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts -+++ b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts -@@ -321,6 +321,18 @@ export type NativeStackNavigationOptions = { - */ - tintColor?: string; - }) => React.ReactNode); -+ /** -+ * String to display in the header underneath the title. -+ * -+ * Ignored when `headerTitle` renders a custom element. -+ * -+ * Only supported on iOS. -+ * -+ * @platform ios -+ * -+ * @supported iOS 26 or higher -+ */ -+ headerSubtitle?: string; - /** - * How to align the the header title. - * Defaults to `left` on platforms other than iOS. -diff --git a/node_modules/@react-navigation/native-stack/src/types.tsx b/node_modules/@react-navigation/native-stack/src/types.tsx -index aeb4939..f5ea8e8 100644 ---- a/node_modules/@react-navigation/native-stack/src/types.tsx -+++ b/node_modules/@react-navigation/native-stack/src/types.tsx -@@ -371,6 +371,18 @@ export type NativeStackNavigationOptions = { - */ - tintColor?: string; - }) => React.ReactNode); -+ /** -+ * String to display in the header underneath the title. -+ * -+ * Ignored when `headerTitle` renders a custom element. -+ * -+ * Only supported on iOS. -+ * -+ * @platform ios -+ * -+ * @supported iOS 26 or higher -+ */ -+ headerSubtitle?: string; - /** - * How to align the the header title. - * Defaults to `left` on platforms other than iOS. -diff --git a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx -index a7e3004..c2dd78a 100644 ---- a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx -+++ b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx -@@ -211,6 +211,7 @@ export function useHeaderConfigProps({ - headerBlurEffect, - headerTintColor, - headerTitle, -+ headerSubtitle, - headerTitleAlign, - headerTitleStyle, - headerTransparent, -@@ -330,6 +331,16 @@ export function useHeaderConfigProps({ - }) - : null; - -+ if (process.env.NODE_ENV !== 'production') { -+ if (headerTitleElement != null && headerSubtitle != null) { -+ console.warn( -+ `'headerSubtitle' is ignored when 'headerTitle' renders a custom element for the screen '${route.name}'. Remove 'headerSubtitle' or use a string 'headerTitle' instead.` -+ ); -+ } -+ } -+ -+ const subtitleText = headerTitleElement != null ? undefined : headerSubtitle; -+ - const supportsHeaderSearchBar = - typeof isSearchBarAvailableForCurrentPlatform === 'boolean' - ? isSearchBarAvailableForCurrentPlatform -@@ -523,6 +534,7 @@ export function useHeaderConfigProps({ - largeTitleFontWeight, - largeTitleHideShadow: headerLargeTitleShadowVisible === false, - title: titleText, -+ subtitle: subtitleText, - titleColor, - titleFontFamily, - titleFontSize, diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch deleted file mode 100644 index 20d6784108f..00000000000 --- a/patches/react-native-screens+4.28.0.patch +++ /dev/null @@ -1,126 +0,0 @@ -diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt -index fa1b773..b2c5167 100644 ---- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt -+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt -@@ -301,6 +301,11 @@ class ScreenStackHeaderConfigViewManager : - value: String?, - ) = Unit - -+ override fun setSubtitle( -+ view: ScreenStackHeaderConfig?, -+ value: String?, -+ ) = Unit -+ - override fun setTopInsetEnabled( - config: ScreenStackHeaderConfig, - topInsetEnabled: Boolean, -diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h -index 8a41697..ead4691 100644 ---- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h -+++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h -@@ -23,6 +23,7 @@ - NS_ASSUME_NONNULL_BEGIN - - @property (nonatomic, retain) NSString *title; -+@property (nonatomic, retain) NSString *subtitle; - @property (nonatomic, retain) NSString *titleFontFamily; - @property (nonatomic, retain) NSNumber *titleFontSize; - @property (nonatomic, retain) NSString *titleFontWeight; -diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm -index a54cb63..7a7d6b4 100644 ---- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm -+++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm -@@ -507,6 +507,11 @@ RNS_IGNORE_SUPER_CALL_END - - if (shouldHide) { - navitem.title = config.title; -+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) -+ if (@available(iOS 26.0, *)) { -+ navitem.subtitle = config.subtitle; -+ } -+#endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) - - // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items. - [navctr setNavigationBarHidden:YES animated:animated]; -@@ -648,6 +653,11 @@ RNS_IGNORE_SUPER_CALL_END - // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug). - // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments) - navitem.title = config.title; -+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) -+ if (@available(iOS 26.0, *)) { -+ navitem.subtitle = config.subtitle; -+ } -+#endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) - navitem.leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems - withCurrentItems:navitem.leftBarButtonItems]; - navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems -@@ -1025,6 +1035,7 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) - } - - _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title); -+ _subtitle = RCTNSStringFromStringNilIfEmpty(newScreenProps.subtitle); - if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) { - _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily); - } -diff --git a/node_modules/react-native-screens/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts b/node_modules/react-native-screens/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts -index ec39792..785af97 100644 ---- a/node_modules/react-native-screens/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts -+++ b/node_modules/react-native-screens/lib/typescript/fabric/ScreenStackHeaderConfigNativeComponent.d.ts -@@ -32,6 +32,7 @@ export interface NativeProps extends ViewProps { - largeTitleColor?: ColorValue | undefined; - translucent?: boolean | undefined; - title?: string | undefined; -+ subtitle?: string | undefined; - titleFontFamily?: string | undefined; - titleFontSize?: CT.Int32 | undefined; - titleFontWeight?: string | undefined; -diff --git a/node_modules/react-native-screens/lib/typescript/types.d.ts b/node_modules/react-native-screens/lib/typescript/types.d.ts -index ff37881..7a7da9a 100644 ---- a/node_modules/react-native-screens/lib/typescript/types.d.ts -+++ b/node_modules/react-native-screens/lib/typescript/types.d.ts -@@ -723,6 +723,14 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { - * String that can be displayed in the header as a fallback for `headerTitle`. - */ - title?: string | undefined; -+ /** -+ * String to display in the header underneath the title. -+ * -+ * @platform ios -+ * -+ * @supported iOS 26 or higher -+ */ -+ subtitle?: string | undefined; - /** - * Allows for setting text color of the title. - */ -diff --git a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts -index da28c51..ef0cb5f 100644 ---- a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts -+++ b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts -@@ -66,6 +66,7 @@ export interface NativeProps extends ViewProps { - largeTitleColor?: ColorValue | undefined; - translucent?: boolean | undefined; - title?: string | undefined; -+ subtitle?: string | undefined; - titleFontFamily?: string | undefined; - titleFontSize?: CT.Int32 | undefined; - titleFontWeight?: string | undefined; -diff --git a/node_modules/react-native-screens/src/types.tsx b/node_modules/react-native-screens/src/types.tsx -index 8594b73..c137d06 100644 ---- a/node_modules/react-native-screens/src/types.tsx -+++ b/node_modules/react-native-screens/src/types.tsx -@@ -827,6 +827,14 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { - * String that can be displayed in the header as a fallback for `headerTitle`. - */ - title?: string | undefined; -+ /** -+ * String to display in the header underneath the title. -+ * -+ * @platform ios -+ * -+ * @supported iOS 26 or higher -+ */ -+ subtitle?: string | undefined; - /** - * Allows for setting text color of the title. - */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f24110a636..a0d091d07c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,7 +77,7 @@ importers: version: 0.86.3(@babel/core@7.29.7) '@react-navigation/drawer': specifier: ^7.14.1 - version: 7.14.2(dda132b9c8f311fd0053bf630a6001aa) + version: 7.14.2(4657b9012d6122af76bbab8fafd3d31e) '@react-navigation/elements': specifier: ^2.9.42 version: 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) @@ -85,8 +85,8 @@ importers: specifier: ^7.4.1 version: 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': - specifier: ^7.19.1 - version: 7.19.2(9f93b74e18ae1df429c2a9dc7118e4c2) + specifier: link:../../../../Work/react-navigation/packages/native-stack + version: link:../../../../Work/react-navigation/packages/native-stack '@rocket.chat/media-signaling': specifier: 1.0.0-rc.1 version: 1.0.0-rc.1 @@ -292,8 +292,8 @@ importers: specifier: ^5.10.0 version: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-screens: - specifier: ~4.28.0 - version: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + specifier: link:../../../../Work/react-native-screens + version: link:../../../../Work/react-native-screens react-native-skeleton-placeholder: specifier: 5.2.4 version: 5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -2723,15 +2723,6 @@ packages: '@react-native-masked-view/masked-view': optional: true - '@react-navigation/native-stack@7.19.2': - resolution: {integrity: sha512-D6hmtgpMZz1x8R0PLbfqqKh5ajEkb13DguE7X/MNNKYL4riY4zD3TrjbcesUC2EZklpQ/EoJ6h6HAhMNfCCsoQ==} - peerDependencies: - '@react-navigation/native': ^7.4.1 - react: '>= 18.2.0' - react-native: '*' - react-native-safe-area-context: '>= 4.0.0' - react-native-screens: '>= 4.0.0' - '@react-navigation/native@7.4.1': resolution: {integrity: sha512-pEnu5XzAXV6RWr6A6pmZ7qLB+O6TJWMhBO25AjckMdr596UWulMPHQCtBcnOUfg6K+2h9ZUctHF2E5Xu4vcOrQ==} peerDependencies: @@ -6610,12 +6601,6 @@ packages: peerDependencies: react: ^19.2.3 - react-freeze@1.0.4: - resolution: {integrity: sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==} - engines: {node: '>=10'} - peerDependencies: - react: '>=17.0.0' - react-hook-form@7.34.2: resolution: {integrity: sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ==} engines: {node: '>=12.22.0'} @@ -6815,12 +6800,6 @@ packages: react: '*' react-native: '*' - react-native-screens@4.28.0: - resolution: {integrity: sha512-0/79Z8RCibuaAHti+DJ2Dq0m6UFWh4+p1bH58K3cjiQ7IDWs5kU9tCAj4lXcJMHHonbF2mIE2BeaPSEvOpIiFg==} - peerDependencies: - react: '*' - react-native: '*' - react-native-skeleton-placeholder@5.2.4: resolution: {integrity: sha512-OZntVq1hU1UX33FltxK2ezT2v9vHIhV8YnEbnMWUCvxT0N9OsgD1qxiHm6qb9YRJVgq2o5z3S7dNPsPnDF/jNg==} peerDependencies: @@ -10920,7 +10899,7 @@ snapshots: use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/drawer@7.14.2(dda132b9c8f311fd0053bf630a6001aa)': + '@react-navigation/drawer@7.14.2(4657b9012d6122af76bbab8fafd3d31e)': dependencies: '@react-navigation/elements': 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -10931,7 +10910,7 @@ snapshots: react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-screens: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-screens: link:../../../../Work/react-native-screens use-latest-callback: 0.2.6(react@19.2.3) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -10948,20 +10927,6 @@ snapshots: optionalDependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - '@react-navigation/native-stack@7.19.2(9f93b74e18ae1df429c2a9dc7118e4c2)': - dependencies: - '@react-navigation/elements': 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) - '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - color: 4.2.3 - react: 19.2.3 - react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-screens: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - sf-symbols-typescript: 2.2.0 - warn-once: 0.1.1 - transitivePeerDependencies: - - '@react-native-masked-view/masked-view' - '@react-navigation/native@7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: '@react-navigation/core': 7.22.1(react@19.2.3) @@ -15580,10 +15545,6 @@ snapshots: react: 19.2.3 scheduler: 0.27.0 - react-freeze@1.0.4(react@19.2.3): - dependencies: - react: 19.2.3 - react-hook-form@7.34.2(react@19.2.3): dependencies: react: 19.2.3 @@ -15781,13 +15742,6 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-screens@4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): - dependencies: - react: 19.2.3 - react-freeze: 1.0.4(react@19.2.3) - react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - warn-once: 0.1.1 - react-native-skeleton-placeholder@5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) From bcb8d5355d8d4a29c46d1393a4ce4dad832e9502 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 22 Sep 2026 20:29:12 -0300 Subject: [PATCH 051/120] chore: regenerate native-stack and react-native-screens patches from fork Derive patches/@react-navigation+native-stack+7.19.2.patch and patches/react-native-screens+4.28.0.patch directly from the diegolmello forks' feat/legacy-header-subtitle branches (diffed against their merge-base with the registry release), instead of the prior hand-written headerSubtitle-only patch. Adds headerTitleImageSource and headerSubtitleImageSource, which useNativeRoomHeader already expects. Drop the tsconfig path alias that forced tsc to resolve @react-navigation/native-stack straight to the package's .tsx source; the patch now covers lib/typescript/src/types.d.ts, so the alias is no longer needed and was the cause of a stale type-checking failure. --- config/import-aliases.js | 3 +- ios/Podfile.lock | 2 +- metro.config.js | 12 +- package.json | 4 +- ...react-navigation+native-stack+7.19.2.patch | 182 +++++ patches/react-native-screens+4.28.0.patch | 649 ++++++++++++++++++ pnpm-lock.yaml | 60 +- tsconfig.json | 1 - 8 files changed, 890 insertions(+), 23 deletions(-) create mode 100644 patches/@react-navigation+native-stack+7.19.2.patch create mode 100644 patches/react-native-screens+4.28.0.patch diff --git a/config/import-aliases.js b/config/import-aliases.js index 67e1f028991..0db92e7530a 100644 --- a/config/import-aliases.js +++ b/config/import-aliases.js @@ -5,7 +5,6 @@ const tsconfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../tsconfig const paths = tsconfig.compilerOptions.paths; const entries = Object.entries(paths).sort(([left], [right]) => right.length - left.length); -const babelEntries = entries.filter(([, [value]]) => !value.startsWith('./node_modules/')); const stripWildcard = value => value.replace(/\*$/, ''); const stripAliasSuffix = value => stripWildcard(value).replace(/\/$/, ''); @@ -13,7 +12,7 @@ const relativeTarget = value => stripAliasSuffix(value).replace(/^\.\//, ''); const relativeTargetWithSlash = value => stripWildcard(value).replace(/^\.\//, ''); const getAliasConfig = () => ({ - babel: Object.fromEntries(babelEntries.map(([key, [value]]) => [stripAliasSuffix(key), `./${relativeTarget(value)}`])), + babel: Object.fromEntries(entries.map(([key, [value]]) => [stripAliasSuffix(key), `./${relativeTarget(value)}`])), jest: Object.fromEntries( entries.map(([key, [value]]) => [ `^${stripWildcard(key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*)$`, diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f2a0eb6a6a2..c389682a62f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3518,7 +3518,7 @@ SPEC CHECKSUMS: React: 52dce66f71097ed7a4968673d95eeb8095ded74e React-callinvoker: afa0525ab27f4ffbb4c377f855fe9368edb099bc React-Core: fdb67992e049f2526ade3e258c21b8404e7943a3 - React-Core-prebuilt: 9abc518a8aa7aa4fa3bbbe52bc825bcd9fb3c3ce + React-Core-prebuilt: 125b040461c34b44babc54cb7f0730b2d97de6d2 React-CoreModules: 456d91152aa283ddc6ae8abfcec3292af88912f6 React-cxxreact: 73a20c0dde5ec221f98d5e4da0e71349ab48353d React-debug: 4a1d2ef3086d88dd35f28cfb5da803584f819924 diff --git a/metro.config.js b/metro.config.js index 67c1bf90d73..095f759a07c 100644 --- a/metro.config.js +++ b/metro.config.js @@ -25,21 +25,13 @@ const config = { }, resolver: { ...(localNavigation && { - nodeModulesPaths: [ - path.join(__dirname, 'node_modules'), - path.join(localPackagePaths[0], 'node_modules'), - path.join(localPackagePaths[1], 'node_modules') - ], + nodeModulesPaths: [path.join(__dirname, 'node_modules')], resolveRequest: (context, moduleName, platform) => context.resolveRequest( localPackagePaths.some(directory => context.originModulePath.startsWith(`${directory}${path.sep}`)) ? { ...context, disableHierarchicalLookup: true } : context, - moduleName === '@react-navigation/native-stack' - ? path.join(localPackagePaths[0], 'src/index.tsx') - : moduleName === 'react-native-screens' - ? path.join(localPackagePaths[1], 'src/index.tsx') - : moduleName, + moduleName === '@react-navigation/native-stack' ? path.join(nativeStackPath, 'src/index.tsx') : moduleName, platform ) }), diff --git a/package.json b/package.json index 768e405cae9..ca9e103cd16 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@react-navigation/drawer": "^7.14.1", "@react-navigation/elements": "^2.9.42", "@react-navigation/native": "^7.4.1", - "@react-navigation/native-stack": "link:../../../../Work/react-navigation/packages/native-stack", + "@react-navigation/native-stack": "^7.19.1", "@rocket.chat/media-signaling": "1.0.0-rc.1", "@rocket.chat/message-parser": "0.31.36", "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto#main", @@ -118,7 +118,7 @@ "react-native-popover-view": "5.1.7", "react-native-reanimated": "4.6.0", "react-native-safe-area-context": "^5.10.0", - "react-native-screens": "link:../../../../Work/react-native-screens", + "react-native-screens": "~4.28.0", "react-native-skeleton-placeholder": "5.2.4", "react-native-svg": "^15.12.1", "react-native-url-polyfill": "2.0.0", diff --git a/patches/@react-navigation+native-stack+7.19.2.patch b/patches/@react-navigation+native-stack+7.19.2.patch new file mode 100644 index 00000000000..12732043d24 --- /dev/null +++ b/patches/@react-navigation+native-stack+7.19.2.patch @@ -0,0 +1,182 @@ +diff --git a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js +index a56cb12..301d782 100644 +--- a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js ++++ b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js +@@ -149,7 +149,10 @@ export function useHeaderConfigProps({ + headerStyle, + headerBlurEffect, + headerTintColor, ++ headerSubtitle, ++ headerSubtitleImageSource, + headerTitle, ++ headerTitleImageSource, + headerTitleAlign, + headerTitleStyle, + headerTransparent, +@@ -230,6 +233,14 @@ export function useHeaderConfigProps({ + tintColor, + children: titleText + }) : null; ++ if (process.env.NODE_ENV !== 'production') { ++ if (headerTitleElement != null && headerSubtitle != null) { ++ console.warn(`'headerSubtitle' is ignored when 'headerTitle' renders a custom element for the screen '${route.name}'. Remove 'headerSubtitle' or use a string 'headerTitle' instead.`); ++ } ++ } ++ const subtitleText = headerTitleElement != null ? undefined : headerSubtitle; ++ const titleImageSource = headerTitleElement != null ? undefined : headerTitleImageSource; ++ const subtitleImageSource = headerTitleElement != null ? undefined : headerSubtitleImageSource; + const supportsHeaderSearchBar = typeof isSearchBarAvailableForCurrentPlatform === 'boolean' ? isSearchBarAvailableForCurrentPlatform : + // Fallback for older versions of react-native-screens + Platform.OS === 'ios' && SearchBar != null; +@@ -356,7 +367,10 @@ export function useHeaderConfigProps({ + largeTitleFontSize, + largeTitleFontWeight, + largeTitleHideShadow: headerLargeTitleShadowVisible === false, ++ subtitle: subtitleText, ++ subtitleImageSource, + title: titleText, ++ titleImageSource, + titleColor, + titleFontFamily, + titleFontSize, +diff --git a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts +index c32d1da..4717022 100644 +--- a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts ++++ b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts +@@ -321,6 +321,42 @@ export type NativeStackNavigationOptions = { + */ + tintColor?: string; + }) => React.ReactNode); ++ /** ++ * Image rendered as a leading glyph before the header title. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerTitleImageSource?: ImageSourcePropType; ++ /** ++ * String to display in the header underneath the title. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerSubtitle?: string; ++ /** ++ * Image rendered as a leading glyph before the header subtitle. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerSubtitleImageSource?: ImageSourcePropType; + /** + * How to align the the header title. + * Defaults to `left` on platforms other than iOS. +diff --git a/node_modules/@react-navigation/native-stack/src/types.tsx b/node_modules/@react-navigation/native-stack/src/types.tsx +index aeb4939..1373158 100644 +--- a/node_modules/@react-navigation/native-stack/src/types.tsx ++++ b/node_modules/@react-navigation/native-stack/src/types.tsx +@@ -371,6 +371,42 @@ export type NativeStackNavigationOptions = { + */ + tintColor?: string; + }) => React.ReactNode); ++ /** ++ * Image rendered as a leading glyph before the header title. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerTitleImageSource?: ImageSourcePropType; ++ /** ++ * String to display in the header underneath the title. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerSubtitle?: string; ++ /** ++ * Image rendered as a leading glyph before the header subtitle. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerSubtitleImageSource?: ImageSourcePropType; + /** + * How to align the the header title. + * Defaults to `left` on platforms other than iOS. +diff --git a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx +index a7e3004..3addeae 100644 +--- a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx ++++ b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx +@@ -210,7 +210,10 @@ export function useHeaderConfigProps({ + headerStyle, + headerBlurEffect, + headerTintColor, ++ headerSubtitle, ++ headerSubtitleImageSource, + headerTitle, ++ headerTitleImageSource, + headerTitleAlign, + headerTitleStyle, + headerTransparent, +@@ -330,6 +333,20 @@ export function useHeaderConfigProps({ + }) + : null; + ++ if (process.env.NODE_ENV !== 'production') { ++ if (headerTitleElement != null && headerSubtitle != null) { ++ console.warn( ++ `'headerSubtitle' is ignored when 'headerTitle' renders a custom element for the screen '${route.name}'. Remove 'headerSubtitle' or use a string 'headerTitle' instead.` ++ ); ++ } ++ } ++ ++ const subtitleText = headerTitleElement != null ? undefined : headerSubtitle; ++ const titleImageSource = ++ headerTitleElement != null ? undefined : headerTitleImageSource; ++ const subtitleImageSource = ++ headerTitleElement != null ? undefined : headerSubtitleImageSource; ++ + const supportsHeaderSearchBar = + typeof isSearchBarAvailableForCurrentPlatform === 'boolean' + ? isSearchBarAvailableForCurrentPlatform +@@ -522,7 +539,10 @@ export function useHeaderConfigProps({ + largeTitleFontSize, + largeTitleFontWeight, + largeTitleHideShadow: headerLargeTitleShadowVisible === false, ++ subtitle: subtitleText, ++ subtitleImageSource, + title: titleText, ++ titleImageSource, + titleColor, + titleFontFamily, + titleFontSize, diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch new file mode 100644 index 00000000000..6ca90879d00 --- /dev/null +++ b/patches/react-native-screens+4.28.0.patch @@ -0,0 +1,649 @@ +diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +index fa1b7731..153d712e 100644 +--- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt ++++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +@@ -3,6 +3,7 @@ package com.swmansion.rnscreens + import android.view.View + import com.facebook.react.bridge.JSApplicationCausedNativeException + import com.facebook.react.bridge.ReadableArray ++import com.facebook.react.bridge.ReadableMap + import com.facebook.react.module.annotations.ReactModule + import com.facebook.react.uimanager.ReactStylesDiffMap + import com.facebook.react.uimanager.StateWrapper +@@ -236,6 +237,21 @@ class ScreenStackHeaderConfigViewManager : + value: String?, + ) = Unit + ++ override fun setSubtitle( ++ view: ScreenStackHeaderConfig?, ++ value: String?, ++ ) = Unit ++ ++ override fun setTitleImageSource( ++ view: ScreenStackHeaderConfig?, ++ value: ReadableMap?, ++ ) = Unit ++ ++ override fun setSubtitleImageSource( ++ view: ScreenStackHeaderConfig?, ++ value: ReadableMap?, ++ ) = Unit ++ + override fun setBackTitleFontFamily( + view: ScreenStackHeaderConfig?, + value: String?, +diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h +index 8a416972..9b13f60d 100644 +--- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h ++++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h +@@ -8,10 +8,14 @@ + #import "RNSScreenStackHeaderSubview.h" + #import "RNSSearchBar.h" + ++@class RCTImageSource; ++ + @interface NSString (RNSStringUtil) + + + (BOOL)rnscreens_isBlankOrNull:(nullable NSString *)string; + +++ (nullable NSString *)rnscreens_stringByStrippingTitleGlyph:(nullable NSString *)string; ++ + @end + + @interface RNSScreenStackHeaderConfig : RNSReactBaseView +@@ -23,10 +27,13 @@ + NS_ASSUME_NONNULL_BEGIN + + @property (nonatomic, retain) NSString *title; ++@property (nonatomic, retain) NSString *subtitle; + @property (nonatomic, retain) NSString *titleFontFamily; + @property (nonatomic, retain) NSNumber *titleFontSize; + @property (nonatomic, retain) NSString *titleFontWeight; + @property (nonatomic, retain) UIColor *titleColor; ++@property (nonatomic, retain, nullable) RCTImageSource *titleImageSource; ++@property (nonatomic, retain, nullable) RCTImageSource *subtitleImageSource; + @property (nonatomic, retain) NSString *backTitle; + @property (nonatomic, retain) NSString *backTitleFontFamily; + @property (nonatomic, retain) NSNumber *backTitleFontSize; +diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +index a54cb632..43905c9d 100644 +--- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm ++++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +@@ -13,6 +13,7 @@ + #import + #import + #import ++#import + #import + #import + #import "RCTImageComponentView+RNSScreenStackHeaderConfig.h" +@@ -20,6 +21,7 @@ + #import "RNSBarButtonItem.h" + #import "RNSConvert.h" + #import "RNSDefines.h" ++#import "RNSImageLoadingHelper.h" + #import "RNSScreen.h" + #import "RNSSearchBar.h" + #import "UINavigationBar+RNSUtility.h" +@@ -28,6 +30,10 @@ namespace react = facebook::react; + + static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17; + static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; ++static const NSNumber *const DEFAULT_SUBTITLE_FONT_SIZE = @12; ++ ++#define RNS_TITLE_GLYPH_PLACEHOLDER @"\uFFFC" ++#define RNS_TITLE_GLYPH_SEPARATOR @"\u2009" + + @interface RCTImageLoader (Private) + - (id)imageCache; +@@ -43,6 +49,105 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; + return [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0; + } + +++ (nullable NSString *)rnscreens_stringByStrippingTitleGlyph:(nullable NSString *)string ++{ ++ if (string == nil) { ++ return nil; ++ } ++ NSCharacterSet *glyphCharacters = ++ [NSCharacterSet characterSetWithCharactersInString:RNS_TITLE_GLYPH_PLACEHOLDER RNS_TITLE_GLYPH_SEPARATOR]; ++ return [[string componentsSeparatedByCharactersInSet:glyphCharacters] componentsJoinedByString:@""]; ++} ++ ++@end ++ ++@interface RNSHeaderImageSlot : NSObject ++ ++- (nullable UIImage *)resolveImageFromSource:(nullable RCTImageSource *)source ++ withImageLoader:(nullable RCTImageLoader *)imageLoader ++ onLoaded:(nonnull dispatch_block_t)onLoaded; ++ ++- (void)reset; ++ ++@end ++ ++@implementation RNSHeaderImageSlot { ++ NSString *_Nullable _requestedUri; ++ NSUInteger _generation; ++} ++ +++ (NSCache *)sharedImageCache ++{ ++ static NSCache *cache; ++ static dispatch_once_t onceToken; ++ dispatch_once(&onceToken, ^{ ++ cache = [NSCache new]; ++ }); ++ return cache; ++} ++ ++- (nullable UIImage *)resolveImageFromSource:(nullable RCTImageSource *)source ++ withImageLoader:(nullable RCTImageLoader *)imageLoader ++ onLoaded:(nonnull dispatch_block_t)onLoaded ++{ ++ if (source == nil) { ++ [self reset]; ++ return nil; ++ } ++ ++ NSString *uri = source.request.URL.absoluteString; ++ if (uri == nil) { ++ [self reset]; ++ return nil; ++ } ++ ++ const BOOL isNewSource = ![uri isEqualToString:_requestedUri]; ++ if (isNewSource) { ++ _requestedUri = uri; ++ _generation += 1; ++ } ++ ++ UIImage *cachedImage = [[RNSHeaderImageSlot sharedImageCache] objectForKey:uri]; ++ if (cachedImage != nil) { ++ return cachedImage; ++ } ++ ++ if (!isNewSource || imageLoader == nil) { ++ return nil; ++ } ++ ++ const NSUInteger generation = _generation; ++ ++ __weak RNSHeaderImageSlot *weakSelf = self; ++ [RNSImageLoadingHelper ++ loadImageFromSource:source ++ withImageLoader:imageLoader ++ asTemplate:YES ++ completionBlock:^(UIImage *image) { ++ RNSHeaderImageSlot *strongSelf = weakSelf; ++ if (strongSelf == nil || generation != strongSelf->_generation) { ++ return; ++ } ++ if (image == nil) { ++#if !defined(NDEBUG) ++ RCTLogWarn( ++ @"[RNScreens] Failed to load header image source \"%@\", falling back to text-only header.", uri); ++#endif // !defined(NDEBUG) ++ return; ++ } ++ [[RNSHeaderImageSlot sharedImageCache] setObject:image forKey:uri]; ++ dispatch_async(dispatch_get_main_queue(), onLoaded); ++ }]; ++ ++ return nil; ++} ++ ++- (void)reset ++{ ++ _requestedUri = nil; ++ _generation += 1; ++} ++ + @end + + @interface RNSScreenStackHeaderConfig () +@@ -59,6 +164,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; + /// transaction via RCTMountingTransactionObserving protocol. + bool _addedReactSubviewsInCurrentTransaction; + RCTImageLoader *_imageLoader; ++ RNSHeaderImageSlot *_titleImageSlot; ++ RNSHeaderImageSlot *_subtitleImageSlot; + } + + - (instancetype)initWithFrame:(CGRect)frame +@@ -69,6 +176,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; + _show = YES; + _translucent = NO; + _addedReactSubviewsInCurrentTransaction = false; ++ _titleImageSlot = [RNSHeaderImageSlot new]; ++ _subtitleImageSlot = [RNSHeaderImageSlot new]; + _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}, react::Point{}); + [self initProps]; + } +@@ -131,6 +240,52 @@ RNS_IGNORE_SUPER_CALL_END + return nil; + } + ++- (nullable UIImage *)resolvedTitleImage ++{ ++ __weak RNSScreenStackHeaderConfig *weakSelf = self; ++ return [_titleImageSlot resolveImageFromSource:_titleImageSource ++ withImageLoader:_imageLoader ++ onLoaded:^{ ++ [weakSelf updateViewControllerIfNeeded]; ++ }]; ++} ++ ++- (nullable UIImage *)resolvedSubtitleImage ++{ ++ __weak RNSScreenStackHeaderConfig *weakSelf = self; ++ return [_subtitleImageSlot resolveImageFromSource:_subtitleImageSource ++ withImageLoader:_imageLoader ++ onLoaded:^{ ++ [weakSelf updateViewControllerIfNeeded]; ++ }]; ++} ++ ++- (nullable NSAttributedString *)attributedTitle ++{ ++ UIImage *glyph = [self resolvedTitleImage]; ++ if (glyph == nil || _title == nil) { ++ return nil; ++ } ++ ++ return [RNSScreenStackHeaderConfig ++ attributedStringWithLeadingGlyph:glyph ++ text:_title ++ textAttributes:[RNSScreenStackHeaderConfig titleTextAttributesForConfig:self]]; ++} ++ ++- (nullable NSAttributedString *)attributedSubtitle ++{ ++ UIImage *glyph = [self resolvedSubtitleImage]; ++ if (glyph == nil || _subtitle == nil) { ++ return nil; ++ } ++ ++ return [RNSScreenStackHeaderConfig ++ attributedStringWithLeadingGlyph:glyph ++ text:_subtitle ++ textAttributes:[RNSScreenStackHeaderConfig subtitleTextAttributesForConfig:self]]; ++} ++ + - (void)updateViewControllerIfNeeded + { + UIViewController *vc = _screenView.controller; +@@ -181,10 +336,10 @@ RNS_IGNORE_SUPER_CALL_END + + if (newState != _lastSendState) { + _lastSendState = newState; +- _state->updateState(std::move(newState), +- _synchronousShadowStateUpdatesEnabled +- ? facebook::react::EventQueue::UpdateMode::unstable_Immediate +- : facebook::react::EventQueue::UpdateMode::Asynchronous); ++ _state->updateState( ++ std::move(newState), ++ _synchronousShadowStateUpdatesEnabled ? facebook::react::EventQueue::UpdateMode::unstable_Immediate ++ : facebook::react::EventQueue::UpdateMode::Asynchronous); + } + } + +@@ -293,10 +448,11 @@ RNS_IGNORE_SUPER_CALL_END + // in the image attribute not being updated. We manually set frame to the size of an image + // in order to trigger proper reload that'd update the image attribute. + RCTImageSource *imageSource = [RNSScreenStackHeaderConfig imageSourceFromImageView:imageView]; +- [imageView reactSetFrame:CGRectMake(imageView.frame.origin.x, +- imageView.frame.origin.y, +- imageSource.size.width, +- imageSource.size.height)]; ++ [imageView reactSetFrame:CGRectMake( ++ imageView.frame.origin.x, ++ imageView.frame.origin.y, ++ imageSource.size.width, ++ imageSource.size.height)]; + } + + UIImage *image = imageView.image; +@@ -365,6 +521,83 @@ RNS_IGNORE_SUPER_CALL_END + } + } + +++ (NSDictionary *)resolvedTitleTextAttributesWithFontFamily:(NSString *)fontFamily ++ fontSize:(NSNumber *)fontSize ++ fontWeight:(NSString *)fontWeight ++ color:(UIColor *)color ++ boldFallbackFont:(UIFont *)boldFallbackFont ++{ ++ NSMutableDictionary *attrs = [NSMutableDictionary new]; ++ ++ // Ignore changing header title color on visionOS ++#if !TARGET_OS_VISION ++ if (color) { ++ attrs[NSForegroundColorAttributeName] = color; ++ } ++#endif ++ ++ if (fontFamily || fontWeight) { ++ attrs[NSFontAttributeName] = [RCTFont updateFont:nil ++ withFamily:fontFamily ++ size:fontSize ++ weight:fontWeight ++ style:nil ++ variant:nil ++ scaleMultiplier:1.0]; ++ } else { ++ attrs[NSFontAttributeName] = boldFallbackFont; ++ } ++ ++ return attrs; ++} ++ +++ (NSDictionary *)titleTextAttributesForConfig:(RNSScreenStackHeaderConfig *)config ++{ ++ NSNumber *size = config.titleFontSize ?: [DEFAULT_TITLE_FONT_SIZE copy]; ++ return [self resolvedTitleTextAttributesWithFontFamily:config.titleFontFamily ++ fontSize:size ++ fontWeight:config.titleFontWeight ++ color:config.titleColor ++ boldFallbackFont:[UIFont boldSystemFontOfSize:[size floatValue]]]; ++} ++ +++ (NSDictionary *)subtitleTextAttributesForConfig:(RNSScreenStackHeaderConfig *)config ++{ ++ NSNumber *size = [DEFAULT_SUBTITLE_FONT_SIZE copy]; ++ return [self resolvedTitleTextAttributesWithFontFamily:config.titleFontFamily ++ fontSize:size ++ fontWeight:nil ++ color:config.titleColor ++ boldFallbackFont:[UIFont systemFontOfSize:[size floatValue]]]; ++} ++ +++ (NSAttributedString *)attributedStringWithLeadingGlyph:(UIImage *)glyph ++ text:(NSString *)text ++ textAttributes:(NSDictionary *)textAttributes ++{ ++ UIFont *font = ++ textAttributes[NSFontAttributeName] ?: [UIFont boldSystemFontOfSize:[DEFAULT_TITLE_FONT_SIZE floatValue]]; ++ UIColor *tintColor = textAttributes[NSForegroundColorAttributeName] ?: UIColor.labelColor; ++ ++ CGFloat glyphHeight = font.capHeight; ++ CGSize intrinsicSize = glyph.size; ++ CGFloat aspectRatio = intrinsicSize.height > 0 ? intrinsicSize.width / intrinsicSize.height : 1; ++ ++ NSTextAttachment *attachment = [NSTextAttachment new]; ++ attachment.image = [[glyph imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] ++ imageWithTintColor:tintColor ++ renderingMode:UIImageRenderingModeAlwaysOriginal]; ++ attachment.bounds = CGRectMake(0, 0, glyphHeight * aspectRatio, glyphHeight); ++ ++ NSMutableAttributedString *composed = [[NSMutableAttributedString alloc] ++ initWithAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; ++ [composed appendAttributedString:[[NSAttributedString alloc] ++ initWithString:[RNS_TITLE_GLYPH_SEPARATOR stringByAppendingString:text]]]; ++ [composed addAttributes:textAttributes range:NSMakeRange(0, composed.length)]; ++ ++ return composed; ++} ++ + + (UINavigationBarAppearance *)buildAppearance:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config + { + UINavigationBarAppearance *appearance = [UINavigationBarAppearance new]; +@@ -380,8 +613,10 @@ RNS_IGNORE_SUPER_CALL_END + appearance.shadowColor = shadowColor; + appearance.shadowImage = shadowImage; + } +- } else { ++ } else if (config.backgroundColor) { + [appearance configureWithOpaqueBackground]; ++ } else { ++ [appearance configureWithDefaultBackground]; + } + + // set background color if specified +@@ -408,59 +643,20 @@ RNS_IGNORE_SUPER_CALL_END + } + + if (config.titleFontFamily || config.titleFontSize || config.titleFontWeight || config.titleColor) { +- NSMutableDictionary *attrs = [NSMutableDictionary new]; +- +- // Ignore changing header title color on visionOS +-#if !TARGET_OS_VISION +- if (config.titleColor) { +- attrs[NSForegroundColorAttributeName] = config.titleColor; +- } +-#endif +- +- NSString *family = config.titleFontFamily ?: nil; +- NSNumber *size = config.titleFontSize ?: [DEFAULT_TITLE_FONT_SIZE copy]; +- NSString *weight = config.titleFontWeight ?: nil; +- if (family || weight) { +- attrs[NSFontAttributeName] = [RCTFont updateFont:nil +- withFamily:config.titleFontFamily +- size:size +- weight:weight +- style:nil +- variant:nil +- scaleMultiplier:1.0]; +- } else { +- attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]]; +- } +- appearance.titleTextAttributes = attrs; ++ appearance.titleTextAttributes = [self titleTextAttributesForConfig:config]; + } + + if (config.largeTitleFontFamily || config.largeTitleFontSize || config.largeTitleFontWeight || + config.largeTitleColor || config.titleColor) { +- NSMutableDictionary *largeAttrs = [NSMutableDictionary new]; +- +- // Ignore changing header title color on visionOS +-#if !TARGET_OS_VISION +- if (config.largeTitleColor || config.titleColor) { +- largeAttrs[NSForegroundColorAttributeName] = config.largeTitleColor ? config.largeTitleColor : config.titleColor; +- } +-#endif +- +- NSString *largeFamily = config.largeTitleFontFamily ?: nil; + NSNumber *largeSize = config.largeTitleFontSize ?: [DEFAULT_TITLE_LARGE_FONT_SIZE copy]; +- NSString *largeWeight = config.largeTitleFontWeight ?: nil; +- if (largeFamily || largeWeight) { +- largeAttrs[NSFontAttributeName] = [RCTFont updateFont:nil +- withFamily:largeFamily +- size:largeSize +- weight:largeWeight +- style:nil +- variant:nil +- scaleMultiplier:1.0]; +- } else { +- largeAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:[largeSize floatValue] weight:UIFontWeightBold]; +- } +- +- appearance.largeTitleTextAttributes = largeAttrs; ++ UIColor *largeColor = config.largeTitleColor ? config.largeTitleColor : config.titleColor; ++ appearance.largeTitleTextAttributes = ++ [self resolvedTitleTextAttributesWithFontFamily:config.largeTitleFontFamily ++ fontSize:largeSize ++ fontWeight:config.largeTitleFontWeight ++ color:largeColor ++ boldFallbackFont:[UIFont systemFontOfSize:[largeSize floatValue] ++ weight:UIFontWeightBold]]; + } + + UIImage *backButtonImage = [config loadBackButtonImageInViewController:vc]; +@@ -507,6 +703,11 @@ RNS_IGNORE_SUPER_CALL_END + + if (shouldHide) { + navitem.title = config.title; ++#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) ++ if (@available(iOS 26.0, *)) { ++ navitem.subtitle = config.subtitle; ++ } ++#endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) + + // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items. + [navctr setNavigationBarHidden:YES animated:animated]; +@@ -648,6 +849,21 @@ RNS_IGNORE_SUPER_CALL_END + // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug). + // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments) + navitem.title = config.title; ++#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) ++ if (@available(iOS 26.0, *)) { ++ navitem.subtitle = config.subtitle; ++ ++ NSAttributedString *attributedTitle = [config attributedTitle]; ++ if (attributedTitle != nil) { ++ navitem.attributedTitle = attributedTitle; ++ } ++ ++ NSAttributedString *attributedSubtitle = [config attributedSubtitle]; ++ if (attributedSubtitle != nil) { ++ navitem.attributedSubtitle = attributedSubtitle; ++ } ++ } ++#endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) + navitem.leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems + withCurrentItems:navitem.leftBarButtonItems]; + navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems +@@ -700,7 +916,8 @@ RNS_IGNORE_SUPER_CALL_END + const auto *config = self; + + const auto isBackTitleBlank = [NSString rnscreens_isBlankOrNull:config.backTitle] == YES; +- NSString *resolvedBackTitle = isBackTitleBlank ? prevItem.title : config.backTitle; ++ NSString *resolvedBackTitle = ++ isBackTitleBlank ? [NSString rnscreens_stringByStrippingTitleGlyph:prevItem.title] : config.backTitle; + + // If previous screen controller was recreated (e.g. when you go back to tab with stack that has multiple screens), + // its navigationItem may not have any information from screen's headerConfig, including the title. +@@ -858,12 +1075,13 @@ RNS_IGNORE_SUPER_CALL_END + return; + } + +- RCTAssert(childComponentView.superview == nil, +- @"Attempt to mount already mounted component view. (parent: %@, child: %@, index: %@, existing parent: %@)", +- self, +- childComponentView, +- @(index), +- @([childComponentView.superview tag])); ++ RCTAssert( ++ childComponentView.superview == nil, ++ @"Attempt to mount already mounted component view. (parent: %@, child: %@, index: %@, existing parent: %@)", ++ self, ++ childComponentView, ++ @(index), ++ @([childComponentView.superview tag])); + + // [_reactSubviews insertObject:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; + [self insertReactSubview:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; +@@ -987,6 +1205,8 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) + { + [super prepareForRecycle]; + _initialPropsSet = NO; ++ [_titleImageSlot reset]; ++ [_subtitleImageSlot reset]; + + _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}, react::Point{}); + } +@@ -1025,6 +1245,7 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) + } + + _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title); ++ _subtitle = RCTNSStringFromStringNilIfEmpty(newScreenProps.subtitle); + if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) { + _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily); + } +@@ -1064,6 +1285,24 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) + // We cannot compare SharedColor because it is shared value. + // We could compare color value, but it is more performant to just assign new value + _titleColor = RCTUIColorFromSharedColor(newScreenProps.titleColor); ++ if (newScreenProps.titleImageSource != oldScreenProps.titleImageSource) { ++ const auto &titleImageSource = newScreenProps.titleImageSource; ++ _titleImageSource = titleImageSource.uri.empty() ++ ? nil ++ : [[RCTImageSource alloc] ++ initWithURLRequest:NSURLRequestFromImageSource(titleImageSource) ++ size:CGSizeMake(titleImageSource.size.width, titleImageSource.size.height) ++ scale:titleImageSource.scale]; ++ } ++ if (newScreenProps.subtitleImageSource != oldScreenProps.subtitleImageSource) { ++ const auto &subtitleImageSource = newScreenProps.subtitleImageSource; ++ _subtitleImageSource = subtitleImageSource.uri.empty() ++ ? nil ++ : [[RCTImageSource alloc] ++ initWithURLRequest:NSURLRequestFromImageSource(subtitleImageSource) ++ size:CGSizeMake(subtitleImageSource.size.width, subtitleImageSource.size.height) ++ scale:subtitleImageSource.scale]; ++ } + _largeTitleColor = RCTUIColorFromSharedColor(newScreenProps.largeTitleColor); + _color = RCTUIColorFromSharedColor(newScreenProps.color); + _backgroundColor = RCTUIColorFromSharedColor(newScreenProps.backgroundColor); +diff --git a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts +index da28c51c..c421621a 100644 +--- a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts ++++ b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts +@@ -4,6 +4,7 @@ import { codegenNativeComponent } from 'react-native'; + import type { + CodegenTypes as CT, + HostComponent, ++ ImageSource, + ViewProps, + ColorValue, + } from 'react-native'; +@@ -66,10 +67,13 @@ export interface NativeProps extends ViewProps { + largeTitleColor?: ColorValue | undefined; + translucent?: boolean | undefined; + title?: string | undefined; ++ subtitle?: string | undefined; + titleFontFamily?: string | undefined; + titleFontSize?: CT.Int32 | undefined; + titleFontWeight?: string | undefined; + titleColor?: ColorValue | undefined; ++ titleImageSource?: ImageSource | undefined; ++ subtitleImageSource?: ImageSource | undefined; + disableBackButtonMenu?: boolean | undefined; + backButtonDisplayMode?: CT.WithDefault; + hideBackButton?: boolean | undefined; +diff --git a/node_modules/react-native-screens/src/types.tsx b/node_modules/react-native-screens/src/types.tsx +index 8594b733..777ae443 100644 +--- a/node_modules/react-native-screens/src/types.tsx ++++ b/node_modules/react-native-screens/src/types.tsx +@@ -827,10 +827,41 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { + * String that can be displayed in the header as a fallback for `headerTitle`. + */ + title?: string | undefined; ++ /** ++ * String to display in the header underneath the title. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ subtitle?: string | undefined; + /** + * Allows for setting text color of the title. + */ + titleColor?: ColorValue | undefined; ++ /** ++ * Image rendered as a leading glyph before the title. The glyph is sized from ++ * the resolved title font and tinted with `titleColor`. ++ * Requires iOS 26 or higher and is ignored on Android and earlier iOS versions. ++ * The glyph is hidden while the large title is expanded and appears on collapse. ++ * A custom header title view replaces the title and its glyph. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ titleImageSource?: ImageSourcePropType | undefined; ++ /** ++ * Image rendered as a leading glyph before the subtitle. The glyph is sized ++ * from the resolved subtitle font and tinted with `titleColor`. ++ * Requires iOS 26 or higher and is ignored on Android and earlier iOS versions. ++ * A custom header title view does not replace the subtitle or its glyph. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ subtitleImageSource?: ImageSourcePropType | undefined; + /** + * Customize font family to be used for the title. + */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0d091d07c0..2f24110a636 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,7 +77,7 @@ importers: version: 0.86.3(@babel/core@7.29.7) '@react-navigation/drawer': specifier: ^7.14.1 - version: 7.14.2(4657b9012d6122af76bbab8fafd3d31e) + version: 7.14.2(dda132b9c8f311fd0053bf630a6001aa) '@react-navigation/elements': specifier: ^2.9.42 version: 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) @@ -85,8 +85,8 @@ importers: specifier: ^7.4.1 version: 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': - specifier: link:../../../../Work/react-navigation/packages/native-stack - version: link:../../../../Work/react-navigation/packages/native-stack + specifier: ^7.19.1 + version: 7.19.2(9f93b74e18ae1df429c2a9dc7118e4c2) '@rocket.chat/media-signaling': specifier: 1.0.0-rc.1 version: 1.0.0-rc.1 @@ -292,8 +292,8 @@ importers: specifier: ^5.10.0 version: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-screens: - specifier: link:../../../../Work/react-native-screens - version: link:../../../../Work/react-native-screens + specifier: ~4.28.0 + version: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-skeleton-placeholder: specifier: 5.2.4 version: 5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -2723,6 +2723,15 @@ packages: '@react-native-masked-view/masked-view': optional: true + '@react-navigation/native-stack@7.19.2': + resolution: {integrity: sha512-D6hmtgpMZz1x8R0PLbfqqKh5ajEkb13DguE7X/MNNKYL4riY4zD3TrjbcesUC2EZklpQ/EoJ6h6HAhMNfCCsoQ==} + peerDependencies: + '@react-navigation/native': ^7.4.1 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + react-native-screens: '>= 4.0.0' + '@react-navigation/native@7.4.1': resolution: {integrity: sha512-pEnu5XzAXV6RWr6A6pmZ7qLB+O6TJWMhBO25AjckMdr596UWulMPHQCtBcnOUfg6K+2h9ZUctHF2E5Xu4vcOrQ==} peerDependencies: @@ -6601,6 +6610,12 @@ packages: peerDependencies: react: ^19.2.3 + react-freeze@1.0.4: + resolution: {integrity: sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==} + engines: {node: '>=10'} + peerDependencies: + react: '>=17.0.0' + react-hook-form@7.34.2: resolution: {integrity: sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ==} engines: {node: '>=12.22.0'} @@ -6800,6 +6815,12 @@ packages: react: '*' react-native: '*' + react-native-screens@4.28.0: + resolution: {integrity: sha512-0/79Z8RCibuaAHti+DJ2Dq0m6UFWh4+p1bH58K3cjiQ7IDWs5kU9tCAj4lXcJMHHonbF2mIE2BeaPSEvOpIiFg==} + peerDependencies: + react: '*' + react-native: '*' + react-native-skeleton-placeholder@5.2.4: resolution: {integrity: sha512-OZntVq1hU1UX33FltxK2ezT2v9vHIhV8YnEbnMWUCvxT0N9OsgD1qxiHm6qb9YRJVgq2o5z3S7dNPsPnDF/jNg==} peerDependencies: @@ -10899,7 +10920,7 @@ snapshots: use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/drawer@7.14.2(4657b9012d6122af76bbab8fafd3d31e)': + '@react-navigation/drawer@7.14.2(dda132b9c8f311fd0053bf630a6001aa)': dependencies: '@react-navigation/elements': 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -10910,7 +10931,7 @@ snapshots: react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - react-native-screens: link:../../../../Work/react-native-screens + react-native-screens: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -10927,6 +10948,20 @@ snapshots: optionalDependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@react-navigation/native-stack@7.19.2(9f93b74e18ae1df429c2a9dc7118e4c2)': + dependencies: + '@react-navigation/elements': 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) + '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + color: 4.2.3 + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + sf-symbols-typescript: 2.2.0 + warn-once: 0.1.1 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + '@react-navigation/native@7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: '@react-navigation/core': 7.22.1(react@19.2.3) @@ -15545,6 +15580,10 @@ snapshots: react: 19.2.3 scheduler: 0.27.0 + react-freeze@1.0.4(react@19.2.3): + dependencies: + react: 19.2.3 + react-hook-form@7.34.2(react@19.2.3): dependencies: react: 19.2.3 @@ -15742,6 +15781,13 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + react-native-screens@4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-freeze: 1.0.4(react@19.2.3) + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + warn-once: 0.1.1 + react-native-skeleton-placeholder@5.2.4(@react-native-masked-view/masked-view@0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native-linear-gradient@2.8.3(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) diff --git a/tsconfig.json b/tsconfig.json index af8d8a336ae..6fb88695a15 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,6 @@ "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "paths": { - "@react-navigation/native-stack": ["./node_modules/@react-navigation/native-stack/src/index.tsx"], "~/*": ["./app/*"], "~/scripts/*": ["./scripts/*"], "~/.rnstorybook/*": ["./.rnstorybook/*"] From a48a9f25b2945ec77dc0c9007fc311cc51659505 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 22 Sep 2026 20:30:30 -0300 Subject: [PATCH 052/120] chore: update Podfile.lock after pod install Refresh the React-Core-prebuilt checksum after reinstalling pods against the reverted react-native-screens and native-stack deps. --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c389682a62f..f2a0eb6a6a2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3518,7 +3518,7 @@ SPEC CHECKSUMS: React: 52dce66f71097ed7a4968673d95eeb8095ded74e React-callinvoker: afa0525ab27f4ffbb4c377f855fe9368edb099bc React-Core: fdb67992e049f2526ade3e258c21b8404e7943a3 - React-Core-prebuilt: 125b040461c34b44babc54cb7f0730b2d97de6d2 + React-Core-prebuilt: 9abc518a8aa7aa4fa3bbbe52bc825bcd9fb3c3ce React-CoreModules: 456d91152aa283ddc6ae8abfcec3292af88912f6 React-cxxreact: 73a20c0dde5ec221f98d5e4da0e71349ab48353d React-debug: 4a1d2ef3086d88dd35f28cfb5da803584f819924 From 1d6a2a6be6bf04958647777bb8487c3964fee61e Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 22 Sep 2026 20:36:56 -0300 Subject: [PATCH 053/120] feat(header): add native header item helpers for outside-stack screens Adds outsideHeaderRightLegal/outsideHeaderLeftClose helpers to render the Legal/Close buttons via unstable_headerRightItems/LeftItems on iOS native headers, falling back to the existing HeaderButton components on Android. Login, NewServer and Register views now use these helpers instead of setting headerRight/headerLeft directly. --- app/lib/methods/helpers/navigation/index.ts | 36 +++++++++++++++++++++ app/views/LoginView/index.tsx | 4 +-- app/views/NewServerView/index.tsx | 7 ++-- app/views/RegisterView/index.tsx | 8 ++--- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 42026aefbc6..0b5b28fb66d 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -6,6 +6,8 @@ import { themes } from '~/lib/constants/colors'; import { type TSupportedThemes } from '~/theme'; import sharedStyles from '~/views/Styles'; import Header from '~/containers/Header'; +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; +import I18n from '~/i18n'; import { isIOS } from '~/lib/methods/helpers'; export const defaultHeader: NativeStackNavigationOptions = isIOS @@ -16,6 +18,40 @@ export const defaultHeader: NativeStackNavigationOptions = isIOS header: (props: NativeStackHeaderProps): ReactElement => createElement(Header, props) }; +export const outsideHeaderRightLegal = (navigation: any, testID: string): NativeStackNavigationOptions => + isIOS + ? { + unstable_headerRightItems: () => [ + { + type: 'button', + label: I18n.t('More'), + accessibilityLabel: I18n.t('More'), + icon: { type: 'sfSymbol', name: 'ellipsis' }, + onPress: () => navigation?.navigate('LegalView') + } + ] + } + : { + headerRight: (): ReactElement => createElement(HeaderButton.Legal, { testID, navigation }) + }; + +export const outsideHeaderLeftClose = (onPress: () => void, testID: string): NativeStackNavigationOptions => + isIOS + ? { + unstable_headerLeftItems: () => [ + { + type: 'button', + label: I18n.t('Close'), + accessibilityLabel: I18n.t('Close'), + icon: { type: 'sfSymbol', name: 'xmark' }, + onPress + } + ] + } + : { + headerLeft: (): ReactElement => createElement(HeaderButton.CloseModal, { onPress, testID }) + }; + export const drawerStyle = { width: 320 }; diff --git a/app/views/LoginView/index.tsx b/app/views/LoginView/index.tsx index f56401804da..e447f987e1c 100644 --- a/app/views/LoginView/index.tsx +++ b/app/views/LoginView/index.tsx @@ -4,8 +4,8 @@ import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import FormContainer, { FormContainerInner } from '~/containers/FormContainer'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import LoginServices from '~/containers/LoginServices'; +import { outsideHeaderRightLegal } from '~/lib/methods/helpers/navigation'; import { type OutsideParamList } from '~/stacks/types'; import UserForm from './UserForm'; @@ -25,7 +25,7 @@ const LoginView = ({ route }: LoginViewProps) => { useLayoutEffect(() => { navigation.setOptions({ title: title ?? 'Rocket.Chat', - headerRight: () => + ...outsideHeaderRightLegal(navigation, 'login-view-more') }); }, [navigation, title]); diff --git a/app/views/NewServerView/index.tsx b/app/views/NewServerView/index.tsx index 99b885350f6..c4c07bf127d 100644 --- a/app/views/NewServerView/index.tsx +++ b/app/views/NewServerView/index.tsx @@ -10,11 +10,11 @@ import { inviteLinksClear } from '~/actions/inviteLinks'; import { selectServerRequest, serverFinishAdd, serverRequest } from '~/actions/server'; import Button from '~/containers/Button'; import FormContainer, { FormContainerInner } from '~/containers/FormContainer'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import { type TServerHistoryModel } from '~/definitions'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; import { isAndroid, isTablet } from '~/lib/methods/helpers'; +import { outsideHeaderLeftClose } from '~/lib/methods/helpers/navigation'; import EventEmitter from '~/lib/methods/helpers/events'; import ServerInput from './components/ServerInput'; import { getServerById } from '~/lib/database/services/Server'; @@ -99,8 +99,9 @@ const NewServerView = () => { if (previousServer) { return navigation.setOptions({ headerTitle: I18n.t('Add_Server'), - headerLeft: () => - !connecting ? : null + ...(connecting + ? { headerLeft: () => null, unstable_headerLeftItems: () => [] } + : outsideHeaderLeftClose(close, 'new-server-view-close')) }); } return navigation.setOptions({ diff --git a/app/views/RegisterView/index.tsx b/app/views/RegisterView/index.tsx index 6697c9f1448..ea50310af83 100644 --- a/app/views/RegisterView/index.tsx +++ b/app/views/RegisterView/index.tsx @@ -11,10 +11,10 @@ import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { loginRequest } from '~/actions/login'; import Button from '~/containers/Button'; import FormContainer, { FormContainerInner } from '~/containers/FormContainer'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import LoginServices from '~/containers/LoginServices'; import { ControlledFormTextInput } from '~/containers/TextInput'; import I18n from '~/i18n'; +import { outsideHeaderRightLegal } from '~/lib/methods/helpers/navigation'; import { getShowLoginButton } from '~/selectors/login'; import { type OutsideParamList } from '~/stacks/types'; import { useTheme } from '~/theme'; @@ -168,11 +168,9 @@ const RegisterView = ({ route }: RegisterViewProps) => { useLayoutEffect(() => { navigation.setOptions({ title: route?.params?.title, - headerRight: () => ( - - ) + ...outsideHeaderRightLegal(navigation, 'register-view-more') }); - }, []); + }, [navigation, route?.params?.title]); return ( From 8ee6f29196d25267410dcf591626f170f7106403 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 22 Sep 2026 20:37:11 -0300 Subject: [PATCH 054/120] refactor(header): consolidate room header right buttons into shared components Removes the platform-specific *RightButtonsNative components and useRoomRightButtonsNativeData in favor of a single useRoomHeaderRightItems hook shared by RoomRightButtons, ThreadRightButtons and OmnichannelRightButtons. RoomsListView's useHeader is updated to match. --- .../RightButtons/OmnichannelRightButtons.tsx | 7 +- .../OmnichannelRightButtonsNative.tsx | 13 - .../RightButtons/RoomRightButtons.tsx | 6 - .../RightButtons/RoomRightButtonsNative.tsx | 145 --------- .../RightButtons/ThreadRightButtons.tsx | 6 - .../RightButtons/ThreadRightButtonsNative.tsx | 23 -- .../OmnichannelRightButtons.test.tsx | 27 +- .../__tests__/RoomRightButtons.test.tsx | 187 +++-------- .../__tests__/ThreadRightButtons.test.tsx | 29 -- .../useRoomRightButtonsNativeData.ts | 15 - .../hooks/__tests__/useHeader.test.tsx | 12 + app/views/RoomView/hooks/useHeader.tsx | 13 +- .../RoomView/hooks/useRoomHeaderRightItems.ts | 308 ++++++++++++++++++ .../hooks/__tests__/useHeader.test.tsx | 35 +- app/views/RoomsListView/hooks/useHeader.tsx | 111 ++++--- 15 files changed, 463 insertions(+), 474 deletions(-) delete mode 100644 app/views/RoomView/components/RightButtons/OmnichannelRightButtonsNative.tsx delete mode 100644 app/views/RoomView/components/RightButtons/RoomRightButtonsNative.tsx delete mode 100644 app/views/RoomView/components/RightButtons/ThreadRightButtonsNative.tsx delete mode 100644 app/views/RoomView/components/RightButtons/useRoomRightButtonsNativeData.ts create mode 100644 app/views/RoomView/hooks/useRoomHeaderRightItems.ts diff --git a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx b/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx index fe91bca5cc1..91f523d0ef3 100644 --- a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx @@ -4,7 +4,7 @@ import { useNavigation } from '@react-navigation/native'; import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; import i18n from '~/i18n'; -import { hasNativeHeaderBar, showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; +import { showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; @@ -17,7 +17,6 @@ import { navigateToScreen, type TRoomStackNavigation } from '~/views/RoomView/se import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; import { placeLivechatOnHold } from '~/views/RoomView/services/placeLivechatOnHold'; import { OmnichannelRightButtonsLegacy } from './OmnichannelRightButtonsLegacy'; -import { OmnichannelRightButtonsNative } from './OmnichannelRightButtonsNative'; interface IOmnichannelRightButtonsProps { rid: string; @@ -92,9 +91,5 @@ export const OmnichannelRightButtons = ({ rid, roomStore }: IOmnichannelRightBut showActionSheet({ options }); }; - if (hasNativeHeaderBar) { - return ; - } - return ; }; diff --git a/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsNative.tsx b/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsNative.tsx deleted file mode 100644 index 5e51de957db..00000000000 --- a/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsNative.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { type ReactElement } from 'react'; - -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; - -interface IOmnichannelRightButtonsNativeProps { - onShowMoreActions: () => void; -} - -export const OmnichannelRightButtonsNative = ({ onShowMoreActions }: IOmnichannelRightButtonsNativeProps): ReactElement => ( - - - -); diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx index 089c4777bd0..b8d9ec5efc4 100644 --- a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx @@ -1,9 +1,7 @@ import { type ReactElement } from 'react'; -import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import { type RoomStore } from '~/views/RoomView/definitions'; import { RoomRightButtonsLegacy } from './RoomRightButtonsLegacy'; -import { RoomRightButtonsNative } from './RoomRightButtonsNative'; import { useRoomRightButtonsData } from './useRoomRightButtonsData'; interface IRoomRightButtonsProps { @@ -14,9 +12,5 @@ interface IRoomRightButtonsProps { export const RoomRightButtons = ({ rid, roomStore }: IRoomRightButtonsProps): ReactElement => { const data = useRoomRightButtonsData(rid, roomStore); - if (hasNativeHeaderBar) { - return ; - } - return ; }; diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtonsNative.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtonsNative.tsx deleted file mode 100644 index 20f3e9439c2..00000000000 --- a/app/views/RoomView/components/RightButtons/RoomRightButtonsNative.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { type ReactElement } from 'react'; - -import { showActionSheetRef } from '~/containers/ActionSheet'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import i18n from '~/i18n'; -import { splitRoomHeaderActions, type TRoomHeaderActionKey } from '~/views/RoomView/helpers/roomHeaderActions'; -import { type RoomStore } from '~/views/RoomView/definitions'; -import { type useRoomRightButtonsData } from './useRoomRightButtonsData'; -import { useRoomRightButtonsNativeData } from './useRoomRightButtonsNativeData'; - -interface IRoomRightButtonsNativeProps { - rid: string; - roomStore: RoomStore; - data: ReturnType; -} - -export const RoomRightButtonsNative = ({ rid, roomStore, data }: IRoomRightButtonsNativeProps): ReactElement => { - const { - colors, - threadsEnabled, - issuesWithNotifications, - disableNotifications, - hasE2EEWarning, - canToggleEncryption, - isSelfDm, - tunread, - tunreadUser, - tunreadGroup, - callAccessibilityLabel, - goThreadsView, - navigateToNotificationOrPushTroubleshoot, - goSearchView, - goE2EEToggleRoomView, - threadsAccessibilityLabel - } = data; - const { callPresent, isCallDisabled, onPressCall, goRoomActionsView } = useRoomRightButtonsNativeData(rid, roomStore, isSelfDm); - - const { visibleKeys, overflowKeys } = splitRoomHeaderActions({ - threads: threadsEnabled, - call: callPresent, - encryption: hasE2EEWarning, - notifications: issuesWithNotifications || disableNotifications - }); - const isVisible = (key: TRoomHeaderActionKey) => visibleKeys.includes(key); - - const overflowOptions = [ - ...overflowKeys.map(key => { - if (key === 'threads') { - return { - title: threadsAccessibilityLabel, - icon: 'threads' as const, - testID: 'room-view-header-threads', - onPress: goThreadsView - }; - } - if (key === 'encryption') { - return { - title: i18n.t('Encrypted'), - icon: 'encrypted' as const, - testID: 'room-view-header-encryption', - enabled: canToggleEncryption, - onPress: goE2EEToggleRoomView - }; - } - if (key === 'call') { - return { - title: callAccessibilityLabel, - icon: 'phone' as const, - testID: 'room-view-header-call', - enabled: !isCallDisabled, - onPress: onPressCall - }; - } - if (key === 'notifications') { - return { - title: i18n.t('Troubleshooting'), - icon: 'notification-disabled' as const, - testID: 'room-view-push-troubleshoot', - onPress: navigateToNotificationOrPushTroubleshoot - }; - } - throw new Error(`Unhandled room header action key: ${key}`); - }), - { - title: i18n.t('Search_Messages'), - icon: 'search' as const, - testID: 'room-view-search', - enabled: !hasE2EEWarning, - onPress: goSearchView - }, - { - title: i18n.t('Actions'), - icon: 'kebab' as const, - testID: 'room-view-header-room-actions', - onPress: () => goRoomActionsView() - } - ]; - - return ( - - {isVisible('encryption') ? ( - - ) : null} - {isVisible('notifications') ? ( - - ) : null} - {isVisible('call') ? ( - - ) : null} - {isVisible('threads') ? ( - } - disabled={hasE2EEWarning} - /> - ) : null} - showActionSheetRef({ options: overflowOptions })} - testID='room-view-header-more' - /> - - ); -}; diff --git a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx b/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx index 822346c6bc5..e7ee226b2df 100644 --- a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx @@ -1,13 +1,11 @@ import { type ReactElement } from 'react'; -import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; import { getUserSelector } from '~/selectors/login'; import { useThreadFollowing } from '~/views/RoomView/hooks/useThreadFollowing'; import { ThreadRightButtonsLegacy } from './ThreadRightButtonsLegacy'; -import { ThreadRightButtonsNative } from './ThreadRightButtonsNative'; interface IThreadRightButtonsProps { tmid: string; @@ -22,9 +20,5 @@ export const ThreadRightButtons = ({ tmid }: IThreadRightButtonsProps): ReactEle toggleFollowThread(tmid, isFollowingThread); }; - if (hasNativeHeaderBar) { - return ; - } - return ; }; diff --git a/app/views/RoomView/components/RightButtons/ThreadRightButtonsNative.tsx b/app/views/RoomView/components/RightButtons/ThreadRightButtonsNative.tsx deleted file mode 100644 index 5281a2b54b4..00000000000 --- a/app/views/RoomView/components/RightButtons/ThreadRightButtonsNative.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { type ReactElement } from 'react'; - -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import i18n from '~/i18n'; - -interface IThreadRightButtonsNativeProps { - isFollowingThread: boolean; - onToggleFollowThread: () => void; -} - -export const ThreadRightButtonsNative = ({ - isFollowingThread, - onToggleFollowThread -}: IThreadRightButtonsNativeProps): ReactElement => ( - - - -); diff --git a/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx index 29bdce2cdc7..e8579d87263 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx @@ -49,21 +49,10 @@ jest.mock('~/views/RoomView/hooks/useCanPlaceLivechatOnHold', () => ({ jest.mock('~/views/RoomView/services/closeLivechat', () => ({ closeLivechat: jest.fn() })); jest.mock('~/views/RoomView/services/placeLivechatOnHold', () => ({ placeLivechatOnHold: jest.fn() })); jest.mock('~/lib/services/restApi', () => ({ returnLivechat: jest.fn() })); -let mockIsIOS = true; -let mockIsTablet = false; jest.mock('~/lib/methods/helpers', () => ({ ...jest.requireActual('~/lib/methods/helpers'), showConfirmationAlert: jest.fn(), - showErrorAlert: jest.fn(), - get isIOS() { - return mockIsIOS; - }, - get isTablet() { - return mockIsTablet; - }, - get hasNativeHeaderBar() { - return mockIsIOS && !mockIsTablet; - } + showErrorAlert: jest.fn() })); jest.mock('~/containers/Header/components/HeaderButton', () => { @@ -96,8 +85,6 @@ describe('OmnichannelRightButtons', () => { }; mockCanReturnQueue = false; mockCanPlaceLivechatOnHold = false; - mockIsIOS = true; - mockIsTablet = false; }); it('renders the kebab button', () => { @@ -200,16 +187,4 @@ describe('OmnichannelRightButtons', () => { navigation: mockNavigation }); }); - - it.each([ - ['Android', false, false], - ['iPad', true, true] - ])('offers the close option on the legacy bar (%s)', (_label, isIOSValue, isTabletValue) => { - mockIsIOS = isIOSValue; - mockIsTablet = isTabletValue; - - const options = openKebab(); - - expect(titlesOf(options)).toEqual(['Close']); - }); }); diff --git a/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx index dbd6d870c05..02342646251 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx @@ -1,6 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react-native'; -import { showActionSheetRef } from '~/containers/ActionSheet'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { type RoomStore } from '~/views/RoomView/definitions'; import { RoomRightButtons } from '../RoomRightButtons'; @@ -22,20 +21,9 @@ jest.mock('~/lib/hooks/useMasterDetail', () => ({ jest.mock('~/theme', () => ({ useTheme: () => ({ colors: { fontDanger: '#f00' } }) })); jest.mock('~/lib/helpers/getRoomAccessibilityLabel', () => ({ __esModule: true, default: () => 'channel label' })); -let mockIsIOS = true; -let mockIsTablet = false; jest.mock('~/lib/methods/helpers', () => ({ getRoomTitle: () => 'Room Title', - isGroupChat: () => false, - get isIOS() { - return mockIsIOS; - }, - get isTablet() { - return mockIsTablet; - }, - get hasNativeHeaderBar() { - return mockIsIOS && !mockIsTablet; - } + isGroupChat: () => false })); let mockThreadsEnabled = true; @@ -95,15 +83,6 @@ jest.mock('~/lib/hooks/useNewMediaCall', () => ({ }) })); -const mockGoRoomActionsView = jest.fn(); -jest.mock('~/views/RoomView/hooks/useGoRoomActionsView', () => ({ - useGoRoomActionsView: () => mockGoRoomActionsView -})); - -jest.mock('~/containers/ActionSheet', () => ({ - showActionSheetRef: jest.fn() -})); - jest.mock('~/containers/Header/components/HeaderButton', () => { const ReactActual = jest.requireActual('react'); return { @@ -150,8 +129,6 @@ describe('RoomRightButtons', () => { beforeEach(() => { jest.clearAllMocks(); mockIsMasterDetail = false; - mockIsIOS = true; - mockIsTablet = false; mockThreadsEnabled = true; mockAppState = { login: { user: { id: 'u1', username: 'user', token: 'tok' } }, @@ -165,133 +142,51 @@ describe('RoomRightButtons', () => { mockHasMediaCallPermission = false; }); - describe('on the iOS native bar', () => { - it('shows threads and call as bar items and folds search and Room Actions into the overflow menu', () => { - renderRoomRightButtons(); - - expect(screen.getByTestId('room-view-header-threads')).toHaveProp('iconName', 'threads'); - expect(screen.getByTestId('room-view-header-call')).toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-search')).not.toBeOnTheScreen(); - expect(screen.getByTestId('room-view-header-more')).toBeOnTheScreen(); - - fireEvent.press(screen.getByTestId('room-view-header-more')); - const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; - expect(options.map((option: { testID: string }) => option.testID)).toEqual([ - 'room-view-search', - 'room-view-header-room-actions' - ]); - }); - - it('demotes encryption and notifications to overflow when every warning is active', () => { - mockHasE2EEWarning = true; - mockCanToggleEncryption = true; - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - - renderRoomRightButtons(); - - expect(screen.getByTestId('room-view-header-threads')).toBeOnTheScreen(); - expect(screen.getByTestId('room-view-header-call')).toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-header-encryption')).not.toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-push-troubleshoot')).not.toBeOnTheScreen(); - - fireEvent.press(screen.getByTestId('room-view-header-more')); - const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; - expect(options.map((option: { testID: string }) => option.testID)).toEqual([ - 'room-view-header-encryption', - 'room-view-push-troubleshoot', - 'room-view-search', - 'room-view-header-room-actions' - ]); - }); - - it('promotes call to a bar item once threads is disabled, keeping the cap at two', () => { - mockThreadsEnabled = false; - mockHasE2EEWarning = true; - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - - renderRoomRightButtons(); - - expect(screen.queryByTestId('room-view-header-threads')).not.toBeOnTheScreen(); - expect(screen.getByTestId('room-view-header-call')).toBeOnTheScreen(); - expect(screen.getByTestId('room-view-header-encryption')).toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-push-troubleshoot')).not.toBeOnTheScreen(); - }); - - it('opens Room Actions from the overflow menu', () => { - renderRoomRightButtons(); - - fireEvent.press(screen.getByTestId('room-view-header-more')); - const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; - const roomActionsOption = options.find((option: { testID: string }) => option.testID === 'room-view-header-room-actions'); - roomActionsOption.onPress(); - - expect(mockGoRoomActionsView).toHaveBeenCalledWith(); - }); - - it('hides the call bar item on a self DM', () => { - mockRoomState = { room: { rid: 'rid-1', t: 'd', name: 'user' } }; - mockUnreads = { ...mockUnreads, isSelfDm: true }; - - renderRoomRightButtons(); - - expect(screen.queryByTestId('room-view-header-call')).not.toBeOnTheScreen(); - }); - }); + it('renders every present item directly with no overflow menu', () => { + mockHasE2EEWarning = true; + mockCanToggleEncryption = true; + mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - describe('on Android', () => { - beforeEach(() => { - mockIsIOS = false; - }); + renderRoomRightButtons(); - it('renders every present item directly with no overflow menu', () => { - mockHasE2EEWarning = true; - mockCanToggleEncryption = true; - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; + expect(screen.getByTestId('room-view-header-encryption')).toBeOnTheScreen(); + expect(screen.getByTestId('room-view-push-troubleshoot')).toBeOnTheScreen(); + expect(screen.getByTestId('room-view-header-threads')).toBeOnTheScreen(); + expect(screen.getByTestId('room-view-search')).toBeOnTheScreen(); + expect(screen.queryByTestId('room-view-header-more')).not.toBeOnTheScreen(); + }); - renderRoomRightButtons(); + it('hides the call button on a self DM', () => { + mockRoomState = { room: { rid: 'rid-1', t: 'd', name: 'user' } }; + mockUnreads = { ...mockUnreads, isSelfDm: true }; - expect(screen.getByTestId('room-view-header-encryption')).toBeOnTheScreen(); - expect(screen.getByTestId('room-view-push-troubleshoot')).toBeOnTheScreen(); - expect(screen.getByTestId('room-view-header-threads')).toBeOnTheScreen(); - expect(screen.getByTestId('room-view-search')).toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-header-more')).not.toBeOnTheScreen(); - }); + renderRoomRightButtons(); + + expect(screen.queryByTestId('header-call-button-stub')).not.toBeOnTheScreen(); }); - describe('on iPad', () => { - beforeEach(() => { - mockIsTablet = true; - }); + it('hides the threads button when threads are disabled', () => { + mockThreadsEnabled = false; - it('renders every present item directly with no overflow menu', () => { - renderRoomRightButtons(); + renderRoomRightButtons(); - expect(screen.getByTestId('room-view-header-threads')).toBeOnTheScreen(); - expect(screen.getByTestId('room-view-search')).toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-header-more')).not.toBeOnTheScreen(); - }); + expect(screen.queryByTestId('room-view-header-threads')).not.toBeOnTheScreen(); }); - it.each([false, true])( - 'routes notification issues to push troubleshooting from the overflow menu (master-detail: %s)', - isMasterDetail => { - mockIsMasterDetail = isMasterDetail; - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general' } }; - - renderRoomRightButtons(); - - fireEvent.press(screen.getByTestId('room-view-header-more')); - const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; - const notificationsOption = options.find((option: { testID: string }) => option.testID === 'room-view-push-troubleshoot'); - notificationsOption.onPress(); - expect(mockNavigation.navigate).toHaveBeenCalledWith( - ...(isMasterDetail - ? ['ModalStackNavigator', { screen: 'PushTroubleshootView', params: undefined }] - : ['PushTroubleshootView', undefined]) - ); - } - ); + it.each([false, true])('routes notification issues to push troubleshooting (master-detail: %s)', isMasterDetail => { + mockIsMasterDetail = isMasterDetail; + mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; + mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general' } }; + + renderRoomRightButtons(); + + fireEvent.press(screen.getByTestId('room-view-push-troubleshoot')); + expect(mockNavigation.navigate).toHaveBeenCalledWith( + ...(isMasterDetail + ? ['ModalStackNavigator', { screen: 'PushTroubleshootView', params: undefined }] + : ['PushTroubleshootView', undefined]) + ); + }); it('navigates to the threads screen', () => { mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general', encrypted: true } }; @@ -302,19 +197,17 @@ describe('RoomRightButtons', () => { expect(mockNavigation.navigate).toHaveBeenCalledWith('ThreadMessagesView', { rid: 'rid-1', t: 'c' }); }); - it('offers encryption navigation from the overflow menu while other bar items are disabled', () => { + it('offers encryption navigation while other items are disabled', () => { mockHasE2EEWarning = true; mockCanToggleEncryption = true; mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general' } }; renderRoomRightButtons(); - fireEvent.press(screen.getByTestId('room-view-header-more')); - const options = (showActionSheetRef as jest.Mock).mock.calls[0][0].options; - const encryptionOption = options.find((option: { testID: string }) => option.testID === 'room-view-header-encryption'); - expect(encryptionOption.enabled).toBe(true); + const encryptionButton = screen.getByTestId('room-view-header-encryption'); + expect(encryptionButton).toHaveProp('disabled', false); - encryptionOption.onPress(); + fireEvent.press(encryptionButton); expect(logEvent).toHaveBeenCalledTimes(1); expect(logEvent).toHaveBeenCalledWith(events.ROOM_GO_E2EE); expect(mockNavigation.navigate).toHaveBeenCalledWith('E2EEToggleRoomView', { rid: 'rid-1' }); diff --git a/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx index f82f61a5d9d..6e16f6df164 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx @@ -15,20 +15,6 @@ jest.mock('~/views/RoomView/hooks/useThreadFollowing', () => ({ jest.mock('~/lib/methods/toggleFollowThread', () => ({ toggleFollowThread: jest.fn() })); -let mockIsIOS = true; -let mockIsTablet = false; -jest.mock('~/lib/methods/helpers', () => ({ - get isIOS() { - return mockIsIOS; - }, - get isTablet() { - return mockIsTablet; - }, - get hasNativeHeaderBar() { - return mockIsIOS && !mockIsTablet; - } -})); - jest.mock('~/containers/Header/components/HeaderButton', () => { const ReactActual = jest.requireActual('react'); return { @@ -52,8 +38,6 @@ describe('ThreadRightButtons', () => { jest.clearAllMocks(); mockFollowersByThread = {}; mockAppState = { login: { user: { id: 'u1', username: 'user', token: 'tok' } } }; - mockIsIOS = true; - mockIsTablet = false; }); it('renders the follow button when the thread is not followed', () => { @@ -108,17 +92,4 @@ describe('ThreadRightButtons', () => { expect(toggleFollowThread).toHaveBeenCalledWith('tmid-1', true); }); - - it.each([ - ['Android', false, false], - ['iPad', true, true] - ])('follows the thread on the legacy bar (%s)', (_label, isIOSValue, isTabletValue) => { - mockIsIOS = isIOSValue; - mockIsTablet = isTabletValue; - - render(); - fireEvent.press(screen.getByTestId('room-view-header-follow')); - - expect(toggleFollowThread).toHaveBeenCalledWith('tmid-1', false); - }); }); diff --git a/app/views/RoomView/components/RightButtons/useRoomRightButtonsNativeData.ts b/app/views/RoomView/components/RightButtons/useRoomRightButtonsNativeData.ts deleted file mode 100644 index 59120f82b3f..00000000000 --- a/app/views/RoomView/components/RightButtons/useRoomRightButtonsNativeData.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type RoomStore } from '~/views/RoomView/definitions'; -import { useGoRoomActionsView } from '~/views/RoomView/hooks/useGoRoomActionsView'; -import { useHeaderCallPress } from './useHeaderCallPress'; - -export const useRoomRightButtonsNativeData = (rid: string, roomStore: RoomStore, isSelfDm: boolean) => { - const goRoomActionsView = useGoRoomActionsView(roomStore); - const { callPresent, isCallDisabled, onPressCall } = useHeaderCallPress(rid); - - return { - callPresent: !isSelfDm && callPresent, - isCallDisabled, - onPressCall, - goRoomActionsView - }; -}; diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index d4d23a52e7c..798dff3176b 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -8,6 +8,8 @@ import { useHeader } from '../useHeader'; import { useNativeRoomHeader } from '../useNativeRoomHeader'; jest.mock('../useNativeRoomHeader', () => ({ useNativeRoomHeader: jest.fn() })); +const mockNativeRightItems: unknown[] = []; +jest.mock('../useRoomHeaderRightItems', () => ({ useRoomHeaderRightItems: jest.fn(() => mockNativeRightItems) })); let mockTestStore: RoomStore; let mockIsIOS = false; @@ -126,6 +128,16 @@ describe('useHeader', () => { expect(typeof titleOptions.headerTitle).toBe('function'); expect(titleOptions.headerTitle().props.title).toBe('Room Title'); }); + + it('sets unstable_headerRightItems and keeps the native back button instead of headerLeft/headerRight', () => { + renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + + const sideOptions = mockSetOptions.mock.calls[0][0]; + expect(typeof sideOptions.unstable_headerRightItems).toBe('function'); + expect(sideOptions.unstable_headerRightItems()).toBe(mockNativeRightItems); + expect(sideOptions).not.toHaveProperty('headerLeft'); + expect(sideOptions).not.toHaveProperty('headerRight'); + }); }); describe('on iPad', () => { diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index 4fa0f196605..a1a87071598 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -5,7 +5,7 @@ import { useShallow } from 'zustand/react/shallow'; import { useStore } from 'zustand'; import RoomHeader from '~/containers/RoomHeader'; -import { getRoomTitle, isGroupChat } from '~/lib/methods/helpers'; +import { getRoomTitle, hasNativeHeaderBar, isGroupChat } from '~/lib/methods/helpers'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; import LeftButtons from '../components/LeftButtons'; @@ -15,6 +15,7 @@ import { type RoomStore } from '../definitions'; import { fromSubscription } from '../stores/RoomStoreContext'; import { useGoRoomActionsView } from './useGoRoomActionsView'; import { useNativeRoomHeader } from './useNativeRoomHeader'; +import { useRoomHeaderRightItems } from './useRoomHeaderRightItems'; interface IUseHeaderParams { rid?: string; @@ -69,6 +70,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa const goRoomActionsView = useGoRoomActionsView(roomStore); const nativeTitle = Platform.OS === 'ios' && Number.parseInt(String(Platform.Version), 10) >= 26; useNativeRoomHeader(!!rid && nativeTitle, headerFields, tmid, roomUserId); + const nativeRightItems = useRoomHeaderRightItems(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); useLayoutEffect(() => { if (!rid) { @@ -76,11 +78,18 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa navigation.setOptions({ headerLeft: () => }); return; } + + if (hasNativeHeaderBar) { + // Native back button keeps the native-stack default; only the right cluster is overridden. + navigation.setOptions({ unstable_headerRightItems: () => nativeRightItems }); + return; + } + navigation.setOptions({ headerLeft: () => , headerRight: () => }); - }, [rid, tmid, navigation, roomStore]); + }, [rid, tmid, navigation, roomStore, nativeRightItems]); useLayoutEffect(() => { if (!rid || nativeTitle) { diff --git a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts new file mode 100644 index 00000000000..65b50237312 --- /dev/null +++ b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts @@ -0,0 +1,308 @@ +import { useNavigation } from '@react-navigation/native'; +import { type NativeStackHeaderItem } from '@react-navigation/native-stack'; +import { useStore } from 'zustand'; +import { useShallow } from 'zustand/react/shallow'; + +import { showActionSheetRef, useActionSheet, type TActionSheetOptionsItem } from '~/containers/ActionSheet'; +import i18n from '~/i18n'; +import { useAppSelector } from '~/lib/hooks/useAppSelector'; +import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; +import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; +import { useSetting } from '~/lib/hooks/useSetting'; +import { showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; +import { events, logEvent } from '~/lib/methods/helpers/log'; +import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; +import { returnLivechat } from '~/lib/services/restApi'; +import { getUserSelector } from '~/selectors/login'; +import { useTheme } from '~/theme'; +import { type RoomStore } from '../definitions'; +import { fromSubscription } from '../stores/RoomStoreContext'; +import { closeLivechat } from '../services/closeLivechat'; +import { placeLivechatOnHold } from '../services/placeLivechatOnHold'; +import { navigateToScreen, type TRoomStackNavigation } from '../services/navigateToScreen'; +import { splitRoomHeaderActions, type TRoomHeaderActionKey } from '../helpers/roomHeaderActions'; +import { useCanPlaceLivechatOnHold } from './useCanPlaceLivechatOnHold'; +import { useGoRoomActionsView } from './useGoRoomActionsView'; +import { useThreadFollowing } from './useThreadFollowing'; +import { useRoomRightButtonsData } from '../components/RightButtons/useRoomRightButtonsData'; +import { useHeaderCallPress } from '../components/RightButtons/useHeaderCallPress'; + +const EMPTY_ITEMS: NativeStackHeaderItem[] = []; + +const getTunreadBadgeColor = ( + tunreadUser: string[], + tunreadGroup: string[], + colors: { badgeBackgroundLevel4: string; badgeBackgroundLevel3: string; fontInfo: string } +) => { + if (tunreadUser.length) return colors.badgeBackgroundLevel4; + if (tunreadGroup.length) return colors.badgeBackgroundLevel3; + return colors.fontInfo; +}; + +const useOmnichannelRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): NativeStackHeaderItem[] => { + const navigation = useNavigation(); + const isMasterDetail = useMasterDetail(); + const { showActionSheet } = useActionSheet(); + const livechatRequestComment = useSetting('Livechat_request_comment_when_closing_conversation') as boolean; + + const departmentId = useStore( + roomStore, + fromSubscription(room => room.departmentId, undefined) + ); + const canForwardGuest = useStore(roomStore, s => s.canForwardGuest); + const canReturnQueue = useCanReturnQueue(enabled); + const canPlaceLivechatOnHold = useCanPlaceLivechatOnHold(roomStore); + + const handleReturnLivechat = () => { + showConfirmationAlert({ + message: i18n.t('Would_you_like_to_return_the_inquiry'), + confirmationText: i18n.t('Yes'), + onPress: async () => { + try { + await returnLivechat(rid, departmentId); + } catch (e: any) { + showErrorAlert(e.reason, i18n.t('Oops')); + } + } + }); + }; + + const showMoreActions = () => { + logEvent(events.ROOM_SHOW_MORE_ACTIONS); + const options = [] as TActionSheetOptionsItem[]; + if (canPlaceLivechatOnHold) { + options.push({ + title: i18n.t('Place_chat_on_hold'), + icon: 'pause', + onPress: () => placeLivechatOnHold({ rid, navigation }) + }); + } + if (canForwardGuest) { + options.push({ + title: i18n.t('Forward_Chat'), + icon: 'chat-forward', + onPress: () => { + navigateToScreen({ navigation, isMasterDetail, screen: 'ForwardLivechatView', params: { rid } }); + } + }); + } + if (canReturnQueue) { + options.push({ + title: i18n.t('Return_to_waiting_line'), + icon: 'move-to-the-queue', + onPress: () => handleReturnLivechat() + }); + } + options.push({ + title: i18n.t('Close'), + icon: 'chat-close', + onPress: () => closeLivechat({ rid, departmentId, isMasterDetail, livechatRequestComment, navigation }), + danger: true + }); + showActionSheet({ options }); + }; + + if (!enabled) { + return EMPTY_ITEMS; + } + + return [ + { + type: 'button', + label: i18n.t('More'), + accessibilityLabel: i18n.t('More'), + icon: { type: 'sfSymbol', name: 'ellipsis' }, + onPress: showMoreActions + } + ]; +}; + +const useThreadRightItems = (tmid: string | undefined, enabled: boolean): NativeStackHeaderItem[] => { + const userId = useAppSelector(state => getUserSelector(state).id); + const isFollowingThread = useThreadFollowing(tmid, userId); + + const onToggleFollowThread = () => { + logEvent(events.ROOM_TOGGLE_FOLLOW_THREADS); + if (tmid) { + toggleFollowThread(tmid, isFollowingThread); + } + }; + + if (!enabled) { + return EMPTY_ITEMS; + } + + return [ + { + type: 'button', + label: i18n.t(isFollowingThread ? 'Unfollow_thread' : 'Follow_thread'), + accessibilityLabel: i18n.t(isFollowingThread ? 'Unfollow_thread' : 'Follow_thread'), + icon: { type: 'sfSymbol', name: isFollowingThread ? 'bell' : 'bell.slash' }, + onPress: onToggleFollowThread + } + ]; +}; + +const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): NativeStackHeaderItem[] => { + const { colors } = useTheme(); + const data = useRoomRightButtonsData(rid, roomStore); + const { + threadsEnabled, + issuesWithNotifications, + disableNotifications, + hasE2EEWarning, + canToggleEncryption, + isSelfDm, + tunread, + tunreadUser, + tunreadGroup, + callAccessibilityLabel, + goThreadsView, + navigateToNotificationOrPushTroubleshoot, + goSearchView, + goE2EEToggleRoomView, + threadsAccessibilityLabel + } = data; + const { callPresent: callPresentRaw, isCallDisabled, onPressCall } = useHeaderCallPress(rid); + const callPresent = !isSelfDm && callPresentRaw; + const goRoomActions = useGoRoomActionsView(roomStore); + + const { visibleKeys, overflowKeys } = splitRoomHeaderActions({ + threads: threadsEnabled, + call: callPresent, + encryption: hasE2EEWarning, + notifications: issuesWithNotifications || disableNotifications + }); + const isVisible = (key: TRoomHeaderActionKey) => visibleKeys.includes(key); + + const tunreadBadge = + threadsEnabled && tunread.length + ? { value: tunread.length, style: { backgroundColor: getTunreadBadgeColor(tunreadUser ?? [], tunreadGroup ?? [], colors) } } + : undefined; + + const overflowOptions = [ + ...overflowKeys.map(key => { + if (key === 'threads') { + return { title: threadsAccessibilityLabel, icon: 'threads' as const, onPress: goThreadsView }; + } + if (key === 'encryption') { + return { + title: i18n.t('Encrypted'), + icon: 'encrypted' as const, + enabled: canToggleEncryption, + onPress: goE2EEToggleRoomView + }; + } + if (key === 'call') { + return { + title: callAccessibilityLabel, + icon: 'phone' as const, + enabled: !isCallDisabled, + onPress: onPressCall + }; + } + if (key === 'notifications') { + return { + title: i18n.t('Troubleshooting'), + icon: 'notification-disabled' as const, + onPress: navigateToNotificationOrPushTroubleshoot + }; + } + throw new Error(`Unhandled room header action key: ${key}`); + }), + { title: i18n.t('Search_Messages'), icon: 'search' as const, enabled: !hasE2EEWarning, onPress: goSearchView }, + { title: i18n.t('Actions'), icon: 'kebab' as const, onPress: () => goRoomActions() } + ]; + + if (!enabled) { + return EMPTY_ITEMS; + } + + const items: NativeStackHeaderItem[] = []; + if (isVisible('encryption')) { + items.push({ + type: 'button', + label: i18n.t('Encrypted'), + accessibilityLabel: i18n.t('Encrypted'), + icon: { type: 'sfSymbol', name: 'lock' }, + disabled: !canToggleEncryption, + onPress: goE2EEToggleRoomView + }); + } + if (isVisible('notifications')) { + items.push({ + type: 'button', + label: i18n.t('Troubleshooting'), + accessibilityLabel: i18n.t('Troubleshooting'), + icon: { type: 'sfSymbol', name: 'bell.slash' }, + tintColor: issuesWithNotifications ? colors.fontDanger : undefined, + disabled: hasE2EEWarning, + onPress: navigateToNotificationOrPushTroubleshoot + }); + } + if (isVisible('call')) { + items.push({ + type: 'button', + label: callAccessibilityLabel, + accessibilityLabel: callAccessibilityLabel, + icon: { type: 'sfSymbol', name: 'phone' }, + disabled: hasE2EEWarning || isCallDisabled, + onPress: onPressCall + }); + } + if (isVisible('threads')) { + items.push({ + type: 'button', + label: threadsAccessibilityLabel, + accessibilityLabel: threadsAccessibilityLabel, + icon: { type: 'sfSymbol', name: 'bubble.left.and.bubble.right' }, + disabled: hasE2EEWarning, + badge: tunreadBadge, + onPress: goThreadsView + }); + } + items.push({ + type: 'button', + label: i18n.t('More'), + accessibilityLabel: i18n.t('More'), + icon: { type: 'sfSymbol', name: 'ellipsis' }, + onPress: () => showActionSheetRef({ options: overflowOptions }) + }); + + return items; +}; + +export const useRoomHeaderRightItems = ( + rid: string | undefined, + tmid: string | undefined, + roomStore: RoomStore +): NativeStackHeaderItem[] => { + const { t, status, membership } = useStore( + roomStore, + useShallow(s => ({ + t: s.room.t, + status: fromSubscription(r => r.status, undefined)(s), + membership: s.membership + })) + ); + + const isInvited = membership === 'invited'; + const isOmnichannel = !!rid && !isInvited && t === 'l' && status !== 'queued' && membership === 'subscribed'; + const isThread = !!rid && !isInvited && !isOmnichannel && !!tmid; + const isRoom = !!rid && !isInvited && !isOmnichannel && !isThread; + + const omnichannelItems = useOmnichannelRightItems(rid ?? '', roomStore, isOmnichannel); + const threadItems = useThreadRightItems(tmid, isThread); + const roomItems = useRoomRightItems(rid ?? '', roomStore, isRoom); + + if (!rid || isInvited) { + return EMPTY_ITEMS; + } + if (isOmnichannel) { + return omnichannelItems; + } + if (isThread) { + return threadItems; + } + return roomItems; +}; diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index b074980f5c2..355f4d2715a 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -31,8 +31,10 @@ jest.mock('~/lib/hooks/usePermissions', () => ({ usePermissions: () => [true, fa jest.mock('~/selectors/login', () => ({ getUserSelector: () => ({ requirePasswordChange: false }) })); const mockColors = { fontDanger: '#f00', buttonBackgroundDangerDefault: '#f00', userPresenceDisabled: '#f00' }; jest.mock('~/theme', () => ({ useTheme: () => ({ colors: mockColors }) })); -jest.mock('~/containers/ActionSheet', () => ({ showActionSheetRef: jest.fn() })); +const mockShowActionSheetRef = jest.fn(); +jest.mock('~/containers/ActionSheet', () => ({ showActionSheetRef: (...args: unknown[]) => mockShowActionSheetRef(...args) })); jest.mock('../../components/Header', () => ({ __esModule: true, default: 'RoomsListHeaderView' })); +jest.mock('../../components/ServersList', () => ({ __esModule: true, default: 'ServersList' })); jest.mock('~/containers/Header/components/HeaderButton', () => { const ReactActual = jest.requireActual('react'); @@ -128,26 +130,41 @@ describe('RoomsListView useHeader', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; - const rightButtons: ReactElement<{ testID: string }>[] = options.headerRight().props.children.filter(Boolean); - const testIDs = rightButtons.map(button => button.props.testID); - expect(testIDs).toEqual(['rooms-list-view-create-channel', 'rooms-list-view-push-troubleshoot', 'rooms-list-view-directory']); + const rightItems = options.unstable_headerRightItems(); + const labels = rightItems.map((item: { accessibilityLabel: string }) => item.accessibilityLabel); + expect(labels).toEqual(['Create new channel, team, direct message or discussion', 'Troubleshooting', 'Directory']); }); it('does not render an overflow control when nothing extra is present', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; - const rightButtons: ReactElement<{ testID: string }>[] = options.headerRight().props.children.filter(Boolean); - const testIDs = rightButtons.map(button => button.props.testID); - expect(testIDs).toEqual(['rooms-list-view-create-channel', 'rooms-list-view-directory']); + const rightItems = options.unstable_headerRightItems(); + const labels = rightItems.map((item: { accessibilityLabel: string }) => item.accessibilityLabel); + expect(labels).toEqual(['Create new channel, team, direct message or discussion', 'Directory']); + expect(rightItems.every((item: { type: string }) => item.type === 'button')).toBe(true); + }); + + it('exposes the drawer and server switcher as native left items', () => { + renderUseHeader(); + + const options = mockSetOptions.mock.calls[0][0]; + const leftItems = options.unstable_headerLeftItems(); + expect(leftItems).toHaveLength(2); + expect(leftItems[0].icon).toEqual({ type: 'sfSymbol', name: 'line.3.horizontal' }); + expect(leftItems[1].icon).toEqual({ type: 'sfSymbol', name: 'server.rack' }); + expect(leftItems[1].label).toBe('Rocket.Chat'); + + leftItems[1].onPress(); + expect(mockShowActionSheetRef).toHaveBeenCalledWith(expect.objectContaining({ enableContentPanningGesture: false })); }); it('configures a system search bar instead of a right-cluster search item', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; - const rightButtons: ReactElement<{ testID: string }>[] = options.headerRight().props.children.filter(Boolean); - expect(rightButtons.some(button => button.props.testID === 'rooms-list-view-search')).toBe(false); + const rightItems = options.unstable_headerRightItems(); + expect(rightItems.some((item: { accessibilityLabel: string }) => item.accessibilityLabel === 'Search')).toBe(false); expect(options.headerSearchBarOptions.placement).toBe('automatic'); expect(options.headerSearchBarOptions.ref.current).toBeNull(); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index a8a2d7758fa..43d14d9828e 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -5,7 +5,6 @@ import { type KeyboardFocus } from 'react-native-external-keyboard'; import { type SearchBarCommands } from 'react-native-screens'; import { showActionSheetRef } from '~/containers/ActionSheet'; -import { type TIconsName } from '~/containers/CustomIcon'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import i18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; @@ -17,6 +16,7 @@ import { events, logEvent } from '~/lib/methods/helpers/log'; import { getUserSelector } from '~/selectors/login'; import { useTheme } from '~/theme'; import RoomsListHeaderView from '../components/Header'; +import ServersList from '../components/ServersList'; import { RoomsSearchContext } from '../contexts/RoomsSearchProvider'; const MAX_HEADER_RIGHT_ACTIONS = 2; @@ -24,10 +24,9 @@ const MAX_HEADER_RIGHT_ACTIONS = 2; interface IHeaderRightAction { key: string; present: boolean; - iconName: TIconsName; - testID: string; + sfSymbol: string; accessibilityLabel: string; - color?: string; + tintColor?: string; disabled?: boolean; onPress: () => void; } @@ -97,6 +96,13 @@ export const useHeader = () => { return null; }, [supportedVersionsStatus, notificationPresenceCap, colors]); + const nativeBadgeColor = + supportedVersionsStatus === 'warn' + ? colors.buttonBackgroundDangerDefault + : notificationPresenceCap + ? colors.userPresenceDisabled + : undefined; + const goDirectory = useCallback(() => { logEvent(events.RL_GO_DIRECTORY); if (isMasterDetail) { @@ -162,27 +168,24 @@ export const useHeader = () => { { key: 'create', present: canCreateRoom, - iconName: 'add', + sfSymbol: 'plus', accessibilityLabel: i18n.t('Create_new_channel_team_dm_discussion'), - testID: 'rooms-list-view-create-channel', disabled, onPress: goToNewMessage }, { key: 'push-troubleshoot', present: issuesWithNotifications, - iconName: 'notification-disabled', + sfSymbol: 'bell.slash', accessibilityLabel: i18n.t('Troubleshooting'), - testID: 'rooms-list-view-push-troubleshoot', - color: colors.fontDanger, + tintColor: colors.fontDanger, onPress: navigateToPushTroubleshootView }, { key: 'directory', present: true, - iconName: 'directory', + sfSymbol: 'globe', accessibilityLabel: i18n.t('Directory'), - testID: 'rooms-list-view-directory', disabled, onPress: goDirectory } @@ -192,7 +195,6 @@ export const useHeader = () => { headerLargeTitle: false, headerTitle: serverName, headerSubtitle: nativeHeaderSubtitle, - headerLeft, headerStyle: { backgroundColor: colors.surfaceNeutral }, headerTransparent: false, scrollEdgeEffects: { top: 'hidden' }, @@ -204,41 +206,55 @@ export const useHeader = () => { onChangeText: (event: { nativeEvent: { text: string } }) => search(event.nativeEvent.text), onCancelButtonPress: stopSearch }, - headerRight: () => ( - - {[ - ...visible.map(action => ( - - )), - overflow.length >= 2 ? ( - - showActionSheetRef({ - options: overflow.map(action => ({ - title: action.accessibilityLabel, - icon: action.iconName, - testID: action.testID, - onPress: action.onPress - })) - }) + unstable_headerLeftItems: () => [ + { + type: 'button', + label: i18n.t('Menu'), + accessibilityLabel: i18n.t('Menu'), + icon: { type: 'sfSymbol', name: 'line.3.horizontal' }, + disabled, + badge: nativeBadgeColor ? { value: '', style: { backgroundColor: nativeBadgeColor } } : undefined, + onPress: isMasterDetail + ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) + : () => navigation.toggleDrawer() + }, + { + type: 'button', + label: serverName, + accessibilityLabel: `${serverName} ${nativeHeaderSubtitle}`, + icon: { type: 'sfSymbol', name: 'server.rack' }, + onPress: () => showActionSheetRef({ children: , enableContentPanningGesture: false }) + } + ], + unstable_headerRightItems: () => [ + ...visible.map(action => ({ + type: 'button' as const, + label: action.accessibilityLabel, + accessibilityLabel: action.accessibilityLabel, + icon: { type: 'sfSymbol' as const, name: action.sfSymbol }, + tintColor: action.tintColor, + disabled: action.disabled, + onPress: action.onPress + })), + ...(overflow.length >= 2 + ? [ + { + type: 'menu' as const, + label: i18n.t('More'), + accessibilityLabel: i18n.t('More'), + icon: { type: 'sfSymbol' as const, name: 'ellipsis' }, + menu: { + items: overflow.map(action => ({ + type: 'action' as const, + label: action.accessibilityLabel, + icon: { type: 'sfSymbol' as const, name: action.sfSymbol }, + onPress: action.onPress + })) } - /> - ) : null - ]} - - ) + } + ] + : []) + ] }); return; } @@ -303,7 +319,8 @@ export const useHeader = () => { stopSearch, search, serverName, - nativeHeaderSubtitle + nativeHeaderSubtitle, + nativeBadgeColor ]); useEffect(() => { From a59cf671146c7122de50d6b9478867790a2badaf Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 11:07:40 -0300 Subject: [PATCH 055/120] fix(test): match both Android back-button testIDs in go-back helper The custom Android header (app/containers/Header/index.tsx) renders the back button with testID 'custom-header-back', while some screens using HeaderBackButton directly keep the default 'header-back'. The Android branch of the go-back helper only matched 'header-back' as a full regex, so it never found the header rendered by the custom Header component and failed with 'Element not found: Id matching regex: header-back'. --- .maestro/helpers/go-back.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.maestro/helpers/go-back.yaml b/.maestro/helpers/go-back.yaml index 450c744684e..3e01792413e 100644 --- a/.maestro/helpers/go-back.yaml +++ b/.maestro/helpers/go-back.yaml @@ -9,7 +9,7 @@ tags: platform: Android commands: - tapOn: - id: 'header-back' + id: '(custom-)?header-back' retryTapIfNoChange: true - runFlow: From 8946b90f25a7fc5815aa63c9ca4e2644ca5ec045 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 11:36:38 -0300 Subject: [PATCH 056/120] fix(test): match iOS native header items by label instead of testID iOS native stack header items (unstable_headerLeftItems/RightItems) and the iOS 26+ native room title carry no accessibilityIdentifier, only a label/text. Add tap-header-item and assert-room-title helpers that tap or assert by testID on Android and by visible label/text on iOS, and wire them into the sidebar, create-channel, directory, room-title and room-actions-overflow flows that previously assumed a testID existed on both platforms. --- .maestro/helpers/assert-room-title.yaml | 25 +++++++++ .maestro/helpers/logout.yaml | 10 ++++ .maestro/helpers/navigate-to-recent-room.yaml | 8 +-- .maestro/helpers/navigate-to-room.yaml | 8 +-- .maestro/helpers/open-room-actions.yaml | 8 ++- .maestro/helpers/open-servers-list.yaml | 2 +- .maestro/helpers/tap-header-item.yaml | 21 +++++++ .maestro/tests/assorted/display-perf.yaml | 46 +++++++++++---- .maestro/tests/assorted/profile.yaml | 11 ++-- .maestro/tests/assorted/screen-lock.yaml | 14 +++-- .maestro/tests/assorted/setting.yaml | 16 ++++-- .maestro/tests/assorted/status.yaml | 11 ++-- .../utils/go-to-accessibility-appearance.yaml | 11 ++-- .../assorted/utils/go-to-display-pref.yaml | 11 ++-- .../assorted/utils/go-to-user-preference.yaml | 11 ++-- .../tests/assorted/utils/nav-to-language.yaml | 11 ++-- .../e2ee/utils/navigate-to-e2ee-security.yaml | 12 ++-- .../tests/room/message-markdown-click.yaml | 56 +++++++++---------- .maestro/tests/room/share-message.yaml | 8 +-- .maestro/tests/room/utils/clear-cache.yaml | 7 ++- .maestro/tests/teams/convert-team.yaml | 24 ++++---- .maestro/tests/teams/create-team.yaml | 15 +++-- .maestro/tests/teams/team.yaml | 22 ++++---- .../tests/teams/utils/create-channel.yaml | 19 +++---- 24 files changed, 240 insertions(+), 147 deletions(-) create mode 100644 .maestro/helpers/assert-room-title.yaml create mode 100644 .maestro/helpers/tap-header-item.yaml diff --git a/.maestro/helpers/assert-room-title.yaml b/.maestro/helpers/assert-room-title.yaml new file mode 100644 index 00000000000..da6244ed013 --- /dev/null +++ b/.maestro/helpers/assert-room-title.yaml @@ -0,0 +1,25 @@ +appId: ${APP_ID} +name: Assert room title +tags: + - 'util' + +--- +# On iOS 26+ the room title is the native stack's headerTitle text with no accessibilityIdentifier, +# so it is matched by its visible text there. Android keeps the JS-rendered title, matched by testID. +- runFlow: + when: + platform: Android + commands: + - extendedWaitUntil: + visible: + id: 'room-view-title-${ROOM}' + timeout: 60000 + +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: + text: '${ROOM}' + timeout: 60000 diff --git a/.maestro/helpers/logout.yaml b/.maestro/helpers/logout.yaml index 70ddc19cfb7..b19b9eba4ee 100644 --- a/.maestro/helpers/logout.yaml +++ b/.maestro/helpers/logout.yaml @@ -14,12 +14,22 @@ tags: - runFlow: when: + platform: Android visible: id: 'rooms-list-view-sidebar' commands: - tapOn: id: 'rooms-list-view-sidebar' +- runFlow: + when: + platform: iOS + visible: + text: 'Menu' + commands: + - tapOn: + text: 'Menu' + - runFlow: when: platform: Android diff --git a/.maestro/helpers/navigate-to-recent-room.yaml b/.maestro/helpers/navigate-to-recent-room.yaml index 310e019ea2c..5da07125d33 100644 --- a/.maestro/helpers/navigate-to-recent-room.yaml +++ b/.maestro/helpers/navigate-to-recent-room.yaml @@ -24,7 +24,7 @@ tags: timeout: 60000 - tapOn: id: 'rooms-list-view-item-${ROOM}' -- extendedWaitUntil: - visible: - id: 'room-view-title-${ROOM}' - timeout: 60000 +- runFlow: + file: 'assert-room-title.yaml' + env: + ROOM: ${ROOM} diff --git a/.maestro/helpers/navigate-to-room.yaml b/.maestro/helpers/navigate-to-room.yaml index e8eba0728b0..0f899464db7 100644 --- a/.maestro/helpers/navigate-to-room.yaml +++ b/.maestro/helpers/navigate-to-room.yaml @@ -11,7 +11,7 @@ tags: - waitForAnimationToEnd - tapOn: id: 'rooms-list-view-item-${ROOM}' -- extendedWaitUntil: - visible: - id: 'room-view-title-${ROOM}' - timeout: 60000 +- runFlow: + file: 'assert-room-title.yaml' + env: + ROOM: ${ROOM} diff --git a/.maestro/helpers/open-room-actions.yaml b/.maestro/helpers/open-room-actions.yaml index 5697cf0b288..ecb67ef40e3 100644 --- a/.maestro/helpers/open-room-actions.yaml +++ b/.maestro/helpers/open-room-actions.yaml @@ -8,15 +8,17 @@ tags: when: platform: iOS commands: + # The native header's overflow ("More") item carries only a label, no accessibilityIdentifier. + # It opens a JS action sheet whose "Actions" entry navigates to Room Actions. - tapOn: - id: 'room-view-header-more' + text: 'More' retryTapIfNoChange: true - extendedWaitUntil: visible: - id: 'action-sheet-handle' + text: 'Actions' timeout: 60000 - tapOn: - id: 'room-view-header-room-actions' + text: 'Actions' - runFlow: when: platform: Android diff --git a/.maestro/helpers/open-servers-list.yaml b/.maestro/helpers/open-servers-list.yaml index 366cc97c2b6..8bee46aae21 100644 --- a/.maestro/helpers/open-servers-list.yaml +++ b/.maestro/helpers/open-servers-list.yaml @@ -9,7 +9,7 @@ tags: platform: iOS commands: - tapOn: - id: 'rooms-list-view-sidebar' + text: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-workspaces' diff --git a/.maestro/helpers/tap-header-item.yaml b/.maestro/helpers/tap-header-item.yaml new file mode 100644 index 00000000000..963ba9f797c --- /dev/null +++ b/.maestro/helpers/tap-header-item.yaml @@ -0,0 +1,21 @@ +appId: ${APP_ID} +name: Tap header item +tags: + - 'util' + +--- +# iOS native header items (unstable_headerLeftItems/RightItems) carry no accessibilityIdentifier, +# only a label. Android keeps the JS-rendered header, matched by testID as before. +- runFlow: + when: + platform: Android + commands: + - tapOn: + id: ${ANDROID_ID} + +- runFlow: + when: + platform: iOS + commands: + - tapOn: + text: ${IOS_LABEL} diff --git a/.maestro/tests/assorted/display-perf.yaml b/.maestro/tests/assorted/display-perf.yaml index 54927e65a37..bbdaf0b54d7 100644 --- a/.maestro/tests/assorted/display-perf.yaml +++ b/.maestro/tests/assorted/display-perf.yaml @@ -30,18 +30,44 @@ tags: label: 'Should have room item' # should have create channel button -- extendedWaitUntil: - visible: - id: 'rooms-list-view-create-channel' - timeout: 60000 - label: 'Should have create channel button' +- runFlow: + when: + platform: Android + commands: + - extendedWaitUntil: + visible: + id: 'rooms-list-view-create-channel' + timeout: 60000 + label: 'Should have create channel button' +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: + text: 'Create new channel, team, direct message or discussion' + timeout: 60000 + label: 'Should have create channel button' # should have sidebar button -- extendedWaitUntil: - visible: - id: 'rooms-list-view-sidebar' - timeout: 60000 - label: 'Should have sidebar button' +- runFlow: + when: + platform: Android + commands: + - extendedWaitUntil: + visible: + id: 'rooms-list-view-sidebar' + timeout: 60000 + label: 'Should have sidebar button' +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: + text: 'Menu' + timeout: 60000 + label: 'Should have sidebar button' # should go to Display Preferences - runFlow: diff --git a/.maestro/tests/assorted/profile.yaml b/.maestro/tests/assorted/profile.yaml index 257cc297509..e973fcfe20a 100644 --- a/.maestro/tests/assorted/profile.yaml +++ b/.maestro/tests/assorted/profile.yaml @@ -14,12 +14,11 @@ tags: env: USERNAME: ${output.user.username} PASSWORD: ${output.user.password} -- extendedWaitUntil: - visible: - id: 'rooms-list-view-sidebar' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' diff --git a/.maestro/tests/assorted/screen-lock.yaml b/.maestro/tests/assorted/screen-lock.yaml index 4ea169bfcb4..7fb93da90df 100644 --- a/.maestro/tests/assorted/screen-lock.yaml +++ b/.maestro/tests/assorted/screen-lock.yaml @@ -20,8 +20,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-settings' @@ -122,8 +125,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-settings' diff --git a/.maestro/tests/assorted/setting.yaml b/.maestro/tests/assorted/setting.yaml index 9186da79f1e..3d4a08d2143 100644 --- a/.maestro/tests/assorted/setting.yaml +++ b/.maestro/tests/assorted/setting.yaml @@ -20,8 +20,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' @@ -177,10 +180,11 @@ tags: timeout: 60000 # should navigate to legalview -- assertVisible: - id: 'rooms-list-view-sidebar' -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' diff --git a/.maestro/tests/assorted/status.yaml b/.maestro/tests/assorted/status.yaml index 88e5f418369..63ef6bcc4fc 100644 --- a/.maestro/tests/assorted/status.yaml +++ b/.maestro/tests/assorted/status.yaml @@ -14,12 +14,11 @@ tags: env: USERNAME: ${output.user.username} PASSWORD: ${output.user.password} -- extendedWaitUntil: - visible: - id: 'rooms-list-view-sidebar' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' diff --git a/.maestro/tests/assorted/utils/go-to-accessibility-appearance.yaml b/.maestro/tests/assorted/utils/go-to-accessibility-appearance.yaml index f54a4946e4c..42b2309f656 100644 --- a/.maestro/tests/assorted/utils/go-to-accessibility-appearance.yaml +++ b/.maestro/tests/assorted/utils/go-to-accessibility-appearance.yaml @@ -4,12 +4,11 @@ tags: - 'util' --- -- extendedWaitUntil: - visible: - id: 'rooms-list-view-sidebar' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-accessibility' diff --git a/.maestro/tests/assorted/utils/go-to-display-pref.yaml b/.maestro/tests/assorted/utils/go-to-display-pref.yaml index 4acea68390c..110ae664ff4 100644 --- a/.maestro/tests/assorted/utils/go-to-display-pref.yaml +++ b/.maestro/tests/assorted/utils/go-to-display-pref.yaml @@ -4,12 +4,11 @@ tags: - 'util' --- -- extendedWaitUntil: - visible: - id: 'rooms-list-view-sidebar' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-accessibility' diff --git a/.maestro/tests/assorted/utils/go-to-user-preference.yaml b/.maestro/tests/assorted/utils/go-to-user-preference.yaml index 3a735db6b03..18ac81e4df5 100644 --- a/.maestro/tests/assorted/utils/go-to-user-preference.yaml +++ b/.maestro/tests/assorted/utils/go-to-user-preference.yaml @@ -4,12 +4,11 @@ tags: - 'util' --- -- extendedWaitUntil: - visible: - id: 'rooms-list-view-sidebar' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-profile' diff --git a/.maestro/tests/assorted/utils/nav-to-language.yaml b/.maestro/tests/assorted/utils/nav-to-language.yaml index e92b341fad1..1cfd88f8d61 100644 --- a/.maestro/tests/assorted/utils/nav-to-language.yaml +++ b/.maestro/tests/assorted/utils/nav-to-language.yaml @@ -8,12 +8,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'rooms-list-view-sidebar' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' diff --git a/.maestro/tests/e2ee/utils/navigate-to-e2ee-security.yaml b/.maestro/tests/e2ee/utils/navigate-to-e2ee-security.yaml index 00c21e89233..e37a6e5bd0e 100644 --- a/.maestro/tests/e2ee/utils/navigate-to-e2ee-security.yaml +++ b/.maestro/tests/e2ee/utils/navigate-to-e2ee-security.yaml @@ -18,13 +18,11 @@ tags: visible: 'Enter E2EE password' commands: - assertVisible: Enter E2EE password -- extendedWaitUntil: - visible: - id: rooms-list-view-sidebar - enabled: true - timeout: 60000 -- tapOn: - id: rooms-list-view-sidebar +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: sidebar-view diff --git a/.maestro/tests/room/message-markdown-click.yaml b/.maestro/tests/room/message-markdown-click.yaml index d102fff2e81..c6ea511cd20 100644 --- a/.maestro/tests/room/message-markdown-click.yaml +++ b/.maestro/tests/room/message-markdown-click.yaml @@ -60,10 +60,10 @@ tags: - runFlow: '../../helpers/go-back.yaml' # Wait for room view to be visible again -- extendedWaitUntil: - visible: - id: 'room-view-title-maestro-message-clickable-test' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: maestro-message-clickable-test # Tap on the URL link to open alert - runFlow: @@ -139,55 +139,55 @@ tags: - runFlow: '../../helpers/go-back.yaml' # Wait for room view to be visible again -- extendedWaitUntil: - visible: - id: 'room-view-title-maestro-message-clickable-test' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: maestro-message-clickable-test # Tap on message with thread to open thread room - tapOn: text: '.*Message with thread*.' # Verify thread room view is opened with the correct id -- extendedWaitUntil: - visible: - id: 'room-view-title-message with thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: message with thread # Go back to main room - runFlow: '../../helpers/go-back.yaml' # Wait for room view to be visible again -- extendedWaitUntil: - visible: - id: 'room-view-title-maestro-message-clickable-test' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: maestro-message-clickable-test # Tap on "View thread" button - tapOn: id: 'message-thread-button-message with thread' # Verify thread room view is opened -- extendedWaitUntil: - visible: - id: 'room-view-title-message with thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: message with thread # Go back to main room - runFlow: '../../helpers/go-back.yaml' # Wait for room view to be visible again -- extendedWaitUntil: - visible: - id: 'room-view-title-maestro-message-clickable-test' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: maestro-message-clickable-test # Now tap on a message within the thread - tapOn: text: '.*a message in thread*.' # Verify thread room view remains (clicking message in thread stays in thread) -- extendedWaitUntil: - visible: - id: 'room-view-title-message with thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: message with thread diff --git a/.maestro/tests/room/share-message.yaml b/.maestro/tests/room/share-message.yaml index 461818f2f37..0448192fe26 100644 --- a/.maestro/tests/room/share-message.yaml +++ b/.maestro/tests/room/share-message.yaml @@ -89,10 +89,10 @@ tags: timeout: 60000 - tapOn: id: 'forward-message-view-send' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} # should go to otherUser DM and verify if exist both messages - runFlow: '../../helpers/go-back.yaml' diff --git a/.maestro/tests/room/utils/clear-cache.yaml b/.maestro/tests/room/utils/clear-cache.yaml index 35a30021282..d54f70f408a 100644 --- a/.maestro/tests/room/utils/clear-cache.yaml +++ b/.maestro/tests/room/utils/clear-cache.yaml @@ -13,8 +13,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - waitForAnimationToEnd: timeout: 60000 - extendedWaitUntil: diff --git a/.maestro/tests/teams/convert-team.yaml b/.maestro/tests/teams/convert-team.yaml index 004241b7413..0442b800a1b 100644 --- a/.maestro/tests/teams/convert-team.yaml +++ b/.maestro/tests/teams/convert-team.yaml @@ -42,10 +42,10 @@ tags: text: 'Convert' - tapOn: text: 'Convert' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.publicChannelToBeConverted}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.publicChannelToBeConverted} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -74,10 +74,10 @@ tags: text: 'Convert' - tapOn: text: 'Convert' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.toBeConverted}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.toBeConverted} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -173,10 +173,10 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.toBeConverted}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.toBeConverted} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: diff --git a/.maestro/tests/teams/create-team.yaml b/.maestro/tests/teams/create-team.yaml index 4712adaaaad..fbc106dd3ff 100644 --- a/.maestro/tests/teams/create-team.yaml +++ b/.maestro/tests/teams/create-team.yaml @@ -17,10 +17,11 @@ tags: USERNAME: ${output.user.username} PASSWORD: ${output.user.password} -- assertVisible: - id: 'rooms-list-view-create-channel' -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' - assertVisible: id: 'new-message-view-create-team' - tapOn: @@ -39,8 +40,10 @@ tags: id: 'create-channel-submit' - assertVisible: id: 'room-view-messages' -- assertVisible: - id: 'room-view-title-${output.teamname}' +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.teamname} - runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: 'room-actions-info' diff --git a/.maestro/tests/teams/team.yaml b/.maestro/tests/teams/team.yaml index 5923f0a0356..691150451cd 100644 --- a/.maestro/tests/teams/team.yaml +++ b/.maestro/tests/teams/team.yaml @@ -178,12 +178,14 @@ tags: id: rooms-list-view-item-${output.privateRoomName} - tapOn: id: rooms-list-view-item-${output.privateRoomName} -- extendedWaitUntil: - visible: - id: room-view-title-${output.privateRoomName} - timeout: 60000 -- assertVisible: - id: room-view-title-${output.privateRoomName} +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.privateRoomName} +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.privateRoomName} - assertVisible: id: room-view-header-call - assertVisible: @@ -258,10 +260,10 @@ tags: label: should have submit button - tapOn: id: add-existing-channel-view-submit -- extendedWaitUntil: - visible: - id: room-view-title-${output.team} - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.team} - runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: room-actions-teams diff --git a/.maestro/tests/teams/utils/create-channel.yaml b/.maestro/tests/teams/utils/create-channel.yaml index b84015412ad..63cadb5feaf 100644 --- a/.maestro/tests/teams/utils/create-channel.yaml +++ b/.maestro/tests/teams/utils/create-channel.yaml @@ -3,12 +3,11 @@ tags: - 'util' --- -- extendedWaitUntil: - visible: - id: rooms-list-view-create-channel - timeout: 60000 -- tapOn: - id: rooms-list-view-create-channel +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' - extendedWaitUntil: visible: id: new-message-view @@ -54,10 +53,10 @@ tags: visible: id: room-view timeout: 60000 -- extendedWaitUntil: - visible: - id: room-view-title-${channelName} - timeout: 60000 +- runFlow: + file: '../../../helpers/assert-room-title.yaml' + env: + ROOM: ${channelName} - runFlow: ../../../helpers/go-back.yaml - extendedWaitUntil: visible: From 0bed1b02636b6e192179c04c68ed7ddc79bd0873 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 12:00:50 -0300 Subject: [PATCH 057/120] fix(test): migrate remaining Maestro flows to iOS-safe header assertions Native iOS header items on RN's new-architecture stack carry only a visible label, no accessibilityIdentifier, so id-only assertions for rooms-list-view-sidebar/create-channel and room-view-title/header never matched on iPhone. Route these through the existing tap-header-item/assert-room-title helpers, or inline android/iOS branches matching iOS by text. --- .maestro/tests/assorted/admin-panel.yaml | 7 +- .maestro/tests/assorted/broadcast.yaml | 41 +-- .maestro/tests/assorted/change-avatar.yaml | 11 +- .maestro/tests/assorted/channel-deeplink.yaml | 32 +- .maestro/tests/assorted/deeplink.yaml | 78 ++--- .maestro/tests/assorted/i18n.yaml | 14 +- .../tests/assorted/in-app-notification.yaml | 16 +- .../tests/assorted/join-from-directory.yaml | 88 +++--- .maestro/tests/assorted/join-public-room.yaml | 2 +- .../tests/assorted/utils/check-server.yaml | 11 +- .../tests/e2ee/flows/create-e2ee-room.yaml | 19 +- .../tests/e2ee/flows/reset-keys-and-send.yaml | 39 ++- .maestro/tests/onboarding/roomslist.yaml | 20 +- .maestro/tests/room/create-dm-group.yaml | 19 +- .maestro/tests/room/create-room.yaml | 83 +++-- .maestro/tests/room/discussion.yaml | 41 +-- .maestro/tests/room/ignoreuser.yaml | 16 +- .maestro/tests/room/jump-to-message.yaml | 56 ++-- .maestro/tests/room/room-actions.yaml | 8 +- .maestro/tests/room/room.yaml | 8 +- .maestro/tests/room/threads.yaml | 283 +++++++++++------- .../room/utils/expect-thread-messages.yaml | 8 +- .maestro/tests/teams/team.yaml | 44 ++- 23 files changed, 545 insertions(+), 399 deletions(-) diff --git a/.maestro/tests/assorted/admin-panel.yaml b/.maestro/tests/assorted/admin-panel.yaml index e783063497f..7380dfca90a 100644 --- a/.maestro/tests/assorted/admin-panel.yaml +++ b/.maestro/tests/assorted/admin-panel.yaml @@ -19,8 +19,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' diff --git a/.maestro/tests/assorted/broadcast.yaml b/.maestro/tests/assorted/broadcast.yaml index 4529ed56c40..4626b35b9c6 100644 --- a/.maestro/tests/assorted/broadcast.yaml +++ b/.maestro/tests/assorted/broadcast.yaml @@ -19,12 +19,11 @@ tags: PASSWORD: ${output.user.password} # should create broadcast room -- extendedWaitUntil: - visible: - id: 'rooms-list-view-create-channel' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' - extendedWaitUntil: visible: id: 'new-message-view' @@ -76,9 +75,10 @@ tags: timeout: 60000 - tapOn: id: 'create-channel-submit' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room}' +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room} - runFlow: '../../helpers/open-room-actions.yaml' - tapOn: id: 'room-actions-info' @@ -112,10 +112,10 @@ tags: file: '../../helpers/search-and-navigate-room.yaml' env: ROOM: ${output.room} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room} # should not have message composer - assertNotVisible: @@ -134,10 +134,10 @@ tags: id: 'message-broadcast-reply' - tapOn: id: 'message-broadcast-reply' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.user.username}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.user.username} # should reply broadcasted message - assertVisible: @@ -160,6 +160,7 @@ tags: timeout: 60000 - tapOn: text: 'Jump to message' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room}' +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room} diff --git a/.maestro/tests/assorted/change-avatar.yaml b/.maestro/tests/assorted/change-avatar.yaml index e317392b0e8..087c693591e 100644 --- a/.maestro/tests/assorted/change-avatar.yaml +++ b/.maestro/tests/assorted/change-avatar.yaml @@ -16,12 +16,11 @@ tags: env: USERNAME: ${output.user.username} PASSWORD: ${output.user.password} -- extendedWaitUntil: - visible: - id: 'rooms-list-view-sidebar' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' diff --git a/.maestro/tests/assorted/channel-deeplink.yaml b/.maestro/tests/assorted/channel-deeplink.yaml index 6edcc747621..5f1d4ea0110 100644 --- a/.maestro/tests/assorted/channel-deeplink.yaml +++ b/.maestro/tests/assorted/channel-deeplink.yaml @@ -24,10 +24,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('room', output.data.server, 'path=group/', output.group.name)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.group.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.group.name} # open group using id - killApp @@ -35,10 +35,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('room', output.data.server, 'path=group/', output.group._id)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.group.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.group.name} # open channel using name - killApp @@ -46,10 +46,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('room', output.data.server, 'path=channel/', output.channel.name)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.channel.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.channel.name} # open channel using id - killApp @@ -57,7 +57,7 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('room', output.data.server, 'path=channel/', output.channel._id)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.channel.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.channel.name} diff --git a/.maestro/tests/assorted/deeplink.yaml b/.maestro/tests/assorted/deeplink.yaml index 5a4b7d0f67d..eef191bd36e 100644 --- a/.maestro/tests/assorted/deeplink.yaml +++ b/.maestro/tests/assorted/deeplink.yaml @@ -37,10 +37,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('auth', output.data.server, 'userId=', output.login.userId, '&token=', output.login.authToken, '&path=group/', output.room.name)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -70,10 +70,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('auth', output.data.server, 'userId=', output.login.userId, '&token=', output.login.authToken, '&path=group/', output.room.name)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -94,10 +94,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('room', output.data.server, 'path=group/', output.room.name)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} # should navigate to the thread using path - stopApp: ${APP_ID} @@ -105,10 +105,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('room', output.data.server, 'path=group/', output.room.name, '/thread/', output.result.message._id)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.threadMessage}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.threadMessage} # should navigate to the room using rid - stopApp: ${APP_ID} @@ -116,10 +116,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('room', output.data.server, 'rid=', output.room._id)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -132,10 +132,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('room', output.data.server, 'path=group/', output.room.name)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} # should simulate a tap on a push notification and navigate to the room # we are skipping this because we can't send push on iOS simulator and for android we have similar test @@ -162,10 +162,10 @@ tags: file: '../../helpers/open-deeplink.yaml' env: link: ${output.utils.getDeepLink('room', output.data.server, 'path=group/', output.room.name)} -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} # should add a not existing server and fallback to the previous one - stopApp: ${APP_ID} @@ -333,8 +333,18 @@ tags: visible: id: 'new-server-view' timeout: 60000 -- assertNotVisible: - id: 'room-view-title-${output.room.name}' +- runFlow: + when: + platform: android + commands: + - assertNotVisible: + id: 'room-view-title-${output.room.name}' +- runFlow: + when: + platform: ios + commands: + - assertNotVisible: + text: '${output.room.name}' # should show a security confirmation prompt on deep link login and sign in when confirmed - runFlow: @@ -366,10 +376,10 @@ tags: commands: - tapOn: text: 'Login' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: diff --git a/.maestro/tests/assorted/i18n.yaml b/.maestro/tests/assorted/i18n.yaml index b4be402f7b2..928eed1f68a 100644 --- a/.maestro/tests/assorted/i18n.yaml +++ b/.maestro/tests/assorted/i18n.yaml @@ -25,8 +25,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' @@ -54,8 +57,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' diff --git a/.maestro/tests/assorted/in-app-notification.yaml b/.maestro/tests/assorted/in-app-notification.yaml index 7b60d11506f..aa0a6cfe914 100644 --- a/.maestro/tests/assorted/in-app-notification.yaml +++ b/.maestro/tests/assorted/in-app-notification.yaml @@ -28,10 +28,10 @@ tags: timeout: 60000 - tapOn: id: 'in-app-notification-Message in DM' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.sender.username}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.sender.username} - runFlow: '../../helpers/go-back.yaml' # receive in another room @@ -46,10 +46,10 @@ tags: timeout: 60000 - tapOn: id: 'in-app-notification-Another msg' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.sender.username}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.sender.username} # should tap back and go back to RoomsListView - runFlow: '../../helpers/go-back.yaml' diff --git a/.maestro/tests/assorted/join-from-directory.yaml b/.maestro/tests/assorted/join-from-directory.yaml index df89a0a7dc9..d18f1f3d063 100644 --- a/.maestro/tests/assorted/join-from-directory.yaml +++ b/.maestro/tests/assorted/join-from-directory.yaml @@ -22,14 +22,13 @@ tags: PASSWORD: ${output.user.password} # should tap directory -- extendedWaitUntil: - visible: - id: 'rooms-list-view-directory' - timeout: 60000 - waitForAnimationToEnd: timeout: 10000 -- tapOn: - id: 'rooms-list-view-directory' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-directory' + IOS_LABEL: 'Directory' - extendedWaitUntil: visible: id: 'directory-view' @@ -53,36 +52,46 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-join-from-directory' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'join-from-directory' # should navigate to thread messages view and load messages -- extendedWaitUntil: - visible: - id: 'room-view-header-threads' - timeout: 60000 -- tapOn: - id: 'room-view-header-threads' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'room-view-header-threads' + IOS_LABEL: 'Threads' - extendedWaitUntil: visible: id: 'thread-messages-view-${output.thread}' timeout: 60000 - runFlow: '../../helpers/go-back.yaml' -- extendedWaitUntil: - visible: - id: 'room-view-header-threads' - timeout: 60000 +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + visible: + id: 'room-view-header-threads' + timeout: 60000 +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: + text: 'Threads' + timeout: 60000 # should search user and navigate - runFlow: '../../helpers/go-back.yaml' -- extendedWaitUntil: - visible: - id: 'rooms-list-view-directory' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-directory' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-directory' + IOS_LABEL: 'Directory' - extendedWaitUntil: visible: id: 'directory-view' @@ -117,19 +126,18 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.otherUser.username}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.otherUser.username} # should search team and navigate - runFlow: '../../helpers/go-back.yaml' -- extendedWaitUntil: - visible: - id: 'rooms-list-view-directory' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-directory' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-directory' + IOS_LABEL: 'Directory' - extendedWaitUntil: visible: id: 'directory-view' @@ -159,7 +167,7 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.team}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.team} diff --git a/.maestro/tests/assorted/join-public-room.yaml b/.maestro/tests/assorted/join-public-room.yaml index 845ca5f9a55..c6210a77381 100644 --- a/.maestro/tests/assorted/join-public-room.yaml +++ b/.maestro/tests/assorted/join-public-room.yaml @@ -36,7 +36,7 @@ tags: platform: iOS commands: - assertVisible: - id: room-view-header-more + text: 'More' # should have join - assertVisible: diff --git a/.maestro/tests/assorted/utils/check-server.yaml b/.maestro/tests/assorted/utils/check-server.yaml index 7e180616e21..486c3e14d43 100644 --- a/.maestro/tests/assorted/utils/check-server.yaml +++ b/.maestro/tests/assorted/utils/check-server.yaml @@ -4,12 +4,11 @@ tags: - 'util' --- -- extendedWaitUntil: - visible: - id: 'rooms-list-view-sidebar' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-sidebar' +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-sidebar' + IOS_LABEL: 'Menu' - extendedWaitUntil: visible: id: 'sidebar-view' diff --git a/.maestro/tests/e2ee/flows/create-e2ee-room.yaml b/.maestro/tests/e2ee/flows/create-e2ee-room.yaml index 02219c31d1d..3d02dca47d7 100644 --- a/.maestro/tests/e2ee/flows/create-e2ee-room.yaml +++ b/.maestro/tests/e2ee/flows/create-e2ee-room.yaml @@ -4,12 +4,11 @@ tags: - 'flow' --- - launchApp -- extendedWaitUntil: - visible: - id: 'rooms-list-view-create-channel' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' - extendedWaitUntil: visible: id: 'new-message-view' @@ -53,7 +52,7 @@ tags: id: 'create-channel-encrypted' - tapOn: id: 'create-channel-submit' -- extendedWaitUntil: - visible: - id: 'room-view-title-${ROOM}' - timeout: 60000 +- runFlow: + file: '../../../helpers/assert-room-title.yaml' + env: + ROOM: ${ROOM} diff --git a/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml b/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml index b11b89a3685..9a2ab1da641 100644 --- a/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml +++ b/.maestro/tests/e2ee/flows/reset-keys-and-send.yaml @@ -39,20 +39,33 @@ env: visible: text: '.*The encryption keys for the room need to be updated, another room member needs to be online for this to happen.*' timeout: 60000 +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + visible: + id: 'room-view-header-encryption' + timeout: 60000 + - tapOn: + id: 'room-view-header-encryption' - runFlow: when: platform: iOS - notVisible: - id: 'room-view-header-encryption' commands: + - runFlow: + when: + notVisible: + text: 'Encrypted' + commands: + - tapOn: + text: 'More' + - extendedWaitUntil: + visible: + text: 'Encrypted' + timeout: 60000 - tapOn: - id: 'room-view-header-more' -- extendedWaitUntil: - visible: - id: 'room-view-header-encryption' - timeout: 60000 -- tapOn: - id: 'room-view-header-encryption' + text: 'Encrypted' - extendedWaitUntil: visible: id: 'e2ee-toggle-room-view' @@ -77,10 +90,10 @@ env: file: '../../../helpers/navigate-to-room.yaml' env: ROOM: ${ROOM} -- extendedWaitUntil: - visible: - id: 'room-view-title-${ROOM}' - timeout: 60000 +- runFlow: + file: '../../../helpers/assert-room-title.yaml' + env: + ROOM: ${ROOM} - runFlow: file: '../../../helpers/send-message.yaml' env: diff --git a/.maestro/tests/onboarding/roomslist.yaml b/.maestro/tests/onboarding/roomslist.yaml index ca236a66b74..2421a01478b 100644 --- a/.maestro/tests/onboarding/roomslist.yaml +++ b/.maestro/tests/onboarding/roomslist.yaml @@ -19,10 +19,22 @@ tags: id: 'rooms-list-view' - assertVisible: id: 'rooms-list-view-item-general' -- assertVisible: - id: 'rooms-list-view-create-channel' -- assertVisible: - id: 'rooms-list-view-sidebar' +- runFlow: + when: + platform: android + commands: + - assertVisible: + id: 'rooms-list-view-create-channel' + - assertVisible: + id: 'rooms-list-view-sidebar' +- runFlow: + when: + platform: iOS + commands: + - assertVisible: + text: 'Create new channel, team, direct message or discussion' + - assertVisible: + text: 'Menu' - runFlow: file: ../../helpers/search-room.yaml env: diff --git a/.maestro/tests/room/create-dm-group.yaml b/.maestro/tests/room/create-dm-group.yaml index 12750ea7d0d..e8e5d1deeef 100644 --- a/.maestro/tests/room/create-dm-group.yaml +++ b/.maestro/tests/room/create-dm-group.yaml @@ -18,12 +18,11 @@ tags: env: USERNAME: ${output.user.username} PASSWORD: ${output.user.password} -- extendedWaitUntil: - visible: - id: 'rooms-list-view-create-channel' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' # should have new message screen - extendedWaitUntil: @@ -77,7 +76,7 @@ tags: id: 'selected-users-view-submit' # check Group DM exist -- extendedWaitUntil: - visible: - id: 'room-view-title-rocket.cat, ${output.otherUser.username}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'rocket.cat, ${output.otherUser.username}' diff --git a/.maestro/tests/room/create-room.yaml b/.maestro/tests/room/create-room.yaml index d7dc2f2df39..396b8dcbbcc 100644 --- a/.maestro/tests/room/create-room.yaml +++ b/.maestro/tests/room/create-room.yaml @@ -15,12 +15,11 @@ tags: USERNAME: ${output.user.username} PASSWORD: ${output.user.password} -- extendedWaitUntil: - visible: - id: 'rooms-list-view-create-channel' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' # should have new message screen - extendedWaitUntil: @@ -45,8 +44,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' - extendedWaitUntil: visible: id: 'new-message-view' @@ -70,10 +72,10 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-rocket.cat' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'rocket.cat' - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -81,12 +83,11 @@ tags: timeout: 60000 # should navigate to select users -- extendedWaitUntil: - visible: - id: 'rooms-list-view-create-channel' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' - extendedWaitUntil: visible: id: 'new-message-view' @@ -203,10 +204,10 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: ${'room-view-title-' + output.publicRoomName} - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.publicRoomName} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -223,12 +224,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'rooms-list-view-create-channel' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' - extendedWaitUntil: visible: id: 'new-message-view' @@ -269,10 +269,10 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: ${'room-view-title-' + output.privateRoomName} - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.privateRoomName} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -289,12 +289,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'rooms-list-view-create-channel' - timeout: 60000 -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' - extendedWaitUntil: visible: id: 'new-message-view' @@ -333,10 +332,10 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: ${'room-view-title-' + output.emptyRoomName} - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.emptyRoomName} - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: diff --git a/.maestro/tests/room/discussion.yaml b/.maestro/tests/room/discussion.yaml index b784f566d38..66695b626de 100644 --- a/.maestro/tests/room/discussion.yaml +++ b/.maestro/tests/room/discussion.yaml @@ -25,10 +25,11 @@ tags: visible: id: 'rooms-list-view' timeout: 60000 -- assertVisible: - id: 'rooms-list-view-create-channel' -- tapOn: - id: 'rooms-list-view-create-channel' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'rooms-list-view-create-channel' + IOS_LABEL: 'Create new channel, team, direct message or discussion' - extendedWaitUntil: visible: id: 'new-message-view' @@ -89,10 +90,10 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.discussionFromNewMessage}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.discussionFromNewMessage} - runFlow: '../../helpers/hide-keyboard.yaml' - runFlow: when: @@ -141,10 +142,10 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.discussionFromMessageComposer}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.discussionFromMessageComposer} # Create Discussion from action sheet - extendedWaitUntil: @@ -182,10 +183,10 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-message' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'message' # Check RoomActionsView render - runFlow: '../../helpers/open-room-actions.yaml' @@ -290,7 +291,7 @@ tags: timeout: 60000 - tapOn: id: 'discussions-view-${output.discussionFromNewMessage}' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.discussionFromNewMessage}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.discussionFromNewMessage} diff --git a/.maestro/tests/room/ignoreuser.yaml b/.maestro/tests/room/ignoreuser.yaml index f2ea05b2a37..90fe1d4b380 100644 --- a/.maestro/tests/room/ignoreuser.yaml +++ b/.maestro/tests/room/ignoreuser.yaml @@ -224,10 +224,10 @@ tags: timeout: 60000 - tapOn: id: 'report-user-view-submit' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.otherUser.username}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.otherUser.username} # should go to user info view from a channel - runFlow: '../../helpers/go-back.yaml' @@ -281,7 +281,7 @@ tags: timeout: 60000 - tapOn: id: 'report-user-view-submit' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} diff --git a/.maestro/tests/room/jump-to-message.yaml b/.maestro/tests/room/jump-to-message.yaml index 2e206e14bcc..4c966f24015 100644 --- a/.maestro/tests/room/jump-to-message.yaml +++ b/.maestro/tests/room/jump-to-message.yaml @@ -90,13 +90,19 @@ tags: platform: iOS commands: - tapOn: - id: 'room-view-header-more' -- extendedWaitUntil: - visible: - id: 'room-view-search' - timeout: 60000 -- tapOn: - id: 'room-view-search' + text: 'More' + - tapOn: + text: 'Search messages' +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + visible: + id: 'room-view-search' + timeout: 60000 + - tapOn: + id: 'room-view-search' - extendedWaitUntil: visible: id: 'search-messages-view' @@ -216,10 +222,10 @@ tags: - runFlow: '../../helpers/go-back.yaml' # should tap on thread message from main room -- extendedWaitUntil: - visible: - id: 'room-view-title-jumping-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'jumping-thread' - extendedWaitUntil: visible: text: '.*thread message sent to main room.*' @@ -234,10 +240,10 @@ tags: - runFlow: '../../helpers/go-back.yaml' # should tap on quote -- extendedWaitUntil: - visible: - id: 'room-view-title-jumping-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'jumping-thread' - extendedWaitUntil: visible: id: '.*reply-.*-quoted.*' @@ -258,18 +264,24 @@ tags: - runFlow: '../../helpers/go-back.yaml' # should jump from search message -- extendedWaitUntil: - visible: - id: 'room-view-title-jumping-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'jumping-thread' - runFlow: when: platform: iOS commands: - tapOn: - id: 'room-view-header-more' -- tapOn: - id: 'room-view-search' + text: 'More' + - tapOn: + text: 'Search messages' +- runFlow: + when: + platform: android + commands: + - tapOn: + id: 'room-view-search' - extendedWaitUntil: visible: id: 'search-messages-view' diff --git a/.maestro/tests/room/room-actions.yaml b/.maestro/tests/room/room-actions.yaml index 2b25bff8672..fdff575b0e3 100644 --- a/.maestro/tests/room/room-actions.yaml +++ b/.maestro/tests/room/room-actions.yaml @@ -713,10 +713,10 @@ tags: visible: id: 'room-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.otherUser.username}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.otherUser.username} - tapOn: Back - extendedWaitUntil: visible: diff --git a/.maestro/tests/room/room.yaml b/.maestro/tests/room/room.yaml index a719d7f004c..092470c6269 100644 --- a/.maestro/tests/room/room.yaml +++ b/.maestro/tests/room/room.yaml @@ -483,10 +483,10 @@ tags: timeout: 60000 - tapOn: id: message-actions-reply-in-dm -- extendedWaitUntil: - visible: - id: room-view-title-${output.replyUser.username} - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.replyUser.username} - extendedWaitUntil: visible: id: message-composer-input diff --git a/.maestro/tests/room/threads.yaml b/.maestro/tests/room/threads.yaml index 408c6d5fffb..181e690beda 100644 --- a/.maestro/tests/room/threads.yaml +++ b/.maestro/tests/room/threads.yaml @@ -23,10 +23,10 @@ tags: ROOM: ${output.room.name} # should have room screen -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} # should have actions button - runFlow: @@ -43,14 +43,26 @@ tags: commands: - extendedWaitUntil: visible: - id: 'room-view-header-more' + text: 'More' timeout: 60000 # should have threads button -- extendedWaitUntil: - visible: - id: 'room-view-header-threads' - timeout: 60000 +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + visible: + id: 'room-view-header-threads' + timeout: 60000 +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: + text: 'Threads' + timeout: 60000 # should create thread - runFlow: @@ -73,10 +85,10 @@ tags: timeout: 60000 - tapOn: text: 'Reply in thread' -- extendedWaitUntil: - visible: - id: 'room-view-title-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread' - extendedWaitUntil: visible: id: 'message-composer-input-thread' @@ -92,38 +104,48 @@ tags: timeout: 60000 - runFlow: '../../helpers/go-back.yaml' # should navigate to thread from button -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} - extendedWaitUntil: visible: id: 'message-thread-button-thread' timeout: 60000 - tapOn: id: 'message-thread-button-thread' -- extendedWaitUntil: - visible: - id: 'room-view-title-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread' # should toggle follow thread -- extendedWaitUntil: - visible: - id: 'room-view-header-unfollow' - timeout: 60000 -- tapOn: - id: 'room-view-header-unfollow' -- extendedWaitUntil: - visible: - id: 'room-view-header-follow' - timeout: 60000 -- tapOn: - id: 'room-view-header-follow' -- extendedWaitUntil: - visible: - id: 'room-view-header-unfollow' - timeout: 60000 +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'room-view-header-unfollow' + IOS_LABEL: 'Unfollow thread' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'room-view-header-follow' + IOS_LABEL: 'Follow thread' +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + visible: + id: 'room-view-header-unfollow' + timeout: 60000 +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: + text: 'Unfollow thread' + timeout: 60000 # should send message in thread only - runFlow: @@ -132,14 +154,26 @@ tags: message: threadonly isThread: 'true' - runFlow: '../../helpers/go-back.yaml' -- extendedWaitUntil: - notVisible: - id: 'room-view-title-thread' - timeout: 5000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 5000 +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + notVisible: + id: 'room-view-title-thread' + timeout: 5000 +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + notVisible: + text: 'thread' + timeout: 5000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} - extendedWaitUntil: notVisible: id: 'message-content-threadonly' @@ -152,10 +186,10 @@ tags: timeout: 60000 - tapOn: id: 'message-thread-button-thread' -- extendedWaitUntil: - visible: - id: 'room-view-title-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread' - tapOn: id: 'message-composer-input-thread' - inputText: 'sendToChannel' @@ -164,14 +198,26 @@ tags: - tapOn: id: message-composer-send - runFlow: '../../helpers/go-back.yaml' -- extendedWaitUntil: - notVisible: - id: 'room-view-title-thread' - timeout: 5000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 5000 +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + notVisible: + id: 'room-view-title-thread' + timeout: 5000 +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + notVisible: + text: 'thread' + timeout: 5000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} - extendedWaitUntil: visible: text: '.*sendToChannel.*' @@ -196,33 +242,44 @@ tags: - tapOn: id: 'message-composer-send' - runFlow: '../../helpers/go-back.yaml' -- extendedWaitUntil: - notVisible: - id: 'room-view-title-thread' - timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + notVisible: + id: 'room-view-title-thread' + timeout: 60000 +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + notVisible: + text: 'thread' + timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} - extendedWaitUntil: visible: id: 'message-content-navthreadname' timeout: 60000 - tapOn: id: 'message-content-navthreadname' -- extendedWaitUntil: - visible: - id: 'room-view-title-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread' - runFlow: '../../helpers/go-back.yaml' # should navigate to thread from threads view -- extendedWaitUntil: - visible: - id: 'room-view-header-threads' - timeout: 60000 -- tapOn: - id: 'room-view-header-threads' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'room-view-header-threads' + IOS_LABEL: 'Threads' - extendedWaitUntil: visible: id: 'thread-messages-view' @@ -233,10 +290,10 @@ tags: timeout: 60000 - tapOn: id: 'thread-messages-view-thread' -- extendedWaitUntil: - visible: - id: 'room-view-title-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread' - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: @@ -250,10 +307,10 @@ tags: timeout: 60000 - tapOn: id: 'message-thread-button-thread' -- extendedWaitUntil: - visible: - id: 'room-view-title-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread' - tapOn: id: 'message-composer-input-thread' - inputText: 'draftthread' @@ -265,10 +322,10 @@ tags: timeout: 60000 - tapOn: id: 'message-thread-button-thread' -- extendedWaitUntil: - visible: - id: 'room-view-title-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread' - extendedWaitUntil: visible: text: '.*draftthread.*' @@ -284,10 +341,10 @@ tags: timeout: 60000 - tapOn: id: 'message-thread-button-thread' -- extendedWaitUntil: - visible: - id: 'room-view-title-thread' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread' - extendedWaitUntil: notVisible: text: '.*draftthread.*' @@ -297,14 +354,26 @@ tags: text: '.*Add thread reply.*' timeout: 60000 - runFlow: '../../helpers/go-back.yaml' -- extendedWaitUntil: - notVisible: - id: 'room-view-title-thread' - timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + notVisible: + id: 'room-view-title-thread' + timeout: 60000 +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + notVisible: + text: 'thread' + timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} # should create thread delete the message and the thread show the correct number of messages - runFlow: @@ -327,10 +396,10 @@ tags: timeout: 60000 - tapOn: text: 'Reply in thread' -- extendedWaitUntil: - visible: - id: 'room-view-title-thread-message-count' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread-message-count' - extendedWaitUntil: visible: id: 'message-composer-input-thread' @@ -378,10 +447,10 @@ tags: timeout: 60000 - tapOn: 'DELETE' - runFlow: '../../helpers/go-back.yaml' -- extendedWaitUntil: - visible: - id: 'room-view-title-${output.room.name}' - timeout: 60000 +- runFlow: + file: '../../helpers/assert-room-title.yaml' + env: + ROOM: ${output.room.name} - extendedWaitUntil: visible: id: 'thread-count-0' diff --git a/.maestro/tests/room/utils/expect-thread-messages.yaml b/.maestro/tests/room/utils/expect-thread-messages.yaml index 5f3fbc3ae44..ec204c19e36 100644 --- a/.maestro/tests/room/utils/expect-thread-messages.yaml +++ b/.maestro/tests/room/utils/expect-thread-messages.yaml @@ -4,10 +4,10 @@ tags: - 'util' --- -- extendedWaitUntil: - visible: - id: 'room-view-title-thread 1' - timeout: 60000 +- runFlow: + file: '../../../helpers/assert-room-title.yaml' + env: + ROOM: 'thread 1' - extendedWaitUntil: visible: id: '.*message-content-${message}.*' diff --git a/.maestro/tests/teams/team.yaml b/.maestro/tests/teams/team.yaml index 691150451cd..e5251a7caff 100644 --- a/.maestro/tests/teams/team.yaml +++ b/.maestro/tests/teams/team.yaml @@ -33,18 +33,26 @@ tags: id: room-header - assertVisible: id: room-view-search -- assertVisible: - id: room-view-header-call -- assertVisible: - id: room-view-header-threads + - assertVisible: + id: room-view-header-call + - assertVisible: + id: room-view-header-threads - runFlow: when: platform: iOS commands: + - assertVisible: + text: '.*Call.*' + - assertVisible: + text: 'Threads' - tapOn: - id: 'room-view-header-more' + text: 'More' + - extendedWaitUntil: + visible: + id: 'action-sheet' + timeout: 60000 - assertVisible: - id: 'room-view-search' + text: 'Search messages' - tapOn: id: 'action-sheet-handle' @@ -158,7 +166,7 @@ tags: platform: iOS commands: - assertVisible: - id: room-view-header-more + text: 'More' - runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: room-actions-teams @@ -186,24 +194,32 @@ tags: file: '../../helpers/assert-room-title.yaml' env: ROOM: ${output.privateRoomName} -- assertVisible: - id: room-view-header-call -- assertVisible: - id: room-view-header-threads - runFlow: when: platform: android commands: + - assertVisible: + id: room-view-header-call + - assertVisible: + id: room-view-header-threads - assertVisible: id: room-view-search - runFlow: when: platform: iOS commands: + - assertVisible: + text: '.*Call.*' + - assertVisible: + text: 'Threads' - tapOn: - id: 'room-view-header-more' + text: 'More' + - extendedWaitUntil: + visible: + id: 'action-sheet' + timeout: 60000 - assertVisible: - id: 'room-view-search' + text: 'Search messages' - tapOn: id: 'action-sheet-handle' - runFlow: '../../helpers/go-back.yaml' @@ -224,7 +240,7 @@ tags: platform: iOS commands: - assertVisible: - id: room-view-header-more + text: 'More' - runFlow: '../../helpers/open-room-actions.yaml' - assertVisible: id: room-actions-teams From e8b8cfbe4c4c5b60882b3a5ddf6c275d861aa36c Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 13:54:50 -0300 Subject: [PATCH 058/120] fix(test): match iOS back button by label instead of testID go-back.yaml's iOS branch still tapped id: 'Back', but headerBackButtonDisplayMode: 'minimal' leaves no accessibilityIdentifier on the native back button, only the 'Back' accessibility label. This was the dominant cause of iOS Maestro failures in run 35878569115 shards 5 and 8 (Change Avatar, Broadcast, Setting, User Preferences, Display Perf, Status). --- .maestro/helpers/go-back.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.maestro/helpers/go-back.yaml b/.maestro/helpers/go-back.yaml index 3e01792413e..df9b991cb07 100644 --- a/.maestro/helpers/go-back.yaml +++ b/.maestro/helpers/go-back.yaml @@ -16,6 +16,9 @@ tags: when: platform: iOS commands: + # The native stack's back button carries no accessibilityIdentifier, only + # the "Back" accessibility label (headerBackButtonDisplayMode: 'minimal' + # hides the title text but keeps that label for VoiceOver/XCUITest). - tapOn: - id: 'Back' + text: 'Back' retryTapIfNoChange: true From bfe70e96b840099056118449ca8a52f255e5cc8b Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 16:02:03 -0300 Subject: [PATCH 059/120] fix(ios): render Liquid Glass on native headers MediaCallHeader rendered an empty placeholder with no active call, which covered the status bar area and hid the glass effect on native header bars. It now returns null there. The forced opaque header styling (themedHeader on iOS, RoomsListView headerStyle and scrollEdgeEffects) was removed so the system bar can render Liquid Glass. --- .../MediaCallHeader/MediaCallHeader.test.tsx | 19 +++++++++++++++++++ .../MediaCallHeader/MediaCallHeader.tsx | 4 ++++ app/lib/methods/helpers/navigation/index.ts | 8 +------- .../hooks/__tests__/useHeader.test.tsx | 2 +- app/views/RoomsListView/hooks/useHeader.tsx | 6 ++---- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/app/containers/MediaCallHeader/MediaCallHeader.test.tsx b/app/containers/MediaCallHeader/MediaCallHeader.test.tsx index c9695f474ef..b2598a81201 100644 --- a/app/containers/MediaCallHeader/MediaCallHeader.test.tsx +++ b/app/containers/MediaCallHeader/MediaCallHeader.test.tsx @@ -11,6 +11,12 @@ import { generateSnapshots } from '~/.rnstorybook/generateSnapshots'; const mockNavigateToCallRoom = jest.mocked(navigateToCallRoom); +let mockHasNativeHeaderBar = false; +jest.mock('~/lib/methods/helpers/deviceInfo', () => { + const actual = jest.requireActual('~/lib/methods/helpers/deviceInfo'); + return Object.defineProperty({ ...actual }, 'hasNativeHeaderBar', { get: () => mockHasNativeHeaderBar }); +}); + jest.mock('~/lib/services/voip/navigateToCallRoom', () => ({ navigateToCallRoom: jest.fn().mockResolvedValue(undefined) })); @@ -94,6 +100,19 @@ describe('MediaCallHeader', () => { expect(queryByTestId('media-call-header-end')).toBeNull(); }); + it('should render nothing on the native header bar when there is no call', () => { + mockHasNativeHeaderBar = true; + useCallStore.setState({ call: null }); + const { toJSON } = render( + + + + ); + + expect(toJSON()).toBeNull(); + mockHasNativeHeaderBar = false; + }); + it('should render empty placeholder when native accepted but call not bound yet (before answerCall completes)', () => { useCallStore.getState().setNativeAcceptedCallId('e3246c4d-d23a-412f-8a8b-37ec9f29ef1a'); const { getByTestId, queryByTestId } = render( diff --git a/app/containers/MediaCallHeader/MediaCallHeader.tsx b/app/containers/MediaCallHeader/MediaCallHeader.tsx index 43839f75159..d24f9f6b664 100644 --- a/app/containers/MediaCallHeader/MediaCallHeader.tsx +++ b/app/containers/MediaCallHeader/MediaCallHeader.tsx @@ -9,6 +9,7 @@ import EndCall from './components/EndCall'; import { useCallStore, useControlsVisible } from '~/lib/services/voip/useCallStore'; import { Content } from './components/Content'; import { CONTROLS_ANIMATION_DURATION } from '~/views/CallView/styles'; +import { hasNativeHeaderBar } from '~/lib/methods/helpers/deviceInfo'; const styles = StyleSheet.create({ header: { @@ -49,6 +50,9 @@ const MediaCallHeader = () => { }; if (!call) { + if (hasNativeHeaderBar) { + return null; + } return ; } diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 0b5b28fb66d..60c87f6349c 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -58,13 +58,7 @@ export const drawerStyle = { export const themedHeader = (theme: TSupportedThemes): NativeStackNavigationOptions => isIOS - ? { - headerStyle: { - backgroundColor: themes[theme].surfaceNeutral - }, - headerTransparent: false, - headerTitleStyle: { color: themes[theme].fontTitlesLabels } - } + ? {} : { headerStyle: { backgroundColor: themes[theme].surfaceNeutral diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 355f4d2715a..0f9bd57c062 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -104,7 +104,7 @@ describe('RoomsListView useHeader', () => { expect(typeof options.headerTitle).toBe('string'); expect(options.headerTitle).toBe('Rocket.Chat'); expect(options.headerSubtitle).toBe('open.rocket.chat'); - expect(options.headerLargeTitle).toBe(false); + expect(options.headerLargeTitle).toBe(true); }); it.each([ diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 43d14d9828e..f4427e5a927 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -192,12 +192,10 @@ export const useHeader = () => { ]); navigation.setOptions({ - headerLargeTitle: false, + headerLargeTitle: true, + headerTransparent: true, headerTitle: serverName, headerSubtitle: nativeHeaderSubtitle, - headerStyle: { backgroundColor: colors.surfaceNeutral }, - headerTransparent: false, - scrollEdgeEffects: { top: 'hidden' }, headerSearchBarOptions: { ref: searchBarRef, placement: 'automatic', From e991cf000ee50272e49fcd6fba0bae5250722988 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 16:23:45 -0300 Subject: [PATCH 060/120] feat(ios): use app icons on native header buttons --- .../navigation/__tests__/headerIcon.test.ts | 21 +++++++++++ .../methods/helpers/navigation/headerIcon.ts | 37 +++++++++++++++++++ app/lib/methods/helpers/navigation/index.ts | 5 ++- app/sagas/init.js | 10 +++++ .../RoomView/hooks/useRoomHeaderRightItems.ts | 15 ++++---- .../hooks/__tests__/useHeader.test.tsx | 8 +++- app/views/RoomsListView/hooks/useHeader.tsx | 20 +++++----- 7 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts create mode 100644 app/lib/methods/helpers/navigation/headerIcon.ts diff --git a/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts b/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts new file mode 100644 index 00000000000..1ee513ba061 --- /dev/null +++ b/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts @@ -0,0 +1,21 @@ +import { headerIcon, preloadHeaderIcons } from '../headerIcon'; + +const mockGetImageSource = jest.fn(); +jest.mock('~/containers/CustomIcon', () => ({ + IconSet: { getImageSource: (...args: unknown[]) => mockGetImageSource(...args) } +})); + +describe('headerIcon', () => { + it('returns no icon before the header icons are preloaded', () => { + expect(headerIcon('kebab')).toBeUndefined(); + }); + + it('returns the rendered font glyph as an image once preloaded', async () => { + mockGetImageSource.mockImplementation(async (name: string) => ({ uri: `file://${name}.png`, scale: 3 })); + + await preloadHeaderIcons(); + + expect(headerIcon('kebab')).toEqual({ type: 'image', source: { uri: 'file://kebab.png', scale: 3 } }); + expect(mockGetImageSource).toHaveBeenCalledWith('kebab', 24, 'black'); + }); +}); diff --git a/app/lib/methods/helpers/navigation/headerIcon.ts b/app/lib/methods/helpers/navigation/headerIcon.ts new file mode 100644 index 00000000000..b88586c19cc --- /dev/null +++ b/app/lib/methods/helpers/navigation/headerIcon.ts @@ -0,0 +1,37 @@ +import type { ImageSourcePropType } from 'react-native'; + +import { IconSet, type TIconsName } from '~/containers/CustomIcon'; + +const HEADER_ICON_SIZE = 24; + +const HEADER_ICONS: TIconsName[] = [ + 'close', + 'create', + 'directory', + 'encrypted', + 'hamburguer', + 'kebab', + 'notification', + 'notification-disabled', + 'phone', + 'threads', + 'workspaces' +]; + +const headerIconSources = new Map(); + +export const preloadHeaderIcons = async () => { + await Promise.all( + HEADER_ICONS.map(async name => { + const source = await IconSet.getImageSource(name, HEADER_ICON_SIZE, 'black'); + if (source) { + headerIconSources.set(name, source); + } + }) + ); +}; + +export const headerIcon = (name: TIconsName) => { + const source = headerIconSources.get(name); + return source ? { type: 'image' as const, source } : undefined; +}; diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 60c87f6349c..16f6c7f0776 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -9,6 +9,7 @@ import Header from '~/containers/Header'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import I18n from '~/i18n'; import { isIOS } from '~/lib/methods/helpers'; +import { headerIcon } from './headerIcon'; export const defaultHeader: NativeStackNavigationOptions = isIOS ? { @@ -26,7 +27,7 @@ export const outsideHeaderRightLegal = (navigation: any, testID: string): Native type: 'button', label: I18n.t('More'), accessibilityLabel: I18n.t('More'), - icon: { type: 'sfSymbol', name: 'ellipsis' }, + icon: headerIcon('kebab'), onPress: () => navigation?.navigate('LegalView') } ] @@ -43,7 +44,7 @@ export const outsideHeaderLeftClose = (onPress: () => void, testID: string): Nat type: 'button', label: I18n.t('Close'), accessibilityLabel: I18n.t('Close'), - icon: { type: 'sfSymbol', name: 'xmark' }, + icon: headerIcon('close'), onPress } ] diff --git a/app/sagas/init.js b/app/sagas/init.js index 54b114387ed..957d61f3ea6 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -10,6 +10,7 @@ import { selectServerRequest } from '../actions/server'; import { setAllPreferences } from '../actions/sortPreferences'; import { APP } from '../actions/actionsTypes'; import log from '../lib/methods/helpers/log'; +import { isIOS } from '../lib/methods/helpers'; import { localAuthenticate, UserCanceledError } from '../lib/methods/helpers/localAuthentication'; import { runBiometricTrustMigration } from '../lib/biometricTrustStore/migration'; import { appReady, appStart } from '../actions/app'; @@ -17,6 +18,7 @@ import { RootEnum } from '../definitions'; import { getSortPreferences } from '../lib/methods/userPreferencesMethods'; import { deepLinkingClickCallPush } from '../actions/deepLinking'; import { getServerById } from '../lib/database/services/Server'; +import { preloadHeaderIcons } from '../lib/methods/helpers/navigation/headerIcon'; const PUSH_NOTIFICATION_KEY = 'pushNotification'; @@ -73,6 +75,14 @@ const deliverPendingPushNotification = function* deliverPendingPushNotification( const restore = function* restore() { yield call(runBiometricTrustMigration); + if (isIOS) { + try { + yield call(preloadHeaderIcons); + } catch (e) { + log(e); + } + } + const restoredServer = yield* getServerToRestore(); if (restoredServer) { diff --git a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts index 65b50237312..13565eef5e9 100644 --- a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts +++ b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts @@ -11,6 +11,7 @@ import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; import { useSetting } from '~/lib/hooks/useSetting'; import { showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; +import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; import { returnLivechat } from '~/lib/services/restApi'; import { getUserSelector } from '~/selectors/login'; @@ -111,7 +112,7 @@ const useOmnichannelRightItems = (rid: string, roomStore: RoomStore, enabled: bo type: 'button', label: i18n.t('More'), accessibilityLabel: i18n.t('More'), - icon: { type: 'sfSymbol', name: 'ellipsis' }, + icon: headerIcon('kebab'), onPress: showMoreActions } ]; @@ -137,7 +138,7 @@ const useThreadRightItems = (tmid: string | undefined, enabled: boolean): Native type: 'button', label: i18n.t(isFollowingThread ? 'Unfollow_thread' : 'Follow_thread'), accessibilityLabel: i18n.t(isFollowingThread ? 'Unfollow_thread' : 'Follow_thread'), - icon: { type: 'sfSymbol', name: isFollowingThread ? 'bell' : 'bell.slash' }, + icon: headerIcon(isFollowingThread ? 'notification' : 'notification-disabled'), onPress: onToggleFollowThread } ]; @@ -224,7 +225,7 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): type: 'button', label: i18n.t('Encrypted'), accessibilityLabel: i18n.t('Encrypted'), - icon: { type: 'sfSymbol', name: 'lock' }, + icon: headerIcon('encrypted'), disabled: !canToggleEncryption, onPress: goE2EEToggleRoomView }); @@ -234,7 +235,7 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): type: 'button', label: i18n.t('Troubleshooting'), accessibilityLabel: i18n.t('Troubleshooting'), - icon: { type: 'sfSymbol', name: 'bell.slash' }, + icon: headerIcon('notification-disabled'), tintColor: issuesWithNotifications ? colors.fontDanger : undefined, disabled: hasE2EEWarning, onPress: navigateToNotificationOrPushTroubleshoot @@ -245,7 +246,7 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): type: 'button', label: callAccessibilityLabel, accessibilityLabel: callAccessibilityLabel, - icon: { type: 'sfSymbol', name: 'phone' }, + icon: headerIcon('phone'), disabled: hasE2EEWarning || isCallDisabled, onPress: onPressCall }); @@ -255,7 +256,7 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): type: 'button', label: threadsAccessibilityLabel, accessibilityLabel: threadsAccessibilityLabel, - icon: { type: 'sfSymbol', name: 'bubble.left.and.bubble.right' }, + icon: headerIcon('threads'), disabled: hasE2EEWarning, badge: tunreadBadge, onPress: goThreadsView @@ -265,7 +266,7 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): type: 'button', label: i18n.t('More'), accessibilityLabel: i18n.t('More'), - icon: { type: 'sfSymbol', name: 'ellipsis' }, + icon: headerIcon('kebab'), onPress: () => showActionSheetRef({ options: overflowOptions }) }); diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 0f9bd57c062..81e700f0d24 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -25,6 +25,10 @@ jest.mock('~/lib/methods/helpers', () => ) ); +jest.mock('~/lib/methods/helpers/navigation/headerIcon', () => ({ + headerIcon: (name: string) => ({ type: 'image', source: { uri: name } }) +})); + jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => false })); jest.mock('~/lib/hooks/useIsAccessibilityNavigationEnabled', () => ({ useIsAccessibilityNavigationEnabled: () => false })); jest.mock('~/lib/hooks/usePermissions', () => ({ usePermissions: () => [true, false, false, false, false] })); @@ -151,8 +155,8 @@ describe('RoomsListView useHeader', () => { const options = mockSetOptions.mock.calls[0][0]; const leftItems = options.unstable_headerLeftItems(); expect(leftItems).toHaveLength(2); - expect(leftItems[0].icon).toEqual({ type: 'sfSymbol', name: 'line.3.horizontal' }); - expect(leftItems[1].icon).toEqual({ type: 'sfSymbol', name: 'server.rack' }); + expect(leftItems[0].icon).toEqual({ type: 'image', source: { uri: 'hamburguer' } }); + expect(leftItems[1].icon).toEqual({ type: 'image', source: { uri: 'workspaces' } }); expect(leftItems[1].label).toBe('Rocket.Chat'); leftItems[1].onPress(); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index f4427e5a927..dad8c823798 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -5,6 +5,7 @@ import { type KeyboardFocus } from 'react-native-external-keyboard'; import { type SearchBarCommands } from 'react-native-screens'; import { showActionSheetRef } from '~/containers/ActionSheet'; +import { type TIconsName } from '~/containers/CustomIcon'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import i18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; @@ -13,6 +14,7 @@ import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { usePermissions } from '~/lib/hooks/usePermissions'; import { hasNativeHeaderBar, isTablet } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; +import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; import { getUserSelector } from '~/selectors/login'; import { useTheme } from '~/theme'; import RoomsListHeaderView from '../components/Header'; @@ -24,7 +26,7 @@ const MAX_HEADER_RIGHT_ACTIONS = 2; interface IHeaderRightAction { key: string; present: boolean; - sfSymbol: string; + icon: TIconsName; accessibilityLabel: string; tintColor?: string; disabled?: boolean; @@ -168,7 +170,7 @@ export const useHeader = () => { { key: 'create', present: canCreateRoom, - sfSymbol: 'plus', + icon: 'create', accessibilityLabel: i18n.t('Create_new_channel_team_dm_discussion'), disabled, onPress: goToNewMessage @@ -176,7 +178,7 @@ export const useHeader = () => { { key: 'push-troubleshoot', present: issuesWithNotifications, - sfSymbol: 'bell.slash', + icon: 'notification-disabled', accessibilityLabel: i18n.t('Troubleshooting'), tintColor: colors.fontDanger, onPress: navigateToPushTroubleshootView @@ -184,7 +186,7 @@ export const useHeader = () => { { key: 'directory', present: true, - sfSymbol: 'globe', + icon: 'directory', accessibilityLabel: i18n.t('Directory'), disabled, onPress: goDirectory @@ -209,7 +211,7 @@ export const useHeader = () => { type: 'button', label: i18n.t('Menu'), accessibilityLabel: i18n.t('Menu'), - icon: { type: 'sfSymbol', name: 'line.3.horizontal' }, + icon: headerIcon('hamburguer'), disabled, badge: nativeBadgeColor ? { value: '', style: { backgroundColor: nativeBadgeColor } } : undefined, onPress: isMasterDetail @@ -220,7 +222,7 @@ export const useHeader = () => { type: 'button', label: serverName, accessibilityLabel: `${serverName} ${nativeHeaderSubtitle}`, - icon: { type: 'sfSymbol', name: 'server.rack' }, + icon: headerIcon('workspaces'), onPress: () => showActionSheetRef({ children: , enableContentPanningGesture: false }) } ], @@ -229,7 +231,7 @@ export const useHeader = () => { type: 'button' as const, label: action.accessibilityLabel, accessibilityLabel: action.accessibilityLabel, - icon: { type: 'sfSymbol' as const, name: action.sfSymbol }, + icon: headerIcon(action.icon), tintColor: action.tintColor, disabled: action.disabled, onPress: action.onPress @@ -240,12 +242,12 @@ export const useHeader = () => { type: 'menu' as const, label: i18n.t('More'), accessibilityLabel: i18n.t('More'), - icon: { type: 'sfSymbol' as const, name: 'ellipsis' }, + icon: headerIcon('kebab'), menu: { items: overflow.map(action => ({ type: 'action' as const, label: action.accessibilityLabel, - icon: { type: 'sfSymbol' as const, name: action.sfSymbol }, + icon: headerIcon(action.icon), onPress: action.onPress })) } From 983c6f6500197d927738f9bd2892d0d5ec670a61 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 16:34:39 -0300 Subject: [PATCH 061/120] fix(ios): load local header icons synchronously to avoid title flash --- patches/react-native-screens+4.28.0.patch | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch index 6ca90879d00..b55bed8b217 100644 --- a/patches/react-native-screens+4.28.0.patch +++ b/patches/react-native-screens+4.28.0.patch @@ -32,6 +32,25 @@ index fa1b7731..153d712e 100644 override fun setBackTitleFontFamily( view: ScreenStackHeaderConfig?, value: String?, +diff --git a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm +index 64beb499..2c16cff4 100644 +--- a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm ++++ b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm +@@ -22,7 +22,13 @@ + imageLoadingCompletionBlock([RNSImageLoadingHelper handleRenderingModeForImage:image isTemplate:isTemplate]); + } else + #endif // !defined(NDEBUG) +- { ++ if (imageSource.request.URL.isFileURL) { ++ UIImage *image = [UIImage imageWithContentsOfFile:imageSource.request.URL.path]; ++ if (image != nil) { ++ image = [UIImage imageWithCGImage:image.CGImage scale:imageSource.scale orientation:image.imageOrientation]; ++ } ++ imageLoadingCompletionBlock([RNSImageLoadingHelper handleRenderingModeForImage:image isTemplate:isTemplate]); ++ } else { + [self loadImageFromSource:imageSource + withImageLoader:imageLoader + asTemplate:isTemplate diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h index 8a416972..9b13f60d 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h From f1f018071b29f5c5620287b5497172bb2ba57072 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 16:44:57 -0300 Subject: [PATCH 062/120] fix(a11y): close drawer when navigating to the focused sidebar route (#7717) * fix(a11y): close drawer when navigating to the focused sidebar route Tapping a sidebar entry for the route already focused left the drawer open, because the drawer router only closes it when the focused index changes. Subsequent taps were swallowed by the drawer scrim. * chore(e2e): map ToastsAndDialogs flow to SidebarView The flow reaches Accessibility & appearance through the sidebar, so a change under app/views/SidebarView can break it. Without this the impact analysis never selects its shard for sidebar-only changes. --- .sniffler/test-map.json | 2 +- .../methods/__tests__/sidebarNavigate.test.ts | 29 +++++++++++++++++++ .../SidebarView/methods/sidebarNavigate.ts | 3 ++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 app/views/SidebarView/methods/__tests__/sidebarNavigate.test.ts diff --git a/.sniffler/test-map.json b/.sniffler/test-map.json index 7c9e3c8bd7c..027e9323988 100644 --- a/.sniffler/test-map.json +++ b/.sniffler/test-map.json @@ -1,7 +1,7 @@ [ { "test": ".maestro/tests/accessibilityAndAppearance/ToastsAndDialogs.yml", - "dependsOn": ["app/views/StatusView/**"] + "dependsOn": ["app/views/StatusView/**", "app/views/SidebarView/**"] }, { "test": ".maestro/tests/assorted/accessibility-and-appearance.yaml", diff --git a/app/views/SidebarView/methods/__tests__/sidebarNavigate.test.ts b/app/views/SidebarView/methods/__tests__/sidebarNavigate.test.ts new file mode 100644 index 00000000000..d1dda82be21 --- /dev/null +++ b/app/views/SidebarView/methods/__tests__/sidebarNavigate.test.ts @@ -0,0 +1,29 @@ +import { sidebarNavigate } from '../sidebarNavigate'; +import Navigation from '~/lib/navigation/appNavigation'; + +jest.mock('~/lib/methods/helpers/log', () => ({ + events: {}, + logEvent: jest.fn() +})); + +const mockNavigate = jest.fn(); +const mockDispatch = jest.fn(); + +describe('sidebarNavigate', () => { + beforeEach(() => { + jest.clearAllMocks(); + Navigation.navigationRef.current = { navigate: mockNavigate, dispatch: mockDispatch } as any; + }); + + it('navigates to the route', () => { + sidebarNavigate('AccessibilityStackNavigator'); + + expect(mockNavigate).toHaveBeenCalledWith('AccessibilityStackNavigator', undefined); + }); + + it('closes the drawer even when navigating to the focused route', () => { + sidebarNavigate('AccessibilityStackNavigator'); + + expect(mockDispatch).toHaveBeenCalledWith(expect.objectContaining({ type: 'CLOSE_DRAWER' })); + }); +}); diff --git a/app/views/SidebarView/methods/sidebarNavigate.ts b/app/views/SidebarView/methods/sidebarNavigate.ts index 07cc3fc6f11..064ba9fb57f 100644 --- a/app/views/SidebarView/methods/sidebarNavigate.ts +++ b/app/views/SidebarView/methods/sidebarNavigate.ts @@ -1,3 +1,5 @@ +import { DrawerActions } from '@react-navigation/native'; + import Navigation from '~/lib/navigation/appNavigation'; import { events, logEvent } from '~/lib/methods/helpers/log'; @@ -5,4 +7,5 @@ export const sidebarNavigate = (route: string) => { // @ts-ignore logEvent(events[`SIDEBAR_GO_${route.replace('StackNavigator', '').replace('View', '').toUpperCase()}`]); Navigation.navigate(route); + Navigation.dispatch(DrawerActions.closeDrawer()); }; From 35729e0d10edf9abe13542083bedf0f7c3aa041e Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 16:46:15 -0300 Subject: [PATCH 063/120] fix(ios): render room title icon with the title on first frame --- .../__tests__/useNativeRoomHeader.test.ts | 7 ++++++ app/views/RoomView/hooks/useHeaderImage.ts | 14 +++++++++-- .../RoomView/hooks/useNativeRoomHeader.ts | 7 +++--- patches/react-native-screens+4.28.0.patch | 25 ++++++++++--------- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts index eea9f512ba2..2769943b0e3 100644 --- a/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts +++ b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts @@ -47,6 +47,13 @@ beforeEach(() => { }; }); +it('holds the title until the room icon is ready', async () => { + renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'p', title: 'Held' })); + expect(mockSetOptions.mock.calls[0][0]).toMatchObject({ headerTitle: '', headerSubtitle: undefined }); + await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('channel-private:title')); + expect(mockSetOptions.mock.calls.every(([options]) => options.headerTitle === '' || options.headerTitleImageSource)).toBe(true); +}); + it('shows plain topic text and the room icon', async () => { renderHook(() => useNativeRoomHeader(true, fields)); await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('channel-public:title')); diff --git a/app/views/RoomView/hooks/useHeaderImage.ts b/app/views/RoomView/hooks/useHeaderImage.ts index f3e56648f53..c8bf45f5b38 100644 --- a/app/views/RoomView/hooks/useHeaderImage.ts +++ b/app/views/RoomView/hooks/useHeaderImage.ts @@ -4,13 +4,20 @@ import { Image, type ImageSourcePropType } from 'react-native'; import { IconSet, type TIconsName } from '~/containers/CustomIcon'; import log from '~/lib/methods/helpers/log'; +const iconImageCache = new Map(); + export const useHeaderIconImage = (name: TIconsName | undefined, color: string, size: number) => { const [image, setImage] = useState<{ name: TIconsName; color: string; source: ImageSourcePropType }>(); + const cacheKey = `${name}:${color}:${size}`; + const cachedSource = name ? iconImageCache.get(cacheKey) : undefined; useEffect(() => { let cancelled = false; - if (name) { + if (name && !iconImageCache.has(cacheKey)) { IconSet.getImageSource(name, size, color) .then(source => { + if (source) { + iconImageCache.set(cacheKey, source); + } if (!cancelled && source) { setImage({ name, color, source }); } @@ -20,7 +27,10 @@ export const useHeaderIconImage = (name: TIconsName | undefined, color: string, return () => { cancelled = true; }; - }, [name, color, size]); + }, [name, color, size, cacheKey]); + if (cachedSource) { + return cachedSource; + } return image?.name === name && image?.color === color ? image?.source : undefined; }; diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts index a73477736d7..a9c9156c1cd 100644 --- a/app/views/RoomView/hooks/useNativeRoomHeader.ts +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -142,15 +142,16 @@ export const useNativeRoomHeader = (enabled: boolean, fields: IHeaderFields, tmi const roomImage = remoteImage ?? glyphImage; const clockImage = useHeaderIconImage(enabled && showClock ? 'clock' : undefined, colors.fontSecondaryInfo, 12); const subtitleImage = tmid ? roomImage : clockImage; + const titleReady = !!tmid || !!roomImage; useLayoutEffect(() => { if (enabled) { navigation.setOptions({ - headerTitle: title, - headerSubtitle: subtitle || undefined, + headerTitle: titleReady ? title : '', + headerSubtitle: (titleReady && subtitle) || undefined, headerTitleImageSource: tmid ? undefined : roomImage, headerSubtitleImageSource: subtitleImage }); } - }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage]); + }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage, titleReady]); }; diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch index b55bed8b217..be97ec3d024 100644 --- a/patches/react-native-screens+4.28.0.patch +++ b/patches/react-native-screens+4.28.0.patch @@ -33,24 +33,25 @@ index fa1b7731..153d712e 100644 view: ScreenStackHeaderConfig?, value: String?, diff --git a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm -index 64beb499..2c16cff4 100644 +index 64beb499..87936eac 100644 --- a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm +++ b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm -@@ -22,7 +22,13 @@ - imageLoadingCompletionBlock([RNSImageLoadingHelper handleRenderingModeForImage:image isTemplate:isTemplate]); - } else - #endif // !defined(NDEBUG) -- { +@@ -38,6 +38,15 @@ + RCTAssert(imageSource != nil, @"[RNScreens] imageSource must not be nil"); + RCTAssert(imageLoader != nil, @"[RNScreens] imageLoader must not be nil"); + + if (imageSource.request.URL.isFileURL) { + UIImage *image = [UIImage imageWithContentsOfFile:imageSource.request.URL.path]; + if (image != nil) { + image = [UIImage imageWithCGImage:image.CGImage scale:imageSource.scale orientation:image.imageOrientation]; + } + imageLoadingCompletionBlock([RNSImageLoadingHelper handleRenderingModeForImage:image isTemplate:isTemplate]); -+ } else { - [self loadImageFromSource:imageSource - withImageLoader:imageLoader - asTemplate:isTemplate ++ return; ++ } ++ + [imageLoader loadImageWithURLRequest:imageSource.request + size:imageSource.size + scale:imageSource.scale diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h index 8a416972..9b13f60d 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h @@ -85,7 +86,7 @@ index 8a416972..9b13f60d 100644 @property (nonatomic, retain) NSString *backTitleFontFamily; @property (nonatomic, retain) NSNumber *backTitleFontSize; diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm -index a54cb632..43905c9d 100644 +index a54cb632..90d223ae 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm @@ -13,6 +13,7 @@ @@ -209,7 +210,7 @@ index a54cb632..43905c9d 100644 + dispatch_async(dispatch_get_main_queue(), onLoaded); + }]; + -+ return nil; ++ return [[RNSHeaderImageSlot sharedImageCache] objectForKey:uri]; +} + +- (void)reset From 07cfcd6e38f29f0348625ea436a813782e21dfc3 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 17:04:45 -0300 Subject: [PATCH 064/120] fix(ios): size room header icons to the title font and restore title/subtitle colors --- .../RoomView/hooks/useNativeRoomHeader.ts | 13 ++- ...react-navigation+native-stack+7.19.2.patch | 48 ++++++++--- patches/react-native-screens+4.28.0.patch | 84 +++++++++++-------- 3 files changed, 96 insertions(+), 49 deletions(-) diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts index a9c9156c1cd..53727ae11bd 100644 --- a/app/views/RoomView/hooks/useNativeRoomHeader.ts +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -16,6 +16,9 @@ import { type IRoomViewProps } from '../definitions'; import { type IHeaderFields } from './useHeader'; import { useHeaderIconImage, useHeaderRemoteImage } from './useHeaderImage'; +const TITLE_FONT_SIZE = 17; +const SUBTITLE_FONT_SIZE = 12; + const sourceIcons: Record = { widget: 'livechat-monochromatic', email: 'mail', @@ -130,7 +133,7 @@ export const useNativeRoomHeader = (enabled: boolean, fields: IHeaderFields, tmi const glyphImage = useHeaderIconImage( enabled && fields.type ? roomIcon : undefined, isDirectMessage || fields.type === 'l' ? statusColor : colors.fontTitlesLabels, - 16 + TITLE_FONT_SIZE ); const server = useAppSelector(state => state.server.server); const source = fields.sourceType; @@ -140,7 +143,7 @@ export const useNativeRoomHeader = (enabled: boolean, fields: IHeaderFields, tmi : undefined; const remoteImage = useHeaderRemoteImage(remoteUri); const roomImage = remoteImage ?? glyphImage; - const clockImage = useHeaderIconImage(enabled && showClock ? 'clock' : undefined, colors.fontSecondaryInfo, 12); + const clockImage = useHeaderIconImage(enabled && showClock ? 'clock' : undefined, colors.fontSecondaryInfo, SUBTITLE_FONT_SIZE); const subtitleImage = tmid ? roomImage : clockImage; const titleReady = !!tmid || !!roomImage; @@ -150,8 +153,10 @@ export const useNativeRoomHeader = (enabled: boolean, fields: IHeaderFields, tmi headerTitle: titleReady ? title : '', headerSubtitle: (titleReady && subtitle) || undefined, headerTitleImageSource: tmid ? undefined : roomImage, - headerSubtitleImageSource: subtitleImage + headerSubtitleImageSource: subtitleImage, + headerTitleStyle: { color: colors.fontTitlesLabels }, + headerSubtitleColor: colors.fontSecondaryInfo }); } - }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage, titleReady]); + }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage, titleReady, colors]); }; diff --git a/patches/@react-navigation+native-stack+7.19.2.patch b/patches/@react-navigation+native-stack+7.19.2.patch index 12732043d24..4406c0ccc77 100644 --- a/patches/@react-navigation+native-stack+7.19.2.patch +++ b/patches/@react-navigation+native-stack+7.19.2.patch @@ -1,19 +1,20 @@ diff --git a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js -index a56cb12..301d782 100644 +index a56cb12..5be7312 100644 --- a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js +++ b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js -@@ -149,7 +149,10 @@ export function useHeaderConfigProps({ +@@ -149,7 +149,11 @@ export function useHeaderConfigProps({ headerStyle, headerBlurEffect, headerTintColor, + headerSubtitle, + headerSubtitleImageSource, ++ headerSubtitleColor, headerTitle, + headerTitleImageSource, headerTitleAlign, headerTitleStyle, headerTransparent, -@@ -230,6 +233,14 @@ export function useHeaderConfigProps({ +@@ -230,6 +234,14 @@ export function useHeaderConfigProps({ tintColor, children: titleText }) : null; @@ -28,22 +29,23 @@ index a56cb12..301d782 100644 const supportsHeaderSearchBar = typeof isSearchBarAvailableForCurrentPlatform === 'boolean' ? isSearchBarAvailableForCurrentPlatform : // Fallback for older versions of react-native-screens Platform.OS === 'ios' && SearchBar != null; -@@ -356,7 +367,10 @@ export function useHeaderConfigProps({ +@@ -356,7 +368,11 @@ export function useHeaderConfigProps({ largeTitleFontSize, largeTitleFontWeight, largeTitleHideShadow: headerLargeTitleShadowVisible === false, + subtitle: subtitleText, + subtitleImageSource, ++ subtitleColor: headerSubtitleColor, title: titleText, + titleImageSource, titleColor, titleFontFamily, titleFontSize, diff --git a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts -index c32d1da..4717022 100644 +index c32d1da..2ef28f2 100644 --- a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts +++ b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts -@@ -321,6 +321,42 @@ export type NativeStackNavigationOptions = { +@@ -321,6 +321,52 @@ export type NativeStackNavigationOptions = { */ tintColor?: string; }) => React.ReactNode); @@ -83,14 +85,24 @@ index c32d1da..4717022 100644 + * @supported iOS 26 or higher + */ + headerSubtitleImageSource?: ImageSourcePropType; ++ /** ++ * Color of the header subtitle text. Defaults to the title color. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerSubtitleColor?: ColorValue; /** * How to align the the header title. * Defaults to `left` on platforms other than iOS. diff --git a/node_modules/@react-navigation/native-stack/src/types.tsx b/node_modules/@react-navigation/native-stack/src/types.tsx -index aeb4939..1373158 100644 +index aeb4939..2d89ae8 100644 --- a/node_modules/@react-navigation/native-stack/src/types.tsx +++ b/node_modules/@react-navigation/native-stack/src/types.tsx -@@ -371,6 +371,42 @@ export type NativeStackNavigationOptions = { +@@ -371,6 +371,52 @@ export type NativeStackNavigationOptions = { */ tintColor?: string; }) => React.ReactNode); @@ -130,25 +142,36 @@ index aeb4939..1373158 100644 + * @supported iOS 26 or higher + */ + headerSubtitleImageSource?: ImageSourcePropType; ++ /** ++ * Color of the header subtitle text. Defaults to the title color. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerSubtitleColor?: ColorValue; /** * How to align the the header title. * Defaults to `left` on platforms other than iOS. diff --git a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx -index a7e3004..3addeae 100644 +index a7e3004..af41403 100644 --- a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx +++ b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx -@@ -210,7 +210,10 @@ export function useHeaderConfigProps({ +@@ -210,7 +210,11 @@ export function useHeaderConfigProps({ headerStyle, headerBlurEffect, headerTintColor, + headerSubtitle, + headerSubtitleImageSource, ++ headerSubtitleColor, headerTitle, + headerTitleImageSource, headerTitleAlign, headerTitleStyle, headerTransparent, -@@ -330,6 +333,20 @@ export function useHeaderConfigProps({ +@@ -330,6 +334,20 @@ export function useHeaderConfigProps({ }) : null; @@ -169,12 +192,13 @@ index a7e3004..3addeae 100644 const supportsHeaderSearchBar = typeof isSearchBarAvailableForCurrentPlatform === 'boolean' ? isSearchBarAvailableForCurrentPlatform -@@ -522,7 +539,10 @@ export function useHeaderConfigProps({ +@@ -522,7 +540,11 @@ export function useHeaderConfigProps({ largeTitleFontSize, largeTitleFontWeight, largeTitleHideShadow: headerLargeTitleShadowVisible === false, + subtitle: subtitleText, + subtitleImageSource, ++ subtitleColor: headerSubtitleColor, title: titleText, + titleImageSource, titleColor, diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch index be97ec3d024..1fe001196ed 100644 --- a/patches/react-native-screens+4.28.0.patch +++ b/patches/react-native-screens+4.28.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt -index fa1b7731..153d712e 100644 +index fa1b773..f54b225 100644 --- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt @@ -3,6 +3,7 @@ package com.swmansion.rnscreens @@ -10,7 +10,7 @@ index fa1b7731..153d712e 100644 import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.StateWrapper -@@ -236,6 +237,21 @@ class ScreenStackHeaderConfigViewManager : +@@ -236,6 +237,26 @@ class ScreenStackHeaderConfigViewManager : value: String?, ) = Unit @@ -28,12 +28,17 @@ index fa1b7731..153d712e 100644 + view: ScreenStackHeaderConfig?, + value: ReadableMap?, + ) = Unit ++ ++ override fun setSubtitleColor( ++ view: ScreenStackHeaderConfig?, ++ value: Int?, ++ ) = Unit + override fun setBackTitleFontFamily( view: ScreenStackHeaderConfig?, value: String?, diff --git a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm -index 64beb499..87936eac 100644 +index 64beb49..87936ea 100644 --- a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm +++ b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm @@ -38,6 +38,15 @@ @@ -53,7 +58,7 @@ index 64beb499..87936eac 100644 size:imageSource.size scale:imageSource.scale diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h -index 8a416972..9b13f60d 100644 +index 8a41697..25e6586 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h +++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h @@ -8,10 +8,14 @@ @@ -71,7 +76,7 @@ index 8a416972..9b13f60d 100644 @end @interface RNSScreenStackHeaderConfig : RNSReactBaseView -@@ -23,10 +27,13 @@ +@@ -23,10 +27,14 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, retain) NSString *title; @@ -80,13 +85,14 @@ index 8a416972..9b13f60d 100644 @property (nonatomic, retain) NSNumber *titleFontSize; @property (nonatomic, retain) NSString *titleFontWeight; @property (nonatomic, retain) UIColor *titleColor; ++@property (nonatomic, retain, nullable) UIColor *subtitleColor; +@property (nonatomic, retain, nullable) RCTImageSource *titleImageSource; +@property (nonatomic, retain, nullable) RCTImageSource *subtitleImageSource; @property (nonatomic, retain) NSString *backTitle; @property (nonatomic, retain) NSString *backTitleFontFamily; @property (nonatomic, retain) NSNumber *backTitleFontSize; diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm -index a54cb632..90d223ae 100644 +index a54cb63..98212de 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm @@ -13,6 +13,7 @@ @@ -240,7 +246,7 @@ index a54cb632..90d223ae 100644 _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}, react::Point{}); [self initProps]; } -@@ -131,6 +240,52 @@ RNS_IGNORE_SUPER_CALL_END +@@ -131,6 +240,56 @@ RNS_IGNORE_SUPER_CALL_END return nil; } @@ -280,9 +286,13 @@ index a54cb632..90d223ae 100644 +- (nullable NSAttributedString *)attributedSubtitle +{ + UIImage *glyph = [self resolvedSubtitleImage]; -+ if (glyph == nil || _subtitle == nil) { ++ if (_subtitle == nil || (glyph == nil && _subtitleColor == nil)) { + return nil; + } ++ if (glyph == nil) { ++ return [[NSAttributedString alloc] initWithString:_subtitle ++ attributes:[RNSScreenStackHeaderConfig subtitleTextAttributesForConfig:self]]; ++ } + + return [RNSScreenStackHeaderConfig + attributedStringWithLeadingGlyph:glyph @@ -293,7 +303,7 @@ index a54cb632..90d223ae 100644 - (void)updateViewControllerIfNeeded { UIViewController *vc = _screenView.controller; -@@ -181,10 +336,10 @@ RNS_IGNORE_SUPER_CALL_END +@@ -181,10 +340,10 @@ RNS_IGNORE_SUPER_CALL_END if (newState != _lastSendState) { _lastSendState = newState; @@ -308,7 +318,7 @@ index a54cb632..90d223ae 100644 } } -@@ -293,10 +448,11 @@ RNS_IGNORE_SUPER_CALL_END +@@ -293,10 +452,11 @@ RNS_IGNORE_SUPER_CALL_END // in the image attribute not being updated. We manually set frame to the size of an image // in order to trigger proper reload that'd update the image attribute. RCTImageSource *imageSource = [RNSScreenStackHeaderConfig imageSourceFromImageView:imageView]; @@ -324,7 +334,7 @@ index a54cb632..90d223ae 100644 } UIImage *image = imageView.image; -@@ -365,6 +521,83 @@ RNS_IGNORE_SUPER_CALL_END +@@ -365,6 +525,81 @@ RNS_IGNORE_SUPER_CALL_END } } @@ -374,7 +384,7 @@ index a54cb632..90d223ae 100644 + return [self resolvedTitleTextAttributesWithFontFamily:config.titleFontFamily + fontSize:size + fontWeight:nil -+ color:config.titleColor ++ color:config.subtitleColor ?: config.titleColor + boldFallbackFont:[UIFont systemFontOfSize:[size floatValue]]]; +} + @@ -384,17 +394,15 @@ index a54cb632..90d223ae 100644 +{ + UIFont *font = + textAttributes[NSFontAttributeName] ?: [UIFont boldSystemFontOfSize:[DEFAULT_TITLE_FONT_SIZE floatValue]]; -+ UIColor *tintColor = textAttributes[NSForegroundColorAttributeName] ?: UIColor.labelColor; + -+ CGFloat glyphHeight = font.capHeight; ++ CGFloat glyphHeight = font.pointSize; + CGSize intrinsicSize = glyph.size; + CGFloat aspectRatio = intrinsicSize.height > 0 ? intrinsicSize.width / intrinsicSize.height : 1; + + NSTextAttachment *attachment = [NSTextAttachment new]; -+ attachment.image = [[glyph imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] -+ imageWithTintColor:tintColor -+ renderingMode:UIImageRenderingModeAlwaysOriginal]; -+ attachment.bounds = CGRectMake(0, 0, glyphHeight * aspectRatio, glyphHeight); ++ attachment.image = [glyph imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; ++ attachment.bounds = ++ CGRectMake(0, (font.capHeight - glyphHeight) / 2, glyphHeight * aspectRatio, glyphHeight); + + NSMutableAttributedString *composed = [[NSMutableAttributedString alloc] + initWithAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; @@ -408,7 +416,7 @@ index a54cb632..90d223ae 100644 + (UINavigationBarAppearance *)buildAppearance:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config { UINavigationBarAppearance *appearance = [UINavigationBarAppearance new]; -@@ -380,8 +613,10 @@ RNS_IGNORE_SUPER_CALL_END +@@ -380,8 +615,10 @@ RNS_IGNORE_SUPER_CALL_END appearance.shadowColor = shadowColor; appearance.shadowImage = shadowImage; } @@ -420,7 +428,7 @@ index a54cb632..90d223ae 100644 } // set background color if specified -@@ -408,59 +643,20 @@ RNS_IGNORE_SUPER_CALL_END +@@ -408,59 +645,20 @@ RNS_IGNORE_SUPER_CALL_END } if (config.titleFontFamily || config.titleFontSize || config.titleFontWeight || config.titleColor) { @@ -489,7 +497,7 @@ index a54cb632..90d223ae 100644 } UIImage *backButtonImage = [config loadBackButtonImageInViewController:vc]; -@@ -507,6 +703,11 @@ RNS_IGNORE_SUPER_CALL_END +@@ -507,6 +705,11 @@ RNS_IGNORE_SUPER_CALL_END if (shouldHide) { navitem.title = config.title; @@ -501,7 +509,7 @@ index a54cb632..90d223ae 100644 // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items. [navctr setNavigationBarHidden:YES animated:animated]; -@@ -648,6 +849,21 @@ RNS_IGNORE_SUPER_CALL_END +@@ -648,6 +851,21 @@ RNS_IGNORE_SUPER_CALL_END // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug). // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments) navitem.title = config.title; @@ -523,7 +531,7 @@ index a54cb632..90d223ae 100644 navitem.leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems withCurrentItems:navitem.leftBarButtonItems]; navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems -@@ -700,7 +916,8 @@ RNS_IGNORE_SUPER_CALL_END +@@ -700,7 +918,8 @@ RNS_IGNORE_SUPER_CALL_END const auto *config = self; const auto isBackTitleBlank = [NSString rnscreens_isBlankOrNull:config.backTitle] == YES; @@ -533,7 +541,7 @@ index a54cb632..90d223ae 100644 // If previous screen controller was recreated (e.g. when you go back to tab with stack that has multiple screens), // its navigationItem may not have any information from screen's headerConfig, including the title. -@@ -858,12 +1075,13 @@ RNS_IGNORE_SUPER_CALL_END +@@ -858,12 +1077,13 @@ RNS_IGNORE_SUPER_CALL_END return; } @@ -553,7 +561,7 @@ index a54cb632..90d223ae 100644 // [_reactSubviews insertObject:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; [self insertReactSubview:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; -@@ -987,6 +1205,8 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -987,6 +1207,8 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) { [super prepareForRecycle]; _initialPropsSet = NO; @@ -562,7 +570,7 @@ index a54cb632..90d223ae 100644 _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}, react::Point{}); } -@@ -1025,6 +1245,7 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -1025,6 +1247,7 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) } _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title); @@ -570,10 +578,11 @@ index a54cb632..90d223ae 100644 if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) { _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily); } -@@ -1064,6 +1285,24 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -1064,6 +1287,25 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) // We cannot compare SharedColor because it is shared value. // We could compare color value, but it is more performant to just assign new value _titleColor = RCTUIColorFromSharedColor(newScreenProps.titleColor); ++ _subtitleColor = RCTUIColorFromSharedColor(newScreenProps.subtitleColor); + if (newScreenProps.titleImageSource != oldScreenProps.titleImageSource) { + const auto &titleImageSource = newScreenProps.titleImageSource; + _titleImageSource = titleImageSource.uri.empty() @@ -596,7 +605,7 @@ index a54cb632..90d223ae 100644 _color = RCTUIColorFromSharedColor(newScreenProps.color); _backgroundColor = RCTUIColorFromSharedColor(newScreenProps.backgroundColor); diff --git a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts -index da28c51c..c421621a 100644 +index da28c51..e6400df 100644 --- a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts +++ b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts @@ -4,6 +4,7 @@ import { codegenNativeComponent } from 'react-native'; @@ -607,7 +616,7 @@ index da28c51c..c421621a 100644 ViewProps, ColorValue, } from 'react-native'; -@@ -66,10 +67,13 @@ export interface NativeProps extends ViewProps { +@@ -66,10 +67,14 @@ export interface NativeProps extends ViewProps { largeTitleColor?: ColorValue | undefined; translucent?: boolean | undefined; title?: string | undefined; @@ -616,16 +625,17 @@ index da28c51c..c421621a 100644 titleFontSize?: CT.Int32 | undefined; titleFontWeight?: string | undefined; titleColor?: ColorValue | undefined; ++ subtitleColor?: ColorValue | undefined; + titleImageSource?: ImageSource | undefined; + subtitleImageSource?: ImageSource | undefined; disableBackButtonMenu?: boolean | undefined; backButtonDisplayMode?: CT.WithDefault; hideBackButton?: boolean | undefined; diff --git a/node_modules/react-native-screens/src/types.tsx b/node_modules/react-native-screens/src/types.tsx -index 8594b733..777ae443 100644 +index 8594b73..299a098 100644 --- a/node_modules/react-native-screens/src/types.tsx +++ b/node_modules/react-native-screens/src/types.tsx -@@ -827,10 +827,41 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { +@@ -827,10 +827,49 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { * String that can be displayed in the header as a fallback for `headerTitle`. */ title?: string | undefined; @@ -643,7 +653,7 @@ index 8594b733..777ae443 100644 titleColor?: ColorValue | undefined; + /** + * Image rendered as a leading glyph before the title. The glyph is sized from -+ * the resolved title font and tinted with `titleColor`. ++ * the resolved title font and keeps its original colors. + * Requires iOS 26 or higher and is ignored on Android and earlier iOS versions. + * The glyph is hidden while the large title is expanded and appears on collapse. + * A custom header title view replaces the title and its glyph. @@ -655,7 +665,7 @@ index 8594b733..777ae443 100644 + titleImageSource?: ImageSourcePropType | undefined; + /** + * Image rendered as a leading glyph before the subtitle. The glyph is sized -+ * from the resolved subtitle font and tinted with `titleColor`. ++ * from the resolved subtitle font and keeps its original colors. + * Requires iOS 26 or higher and is ignored on Android and earlier iOS versions. + * A custom header title view does not replace the subtitle or its glyph. + * @@ -664,6 +674,14 @@ index 8594b733..777ae443 100644 + * @supported iOS 26 or higher + */ + subtitleImageSource?: ImageSourcePropType | undefined; ++ /** ++ * Color of the subtitle text. Defaults to `titleColor`. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ subtitleColor?: ColorValue | undefined; /** * Customize font family to be used for the title. */ From 0ae44ec87c1030a000b0d5c9e519884ab6cea030 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 17:04:49 -0300 Subject: [PATCH 065/120] fix(ios): keep drawer content below the status bar --- app/views/SidebarView/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/views/SidebarView/index.tsx b/app/views/SidebarView/index.tsx index c65c6ba18ef..1f00b54ce25 100644 --- a/app/views/SidebarView/index.tsx +++ b/app/views/SidebarView/index.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { type DrawerNavigationProp } from '@react-navigation/drawer'; import { ScrollView, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import scrollPersistTaps from '~/lib/methods/helpers/scrollPersistTaps'; import styles from './styles'; @@ -12,6 +13,7 @@ import Admin from './components/Admin'; import Profile from './components/Profile'; const SidebarView = ({ navigation }: { navigation: DrawerNavigationProp }) => { + const { top } = useSafeAreaInsets(); const [currentScreen, setCurrentScreen] = useState(null); useEffect(() => { @@ -24,7 +26,11 @@ const SidebarView = ({ navigation }: { navigation: DrawerNavigationProp - + From bd8884b0aeb502842d95825dd319eedf39494751 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 17:16:50 -0300 Subject: [PATCH 066/120] feat(ios): open room header overflow actions as a native menu --- .../methods/helpers/navigation/headerIcon.ts | 5 + .../__tests__/useRoomHeaderRightItems.test.ts | 102 ++++++++++++++++ .../RoomView/hooks/useRoomHeaderRightItems.ts | 111 +++++++++--------- 3 files changed, 165 insertions(+), 53 deletions(-) create mode 100644 app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts diff --git a/app/lib/methods/helpers/navigation/headerIcon.ts b/app/lib/methods/helpers/navigation/headerIcon.ts index b88586c19cc..473be69bc09 100644 --- a/app/lib/methods/helpers/navigation/headerIcon.ts +++ b/app/lib/methods/helpers/navigation/headerIcon.ts @@ -5,15 +5,20 @@ import { IconSet, type TIconsName } from '~/containers/CustomIcon'; const HEADER_ICON_SIZE = 24; const HEADER_ICONS: TIconsName[] = [ + 'chat-close', + 'chat-forward', 'close', 'create', 'directory', 'encrypted', 'hamburguer', 'kebab', + 'move-to-the-queue', 'notification', 'notification-disabled', + 'pause', 'phone', + 'search', 'threads', 'workspaces' ]; diff --git a/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts b/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts new file mode 100644 index 00000000000..87af02e9582 --- /dev/null +++ b/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts @@ -0,0 +1,102 @@ +import { renderHook } from '@testing-library/react-native'; +import { type NativeStackHeaderItem, type NativeStackHeaderItemMenu } from '@react-navigation/native-stack'; + +import { type RoomStore } from '~/views/RoomView/definitions'; +import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; +import { useRoomHeaderRightItems } from '../useRoomHeaderRightItems'; + +jest.mock('@react-navigation/native', () => ({ useNavigation: () => ({ navigate: jest.fn() }) })); +jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => false })); +jest.mock('~/lib/hooks/useSetting', () => ({ useSetting: () => false })); +jest.mock('~/lib/hooks/useAppSelector', () => ({ useAppSelector: () => 'user-1' })); +jest.mock('~/theme', () => ({ useTheme: () => ({ colors: {} }) })); +jest.mock('~/lib/methods/helpers/navigation/headerIcon', () => ({ + headerIcon: (name: string) => ({ type: 'image', source: name }) +})); + +let mockRoomState: Record; +jest.mock('zustand', () => ({ + useStore: (_store: unknown, selector: (state: Record) => unknown) => selector(mockRoomState) +})); +jest.mock('zustand/react/shallow', () => ({ useShallow: (selector: unknown) => selector })); + +let mockCanPlaceLivechatOnHold = false; +jest.mock('~/ee/omnichannel/hooks/useCanReturnQueue', () => ({ useCanReturnQueue: () => false })); +jest.mock('../useCanPlaceLivechatOnHold', () => ({ useCanPlaceLivechatOnHold: () => mockCanPlaceLivechatOnHold })); +jest.mock('../useThreadFollowing', () => ({ useThreadFollowing: () => false })); +const mockGoRoomActions = jest.fn(); +jest.mock('../useGoRoomActionsView', () => ({ useGoRoomActionsView: () => mockGoRoomActions })); +jest.mock('~/views/RoomView/services/closeLivechat', () => ({ closeLivechat: jest.fn() })); +jest.mock('~/views/RoomView/services/placeLivechatOnHold', () => ({ placeLivechatOnHold: jest.fn() })); +jest.mock('~/lib/services/restApi', () => ({ returnLivechat: jest.fn() })); + +const mockGoSearchView = jest.fn(); +let mockButtonsData: Record; +jest.mock('../../components/RightButtons/useRoomRightButtonsData', () => ({ + useRoomRightButtonsData: () => mockButtonsData +})); +let mockCallPresent = false; +jest.mock('../../components/RightButtons/useHeaderCallPress', () => ({ + useHeaderCallPress: () => ({ callPresent: mockCallPresent, isCallDisabled: true, onPressCall: jest.fn() }) +})); + +const roomStore = {} as RoomStore; + +const moreMenuOf = (items: NativeStackHeaderItem[]) => { + const more = items[items.length - 1] as NativeStackHeaderItemMenu; + expect(more.type).toBe('menu'); + return more.menu.items.map(item => item as Extract); +}; + +describe('useRoomHeaderRightItems', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockCanPlaceLivechatOnHold = false; + mockCallPresent = false; + mockRoomState = { room: { t: 'c' }, membership: 'subscribed', canForwardGuest: false }; + mockButtonsData = { + threadsEnabled: false, + hasE2EEWarning: false, + tunread: [], + threadsAccessibilityLabel: 'Threads', + callAccessibilityLabel: 'Call', + goSearchView: mockGoSearchView + }; + }); + + it('lists search and actions in the room more menu and runs the chosen action', () => { + const { result } = renderHook(() => useRoomHeaderRightItems('rid-1', undefined, roomStore)); + const actions = moreMenuOf(result.current); + + expect(actions.map(action => action.label)).toEqual(['Search messages', 'Actions']); + actions[1].onPress(); + expect(mockGoRoomActions).toHaveBeenCalled(); + }); + + it('moves overflowing header actions into the menu, keeping disabled state', () => { + mockButtonsData = { ...mockButtonsData, threadsEnabled: true, hasE2EEWarning: true, canToggleEncryption: false }; + mockCallPresent = true; + + const { result } = renderHook(() => useRoomHeaderRightItems('rid-1', undefined, roomStore)); + const actions = moreMenuOf(result.current); + + expect(actions.map(action => [action.label, !!action.disabled])).toEqual([ + ['Encrypted', true], + ['Search messages', true], + ['Actions', false] + ]); + }); + + it('shows omnichannel options with close as the destructive last item', () => { + mockRoomState = { room: { t: 'l' }, membership: 'subscribed', canForwardGuest: true }; + mockCanPlaceLivechatOnHold = true; + + const { result } = renderHook(() => useRoomHeaderRightItems('rid-1', undefined, roomStore)); + const actions = moreMenuOf(result.current); + + expect(actions.map(action => action.label)).toEqual(['Place chat on hold', 'Forward chat', 'Close']); + expect(actions[2].destructive).toBe(true); + actions[2].onPress(); + expect(closeLivechat).toHaveBeenCalledWith(expect.objectContaining({ rid: 'rid-1' })); + }); +}); diff --git a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts index 13565eef5e9..beb266d8c88 100644 --- a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts +++ b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts @@ -1,9 +1,8 @@ import { useNavigation } from '@react-navigation/native'; -import { type NativeStackHeaderItem } from '@react-navigation/native-stack'; +import { type NativeStackHeaderItem, type NativeStackHeaderItemMenuAction } from '@react-navigation/native-stack'; import { useStore } from 'zustand'; import { useShallow } from 'zustand/react/shallow'; -import { showActionSheetRef, useActionSheet, type TActionSheetOptionsItem } from '~/containers/ActionSheet'; import i18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; @@ -43,7 +42,6 @@ const getTunreadBadgeColor = ( const useOmnichannelRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): NativeStackHeaderItem[] => { const navigation = useNavigation(); const isMasterDetail = useMasterDetail(); - const { showActionSheet } = useActionSheet(); const livechatRequestComment = useSetting('Livechat_request_comment_when_closing_conversation') as boolean; const departmentId = useStore( @@ -68,40 +66,38 @@ const useOmnichannelRightItems = (rid: string, roomStore: RoomStore, enabled: bo }); }; - const showMoreActions = () => { - logEvent(events.ROOM_SHOW_MORE_ACTIONS); - const options = [] as TActionSheetOptionsItem[]; - if (canPlaceLivechatOnHold) { - options.push({ - title: i18n.t('Place_chat_on_hold'), - icon: 'pause', - onPress: () => placeLivechatOnHold({ rid, navigation }) - }); - } - if (canForwardGuest) { - options.push({ - title: i18n.t('Forward_Chat'), - icon: 'chat-forward', - onPress: () => { - navigateToScreen({ navigation, isMasterDetail, screen: 'ForwardLivechatView', params: { rid } }); - } - }); - } - if (canReturnQueue) { - options.push({ - title: i18n.t('Return_to_waiting_line'), - icon: 'move-to-the-queue', - onPress: () => handleReturnLivechat() - }); - } - options.push({ - title: i18n.t('Close'), - icon: 'chat-close', - onPress: () => closeLivechat({ rid, departmentId, isMasterDetail, livechatRequestComment, navigation }), - danger: true + const moreActions: NativeStackHeaderItemMenuAction[] = []; + if (canPlaceLivechatOnHold) { + moreActions.push({ + type: 'action', + label: i18n.t('Place_chat_on_hold'), + icon: headerIcon('pause'), + onPress: () => placeLivechatOnHold({ rid, navigation }) }); - showActionSheet({ options }); - }; + } + if (canForwardGuest) { + moreActions.push({ + type: 'action', + label: i18n.t('Forward_Chat'), + icon: headerIcon('chat-forward'), + onPress: () => navigateToScreen({ navigation, isMasterDetail, screen: 'ForwardLivechatView', params: { rid } }) + }); + } + if (canReturnQueue) { + moreActions.push({ + type: 'action', + label: i18n.t('Return_to_waiting_line'), + icon: headerIcon('move-to-the-queue'), + onPress: handleReturnLivechat + }); + } + moreActions.push({ + type: 'action', + label: i18n.t('Close'), + icon: headerIcon('chat-close'), + destructive: true, + onPress: () => closeLivechat({ rid, departmentId, isMasterDetail, livechatRequestComment, navigation }) + }); if (!enabled) { return EMPTY_ITEMS; @@ -109,11 +105,11 @@ const useOmnichannelRightItems = (rid: string, roomStore: RoomStore, enabled: bo return [ { - type: 'button', + type: 'menu', label: i18n.t('More'), accessibilityLabel: i18n.t('More'), icon: headerIcon('kebab'), - onPress: showMoreActions + menu: { items: moreActions } } ]; }; @@ -181,38 +177,47 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): ? { value: tunread.length, style: { backgroundColor: getTunreadBadgeColor(tunreadUser ?? [], tunreadGroup ?? [], colors) } } : undefined; - const overflowOptions = [ - ...overflowKeys.map(key => { + const overflowActions: NativeStackHeaderItemMenuAction[] = [ + ...overflowKeys.map((key): NativeStackHeaderItemMenuAction => { if (key === 'threads') { - return { title: threadsAccessibilityLabel, icon: 'threads' as const, onPress: goThreadsView }; + return { type: 'action', label: threadsAccessibilityLabel, icon: headerIcon('threads'), onPress: goThreadsView }; } if (key === 'encryption') { return { - title: i18n.t('Encrypted'), - icon: 'encrypted' as const, - enabled: canToggleEncryption, + type: 'action', + label: i18n.t('Encrypted'), + icon: headerIcon('encrypted'), + disabled: !canToggleEncryption, onPress: goE2EEToggleRoomView }; } if (key === 'call') { return { - title: callAccessibilityLabel, - icon: 'phone' as const, - enabled: !isCallDisabled, + type: 'action', + label: callAccessibilityLabel, + icon: headerIcon('phone'), + disabled: isCallDisabled, onPress: onPressCall }; } if (key === 'notifications') { return { - title: i18n.t('Troubleshooting'), - icon: 'notification-disabled' as const, + type: 'action', + label: i18n.t('Troubleshooting'), + icon: headerIcon('notification-disabled'), onPress: navigateToNotificationOrPushTroubleshoot }; } throw new Error(`Unhandled room header action key: ${key}`); }), - { title: i18n.t('Search_Messages'), icon: 'search' as const, enabled: !hasE2EEWarning, onPress: goSearchView }, - { title: i18n.t('Actions'), icon: 'kebab' as const, onPress: () => goRoomActions() } + { + type: 'action', + label: i18n.t('Search_Messages'), + icon: headerIcon('search'), + disabled: hasE2EEWarning, + onPress: goSearchView + }, + { type: 'action', label: i18n.t('Actions'), icon: headerIcon('kebab'), onPress: () => goRoomActions() } ]; if (!enabled) { @@ -263,11 +268,11 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): }); } items.push({ - type: 'button', + type: 'menu', label: i18n.t('More'), accessibilityLabel: i18n.t('More'), icon: headerIcon('kebab'), - onPress: () => showActionSheetRef({ options: overflowOptions }) + menu: { items: overflowActions } }); return items; From b2ea3b25dac2a7b9c04afb750123348cccfa1215 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 17:25:42 -0300 Subject: [PATCH 067/120] fix(ios): match custom header icon size to system bar icons --- app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts | 2 +- app/lib/methods/helpers/navigation/headerIcon.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts b/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts index 1ee513ba061..35267595edc 100644 --- a/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts +++ b/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts @@ -16,6 +16,6 @@ describe('headerIcon', () => { await preloadHeaderIcons(); expect(headerIcon('kebab')).toEqual({ type: 'image', source: { uri: 'file://kebab.png', scale: 3 } }); - expect(mockGetImageSource).toHaveBeenCalledWith('kebab', 24, 'black'); + expect(mockGetImageSource).toHaveBeenCalledWith('kebab', 30, 'black'); }); }); diff --git a/app/lib/methods/helpers/navigation/headerIcon.ts b/app/lib/methods/helpers/navigation/headerIcon.ts index 473be69bc09..13fe11060bb 100644 --- a/app/lib/methods/helpers/navigation/headerIcon.ts +++ b/app/lib/methods/helpers/navigation/headerIcon.ts @@ -2,7 +2,7 @@ import type { ImageSourcePropType } from 'react-native'; import { IconSet, type TIconsName } from '~/containers/CustomIcon'; -const HEADER_ICON_SIZE = 24; +const HEADER_ICON_SIZE = 30; const HEADER_ICONS: TIconsName[] = [ 'chat-close', From 63ebbfd7853c1df2a5499c4cfd1bc9cb8196da07 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 17:53:22 -0300 Subject: [PATCH 068/120] feat(ios): open room actions from the native room title --- .../RoomView/hooks/__tests__/useHeader.test.tsx | 2 +- .../hooks/__tests__/useNativeRoomHeader.test.ts | 14 ++++++++++++++ .../__tests__/useRoomHeaderRightItems.test.ts | 13 +++++-------- app/views/RoomView/hooks/useHeader.tsx | 2 +- app/views/RoomView/hooks/useNativeRoomHeader.ts | 13 ++++++++++--- .../RoomView/hooks/useRoomHeaderRightItems.ts | 5 +---- 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index 798dff3176b..22ae2539c06 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -173,7 +173,7 @@ describe('native title availability', () => { Object.defineProperty(Platform, 'OS', { configurable: true, value: os }); Object.defineProperty(Platform, 'Version', { configurable: true, value: version }); renderHook(() => useHeader({ rid: 'rid-1', roomStore: makeRoomStore() })); - expect(useNativeRoomHeader).toHaveBeenCalledWith(native, expect.any(Object), undefined, null); + expect(useNativeRoomHeader).toHaveBeenCalledWith(native, expect.any(Object), undefined, null, expect.any(Function)); expect(mockSetOptions.mock.calls.some(([options]) => typeof options.headerTitle === 'function')).toBe(!native); }); }); diff --git a/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts index 2769943b0e3..c17ac49c706 100644 --- a/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts +++ b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts @@ -109,3 +109,17 @@ it('uses supported Omnichannel app images and falls back when decoding fails', a await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('omnichannel:offline')); getSize.mockRestore(); }); + +it('opens room actions when the title is pressed', async () => { + const onTitlePress = jest.fn(); + renderHook(() => useNativeRoomHeader(true, fields, undefined, undefined, onTitlePress)); + await waitFor(() => expect(latestOptions().onHeaderTitlePress).toBeInstanceOf(Function)); + latestOptions().onHeaderTitlePress({ nativeEvent: {} }); + expect(onTitlePress).toHaveBeenCalledWith(); +}); + +it('does not make the title pressable for invite subscriptions', async () => { + renderHook(() => useNativeRoomHeader(true, { ...fields, disabled: true }, undefined, undefined, jest.fn())); + await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('channel-public:title')); + expect(latestOptions().onHeaderTitlePress).toBeUndefined(); +}); diff --git a/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts b/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts index 87af02e9582..ac8cf10c0f6 100644 --- a/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts +++ b/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts @@ -24,8 +24,6 @@ let mockCanPlaceLivechatOnHold = false; jest.mock('~/ee/omnichannel/hooks/useCanReturnQueue', () => ({ useCanReturnQueue: () => false })); jest.mock('../useCanPlaceLivechatOnHold', () => ({ useCanPlaceLivechatOnHold: () => mockCanPlaceLivechatOnHold })); jest.mock('../useThreadFollowing', () => ({ useThreadFollowing: () => false })); -const mockGoRoomActions = jest.fn(); -jest.mock('../useGoRoomActionsView', () => ({ useGoRoomActionsView: () => mockGoRoomActions })); jest.mock('~/views/RoomView/services/closeLivechat', () => ({ closeLivechat: jest.fn() })); jest.mock('~/views/RoomView/services/placeLivechatOnHold', () => ({ placeLivechatOnHold: jest.fn() })); jest.mock('~/lib/services/restApi', () => ({ returnLivechat: jest.fn() })); @@ -64,13 +62,13 @@ describe('useRoomHeaderRightItems', () => { }; }); - it('lists search and actions in the room more menu and runs the chosen action', () => { + it('lists search in the room more menu and runs the chosen action', () => { const { result } = renderHook(() => useRoomHeaderRightItems('rid-1', undefined, roomStore)); const actions = moreMenuOf(result.current); - expect(actions.map(action => action.label)).toEqual(['Search messages', 'Actions']); - actions[1].onPress(); - expect(mockGoRoomActions).toHaveBeenCalled(); + expect(actions.map(action => action.label)).toEqual(['Search messages']); + actions[0].onPress(); + expect(mockGoSearchView).toHaveBeenCalled(); }); it('moves overflowing header actions into the menu, keeping disabled state', () => { @@ -82,8 +80,7 @@ describe('useRoomHeaderRightItems', () => { expect(actions.map(action => [action.label, !!action.disabled])).toEqual([ ['Encrypted', true], - ['Search messages', true], - ['Actions', false] + ['Search messages', true] ]); }); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index a1a87071598..688941ea978 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -69,7 +69,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa const roomUserId = useStore(roomStore, s => s.roomUserId); const goRoomActionsView = useGoRoomActionsView(roomStore); const nativeTitle = Platform.OS === 'ios' && Number.parseInt(String(Platform.Version), 10) >= 26; - useNativeRoomHeader(!!rid && nativeTitle, headerFields, tmid, roomUserId); + useNativeRoomHeader(!!rid && nativeTitle, headerFields, tmid, roomUserId, goRoomActionsView); const nativeRightItems = useRoomHeaderRightItems(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); useLayoutEffect(() => { diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts index 53727ae11bd..d34d9f9f670 100644 --- a/app/views/RoomView/hooks/useNativeRoomHeader.ts +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -122,7 +122,13 @@ const useRoomHeaderContent = ( }; }; -export const useNativeRoomHeader = (enabled: boolean, fields: IHeaderFields, tmid?: string, roomUserId?: string | null) => { +export const useNativeRoomHeader = ( + enabled: boolean, + fields: IHeaderFields, + tmid?: string, + roomUserId?: string | null, + onTitlePress?: () => void +) => { const navigation = useNavigation(); const { colors } = useTheme(); const { connected, activeUser, isDirectMessage, status, statusColor } = useRoomHeaderPresence(enabled, fields, roomUserId); @@ -155,8 +161,9 @@ export const useNativeRoomHeader = (enabled: boolean, fields: IHeaderFields, tmi headerTitleImageSource: tmid ? undefined : roomImage, headerSubtitleImageSource: subtitleImage, headerTitleStyle: { color: colors.fontTitlesLabels }, - headerSubtitleColor: colors.fontSecondaryInfo + headerSubtitleColor: colors.fontSecondaryInfo, + onHeaderTitlePress: onTitlePress && !fields.disabled ? () => onTitlePress() : undefined }); } - }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage, titleReady, colors]); + }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage, titleReady, colors, onTitlePress, fields.disabled]); }; diff --git a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts index beb266d8c88..faaa72de824 100644 --- a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts +++ b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts @@ -22,7 +22,6 @@ import { placeLivechatOnHold } from '../services/placeLivechatOnHold'; import { navigateToScreen, type TRoomStackNavigation } from '../services/navigateToScreen'; import { splitRoomHeaderActions, type TRoomHeaderActionKey } from '../helpers/roomHeaderActions'; import { useCanPlaceLivechatOnHold } from './useCanPlaceLivechatOnHold'; -import { useGoRoomActionsView } from './useGoRoomActionsView'; import { useThreadFollowing } from './useThreadFollowing'; import { useRoomRightButtonsData } from '../components/RightButtons/useRoomRightButtonsData'; import { useHeaderCallPress } from '../components/RightButtons/useHeaderCallPress'; @@ -162,7 +161,6 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): } = data; const { callPresent: callPresentRaw, isCallDisabled, onPressCall } = useHeaderCallPress(rid); const callPresent = !isSelfDm && callPresentRaw; - const goRoomActions = useGoRoomActionsView(roomStore); const { visibleKeys, overflowKeys } = splitRoomHeaderActions({ threads: threadsEnabled, @@ -216,8 +214,7 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): icon: headerIcon('search'), disabled: hasE2EEWarning, onPress: goSearchView - }, - { type: 'action', label: i18n.t('Actions'), icon: headerIcon('kebab'), onPress: () => goRoomActions() } + } ]; if (!enabled) { From 12348cb89257490d79cc2a136be264853dae268f Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 17:53:27 -0300 Subject: [PATCH 069/120] feat(ios): open workspaces from the rooms list title --- .../hooks/__tests__/useHeader.test.tsx | 13 ++++++++----- app/views/RoomsListView/hooks/useHeader.tsx | 8 +------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 81e700f0d24..566fba2accc 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -149,17 +149,20 @@ describe('RoomsListView useHeader', () => { expect(rightItems.every((item: { type: string }) => item.type === 'button')).toBe(true); }); - it('exposes the drawer and server switcher as native left items', () => { + it('exposes only the drawer as a native left item', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; const leftItems = options.unstable_headerLeftItems(); - expect(leftItems).toHaveLength(2); + expect(leftItems).toHaveLength(1); expect(leftItems[0].icon).toEqual({ type: 'image', source: { uri: 'hamburguer' } }); - expect(leftItems[1].icon).toEqual({ type: 'image', source: { uri: 'workspaces' } }); - expect(leftItems[1].label).toBe('Rocket.Chat'); + }); + + it('opens the server switcher when the header title is pressed', () => { + renderUseHeader(); - leftItems[1].onPress(); + const options = mockSetOptions.mock.calls[0][0]; + options.onHeaderTitlePress(); expect(mockShowActionSheetRef).toHaveBeenCalledWith(expect.objectContaining({ enableContentPanningGesture: false })); }); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index dad8c823798..4b5a7e11a19 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -198,6 +198,7 @@ export const useHeader = () => { headerTransparent: true, headerTitle: serverName, headerSubtitle: nativeHeaderSubtitle, + onHeaderTitlePress: () => showActionSheetRef({ children: , enableContentPanningGesture: false }), headerSearchBarOptions: { ref: searchBarRef, placement: 'automatic', @@ -217,13 +218,6 @@ export const useHeader = () => { onPress: isMasterDetail ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) : () => navigation.toggleDrawer() - }, - { - type: 'button', - label: serverName, - accessibilityLabel: `${serverName} ${nativeHeaderSubtitle}`, - icon: headerIcon('workspaces'), - onPress: () => showActionSheetRef({ children: , enableContentPanningGesture: false }) } ], unstable_headerRightItems: () => [ From a7331d38de7a53162a6288456e265cd1319a06ca Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 18:10:52 -0300 Subject: [PATCH 070/120] chore(ios): patch screens and native-stack for title press and icon labels --- ...react-navigation+native-stack+7.19.2.patch | 73 +++- patches/react-native-screens+4.28.0.patch | 317 ++++++++++++++++-- 2 files changed, 350 insertions(+), 40 deletions(-) diff --git a/patches/@react-navigation+native-stack+7.19.2.patch b/patches/@react-navigation+native-stack+7.19.2.patch index 4406c0ccc77..204588af899 100644 --- a/patches/@react-navigation+native-stack+7.19.2.patch +++ b/patches/@react-navigation+native-stack+7.19.2.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js -index a56cb12..5be7312 100644 +index a56cb12..6d4b38e 100644 --- a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js +++ b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js -@@ -149,7 +149,11 @@ export function useHeaderConfigProps({ +@@ -149,7 +149,12 @@ export function useHeaderConfigProps({ headerStyle, headerBlurEffect, headerTintColor, @@ -11,10 +11,11 @@ index a56cb12..5be7312 100644 + headerSubtitleColor, headerTitle, + headerTitleImageSource, ++ onHeaderTitlePress, headerTitleAlign, headerTitleStyle, headerTransparent, -@@ -230,6 +234,14 @@ export function useHeaderConfigProps({ +@@ -230,6 +235,15 @@ export function useHeaderConfigProps({ tintColor, children: titleText }) : null; @@ -26,10 +27,11 @@ index a56cb12..5be7312 100644 + const subtitleText = headerTitleElement != null ? undefined : headerSubtitle; + const titleImageSource = headerTitleElement != null ? undefined : headerTitleImageSource; + const subtitleImageSource = headerTitleElement != null ? undefined : headerSubtitleImageSource; ++ const titlePress = headerTitleElement != null ? undefined : onHeaderTitlePress; const supportsHeaderSearchBar = typeof isSearchBarAvailableForCurrentPlatform === 'boolean' ? isSearchBarAvailableForCurrentPlatform : // Fallback for older versions of react-native-screens Platform.OS === 'ios' && SearchBar != null; -@@ -356,7 +368,11 @@ export function useHeaderConfigProps({ +@@ -356,7 +370,13 @@ export function useHeaderConfigProps({ largeTitleFontSize, largeTitleFontWeight, largeTitleHideShadow: headerLargeTitleShadowVisible === false, @@ -38,14 +40,16 @@ index a56cb12..5be7312 100644 + subtitleColor: headerSubtitleColor, title: titleText, + titleImageSource, ++ titlePressEnabled: titlePress != null, ++ onTitlePress: titlePress, titleColor, titleFontFamily, titleFontSize, diff --git a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts -index c32d1da..2ef28f2 100644 +index c32d1da..7109bb9 100644 --- a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts +++ b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts -@@ -321,6 +321,52 @@ export type NativeStackNavigationOptions = { +@@ -321,6 +321,64 @@ export type NativeStackNavigationOptions = { */ tintColor?: string; }) => React.ReactNode); @@ -95,14 +99,34 @@ index c32d1da..2ef28f2 100644 + * @supported iOS 26 or higher + */ + headerSubtitleColor?: ColorValue; ++ /** ++ * Called when the native header title or subtitle is tapped. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ onHeaderTitlePress?: () => void; /** * How to align the the header title. * Defaults to `left` on platforms other than iOS. +@@ -937,6 +995,7 @@ export type NativeStackHeaderItemButton = SharedHeaderItem & { + * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/isselected + */ + selected?: boolean; ++ showsLabelWithIcon?: boolean; + }; + /** + * An action item in a menu. diff --git a/node_modules/@react-navigation/native-stack/src/types.tsx b/node_modules/@react-navigation/native-stack/src/types.tsx -index aeb4939..2d89ae8 100644 +index aeb4939..7016cb9 100644 --- a/node_modules/@react-navigation/native-stack/src/types.tsx +++ b/node_modules/@react-navigation/native-stack/src/types.tsx -@@ -371,6 +371,52 @@ export type NativeStackNavigationOptions = { +@@ -371,6 +371,64 @@ export type NativeStackNavigationOptions = { */ tintColor?: string; }) => React.ReactNode); @@ -152,14 +176,34 @@ index aeb4939..2d89ae8 100644 + * @supported iOS 26 or higher + */ + headerSubtitleColor?: ColorValue; ++ /** ++ * Called when the native header title or subtitle is tapped. ++ * ++ * Ignored when `headerTitle` renders a custom element. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ onHeaderTitlePress?: () => void; /** * How to align the the header title. * Defaults to `left` on platforms other than iOS. +@@ -994,6 +1052,7 @@ export type NativeStackHeaderItemButton = SharedHeaderItem & { + * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/isselected + */ + selected?: boolean; ++ showsLabelWithIcon?: boolean; + }; + + /** diff --git a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx -index a7e3004..af41403 100644 +index a7e3004..c57096f 100644 --- a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx +++ b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx -@@ -210,7 +210,11 @@ export function useHeaderConfigProps({ +@@ -210,7 +210,12 @@ export function useHeaderConfigProps({ headerStyle, headerBlurEffect, headerTintColor, @@ -168,10 +212,11 @@ index a7e3004..af41403 100644 + headerSubtitleColor, headerTitle, + headerTitleImageSource, ++ onHeaderTitlePress, headerTitleAlign, headerTitleStyle, headerTransparent, -@@ -330,6 +334,20 @@ export function useHeaderConfigProps({ +@@ -330,6 +335,22 @@ export function useHeaderConfigProps({ }) : null; @@ -188,11 +233,13 @@ index a7e3004..af41403 100644 + headerTitleElement != null ? undefined : headerTitleImageSource; + const subtitleImageSource = + headerTitleElement != null ? undefined : headerSubtitleImageSource; ++ const titlePress = ++ headerTitleElement != null ? undefined : onHeaderTitlePress; + const supportsHeaderSearchBar = typeof isSearchBarAvailableForCurrentPlatform === 'boolean' ? isSearchBarAvailableForCurrentPlatform -@@ -522,7 +540,11 @@ export function useHeaderConfigProps({ +@@ -522,7 +543,13 @@ export function useHeaderConfigProps({ largeTitleFontSize, largeTitleFontWeight, largeTitleHideShadow: headerLargeTitleShadowVisible === false, @@ -201,6 +248,8 @@ index a7e3004..af41403 100644 + subtitleColor: headerSubtitleColor, title: titleText, + titleImageSource, ++ titlePressEnabled: titlePress != null, ++ onTitlePress: titlePress, titleColor, titleFontFamily, titleFontSize, diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch index 1fe001196ed..fb0a4446a97 100644 --- a/patches/react-native-screens+4.28.0.patch +++ b/patches/react-native-screens+4.28.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt -index fa1b773..f54b225 100644 +index fa1b773..41866aa 100644 --- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt @@ -3,6 +3,7 @@ package com.swmansion.rnscreens @@ -10,7 +10,7 @@ index fa1b773..f54b225 100644 import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.StateWrapper -@@ -236,6 +237,26 @@ class ScreenStackHeaderConfigViewManager : +@@ -236,6 +237,31 @@ class ScreenStackHeaderConfigViewManager : value: String?, ) = Unit @@ -33,6 +33,11 @@ index fa1b773..f54b225 100644 + view: ScreenStackHeaderConfig?, + value: Int?, + ) = Unit ++ ++ override fun setTitlePressEnabled( ++ view: ScreenStackHeaderConfig?, ++ value: Boolean, ++ ) = Unit + override fun setBackTitleFontFamily( view: ScreenStackHeaderConfig?, @@ -57,8 +62,41 @@ index 64beb49..87936ea 100644 [imageLoader loadImageWithURLRequest:imageSource.request size:imageSource.size scale:imageSource.scale +diff --git a/node_modules/react-native-screens/ios/RNSBarButtonItem.mm b/node_modules/react-native-screens/ios/RNSBarButtonItem.mm +index 76a39d4..a545974 100644 +--- a/node_modules/react-native-screens/ios/RNSBarButtonItem.mm ++++ b/node_modules/react-native-screens/ios/RNSBarButtonItem.mm +@@ -126,10 +126,28 @@ static UIMenuOptions RNSMakeUIMenuOptionsFromConfig(NSDictionary *config); + self.action = @selector(handleBarButtonItemPress:); + _itemAction = action; + _buttonId = buttonId; ++ if ([dict[@"showsLabelWithIcon"] boolValue] && self.image != nil && self.title.length > 0) { ++ [self useImageAndTitleButton]; ++ } + } + return self; + } + ++- (void)useImageAndTitleButton ++{ ++ UIButtonConfiguration *configuration = [UIButtonConfiguration plainButtonConfiguration]; ++ configuration.image = self.image; ++ configuration.title = self.title; ++ configuration.imagePadding = 6; ++ configuration.preferredSymbolConfigurationForImage = ++ [UIImageSymbolConfiguration configurationWithTextStyle:UIFontTextStyleBody scale:UIImageSymbolScaleLarge]; ++ UIButton *button = [UIButton buttonWithConfiguration:configuration primaryAction:nil]; ++ [button addTarget:self action:@selector(handleBarButtonItemPress:) forControlEvents:UIControlEventPrimaryActionTriggered]; ++ button.accessibilityLabel = self.accessibilityLabel; ++ [button setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; ++ self.customView = button; ++} ++ + + (void)resolveImageFromConfig:(NSDictionary *)dict + imageLoader:(RCTImageLoader *)imageLoader + completionBlock:(void (^)(UIImage *_Nullable))completionBlock diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h -index 8a41697..25e6586 100644 +index 8a41697..c6f3c20 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h +++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h @@ -8,10 +8,14 @@ @@ -76,7 +114,7 @@ index 8a41697..25e6586 100644 @end @interface RNSScreenStackHeaderConfig : RNSReactBaseView -@@ -23,10 +27,14 @@ +@@ -23,10 +27,15 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, retain) NSString *title; @@ -88,14 +126,21 @@ index 8a41697..25e6586 100644 +@property (nonatomic, retain, nullable) UIColor *subtitleColor; +@property (nonatomic, retain, nullable) RCTImageSource *titleImageSource; +@property (nonatomic, retain, nullable) RCTImageSource *subtitleImageSource; ++@property (nonatomic) BOOL titlePressEnabled; @property (nonatomic, retain) NSString *backTitle; @property (nonatomic, retain) NSString *backTitleFontFamily; @property (nonatomic, retain) NSNumber *backTitleFontSize; diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm -index a54cb63..98212de 100644 +index a54cb63..592a6c2 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm -@@ -13,6 +13,7 @@ +@@ -1,4 +1,5 @@ + #import "RNSScreenStackHeaderConfig.h" ++#import + #import + #import + #import +@@ -13,6 +14,7 @@ #import #import #import @@ -103,7 +148,7 @@ index a54cb63..98212de 100644 #import #import #import "RCTImageComponentView+RNSScreenStackHeaderConfig.h" -@@ -20,6 +21,7 @@ +@@ -20,6 +22,7 @@ #import "RNSBarButtonItem.h" #import "RNSConvert.h" #import "RNSDefines.h" @@ -111,7 +156,7 @@ index a54cb63..98212de 100644 #import "RNSScreen.h" #import "RNSSearchBar.h" #import "UINavigationBar+RNSUtility.h" -@@ -28,6 +30,10 @@ namespace react = facebook::react; +@@ -28,11 +31,172 @@ namespace react = facebook::react; static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17; static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; @@ -122,7 +167,169 @@ index a54cb63..98212de 100644 @interface RCTImageLoader (Private) - (id)imageCache; -@@ -43,6 +49,105 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; + @end + ++@interface RNSScreenStackHeaderConfig () ++- (void)emitTitlePress; ++@end ++ ++@interface RNSHeaderTitlePressHandler : NSObject ++@property (nonatomic, weak) UINavigationController *navigationController; ++@property (nonatomic, copy) NSArray *highlightedViews; ++@property (nonatomic) CGRect pressFrame; +++ (void)installOnNavigationController:(UINavigationController *)navigationController; ++@end ++ ++@implementation RNSHeaderTitlePressHandler ++ ++static const void *RNSHeaderTitlePressHandlerKey = &RNSHeaderTitlePressHandlerKey; ++static const CGFloat RNSHeaderTitlePressSlop = 8; ++static const CGFloat RNSHeaderTitlePressHighlightAlpha = 0.35; ++ +++ (void)installOnNavigationController:(UINavigationController *)navigationController ++{ ++ UINavigationBar *bar = navigationController.navigationBar; ++ RNSHeaderTitlePressHandler *handler = objc_getAssociatedObject(bar, RNSHeaderTitlePressHandlerKey); ++ if (handler != nil) { ++ handler.navigationController = navigationController; ++ return; ++ } ++ handler = [RNSHeaderTitlePressHandler new]; ++ handler.navigationController = navigationController; ++ UILongPressGestureRecognizer *recognizer = ++ [[UILongPressGestureRecognizer alloc] initWithTarget:handler action:@selector(handlePress:)]; ++ recognizer.minimumPressDuration = 0; ++ recognizer.allowableMovement = CGFLOAT_MAX; ++ recognizer.cancelsTouchesInView = NO; ++ recognizer.delaysTouchesEnded = NO; ++ recognizer.delegate = handler; ++ [navigationController.view addGestureRecognizer:recognizer]; ++ objc_setAssociatedObject(bar, RNSHeaderTitlePressHandlerKey, handler, OBJC_ASSOCIATION_RETAIN_NONATOMIC); ++} ++ ++- (nullable RNSScreenStackHeaderConfig *)topConfig ++{ ++ UIViewController *top = self.navigationController.topViewController; ++ if (![top isKindOfClass:[RNSScreen class]]) { ++ return nil; ++ } ++ return [((RNSScreen *)top).screenView findHeaderConfig]; ++} ++ ++- (NSArray *)titleViewsInBar:(UINavigationBar *)bar forConfig:(RNSScreenStackHeaderConfig *)config ++{ ++ NSMutableArray *titleViews = [NSMutableArray new]; ++ NSMutableArray *pending = [NSMutableArray arrayWithArray:bar.subviews]; ++ UIView *topView = self.navigationController.topViewController.view; ++ if (topView != nil) { ++ [pending addObject:topView]; ++ } ++ while (pending.count > 0) { ++ UIView *view = pending.lastObject; ++ [pending removeLastObject]; ++ if (view.isHidden || view.alpha <= 0 || !CGRectIntersectsRect([view convertRect:view.bounds toView:bar], bar.bounds)) { ++ continue; ++ } ++ if ([view isKindOfClass:[UILabel class]]) { ++ NSString *text = ((UILabel *)view).attributedText.string ?: ((UILabel *)view).text; ++ BOOL isTitle = config.title.length > 0 && [text containsString:config.title]; ++ BOOL isSubtitle = config.subtitle.length > 0 && [text containsString:config.subtitle]; ++ if (isTitle || isSubtitle) { ++ [titleViews addObject:view]; ++ for (UIView *sibling in view.superview.subviews) { ++ if ([sibling isKindOfClass:[UIImageView class]] && !sibling.isHidden && ![titleViews containsObject:sibling]) { ++ [titleViews addObject:sibling]; ++ } ++ } ++ } ++ continue; ++ } ++ [pending addObjectsFromArray:view.subviews]; ++ } ++ return titleViews; ++} ++ ++- (CGRect)frameOfViews:(NSArray *)views inBar:(UINavigationBar *)bar ++{ ++ CGRect frame = CGRectNull; ++ for (UIView *view in views) { ++ frame = CGRectUnion(frame, [view convertRect:view.bounds toView:bar]); ++ } ++ return CGRectIsNull(frame) ? frame : CGRectInset(frame, -RNSHeaderTitlePressSlop, -RNSHeaderTitlePressSlop); ++} ++ ++- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch ++{ ++ RNSScreenStackHeaderConfig *config = [self topConfig]; ++ if (config == nil || !config.titlePressEnabled) { ++ return NO; ++ } ++ UINavigationBar *bar = self.navigationController.navigationBar; ++ if (!CGRectContainsPoint(bar.bounds, [touch locationInView:bar])) { ++ return NO; ++ } ++ NSArray *titleViews = [self titleViewsInBar:bar forConfig:config]; ++ CGRect titleFrame = [self frameOfViews:titleViews inBar:bar]; ++ if (CGRectIsNull(titleFrame) || !CGRectContainsPoint(titleFrame, [touch locationInView:bar])) { ++ return NO; ++ } ++ self.highlightedViews = titleViews; ++ self.pressFrame = titleFrame; ++ return YES; ++} ++ ++- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer ++ shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer ++{ ++ return YES; ++} ++ ++- (void)setHighlighted:(BOOL)highlighted ++{ ++ CGFloat alpha = highlighted ? RNSHeaderTitlePressHighlightAlpha : 1; ++ NSArray *views = self.highlightedViews; ++ [UIView animateWithDuration:highlighted ? 0.1 : 0.25 ++ delay:0 ++ options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction ++ animations:^{ ++ for (UIView *view in views) { ++ view.alpha = alpha; ++ } ++ } ++ completion:nil]; ++} ++ ++- (void)handlePress:(UILongPressGestureRecognizer *)recognizer ++{ ++ BOOL isInside = CGRectContainsPoint(self.pressFrame, [recognizer locationInView:self.navigationController.navigationBar]); ++ switch (recognizer.state) { ++ case UIGestureRecognizerStateBegan: ++ case UIGestureRecognizerStateChanged: ++ [self setHighlighted:isInside]; ++ break; ++ case UIGestureRecognizerStateEnded: { ++ [self setHighlighted:NO]; ++ RNSScreenStackHeaderConfig *config = [self topConfig]; ++ if (isInside && config.titlePressEnabled) { ++ [config emitTitlePress]; ++ } ++ break; ++ } ++ case UIGestureRecognizerStateCancelled: ++ case UIGestureRecognizerStateFailed: ++ [self setHighlighted:NO]; ++ break; ++ default: ++ break; ++ } ++} ++ ++@end ++ + @implementation NSString (RNSStringUtil) + + + (BOOL)rnscreens_isBlankOrNull:(NSString *)string +@@ -43,6 +207,105 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; return [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0; } @@ -228,7 +435,7 @@ index a54cb63..98212de 100644 @end @interface RNSScreenStackHeaderConfig () -@@ -59,6 +164,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; +@@ -59,6 +322,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; /// transaction via RCTMountingTransactionObserving protocol. bool _addedReactSubviewsInCurrentTransaction; RCTImageLoader *_imageLoader; @@ -237,7 +444,7 @@ index a54cb63..98212de 100644 } - (instancetype)initWithFrame:(CGRect)frame -@@ -69,6 +176,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; +@@ -69,6 +334,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; _show = YES; _translucent = NO; _addedReactSubviewsInCurrentTransaction = false; @@ -246,7 +453,7 @@ index a54cb63..98212de 100644 _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}, react::Point{}); [self initProps]; } -@@ -131,6 +240,56 @@ RNS_IGNORE_SUPER_CALL_END +@@ -131,6 +398,56 @@ RNS_IGNORE_SUPER_CALL_END return nil; } @@ -303,7 +510,7 @@ index a54cb63..98212de 100644 - (void)updateViewControllerIfNeeded { UIViewController *vc = _screenView.controller; -@@ -181,10 +340,10 @@ RNS_IGNORE_SUPER_CALL_END +@@ -181,10 +498,10 @@ RNS_IGNORE_SUPER_CALL_END if (newState != _lastSendState) { _lastSendState = newState; @@ -318,7 +525,7 @@ index a54cb63..98212de 100644 } } -@@ -293,10 +452,11 @@ RNS_IGNORE_SUPER_CALL_END +@@ -293,10 +610,11 @@ RNS_IGNORE_SUPER_CALL_END // in the image attribute not being updated. We manually set frame to the size of an image // in order to trigger proper reload that'd update the image attribute. RCTImageSource *imageSource = [RNSScreenStackHeaderConfig imageSourceFromImageView:imageView]; @@ -334,7 +541,7 @@ index a54cb63..98212de 100644 } UIImage *image = imageView.image; -@@ -365,6 +525,81 @@ RNS_IGNORE_SUPER_CALL_END +@@ -365,6 +683,81 @@ RNS_IGNORE_SUPER_CALL_END } } @@ -416,7 +623,7 @@ index a54cb63..98212de 100644 + (UINavigationBarAppearance *)buildAppearance:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config { UINavigationBarAppearance *appearance = [UINavigationBarAppearance new]; -@@ -380,8 +615,10 @@ RNS_IGNORE_SUPER_CALL_END +@@ -380,8 +773,10 @@ RNS_IGNORE_SUPER_CALL_END appearance.shadowColor = shadowColor; appearance.shadowImage = shadowImage; } @@ -428,7 +635,7 @@ index a54cb63..98212de 100644 } // set background color if specified -@@ -408,59 +645,20 @@ RNS_IGNORE_SUPER_CALL_END +@@ -408,59 +803,20 @@ RNS_IGNORE_SUPER_CALL_END } if (config.titleFontFamily || config.titleFontSize || config.titleFontWeight || config.titleColor) { @@ -497,7 +704,7 @@ index a54cb63..98212de 100644 } UIImage *backButtonImage = [config loadBackButtonImageInViewController:vc]; -@@ -507,6 +705,11 @@ RNS_IGNORE_SUPER_CALL_END +@@ -507,6 +863,11 @@ RNS_IGNORE_SUPER_CALL_END if (shouldHide) { navitem.title = config.title; @@ -509,7 +716,19 @@ index a54cb63..98212de 100644 // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items. [navctr setNavigationBarHidden:YES animated:animated]; -@@ -648,6 +851,21 @@ RNS_IGNORE_SUPER_CALL_END +@@ -514,6 +875,11 @@ RNS_IGNORE_SUPER_CALL_END + } + + navctr.navigationBar.overrideUserInterfaceStyle = config.userInterfaceStyle; ++#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) ++ if (@available(iOS 26.0, *)) { ++ [RNSHeaderTitlePressHandler installOnNavigationController:navctr]; ++ } ++#endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) + + #if !TARGET_OS_TV + [config configureBackItem:prevItem withPrevVC:prevVC]; +@@ -648,6 +1014,21 @@ RNS_IGNORE_SUPER_CALL_END // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug). // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments) navitem.title = config.title; @@ -531,7 +750,7 @@ index a54cb63..98212de 100644 navitem.leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems withCurrentItems:navitem.leftBarButtonItems]; navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems -@@ -700,7 +918,8 @@ RNS_IGNORE_SUPER_CALL_END +@@ -700,7 +1081,8 @@ RNS_IGNORE_SUPER_CALL_END const auto *config = self; const auto isBackTitleBlank = [NSString rnscreens_isBlankOrNull:config.backTitle] == YES; @@ -541,7 +760,7 @@ index a54cb63..98212de 100644 // If previous screen controller was recreated (e.g. when you go back to tab with stack that has multiple screens), // its navigationItem may not have any information from screen's headerConfig, including the title. -@@ -858,12 +1077,13 @@ RNS_IGNORE_SUPER_CALL_END +@@ -858,12 +1240,13 @@ RNS_IGNORE_SUPER_CALL_END return; } @@ -561,7 +780,7 @@ index a54cb63..98212de 100644 // [_reactSubviews insertObject:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; [self insertReactSubview:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; -@@ -987,6 +1207,8 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -987,6 +1370,8 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) { [super prepareForRecycle]; _initialPropsSet = NO; @@ -570,7 +789,22 @@ index a54cb63..98212de 100644 _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}, react::Point{}); } -@@ -1025,6 +1247,7 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -1003,6 +1388,14 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) + return react::concreteComponentDescriptorProvider(); + } + ++- (void)emitTitlePress ++{ ++ auto eventEmitter = std::static_pointer_cast(_eventEmitter); ++ if (eventEmitter) { ++ eventEmitter->onTitlePress(facebook::react::RNSScreenStackHeaderConfigEventEmitter::OnTitlePress{}); ++ } ++} ++ + - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::Shared const &)oldProps + { + const auto &oldScreenProps = *std::static_pointer_cast(_props); +@@ -1025,6 +1418,7 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) } _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title); @@ -578,11 +812,12 @@ index a54cb63..98212de 100644 if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) { _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily); } -@@ -1064,6 +1287,25 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -1064,6 +1458,26 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) // We cannot compare SharedColor because it is shared value. // We could compare color value, but it is more performant to just assign new value _titleColor = RCTUIColorFromSharedColor(newScreenProps.titleColor); + _subtitleColor = RCTUIColorFromSharedColor(newScreenProps.subtitleColor); ++ _titlePressEnabled = newScreenProps.titlePressEnabled; + if (newScreenProps.titleImageSource != oldScreenProps.titleImageSource) { + const auto &titleImageSource = newScreenProps.titleImageSource; + _titleImageSource = titleImageSource.uri.empty() @@ -605,7 +840,7 @@ index a54cb63..98212de 100644 _color = RCTUIColorFromSharedColor(newScreenProps.color); _backgroundColor = RCTUIColorFromSharedColor(newScreenProps.backgroundColor); diff --git a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts -index da28c51..e6400df 100644 +index da28c51..0803e9e 100644 --- a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts +++ b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts @@ -4,6 +4,7 @@ import { codegenNativeComponent } from 'react-native'; @@ -616,7 +851,15 @@ index da28c51..e6400df 100644 ViewProps, ColorValue, } from 'react-native'; -@@ -66,10 +67,14 @@ export interface NativeProps extends ViewProps { +@@ -12,6 +13,7 @@ type DirectionType = 'rtl' | 'ltr'; + + // eslint-disable-next-line @typescript-eslint/ban-types + type OnAttachedEvent = Readonly<{}>; ++type OnTitlePressEvent = Readonly<{}>; + // eslint-disable-next-line @typescript-eslint/ban-types + type OnDetachedEvent = Readonly<{}>; + +@@ -66,10 +68,16 @@ export interface NativeProps extends ViewProps { largeTitleColor?: ColorValue | undefined; translucent?: boolean | undefined; title?: string | undefined; @@ -628,14 +871,16 @@ index da28c51..e6400df 100644 + subtitleColor?: ColorValue | undefined; + titleImageSource?: ImageSource | undefined; + subtitleImageSource?: ImageSource | undefined; ++ titlePressEnabled?: boolean | undefined; ++ onTitlePress?: CT.DirectEventHandler | undefined; disableBackButtonMenu?: boolean | undefined; backButtonDisplayMode?: CT.WithDefault; hideBackButton?: boolean | undefined; diff --git a/node_modules/react-native-screens/src/types.tsx b/node_modules/react-native-screens/src/types.tsx -index 8594b73..299a098 100644 +index 8594b73..5afaf41 100644 --- a/node_modules/react-native-screens/src/types.tsx +++ b/node_modules/react-native-screens/src/types.tsx -@@ -827,10 +827,49 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { +@@ -827,10 +827,65 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { * String that can be displayed in the header as a fallback for `headerTitle`. */ title?: string | undefined; @@ -682,6 +927,22 @@ index 8594b73..299a098 100644 + * @supported iOS 26 or higher + */ + subtitleColor?: ColorValue | undefined; ++ /** ++ * Whether tapping the native header title calls `onTitlePress`. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ titlePressEnabled?: boolean | undefined; ++ /** ++ * Called when the native header title or subtitle is tapped and `titlePressEnabled` is true. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ onTitlePress?: (() => void) | undefined; /** * Customize font family to be used for the title. */ From e30bf6cb4f56ca84aaf4bc6760c1552d092c4d05 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 18:10:55 -0300 Subject: [PATCH 071/120] feat(ios): use the native back button in the room header --- .../hooks/__tests__/useHeader.test.tsx | 1 + .../__tests__/useNativeBackButton.test.ts | 55 +++++++++++++++++++ app/views/RoomView/hooks/useHeader.tsx | 2 + .../RoomView/hooks/useNativeBackButton.ts | 34 ++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts create mode 100644 app/views/RoomView/hooks/useNativeBackButton.ts diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index 22ae2539c06..42b16bba523 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -8,6 +8,7 @@ import { useHeader } from '../useHeader'; import { useNativeRoomHeader } from '../useNativeRoomHeader'; jest.mock('../useNativeRoomHeader', () => ({ useNativeRoomHeader: jest.fn() })); +jest.mock('../useNativeBackButton', () => ({ useNativeBackButton: jest.fn() })); const mockNativeRightItems: unknown[] = []; jest.mock('../useRoomHeaderRightItems', () => ({ useRoomHeaderRightItems: jest.fn(() => mockNativeRightItems) })); diff --git a/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts b/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts new file mode 100644 index 00000000000..f956bd44e5b --- /dev/null +++ b/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts @@ -0,0 +1,55 @@ +import { renderHook } from '@testing-library/react-native'; + +import { useNativeBackButton } from '../useNativeBackButton'; + +const mockSetOptions = jest.fn(); +const mockGoBack = jest.fn(); +let mockUnreads: number | null = null; +const mockUseUnreadsCount = jest.fn((_rid?: string) => mockUnreads); + +jest.mock('@react-navigation/native', () => ({ useNavigation: () => ({ setOptions: mockSetOptions, goBack: mockGoBack }) })); +jest.mock('../useUnreadsCount', () => ({ useUnreadsCount: (rid?: string) => mockUseUnreadsCount(rid) })); + +const renderBackItem = (unreads: number | null) => { + mockUnreads = unreads; + renderHook(() => useNativeBackButton(true, 'rid')); + const options = mockSetOptions.mock.lastCall[0]; + expect(options.headerBackVisible).toBe(false); + const [backItem] = options.unstable_headerLeftItems(); + return backItem; +}; + +beforeEach(() => { + jest.clearAllMocks(); + mockUnreads = null; +}); + +it('replaces the back button with a chevron labelled with the unread count', () => { + const backItem = renderBackItem(7); + expect(mockUseUnreadsCount).toHaveBeenCalledWith('rid'); + expect(backItem).toMatchObject({ + label: '7', + icon: { type: 'sfSymbol', name: 'chevron.backward' }, + showsLabelWithIcon: true + }); +}); + +it('caps the count at +99', () => { + expect(renderBackItem(150).label).toBe('+99'); +}); + +it('shows only the chevron without unreads', () => { + expect(renderBackItem(0).label).toBe(''); +}); + +it('goes back when pressed', () => { + renderBackItem(3).onPress(); + expect(mockGoBack).toHaveBeenCalledTimes(1); +}); + +it('does nothing when disabled', () => { + mockUnreads = 3; + renderHook(() => useNativeBackButton(false, 'rid')); + expect(mockUseUnreadsCount).toHaveBeenCalledWith(undefined); + expect(mockSetOptions).not.toHaveBeenCalled(); +}); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index 688941ea978..bffa1fb6e84 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -15,6 +15,7 @@ import { type RoomStore } from '../definitions'; import { fromSubscription } from '../stores/RoomStoreContext'; import { useGoRoomActionsView } from './useGoRoomActionsView'; import { useNativeRoomHeader } from './useNativeRoomHeader'; +import { useNativeBackButton } from './useNativeBackButton'; import { useRoomHeaderRightItems } from './useRoomHeaderRightItems'; interface IUseHeaderParams { @@ -70,6 +71,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa const goRoomActionsView = useGoRoomActionsView(roomStore); const nativeTitle = Platform.OS === 'ios' && Number.parseInt(String(Platform.Version), 10) >= 26; useNativeRoomHeader(!!rid && nativeTitle, headerFields, tmid, roomUserId, goRoomActionsView); + useNativeBackButton(!!rid && nativeTitle, rid); const nativeRightItems = useRoomHeaderRightItems(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); useLayoutEffect(() => { diff --git a/app/views/RoomView/hooks/useNativeBackButton.ts b/app/views/RoomView/hooks/useNativeBackButton.ts new file mode 100644 index 00000000000..a16e78e06dc --- /dev/null +++ b/app/views/RoomView/hooks/useNativeBackButton.ts @@ -0,0 +1,34 @@ +import { useLayoutEffect } from 'react'; +import { useNavigation } from '@react-navigation/native'; +import { type NativeStackHeaderItem } from '@react-navigation/native-stack'; + +import i18n from '~/i18n'; +import { type IRoomViewProps } from '../definitions'; +import { useUnreadsCount } from './useUnreadsCount'; + +export const formatUnreadsCount = (unreadsCount: number | null) => { + if (!unreadsCount) { + return ''; + } + return unreadsCount > 99 ? '+99' : unreadsCount.toString(); +}; + +export const useNativeBackButton = (enabled: boolean, rid?: string) => { + const navigation = useNavigation(); + const unreadsLabel = formatUnreadsCount(useUnreadsCount(enabled ? rid : undefined)); + + useLayoutEffect(() => { + if (!enabled) { + return; + } + const backItem: NativeStackHeaderItem = { + type: 'button', + label: unreadsLabel, + icon: { type: 'sfSymbol', name: 'chevron.backward' }, + showsLabelWithIcon: true, + accessibilityLabel: unreadsLabel ? `${i18n.t('Back')}, ${unreadsLabel} ${i18n.t('Unread')}` : i18n.t('Back'), + onPress: () => navigation.goBack() + }; + navigation.setOptions({ headerBackVisible: false, unstable_headerLeftItems: () => [backItem] }); + }, [enabled, navigation, unreadsLabel]); +}; From 15b785cf1363a3c6145c0ee17f32d464943b8ce7 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 18:22:53 -0300 Subject: [PATCH 072/120] fix(ios): match header icon spacing to system bar items --- .../navigation/__tests__/headerIcon.test.ts | 2 +- .../methods/helpers/navigation/headerIcon.ts | 7 ++++--- patches/react-native-screens+4.28.0.patch | 20 +++++++++++++++++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts b/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts index 35267595edc..03ef4d0ffd4 100644 --- a/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts +++ b/app/lib/methods/helpers/navigation/__tests__/headerIcon.test.ts @@ -15,7 +15,7 @@ describe('headerIcon', () => { await preloadHeaderIcons(); - expect(headerIcon('kebab')).toEqual({ type: 'image', source: { uri: 'file://kebab.png', scale: 3 } }); + expect(headerIcon('kebab')).toEqual({ type: 'image', source: { uri: 'file://kebab.png', scale: 3, alignmentInset: 2.5 } }); expect(mockGetImageSource).toHaveBeenCalledWith('kebab', 30, 'black'); }); }); diff --git a/app/lib/methods/helpers/navigation/headerIcon.ts b/app/lib/methods/helpers/navigation/headerIcon.ts index 13fe11060bb..793e38c6ea6 100644 --- a/app/lib/methods/helpers/navigation/headerIcon.ts +++ b/app/lib/methods/helpers/navigation/headerIcon.ts @@ -1,8 +1,9 @@ -import type { ImageSourcePropType } from 'react-native'; +import type { ImageURISource } from 'react-native'; import { IconSet, type TIconsName } from '~/containers/CustomIcon'; const HEADER_ICON_SIZE = 30; +const HEADER_ICON_GLYPH_PADDING = HEADER_ICON_SIZE / 12; const HEADER_ICONS: TIconsName[] = [ 'chat-close', @@ -23,14 +24,14 @@ const HEADER_ICONS: TIconsName[] = [ 'workspaces' ]; -const headerIconSources = new Map(); +const headerIconSources = new Map(); export const preloadHeaderIcons = async () => { await Promise.all( HEADER_ICONS.map(async name => { const source = await IconSet.getImageSource(name, HEADER_ICON_SIZE, 'black'); if (source) { - headerIconSources.set(name, source); + headerIconSources.set(name, { ...source, alignmentInset: HEADER_ICON_GLYPH_PADDING }); } }) ); diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch index fb0a4446a97..be3942289ef 100644 --- a/patches/react-native-screens+4.28.0.patch +++ b/patches/react-native-screens+4.28.0.patch @@ -43,10 +43,26 @@ index fa1b773..41866aa 100644 view: ScreenStackHeaderConfig?, value: String?, diff --git a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm -index 64beb49..87936ea 100644 +index 64beb49..14bb54a 100644 --- a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm +++ b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm -@@ -38,6 +38,15 @@ +@@ -13,6 +13,15 @@ + RCTImageSource *imageSource = [RCTConvert RCTImageSource:jsonImageSource]; + RCTAssert(imageSource != nil, @"[RNScreens] Expected nonnil image source"); + ++ NSNumber *alignmentInset = jsonImageSource[@"alignmentInset"]; ++ if (alignmentInset != nil) { ++ CGFloat inset = alignmentInset.doubleValue; ++ void (^loadingCompletionBlock)(UIImage *_Nullable) = imageLoadingCompletionBlock; ++ imageLoadingCompletionBlock = ^(UIImage *_Nullable image) { ++ loadingCompletionBlock([image imageWithAlignmentRectInsets:UIEdgeInsetsMake(inset, inset, inset, inset)]); ++ }; ++ } ++ + #if !defined(NDEBUG) // We're in debug mode here + if (imageSource.packagerAsset) { + // We use `+ [RCTConvert UIImage:]` only in debug mode, because it is deprecated, however +@@ -38,6 +47,15 @@ RCTAssert(imageSource != nil, @"[RNScreens] imageSource must not be nil"); RCTAssert(imageLoader != nil, @"[RNScreens] imageLoader must not be nil"); From 5f46f5be3286a5fb9b74b06d2df366c01f176985 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 21:42:34 -0300 Subject: [PATCH 073/120] fix(ios): guard room header title before formatting --- app/views/RoomView/hooks/useNativeRoomHeader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts index d34d9f9f670..d96937974c4 100644 --- a/app/views/RoomView/hooks/useNativeRoomHeader.ts +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -112,7 +112,7 @@ const useRoomHeaderContent = ( const usersTyping = useAppSelector(state => state.usersTyping, shallowEqual); const typing = !tmid && usersTyping.length > 0; const subtitle = getSubtitle({ fields, tmid, usersTyping, connected, connecting, activeUser }); - const plainTitle = usePreviewFormatText(fields.title); + const plainTitle = usePreviewFormatText(fields.title ?? ''); const formattedSubtitle = usePreviewFormatText(subtitle ?? ''); const hasStatusExpiry = fields.type === 'd' && connected && !!formatStatusExpiry(activeUser?.statusExpiresAt); return { From c99d961eab375cf6cfb2a2db221b1c1badbcbcff Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 21:42:37 -0300 Subject: [PATCH 074/120] feat(ios): present tablet modals as native form sheets --- app/stacks/MasterDetailStack/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/stacks/MasterDetailStack/index.tsx b/app/stacks/MasterDetailStack/index.tsx index 87a65ff9496..a446b230cec 100644 --- a/app/stacks/MasterDetailStack/index.tsx +++ b/app/stacks/MasterDetailStack/index.tsx @@ -250,6 +250,9 @@ const ModalStack = createNativeStackNavigator({ }).with(({ Navigator }) => { const { theme } = useContext(ThemeContext); const navigation = useNavigation>(); + if (isIOS) { + return ; + } return ( @@ -269,7 +272,9 @@ const InsideStack = createNativeStackNavigator({ }), ModalStackNavigator: createNativeStackScreen({ screen: ModalStack as any, - options: { headerShown: false } + options: isIOS + ? { headerShown: false, presentation: 'formSheet', sheetAllowedDetents: [1.0], sheetGrabberVisible: false } + : { headerShown: false } }), AttachmentView, ModalBlockView: createNativeStackScreen({ From 4c81e3febe21d9346fe84743b450c1c1fac7c33d Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 23 Sep 2026 21:42:41 -0300 Subject: [PATCH 075/120] fix(ios): show the room back button only when it can go back --- .../hooks/__tests__/useNativeBackButton.test.ts | 12 +++++++++++- app/views/RoomView/hooks/useNativeBackButton.ts | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts b/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts index f956bd44e5b..60379d053b8 100644 --- a/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts +++ b/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts @@ -4,10 +4,13 @@ import { useNativeBackButton } from '../useNativeBackButton'; const mockSetOptions = jest.fn(); const mockGoBack = jest.fn(); +let mockCanGoBack = true; let mockUnreads: number | null = null; const mockUseUnreadsCount = jest.fn((_rid?: string) => mockUnreads); -jest.mock('@react-navigation/native', () => ({ useNavigation: () => ({ setOptions: mockSetOptions, goBack: mockGoBack }) })); +jest.mock('@react-navigation/native', () => ({ + useNavigation: () => ({ setOptions: mockSetOptions, goBack: mockGoBack, canGoBack: () => mockCanGoBack }) +})); jest.mock('../useUnreadsCount', () => ({ useUnreadsCount: (rid?: string) => mockUseUnreadsCount(rid) })); const renderBackItem = (unreads: number | null) => { @@ -22,6 +25,7 @@ const renderBackItem = (unreads: number | null) => { beforeEach(() => { jest.clearAllMocks(); mockUnreads = null; + mockCanGoBack = true; }); it('replaces the back button with a chevron labelled with the unread count', () => { @@ -53,3 +57,9 @@ it('does nothing when disabled', () => { expect(mockUseUnreadsCount).toHaveBeenCalledWith(undefined); expect(mockSetOptions).not.toHaveBeenCalled(); }); + +it('keeps the default header when it cannot go back', () => { + mockCanGoBack = false; + renderHook(() => useNativeBackButton(true, 'rid')); + expect(mockSetOptions).not.toHaveBeenCalled(); +}); diff --git a/app/views/RoomView/hooks/useNativeBackButton.ts b/app/views/RoomView/hooks/useNativeBackButton.ts index a16e78e06dc..6208b48f4b1 100644 --- a/app/views/RoomView/hooks/useNativeBackButton.ts +++ b/app/views/RoomView/hooks/useNativeBackButton.ts @@ -18,7 +18,7 @@ export const useNativeBackButton = (enabled: boolean, rid?: string) => { const unreadsLabel = formatUnreadsCount(useUnreadsCount(enabled ? rid : undefined)); useLayoutEffect(() => { - if (!enabled) { + if (!enabled || !navigation.canGoBack()) { return; } const backItem: NativeStackHeaderItem = { From 42f4f81e524df7bf73a5b000f2bb330b92d0384b Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 09:55:18 -0300 Subject: [PATCH 076/120] feat(ios): use the native glass header for the tablet rooms list --- app/lib/methods/helpers/navigation/index.ts | 4 - .../MasterDetailStack/SplitNavigator.tsx | 79 +++++++++++++++++++ app/stacks/MasterDetailStack/index.tsx | 25 ++++-- app/stacks/MasterDetailStack/types.ts | 5 +- .../hooks/__tests__/useHeader.test.tsx | 9 +-- app/views/RoomsListView/hooks/useHeader.tsx | 8 +- app/views/RoomsListView/index.tsx | 16 +--- 7 files changed, 113 insertions(+), 33 deletions(-) create mode 100644 app/stacks/MasterDetailStack/SplitNavigator.tsx diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 16f6c7f0776..f2acb7e1ebd 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -53,10 +53,6 @@ export const outsideHeaderLeftClose = (onPress: () => void, testID: string): Nat headerLeft: (): ReactElement => createElement(HeaderButton.CloseModal, { onPress, testID }) }; -export const drawerStyle = { - width: 320 -}; - export const themedHeader = (theme: TSupportedThemes): NativeStackNavigationOptions => isIOS ? {} diff --git a/app/stacks/MasterDetailStack/SplitNavigator.tsx b/app/stacks/MasterDetailStack/SplitNavigator.tsx new file mode 100644 index 00000000000..0598b3e25bd --- /dev/null +++ b/app/stacks/MasterDetailStack/SplitNavigator.tsx @@ -0,0 +1,79 @@ +import { + createNavigatorFactory, + type DefaultNavigatorOptions, + type ParamListBase, + type TabNavigationState, + TabRouter, + type TabRouterOptions, + useNavigationBuilder +} from '@react-navigation/native'; +import { StyleSheet, View } from 'react-native'; + +import { MAX_SIDEBAR_WIDTH } from '~/lib/constants/tablet'; +import { useTheme } from '~/theme'; + +const DetailFocusedRouter = (options: TabRouterOptions): ReturnType => { + const router = TabRouter({ ...options, backBehavior: 'none' }); + return { + ...router, + getStateForAction(state, action, configOptions) { + const nextState = router.getStateForAction(state, action, configOptions); + if (nextState === null || nextState.index === state.index) { + return nextState; + } + return { ...nextState, index: state.index }; + } + }; +}; + +type SplitNavigatorProps = DefaultNavigatorOptions< + ParamListBase, + string | undefined, + TabNavigationState, + {}, + {}, + any +> & + TabRouterOptions; + +const SplitNavigator = ({ id, initialRouteName, children, layout, screenListeners, screenOptions }: SplitNavigatorProps) => { + const { colors } = useTheme(); + const { state, descriptors, NavigationContent } = useNavigationBuilder(DetailFocusedRouter, { + id, + initialRouteName, + children, + layout, + screenListeners, + screenOptions + }); + const [master, ...details] = state.routes; + + return ( + + + {descriptors[master.key].render()} + {details.map(route => ( + + {descriptors[route.key].render()} + + ))} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'row' + }, + master: { + width: MAX_SIDEBAR_WIDTH, + borderEndWidth: StyleSheet.hairlineWidth + }, + detail: { + flex: 1 + } +}); + +export const createSplitNavigator = createNavigatorFactory(SplitNavigator); diff --git a/app/stacks/MasterDetailStack/index.tsx b/app/stacks/MasterDetailStack/index.tsx index a446b230cec..e51be41993d 100644 --- a/app/stacks/MasterDetailStack/index.tsx +++ b/app/stacks/MasterDetailStack/index.tsx @@ -4,14 +4,14 @@ import { createNativeStackScreen, type NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { createDrawerNavigator } from '@react-navigation/drawer'; import { type StaticScreenProps, useNavigation } from '@react-navigation/native'; import { ThemeContext } from '~/theme'; -import { defaultHeader, themedHeader, drawerStyle } from '~/lib/methods/helpers/navigation'; +import { defaultHeader, themedHeader } from '~/lib/methods/helpers/navigation'; import withNavigation from '~/lib/navigation/withNavigation'; import { isIOS } from '~/lib/methods/helpers'; import { ModalContainer } from './ModalContainer'; +import { createSplitNavigator } from './SplitNavigator'; import { type MasterDetailChatsStackParamList, type MasterDetailInsideStackParamList, type ModalStackParamList } from './types'; import RoomView from '~/views/RoomView'; import RoomsListView from '~/views/RoomsListView'; @@ -168,10 +168,23 @@ const ChatsStack = createNativeStackNavigator({ return ; }); -const DrawerNav = createDrawerNavigator({ - screenOptions: { drawerType: 'permanent', headerShown: false, drawerStyle: { ...drawerStyle } }, - drawerContent: () => , +const RoomsListStack = createNativeStackNavigator({ + screenOptions: defaultHeader, + screens: { + RoomsListView: createNativeStackScreen({ + screen: RoomsListView, + options: { headerShown: isIOS } + }) + } +}).with(({ Navigator }) => { + const { theme } = useContext(ThemeContext); + return ; +}); + +const SplitNav = createSplitNavigator({ + initialRouteName: 'ChatsStackNavigator', screens: { + RoomsListStackNavigator: RoomsListStack, ChatsStackNavigator: ChatsStack } }); @@ -267,7 +280,7 @@ const InsideStack = createNativeStackNavigator({ }, screens: { DrawerNavigator: createNativeStackScreen({ - screen: DrawerNav as any, + screen: SplitNav as any, options: { headerShown: false } }), ModalStackNavigator: createNativeStackScreen({ diff --git a/app/stacks/MasterDetailStack/types.ts b/app/stacks/MasterDetailStack/types.ts index cdd11f23bba..c4c397fe2c1 100644 --- a/app/stacks/MasterDetailStack/types.ts +++ b/app/stacks/MasterDetailStack/types.ts @@ -23,7 +23,8 @@ export type MasterDetailChatsStackParamList = { }; }; -export type MasterDetailDrawerParamList = { +export type MasterDetailSplitParamList = { + RoomsListStackNavigator: NavigatorScreenParams<{ RoomsListView: undefined }>; ChatsStackNavigator: NavigatorScreenParams; }; @@ -206,7 +207,7 @@ export type ModalStackParamList = { }; export type MasterDetailInsideStackParamList = { - DrawerNavigator: NavigatorScreenParams>; // TODO: Change + DrawerNavigator: NavigatorScreenParams>; // TODO: Change ModalStackNavigator: NavigatorScreenParams; ModalBlockView: { data: any; // TODO: Change diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 566fba2accc..50263f28560 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -19,8 +19,7 @@ jest.mock('~/lib/methods/helpers', () => { ...jest.requireActual('~/lib/methods/helpers') }, { isIOS: { get: () => mockIsIOS, configurable: true }, - isTablet: { get: () => mockIsTablet, configurable: true }, - hasNativeHeaderBar: { get: () => mockIsIOS && !mockIsTablet, configurable: true } + isTablet: { get: () => mockIsTablet, configurable: true } } ) ); @@ -216,13 +215,13 @@ describe('RoomsListView useHeader', () => { expect(typeof options.headerTitle).toBe('function'); }); - it('falls back to the JS header on iPad', () => { + it('uses the native header on iPad', () => { mockIsTablet = true; renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; - expect(options.headerLargeTitle).toBeUndefined(); - expect(typeof options.headerTitle).toBe('function'); + expect(options.headerLargeTitle).toBe(true); + expect(options.headerTitle).toBe('Rocket.Chat'); }); }); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 4b5a7e11a19..05d1f457be3 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -12,7 +12,7 @@ import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useIsAccessibilityNavigationEnabled } from '~/lib/hooks/useIsAccessibilityNavigationEnabled'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { usePermissions } from '~/lib/hooks/usePermissions'; -import { hasNativeHeaderBar, isTablet } from '~/lib/methods/helpers'; +import { isIOS, isTablet } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; import { getUserSelector } from '~/selectors/login'; @@ -148,7 +148,7 @@ export const useHeader = () => { /> ); - if (searchEnabled && !hasNativeHeaderBar) { + if (searchEnabled && !isIOS) { const searchOptions = { headerLeft: () => ( @@ -165,7 +165,7 @@ export const useHeader = () => { return; } - if (hasNativeHeaderBar) { + if (isIOS) { const { visible, overflow } = splitHeaderRightActions([ { key: 'create', @@ -318,7 +318,7 @@ export const useHeader = () => { ]); useEffect(() => { - if (hasNativeHeaderBar && !searchEnabled) { + if (isIOS && !searchEnabled) { searchBarRef.current?.clearText(); } }, [searchEnabled]); diff --git a/app/views/RoomsListView/index.tsx b/app/views/RoomsListView/index.tsx index 402b86eacdb..c99cd872d25 100644 --- a/app/views/RoomsListView/index.tsx +++ b/app/views/RoomsListView/index.tsx @@ -14,15 +14,7 @@ import i18n from '~/i18n'; import { MAX_SIDEBAR_WIDTH } from '~/lib/constants/tablet'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; -import { - getRoomAvatar, - getRoomTitle, - getUidDirectMessage, - hasNativeHeaderBar, - isIOS, - isRead, - isTablet -} from '~/lib/methods/helpers'; +import { getRoomAvatar, getRoomTitle, getUidDirectMessage, isIOS, isRead, isTablet } from '~/lib/methods/helpers'; import { goRoom } from '~/lib/methods/helpers/goRoom'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { getUserSelector } from '~/selectors/login'; @@ -38,7 +30,7 @@ import { useSubscriptions } from './hooks/useSubscriptions'; import styles from './styles'; const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12; -const BOTTOM_SEARCH_TOOLBAR_HEIGHT = hasNativeHeaderBar ? 44 : 0; +const BOTTOM_SEARCH_TOOLBAR_HEIGHT = isIOS ? 44 : 0; const RoomsListView = memo(function RoomsListView() { useHeader(); @@ -73,7 +65,7 @@ const RoomsListView = memo(function RoomsListView() { }, [searchEnabled]); const onPressItem = (item = {} as IRoomItem) => { - if (!navigation.isFocused()) { + if (!isMasterDetail && !navigation.isFocused()) { return; } if (item.rid === subscribedRoom) { @@ -145,7 +137,7 @@ const RoomsListView = memo(function RoomsListView() { ListHeaderComponent={ListHeader} ListFooterComponent={searching ? () => : undefined} getItemLayout={getItemLayout} - contentInsetAdjustmentBehavior={hasNativeHeaderBar ? 'automatic' : undefined} + contentInsetAdjustmentBehavior={isIOS ? 'automatic' : undefined} keyboardShouldPersistTaps='always' initialNumToRender={INITIAL_NUM_TO_RENDER} refreshControl={} From b8220d58788baa2162c650854c5f509f4ec09745 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 10:12:50 -0300 Subject: [PATCH 077/120] fix(ios): dismiss the tablet rooms search when a room opens --- .../hooks/__tests__/useHeader.test.tsx | 32 +++++++++-- app/views/RoomsListView/hooks/useHeader.tsx | 55 +++++++++++++------ 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 50263f28560..5e151ac8cf2 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -5,11 +5,10 @@ import { RoomsSearchContext } from '../../contexts/RoomsSearchProvider'; import { useHeader } from '../useHeader'; const mockSetOptions = jest.fn(); -const mockNavigation = { setOptions: mockSetOptions, navigate: jest.fn(), toggleDrawer: jest.fn() }; +const mockNavigation = { setOptions: mockSetOptions, navigate: jest.fn(), toggleDrawer: jest.fn(), getParent: jest.fn() }; jest.mock('@react-navigation/native', () => ({ - useNavigation: () => mockNavigation, - useFocusEffect: jest.fn() + useNavigation: () => mockNavigation })); let mockIsIOS = true; @@ -28,7 +27,8 @@ jest.mock('~/lib/methods/helpers/navigation/headerIcon', () => ({ headerIcon: (name: string) => ({ type: 'image', source: { uri: name } }) })); -jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => false })); +let mockIsMasterDetail = false; +jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => mockIsMasterDetail })); jest.mock('~/lib/hooks/useIsAccessibilityNavigationEnabled', () => ({ useIsAccessibilityNavigationEnabled: () => false })); jest.mock('~/lib/hooks/usePermissions', () => ({ usePermissions: () => [true, false, false, false, false] })); jest.mock('~/selectors/login', () => ({ getUserSelector: () => ({ requirePasswordChange: false }) })); @@ -88,6 +88,7 @@ describe('RoomsListView useHeader', () => { jest.clearAllMocks(); mockIsIOS = true; mockIsTablet = false; + mockIsMasterDetail = false; mockAppState = { supportedVersions: { status: 'supported' }, troubleshootingNotification: { issuesWithNotifications: false }, @@ -172,7 +173,7 @@ describe('RoomsListView useHeader', () => { const rightItems = options.unstable_headerRightItems(); expect(rightItems.some((item: { accessibilityLabel: string }) => item.accessibilityLabel === 'Search')).toBe(false); - expect(options.headerSearchBarOptions.placement).toBe('automatic'); + expect(options.headerSearchBarOptions.placement).toBe('stacked'); expect(options.headerSearchBarOptions.ref.current).toBeNull(); options.headerSearchBarOptions.onFocus(); @@ -205,6 +206,27 @@ describe('RoomsListView useHeader', () => { expect(clearText).toHaveBeenCalledTimes(1); }); + it('deactivates the system search bar once search stops on tablet', () => { + mockIsMasterDetail = true; + let setSearchEnabled: (value: boolean) => void = () => {}; + const wrapper = ({ children }: { children: ReactElement }) => { + const [searchEnabled, setter] = useState(true); + setSearchEnabled = setter; + return ( + {children} + ); + }; + + renderHook(() => useHeader(), { wrapper }); + + const cancelSearch = jest.fn(); + mockSetOptions.mock.calls[0][0].headerSearchBarOptions.ref.current = { cancelSearch }; + + act(() => setSearchEnabled(false)); + + expect(cancelSearch).toHaveBeenCalledTimes(1); + }); + it('falls back to the JS header on Android', () => { mockIsIOS = false; diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 05d1f457be3..f83a0e0c914 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -1,4 +1,4 @@ -import { useFocusEffect, useNavigation } from '@react-navigation/native'; +import { useNavigation } from '@react-navigation/native'; import { useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { InteractionManager } from 'react-native'; import { type KeyboardFocus } from 'react-native-external-keyboard'; @@ -41,6 +41,13 @@ const splitHeaderRightActions = (actions: IHeaderRightAction[]) => { return { visible: present.slice(0, MAX_HEADER_RIGHT_ACTIONS), overflow: present.slice(MAX_HEADER_RIGHT_ACTIONS) }; }; +const getScreenFocusNavigation = (navigation: any, isMasterDetail: boolean) => { + if (!isMasterDetail) { + return navigation; + } + return navigation.getParent()?.getParent() ?? navigation; +}; + export const useHeader = () => { const { searchEnabled, search, startSearch, stopSearch } = useContext(RoomsSearchContext); const [options, setOptions] = useState(null); @@ -201,7 +208,7 @@ export const useHeader = () => { onHeaderTitlePress: () => showActionSheetRef({ children: , enableContentPanningGesture: false }), headerSearchBarOptions: { ref: searchBarRef, - placement: 'automatic', + placement: 'stacked', placeholder: i18n.t('Search'), onFocus: startSearch, onChangeText: (event: { nativeEvent: { text: string } }) => search(event.nativeEvent.text), @@ -318,26 +325,38 @@ export const useHeader = () => { ]); useEffect(() => { - if (isIOS && !searchEnabled) { - searchBarRef.current?.clearText(); + if (!isIOS || searchEnabled) { + return; } - }, [searchEnabled]); + if (isMasterDetail) { + searchBarRef.current?.cancelSearch(); + return; + } + searchBarRef.current?.clearText(); + }, [searchEnabled, isMasterDetail]); - // The rooms list header persists across native-stack navigation, so autoFocus (mount-only) - // won't re-fire on back-return or after the list/banner render asynchronously. Re-assert focus - // on the drawer button every time the screen is focused so external-keyboard/screen-reader - // navigation always starts from a known element. Regular touch users are left untouched. - useFocusEffect( - useCallback(() => { - if (!isAccessibilityNavigationEnabled) { - return; - } - const task = InteractionManager.runAfterInteractions(() => { + const focusNavigation = getScreenFocusNavigation(navigation, isMasterDetail); + + useEffect(() => { + if (!isAccessibilityNavigationEnabled) { + return; + } + let task: ReturnType | undefined; + const focusDrawerButton = () => { + task?.cancel(); + task = InteractionManager.runAfterInteractions(() => { drawerButtonRef.current?.focus(); }); - return () => task.cancel(); - }, [isAccessibilityNavigationEnabled]) - ); + }; + if (focusNavigation.isFocused()) { + focusDrawerButton(); + } + const unsubscribe = focusNavigation.addListener('focus', focusDrawerButton); + return () => { + unsubscribe(); + task?.cancel(); + }; + }, [focusNavigation, isAccessibilityNavigationEnabled]); return { options }; }; From 397cf92b0fd950847142f13708b5d142f9afb174 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 10:12:53 -0300 Subject: [PATCH 078/120] fix(ios): use native room header items on tablet --- .../RoomView/components/RoomBodyFocusAnchor.tsx | 4 ++-- .../RoomView/hooks/__tests__/useHeader.test.tsx | 17 ++++++++++++++--- app/views/RoomView/hooks/useHeader.tsx | 10 +++++++--- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/app/views/RoomView/components/RoomBodyFocusAnchor.tsx b/app/views/RoomView/components/RoomBodyFocusAnchor.tsx index 1d48596a08b..13675e34b65 100644 --- a/app/views/RoomView/components/RoomBodyFocusAnchor.tsx +++ b/app/views/RoomView/components/RoomBodyFocusAnchor.tsx @@ -2,7 +2,7 @@ import { type ReactNode } from 'react'; import { StyleSheet } from 'react-native'; import { KeyboardFocusView } from 'react-native-external-keyboard'; -import { hasNativeHeaderBar } from '~/lib/methods/helpers'; +import { isIOS } from '~/lib/methods/helpers'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; import { useIsAccessibilityNavigationEnabled } from '~/lib/hooks/useIsAccessibilityNavigationEnabled'; import { fromSubscription, useRoomStore } from '../stores/RoomStoreContext'; @@ -22,7 +22,7 @@ export const RoomBodyFocusAnchor = ({ children }: IRoomBodyFocusAnchorProps) => const disabled = useRoomStore(fromSubscription(isInviteSubscription, false)); const accessibilityNavigationEnabled = useIsAccessibilityNavigationEnabled(); - if (!hasNativeHeaderBar) { + if (!isIOS) { return <>{children}; } diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index 42b16bba523..f3e5195d450 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -28,9 +28,6 @@ jest.mock('~/lib/methods/helpers', () => ({ }, get isTablet() { return mockIsTablet; - }, - get hasNativeHeaderBar() { - return mockIsIOS && !mockIsTablet; } })); jest.mock('~/lib/methods/isInviteSubscription', () => ({ @@ -130,6 +127,12 @@ describe('useHeader', () => { expect(titleOptions.headerTitle().props.title).toBe('Room Title'); }); + it('leaves the native header empty when rid is missing', () => { + renderHook(() => useHeader({ rid: undefined, tmid: undefined, name: 'general', roomStore: mockTestStore })); + + expect(mockSetOptions).not.toHaveBeenCalled(); + }); + it('sets unstable_headerRightItems and keeps the native back button instead of headerLeft/headerRight', () => { renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); @@ -154,6 +157,14 @@ describe('useHeader', () => { expect(titleOptions).toHaveProperty('headerTitle'); expect(typeof titleOptions.headerTitle).toBe('function'); }); + + it('uses the native right items like iPhone', () => { + renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + + const sideOptions = mockSetOptions.mock.calls[0][0]; + expect(sideOptions.unstable_headerRightItems()).toBe(mockNativeRightItems); + expect(sideOptions).not.toHaveProperty('headerRight'); + }); }); }); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index bffa1fb6e84..2083d654d66 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -5,7 +5,7 @@ import { useShallow } from 'zustand/react/shallow'; import { useStore } from 'zustand'; import RoomHeader from '~/containers/RoomHeader'; -import { getRoomTitle, hasNativeHeaderBar, isGroupChat } from '~/lib/methods/helpers'; +import { getRoomTitle, isIOS, isGroupChat } from '~/lib/methods/helpers'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; import LeftButtons from '../components/LeftButtons'; @@ -72,16 +72,20 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa const nativeTitle = Platform.OS === 'ios' && Number.parseInt(String(Platform.Version), 10) >= 26; useNativeRoomHeader(!!rid && nativeTitle, headerFields, tmid, roomUserId, goRoomActionsView); useNativeBackButton(!!rid && nativeTitle, rid); - const nativeRightItems = useRoomHeaderRightItems(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); + const nativeRightItems = useRoomHeaderRightItems(isIOS ? rid : undefined, tmid, roomStore); useLayoutEffect(() => { + if (!rid && isIOS) { + return; + } + if (!rid) { const height = 37 * PixelRatio.getFontScale(); navigation.setOptions({ headerLeft: () => }); return; } - if (hasNativeHeaderBar) { + if (isIOS) { // Native back button keeps the native-stack default; only the right cluster is overridden. navigation.setOptions({ unstable_headerRightItems: () => nativeRightItems }); return; From 8b90103c7b0001ef38bfd8de4ff950c8df66df26 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 10:48:15 -0300 Subject: [PATCH 079/120] fix(ios): let the tablet rooms search be cancelled --- .../hooks/__tests__/useHeader.test.tsx | 30 +++++++++ app/views/RoomsListView/hooks/useHeader.tsx | 61 +++++++++++-------- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 5e151ac8cf2..9f80bcd26f0 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -227,6 +227,36 @@ describe('RoomsListView useHeader', () => { expect(cancelSearch).toHaveBeenCalledTimes(1); }); + it('replaces the tablet right actions with a Cancel item that stops search while searching', () => { + mockIsTablet = true; + const wrapper = ({ children }: { children: ReactElement }) => ( + {children} + ); + + renderHook(() => useHeader(), { wrapper }); + + const options = mockSetOptions.mock.calls[0][0]; + expect(options.headerSearchBarOptions.hideNavigationBar).toBe(false); + const rightItems = options.unstable_headerRightItems(); + expect(rightItems.map((item: { label: string }) => item.label)).toEqual(['Cancel']); + + rightItems[0].onPress(); + expect(mockStopSearch).toHaveBeenCalledTimes(1); + }); + + it('keeps the iPhone right actions and hides the navigation bar while searching', () => { + const wrapper = ({ children }: { children: ReactElement }) => ( + {children} + ); + + renderHook(() => useHeader(), { wrapper }); + + const options = mockSetOptions.mock.calls[0][0]; + expect(options.headerSearchBarOptions.hideNavigationBar).toBe(true); + const labels = options.unstable_headerRightItems().map((item: { label: string }) => item.label); + expect(labels).not.toContain('Cancel'); + }); + it('falls back to the JS header on Android', () => { mockIsIOS = false; diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index f83a0e0c914..d230e0d6d54 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -210,6 +210,7 @@ export const useHeader = () => { ref: searchBarRef, placement: 'stacked', placeholder: i18n.t('Search'), + hideNavigationBar: !isTablet, onFocus: startSearch, onChangeText: (event: { nativeEvent: { text: string } }) => search(event.nativeEvent.text), onCancelButtonPress: stopSearch @@ -227,35 +228,45 @@ export const useHeader = () => { : () => navigation.toggleDrawer() } ], - unstable_headerRightItems: () => [ - ...visible.map(action => ({ - type: 'button' as const, - label: action.accessibilityLabel, - accessibilityLabel: action.accessibilityLabel, - icon: headerIcon(action.icon), - tintColor: action.tintColor, - disabled: action.disabled, - onPress: action.onPress - })), - ...(overflow.length >= 2 + unstable_headerRightItems: () => + isTablet && searchEnabled ? [ { - type: 'menu' as const, - label: i18n.t('More'), - accessibilityLabel: i18n.t('More'), - icon: headerIcon('kebab'), - menu: { - items: overflow.map(action => ({ - type: 'action' as const, - label: action.accessibilityLabel, - icon: headerIcon(action.icon), - onPress: action.onPress - })) - } + type: 'button' as const, + label: i18n.t('Cancel'), + accessibilityLabel: i18n.t('Cancel'), + onPress: stopSearch } ] - : []) - ] + : [ + ...visible.map(action => ({ + type: 'button' as const, + label: action.accessibilityLabel, + accessibilityLabel: action.accessibilityLabel, + icon: headerIcon(action.icon), + tintColor: action.tintColor, + disabled: action.disabled, + onPress: action.onPress + })), + ...(overflow.length >= 2 + ? [ + { + type: 'menu' as const, + label: i18n.t('More'), + accessibilityLabel: i18n.t('More'), + icon: headerIcon('kebab'), + menu: { + items: overflow.map(action => ({ + type: 'action' as const, + label: action.accessibilityLabel, + icon: headerIcon(action.icon), + onPress: action.onPress + })) + } + } + ] + : []) + ] }); return; } From e4a83a3497a630040fec8b912d6c9f7cc8977eb1 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 13:00:39 -0300 Subject: [PATCH 080/120] fix(ios): show the room avatar in the tablet room header --- .../RoomView/hooks/__tests__/useHeader.test.tsx | 16 ++++++++++++++++ app/views/RoomView/hooks/useHeader.tsx | 17 ++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index f3e5195d450..8a0c80aac4a 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -30,6 +30,7 @@ jest.mock('~/lib/methods/helpers', () => ({ return mockIsTablet; } })); +jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => mockIsTablet })); jest.mock('~/lib/methods/isInviteSubscription', () => ({ isInviteSubscription: jest.fn(() => false) })); @@ -165,6 +166,21 @@ describe('useHeader', () => { expect(sideOptions.unstable_headerRightItems()).toBe(mockNativeRightItems); expect(sideOptions).not.toHaveProperty('headerRight'); }); + + it('shows the room avatar without the shared glass background', () => { + renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + + const [avatarItem] = mockSetOptions.mock.calls[0][0].unstable_headerLeftItems(); + expect(avatarItem.type).toBe('custom'); + expect(avatarItem.hidesSharedBackground).toBe(true); + expect(avatarItem.element.type).toBe('LeftButtons'); + }); + + it('keeps the native back button on a thread', () => { + renderHook(() => useHeader({ rid: 'rid-1', tmid: 'tmid-1', name: 'Thread', roomStore: mockTestStore })); + + expect(mockSetOptions.mock.calls[0][0]).not.toHaveProperty('unstable_headerLeftItems'); + }); }); }); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index 2083d654d66..241a074adfd 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -1,11 +1,13 @@ import { useLayoutEffect } from 'react'; import { PixelRatio, Platform, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; +import { type NativeStackHeaderItem } from '@react-navigation/native-stack'; import { useShallow } from 'zustand/react/shallow'; import { useStore } from 'zustand'; import RoomHeader from '~/containers/RoomHeader'; import { getRoomTitle, isIOS, isGroupChat } from '~/lib/methods/helpers'; +import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; import LeftButtons from '../components/LeftButtons'; @@ -73,6 +75,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa useNativeRoomHeader(!!rid && nativeTitle, headerFields, tmid, roomUserId, goRoomActionsView); useNativeBackButton(!!rid && nativeTitle, rid); const nativeRightItems = useRoomHeaderRightItems(isIOS ? rid : undefined, tmid, roomStore); + const isMasterDetail = useMasterDetail(); useLayoutEffect(() => { if (!rid && isIOS) { @@ -86,8 +89,16 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa } if (isIOS) { - // Native back button keeps the native-stack default; only the right cluster is overridden. - navigation.setOptions({ unstable_headerRightItems: () => nativeRightItems }); + const avatarItem: NativeStackHeaderItem = { + type: 'custom', + element: , + hidesSharedBackground: true + }; + navigation.setOptions( + isMasterDetail && !tmid + ? { unstable_headerLeftItems: () => [avatarItem], unstable_headerRightItems: () => nativeRightItems } + : { unstable_headerRightItems: () => nativeRightItems } + ); return; } @@ -95,7 +106,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa headerLeft: () => , headerRight: () => }); - }, [rid, tmid, navigation, roomStore, nativeRightItems]); + }, [rid, tmid, navigation, roomStore, nativeRightItems, isMasterDetail]); useLayoutEffect(() => { if (!rid || nativeTitle) { From c9aa2777a96fe8412499722442e16104f2e7f797 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 13:39:40 -0300 Subject: [PATCH 081/120] fix(ios): tint native headers with the theme surface color Stack headers use surfaceNeutral as on develop. The rooms list header, which content scrolls under, uses a translucent surfaceNeutral over a blur so the tint shows while the bar stays translucent. --- app/lib/methods/helpers/navigation/index.ts | 6 +++++- app/views/RoomsListView/hooks/useHeader.tsx | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index f2acb7e1ebd..2e8dce254de 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -55,7 +55,11 @@ export const outsideHeaderLeftClose = (onPress: () => void, testID: string): Nat export const themedHeader = (theme: TSupportedThemes): NativeStackNavigationOptions => isIOS - ? {} + ? { + headerStyle: { + backgroundColor: themes[theme].surfaceNeutral + } + } : { headerStyle: { backgroundColor: themes[theme].surfaceNeutral diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index d230e0d6d54..abaeab0bdac 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -203,6 +203,8 @@ export const useHeader = () => { navigation.setOptions({ headerLargeTitle: true, headerTransparent: true, + headerStyle: { backgroundColor: `${colors.surfaceNeutral}B3` }, + headerBlurEffect: 'regular', headerTitle: serverName, headerSubtitle: nativeHeaderSubtitle, onHeaderTitlePress: () => showActionSheetRef({ children: , enableContentPanningGesture: false }), From e4bbb8955b63fb3357456dcfb1acd6eff3ca6200 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 14:48:42 -0300 Subject: [PATCH 082/120] refactor(ios): gate native headers on iOS 26 and drop dead header code --- app/lib/methods/helpers/deviceInfo.ts | 2 +- app/lib/methods/helpers/navigation/index.ts | 38 +++--- .../hooks/__tests__/useHeader.test.tsx | 36 +----- app/views/RoomView/hooks/useHeader.tsx | 19 ++- .../RoomView/hooks/useRoomHeaderRightItems.ts | 50 +++----- .../hooks/__tests__/useHeader.test.tsx | 6 +- app/views/RoomsListView/hooks/useHeader.tsx | 110 +++++++----------- app/views/ThreadMessagesView/index.tsx | 1 - metro.config.js | 22 ---- tsconfig.json | 1 - 10 files changed, 95 insertions(+), 190 deletions(-) diff --git a/app/lib/methods/helpers/deviceInfo.ts b/app/lib/methods/helpers/deviceInfo.ts index ce331833284..5e16e945afe 100644 --- a/app/lib/methods/helpers/deviceInfo.ts +++ b/app/lib/methods/helpers/deviceInfo.ts @@ -17,4 +17,4 @@ export const supportSystemTheme = (): boolean => { // Tablet info export const isTablet = DeviceInfo.isTablet(); -export const hasNativeHeaderBar = isIOS && !isTablet; +export const hasNativeHeaderBar = isIOS && parseInt(DeviceInfo.getSystemVersion(), 10) >= 26; diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 2e8dce254de..4668d0f3780 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -8,10 +8,10 @@ import sharedStyles from '~/views/Styles'; import Header from '~/containers/Header'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import I18n from '~/i18n'; -import { isIOS } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import { headerIcon } from './headerIcon'; -export const defaultHeader: NativeStackNavigationOptions = isIOS +export const defaultHeader: NativeStackNavigationOptions = hasNativeHeaderBar ? { headerBackButtonDisplayMode: 'minimal' } @@ -19,8 +19,11 @@ export const defaultHeader: NativeStackNavigationOptions = isIOS header: (props: NativeStackHeaderProps): ReactElement => createElement(Header, props) }; -export const outsideHeaderRightLegal = (navigation: any, testID: string): NativeStackNavigationOptions => - isIOS +export const outsideHeaderRightLegal = ( + navigation: { navigate: (screen: 'LegalView') => void }, + testID: string +): NativeStackNavigationOptions => + hasNativeHeaderBar ? { unstable_headerRightItems: () => [ { @@ -28,7 +31,7 @@ export const outsideHeaderRightLegal = (navigation: any, testID: string): Native label: I18n.t('More'), accessibilityLabel: I18n.t('More'), icon: headerIcon('kebab'), - onPress: () => navigation?.navigate('LegalView') + onPress: () => navigation.navigate('LegalView') } ] } @@ -37,7 +40,7 @@ export const outsideHeaderRightLegal = (navigation: any, testID: string): Native }; export const outsideHeaderLeftClose = (onPress: () => void, testID: string): NativeStackNavigationOptions => - isIOS + hasNativeHeaderBar ? { unstable_headerLeftItems: () => [ { @@ -53,20 +56,15 @@ export const outsideHeaderLeftClose = (onPress: () => void, testID: string): Nat headerLeft: (): ReactElement => createElement(HeaderButton.CloseModal, { onPress, testID }) }; -export const themedHeader = (theme: TSupportedThemes): NativeStackNavigationOptions => - isIOS - ? { - headerStyle: { - backgroundColor: themes[theme].surfaceNeutral - } - } - : { - headerStyle: { - backgroundColor: themes[theme].surfaceNeutral - }, - headerTintColor: themes[theme].fontDefault, - headerTitleStyle: { ...sharedStyles.textBold, color: themes[theme].fontTitlesLabels, fontSize: 16 } - }; +export const themedHeader = (theme: TSupportedThemes): NativeStackNavigationOptions => ({ + headerStyle: { + backgroundColor: themes[theme].surfaceNeutral + }, + ...(!hasNativeHeaderBar && { + headerTintColor: themes[theme].fontDefault, + headerTitleStyle: { ...sharedStyles.textBold, color: themes[theme].fontTitlesLabels, fontSize: 16 } + }) +}); export const navigationTheme = (theme: TSupportedThemes) => { const defaultNavTheme = theme === 'light' ? DefaultTheme : DarkTheme; diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index 8a0c80aac4a..bf2a673c931 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -1,5 +1,4 @@ import { act, renderHook } from '@testing-library/react-native'; -import { Platform } from 'react-native'; import { createStore } from 'zustand'; @@ -23,7 +22,7 @@ let mockIsTablet = false; jest.mock('~/lib/methods/helpers', () => ({ getRoomTitle: jest.fn(() => 'Room Title'), isGroupChat: jest.fn(() => false), - get isIOS() { + get hasNativeHeaderBar() { return mockIsIOS; }, get isTablet() { @@ -120,14 +119,6 @@ describe('useHeader', () => { mockIsIOS = true; }); - it('renders the tappable RoomHeader under the native header bar', () => { - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - - const titleOptions = mockSetOptions.mock.calls[1][0]; - expect(typeof titleOptions.headerTitle).toBe('function'); - expect(titleOptions.headerTitle().props.title).toBe('Room Title'); - }); - it('leaves the native header empty when rid is missing', () => { renderHook(() => useHeader({ rid: undefined, tmid: undefined, name: 'general', roomStore: mockTestStore })); @@ -151,14 +142,6 @@ describe('useHeader', () => { mockIsTablet = true; }); - it('renders the tappable RoomHeader instead of the native string title', () => { - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - - const titleOptions = mockSetOptions.mock.calls[1][0]; - expect(titleOptions).toHaveProperty('headerTitle'); - expect(typeof titleOptions.headerTitle).toBe('function'); - }); - it('uses the native right items like iPhone', () => { renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); @@ -185,21 +168,12 @@ describe('useHeader', () => { }); describe('native title availability', () => { - const originalVersion = Platform.Version; - const originalOS = Platform.OS; - afterEach(() => { - Object.defineProperty(Platform, 'Version', { configurable: true, value: originalVersion }); - Object.defineProperty(Platform, 'OS', { configurable: true, value: originalOS }); - }); - it.each([ - ['ios', '26.0', true], - ['ios', '18.0', false], - ['android', 36, false] - ])('uses the appropriate title on %s %s', (os, version, native) => { + [true, true], + [false, false] + ])('uses the native title when the native header bar is %s', (nativeHeaderBar, native) => { jest.clearAllMocks(); - Object.defineProperty(Platform, 'OS', { configurable: true, value: os }); - Object.defineProperty(Platform, 'Version', { configurable: true, value: version }); + mockIsIOS = nativeHeaderBar; renderHook(() => useHeader({ rid: 'rid-1', roomStore: makeRoomStore() })); expect(useNativeRoomHeader).toHaveBeenCalledWith(native, expect.any(Object), undefined, null, expect.any(Function)); expect(mockSetOptions.mock.calls.some(([options]) => typeof options.headerTitle === 'function')).toBe(!native); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index 241a074adfd..8e197ab8c35 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -1,12 +1,12 @@ import { useLayoutEffect } from 'react'; -import { PixelRatio, Platform, View } from 'react-native'; +import { PixelRatio, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { type NativeStackHeaderItem } from '@react-navigation/native-stack'; import { useShallow } from 'zustand/react/shallow'; import { useStore } from 'zustand'; import RoomHeader from '~/containers/RoomHeader'; -import { getRoomTitle, isIOS, isGroupChat } from '~/lib/methods/helpers'; +import { getRoomTitle, hasNativeHeaderBar, isGroupChat } from '~/lib/methods/helpers'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; @@ -71,14 +71,13 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa ); const roomUserId = useStore(roomStore, s => s.roomUserId); const goRoomActionsView = useGoRoomActionsView(roomStore); - const nativeTitle = Platform.OS === 'ios' && Number.parseInt(String(Platform.Version), 10) >= 26; - useNativeRoomHeader(!!rid && nativeTitle, headerFields, tmid, roomUserId, goRoomActionsView); - useNativeBackButton(!!rid && nativeTitle, rid); - const nativeRightItems = useRoomHeaderRightItems(isIOS ? rid : undefined, tmid, roomStore); + useNativeRoomHeader(!!rid && hasNativeHeaderBar, headerFields, tmid, roomUserId, goRoomActionsView); + useNativeBackButton(!!rid && hasNativeHeaderBar, rid); + const nativeRightItems = useRoomHeaderRightItems(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); const isMasterDetail = useMasterDetail(); useLayoutEffect(() => { - if (!rid && isIOS) { + if (!rid && hasNativeHeaderBar) { return; } @@ -88,7 +87,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa return; } - if (isIOS) { + if (hasNativeHeaderBar) { const avatarItem: NativeStackHeaderItem = { type: 'custom', element: , @@ -109,7 +108,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa }, [rid, tmid, navigation, roomStore, nativeRightItems, isMasterDetail]); useLayoutEffect(() => { - if (!rid || nativeTitle) { + if (!rid || hasNativeHeaderBar) { return; } @@ -134,5 +133,5 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa /> ) }); - }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView, nativeTitle]); + }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView]); }; diff --git a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts index faaa72de824..fb48a570125 100644 --- a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts +++ b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts @@ -176,38 +176,24 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): : undefined; const overflowActions: NativeStackHeaderItemMenuAction[] = [ - ...overflowKeys.map((key): NativeStackHeaderItemMenuAction => { - if (key === 'threads') { - return { type: 'action', label: threadsAccessibilityLabel, icon: headerIcon('threads'), onPress: goThreadsView }; - } - if (key === 'encryption') { - return { - type: 'action', - label: i18n.t('Encrypted'), - icon: headerIcon('encrypted'), - disabled: !canToggleEncryption, - onPress: goE2EEToggleRoomView - }; - } - if (key === 'call') { - return { - type: 'action', - label: callAccessibilityLabel, - icon: headerIcon('phone'), - disabled: isCallDisabled, - onPress: onPressCall - }; - } - if (key === 'notifications') { - return { - type: 'action', - label: i18n.t('Troubleshooting'), - icon: headerIcon('notification-disabled'), - onPress: navigateToNotificationOrPushTroubleshoot - }; - } - throw new Error(`Unhandled room header action key: ${key}`); - }), + ...overflowKeys.map( + (key): NativeStackHeaderItemMenuAction => + key === 'encryption' + ? { + type: 'action', + label: i18n.t('Encrypted'), + icon: headerIcon('encrypted'), + disabled: !canToggleEncryption, + onPress: goE2EEToggleRoomView + } + : { + type: 'action', + label: i18n.t('Troubleshooting'), + icon: headerIcon('notification-disabled'), + disabled: hasE2EEWarning, + onPress: navigateToNotificationOrPushTroubleshoot + } + ), { type: 'action', label: i18n.t('Search_Messages'), diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 9f80bcd26f0..832f6f3ff66 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -17,7 +17,7 @@ jest.mock('~/lib/methods/helpers', () => Object.defineProperties( { ...jest.requireActual('~/lib/methods/helpers') }, { - isIOS: { get: () => mockIsIOS, configurable: true }, + hasNativeHeaderBar: { get: () => mockIsIOS, configurable: true }, isTablet: { get: () => mockIsTablet, configurable: true } } ) @@ -128,7 +128,7 @@ describe('RoomsListView useHeader', () => { expect(options.headerSubtitle).toBe(expectedSubtitle); }); - it('builds the right cluster in create, push-troubleshoot priority order, overflowing directory', () => { + it('builds the right cluster in create, push-troubleshoot, directory order', () => { mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; renderUseHeader(); @@ -139,7 +139,7 @@ describe('RoomsListView useHeader', () => { expect(labels).toEqual(['Create new channel, team, direct message or discussion', 'Troubleshooting', 'Directory']); }); - it('does not render an overflow control when nothing extra is present', () => { + it('renders only buttons when push troubleshooting is absent', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index abaeab0bdac..69387c99419 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -12,7 +12,7 @@ import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useIsAccessibilityNavigationEnabled } from '~/lib/hooks/useIsAccessibilityNavigationEnabled'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { usePermissions } from '~/lib/hooks/usePermissions'; -import { isIOS, isTablet } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar, isTablet } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; import { getUserSelector } from '~/selectors/login'; @@ -21,8 +21,6 @@ import RoomsListHeaderView from '../components/Header'; import ServersList from '../components/ServersList'; import { RoomsSearchContext } from '../contexts/RoomsSearchProvider'; -const MAX_HEADER_RIGHT_ACTIONS = 2; - interface IHeaderRightAction { key: string; present: boolean; @@ -33,14 +31,6 @@ interface IHeaderRightAction { onPress: () => void; } -const splitHeaderRightActions = (actions: IHeaderRightAction[]) => { - const present = actions.filter(action => action.present); - if (present.length <= MAX_HEADER_RIGHT_ACTIONS + 1) { - return { visible: present, overflow: [] as IHeaderRightAction[] }; - } - return { visible: present.slice(0, MAX_HEADER_RIGHT_ACTIONS), overflow: present.slice(MAX_HEADER_RIGHT_ACTIONS) }; -}; - const getScreenFocusNavigation = (navigation: any, isMasterDetail: boolean) => { if (!isMasterDetail) { return navigation; @@ -155,7 +145,7 @@ export const useHeader = () => { /> ); - if (searchEnabled && !isIOS) { + if (searchEnabled && !hasNativeHeaderBar) { const searchOptions = { headerLeft: () => ( @@ -172,33 +162,35 @@ export const useHeader = () => { return; } - if (isIOS) { - const { visible, overflow } = splitHeaderRightActions([ - { - key: 'create', - present: canCreateRoom, - icon: 'create', - accessibilityLabel: i18n.t('Create_new_channel_team_dm_discussion'), - disabled, - onPress: goToNewMessage - }, - { - key: 'push-troubleshoot', - present: issuesWithNotifications, - icon: 'notification-disabled', - accessibilityLabel: i18n.t('Troubleshooting'), - tintColor: colors.fontDanger, - onPress: navigateToPushTroubleshootView - }, - { - key: 'directory', - present: true, - icon: 'directory', - accessibilityLabel: i18n.t('Directory'), - disabled, - onPress: goDirectory - } - ]); + if (hasNativeHeaderBar) { + const actions = ( + [ + { + key: 'create', + present: canCreateRoom, + icon: 'create', + accessibilityLabel: i18n.t('Create_new_channel_team_dm_discussion'), + disabled, + onPress: goToNewMessage + }, + { + key: 'push-troubleshoot', + present: issuesWithNotifications, + icon: 'notification-disabled', + accessibilityLabel: i18n.t('Troubleshooting'), + tintColor: colors.fontDanger, + onPress: navigateToPushTroubleshootView + }, + { + key: 'directory', + present: true, + icon: 'directory', + accessibilityLabel: i18n.t('Directory'), + disabled, + onPress: goDirectory + } + ] satisfies IHeaderRightAction[] + ).filter(action => action.present); navigation.setOptions({ headerLargeTitle: true, @@ -240,35 +232,15 @@ export const useHeader = () => { onPress: stopSearch } ] - : [ - ...visible.map(action => ({ - type: 'button' as const, - label: action.accessibilityLabel, - accessibilityLabel: action.accessibilityLabel, - icon: headerIcon(action.icon), - tintColor: action.tintColor, - disabled: action.disabled, - onPress: action.onPress - })), - ...(overflow.length >= 2 - ? [ - { - type: 'menu' as const, - label: i18n.t('More'), - accessibilityLabel: i18n.t('More'), - icon: headerIcon('kebab'), - menu: { - items: overflow.map(action => ({ - type: 'action' as const, - label: action.accessibilityLabel, - icon: headerIcon(action.icon), - onPress: action.onPress - })) - } - } - ] - : []) - ] + : actions.map(action => ({ + type: 'button' as const, + label: action.accessibilityLabel, + accessibilityLabel: action.accessibilityLabel, + icon: headerIcon(action.icon), + tintColor: action.tintColor, + disabled: action.disabled, + onPress: action.onPress + })) }); return; } @@ -338,7 +310,7 @@ export const useHeader = () => { ]); useEffect(() => { - if (!isIOS || searchEnabled) { + if (!hasNativeHeaderBar || searchEnabled) { return; } if (isMasterDetail) { diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 16e0244493e..193c8c9534d 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -125,7 +125,6 @@ class ThreadMessagesView extends Component - context.resolveRequest( - localPackagePaths.some(directory => context.originModulePath.startsWith(`${directory}${path.sep}`)) - ? { ...context, disableHierarchicalLookup: true } - : context, - moduleName === '@react-navigation/native-stack' ? path.join(nativeStackPath, 'src/index.tsx') : moduleName, - platform - ) - }), // When running E2E tests, prioritize .mock.ts files for app code sourceExts: process.env.RUNNING_E2E_TESTS === 'true' ? ['mock.ts', ...sourceExts] : sourceExts } diff --git a/tsconfig.json b/tsconfig.json index 6fb88695a15..8bf509d9040 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,6 @@ "target": "esnext", "module": "esnext", "moduleResolution": "bundler", - "preserveSymlinks": true, "jsx": "react-native", "allowJs": true, "noEmit": true, From c9ab16e5567b227904c86fb721bd0ba4d984b57f Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 14:48:46 -0300 Subject: [PATCH 083/120] fix(ios): restore rooms list large title and bottom search bar on iPhone --- app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx | 2 +- app/views/RoomsListView/hooks/useHeader.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 832f6f3ff66..6b80d8580e9 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -173,7 +173,7 @@ describe('RoomsListView useHeader', () => { const rightItems = options.unstable_headerRightItems(); expect(rightItems.some((item: { accessibilityLabel: string }) => item.accessibilityLabel === 'Search')).toBe(false); - expect(options.headerSearchBarOptions.placement).toBe('stacked'); + expect(options.headerSearchBarOptions.placement).toBe('automatic'); expect(options.headerSearchBarOptions.ref.current).toBeNull(); options.headerSearchBarOptions.onFocus(); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 69387c99419..d50a94654b5 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -196,13 +196,14 @@ export const useHeader = () => { headerLargeTitle: true, headerTransparent: true, headerStyle: { backgroundColor: `${colors.surfaceNeutral}B3` }, + headerLargeStyle: { backgroundColor: 'transparent' }, headerBlurEffect: 'regular', headerTitle: serverName, headerSubtitle: nativeHeaderSubtitle, onHeaderTitlePress: () => showActionSheetRef({ children: , enableContentPanningGesture: false }), headerSearchBarOptions: { ref: searchBarRef, - placement: 'stacked', + placement: isTablet ? 'stacked' : 'automatic', placeholder: i18n.t('Search'), hideNavigationBar: !isTablet, onFocus: startSearch, From c8f913259dd2013dbca8dd3f023fe6d21f6ad9cc Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 17:46:42 -0300 Subject: [PATCH 084/120] fix(ios): merge header right buttons and fix threads list header --- .../__snapshots__/index.test.tsx.snap | 381 +++++++++++------- app/containers/BackgroundContainer/index.tsx | 10 +- .../{ => __tests__}/MediaCallHeader.test.tsx | 4 +- .../MediaCallHeader.test.tsx.snap | 12 +- app/i18n/locales/en.json | 2 + .../methods/helpers/navigation/headerIcon.ts | 1 + app/stacks/MasterDetailStack/index.tsx | 4 +- .../RightButtons/OmnichannelRightButtons.tsx | 8 +- .../OmnichannelRightButtonsLegacy.tsx | 13 - .../components/RightButtons/RightButtons.tsx | 16 +- .../RightButtons/RoomRightButtons.tsx | 63 ++- .../RightButtons/RoomRightButtonsLegacy.tsx | 72 ---- .../RightButtons/ThreadRightButtons.tsx | 14 +- .../RightButtons/ThreadRightButtonsLegacy.tsx | 23 -- .../RoomView/helpers/getRoomHeaderMode.ts | 28 ++ .../__tests__/useNativeRoomHeader.test.ts | 8 +- .../RoomView/hooks/useNativeRoomHeader.ts | 7 +- .../RoomView/hooks/useRoomHeaderRightItems.ts | 27 +- app/views/RoomsListView/components/Header.tsx | 2 +- .../RoomsListView/components/ListHeader.tsx | 1 - app/views/RoomsListView/hooks/useHeader.tsx | 2 +- app/views/RoomsListView/index.tsx | 5 +- app/views/TeamChannelsView.tsx | 2 + app/views/ThreadMessagesView/index.tsx | 38 +- 24 files changed, 412 insertions(+), 331 deletions(-) rename app/containers/MediaCallHeader/{ => __tests__}/MediaCallHeader.test.tsx (98%) rename app/containers/MediaCallHeader/{ => __tests__}/__snapshots__/MediaCallHeader.test.tsx.snap (99%) delete mode 100644 app/views/RoomView/components/RightButtons/OmnichannelRightButtonsLegacy.tsx delete mode 100644 app/views/RoomView/components/RightButtons/RoomRightButtonsLegacy.tsx delete mode 100644 app/views/RoomView/components/RightButtons/ThreadRightButtonsLegacy.tsx create mode 100644 app/views/RoomView/helpers/getRoomHeaderMode.ts diff --git a/app/containers/BackgroundContainer/__snapshots__/index.test.tsx.snap b/app/containers/BackgroundContainer/__snapshots__/index.test.tsx.snap index 0a5d30836a0..3cd43ba0c33 100644 --- a/app/containers/BackgroundContainer/__snapshots__/index.test.tsx.snap +++ b/app/containers/BackgroundContainer/__snapshots__/index.test.tsx.snap @@ -42,6 +42,16 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` } /> + + + `; @@ -87,26 +97,37 @@ exports[`Story Snapshots: BlackThemeLoading should match snapshot 1`] = ` } /> - + contentInsetAdjustmentBehavior="automatic" + > + + + + `; @@ -152,32 +173,43 @@ exports[`Story Snapshots: BlackThemeText should match snapshot 1`] = ` } /> - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + `; @@ -223,26 +255,37 @@ exports[`Story Snapshots: DarkThemeLoading should match snapshot 1`] = ` } /> - + contentInsetAdjustmentBehavior="automatic" + > + + + + `; @@ -288,32 +331,43 @@ exports[`Story Snapshots: DarkThemeText should match snapshot 1`] = ` } /> - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + `; @@ -359,26 +413,37 @@ exports[`Story Snapshots: Loading should match snapshot 1`] = ` } /> - + contentInsetAdjustmentBehavior="automatic" + > + + + + `; @@ -424,32 +489,43 @@ exports[`Story Snapshots: LongText should match snapshot 1`] = ` } /> - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + `; @@ -495,31 +571,42 @@ exports[`Story Snapshots: Text should match snapshot 1`] = ` } /> - - Text here - + + + Text here + + + `; diff --git a/app/containers/BackgroundContainer/index.tsx b/app/containers/BackgroundContainer/index.tsx index 57a64dad1d5..180ff2fec18 100644 --- a/app/containers/BackgroundContainer/index.tsx +++ b/app/containers/BackgroundContainer/index.tsx @@ -1,5 +1,5 @@ import { type ReactElement } from 'react'; -import { ActivityIndicator, ImageBackground, StyleSheet, Text, View } from 'react-native'; +import { ActivityIndicator, ImageBackground, ScrollView, StyleSheet, Text, View } from 'react-native'; import { useTheme } from '~/theme'; import sharedStyles from '~/views/Styles'; @@ -36,9 +36,11 @@ const BackgroundContainer = ({ text, loading }: IBackgroundContainer): ReactElem return ( - {text && !loading ? {text} : null} - {/* @ts-ignore */} - {loading ? : null} + + {text && !loading ? {text} : null} + {/* @ts-ignore */} + {loading ? : null} + ); }; diff --git a/app/containers/MediaCallHeader/MediaCallHeader.test.tsx b/app/containers/MediaCallHeader/__tests__/MediaCallHeader.test.tsx similarity index 98% rename from app/containers/MediaCallHeader/MediaCallHeader.test.tsx rename to app/containers/MediaCallHeader/__tests__/MediaCallHeader.test.tsx index b2598a81201..99b4346b057 100644 --- a/app/containers/MediaCallHeader/MediaCallHeader.test.tsx +++ b/app/containers/MediaCallHeader/__tests__/MediaCallHeader.test.tsx @@ -2,11 +2,11 @@ import { fireEvent, render } from '@testing-library/react-native'; import { Provider } from 'react-redux'; import { type ReactNode } from 'react'; -import MediaCallHeader from './MediaCallHeader'; +import MediaCallHeader from '../MediaCallHeader'; import { navigateToCallRoom } from '~/lib/services/voip/navigateToCallRoom'; import { useCallStore } from '~/lib/services/voip/useCallStore'; import { mockedStore } from '~/reducers/mockedStore'; -import * as stories from './MediaCallHeader.stories'; +import * as stories from '../MediaCallHeader.stories'; import { generateSnapshots } from '~/.rnstorybook/generateSnapshots'; const mockNavigateToCallRoom = jest.mocked(navigateToCallRoom); diff --git a/app/containers/MediaCallHeader/__snapshots__/MediaCallHeader.test.tsx.snap b/app/containers/MediaCallHeader/__tests__/__snapshots__/MediaCallHeader.test.tsx.snap similarity index 99% rename from app/containers/MediaCallHeader/__snapshots__/MediaCallHeader.test.tsx.snap rename to app/containers/MediaCallHeader/__tests__/__snapshots__/MediaCallHeader.test.tsx.snap index 362aca97c9c..08026043bd9 100644 --- a/app/containers/MediaCallHeader/__snapshots__/MediaCallHeader.test.tsx.snap +++ b/app/containers/MediaCallHeader/__tests__/__snapshots__/MediaCallHeader.test.tsx.snap @@ -80,7 +80,7 @@ exports[`Story Snapshots: ActiveCall should match snapshot 1`] = ` } > { diff --git a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx b/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx index 91f523d0ef3..da8d0a635b8 100644 --- a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx @@ -3,6 +3,7 @@ import { useStore } from 'zustand'; import { useNavigation } from '@react-navigation/native'; import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import i18n from '~/i18n'; import { showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; @@ -16,7 +17,6 @@ import { useCanPlaceLivechatOnHold } from '~/views/RoomView/hooks/useCanPlaceLiv import { navigateToScreen, type TRoomStackNavigation } from '~/views/RoomView/services/navigateToScreen'; import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; import { placeLivechatOnHold } from '~/views/RoomView/services/placeLivechatOnHold'; -import { OmnichannelRightButtonsLegacy } from './OmnichannelRightButtonsLegacy'; interface IOmnichannelRightButtonsProps { rid: string; @@ -91,5 +91,9 @@ export const OmnichannelRightButtons = ({ rid, roomStore }: IOmnichannelRightBut showActionSheet({ options }); }; - return ; + return ( + + + + ); }; diff --git a/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsLegacy.tsx b/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsLegacy.tsx deleted file mode 100644 index f03d1bef40d..00000000000 --- a/app/views/RoomView/components/RightButtons/OmnichannelRightButtonsLegacy.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { type ReactElement } from 'react'; - -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; - -interface IOmnichannelRightButtonsLegacyProps { - onShowMoreActions: () => void; -} - -export const OmnichannelRightButtonsLegacy = ({ onShowMoreActions }: IOmnichannelRightButtonsLegacyProps): ReactElement => ( - - - -); diff --git a/app/views/RoomView/components/RightButtons/RightButtons.tsx b/app/views/RoomView/components/RightButtons/RightButtons.tsx index d147c1a75b1..aac8363051d 100644 --- a/app/views/RoomView/components/RightButtons/RightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/RightButtons.tsx @@ -4,6 +4,7 @@ import { useShallow } from 'zustand/react/shallow'; import { type RoomStore } from '~/views/RoomView/definitions'; import { fromSubscription } from '~/views/RoomView/stores/RoomStoreContext'; +import { getRoomHeaderMode } from '~/views/RoomView/helpers/getRoomHeaderMode'; import { OmnichannelRightButtons } from './OmnichannelRightButtons'; import { RoomRightButtons } from './RoomRightButtons'; import { ThreadRightButtons } from './ThreadRightButtons'; @@ -24,22 +25,15 @@ const RightButtons = ({ rid, tmid, roomStore }: IRightButtonsProps): ReactElemen })) ); - if (!rid) { - return null; - } + const mode = getRoomHeaderMode({ rid, tmid, t, status, membership }); - if (membership === 'invited') { + if (!rid || mode === 'none') { return null; } - - if (t === 'l') { - if (status === 'queued' || membership !== 'subscribed') { - return null; - } + if (mode === 'omnichannel') { return ; } - - if (tmid) { + if (mode === 'thread' && tmid) { return ; } diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx index b8d9ec5efc4..1db452ca03c 100644 --- a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx @@ -1,7 +1,9 @@ import { type ReactElement } from 'react'; +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; +import i18n from '~/i18n'; import { type RoomStore } from '~/views/RoomView/definitions'; -import { RoomRightButtonsLegacy } from './RoomRightButtonsLegacy'; +import { HeaderCallButton } from './HeaderCallButton'; import { useRoomRightButtonsData } from './useRoomRightButtonsData'; interface IRoomRightButtonsProps { @@ -10,7 +12,62 @@ interface IRoomRightButtonsProps { } export const RoomRightButtons = ({ rid, roomStore }: IRoomRightButtonsProps): ReactElement => { - const data = useRoomRightButtonsData(rid, roomStore); + const { + colors, + threadsEnabled, + issuesWithNotifications, + disableNotifications, + hasE2EEWarning, + canToggleEncryption, + isSelfDm, + tunread, + tunreadUser, + tunreadGroup, + callAccessibilityLabel, + goThreadsView, + navigateToNotificationOrPushTroubleshoot, + goSearchView, + goE2EEToggleRoomView, + threadsAccessibilityLabel + } = useRoomRightButtonsData(rid, roomStore); - return ; + return ( + + {hasE2EEWarning ? ( + + ) : null} + {issuesWithNotifications || disableNotifications ? ( + + ) : null} + {!isSelfDm ? : null} + {threadsEnabled ? ( + } + disabled={hasE2EEWarning} + /> + ) : null} + + + ); }; diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtonsLegacy.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtonsLegacy.tsx deleted file mode 100644 index a4374e613e6..00000000000 --- a/app/views/RoomView/components/RightButtons/RoomRightButtonsLegacy.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { type ReactElement } from 'react'; - -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import i18n from '~/i18n'; -import { HeaderCallButton } from './HeaderCallButton'; -import { type useRoomRightButtonsData } from './useRoomRightButtonsData'; - -interface IRoomRightButtonsLegacyProps { - rid: string; - data: ReturnType; -} - -export const RoomRightButtonsLegacy = ({ rid, data }: IRoomRightButtonsLegacyProps): ReactElement => { - const { - colors, - threadsEnabled, - issuesWithNotifications, - disableNotifications, - hasE2EEWarning, - canToggleEncryption, - isSelfDm, - tunread, - tunreadUser, - tunreadGroup, - callAccessibilityLabel, - goThreadsView, - navigateToNotificationOrPushTroubleshoot, - goSearchView, - goE2EEToggleRoomView, - threadsAccessibilityLabel - } = data; - - return ( - - {hasE2EEWarning ? ( - - ) : null} - {issuesWithNotifications || disableNotifications ? ( - - ) : null} - {!isSelfDm ? : null} - {threadsEnabled ? ( - } - disabled={hasE2EEWarning} - /> - ) : null} - - - ); -}; diff --git a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx b/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx index e7ee226b2df..f1029284d86 100644 --- a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx @@ -1,11 +1,12 @@ import { type ReactElement } from 'react'; +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; +import i18n from '~/i18n'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; import { getUserSelector } from '~/selectors/login'; import { useThreadFollowing } from '~/views/RoomView/hooks/useThreadFollowing'; -import { ThreadRightButtonsLegacy } from './ThreadRightButtonsLegacy'; interface IThreadRightButtonsProps { tmid: string; @@ -20,5 +21,14 @@ export const ThreadRightButtons = ({ tmid }: IThreadRightButtonsProps): ReactEle toggleFollowThread(tmid, isFollowingThread); }; - return ; + return ( + + + + ); }; diff --git a/app/views/RoomView/components/RightButtons/ThreadRightButtonsLegacy.tsx b/app/views/RoomView/components/RightButtons/ThreadRightButtonsLegacy.tsx deleted file mode 100644 index 8aaefcf5370..00000000000 --- a/app/views/RoomView/components/RightButtons/ThreadRightButtonsLegacy.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { type ReactElement } from 'react'; - -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import i18n from '~/i18n'; - -interface IThreadRightButtonsLegacyProps { - isFollowingThread: boolean; - onToggleFollowThread: () => void; -} - -export const ThreadRightButtonsLegacy = ({ - isFollowingThread, - onToggleFollowThread -}: IThreadRightButtonsLegacyProps): ReactElement => ( - - - -); diff --git a/app/views/RoomView/helpers/getRoomHeaderMode.ts b/app/views/RoomView/helpers/getRoomHeaderMode.ts new file mode 100644 index 00000000000..71e90274cf9 --- /dev/null +++ b/app/views/RoomView/helpers/getRoomHeaderMode.ts @@ -0,0 +1,28 @@ +import { type RoomMembership } from '../definitions'; + +export type TRoomHeaderMode = 'none' | 'omnichannel' | 'thread' | 'room'; + +export const getRoomHeaderMode = ({ + rid, + tmid, + t, + status, + membership +}: { + rid?: string; + tmid?: string; + t?: string; + status?: string; + membership: RoomMembership; +}): TRoomHeaderMode => { + if (!rid || membership === 'invited') { + return 'none'; + } + if (t === 'l') { + return status === 'queued' || membership !== 'subscribed' ? 'none' : 'omnichannel'; + } + if (tmid) { + return 'thread'; + } + return 'room'; +}; diff --git a/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts index c17ac49c706..1531d94a1c6 100644 --- a/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts +++ b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts @@ -47,11 +47,11 @@ beforeEach(() => { }; }); -it('holds the title until the room icon is ready', async () => { - renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'p', title: 'Held' })); - expect(mockSetOptions.mock.calls[0][0]).toMatchObject({ headerTitle: '', headerSubtitle: undefined }); +it('sets the title before the room icon is ready and adds the icon when it loads', async () => { + renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'p', title: 'Shown' })); + expect(mockSetOptions.mock.calls[0][0]).toMatchObject({ headerTitle: 'Shown', headerSubtitle: 'Topic' }); await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('channel-private:title')); - expect(mockSetOptions.mock.calls.every(([options]) => options.headerTitle === '' || options.headerTitleImageSource)).toBe(true); + expect(latestOptions().headerTitle).toBe('Shown'); }); it('shows plain topic text and the room icon', async () => { diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts index d96937974c4..5012c4746f0 100644 --- a/app/views/RoomView/hooks/useNativeRoomHeader.ts +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -151,13 +151,12 @@ export const useNativeRoomHeader = ( const roomImage = remoteImage ?? glyphImage; const clockImage = useHeaderIconImage(enabled && showClock ? 'clock' : undefined, colors.fontSecondaryInfo, SUBTITLE_FONT_SIZE); const subtitleImage = tmid ? roomImage : clockImage; - const titleReady = !!tmid || !!roomImage; useLayoutEffect(() => { if (enabled) { navigation.setOptions({ - headerTitle: titleReady ? title : '', - headerSubtitle: (titleReady && subtitle) || undefined, + headerTitle: title, + headerSubtitle: subtitle || undefined, headerTitleImageSource: tmid ? undefined : roomImage, headerSubtitleImageSource: subtitleImage, headerTitleStyle: { color: colors.fontTitlesLabels }, @@ -165,5 +164,5 @@ export const useNativeRoomHeader = ( onHeaderTitlePress: onTitlePress && !fields.disabled ? () => onTitlePress() : undefined }); } - }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage, titleReady, colors, onTitlePress, fields.disabled]); + }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage, colors, onTitlePress, fields.disabled]); }; diff --git a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts index fb48a570125..d0753f74c97 100644 --- a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts +++ b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts @@ -20,6 +20,7 @@ import { fromSubscription } from '../stores/RoomStoreContext'; import { closeLivechat } from '../services/closeLivechat'; import { placeLivechatOnHold } from '../services/placeLivechatOnHold'; import { navigateToScreen, type TRoomStackNavigation } from '../services/navigateToScreen'; +import { getRoomHeaderMode } from '../helpers/getRoomHeaderMode'; import { splitRoomHeaderActions, type TRoomHeaderActionKey } from '../helpers/roomHeaderActions'; import { useCanPlaceLivechatOnHold } from './useCanPlaceLivechatOnHold'; import { useThreadFollowing } from './useThreadFollowing'; @@ -141,7 +142,6 @@ const useThreadRightItems = (tmid: string | undefined, enabled: boolean): Native const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): NativeStackHeaderItem[] => { const { colors } = useTheme(); - const data = useRoomRightButtonsData(rid, roomStore); const { threadsEnabled, issuesWithNotifications, @@ -158,7 +158,7 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): goSearchView, goE2EEToggleRoomView, threadsAccessibilityLabel - } = data; + } = useRoomRightButtonsData(rid, roomStore); const { callPresent: callPresentRaw, isCallDisabled, onPressCall } = useHeaderCallPress(rid); const callPresent = !isSelfDm && callPresentRaw; @@ -275,23 +275,20 @@ export const useRoomHeaderRightItems = ( })) ); - const isInvited = membership === 'invited'; - const isOmnichannel = !!rid && !isInvited && t === 'l' && status !== 'queued' && membership === 'subscribed'; - const isThread = !!rid && !isInvited && !isOmnichannel && !!tmid; - const isRoom = !!rid && !isInvited && !isOmnichannel && !isThread; + const mode = getRoomHeaderMode({ rid, tmid, t, status, membership }); - const omnichannelItems = useOmnichannelRightItems(rid ?? '', roomStore, isOmnichannel); - const threadItems = useThreadRightItems(tmid, isThread); - const roomItems = useRoomRightItems(rid ?? '', roomStore, isRoom); + const omnichannelItems = useOmnichannelRightItems(rid ?? '', roomStore, mode === 'omnichannel'); + const threadItems = useThreadRightItems(tmid, mode === 'thread'); + const roomItems = useRoomRightItems(rid ?? '', roomStore, mode === 'room'); - if (!rid || isInvited) { - return EMPTY_ITEMS; - } - if (isOmnichannel) { + if (mode === 'omnichannel') { return omnichannelItems; } - if (isThread) { + if (mode === 'thread') { return threadItems; } - return roomItems; + if (mode === 'room') { + return roomItems; + } + return EMPTY_ITEMS; }; diff --git a/app/views/RoomsListView/components/Header.tsx b/app/views/RoomsListView/components/Header.tsx index 288e581f621..8d7b63fc164 100644 --- a/app/views/RoomsListView/components/Header.tsx +++ b/app/views/RoomsListView/components/Header.tsx @@ -53,7 +53,7 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string) } let subtitle; if (supportedVersionsStatus === 'expired') { - subtitle = 'Cannot connect'; + subtitle = I18n.t('Cannot_connect'); } else if (connecting || isLoggingIn) { subtitle = I18n.t('Connecting'); } else if (isFetching) { diff --git a/app/views/RoomsListView/components/ListHeader.tsx b/app/views/RoomsListView/components/ListHeader.tsx index dfef18be5b1..d03ae4d1ff8 100644 --- a/app/views/RoomsListView/components/ListHeader.tsx +++ b/app/views/RoomsListView/components/ListHeader.tsx @@ -23,7 +23,6 @@ const ListHeader = () => { if (searchEnabled) { return null; } - return null; const goEncryption = () => { logEvent(events.RL_GO_E2E_SAVE_PASSWORD); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index d50a94654b5..4443e421a9b 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -59,7 +59,7 @@ export const useHeader = () => { const nativeHeaderSubtitle = supportedVersionsStatus === 'expired' - ? 'Cannot connect' + ? i18n.t('Cannot_connect') : connecting ? i18n.t('Connecting') : isFetchingRooms diff --git a/app/views/RoomsListView/index.tsx b/app/views/RoomsListView/index.tsx index c99cd872d25..4e6354b34ce 100644 --- a/app/views/RoomsListView/index.tsx +++ b/app/views/RoomsListView/index.tsx @@ -1,6 +1,6 @@ import { useNavigation } from '@react-navigation/native'; import { memo, useContext, useEffect } from 'react'; -import { BackHandler, FlatList, RefreshControl } from 'react-native'; +import { BackHandler, FlatList, Platform, RefreshControl } from 'react-native'; import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context'; import { shallowEqual } from 'react-redux'; @@ -30,7 +30,6 @@ import { useSubscriptions } from './hooks/useSubscriptions'; import styles from './styles'; const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12; -const BOTTOM_SEARCH_TOOLBAR_HEIGHT = isIOS ? 44 : 0; const RoomsListView = memo(function RoomsListView() { useHeader(); @@ -132,7 +131,7 @@ const RoomsListView = memo(function RoomsListView() { extraData={searchEnabled ? searchResults : subscriptions} keyExtractor={item => `${item.rid}-${searchEnabled}`} style={[styles.list, { backgroundColor: colors.surfaceRoom }]} - contentContainerStyle={{ paddingBottom: bottom + BOTTOM_SEARCH_TOOLBAR_HEIGHT }} + contentContainerStyle={{ paddingBottom: Platform.select({ ios: 0, default: bottom }) }} renderItem={renderItem} ListHeaderComponent={ListHeader} ListFooterComponent={searching ? () => : undefined} diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 00b35003206..5167198368d 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -236,6 +236,8 @@ class TeamChannelsView extends Component this.goRoomActionsView(), headerSearchBarOptions: { ref: this.searchBarRef, placement: 'stacked', diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 193c8c9534d..7081d896c5b 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -45,6 +45,7 @@ import { toggleFollowThread as toggleFollowThreadService } from '~/lib/methods/t import UserPreferences from '~/lib/methods/userPreferences'; import Navigation from '~/lib/navigation/appNavigation'; import { withMasterDetail } from '~/lib/hooks/useMasterDetail'; +import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; const API_FETCH_COUNT = 50; const THREADS_FILTER = 'threadsFilter'; @@ -125,6 +126,8 @@ class ThreadMessagesView extends Component this.onSearchChangeText(event.nativeEvent.text), onCancelButtonPress: this.onCancelSearchPress }, - headerRight: () => ( - - - currentFilter !== Filter.All ? ( - - ) : null - } - /> - - ) + unstable_headerRightItems: () => [ + { + type: 'menu', + label: I18n.t('Filter'), + accessibilityLabel: I18n.t('Filter'), + icon: headerIcon('filter'), + menu: { + items: [Filter.All, Filter.Following, Filter.Unread].map(filter => ({ + type: 'action' as const, + label: I18n.t(filter), + state: currentFilter === filter ? ('on' as const) : ('off' as const), + onPress: () => this.onFilterSelected(filter) + })) + } + } + ] }; if (isMasterDetail) { options.headerLeft = () => ; @@ -386,6 +391,8 @@ class ThreadMessagesView extends Component : null} scrollIndicatorInsets={{ right: 1 }} // https://github.com/facebook/react-native/issues/26610#issuecomment-539843444 From 94110c871dcd2aecefc3c241ee625f0a18f04fe9 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 18:26:25 -0300 Subject: [PATCH 085/120] chore(ios): replace app icon with Icon Composer icon --- ios/AppIcon.icon/Assets/Union2.svg | 3 + ios/AppIcon.icon/icon.json | 68 ++++++++ .../AppIcon.appiconset/100.png | Bin 5570 -> 0 bytes .../AppIcon.appiconset/1024 1.png | Bin 57100 -> 0 bytes .../AppIcon.appiconset/1024.png | Bin 57100 -> 0 bytes .../AppIcon.appiconset/114.png | Bin 6320 -> 0 bytes .../AppIcon.appiconset/120.png | Bin 6816 -> 0 bytes .../AppIcon.appiconset/144.png | Bin 8138 -> 0 bytes .../AppIcon.appiconset/152.png | Bin 8614 -> 0 bytes .../AppIcon.appiconset/167.png | Bin 9506 -> 0 bytes .../AppIcon.appiconset/180.png | Bin 10223 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/20.png | Bin 853 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/29.png | Bin 1348 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/40.png | Bin 2005 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/50.png | Bin 2541 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/57.png | Bin 2967 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/58.png | Bin 3044 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/60.png | Bin 3126 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/72.png | Bin 3861 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/76.png | Bin 4062 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/80.png | Bin 4389 -> 0 bytes ios/Assets.xcassets/AppIcon.appiconset/87.png | Bin 4722 -> 0 bytes .../AppIcon.appiconset/Contents.json | 164 ------------------ ios/RocketChat.xcodeproj/project.pbxproj | 6 + 24 files changed, 77 insertions(+), 164 deletions(-) create mode 100644 ios/AppIcon.icon/Assets/Union2.svg create mode 100644 ios/AppIcon.icon/icon.json delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/100.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/1024 1.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/1024.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/114.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/120.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/144.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/152.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/167.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/180.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/20.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/29.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/40.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/50.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/57.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/58.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/60.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/72.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/76.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/80.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/87.png delete mode 100644 ios/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/ios/AppIcon.icon/Assets/Union2.svg b/ios/AppIcon.icon/Assets/Union2.svg new file mode 100644 index 00000000000..d026fb0b915 --- /dev/null +++ b/ios/AppIcon.icon/Assets/Union2.svg @@ -0,0 +1,3 @@ + + + diff --git a/ios/AppIcon.icon/icon.json b/ios/AppIcon.icon/icon.json new file mode 100644 index 00000000000..af29e1e3486 --- /dev/null +++ b/ios/AppIcon.icon/icon.json @@ -0,0 +1,68 @@ +{ + "fill" : { + "linear-gradient" : [ + "display-p3:1.06272,0.30074,0.40032,1.00000", + "display-p3:0.96078,0.27059,0.36078,1.00000" + ] + }, + "groups" : [ + { + "layers" : [ + { + "blend-mode" : "plus-lighter", + "fill-specializations" : [ + { + "value" : { + "linear-gradient" : [ + "display-p3:1.00000,1.00000,1.00000,1.00000", + "srgb:1.00000,1.00000,1.00000,0.80000" + ], + "orientation" : { + "start" : { + "x" : 0.8502197265625, + "y" : 0.04061830677339003 + }, + "stop" : { + "x" : 0.3342950994318182, + "y" : 1 + } + } + } + }, + { + "appearance" : "tinted", + "value" : { + "automatic-gradient" : "extended-srgb:0.00000,0.53333,1.00000,1.00000" + } + } + ], + "glass" : true, + "image-name" : "Union2.svg", + "name" : "Union2", + "opacity" : 0.95, + "position" : { + "scale" : 2, + "translation-in-points" : [ + 5, + -3.0004841536889444 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.3 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/ios/Assets.xcassets/AppIcon.appiconset/100.png b/ios/Assets.xcassets/AppIcon.appiconset/100.png deleted file mode 100644 index 7f24a267978e6d691d032c25024d53f83efe99e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5570 zcmV;z6+P;SP)aT`O05jJ#$6kOVFFlRcf;fO#tb$%bX? zYp^PnR_WS*Zyg+vwqjr^fX36N^6oJm7^cRkHiO>Y)Jb-3GC4Cdx!%@|-KnyYqGg4l zR%lX_Et>$be_^R@qphejzfYS0X)A{O^yldWB+D?i5s=J_bC&TgBg<(9kWL0?gySs? zYLkR?f@c?aHUVO?96DrTwCnil;=|LCFL^nZ5kDy?y>94;#~ty_fsfJh?H z7y^#gK{{6dgu-~Z@41mJu2(U5qf1lB8!$l6#(hv;egZCK_#a3hsPd3 zBoWH@+u^$Xw{XqAH3|l)A7($uX2fBn|D9A^+!K1_8HApG6+%HD2*U;eOvN1=4+o?r zL|gb>6rt>>g!7Vfk@MvJQDdzeZq0i0(yIw$2^>WQjNteKXgL2mMAnzXF=vg#6uFcwU+V$JH}h&xJVLLQlMc>N(G$%c-M)yj;z;1RH@+ z2na`jP#9{HBm#mY0pu(=9e~RPxN~B?y{z)+64_)QAT+kD;PC1-=)ZIUq|;B;m}jDr zETN^y0C{FK?00K%@Z4+Q76dqRa)4Yng#0|9OCH2Luf})+LY0AF2#U`S)cK*-)dPVb z)NllX%K>!BhuEVV(7h`=Kun!l_Uc;FB9?a5B9TQvEG~xBNFa!YbsN#Ewp`mZZJ7SI zH4ib2t0rZ~9%iQ;Kby#au>92dAaC0VavJ27>mje%2z752!$RoQ6Jq~90D)|@cL_~^ zmS;|oy%p;0W_v~Y8!&|mEi9JbSyp%|ru~Wuz>ivj(`W3?OeF}NSTBxBAq=Ef` zg5DaHWvNMNah8bf2ZW||NC*{|z&rPTIA>jxo|Nd@&`+>2$~W`gh@RPUZqS*{;Qvi( z&EyHc@jgN?&qsK31)Ti~APg?j-X}++ji1Z_LYoK#A(wAOe)R?$q$J77Pv%9%cz1Ce zHKuCf08+{}qw&7E2&^cDn*!AS1#Rwf#{pu|R#-VTlEjR0&SQ7Ob?1L*8%uIRTO2MX z-^KG_EKjCOLVS?Je~O8N^xT>WKyfa&vi^GnZkP>u`yMz>J6ZG8+DMIMPD1FE9ykou zA;DYvNsByBAkdlFS`d@XNS+_dc^VR&I&GX+NNOy2@8ggL6+sx#FN4w&6@<(ILYs_4 zplsZRyhCfau~d0Xkc3-J0V2;58_3ij53-z>vVJp^?Yp7w+YhBicWc91GJwS(E*7td z1-&5-E`oT{a7g1uLmYKN;-m~56Ui|G<8Od}!X;3SR6{!T zwQO@J48h}pFmxcC=S_w4%8MbKFf=Gke3AvwkYuH+Ynx|435d;GYU#(0g*&h zf4d4ryH^0cdYA(e7pFwNScdR(uR~t79;&KB=+y&apPpKj&KPg-nwoQhm`Y-2P7458 zNuUC<1RRL{0`l$s8bL)K{&8^MbSC zp8rgux`^9&c>W@U{`>;MKOTg$e*pvv*eKg;X90_;Cmx|!*FxS|2|dx|NI)cplbS#xlLjZi3(uk*wG(Cu*D%Oxg0?PEAVl=M_KRX{xXc$2zK zE7!wcvmX%JXuS{e)}6>dvYs1rL;A#^qC&GDNA)Xjq06u#Kw%$4`XaqeMc=mQ##6p6 z(1^&|O^^okgSX@(fNWI!V_18Azlo5@E>d1B<*ah@b=ePyB*MSE6g?Kd32E}!*!aV+ zp@uUqMYy~IPNLC`x?MP!j$;8Fhs4r84VC-QW9i#kf;9f&@f79Ns!yMRTq+IN7^Bz= zh$unXu^ZCxA@F?mkEC9Vf5;T5;V{I}BbaL(`&eLHCV?Xx(-6R+bsJFh;cJklO-?#n z;HtYIe^(0e#N#Z2((>MH21G<~#Ts$W1=MV%+oe$yiZRlFJiQB@&z&3EjEEcR%jAMuDvJVh)b62i~=Z(L?F=Kkv zcJR|g-qilnFG066Cp7OaQm7&DtSoB_N+v~I{7J!pWuDh@33~k;VmtnS+?S+{@+XD!l&?85oI-KyX{2)3HmkxFJUK}0$3%FS@ zm820)5FnG=Ic6kWH_imQcp?Am-w40`Aw)`ZP|4QlUJ`vt860Phhx5A2SQHXOO|;Kzqc5XkCs59j4svS%^vEg*|ZIg^Us2N-d{~g z0_R)<_0SQmz?HHK9Ou&5a;1%Ro$P^Q%6Pb6e=<4)7wgbt&m;8Wn-FQ^W-UF`AhBj6 za_+tfuKQ=jtdLH$|FnxBo^%3`L7%^z2I3oU)wNwUnKE7^&WZW zBLsf`D5SBY8Pk+K`yh=z5uShkk4CS9%;`hV{R5%7FKM7q=A&X4oYSWu=as*h(l`F` zNrc~81aai?=7yP)q^rx2w{w{m2uL++x# zLz+4vmR@(WLt*&(PlPyj6f^jgsPq=Ur!m|(K4&PMe&2|5AdMew4hU;;S_AL5?`g4< zJ`aCC{OUi^_~4TeZAj1C3ka#4dv4}{M7nR#JxCcbMnIQ>kWN8m)2p8svWYwgy@}AoCWW@!`KU1Bi74pCTaoz;P3h%G$gg&dV>-Iv3+o z>VdPagj!t-6q3=4vx{VZw(o}X+$qR;X^!@z&J;S$E+?p@>i_x7rc{RVeFdD?UWS}G z{~b%utALTE#R$y21JZ<3n8!t~tLxrd;kxUFSbE-`6nXa(1nztU5=m4N8>g&Z2k*MY z5C(CzOYd@ELUUh6==nDwo^oQA*bwf1k~ie};O_}uR`9k55&8Tph$EWU(YebOnpXDw zCryVy$v$dwBQJ(r?6S@9Zdw9?l9F+cZ|GEreS2yBTSOlwmwX%YD!*MK3nuZPn zyq?&O6-Fljjg3&Y?nK`Hl31h8#|hqaA0pqZ)LitI;-G}tWB-t%gd`b_c_Q^tYkly3 zzcjjNS{#Y{7t7$k{I=xPHWIjoAY+>A;k)3vc_!e^MdZunXuR_gcIsGbj=ou*QC6ti zo|-7Gznn=@WbrqkB9h>BYcYe~#zXW}oT{vX`^g93xZ*-)r>Ghkyz>!=1--NanwiVW zV;8Ys0X+Z5oetWeFΏ_b(VGnL!|0P#O30mQn_YZ{RE_U2W5!|sAGaV8InN7255 z30m-!r=LROQh0SQ9EP%ahqhT_1U0Y?8K`YE{^ap|ld`!&JH?{F5|$Em%XjON80_(` zN_p!J?eY-uRE|IzK9qr!1haeESa_H_gwej1K5DUa1mQjuyCdUG2AN~gTJV2kK=Noa{)Z)iw` zo^FBF)WW-CS**I!J*?LkA~^eTNE61S?ef|S2yM2u4(ic*c(;BTT|Apb^MC!rrn1_& zP)O>8BKA}zF;)5$^+i0nB;iQ$D)j&6J&0q;1LN+zp>L0ZR<^xlDlf$)*e4-eN+Qck zko(rNrU^#W)ZvE#6Va2d7I-MN(e|yHmigmwAn@%<dZ_4+bmgw`(Ptr13D?FeclHB9s})7mttdgC7TW3xCb z+YTEHbhm%qnUN4KCF&19LanYr-jAzIML#g(W|p=Wil}I1#|Hx}93s8Rnn(}VK;FI! zLXHcr2X5E8^vxba=e~c)RKSy?<;84SbL@nf1BBTV5y9e8bT3~LO}!hgw*{`fn%M&{`!mVKo|_s&`XKrij;=HcS5rTB+V zVKo+k0^DSUHd90`92EYZ2_VLotq)|r4r;dEfV?eV0=;`CCLJ%I1xggMR)4cP3EdCe zy_Ypl3vL&LuK5rsKSeIH)5)(C(9Ar~-*jt=KvMvJfc;ZgTBi;O$q);BLpoA{b_zX|Yi*O~xlv+|;JIqY`=hRz_785>l3UpKSR9j1)3U&A;CN&X5J^ICc?r6H@;W5C%B&kky?tioIy9Vn zZEX9DLux zN8|UmVya$-@2VJW0*%*nwLx<@jE}ni5E|#afXMtsEc>h7xZ>`5O6*Nf{bxo(BndUe zt58__nRZi2R|m!#dEo@Do!=jTykig4h9Epo@N3k>fH)P>l#C=}tyzi058A|Vm?6MP z`PBmx#zq!?itxMz%-dsMdN`g`sM+VCQbMKG#P$Q?kRTVA!g4t0h z!VhtRD;Gl7E^y7f7_PtG8>KY{WbrqeTD?^b9QuS*_={zbKl_^XX;IBZqzgiHwSXil zC3n{9VysJzy?{_nq+%BYZ*JmO9CQyT7(&COOX0Zmd`K5hV~ugKy2Ieb8lF-eSOO`cdB?u5GUdi=9?3%CD*eQmgwYD1w%}a}YfHa1n?A(LA zJu6!N*bKX%!wXb~GH=t@dGx#K4FUKkT?S?Een=CjXUdRU(v3QEMYTo-tsRt?jhQ3G zy(!(8$2bC3)r}o=ZLP9MRc+kDzS_V(?`LdA(9;)Y`_g@TVpyEl6MEr)5q#hYNQ3%A z7|`F=6t^8L&eRz8NC@v*7fu=u&w}R@*Gd32eGrCwM@c@}6j|^I8vpzp%Qvyq26;Sb zCGo7E&P*{mm2vF>gzIZ{*AJ}yhT3wZi_V5Xt)mtm^2);Re2B>0qJyG;Nv__HhtyFKqTf9NTimSJ<(NLRvgxL zS%m$77`MSv7u?%1dOb!DDI=25F1)m5(#!%9(|48q>cSR|ra4FpfOIl3P5!3*dWIz= z<@Fr%FK|HGQxe*9ZoBbMe=i+KLek%?-R#fAcqssqEF~C$2vQK)Czb zj$eIFm;D{#{S*P|2wUpdYitKVvQJI4456KCZmW7o%e(5Nu)u*7CB*7Uu}WuQg{>yZ zhBc(V_!I$2eS*$6l5xQNz#QX%WSn2_2g$sW8xPTAKi|ds)Zs zc%6V)#$K0tJETAP7@dGv2Bd8W(*(qh)6zB@?I^RP2}noTrX7jVw#ioiKh2}aAjwgF QTL1t607*qoM6N<$f>6@5MgRZ+ diff --git a/ios/Assets.xcassets/AppIcon.appiconset/1024 1.png b/ios/Assets.xcassets/AppIcon.appiconset/1024 1.png deleted file mode 100644 index e737a61ea6f4767936172c3271ff4aa0e125305c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57100 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 Pz`sk%+DdN~%zghKA>Z() diff --git a/ios/Assets.xcassets/AppIcon.appiconset/1024.png b/ios/Assets.xcassets/AppIcon.appiconset/1024.png deleted file mode 100644 index e737a61ea6f4767936172c3271ff4aa0e125305c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57100 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 Pz`sk%+DdN~%zghKA>Z() diff --git a/ios/Assets.xcassets/AppIcon.appiconset/114.png b/ios/Assets.xcassets/AppIcon.appiconset/114.png deleted file mode 100644 index 7b5b7e31f0716c6c4c51566aff681ae953c8773c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6320 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 diff --git a/ios/Assets.xcassets/AppIcon.appiconset/152.png b/ios/Assets.xcassets/AppIcon.appiconset/152.png deleted file mode 100644 index ece9c43b4ed6ea6ca0fd1f190d38cd4f418f085f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8614 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 diff --git a/ios/Assets.xcassets/AppIcon.appiconset/167.png b/ios/Assets.xcassets/AppIcon.appiconset/167.png deleted file mode 100644 index c10c5fae9ab311fcbe3893742bc540954174f316..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9506 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%0D;FmLr+mj{=$DTn^uin9Pe0W>p+aFM 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 Al>h($ diff --git a/ios/Assets.xcassets/AppIcon.appiconset/180.png b/ios/Assets.xcassets/AppIcon.appiconset/180.png deleted file mode 100644 index e883fea3111ad31f0552a66c1f8cd814fd7122c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10223 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 diff --git a/ios/Assets.xcassets/AppIcon.appiconset/20.png b/ios/Assets.xcassets/AppIcon.appiconset/20.png deleted file mode 100644 index 580f31abf68debee42cabbafe4ba4ad8a2663e31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 853 zcmV-b1FHOqP){zg0i?W%W*EH+`erhgEDZa23=O*TL}y^`uk=^kfsT9V^b31?qi4 z{MibZvH(NJN~XXxAW(yBSp{Pdk5Ui$tLxPZD2x=!$ioruN;tkejlKhID~4p18o zfu$f6LM9eKQyh?BgU>h>z;fCuH9~!=&*P%dkDY{I9il0Yl^n#l+=u?4pPJUih}M=_ zpl*gzqDG&iCbMW`cVva_QCs=R;#KGRxLG5w+o)Fv9p|bg*BB$OZ8FQdxXgo=g^uK zV!eI~N+5zY1XgttK7Smv2>GqMF&{oger(gVr`^{Si5hwab+yz#>fvRylg6KaB>4Sz zbBqvVySh>16Ua|ifMvS!GQE1Hi5eNFe(<^vpmg*P2m}y`1z3YmF)!bs^xqJ{_EuQZ zG}mBP)W$HNSfX}YH)2r&mCjK5^*Gk(I3lWnvLGBP*EHT(ww;~!E0b^aY$xl<5adfv zR`SCZXl!uBK?x6#S1(;@^QcYbT|l~&?Ls^9HB(UMv!_>YZn2!R8?)5iui6#Gu3pZk f&hyFsEb-!hR*!DZQGC3600000NkvXXu0mjf41t!@ diff --git a/ios/Assets.xcassets/AppIcon.appiconset/29.png b/ios/Assets.xcassets/AppIcon.appiconset/29.png deleted file mode 100644 index 075803b018db9546af93b0c0fe3d0380a447f770..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1348 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 diff --git a/ios/Assets.xcassets/AppIcon.appiconset/50.png b/ios/Assets.xcassets/AppIcon.appiconset/50.png deleted file mode 100644 index 4b68ad4618eca36d464da6bd3ce3c91bb983aba7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2541 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 D7ec^( diff --git a/ios/Assets.xcassets/AppIcon.appiconset/57.png b/ios/Assets.xcassets/AppIcon.appiconset/57.png deleted file mode 100644 index 5af4cfb70a51d3726d5cf2a028b8cc24250d1149..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2967 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 XBelefF!6qv00000NkvXXu0mjfmS;x} diff --git a/ios/Assets.xcassets/AppIcon.appiconset/76.png b/ios/Assets.xcassets/AppIcon.appiconset/76.png deleted file mode 100644 index caef08f4d2430ebc687fe23e1d714b0064b3c8d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4062 zcmV<44b5+7=gF>TyG~#kx|( zy%mdsipVO762iX85(p4N_V@DEnd$lO%;e3yyd*R4y+E|yb2taWKllFkyWhXw3(P^o_-0ORbn4z#it#4WmT>0u4^|Sv_1b(W>|XwqykQB?6Etic^>8;f1T2K zQwsYg)6rja0)}<;7SqeLGy;|y)sGfz*4I_{mqx&>FKyp(+nKv#WseeI9lNRY9*Eg; zex!ieIgof)VmYn&|L{R}piCrdlVReR=m&J~3P_Tz zIXX_*+Tqw|7y;H4fYRWDx9n3@|7a8tMfLZ(5R9CyyuO3pHl8%g&C0);?gQhaf=NSq|i7!(05Zsu^vdMU;$FntT@p z32g)>O-1#hCCJUp1R@d`fhve9xO%{)!;oYsiUQ^k0j~!_UM@FnxLxXDSTgVQ0GJ$L zw6-Wh`eqHBmybin8&BKQhRjf7WazOMA#}-wFlrct-i2UYa)AtYY(8RhbuHxb{gAip zguHemqNN+4>?ns42!eI(0-tg^}$XgS2=hB5!?+=%S_EMTo~A3lw%$(-$63+6yZ7&Y3#}YaQ2CCMFZ~H$mFC z9oe;I)=g8NjzHtNSzLk}pZ^+{*L`K2O40WhAoTc4h;02Hu7Sq`-3mG+pri(vZknss zzl5*)ss(|#U_!u4U#&uL+MglsKM3)(lVd)vO)^=F zkf}3_&%l71Iw+wKyz3UW%`(m?0#u8rt!;v;1(I`V5wbNoI-KU{X`VMcb2jQ{z5p-f zeEE4PB+ItD6&BEsL?Lh7hOGU?07bHRUzW&U$ugubS3>%FHKejl+^H)KjX)@@ni3TQ z1gz5mMh?BO0OF8=5KkKk#|3ABbuTc(NNegp0~QBfrTPHyh0B0bDK)5U)dtI+TymCc|OC zAAsD@2&K^vCdpvO^@4NK#c=-a1~sx8{i4o%?PvZ$<9M#f2J~$MGz-K?+3D*(taJm! zQ;Oi3|FT)}X6MZizVAtd=e*595{~JqGQ>^kgCSv!cmDcp#>7=GGPQXGMq5gkb!9ra&r*}_veXc zS~?ecr$m+^@7Rr|s%qp+nS_j4GgLkF;9~%-F=r^m;e#|C5~UpNvn(}}lw<@;)*`=t zL(G40HmKAJTsal>3%^Frh`~U1R)>UnzL1vtQE@M1d*R)>*qr=ECGK0L2XQGm2!4ZN#9fG}jB$|47)0swcc)Uu&Yf(Z!5O;o9==RalCNIc;&gMDkPMdI~|D3nTOzAk3b~48yka+K`Pq}$AxFZ z{lIVeI-!{_Ai7{N#NmV0%?b?C`mJz`9R>G{JGs-2JpE6E=gn6SK$D@4g)ry@WITB< z&)>o?&PDj;c@ReqZ3*n23b5Wi;Q3;%s;3q)mEHT`8+j?jF-5lhn0c?E>tJ&?KZ4YSPiuOs~EEC>{hjpj)QyKN`34wRSxqd)k1jp4xS zmSNS+CI{G|8Xz|do+a~4>-dW<0VC5a$cvq}AVB(d9kNMj%Hs8IDs@sx@opm_oH7E^ z>QZEF{!&e7xeQa~`Q}cAP;?TcZ`Q)Q>|NCjgd?1l`i6ad&xFwT7{Kj}b#(*=d371Q z8yBfbt(IQ!IZ3`x@0PBk9psV(tQyGi!t?dJX26DD1V+t2DpmA;6l0LrZ;3@y9yMvB zY5~qqdc3 z!;-%#g?HU&yp%El*6&OR#~mrasHXD;;9dWj*(AsP67r$LV7&`t&YU9Zx-D?sJr%Co zZ!nwEtk)5KcoxJ{iy)P5f^*75xF5dDBtw0!z_bV8IQ3+nKRGWr3+~wun`ERV-=OKr zsSrmGH=ATM3VGdDWL1`^;Oh*NB=~yMLFxAiRt+-Q(ufqH?nLExCZ`{M@M(l!n#hdOaNj!(uHXNf%S7M(FM_u{0Aa|9 zs(F&k+D&lXGFc4_B(q=%f;UcwFt8tx>5iErF|?`%iXy{Xy1?Ya=m7m^Uj^~>k#QM+ zqM&EHy^{ca%JNrz2hWFphj{*2)#o^!P-^Q@-DfO3V@J1)K9U4^({{k=;=YIaXX4-i z=H8fQzT7V{0SXn?!v>liGs$e+t_F061B%}dk$PT6ut;TI3VjRrqtoHK?fT{jNq-f7 z>_vp1eHB8{VB6)IeIG_EQsmoN4sp~_WWN2p3DMw`KOnMj2^?dJpipniILP1VKchS? zQGd#c4EZiPBuX`C9^Sn*EJW<8ZGg0EFS4sND=@T~{3c&(ukUXWu*4U-kyUGuTfbg) z(1r-qSWu+4aC%ZEoqE&Gx`)v^zyd?%@?!K^`o4PaNki*iNGrcX<3-oQbq3{%_I0xe zSOS%aWm|S|KM>C{H26@}`&4*O=%e1^ba>&d9Vjs`8bRgiH8|$Ehv59xRV@_v54{kK zTHoFBlhP}Bz(^HQfPX~^@)!L}?MfJjuL-`Qiy;tTS>B|Sv&<~QivmYQC2Dr=Mvpm9 z!tsmCT6`XU{3V2D&eoiLa!(Xe15CeWTek(-4I5f07+ts&fyuW+9FwSKvkXV;f_f9t z=SQ?`GsNy);aU8ansdbsxok5IojD0PXPlCfn}`F7A2lM)~gLW z`Z=+T7rF7ijIwm&TL~-p@lUAZp}jXdR1IloIZu{dci#%voj1j1FBKv}_8h%YwbU4<&m;&4t@svQb}t9$jhNQGpcaa6(D^L^qexg$3EAg|klzLHW~ylc z@6@*Pijgit0zuyGkgIFJqB6hKa811y&Ks|e?MXivtqV)}U)|YPBZJDR+-wVA+3HqU zIpzd1)J)vI8(9amW}*g|ym<%wXI^OrjPj%6GB__j53GM5L_huaj-25`Qf8LEOzI4yBy`_Fu>3rD zmTI-7W|GmlA0cqZ3=J^#0ZMRr2{JD@2bu5BHtm(nMy@yr9IoZB7xf&AuF!;Dg)uw#X`bPLC+=OW9Mzyn=#@9Uw0JG`G93leCOOQMF88{}4H)Yg;Nw-4U zv;*S6eo*QfP`9QG`PWQD#y@5zNoDObD!gfE-HhhRWEsIbW+3wFdprjeD6P?-7PNSn zVsoF`BB?Qq*i})xJIl-Fud{jojm8lu!;%Ax%AyTh;rRJw$aw00YiFnT`1OY( zm50$ZdIA(F3YMD##pma5C=~r6iax8zz~~#78_b&lmXoDMJpGb|Kb}iIud=3mOv*lB zyrq&<|QMAI0-sJ-&HIcS*i3_Nt1TG&l{A4}i0^^(r; z2BR6EXdTxV}KNe5{`n=Elyr8gdW`hPi7ilIwu2cGT)ft zYz46Pd)v*^@N0W5J)kIMyhbw?*he6O*6)j_Oh5gLw7;<*f`p1o zCuLaY1@I@pY@SUT0JCbsR^@E&z_xg5db9ypY7@7{(^9Rc9T_>Oa4#M)1$OOGU3e0su4O&5lb})b4wkSTLVkNCeQTLRQCjrFbS}WC9R- zGLitaC&d0+p9k>L)4>9O=}pu%$h{%c{&w1V(*l5Y#DAV*RwndMm2rUq?43mb7tfMS z0C@3~<8T}2zu3B@ZGjhZ0T*&E`_+L73+yeoyAX1Fe-jJ<_Woq%Ap5-NVN%=P?L^Ee zaVd}bg1d0?Wyd~5{_r*;ufGfRbQ7e}BY^&W0a-S$ zzp0VE31F%M_AF48pwym#WA033{pv|m%Q6m%zV!h@KUfK+u?f<&iQJ_#=GJD^$sEWg z0PJEiQ2-jwKpa0BS-<;FuCc5D^mB@b#zzN+VxJ^xF3ZP+SFFOh=hh&nbTp9P$MA1j z^3fBKo|Iw<%8;Bg3cS8n^9+y)0K3^yNrb#@7o1BLAnO-DHnZ(oVpe(llO*$5+S(VfHxZ=flj|D-ZCrJw zoPu(=7V_Q$Q1;hAr34j`A@t3ISX>0;WOE>6n~*to?P`H00qg-ARYmL8zoNKy3xs|J z+`7mj%sy7fC(8|n5dGj&L|%Ir@}|$AwzWen83bX-AU=}h_u|t7fQDi?ra)KmP=s37 zz%sD6>g~9jCdWcnt|Y}rT`pM*#ZQW(v9d-Rn=qyB+JY8OQ;~^o*o4TlYZ2VO6RzSS z2*ZbDxb{q(Gkpsrc@7Ew%@ydoehs9#GdK_|VVHc#otevNAb0m}3}CI}(c>b$mh_H# z=oo?z|0@Dpw!<@a6hI-N7fL3`n8iz802^yIi&jq?N7XXJ2zEG`|;y2F@(DYAk)vTHva#f8W&e;oobL;7Q=q=FATfykThL;4aW7W=iL z4FC-*pzK@Mz?971{A%2F$z`-++JEJBP}+TvCYNaxmRb5LjSj*gF}-}p>(T#DYgwF( zgBb6Ce8XlyEE>q$L7P2w+5iT(Y)3)OC-KC=SZVH79sMRk>|X%k%3-`6yO0lUV4xFc zcI-yUAJ;&-VOD&0EwRWO%hB<755P5ZvTawk51>~2iDRyW=a0WhDP8nB4o)MxSbAC6M>P zruar{1$X$@ze8xnvydiDFtO38VZ}axp)i!1I^@)U#+5SETkW}5!!cz7tLqXT0^-bB z$aNqfavemleP?e`7P$&LBL$T?KJ!8PtYG1n+wca`jX)ZyOKPAm^0UHX26yXWb6L z-w9B>Gsd(?lv#C7V*^P~Oe$C^DM-VKRbTMu#acz9lZF42YO z`~%dc7GQ8;Tp6;WmUDnZME2J8DVlYDbrIAv%|LMxH;o0!tFmW@Up9mi5d-fbueY7cT6|H@sAVvxhZvSIU>IqnfH#&5T{ahSe#F!My z-r_jQnp%rT2HzQiyzek1id9Z0OG?tHVL+at<)X}btRAXA0CDILC}hFGAjHzEQmu$; z){0$lfA4O%Ry-U(I*=p~bM2clAJX(m7Rwlpg?#`?sjWNUdH$zx+;v+*91PsD1WNT0 zUb`jJ=md5+%(61OH$(9`toi{@6Ss=@*@dkkkGO}VA;mW|~Xi@M?u^z)3K;;-IaoUg^| zX`I7>vzxb~XwA>zxbtgVPXj;b8$1mn_3AxbOG)(V#GGIsK$0p+2$xqP_w`>xns;4- z6_U%;&Ak(a*OYPP&If|U*kfwA!vWvsZRoRT0kZz26JsHxws7s3@RIlsQK~;b0l)E#KH1AX0B^WIHHM zHJydpcm_&m7=p_Q^vQupAUmC`uVwWmVN07q^1=~T_o?lEe&bbDAPgP^>87v1{m^0v z!%O08({FScu9$S*dN-n*Do{p z=E2A65ty?OY9tDA{1_9&srlHF84ILDQhfxD+0&5q(l7YznguF5s^PzWp~ep?3`Awe zUI=-)afZ03i$`YHq8IEMbECf&T=pb@0A0c$o}avxR&3W zn5hl5W6XN>!aG*?Y6&OF!i*RzRn-Wuei_j>--oKGkf@cUPRK@!X4deN0JLa@RCl$U zgM73Oxh*?2P_&*^5*f$0^_AB!WyDBGw_J~`fBQj<9F27*h9GZNX?>j9bQba_+Yo*C zL&zUjKxu4dXVl^qC0dUyXZ|aSR2Q~cNHf+XjOHDCP_lm`zY)q@S1U;X&=m^lE7Kqv zvm=Y@8pvA55Pjp%XkW4%?nx6MT`>d#-NG7B0Kw~J^4R4f`sSpmg_SIHl}5LS0EEjES@e=2q%`}Nh3=FBiRwZ1S09QRGSso%Ql1>}ys zie+8J=LbR&AQI88p=n2!l!+;ed)yG|{Dkg8lY6pT=-P%{gnrvz45aTwYHH#9=B;p( zTk2U?FNpQqa>g5_NeP0!U<%B?2g<>tJVWb?Hx|k%+hY9oPX7ka+}`!pD2o7UF2NY- zR-kYIvJH%|86~Mt4L$WsMEuTbK3$-cndA?u(n!q^2L;i*Uv}&FD_@3YUf&pk1r1Z`43kG2NfLN7q89=Lu zC5^uf;AH^QL?F%o)9bPYTEagyNo5GY-p+o#AwM%RGX$WeZOriC%mC4~QsWGUIk1dOV3y&i00000NkvXXu0mjfVl_pi diff --git a/ios/Assets.xcassets/AppIcon.appiconset/87.png b/ios/Assets.xcassets/AppIcon.appiconset/87.png deleted file mode 100644 index 17381abce125c774d3f392a0a03d1026d924d217..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4722 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 A4FCWD diff --git a/ios/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index cdcbf4fd039..00000000000 --- a/ios/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "images" : [ - { - "filename" : "40.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "60.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "29.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "58.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "87.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "80.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "120.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "57.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "57x57" - }, - { - "filename" : "114.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "57x57" - }, - { - "filename" : "120.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "180.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "40.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "58.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "80.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "50.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "50x50" - }, - { - "filename" : "100.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "50x50" - }, - { - "filename" : "72.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "72x72" - }, - { - "filename" : "144.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "72x72" - }, - { - "filename" : "76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "152.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "167.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - }, - { - "filename" : "1024 1.png", - "idiom" : "universal", - "platform" : "watchos", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ios/RocketChat.xcodeproj/project.pbxproj b/ios/RocketChat.xcodeproj/project.pbxproj index 2040d68230c..3dfbd7ab355 100644 --- a/ios/RocketChat.xcodeproj/project.pbxproj +++ b/ios/RocketChat.xcodeproj/project.pbxproj @@ -154,6 +154,8 @@ 7A0129EA2C6E921600F84A97 /* MainInterface.storyboard in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6ACB522CB9FC300A41C61 /* MainInterface.storyboard */; }; 7A14FCF4257FEB59005BDCD4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A14FCF3257FEB59005BDCD4 /* Assets.xcassets */; }; 7A1B58412F5F58FF002A6BDE /* VoipPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B58402F5F58FF002A6BDE /* VoipPayload.swift */; }; + A1C0E0022F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; + A1C0E0032F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; 7A1B58452F5F63DB002A6BDE /* AppDelegate+Voip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B58432F5F63DB002A6BDE /* AppDelegate+Voip.swift */; }; 7A3704572F7DB36E009085FC /* VoipPerCallDdpRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3704552F7DB36E009085FC /* VoipPerCallDdpRegistry.swift */; }; 7A37D6FF2F896C360095EBA1 /* MediaCallsAnswerRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83F98EE33D91A93DF8E69F34 /* MediaCallsAnswerRequest.swift */; }; @@ -438,6 +440,7 @@ 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 7A14FCF3257FEB59005BDCD4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7A1B58402F5F58FF002A6BDE /* VoipPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipPayload.swift; sourceTree = ""; }; + A1C0E0012F90000000C0FFEE /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; 7A1B58432F5F63DB002A6BDE /* AppDelegate+Voip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Voip.swift"; sourceTree = ""; }; 7A3704552F7DB36E009085FC /* VoipPerCallDdpRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipPerCallDdpRegistry.swift; sourceTree = ""; }; 7A610CD127ECE38100B8ABDD /* custom.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = custom.ttf; sourceTree = ""; }; @@ -530,6 +533,7 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FB51A68108700A75B9A /* Images.xcassets */, 7A14FCF3257FEB59005BDCD4 /* Assets.xcassets */, + A1C0E0012F90000000C0FFEE /* AppIcon.icon */, 13B07FB61A68108700A75B9A /* Info.plist */, 7AAA749C23043B1D00F1ADE9 /* RocketChatRN-Bridging-Header.h */, 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */, @@ -1202,6 +1206,7 @@ 1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */, 1E044F992B92798E00BCA2FD /* Localizable.xcstrings in Resources */, 65AD383B2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */, + A1C0E0032F90000000C0FFEE /* AppIcon.icon in Resources */, 1ED038952B507B4D00C007D4 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1230,6 +1235,7 @@ files = ( 65B9A71B2AFC24190088956F /* ringtone.mp3 in Resources */, 7A14FCF4257FEB59005BDCD4 /* Assets.xcassets in Resources */, + A1C0E0022F90000000C0FFEE /* AppIcon.icon in Resources */, 7A610CD527ECE38100B8ABDD /* custom.ttf in Resources */, 7AAB3E42257E6A6E00707CF6 /* Images.xcassets in Resources */, 7AE10C0828A59530003593CB /* Inter.ttf in Resources */, From 96e535dacc0349007768e7909b98d33b6e4ad8aa Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 24 Sep 2026 23:11:48 -0300 Subject: [PATCH 086/120] fix(test): restore sequential Android back taps in go-back helper --- .maestro/helpers/go-back.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.maestro/helpers/go-back.yaml b/.maestro/helpers/go-back.yaml index df9b991cb07..7e6cd406ebc 100644 --- a/.maestro/helpers/go-back.yaml +++ b/.maestro/helpers/go-back.yaml @@ -7,10 +7,20 @@ tags: - runFlow: when: platform: Android + visible: + id: 'custom-header-back' commands: - tapOn: - id: '(custom-)?header-back' - retryTapIfNoChange: true + id: 'custom-header-back' + +- runFlow: + when: + platform: Android + visible: + id: 'header-back' + commands: + - tapOn: + id: 'header-back' - runFlow: when: From 75d0303238fc4deb3e9159f82314c7293924d734 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 00:20:17 -0300 Subject: [PATCH 087/120] fix(test): pop to the rooms list before navigating to a room --- .maestro/helpers/go-back.yaml | 13 +---------- .maestro/helpers/navigate-to-room.yaml | 1 + .maestro/helpers/pop-to-rooms-list.yaml | 30 +++++++++++++++++++++++++ .maestro/helpers/search-room.yaml | 1 + 4 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 .maestro/helpers/pop-to-rooms-list.yaml diff --git a/.maestro/helpers/go-back.yaml b/.maestro/helpers/go-back.yaml index 7e6cd406ebc..fe41d560f2e 100644 --- a/.maestro/helpers/go-back.yaml +++ b/.maestro/helpers/go-back.yaml @@ -7,20 +7,9 @@ tags: - runFlow: when: platform: Android - visible: - id: 'custom-header-back' commands: - tapOn: - id: 'custom-header-back' - -- runFlow: - when: - platform: Android - visible: - id: 'header-back' - commands: - - tapOn: - id: 'header-back' + id: '(custom-)?header-back' - runFlow: when: diff --git a/.maestro/helpers/navigate-to-room.yaml b/.maestro/helpers/navigate-to-room.yaml index 0f899464db7..af619b5da61 100644 --- a/.maestro/helpers/navigate-to-room.yaml +++ b/.maestro/helpers/navigate-to-room.yaml @@ -4,6 +4,7 @@ tags: - 'util' --- +- runFlow: './pop-to-rooms-list.yaml' - waitForAnimationToEnd - scrollUntilVisible: element: diff --git a/.maestro/helpers/pop-to-rooms-list.yaml b/.maestro/helpers/pop-to-rooms-list.yaml new file mode 100644 index 00000000000..2e0f2fd5e3f --- /dev/null +++ b/.maestro/helpers/pop-to-rooms-list.yaml @@ -0,0 +1,30 @@ +appId: ${APP_ID} +name: Pop to rooms list +tags: + - 'util' + +--- +- repeat: + times: 4 + while: + notVisible: + id: 'rooms-list-view' + commands: + - runFlow: + when: + platform: Android + visible: + id: '(custom-)?header-back' + commands: + - tapOn: + id: '(custom-)?header-back' + - runFlow: + when: + platform: iOS + visible: + text: 'Back' + commands: + - tapOn: + text: 'Back' + - waitForAnimationToEnd: + timeout: 5000 diff --git a/.maestro/helpers/search-room.yaml b/.maestro/helpers/search-room.yaml index a0f05199799..17dfdc0668f 100644 --- a/.maestro/helpers/search-room.yaml +++ b/.maestro/helpers/search-room.yaml @@ -3,6 +3,7 @@ name: Search room tags: - 'util' --- +- runFlow: './pop-to-rooms-list.yaml' - extendedWaitUntil: visible: id: 'rooms-list-view' From e7a9d5cac573d937458c84e611f2bf7816695654 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 09:48:11 -0300 Subject: [PATCH 088/120] ci(ios): restore Xcode version pin --- .github/actions/build-ios/action.yml | 5 +++++ .github/actions/upload-ios/action.yml | 5 +++++ .github/workflows/e2e-build-ios.yml | 5 +++++ .github/workflows/maestro-ios.yml | 6 ++++++ 4 files changed, 21 insertions(+) diff --git a/.github/actions/build-ios/action.yml b/.github/actions/build-ios/action.yml index 01728c71c7b..54d5eb89c2a 100644 --- a/.github/actions/build-ios/action.yml +++ b/.github/actions/build-ios/action.yml @@ -41,6 +41,11 @@ runs: sudo rm -rf ~/Library/Developer/CoreSimulator/Caches/* shell: bash + - name: Set up Xcode + uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 + with: + xcode-version: '26.2.0' + - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 with: diff --git a/.github/actions/upload-ios/action.yml b/.github/actions/upload-ios/action.yml index bfb05673825..5043327a44a 100644 --- a/.github/actions/upload-ios/action.yml +++ b/.github/actions/upload-ios/action.yml @@ -61,6 +61,11 @@ runs: restore-keys: | pods-v2- + - name: Set up Xcode + uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 + with: + xcode-version: '26.2.0' + - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 with: diff --git a/.github/workflows/e2e-build-ios.yml b/.github/workflows/e2e-build-ios.yml index 6bfdd887904..f290a6d4228 100644 --- a/.github/workflows/e2e-build-ios.yml +++ b/.github/workflows/e2e-build-ios.yml @@ -65,6 +65,11 @@ jobs: ~/.maestro/deps key: maestro-${{ runner.os }}-${{ env.MAESTRO_VERSION }} + - name: Set up Xcode + uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 + with: + xcode-version: '26.2.0' + - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 with: diff --git a/.github/workflows/maestro-ios.yml b/.github/workflows/maestro-ios.yml index c87463cc0e8..3cf48779f52 100644 --- a/.github/workflows/maestro-ios.yml +++ b/.github/workflows/maestro-ios.yml @@ -22,6 +22,12 @@ jobs: - name: Checkout Repo uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Set up Xcode + # Match the Xcode version e2e-build-ios.yml builds with + uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 + with: + xcode-version: '26.2.0' + - name: Configure Simulator run: | defaults write com.apple.iphonesimulator ConnectHardwareKeyboard -bool false From e96f669ee84dedff7f6a694737e0ad622b34af01 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 10:28:05 -0300 Subject: [PATCH 089/120] ci(ios): pin Xcode 26.6 --- .github/actions/build-ios/action.yml | 2 +- .github/actions/upload-ios/action.yml | 2 +- .github/workflows/e2e-build-ios.yml | 2 +- .github/workflows/maestro-ios.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/build-ios/action.yml b/.github/actions/build-ios/action.yml index 54d5eb89c2a..3b718269fb6 100644 --- a/.github/actions/build-ios/action.yml +++ b/.github/actions/build-ios/action.yml @@ -44,7 +44,7 @@ runs: - name: Set up Xcode uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: '26.2.0' + xcode-version: '26.6' - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 diff --git a/.github/actions/upload-ios/action.yml b/.github/actions/upload-ios/action.yml index 5043327a44a..2a5e75d8eb8 100644 --- a/.github/actions/upload-ios/action.yml +++ b/.github/actions/upload-ios/action.yml @@ -64,7 +64,7 @@ runs: - name: Set up Xcode uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: '26.2.0' + xcode-version: '26.6' - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 diff --git a/.github/workflows/e2e-build-ios.yml b/.github/workflows/e2e-build-ios.yml index f290a6d4228..f6f787074f2 100644 --- a/.github/workflows/e2e-build-ios.yml +++ b/.github/workflows/e2e-build-ios.yml @@ -68,7 +68,7 @@ jobs: - name: Set up Xcode uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: '26.2.0' + xcode-version: '26.6' - name: Set up Ruby uses: ruby/setup-ruby@7372622e62b60b3cb750dcd2b9e32c247ffec26a # v1.302.0 diff --git a/.github/workflows/maestro-ios.yml b/.github/workflows/maestro-ios.yml index 3cf48779f52..18115e31568 100644 --- a/.github/workflows/maestro-ios.yml +++ b/.github/workflows/maestro-ios.yml @@ -26,7 +26,7 @@ jobs: # Match the Xcode version e2e-build-ios.yml builds with uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: '26.2.0' + xcode-version: '26.6' - name: Configure Simulator run: | From 6453b200f99137de7a15ad16b350252b0d4f61a6 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 10:30:08 -0300 Subject: [PATCH 090/120] feat(ios): use Expo UI List for List.Container (#7721) --- .sniffler/test-map.json | 6 +- .../__snapshots__/DirectoryItem.test.tsx.snap | 3007 ++- app/containers/DirectoryItem/index.ios.tsx | 64 + app/containers/DirectoryItem/index.tsx | 2 + app/containers/List/ListCheckbox.tsx | 19 + app/containers/List/ListContainer.ios.tsx | 99 + app/containers/List/ListItem.tsx | 12 +- app/containers/List/ListRadio.tsx | 5 +- app/containers/List/ListSection.tsx | 18 +- app/containers/List/ListSeparator.tsx | 6 + .../List/__snapshots__/List.test.tsx.snap | 17531 +++++++--------- .../List/__tests__/ListSection.test.tsx | 43 + app/containers/List/index.ts | 1 + app/containers/List/listChildren.ts | 14 + app/containers/List/native/Accessory.ios.tsx | 55 + app/containers/List/native/Icon.ios.tsx | 22 + app/containers/List/native/Item.ios.tsx | 101 + app/containers/List/native/Item.tsx | 5 + app/containers/List/native/Picker.ios.tsx | 36 + app/containers/List/native/Picker.tsx | 5 + app/containers/List/native/Section.ios.tsx | 41 + app/containers/List/native/Section.tsx | 5 + app/containers/List/native/Status.ios.tsx | 19 + .../__tests__/describeAccessory.test.tsx | 79 + .../List/native/__tests__/itemProps.test.ts | 68 + app/containers/List/native/context.ts | 12 + .../List/native/describeAccessory.ts | 87 + app/containers/List/native/itemProps.ts | 39 + app/containers/List/native/rowMarkers.ts | 16 + app/containers/List/native/types.ts | 27 + .../NativeListRow/Separator.ios.tsx | 28 + app/containers/NativeListRow/Separator.tsx | 5 + app/containers/NativeListRow/constants.ts | 6 + app/containers/NativeListRow/index.tsx | 141 + .../useListBackgroundColor.ios.ts | 6 + .../NativeListRow/useListBackgroundColor.ts | 1 + .../__snapshots__/SearchBox.test.tsx.snap | 7 +- app/containers/SearchBox/index.tsx | 15 +- app/containers/TextInput/FormTextInput.tsx | 13 +- .../TextInput/GlassBackground.ios.tsx | 22 + app/containers/TextInput/GlassBackground.tsx | 3 + app/containers/UserItem.ios.tsx | 43 + app/containers/UserItem.tsx | 2 + .../accessibilityAndAppearanceView.test.tsx | 6 +- .../components/ListPicker.tsx | 34 +- .../ChannelItem.ios.tsx | 25 + .../AddExistingChannelView/ChannelItem.tsx | 31 + app/views/AddExistingChannelView/index.tsx | 22 +- app/views/DefaultBrowserView/index.tsx | 65 +- app/views/DirectoryView/Options.tsx | 4 +- app/views/DirectoryView/index.tsx | 11 +- app/views/DisplayPrefsView.tsx | 12 +- app/views/LanguageView/index.tsx | 53 +- .../MediaAutoDownloadView/ListPicker.tsx | 35 +- app/views/NewMessageView/ButtonCreate.ios.tsx | 41 + app/views/NewMessageView/ButtonCreate.tsx | 2 + app/views/NewMessageView/HeaderNewMessage.tsx | 91 +- app/views/NewMessageView/Item.ios.tsx | 52 + app/views/NewMessageView/Item.test.tsx | 10 +- app/views/NewMessageView/Item.tsx | 18 +- .../__snapshots__/Item.test.tsx.snap | 1297 +- app/views/NewMessageView/index.tsx | 15 +- .../NewMessageView/useStartMediaCall.tsx | 28 + .../NotificationPreferencesView/index.tsx | 33 +- app/views/PickerView/Item.ios.tsx | 24 + app/views/PickerView/Item.tsx | 28 + .../{PickerView.tsx => PickerView/index.tsx} | 53 +- .../components/CommunityEditionPushQuota.tsx | 5 +- .../components/NotificationDelay.tsx | 5 +- .../components/CallSection.tsx | 5 +- .../components/ActionsSection.tsx | 2 +- app/views/RoomMembersView/index.tsx | 29 +- app/views/SelectListView/Item.ios.tsx | 42 + app/views/SelectListView/Item.tsx | 50 + .../index.tsx} | 94 +- app/views/SelectedUsersView/Header.tsx | 9 +- app/views/SelectedUsersView/index.tsx | 11 +- app/views/ShareListView/index.tsx | 10 +- app/views/SidebarView/components/Admin.tsx | 15 +- .../SidebarView/components/CustomStatus.tsx | 8 +- .../SidebarView/components/Profile.ios.tsx | 43 + app/views/SidebarView/components/Profile.tsx | 4 +- .../SidebarView/components/StackItem.tsx | 20 + app/views/SidebarView/components/Stacks.tsx | 64 +- .../components/SupportedVersionsWarnItem.tsx | 9 +- .../__tests__/getSidebarSelection.test.ts | 40 + .../components/getSidebarSelection.ts | 14 + .../SidebarView/components/useStackItems.ts | 53 + app/views/SidebarView/index.ios.tsx | 80 + .../methods/__tests__/sidebarNavigate.test.ts | 29 + .../SidebarView/methods/sidebarNavigate.ts | 3 + .../ClearAfterPicker/ClearAfterNativeRow.tsx | 50 + .../StatusView/ClearAfterPicker/index.tsx | 20 +- app/views/StatusView/index.test.tsx | 35 +- app/views/StatusView/index.tsx | 132 +- app/views/ThemeView.tsx | 37 +- .../ListPicker.tsx | 22 +- app/views/UserPreferencesView/ListPicker.tsx | 27 +- .../UserPreferencesView.test.tsx | 8 +- .../__tests__/ListPicker.test.tsx | 55 + ios/Podfile.lock | 8 + package.json | 1 + pnpm-lock.yaml | 454 + 103 files changed, 12917 insertions(+), 12302 deletions(-) create mode 100644 app/containers/DirectoryItem/index.ios.tsx create mode 100644 app/containers/List/ListCheckbox.tsx create mode 100644 app/containers/List/ListContainer.ios.tsx create mode 100644 app/containers/List/__tests__/ListSection.test.tsx create mode 100644 app/containers/List/listChildren.ts create mode 100644 app/containers/List/native/Accessory.ios.tsx create mode 100644 app/containers/List/native/Icon.ios.tsx create mode 100644 app/containers/List/native/Item.ios.tsx create mode 100644 app/containers/List/native/Item.tsx create mode 100644 app/containers/List/native/Picker.ios.tsx create mode 100644 app/containers/List/native/Picker.tsx create mode 100644 app/containers/List/native/Section.ios.tsx create mode 100644 app/containers/List/native/Section.tsx create mode 100644 app/containers/List/native/Status.ios.tsx create mode 100644 app/containers/List/native/__tests__/describeAccessory.test.tsx create mode 100644 app/containers/List/native/__tests__/itemProps.test.ts create mode 100644 app/containers/List/native/context.ts create mode 100644 app/containers/List/native/describeAccessory.ts create mode 100644 app/containers/List/native/itemProps.ts create mode 100644 app/containers/List/native/rowMarkers.ts create mode 100644 app/containers/List/native/types.ts create mode 100644 app/containers/NativeListRow/Separator.ios.tsx create mode 100644 app/containers/NativeListRow/Separator.tsx create mode 100644 app/containers/NativeListRow/constants.ts create mode 100644 app/containers/NativeListRow/index.tsx create mode 100644 app/containers/NativeListRow/useListBackgroundColor.ios.ts create mode 100644 app/containers/NativeListRow/useListBackgroundColor.ts create mode 100644 app/containers/TextInput/GlassBackground.ios.tsx create mode 100644 app/containers/TextInput/GlassBackground.tsx create mode 100644 app/containers/UserItem.ios.tsx create mode 100644 app/views/AddExistingChannelView/ChannelItem.ios.tsx create mode 100644 app/views/AddExistingChannelView/ChannelItem.tsx create mode 100644 app/views/NewMessageView/ButtonCreate.ios.tsx create mode 100644 app/views/NewMessageView/Item.ios.tsx create mode 100644 app/views/NewMessageView/useStartMediaCall.tsx create mode 100644 app/views/PickerView/Item.ios.tsx create mode 100644 app/views/PickerView/Item.tsx rename app/views/{PickerView.tsx => PickerView/index.tsx} (66%) create mode 100644 app/views/SelectListView/Item.ios.tsx create mode 100644 app/views/SelectListView/Item.tsx rename app/views/{SelectListView.tsx => SelectListView/index.tsx} (65%) create mode 100644 app/views/SidebarView/components/Profile.ios.tsx create mode 100644 app/views/SidebarView/components/StackItem.tsx create mode 100644 app/views/SidebarView/components/__tests__/getSidebarSelection.test.ts create mode 100644 app/views/SidebarView/components/getSidebarSelection.ts create mode 100644 app/views/SidebarView/components/useStackItems.ts create mode 100644 app/views/SidebarView/index.ios.tsx create mode 100644 app/views/SidebarView/methods/__tests__/sidebarNavigate.test.ts create mode 100644 app/views/StatusView/ClearAfterPicker/ClearAfterNativeRow.tsx create mode 100644 app/views/UserPreferencesView/__tests__/ListPicker.test.tsx diff --git a/.sniffler/test-map.json b/.sniffler/test-map.json index 7c9e3c8bd7c..ac04e0d6cee 100644 --- a/.sniffler/test-map.json +++ b/.sniffler/test-map.json @@ -1,7 +1,7 @@ [ { "test": ".maestro/tests/accessibilityAndAppearance/ToastsAndDialogs.yml", - "dependsOn": ["app/views/StatusView/**"] + "dependsOn": ["app/views/StatusView/**", "app/views/SidebarView/**"] }, { "test": ".maestro/tests/assorted/accessibility-and-appearance.yaml", @@ -384,7 +384,7 @@ "test": ".maestro/tests/teams/convert-team.yaml", "dependsOn": [ "app/views/RoomActionsView/**", - "app/views/SelectListView.tsx", + "app/views/SelectListView/**", "app/views/CreateChannelView/**", "app/views/NewMessageView/**", "app/views/SelectedUsersView/**", @@ -414,7 +414,7 @@ "app/views/AddExistingChannelView/**", "app/views/RoomMembersView/**", "app/views/SelectedUsersView/**", - "app/views/SelectListView.tsx", + "app/views/SelectListView/**", "app/views/CreateChannelView/**", "app/sagas/room.js", "app/sagas/createChannel.js" diff --git a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap index d5761084a08..30a6d630432 100644 --- a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap +++ b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap @@ -10,285 +10,239 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` > - - - - - - - - - - - + + + + + + +  + + + -  - - - Styled Room - - - + + - Custom style applied - - - - - - + onGlobalEvent={[Function]} + text="Custom style applied" + /> + + + + + + @@ -304,283 +258,239 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` > - - - - - - - - - - - + + + + + + +  + + + -  - - - General - - - + + - A public room - - - - - - + onGlobalEvent={[Function]} + text="A public room" + /> + + + + + + @@ -596,252 +506,198 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` > - - - - - - - - - - - - Alice Johnson - + style={ + { + "borderRadius": 4, + "height": 30, + "width": 30, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> - + + + - Hey there! - - - - - - + onGlobalEvent={[Function]} + text="Hey there!" + /> + + + + + + @@ -857,259 +713,243 @@ exports[`Story Snapshots: LongRoomName should match snapshot 1`] = ` > - - - - - - - - - - - + + + + + + +  + + + -  - - - This is a very very very very very very very long room name that should be truncated - - - + + + + + + - Long name test + 9999 members - - - 9999 members - - - - - + + + + + @@ -1171,185 +989,157 @@ exports[`Story Snapshots: OnlyTitle should match snapshot 1`] = ` > - - - - - - + - - + +  + + + -  - - - Just a title - - - - - - - + onGlobalEvent={[Function]} + text="Just a title" + /> + + + + + + + @@ -1365,283 +1155,239 @@ exports[`Story Snapshots: TeamMain should match snapshot 1`] = ` > - - - - - - - - - - - + + + + + + +  + + + -  - - - Engineering Team - - - + + - Main team room - - - - - - + onGlobalEvent={[Function]} + text="Main team room" + /> + + + + + + @@ -1657,259 +1403,243 @@ exports[`Story Snapshots: WithRightLabel should match snapshot 1`] = ` > - - - - - - - - - - - + + + + + + +  + + + -  - - - General - - - + + + + + + - Room with label + 123 members - - - 123 members - - - - - + + + + + @@ -1971,244 +1679,215 @@ exports[`Story Snapshots: WithoutDescription should match snapshot 1`] = ` > - - - - - - - - - - - + + + + + + +  + + + -  - - - No description room - - - - - - - + onGlobalEvent={[Function]} + text="No description room" + /> + + + + + + + diff --git a/app/containers/DirectoryItem/index.ios.tsx b/app/containers/DirectoryItem/index.ios.tsx new file mode 100644 index 00000000000..dd34d9eb901 --- /dev/null +++ b/app/containers/DirectoryItem/index.ios.tsx @@ -0,0 +1,64 @@ +import { type ReactElement } from 'react'; +import { Text, type ViewStyle } from 'react-native'; + +import Avatar from '../Avatar'; +import RoomTypeIcon from '../RoomTypeIcon'; +import NativeListRow from '../NativeListRow'; +import { AVATAR_SIZE } from '../NativeListRow/constants'; +import styles from './styles'; +import { useTheme } from '~/theme'; +import usePreviewFormatText from '~/lib/hooks/usePreviewFormatText'; + +export { ROW_HEIGHT } from '../NativeListRow/constants'; + +interface IDirectoryItem { + title: string; + description?: string; + avatar?: string; + type: string; + onPress(): void; + testID: string; + style?: ViewStyle; + rightLabel?: string; + rid?: string; + teamMain?: boolean; + isFirst?: boolean; + isLast?: boolean; +} + +const DirectoryItem = ({ + title, + description, + avatar, + onPress, + testID, + rightLabel, + type, + rid, + teamMain, + isFirst, + isLast +}: IDirectoryItem): ReactElement => { + const { colors } = useTheme(); + const formattedDescription = usePreviewFormatText(description ?? ''); + return ( + } + titleLeading={type !== 'd' ? : undefined} + trailing={ + rightLabel ? ( + {rightLabel} + ) : undefined + } + /> + ); +}; + +export default DirectoryItem; diff --git a/app/containers/DirectoryItem/index.tsx b/app/containers/DirectoryItem/index.tsx index 77881bd3924..19066f29786 100644 --- a/app/containers/DirectoryItem/index.tsx +++ b/app/containers/DirectoryItem/index.tsx @@ -26,6 +26,8 @@ interface IDirectoryItem { rightLabel?: string; rid?: string; teamMain?: boolean; + isFirst?: boolean; + isLast?: boolean; } const DirectoryItemLabel = memo(({ text, color }: IDirectoryItemLabel) => { diff --git a/app/containers/List/ListCheckbox.tsx b/app/containers/List/ListCheckbox.tsx new file mode 100644 index 00000000000..fed58469189 --- /dev/null +++ b/app/containers/List/ListCheckbox.tsx @@ -0,0 +1,19 @@ +import { type ReactElement } from 'react'; + +import { useTheme } from '~/theme'; +import ListIcon from './ListIcon'; + +export interface IListCheckbox { + value: boolean; + onValueChange: (value: boolean) => void; + testID?: string; +} + +const ListCheckbox = ({ value }: IListCheckbox): ReactElement => { + const { colors } = useTheme(); + return ; +}; + +ListCheckbox.displayName = 'List.Checkbox'; + +export default ListCheckbox; diff --git a/app/containers/List/ListContainer.ios.tsx b/app/containers/List/ListContainer.ios.tsx new file mode 100644 index 00000000000..82bf1e96dce --- /dev/null +++ b/app/containers/List/ListContainer.ios.tsx @@ -0,0 +1,99 @@ +import { useState, type ReactElement } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Host } from '@expo/ui'; +import { Group, List, RNHostView } from '@expo/ui/swift-ui'; +import { alignmentGuide, frame, listRowInsets, listStyle, onGeometryChange, tag } from '@expo/ui/swift-ui/modifiers'; + +import { useTheme } from '~/theme'; +import ListSection from './ListSection'; +import ListItem from './ListItem'; +import ListRadio from './ListRadio'; +import { isNativeListRow, isNativeListSection } from './native/rowMarkers'; +import { flattenListChildren, isListSeparator } from './listChildren'; +import { NativeListContext } from './native/context'; +import { ICON_SIZE, PADDING_HORIZONTAL } from './constants'; + +const styles = StyleSheet.create({ + host: { + flex: 1 + } +}); + +const insetGroupedModifiers = [listStyle('insetGrouped')]; +const sidebarModifiers = [listStyle('sidebar')]; + +export interface IListSelection { + selectedTag: string | null; +} + +interface IListContainer { + children: (ReactElement | null)[] | ReactElement | null; + testID?: string; + selection?: IListSelection; +} + +const isSection = (element: ReactElement) => element.type === ListSection || isNativeListSection(element.type); +const isNativeRow = (element: ReactElement) => + element.type === ListItem || element.type === ListRadio || isNativeListRow(element.type); +const hasLeftIcon = (element: ReactElement) => Boolean((element.props as { left?: unknown }).left); +const rowSelectionTag = (element: ReactElement) => { + const { selectionTag, testID } = element.props as { selectionTag?: string; testID?: string }; + return selectionTag ?? testID; +}; + +const selectedTags = ({ selectedTag }: IListSelection) => (selectedTag ? [selectedTag] : []); + +const ListContainer = ({ children, testID, selection }: IListContainer) => { + const { theme } = useTheme(); + const [rowWidth, setRowWidth] = useState(0); + + const selectionTag = (row: ReactElement) => { + const rowTag = rowSelectionTag(row); + return rowTag && selection ? [tag(rowTag)] : []; + }; + + const rowModifiers = (row: ReactElement) => [ + frame({ maxWidth: Number.MAX_SAFE_INTEGER }), + alignmentGuide('listRowSeparatorLeading', hasLeftIcon(row) ? PADDING_HORIZONTAL * 2 + ICON_SIZE : PADDING_HORIZONTAL), + onGeometryChange(({ width }) => setRowWidth(current => (current === width ? current : width))), + listRowInsets({ top: 0, leading: 0, bottom: 0, trailing: 0 }), + ...selectionTag(row) + ]; + + const renderHostedRow = (row: ReactElement) => ( + + + + {row} + + + + ); + + const renderNativeRow = (row: ReactElement) => ( + + {row} + + ); + + const renderRow = (row: ReactElement) => (isNativeRow(row) ? renderNativeRow(row) : renderHostedRow(row)); + + return ( + + + + {flattenListChildren(children) + .filter(element => !isListSeparator(element)) + .map(element => (isSection(element) ? element : renderRow(element)))} + + + + ); +}; + +ListContainer.displayName = 'List.Container'; + +export default ListContainer; diff --git a/app/containers/List/ListItem.tsx b/app/containers/List/ListItem.tsx index fb028afabd6..9404fef622e 100644 --- a/app/containers/List/ListItem.tsx +++ b/app/containers/List/ListItem.tsx @@ -17,6 +17,8 @@ import { useTheme } from '~/theme'; import I18n from '~/i18n'; import Icon from './ListIcon'; import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants'; +import { useNativeListMode } from './native/context'; +import NativeListItem from './native/Item'; import { CustomIcon } from '../CustomIcon'; import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; import EventEmitter from '~/lib/methods/helpers/events'; @@ -256,13 +258,19 @@ export interface IListItem extends Omit, Omit { const { colors } = useTheme(); + const nativeListMode = useNativeListMode(); + const backgroundColor = props.backgroundColor || (nativeListMode ? 'transparent' : colors.surfaceRoom); + + if (nativeListMode === 'native') { + return ; + } if (props.onPress) { const { onPress } = props; - return + ); +}; + +const NativeListItem = ({ item }: INativeListItem) => { + const { colors } = useTheme(); + const subtitle = nativeListItemSubtitle(item); + const leading = describeNativeListAccessory(item.left?.()); + const trailing = describeNativeListAccessory(item.right?.()); + + return ( + + {leading ? : null} + + + {subtitle ? ( + + {subtitle} + + ) : null} + + + {trailing ? : null} + {item.showActionIndicator ? : null} + + ); +}; + +export default NativeListItem; diff --git a/app/containers/List/native/Item.tsx b/app/containers/List/native/Item.tsx new file mode 100644 index 00000000000..0a3a365637c --- /dev/null +++ b/app/containers/List/native/Item.tsx @@ -0,0 +1,5 @@ +import { type INativeListItem } from './types'; + +const NativeListItem = (_: INativeListItem) => null; + +export default NativeListItem; diff --git a/app/containers/List/native/Picker.ios.tsx b/app/containers/List/native/Picker.ios.tsx new file mode 100644 index 00000000000..e910913d871 --- /dev/null +++ b/app/containers/List/native/Picker.ios.tsx @@ -0,0 +1,36 @@ +import { Picker, Text } from '@expo/ui/swift-ui'; +import { font, foregroundStyle, frame, listRowInsets, pickerStyle, tag, tint } from '@expo/ui/swift-ui/modifiers'; + +import { useTheme } from '~/theme'; +import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; +import { BASE_HEIGHT, PADDING_HORIZONTAL } from '../constants'; +import { type INativeListPicker } from './types'; + +const NativeListPicker = ({ title, testID, options, selection, onSelectionChange }: INativeListPicker) => { + const { colors } = useTheme(); + const { fontScale } = useResponsiveLayout(); + + return ( + {title} + } + modifiers={[ + pickerStyle('menu'), + tint(colors.fontSecondaryInfo), + frame({ minHeight: BASE_HEIGHT * fontScale }), + listRowInsets({ leading: PADDING_HORIZONTAL, trailing: PADDING_HORIZONTAL }) + ]}> + {options.map(option => ( + + {option.label} + + ))} + + ); +}; + +export default NativeListPicker; diff --git a/app/containers/List/native/Picker.tsx b/app/containers/List/native/Picker.tsx new file mode 100644 index 00000000000..aaa4cc894c6 --- /dev/null +++ b/app/containers/List/native/Picker.tsx @@ -0,0 +1,5 @@ +import { type INativeListPicker } from './types'; + +const NativeListPicker = (_: INativeListPicker) => null; + +export default NativeListPicker; diff --git a/app/containers/List/native/Section.ios.tsx b/app/containers/List/native/Section.ios.tsx new file mode 100644 index 00000000000..27ceb7f3d8b --- /dev/null +++ b/app/containers/List/native/Section.ios.tsx @@ -0,0 +1,41 @@ +import { useContext, type ReactElement } from 'react'; +import { Section, Text } from '@expo/ui/swift-ui'; + +import ListInfo from '../ListInfo'; +import { flattenListChildren, isListSeparator } from '../listChildren'; +import { NativeListContext } from './context'; +import { translateListText } from './itemProps'; +import { type INativeListSection } from './types'; + +interface IInfoProps { + info: string; + translateInfo?: boolean; +} + +const isInfo = (element: ReactElement): element is ReactElement => element.type === ListInfo; + +const NativeListSection = ({ children, title, translateTitle }: INativeListSection) => { + const { renderRow } = useContext(NativeListContext); + const elements = flattenListChildren(children).filter(element => !isListSeparator(element)); + const infos = elements.filter(isInfo); + const rows = elements.filter(element => !isInfo(element)); + const footer = infos.length ? ( + <> + {infos.map(info => ( + {translateListText(info.props.info, info.props.translateInfo)} + ))} + + ) : undefined; + + if (!rows.length && !infos.length) { + return null; + } + + return ( +
+ {rows.map(renderRow)} +
+ ); +}; + +export default NativeListSection; diff --git a/app/containers/List/native/Section.tsx b/app/containers/List/native/Section.tsx new file mode 100644 index 00000000000..73581d1562f --- /dev/null +++ b/app/containers/List/native/Section.tsx @@ -0,0 +1,5 @@ +import { type INativeListSection } from './types'; + +const NativeListSection = (_: INativeListSection) => null; + +export default NativeListSection; diff --git a/app/containers/List/native/Status.ios.tsx b/app/containers/List/native/Status.ios.tsx new file mode 100644 index 00000000000..95f2646d389 --- /dev/null +++ b/app/containers/List/native/Status.ios.tsx @@ -0,0 +1,19 @@ +import { type TUserStatus } from '~/definitions'; +import { useUserStatusColor } from '~/lib/hooks/useUserStatusColor'; +import { useTheme } from '~/theme'; +import { hasIcon, type TIconsName } from '~/containers/CustomIcon'; +import NativeListIcon from './Icon.ios'; + +const statusIconName = (status: TUserStatus): TIconsName => { + const name = `status-${status}`; + return hasIcon(name) ? (name as TIconsName) : 'status-offline'; +}; + +const NativeListStatus = ({ status }: { status: TUserStatus }) => { + const { colors } = useTheme(); + const statusColor = useUserStatusColor(status); + + return ; +}; + +export default NativeListStatus; diff --git a/app/containers/List/native/__tests__/describeAccessory.test.tsx b/app/containers/List/native/__tests__/describeAccessory.test.tsx new file mode 100644 index 00000000000..247e6ff41b6 --- /dev/null +++ b/app/containers/List/native/__tests__/describeAccessory.test.tsx @@ -0,0 +1,79 @@ +import { Text, View } from 'react-native'; + +import Avatar from '~/containers/Avatar'; +import { CustomIcon } from '~/containers/CustomIcon'; +import NewWindowIcon from '~/containers/NewWindowIcon'; +import Radio from '~/containers/Radio'; +import Status from '~/containers/Status/Status'; +import Switch from '~/containers/Switch'; +import ListCheckbox from '../../ListCheckbox'; +import ListIcon from '../../ListIcon'; +import { describeNativeListAccessory } from '../describeAccessory'; + +describe('describeNativeListAccessory', () => { + it('has nothing to render for an empty slot', () => { + expect(describeNativeListAccessory(null)).toBeNull(); + }); + + it('maps List.Icon and CustomIcon to icon font glyphs', () => { + expect(describeNativeListAccessory()).toEqual({ kind: 'icon', name: 'moon' }); + expect(describeNativeListAccessory()).toEqual({ + kind: 'icon', + name: 'warning', + color: 'red', + size: 20 + }); + }); + + it('ignores an empty icon color', () => { + expect(describeNativeListAccessory()).toEqual({ + kind: 'icon', + name: 'checkbox-unchecked' + }); + }); + + it('maps the external link icon to the new-window glyph', () => { + expect(describeNativeListAccessory()).toEqual({ kind: 'icon', name: 'new-window' }); + }); + + it('shows a checkmark only for a checked radio', () => { + expect(describeNativeListAccessory()).toEqual({ kind: 'check' }); + expect(describeNativeListAccessory()).toBeNull(); + }); + + it('maps the user status', () => { + expect(describeNativeListAccessory()).toEqual({ kind: 'status', status: 'away' }); + }); + + it('maps a Switch to a toggle keeping value, handler and disabled state', () => { + const onValueChange = jest.fn(); + const accessory = describeNativeListAccessory(); + expect(accessory).toEqual({ kind: 'toggle', isOn: true, onValueChange, disabled: true, testID: 'switch' }); + }); + + it('maps List.Checkbox to a checkbox keeping value, handler and testID', () => { + const onValueChange = jest.fn(); + expect(describeNativeListAccessory()).toEqual({ + kind: 'checkbox', + value: true, + onValueChange, + testID: 'checkbox' + }); + }); + + it('maps plain text, also when wrapped in a single View', () => { + expect(describeNativeListAccessory(Off)).toEqual({ kind: 'text', text: 'Off' }); + expect( + describeNativeListAccessory( + + Toasts + + ) + ).toEqual({ kind: 'text', text: 'Toasts' }); + }); + + it('hosts anything else as React Native', () => { + const avatar = ; + expect(describeNativeListAccessory(avatar)).toEqual({ kind: 'hosted', element: avatar }); + }); +}); diff --git a/app/containers/List/native/__tests__/itemProps.test.ts b/app/containers/List/native/__tests__/itemProps.test.ts new file mode 100644 index 00000000000..9f6ff046daa --- /dev/null +++ b/app/containers/List/native/__tests__/itemProps.test.ts @@ -0,0 +1,68 @@ +import EventEmitter from '~/lib/methods/helpers/events'; +import { LISTENER } from '~/containers/Toast'; +import { type IListItem } from '../../ListItem'; +import { nativeListItemAccessibilityLabel, nativeListItemTitle, pressNativeListItem } from '../itemProps'; + +jest.mock('~/i18n', () => ({ t: (key: string) => `t:${key}` })); + +describe('nativeListItemTitle', () => { + it('translates string titles', () => { + expect(nativeListItemTitle({ title: 'Theme' })).toBe('t:Theme'); + }); + + it('has no text for custom title components', () => { + expect(nativeListItemTitle({ title: () => null })).toBeUndefined(); + }); +}); + +describe('nativeListItemAccessibilityLabel', () => { + it('prefers the explicit label', () => { + expect(nativeListItemAccessibilityLabel({ title: 'Theme', accessibilityLabel: 'Custom' })).toBe('Custom'); + }); + + it('joins translated title, subtitle and switch state', () => { + expect( + nativeListItemAccessibilityLabel({ + title: 'Crash_report', + subtitle: 'Info', + translateSubtitle: false, + additionalAccessibilityLabel: true + }) + ).toBe('t:Crash_report Info t:Enabled'); + }); + + it('uses checked wording for checkboxes', () => { + expect( + nativeListItemAccessibilityLabel({ + title: 'Name', + translateTitle: false, + additionalAccessibilityLabel: false, + additionalAccessibilityLabelCheck: true + }) + ).toBe('Name t:Unchecked'); + }); +}); + +describe('pressNativeListItem', () => { + const onPress = jest.fn(); + const item: IListItem = { title: 'Theme', onPress }; + + beforeEach(() => onPress.mockClear()); + + it('calls onPress with the title', () => { + pressNativeListItem(item); + expect(onPress).toHaveBeenCalledWith('Theme'); + }); + + it('ignores presses on disabled rows', () => { + pressNativeListItem({ title: 'Theme', onPress, disabled: true }); + expect(onPress).not.toHaveBeenCalled(); + }); + + it('shows the disabled reason instead of pressing', () => { + const emit = jest.spyOn(EventEmitter, 'emit'); + pressNativeListItem({ title: 'Theme', onPress, disabled: true, disabledReason: 'Not allowed' }); + expect(emit).toHaveBeenCalledWith(LISTENER, { message: 'Not allowed' }); + expect(onPress).not.toHaveBeenCalled(); + }); +}); diff --git a/app/containers/List/native/context.ts b/app/containers/List/native/context.ts new file mode 100644 index 00000000000..7cd85f92865 --- /dev/null +++ b/app/containers/List/native/context.ts @@ -0,0 +1,12 @@ +import { createContext, useContext, type ReactElement } from 'react'; + +export type TNativeListMode = false | 'native' | 'hosted'; + +interface INativeListContext { + mode: TNativeListMode; + renderRow: (row: ReactElement) => ReactElement; +} + +export const NativeListContext = createContext({ mode: false, renderRow: row => row }); + +export const useNativeListMode = () => useContext(NativeListContext).mode; diff --git a/app/containers/List/native/describeAccessory.ts b/app/containers/List/native/describeAccessory.ts new file mode 100644 index 00000000000..78ff82da22e --- /dev/null +++ b/app/containers/List/native/describeAccessory.ts @@ -0,0 +1,87 @@ +import { Children, isValidElement, type ReactElement, type ReactNode } from 'react'; +import { Text, View, type SwitchProps } from 'react-native'; + +import { type TUserStatus } from '~/definitions'; +import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; +import NewWindowIcon from '~/containers/NewWindowIcon'; +import Radio from '~/containers/Radio'; +import Status from '~/containers/Status/Status'; +import Switch from '~/containers/Switch'; +import ListCheckbox, { type IListCheckbox } from '../ListCheckbox'; +import ListIcon from '../ListIcon'; + +export type TNativeListAccessory = + | { kind: 'icon'; name: TIconsName; color?: string; size?: number } + | { kind: 'check' } + | { kind: 'status'; status: TUserStatus } + | { kind: 'toggle'; isOn: boolean; onValueChange?: (value: boolean) => void; disabled: boolean; testID?: string } + | { kind: 'checkbox'; value: boolean; onValueChange: (value: boolean) => void; testID?: string } + | { kind: 'text'; text: string } + | { kind: 'hosted'; element: ReactElement }; + +interface IIconProps { + name: TIconsName; + color?: string; + size?: number; +} + +const iconAccessory = (name: TIconsName, { color, size }: Partial): TNativeListAccessory => ({ + kind: 'icon', + name, + color: color || undefined, + size +}); + +const textContent = (children: ReactNode) => { + const parts = Children.toArray(children); + return parts.every(part => typeof part === 'string' || typeof part === 'number') ? parts.join('') : null; +}; + +const onlyChild = (children: ReactNode) => { + const parts = Children.toArray(children); + return parts.length === 1 && isValidElement(parts[0]) ? parts[0] : null; +}; + +type TProps = Record; +type TDescriber = (props: TProps) => TNativeListAccessory | null | undefined; + +const describeText: TDescriber = ({ children }) => { + const text = textContent(children); + return text === null ? undefined : { kind: 'text', text }; +}; + +const describeToggle: TDescriber = props => { + const { value, onValueChange, disabled, testID } = props as SwitchProps; + return { kind: 'toggle', isOn: Boolean(value), onValueChange: onValueChange ?? undefined, disabled: Boolean(disabled), testID }; +}; + +const describeCheckbox: TDescriber = props => { + const { value, onValueChange, testID } = props as IListCheckbox; + return { kind: 'checkbox', value, onValueChange, testID }; +}; + +const describeWrapper: TDescriber = ({ children }) => { + const child = onlyChild(children); + const described = child ? describeNativeListAccessory(child) : null; + return described?.kind === 'hosted' ? undefined : described; +}; + +const describers = new Map([ + [ListCheckbox, describeCheckbox], + [ListIcon, props => iconAccessory(props.name, props)], + [CustomIcon, props => iconAccessory(props.name, props)], + [NewWindowIcon, props => iconAccessory('new-window', props)], + [Radio, ({ check }) => (check ? { kind: 'check' } : null)], + [Status, ({ status }) => ({ kind: 'status', status: status ?? 'offline' })], + [Switch, describeToggle], + [Text, describeText], + [View, describeWrapper] +]); + +export function describeNativeListAccessory(node: ReactNode): TNativeListAccessory | null { + if (!isValidElement(node)) { + return null; + } + const described = describers.get(node.type)?.(node.props); + return described === undefined ? { kind: 'hosted', element: node } : described; +} diff --git a/app/containers/List/native/itemProps.ts b/app/containers/List/native/itemProps.ts new file mode 100644 index 00000000000..8e4ed4ae02a --- /dev/null +++ b/app/containers/List/native/itemProps.ts @@ -0,0 +1,39 @@ +import I18n from '~/i18n'; +import EventEmitter from '~/lib/methods/helpers/events'; +import { LISTENER } from '~/containers/Toast'; +import { type IListItem } from '../ListItem'; + +export const translateListText = (text: string, shouldTranslate = true) => (shouldTranslate ? I18n.t(text) : text); + +export const nativeListItemTitle = ({ title, translateTitle }: IListItem) => + typeof title === 'string' ? translateListText(title, translateTitle) : undefined; + +export const nativeListItemSubtitle = ({ subtitle, translateSubtitle }: IListItem) => + subtitle ? translateListText(subtitle, translateSubtitle) : undefined; + +const stateLabel = ({ additionalAccessibilityLabel, additionalAccessibilityLabelCheck }: IListItem) => { + if (typeof additionalAccessibilityLabel === 'string') { + return additionalAccessibilityLabel; + } + if (typeof additionalAccessibilityLabel !== 'boolean') { + return undefined; + } + if (additionalAccessibilityLabelCheck) { + return I18n.t(additionalAccessibilityLabel ? 'Checked' : 'Unchecked'); + } + return I18n.t(additionalAccessibilityLabel ? 'Enabled' : 'Disabled'); +}; + +export const nativeListItemAccessibilityLabel = (item: IListItem) => + item.accessibilityLabel ?? + [nativeListItemTitle(item), nativeListItemSubtitle(item), stateLabel(item)].filter(Boolean).join(' '); + +export const pressNativeListItem = ({ disabled, disabledReason, onPress, title }: IListItem) => { + if (disabled && disabledReason) { + EventEmitter.emit(LISTENER, { message: disabledReason }); + return; + } + if (!disabled) { + onPress?.(title); + } +}; diff --git a/app/containers/List/native/rowMarkers.ts b/app/containers/List/native/rowMarkers.ts new file mode 100644 index 00000000000..14e3e2e595b --- /dev/null +++ b/app/containers/List/native/rowMarkers.ts @@ -0,0 +1,16 @@ +import { type ComponentType } from 'react'; + +const createComponentMarker = () => { + const markedTypes = new WeakSet(); + const mark = >(component: Component): Component => { + markedTypes.add(component); + return component; + }; + const isMarked = (type: unknown) => + (typeof type === 'function' || (typeof type === 'object' && type !== null)) && markedTypes.has(type); + return [mark, isMarked] as const; +}; + +export const [asNativeListRow, isNativeListRow] = createComponentMarker(); + +export const [asNativeListSection, isNativeListSection] = createComponentMarker(); diff --git a/app/containers/List/native/types.ts b/app/containers/List/native/types.ts new file mode 100644 index 00000000000..956d344c24d --- /dev/null +++ b/app/containers/List/native/types.ts @@ -0,0 +1,27 @@ +import { type ReactNode } from 'react'; + +import { type IListItem } from '../ListItem'; + +export interface INativeListItem { + item: IListItem; +} + +export interface INativeListSection { + children: ReactNode; + title?: string; + translateTitle?: boolean; +} + +export interface INativeListPickerOption { + label: string; + value: string; + testID?: string; +} + +export interface INativeListPicker { + title: string; + testID?: string; + options: INativeListPickerOption[]; + selection: string; + onSelectionChange: (value: string) => void; +} diff --git a/app/containers/NativeListRow/Separator.ios.tsx b/app/containers/NativeListRow/Separator.ios.tsx new file mode 100644 index 00000000000..a1d9aece613 --- /dev/null +++ b/app/containers/NativeListRow/Separator.ios.tsx @@ -0,0 +1,28 @@ +import { StyleSheet, View } from 'react-native'; + +import { useTheme } from '~/theme'; +import { AVATAR_SIZE, CONTENT_SPACING, ROW_MARGIN_HORIZONTAL, ROW_PADDING_HORIZONTAL } from './constants'; + +const styles = StyleSheet.create({ + card: { + marginHorizontal: ROW_MARGIN_HORIZONTAL + }, + line: { + height: StyleSheet.hairlineWidth + } +}); + +const Line = ({ inset }: { inset: number }) => { + const { colors } = useTheme(); + return ( + + + + ); +}; + +export const PlainSeparator = () => ; + +const Separator = () => ; + +export default Separator; diff --git a/app/containers/NativeListRow/Separator.tsx b/app/containers/NativeListRow/Separator.tsx new file mode 100644 index 00000000000..77c984db2d9 --- /dev/null +++ b/app/containers/NativeListRow/Separator.tsx @@ -0,0 +1,5 @@ +import ListSeparator from '../List/ListSeparator'; + +export const PlainSeparator = ListSeparator; + +export default ListSeparator; diff --git a/app/containers/NativeListRow/constants.ts b/app/containers/NativeListRow/constants.ts new file mode 100644 index 00000000000..95432acad82 --- /dev/null +++ b/app/containers/NativeListRow/constants.ts @@ -0,0 +1,6 @@ +export const ROW_HEIGHT = 54; +export const ROW_RADIUS = 10; +export const ROW_MARGIN_HORIZONTAL = 16; +export const ROW_PADDING_HORIZONTAL = 16; +export const AVATAR_SIZE = 30; +export const CONTENT_SPACING = 12; diff --git a/app/containers/NativeListRow/index.tsx b/app/containers/NativeListRow/index.tsx new file mode 100644 index 00000000000..48097bf67d5 --- /dev/null +++ b/app/containers/NativeListRow/index.tsx @@ -0,0 +1,141 @@ +import { type ReactElement, type ReactNode } from 'react'; +import { PixelRatio, StyleSheet, View } from 'react-native'; +import { Host, ListItem } from '@expo/ui'; +import { Button, HStack, RNHostView, Text } from '@expo/ui/swift-ui'; +import { + accessibilityAddTraits, + accessibilityLabel as accessibilityLabelModifier, + buttonStyle, + disabled as disabledModifier, + font, + foregroundStyle, + lineLimit, + onLongPressGesture, + padding +} from '@expo/ui/swift-ui/modifiers'; + +import { type TIconsName } from '~/containers/CustomIcon'; +import NativeListIcon from '~/containers/List/native/Icon.ios'; +import { useTheme } from '~/theme'; +import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; +import { CONTENT_SPACING, ROW_HEIGHT, ROW_MARGIN_HORIZONTAL, ROW_PADDING_HORIZONTAL, ROW_RADIUS } from './constants'; + +const styles = StyleSheet.create({ + card: { + marginHorizontal: ROW_MARGIN_HORIZONTAL + }, + firstCard: { + borderTopLeftRadius: ROW_RADIUS, + borderTopRightRadius: ROW_RADIUS + }, + lastCard: { + borderBottomLeftRadius: ROW_RADIUS, + borderBottomRightRadius: ROW_RADIUS + }, + host: { + width: '100%' + } +}); + +export interface INativeListRowAction { + icon: TIconsName; + onPress: () => void; + testID: string; + accessibilityLabel: string; + disabled?: boolean; +} + +export interface INativeListRow { + title: string; + subtitle?: string; + leading?: ReactNode; + titleLeading?: ReactElement; + trailing?: ReactNode; + trailingAction?: INativeListRowAction; + onPress: () => void; + onLongPress?: () => void; + testID: string; + accessibilityLabel: string; + isSelected?: boolean; + isFirst?: boolean; + isLast?: boolean; +} + +const NativeListRow = ({ + title, + subtitle, + leading, + titleLeading, + trailing, + trailingAction, + onPress, + onLongPress, + testID, + accessibilityLabel, + isSelected, + isFirst, + isLast +}: INativeListRow) => { + const { colors, theme } = useTheme(); + const { fontScale } = useResponsiveLayout(); + const height = PixelRatio.roundToNearestPixel(ROW_HEIGHT * fontScale); + const rowModifiers = [ + accessibilityLabelModifier(accessibilityLabel), + ...(isSelected ? [accessibilityAddTraits(['isSelected'])] : []), + ...(onLongPress ? [onLongPressGesture(onLongPress)] : []) + ]; + const titleText = ( + + {title} + + ); + + return ( + + + + + {subtitle} + + ) : undefined + }> + {titleLeading ? ( + + {titleLeading} + {titleText} + + ) : ( + titleText + )} + + {trailingAction ? ( + + ) : null} + + + + ); +}; + +export default NativeListRow; diff --git a/app/containers/NativeListRow/useListBackgroundColor.ios.ts b/app/containers/NativeListRow/useListBackgroundColor.ios.ts new file mode 100644 index 00000000000..afac5d421f5 --- /dev/null +++ b/app/containers/NativeListRow/useListBackgroundColor.ios.ts @@ -0,0 +1,6 @@ +import { useTheme } from '~/theme'; + +export const useListBackgroundColor = (_backgroundColor: string) => { + const { colors } = useTheme(); + return colors.surfaceHover; +}; diff --git a/app/containers/NativeListRow/useListBackgroundColor.ts b/app/containers/NativeListRow/useListBackgroundColor.ts new file mode 100644 index 00000000000..66f1affabcd --- /dev/null +++ b/app/containers/NativeListRow/useListBackgroundColor.ts @@ -0,0 +1 @@ +export const useListBackgroundColor = (backgroundColor: string) => backgroundColor; diff --git a/app/containers/SearchBox/__snapshots__/SearchBox.test.tsx.snap b/app/containers/SearchBox/__snapshots__/SearchBox.test.tsx.snap index a72a4351e98..6cd9c5634b6 100644 --- a/app/containers/SearchBox/__snapshots__/SearchBox.test.tsx.snap +++ b/app/containers/SearchBox/__snapshots__/SearchBox.test.tsx.snap @@ -4,7 +4,7 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` const [text, setText] = useState(''); const { colors } = useTheme(); + const backgroundColor = useListBackgroundColor(colors.surfaceRoom); const internalOnChangeText = (value: string) => { setText(value); @@ -25,7 +26,7 @@ const SearchBox = ({ onChangeText, onSubmitEditing, testID }: TextInputProps) => }; return ( - + value={text} testID={testID} onClearInput={() => internalOnChangeText('')} - iconRight={'search'} + iconLeft={supportsLiquidGlass ? 'search' : undefined} + iconRight={supportsLiquidGlass ? undefined : 'search'} + glass /> ); diff --git a/app/containers/TextInput/FormTextInput.tsx b/app/containers/TextInput/FormTextInput.tsx index b76f2388db0..e20236bcea8 100644 --- a/app/containers/TextInput/FormTextInput.tsx +++ b/app/containers/TextInput/FormTextInput.tsx @@ -19,6 +19,7 @@ import { CustomIcon, type TIconsName } from '../CustomIcon'; import { TextInput } from './TextInput'; import { isIOS } from '~/lib/methods/helpers'; import Touch from '../Touch'; +import { GlassBackground, supportsLiquidGlass } from './GlassBackground'; const styles = StyleSheet.create({ error: { @@ -72,6 +73,11 @@ const styles = StyleSheet.create({ iconRight: { right: 12 }, + glassInput: { + borderWidth: 0, + borderRadius: 999, + backgroundColor: 'transparent' + }, clearInputIcon: { width: 20, height: 20 @@ -91,6 +97,7 @@ export interface IRCTextInputProps extends TextInputProps { iconRight?: TIconsName; left?: ReactElement; onClearInput?: () => void; + glass?: boolean; } const getInputError = (error: unknown): string => { @@ -120,12 +127,14 @@ export const FormTextInput = ({ placeholder, accessibilityLabel, showErrorMessage = true, + glass = false, ...inputProps }: IRCTextInputProps): ReactElement => { const { colors } = useTheme(); const [showPassword, setShowPassword] = useState(false); const showClearInput = onClearInput && value && value.length > 0; const inputError = getInputError(error); + const showGlass = glass && supportsLiquidGlass; // iOS 26 surfaces a system "Save Password?" sheet asynchronously after any // credential-classified field submit. It overlays the app and blocks // XCUITest hit-testing, breaking Maestro flows that interact with the @@ -161,6 +170,7 @@ export const FormTextInput = ({ ) : null} + {showGlass ? : null} = 26; + +const capsuleModifiers = [ + foregroundStyle('clear'), + glassEffect({ glass: { variant: 'regular', interactive: true }, shape: 'capsule' }) +]; + +export const GlassBackground = () => { + const { theme } = useTheme(); + return ( + + + + ); +}; diff --git a/app/containers/TextInput/GlassBackground.tsx b/app/containers/TextInput/GlassBackground.tsx new file mode 100644 index 00000000000..808cc0d6d6b --- /dev/null +++ b/app/containers/TextInput/GlassBackground.tsx @@ -0,0 +1,3 @@ +export const supportsLiquidGlass = false; + +export const GlassBackground = () => null; diff --git a/app/containers/UserItem.ios.tsx b/app/containers/UserItem.ios.tsx new file mode 100644 index 00000000000..9d190485cb6 --- /dev/null +++ b/app/containers/UserItem.ios.tsx @@ -0,0 +1,43 @@ +import { type StyleProp, type ViewStyle } from 'react-native'; + +import Avatar from './Avatar'; +import { CustomIcon, type TIconsName } from './CustomIcon'; +import NativeListRow from './NativeListRow'; +import { AVATAR_SIZE } from './NativeListRow/constants'; +import { useTheme } from '../theme'; +import i18n from '../i18n'; + +interface IUserItem { + name: string; + username: string; + onPress(): void; + testID: string; + onLongPress?: () => void; + style?: StyleProp; + icon?: TIconsName | null; + iconColor?: string; + isChecked?: boolean; + isFirst?: boolean; + isLast?: boolean; +} + +const UserItem = ({ name, username, onPress, testID, onLongPress, icon, iconColor, isChecked, isFirst, isLast }: IUserItem) => { + const { colors } = useTheme(); + const label = icon ? `${name} ${isChecked ? i18n.t('Selected') : i18n.t('Unselected')}` : name; + return ( + } + trailing={icon ? : undefined} + /> + ); +}; + +export default UserItem; diff --git a/app/containers/UserItem.tsx b/app/containers/UserItem.tsx index 7a6b8bffc11..9ff3d3be03d 100644 --- a/app/containers/UserItem.tsx +++ b/app/containers/UserItem.tsx @@ -44,6 +44,8 @@ interface IUserItem { icon?: TIconsName | null; iconColor?: string; isChecked?: boolean; + isFirst?: boolean; + isLast?: boolean; } const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, iconColor, isChecked }: IUserItem) => { diff --git a/app/views/AccessibilityAndAppearanceView/accessibilityAndAppearanceView.test.tsx b/app/views/AccessibilityAndAppearanceView/accessibilityAndAppearanceView.test.tsx index 5a2afdd97ac..4e6c7d41e1d 100644 --- a/app/views/AccessibilityAndAppearanceView/accessibilityAndAppearanceView.test.tsx +++ b/app/views/AccessibilityAndAppearanceView/accessibilityAndAppearanceView.test.tsx @@ -28,10 +28,10 @@ describe('Autoplay GIFs switch', () => { const { getByTestId } = render(); - const autoplaySwitch = getByTestId('accessibility-autoplay-gifs-switch').findByProps({ value: false }); - expect(autoplaySwitch.props.value).toBe(false); + const autoplaySwitch = getByTestId('accessibility-autoplay-gifs-switch').findByProps({ isOn: false }); + expect(autoplaySwitch.props.isOn).toBe(false); - fireEvent(autoplaySwitch, 'valueChange', true); + fireEvent(autoplaySwitch, 'isOnChange', { nativeEvent: { isOn: true } }); expect(mockSetAutoplayGifs).toHaveBeenCalledWith(true); }); diff --git a/app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx b/app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx index 495ef5d04ee..ed8093eeea3 100644 --- a/app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx +++ b/app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx @@ -3,22 +3,15 @@ import { StyleSheet, Text, View } from 'react-native'; import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; import { CustomIcon } from '~/containers/CustomIcon'; import * as List from '~/containers/List'; +import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { useNativeListMode } from '~/containers/List/native/context'; +import NativeListPicker from '~/containers/List/native/Picker'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; import sharedStyles from '~/views/Styles'; import { type TAlertDisplayType } from '..'; const styles = StyleSheet.create({ - leftTitleContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'flex-start' - }, - leftTitle: { - ...sharedStyles.textMedium, - fontSize: 16, - lineHeight: 24 - }, rightContainer: { flex: 1 }, @@ -50,6 +43,7 @@ const ListPicker = ({ } & IBaseParams) => { const { showActionSheet, hideActionSheet } = useActionSheet(); const { colors } = useTheme(); + const nativeListMode = useNativeListMode(); const OPTIONS: TOPTIONS = [ { @@ -80,6 +74,17 @@ const ListPicker = ({ right: option?.value === i.value ? () => : undefined })); + if (nativeListMode === 'native') { + return ( + ({ label: i.label, value: i.value }))} + selection={option.value} + onSelectionChange={selected => onChangeValue(selected as TAlertDisplayType)} + /> + ); + } + const openOptions = () => { const options = getOptions(); showActionSheet({ options }); @@ -88,11 +93,8 @@ const ListPicker = ({ ( - - {title} - - )} + title={title} + translateTitle={false} right={() => ( {option?.label} @@ -104,4 +106,4 @@ const ListPicker = ({ ); }; -export default ListPicker; +export default asNativeListRow(ListPicker); diff --git a/app/views/AddExistingChannelView/ChannelItem.ios.tsx b/app/views/AddExistingChannelView/ChannelItem.ios.tsx new file mode 100644 index 00000000000..dd327414a64 --- /dev/null +++ b/app/views/AddExistingChannelView/ChannelItem.ios.tsx @@ -0,0 +1,25 @@ +import * as List from '~/containers/List'; +import NativeListRow from '~/containers/NativeListRow'; +import { AVATAR_SIZE } from '~/containers/NativeListRow/constants'; +import I18n from '~/i18n'; +import { useTheme } from '~/theme'; +import { type IChannelItem } from './ChannelItem'; + +const ChannelItem = ({ title, icon, isChecked, onPress, testID, isFirst, isLast }: IChannelItem) => { + const { colors } = useTheme(); + return ( + } + trailing={isChecked ? : undefined} + /> + ); +}; + +export default ChannelItem; diff --git a/app/views/AddExistingChannelView/ChannelItem.tsx b/app/views/AddExistingChannelView/ChannelItem.tsx new file mode 100644 index 00000000000..10fd6b5614f --- /dev/null +++ b/app/views/AddExistingChannelView/ChannelItem.tsx @@ -0,0 +1,31 @@ +import * as List from '~/containers/List'; +import { type TIconsName } from '~/containers/CustomIcon'; +import { useTheme } from '~/theme'; + +export interface IChannelItem { + title: string; + icon: TIconsName; + isChecked: boolean; + onPress: () => void; + testID: string; + isFirst: boolean; + isLast: boolean; +} + +const ChannelItem = ({ title, icon, isChecked, onPress, testID }: IChannelItem) => { + const { colors } = useTheme(); + return ( + } + right={() => (isChecked ? : null)} + additionalAccessibilityLabel={isChecked} + additionalAccessibilityLabelCheck + /> + ); +}; + +export default ChannelItem; diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index 8e69edbdbe9..60f0520bf85 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -6,7 +6,6 @@ import { Q } from '@nozbe/watermelondb'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { textInputDebounceTime } from '~/lib/constants/debounceConfig'; -import * as List from '~/containers/List'; import database from '~/lib/database'; import I18n from '~/i18n'; import log, { events, logEvent } from '~/lib/methods/helpers/log'; @@ -23,6 +22,9 @@ import { addRoomsToTeam } from '~/lib/services/restApi'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import Navigation from '~/lib/navigation/appNavigation'; +import RowSeparator from '~/containers/NativeListRow/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import ChannelItem from './ChannelItem'; type TNavigation = NativeStackNavigationProp; type TRoute = RouteProp; @@ -34,6 +36,7 @@ const AddExistingChannelView = () => { const [selected, setSelected] = useState([]); const { colors } = useTheme(); + const listBackgroundColor = useListBackgroundColor(colors.surfaceRoom); const navigation = useNavigation(); const { @@ -160,24 +163,23 @@ const AddExistingChannelView = () => { ListHeaderComponent={ onSearchChangeText(text)} testID='add-existing-channel-view-search' /> } - renderItem={({ item }: { item: TSubscriptionModel }) => { + renderItem={({ item, index }: { item: TSubscriptionModel; index: number }) => { // TODO: reuse logic inside RoomTypeIcon const icon = item.t === SubscriptionType.GROUP && !item?.teamId ? 'channel-private' : 'channel-public'; return ( - toggleChannel(item.rid)} testID={`add-existing-channel-view-item-${item.name}`} - left={() => } - right={() => (isChecked(item.rid) ? : null)} - additionalAccessibilityLabel={isChecked(item.rid)} - additionalAccessibilityLabelCheck + isFirst={index === 0} + isLast={index === channels.length - 1} /> ); }} - ItemSeparatorComponent={List.Separator} - contentContainerStyle={{ backgroundColor: colors.surfaceRoom, paddingBottom: bottom }} + ItemSeparatorComponent={RowSeparator} + contentContainerStyle={{ backgroundColor: listBackgroundColor, paddingBottom: bottom }} keyboardShouldPersistTaps='always' /> diff --git a/app/views/DefaultBrowserView/index.tsx b/app/views/DefaultBrowserView/index.tsx index 9b126ab3916..d8a6eb5809f 100644 --- a/app/views/DefaultBrowserView/index.tsx +++ b/app/views/DefaultBrowserView/index.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'; +import { Fragment, useCallback, useEffect, useLayoutEffect, useState } from 'react'; import { FlatList, Linking } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -51,7 +51,7 @@ const DefaultBrowserView = () => { const navigation = useNavigation(); const { bottom } = useSafeAreaInsets(); - const paddingBottom = useMemo(() => Math.max(16, bottom), [bottom]); + const paddingBottom = Math.max(16, bottom); useLayoutEffect(() => { navigation.setOptions({ @@ -85,32 +85,45 @@ const DefaultBrowserView = () => { logEvent(events.DB_CHANGE_DEFAULT_BROWSER_F); } }, []); + const browsers = DEFAULT_BROWSERS.concat(supported); + const renderBrowser = (item: IBrowsersValues) => ( + + ); + return ( - item.value} - contentContainerStyle={[List.styles.contentContainerStyleFlatList, { paddingBottom }]} - renderItem={({ item }) => ( - - )} - ListHeaderComponent={ - <> - - - - } - ListFooterComponent={List.Separator} - ItemSeparatorComponent={List.Separator} - /> + {isIOS ? ( + + + {browsers.map(item => ( + {renderBrowser(item)} + ))} + + + ) : ( + item.value} + contentContainerStyle={[List.styles.contentContainerStyleFlatList, { paddingBottom }]} + renderItem={({ item }) => renderBrowser(item)} + ListHeaderComponent={ + <> + + + + } + ListFooterComponent={List.Separator} + ItemSeparatorComponent={List.Separator} + /> + )} ); }; diff --git a/app/views/DirectoryView/Options.tsx b/app/views/DirectoryView/Options.tsx index 7eb5f491f89..927b1709a11 100644 --- a/app/views/DirectoryView/Options.tsx +++ b/app/views/DirectoryView/Options.tsx @@ -50,7 +50,7 @@ const DirectoryOptions = ({ }; return ( - + {renderItem('channels')} @@ -72,7 +72,7 @@ const DirectoryOptions = ({ ) : null} - + ); }; diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index 7c94a9ebe81..e5b06a65611 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -27,6 +27,8 @@ import { getSubscriptionByRoomId } from '~/lib/database/services/Subscription'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { useDirectorySearch } from './hooks/useDirectorySearch'; +import RowSeparator from '~/containers/NativeListRow/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; interface IDirectoryViewProps { navigation: CompositeNavigationProp< @@ -37,6 +39,7 @@ interface IDirectoryViewProps { const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { const { colors } = useTheme(); + const listBackgroundColor = useListBackgroundColor(colors.surfaceRoom); const { bottom } = useSafeAreaInsets(); const { showActionSheet, hideActionSheet } = useActionSheet(); @@ -154,7 +157,9 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { onPress: () => onPressItem(item), testID: `directory-view-item-${item.name}`, style, - rid: item._id + rid: item._id, + isFirst: index === 0, + isLast: index === data.length - 1 }; if (type === 'users') { @@ -193,7 +198,7 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { }; return ( - + @@ -204,7 +209,7 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { extraData={type} keyExtractor={item => item._id} renderItem={renderItem} - ItemSeparatorComponent={List.Separator} + ItemSeparatorComponent={RowSeparator} keyboardShouldPersistTaps='always' ListFooterComponent={loading ? : null} onEndReached={() => loadMore()} diff --git a/app/views/DisplayPrefsView.tsx b/app/views/DisplayPrefsView.tsx index 9ff29069fea..b4ab2bc552c 100644 --- a/app/views/DisplayPrefsView.tsx +++ b/app/views/DisplayPrefsView.tsx @@ -13,14 +13,12 @@ import Radio from '../containers/Radio'; import { type IPreferences } from '../definitions'; import I18n from '../i18n'; import { type SettingsStackParamList } from '../stacks/types'; -import { useTheme } from '../theme'; import { events, logEvent } from '../lib/methods/helpers/log'; import { saveSortPreference } from '../lib/methods/userPreferencesMethods'; import { useAppSelector } from '../lib/hooks/useAppSelector'; const DisplayPrefsView = (): ReactElement => { const navigation = useNavigation>(); - const { colors } = useTheme(); const { sortBy, groupByType, showFavorites, showUnread, showAvatar, displayMode } = useAppSelector( state => state.sortPreferences @@ -78,8 +76,8 @@ const DisplayPrefsView = (): ReactElement => { setSortPreference({ displayMode: DisplayMode.Condensed }); }; - const renderCheckBox = (value: boolean) => ( - + const renderCheckBox = (value: boolean, onValueChange: () => void) => ( + ); const renderAvatarSwitch = (value: boolean) => ( @@ -156,7 +154,7 @@ const DisplayPrefsView = (): ReactElement => { testID='display-pref-view-unread' left={() => } onPress={toggleUnread} - right={() => renderCheckBox(showUnread)} + right={() => renderCheckBox(showUnread, toggleUnread)} additionalAccessibilityLabel={showUnread} accessibilityRole='checkbox' /> @@ -166,7 +164,7 @@ const DisplayPrefsView = (): ReactElement => { testID='display-pref-view-favorites' left={() => } onPress={toggleGroupByFavorites} - right={() => renderCheckBox(showFavorites)} + right={() => renderCheckBox(showFavorites, toggleGroupByFavorites)} additionalAccessibilityLabel={showFavorites} accessibilityRole='checkbox' /> @@ -176,7 +174,7 @@ const DisplayPrefsView = (): ReactElement => { testID='display-pref-view-types' left={() => } onPress={toggleGroupByType} - right={() => renderCheckBox(groupByType)} + right={() => renderCheckBox(groupByType, toggleGroupByType)} additionalAccessibilityLabel={groupByType} accessibilityRole='checkbox' /> diff --git a/app/views/LanguageView/index.tsx b/app/views/LanguageView/index.tsx index 20f171eb830..8e9142a5796 100644 --- a/app/views/LanguageView/index.tsx +++ b/app/views/LanguageView/index.tsx @@ -1,7 +1,7 @@ import { reloadAppAsync } from 'expo'; -import { useLayoutEffect } from 'react'; -import { FlatList } from 'react-native'; +import { Fragment, useLayoutEffect } from 'react'; import { useDispatch } from 'react-redux'; +import { FlatList } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -17,6 +17,7 @@ import I18n, { isRTL, LANGUAGES } from '~/i18n'; import database from '~/lib/database'; import { getUserSelector } from '~/selectors/login'; import { type SettingsStackParamList } from '~/stacks/types'; +import { isIOS } from '~/lib/methods/helpers'; import { showErrorAlert } from '~/lib/methods/helpers/info'; import log, { events, logEvent } from '~/lib/methods/helpers/log'; import { saveUserPreferences } from '~/lib/services/restApi'; @@ -92,26 +93,38 @@ const LanguageView = () => { } }; + const renderLanguage = (item: (typeof LANGUAGES)[number]) => ( + submit(item.value)} + /> + ); + return ( - item.value} - ListHeaderComponent={List.Separator} - ListFooterComponent={List.Separator} - contentContainerStyle={[List.styles.contentContainerStyleFlatList, { paddingBottom }]} - renderItem={({ item }) => ( - submit(item.value)} - /> - )} - ItemSeparatorComponent={List.Separator} - /> + {isIOS ? ( + + + {LANGUAGES.map(item => ( + {renderLanguage(item)} + ))} + + + ) : ( + item.value} + ListHeaderComponent={List.Separator} + ListFooterComponent={List.Separator} + contentContainerStyle={[List.styles.contentContainerStyleFlatList, { paddingBottom }]} + renderItem={({ item }) => renderLanguage(item)} + ItemSeparatorComponent={List.Separator} + /> + )} ); }; diff --git a/app/views/MediaAutoDownloadView/ListPicker.tsx b/app/views/MediaAutoDownloadView/ListPicker.tsx index ccf3f4f41cd..5eb56804053 100644 --- a/app/views/MediaAutoDownloadView/ListPicker.tsx +++ b/app/views/MediaAutoDownloadView/ListPicker.tsx @@ -3,22 +3,15 @@ import { StyleSheet, Text, View } from 'react-native'; import { useActionSheet } from '~/containers/ActionSheet'; import * as List from '~/containers/List'; +import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { useNativeListMode } from '~/containers/List/native/context'; +import NativeListPicker from '~/containers/List/native/Picker'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; import { type MediaDownloadOption } from '~/lib/constants/mediaAutoDownload'; const styles = StyleSheet.create({ - leftTitleContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'flex-start' - }, - leftTitle: { - ...sharedStyles.textMedium, - fontSize: 16, - lineHeight: 24 - }, rightContainer: { flex: 1 }, @@ -67,6 +60,7 @@ const ListPicker = ({ } & IBaseParams) => { const { showActionSheet, hideActionSheet } = useActionSheet(); const { colors } = useTheme(); + const nativeListMode = useNativeListMode(); const option = OPTIONS.find(option => option.value === value) || OPTIONS[2]; const getOptions = (): ReactElement => ( @@ -93,15 +87,24 @@ const ListPicker = ({ /* when picking an option the label should be Never but when showing among the other settings the label should be Off */ const label = option.label === 'Never' ? I18n.t('Off') : I18n.t(option.label); + if (nativeListMode === 'native') { + return ( + ({ label: I18n.t(i.label), value: i.value, testID: `${testID}-${i.value}` }))} + selection={option.value} + onSelectionChange={selected => onChangeValue(selected as MediaDownloadOption)} + /> + ); + } + return ( showActionSheet({ children: getOptions() })} - title={() => ( - - {title} - - )} + title={title} + translateTitle={false} right={() => ( {label} @@ -113,4 +116,4 @@ const ListPicker = ({ ); }; -export default ListPicker; +export default asNativeListRow(ListPicker); diff --git a/app/views/NewMessageView/ButtonCreate.ios.tsx b/app/views/NewMessageView/ButtonCreate.ios.tsx new file mode 100644 index 00000000000..cccb1db9c22 --- /dev/null +++ b/app/views/NewMessageView/ButtonCreate.ios.tsx @@ -0,0 +1,41 @@ +import { I18nManager } from 'react-native'; +import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; +import NativeListIcon from '~/containers/List/native/Icon.ios'; +import NativeListRow from '~/containers/NativeListRow'; +import { PlainSeparator } from '~/containers/NativeListRow/Separator'; +import I18n from '~/i18n'; +import { useTheme } from '~/theme'; + +interface IButton { + onPress: () => void; + testID: string; + title: string; + icon: TIconsName; + isFirst?: boolean; + isLast?: boolean; +} + +const CHEVRON = I18nManager.isRTL ? 'chevron-left' : 'chevron-right'; + +const ButtonCreate = ({ onPress, testID, title, icon, isFirst, isLast }: IButton) => { + const { colors } = useTheme(); + const translatedTitle = I18n.t(title); + + return ( + <> + } + trailing={} + /> + {isLast ? null : } + + ); +}; + +export default ButtonCreate; diff --git a/app/views/NewMessageView/ButtonCreate.tsx b/app/views/NewMessageView/ButtonCreate.tsx index e33d4ba54a4..671af7311f4 100644 --- a/app/views/NewMessageView/ButtonCreate.tsx +++ b/app/views/NewMessageView/ButtonCreate.tsx @@ -8,6 +8,8 @@ interface IButton { testID: string; title: string; icon: TIconsName; + isFirst?: boolean; + isLast?: boolean; } const ButtonCreate = ({ onPress, testID, title, icon }: IButton) => { diff --git a/app/views/NewMessageView/HeaderNewMessage.tsx b/app/views/NewMessageView/HeaderNewMessage.tsx index f5dca89ffef..da4350d9574 100644 --- a/app/views/NewMessageView/HeaderNewMessage.tsx +++ b/app/views/NewMessageView/HeaderNewMessage.tsx @@ -5,8 +5,8 @@ import { useDispatch } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; import { createChannelRequest } from '~/actions/createChannel'; -import { themes } from '~/lib/constants/colors'; import SearchBox from '~/containers/SearchBox'; +import { type TIconsName } from '~/containers/CustomIcon'; import I18n from '~/i18n'; import Navigation from '~/lib/navigation/appNavigation'; import { useTheme } from '~/theme'; @@ -15,6 +15,7 @@ import { type NewMessageStackParamList } from '~/stacks/types'; import { compareServerVersion } from '~/lib/methods/helpers'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { usePermissions } from '~/lib/hooks/usePermissions'; +import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; import ButtonCreate from './ButtonCreate'; const styles = StyleSheet.create({ @@ -23,10 +24,19 @@ const styles = StyleSheet.create({ } }); +interface IButtonConfig { + visible: boolean; + onPress: () => void; + title: string; + icon: TIconsName; + testID: string; +} + const HeaderNewMessage = ({ maxUsers, onChangeText }: { maxUsers: number; onChangeText: (text: string) => void }) => { const navigation = useNavigation>(); const dispatch = useDispatch(); - const { theme } = useTheme(); + const { colors } = useTheme(); + const listBackgroundColor = useListBackgroundColor(colors.surfaceTint); const serverVersion = useAppSelector(state => state.server.version as string); @@ -68,39 +78,52 @@ const HeaderNewMessage = ({ maxUsers, onChangeText }: { maxUsers: number; onChan }); }, [navigation]); + const buttons = [ + { + visible: createPublicChannelPermission || createPrivateChannelPermission, + onPress: createChannel, + title: 'Channel', + icon: 'channel-public', + testID: 'new-message-view-create-channel' + }, + { + visible: compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.13.0') && createTeamPermission, + onPress: createTeam, + title: 'Team', + icon: 'teams', + testID: 'new-message-view-create-team' + }, + { + visible: maxUsers > 2 && createDirectMessagePermission, + onPress: createGroupChat, + title: 'Direct_message', + icon: 'message', + testID: 'new-message-view-create-direct-message' + }, + { + visible: createDiscussionPermission, + onPress: createDiscussion, + title: 'Discussion', + icon: 'discussions', + testID: 'new-message-view-create-discussion' + } + ].filter((button): button is IButtonConfig => Boolean(button.visible)); + return ( - <> - - {createPublicChannelPermission || createPrivateChannelPermission ? ( - - ) : null} - {compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.13.0') && createTeamPermission ? ( - - ) : null} - {maxUsers > 2 && createDirectMessagePermission ? ( - - ) : null} - {createDiscussionPermission ? ( - - ) : null} - onChangeText(text)} testID='new-message-view-search' /> - - + + {buttons.map((button, index) => ( + + ))} + onChangeText(text)} testID='new-message-view-search' /> + ); }; diff --git a/app/views/NewMessageView/Item.ios.tsx b/app/views/NewMessageView/Item.ios.tsx new file mode 100644 index 00000000000..c62d888563a --- /dev/null +++ b/app/views/NewMessageView/Item.ios.tsx @@ -0,0 +1,52 @@ +import Avatar from '~/containers/Avatar'; +import NativeListRow from '~/containers/NativeListRow'; +import { AVATAR_SIZE } from '~/containers/NativeListRow/constants'; +import I18n from '~/i18n'; +import { useMediaCallPermission } from '~/lib/hooks/useMediaCallPermission'; +import { useIsInActiveVoipCall } from '~/lib/services/voip/isInActiveVoipCall'; +import { isSelfUserId } from '~/lib/services/voip/isSelfUserId'; +import { useStartMediaCall } from './useStartMediaCall'; + +interface IItem { + userId: string; + name: string; + username: string; + onPress(): void; + testID: string; + onLongPress?: () => void; + isFirst?: boolean; + isLast?: boolean; +} + +const Item = ({ userId, name, username, onPress, testID, onLongPress, isFirst, isLast }: IItem) => { + const hasMediaCallPermission = useMediaCallPermission(); + const isInActiveCall = useIsInActiveVoipCall(); + const isSelf = isSelfUserId(userId); + const startMediaCall = useStartMediaCall({ userId, name, username }); + + return ( + } + trailingAction={ + hasMediaCallPermission && !isSelf + ? { + icon: 'phone', + onPress: startMediaCall, + testID: `${testID}-call`, + accessibilityLabel: I18n.t('Voice_call'), + disabled: isInActiveCall + } + : undefined + } + /> + ); +}; + +export default Item; diff --git a/app/views/NewMessageView/Item.test.tsx b/app/views/NewMessageView/Item.test.tsx index 53d9dcf836c..e766c121658 100644 --- a/app/views/NewMessageView/Item.test.tsx +++ b/app/views/NewMessageView/Item.test.tsx @@ -58,12 +58,12 @@ describe('NewMessageView Item', () => { }); it('should display the name', () => { - const { getByText } = render( + const { UNSAFE_getByProps } = render( {}} testID='new-message-view-item-john.doe' /> ); - expect(getByText('John Doe')).toBeTruthy(); + expect(UNSAFE_getByProps({ text: 'John Doe' })).toBeTruthy(); }); it('should call onPress when main item is pressed', () => { @@ -145,12 +145,14 @@ describe('NewMessageView Item', () => { }); it('should have correct accessibility label', () => { - const { getByLabelText } = render( + const { getByTestId } = render( {}} testID='new-message-view-item-john.doe' /> ); - expect(getByLabelText('John Doe')).toBeTruthy(); + expect(getByTestId('new-message-view-item-john.doe').props.modifiers).toContainEqual( + expect.objectContaining({ $type: 'accessibilityLabel', label: 'John Doe' }) + ); }); it('should match snapshot when hasMediaCallPermission is false', () => { diff --git a/app/views/NewMessageView/Item.tsx b/app/views/NewMessageView/Item.tsx index 032e31a9af6..6d7e8933685 100644 --- a/app/views/NewMessageView/Item.tsx +++ b/app/views/NewMessageView/Item.tsx @@ -7,11 +7,9 @@ import sharedStyles from '../Styles'; import { useTheme } from '~/theme'; import I18n from '~/i18n'; import { useMediaCallPermission } from '~/lib/hooks/useMediaCallPermission'; -import { usePeerAutocompleteStore } from '~/lib/services/voip/usePeerAutocompleteStore'; import { useIsInActiveVoipCall } from '~/lib/services/voip/isInActiveVoipCall'; import { isSelfUserId } from '~/lib/services/voip/isSelfUserId'; -import { showActionSheetRef } from '~/containers/ActionSheet'; -import { NewMediaCall } from '~/containers/NewMediaCall'; +import { useStartMediaCall } from './useStartMediaCall'; interface IItem { userId: string; @@ -20,6 +18,8 @@ interface IItem { onPress(): void; testID: string; onLongPress?: () => void; + isFirst?: boolean; + isLast?: boolean; } const Item = ({ userId, name, username, onPress, testID, onLongPress }: IItem) => { @@ -28,17 +28,7 @@ const Item = ({ userId, name, username, onPress, testID, onLongPress }: IItem) = const isInActiveCall = useIsInActiveVoipCall(); const isSelf = isSelfUserId(userId); - const handleCallPress = () => { - if (!userId || isInActiveCall || isSelf) return; - usePeerAutocompleteStore.getState().setSelectedPeer({ type: 'user', value: userId, label: name, username }); - showActionSheetRef({ - children: , - portraitSnaps: ['60%'], - landscapeSnaps: ['90%'], - enableContentPanningGesture: false, - fullContainer: true - }); - }; + const handleCallPress = useStartMediaCall({ userId, name, username }); return ( - - - - - - - - John Doe - - - - + + + + + + + + + + + + + + + `; exports[`Story Snapshots: Default should match snapshot 1`] = ` @@ -167,58 +183,28 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` } } > - - - - - - - - - John Doe - - - - + + + + + + + + + + + + -  - - - - + + + + + `; @@ -397,57 +414,28 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` } } > - - - - - + - - - + + + + + + + + + + + + - Very Long Display Name That Might Get Truncated - - - - -  - - - - + { + "$type": "foregroundStyle", + "style": { + "color": "#2F343D", + "type": "color", + }, + }, + ] + } + onGlobalEvent={[Function]} + text="" + /> + + + + `; @@ -626,57 +640,28 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` } } > - - - - - + - - - + + + + + + + + + + + + - John Doe - - - - -  - - - - + { + "$type": "foregroundStyle", + "style": { + "color": "#2F343D", + "type": "color", + }, + }, + ] + } + onGlobalEvent={[Function]} + text="" + /> + + + + `; diff --git a/app/views/NewMessageView/index.tsx b/app/views/NewMessageView/index.tsx index a61e7b7f280..c726096e86e 100644 --- a/app/views/NewMessageView/index.tsx +++ b/app/views/NewMessageView/index.tsx @@ -20,6 +20,8 @@ import { type NewMessageStackParamList } from '~/stacks/types'; import { search as runSearch } from '~/lib/methods/search'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; +import RowSeparator from '~/containers/NativeListRow/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; import Item from './Item'; import HeaderNewMessage from './HeaderNewMessage'; import { getUidDirectMessage } from '~/lib/methods/helpers/helpers'; @@ -37,6 +39,7 @@ const NewMessageView = () => { const searchId = useRef(0); const { colors } = useTheme(); + const listBackgroundColor = useListBackgroundColor(colors.surfaceTint); const navigation = useNavigation>(); const { bottom } = useSafeAreaInsets(); @@ -110,13 +113,15 @@ const NewMessageView = () => { [isMasterDetail, navigation] ); + const data = search.length > 0 ? search : chats; + return ( 0 ? search : chats} + data={data} keyExtractor={item => item._id || item.rid} ListHeaderComponent={} - renderItem={({ item }) => { + renderItem={({ item, index }) => { const itemSearch = item as ISearch; const itemModel = item as TSubscriptionModel; const userId = itemSearch.search ? itemSearch._id : getUidDirectMessage(itemModel); @@ -128,12 +133,14 @@ const NewMessageView = () => { username={itemSearch.search ? itemSearch.username : itemModel.name} onPress={() => goRoom(itemModel)} testID={`new-message-view-item-${item.name}`} + isFirst={index === 0} + isLast={index === data.length - 1} /> ); }} - ItemSeparatorComponent={List.Separator} + ItemSeparatorComponent={RowSeparator} ListFooterComponent={searching ? () => : List.Separator} - style={{ backgroundColor: colors.surfaceTint }} + style={{ backgroundColor: listBackgroundColor }} contentContainerStyle={{ paddingBottom: bottom }} keyboardShouldPersistTaps='always' /> diff --git a/app/views/NewMessageView/useStartMediaCall.tsx b/app/views/NewMessageView/useStartMediaCall.tsx new file mode 100644 index 00000000000..9c68b7f3eae --- /dev/null +++ b/app/views/NewMessageView/useStartMediaCall.tsx @@ -0,0 +1,28 @@ +import { showActionSheetRef } from '~/containers/ActionSheet'; +import { NewMediaCall } from '~/containers/NewMediaCall'; +import { usePeerAutocompleteStore } from '~/lib/services/voip/usePeerAutocompleteStore'; +import { useIsInActiveVoipCall } from '~/lib/services/voip/isInActiveVoipCall'; +import { isSelfUserId } from '~/lib/services/voip/isSelfUserId'; + +interface IMediaCallPeer { + userId: string; + name: string; + username: string; +} + +export const useStartMediaCall = ({ userId, name, username }: IMediaCallPeer) => { + const isInActiveCall = useIsInActiveVoipCall(); + const isSelf = isSelfUserId(userId); + + return () => { + if (!userId || isInActiveCall || isSelf) return; + usePeerAutocompleteStore.getState().setSelectedPeer({ type: 'user', value: userId, label: name, username }); + showActionSheetRef({ + children: , + portraitSnaps: ['60%'], + landscapeSnaps: ['90%'], + enableContentPanningGesture: false, + fullContainer: true + }); + }; +}; diff --git a/app/views/NotificationPreferencesView/index.tsx b/app/views/NotificationPreferencesView/index.tsx index 36c2f67f831..437b15ee7e0 100644 --- a/app/views/NotificationPreferencesView/index.tsx +++ b/app/views/NotificationPreferencesView/index.tsx @@ -6,6 +6,9 @@ import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; import { CustomIcon } from '~/containers/CustomIcon'; import * as List from '~/containers/List'; +import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { useNativeListMode } from '~/containers/List/native/context'; +import NativeListPicker from '~/containers/List/native/Picker'; import SafeAreaView from '~/containers/SafeAreaView'; import { type IRoomNotifications, type TRoomNotificationsModel } from '~/definitions'; import I18n from '~/i18n'; @@ -43,6 +46,7 @@ const RenderListPicker = ({ } & IBaseParams) => { const { showActionSheet, hideActionSheet } = useActionSheet(); const { colors } = useTheme(); + const nativeListMode = useNativeListMode(); const pref = room[preference] ? OPTIONS[preference as TOptions].find(option => option.value === room[preference]) @@ -62,6 +66,25 @@ const RenderListPicker = ({ const label = option?.label ? I18n.t(option?.label, { defaultValue: option?.label, second: option?.second }) : option?.label; + if (nativeListMode === 'native') { + return ( + ({ + label: I18n.t(i.label, { defaultValue: i.label, second: i.second }), + value: i.value.toString() + }))} + selection={option?.value.toString() ?? ''} + onSelectionChange={selected => { + const previous = option; + onChangeValue(preference, { [preference]: selected }, () => setOption(previous)); + setOption(OPTIONS[preference as TOptions].find(i => i.value.toString() === selected)); + }} + /> + ); + } + return ( { const [switchValue, setSwitchValue] = useState(!room[preference]); return ( @@ -200,7 +225,7 @@ const NotificationPreferencesView = (): ReactElement => { - { onChangeValue={handleSaveNotificationSettings} /> - { - { - { + const { colors } = useTheme(); + const title = I18n.t(item.label, { defaultValue: item.label, second: item?.second }); + return ( + : undefined} + /> + ); +}; + +export default Item; diff --git a/app/views/PickerView/Item.tsx b/app/views/PickerView/Item.tsx new file mode 100644 index 00000000000..decc31504c0 --- /dev/null +++ b/app/views/PickerView/Item.tsx @@ -0,0 +1,28 @@ +import * as List from '~/containers/List'; +import I18n from '~/i18n'; +import { useTheme } from '~/theme'; +import { type IOptionsField } from '../NotificationPreferencesView/options'; + +export interface IPickerItem { + item: IOptionsField; + selected: boolean; + onItemPress: () => void; + isFirst: boolean; + isLast: boolean; +} + +const Item = ({ item, selected, onItemPress }: IPickerItem) => { + const { colors } = useTheme(); + return ( + (selected ? : null)} + onPress={onItemPress} + translateTitle={false} + additionalAccessibilityLabel={selected} + additionalAccessibilityLabelCheck + /> + ); +}; + +export default Item; diff --git a/app/views/PickerView.tsx b/app/views/PickerView/index.tsx similarity index 66% rename from app/views/PickerView.tsx rename to app/views/PickerView/index.tsx index ecd79700e98..6348d8a4696 100644 --- a/app/views/PickerView.tsx +++ b/app/views/PickerView/index.tsx @@ -2,17 +2,19 @@ import { type ReactElement, useLayoutEffect, useState } from 'react'; import { FlatList, StyleSheet, Text, type TextInputProps } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { textInputDebounceTime } from '../lib/constants/debounceConfig'; -import * as List from '../containers/List'; -import SafeAreaView from '../containers/SafeAreaView'; -import SearchBox from '../containers/SearchBox'; -import I18n from '../i18n'; -import { useAppNavigation, useAppRoute } from '../lib/hooks/navigation'; -import { useDebounce } from '../lib/methods/helpers'; -import { type TNavigation } from '../stacks/stackType'; -import { useTheme } from '../theme'; -import { type IOptionsField } from './NotificationPreferencesView/options'; -import sharedStyles from './Styles'; +import { textInputDebounceTime } from '~/lib/constants/debounceConfig'; +import * as List from '~/containers/List'; +import SafeAreaView from '~/containers/SafeAreaView'; +import SearchBox from '~/containers/SearchBox'; +import I18n from '~/i18n'; +import { useAppNavigation, useAppRoute } from '~/lib/hooks/navigation'; +import { useDebounce } from '~/lib/methods/helpers'; +import { type TNavigation } from '~/stacks/stackType'; +import { useTheme } from '~/theme'; +import sharedStyles from '../Styles'; +import { PlainSeparator } from '~/containers/NativeListRow/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import Item from './Item'; const styles = StyleSheet.create({ noResult: { @@ -23,26 +25,6 @@ const styles = StyleSheet.create({ } }); -interface IItem { - item: IOptionsField; - selected: boolean; - onItemPress: () => void; -} - -const Item = ({ item, selected, onItemPress }: IItem) => { - const { colors } = useTheme(); - return ( - (selected ? : null)} - onPress={onItemPress} - translateTitle={false} - additionalAccessibilityLabel={selected} - additionalAccessibilityLabelCheck - /> - ); -}; - const RenderSearch = ({ onChangeText }: TextInputProps) => ( <> @@ -57,6 +39,7 @@ const PickerView = (): ReactElement => { } = useAppRoute(); const { colors } = useTheme(); + const listBackgroundColor = useListBackgroundColor(colors.surfaceHover); const { bottom } = useSafeAreaInsets(); const [data, setData] = useState(paramData); @@ -93,20 +76,22 @@ const PickerView = (): ReactElement => { }; return ( - + item.value as string} - renderItem={({ item }) => ( + renderItem={({ item, index }) => ( handleChangeValue(item.value)} + isFirst={index === 0} + isLast={index === data.length - 1} /> )} onEndReached={handleOnEndReached} onEndReachedThreshold={0.5} - ItemSeparatorComponent={List.Separator} + ItemSeparatorComponent={PlainSeparator} ListHeaderComponent={} contentContainerStyle={{ paddingBottom: bottom }} ListFooterComponent={List.Separator} diff --git a/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx b/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx index e5b76c2bd36..92270a5a259 100644 --- a/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx +++ b/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx @@ -2,6 +2,7 @@ import { Alert, StyleSheet, Text } from 'react-native'; import { type ReactElement } from 'react'; import * as List from '~/containers/List'; +import { asNativeListSection } from '~/containers/List/native/rowMarkers'; import i18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useTheme } from '~/theme'; @@ -10,7 +11,7 @@ import sharedStyles from '~/views/Styles'; const WARNING_MINIMUM_VALUE = 70; const WARNING_MAXIMUM_VALUE = 90; -export default function CommunityEditionPushQuota(): ReactElement | null { +function CommunityEditionPushQuota(): ReactElement | null { const { colors } = useTheme(); const { consumptionPercentage, isCommunityEdition } = useAppSelector(state => ({ isCommunityEdition: state.troubleshootingNotification.isCommunityEdition, @@ -55,3 +56,5 @@ const styles = StyleSheet.create({ fontSize: 16 } }); + +export default asNativeListSection(CommunityEditionPushQuota); diff --git a/app/views/PushTroubleshootView/components/NotificationDelay.tsx b/app/views/PushTroubleshootView/components/NotificationDelay.tsx index 808da1d75da..cbfd9ecd4e4 100644 --- a/app/views/PushTroubleshootView/components/NotificationDelay.tsx +++ b/app/views/PushTroubleshootView/components/NotificationDelay.tsx @@ -3,8 +3,9 @@ import { type ReactElement } from 'react'; import NewWindowIcon from '~/containers/NewWindowIcon'; import * as List from '~/containers/List'; +import { asNativeListSection } from '~/containers/List/native/rowMarkers'; -export default function NotificationDelay(): ReactElement { +function NotificationDelay(): ReactElement { const openNotificationDocumentation = () => Linking.openURL('https://go.rocket.chat/i/push-notifications'); return ( @@ -22,3 +23,5 @@ export default function NotificationDelay(): ReactElement { ); } + +export default asNativeListSection(NotificationDelay); diff --git a/app/views/RoomActionsView/components/CallSection.tsx b/app/views/RoomActionsView/components/CallSection.tsx index f8d8e8ec0be..61529bbbf8d 100644 --- a/app/views/RoomActionsView/components/CallSection.tsx +++ b/app/views/RoomActionsView/components/CallSection.tsx @@ -1,13 +1,14 @@ import { type ReactElement, useEffect, useState } from 'react'; import * as List from '~/containers/List'; +import { asNativeListSection } from '~/containers/List/native/rowMarkers'; import { useVideoConf } from '~/lib/hooks/useVideoConf'; import type { TSubscriptionModel } from '~/definitions'; import { useNewMediaCall } from '~/lib/hooks/useNewMediaCall'; import { useIsInActiveVoipCall } from '~/lib/services/voip/isInActiveVoipCall'; import { videoConferenceGetCapabilities } from '~/lib/services/restApi'; -export default function CallSection({ +function CallSection({ room, disabled, itsMe @@ -69,3 +70,5 @@ export default function CallSection({ ); } + +export default asNativeListSection(CallSection); diff --git a/app/views/RoomMembersView/components/ActionsSection.tsx b/app/views/RoomMembersView/components/ActionsSection.tsx index a78300c488d..b54db44fca7 100644 --- a/app/views/RoomMembersView/components/ActionsSection.tsx +++ b/app/views/RoomMembersView/components/ActionsSection.tsx @@ -66,7 +66,7 @@ export default function ActionsSection({ rid, t, joined, abacAttributes }: IActi }; return ( - + {['c', 'p'].includes(t) && canAddUser ? ( <> diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 8f3a0d8e7e2..aa74a531e7a 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -1,6 +1,6 @@ import { type NavigationProp, type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import { type ReactElement, useCallback, useEffect, useReducer, useRef } from 'react'; -import { FlatList, Text, View } from 'react-native'; +import { FlatList, Text } from 'react-native'; import { shallowEqual } from 'react-redux'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -9,8 +9,9 @@ import { sendLoadingEvent } from '~/containers/Loading'; import ActivityIndicator from '~/containers/ActivityIndicator'; import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import * as List from '~/containers/List'; import SafeAreaView from '~/containers/SafeAreaView'; +import RowSeparator from '~/containers/NativeListRow/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; import SearchBox from '~/containers/SearchBox'; import UserItem from '~/containers/UserItem'; import Radio from '~/containers/Radio'; @@ -73,6 +74,7 @@ const RightIcon = ({ check, label }: { check: boolean; label: string }) => { const RoomMembersView = (): ReactElement => { const { showActionSheet } = useActionSheet(); const { colors } = useTheme(); + const listBackgroundColor = useListBackgroundColor(colors.surfaceHover); const { params } = useRoute>(); const navigation = useNavigation>(); @@ -429,20 +431,21 @@ const RoomMembersView = (): ReactElement => { ( - - onPressUser(item)} - testID={`room-members-view-item-${item.username}`} - /> - + renderItem={({ item, index }) => ( + onPressUser(item)} + testID={`room-members-view-item-${item.username}`} + style={{ backgroundColor: colors.surfaceRoom }} + isFirst={index === 0} + isLast={index === state.members.length - 1} + /> )} - style={styles.list} + style={[styles.list, { backgroundColor: listBackgroundColor }]} contentContainerStyle={{ paddingBottom: bottom }} keyExtractor={item => item._id} - ItemSeparatorComponent={List.Separator} + ItemSeparatorComponent={RowSeparator} ListHeaderComponent={ <> { + const { colors } = useTheme(); + const renderTrailing = () => { + if (isRadio) { + return ( + + ); + } + return isChecked ? : undefined; + }; + return ( + <> + {isFirst ? null : } + } + trailing={alert ? : renderTrailing()} + /> + + ); +}; + +export default Item; diff --git a/app/views/SelectListView/Item.tsx b/app/views/SelectListView/Item.tsx new file mode 100644 index 00000000000..95abd9b1590 --- /dev/null +++ b/app/views/SelectListView/Item.tsx @@ -0,0 +1,50 @@ +import * as List from '~/containers/List'; +import { ICON_SIZE } from '~/containers/List/constants'; +import { type TIconsName } from '~/containers/CustomIcon'; +import Radio from '~/containers/Radio'; +import { useTheme } from '~/theme'; + +export interface ISelectListItem { + name: string; + icon: TIconsName; + alert?: boolean; + isRadio?: boolean; + isChecked: boolean; + accessibilityState: string; + onPress: () => void; + isFirst: boolean; + isLast: boolean; +} + +const Item = ({ name, icon, alert, isRadio, isChecked, accessibilityState, onPress }: ISelectListItem) => { + const { colors } = useTheme(); + const renderRight = () => { + if (isRadio) { + return ( + + ); + } + return isChecked ? : null; + }; + return ( + <> + + } + right={renderRight} + additionalAccessibilityLabel={accessibilityState} + /> + + ); +}; + +export default Item; diff --git a/app/views/SelectListView.tsx b/app/views/SelectListView/index.tsx similarity index 65% rename from app/views/SelectListView.tsx rename to app/views/SelectListView/index.tsx index 39f193bbfca..0daf0c1efa0 100644 --- a/app/views/SelectListView.tsx +++ b/app/views/SelectListView/index.tsx @@ -4,20 +4,19 @@ import { type RouteProp } from '@react-navigation/native'; import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; import { Component } from 'react'; -import { type ChatsStackParamList } from '../stacks/types'; -import log from '../lib/methods/helpers/log'; -import * as List from '../containers/List'; -import I18n from '../i18n'; -import * as HeaderButton from '../containers/Header/components/HeaderButton'; -import { themes } from '../lib/constants/colors'; -import { type TSupportedThemes, withTheme } from '../theme'; -import SafeAreaView from '../containers/SafeAreaView'; -import { ICON_SIZE } from '../containers/List/constants'; -import SearchBox from '../containers/SearchBox'; -import Radio from '../containers/Radio'; -import sharedStyles from './Styles'; -import { type TDataSelect } from '../definitions/IDataSelect'; -import { withMasterDetail } from '../lib/hooks/useMasterDetail'; +import { type ChatsStackParamList } from '~/stacks/types'; +import log from '~/lib/methods/helpers/log'; +import I18n from '~/i18n'; +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; +import { themes } from '~/lib/constants/colors'; +import { type TSupportedThemes, withTheme } from '~/theme'; +import SafeAreaView from '~/containers/SafeAreaView'; +import SearchBox from '~/containers/SearchBox'; +import sharedStyles from '../Styles'; +import Item from './Item'; +import { isIOS } from '~/lib/methods/helpers'; +import { type TDataSelect } from '~/definitions/IDataSelect'; +import { withMasterDetail } from '~/lib/hooks/useMasterDetail'; const styles = StyleSheet.create({ buttonText: { @@ -138,55 +137,27 @@ class SelectListView extends Component { - const { theme } = this.props; - const { selected } = this.state; - + renderItem = ({ item, index }: { item: TDataSelect; index: number }) => { + const { isSearching, data, dataFiltered } = this.state; + const rowCount = (isSearching ? dataFiltered : data)?.length ?? 0; const channelIcon = item.t === 'p' ? 'channel-private' : 'channel-public'; const teamIcon = item.t === 'p' ? 'teams-private' : 'teams'; - const icon = item.teamMain ? teamIcon : channelIcon; - const checked = this.isChecked(item.rid) ? 'check' : ''; - - const showRadio = () => ( - - ); - const showCheck = () => - checked !== '' ? ( - - ) : null; - - const handleAcessibilityLabel = (rid: string) => { - let label = ''; - if (this.isRadio) { - label = this.isChecked(rid) ? I18n.t('Selected') : I18n.t('Unselected'); - } else { - label = this.isChecked(rid) ? I18n.t('Checked') : I18n.t('Unchecked'); - } - return label; - }; + const isChecked = this.isChecked(item.rid); + const checkedLabel = isChecked ? I18n.t('Checked') : I18n.t('Unchecked'); + const radioLabel = isChecked ? I18n.t('Selected') : I18n.t('Unselected'); return ( - <> - - (item.alert ? this.showAlert() : this.toggleItem(item.rid))} - alert={item.alert} - left={() => } - right={() => (this.isRadio ? showRadio() : showCheck())} - additionalAccessibilityLabel={handleAcessibilityLabel(item.rid)} - /> - + (item.alert ? this.showAlert() : this.toggleItem(item.rid))} + isFirst={index === 0} + isLast={index === rowCount - 1} + /> ); }; @@ -201,7 +172,10 @@ class SelectListView extends Component item.rid} renderItem={this.renderItem} ListHeaderComponent={this.isSearch ? this.renderSearch : this.renderInfoText} - contentContainerStyle={{ backgroundColor: themes[theme].surfaceRoom, paddingBottom: insets.bottom }} + contentContainerStyle={{ + backgroundColor: isIOS ? themes[theme].surfaceHover : themes[theme].surfaceRoom, + paddingBottom: insets.bottom + }} keyboardShouldPersistTaps='always' /> diff --git a/app/views/SelectedUsersView/Header.tsx b/app/views/SelectedUsersView/Header.tsx index d444bd565e1..361c2dd2fb0 100644 --- a/app/views/SelectedUsersView/Header.tsx +++ b/app/views/SelectedUsersView/Header.tsx @@ -1,7 +1,6 @@ import { useRef } from 'react'; import { FlatList, View, Text, StyleSheet } from 'react-native'; -import { themes } from '~/lib/constants/colors'; import SearchBox from '~/containers/SearchBox'; import I18n from '~/i18n'; import { type ISelectedUser } from '~/reducers/selectedUsers'; @@ -9,6 +8,7 @@ import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import Chip from '~/containers/Chip'; +import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; const styles = StyleSheet.create({ selectedText: { @@ -33,7 +33,8 @@ const Header = ({ onPressItem: (userItem: ISelectedUser) => void; }) => { const flatlist = useRef>(null); - const { theme } = useTheme(); + const { colors } = useTheme(); + const backgroundColor = useListBackgroundColor(colors.surfaceRoom); const { users } = useAppSelector(state => ({ users: state.selectedUsers.users })); @@ -41,11 +42,11 @@ const Header = ({ const onContentSizeChange = () => flatlist?.current?.scrollToEnd({ animated: true }); return ( - + onChangeText(text)} testID='select-users-view-search' /> {users.length === 0 ? null : ( - + {I18n.t('N_Selected_members', { n: users.length })} { const navigation = useNavigation(); const { colors } = useTheme(); + const listBackgroundColor = useListBackgroundColor(colors.surfaceRoom); const dispatch = useDispatch(); const { bottom } = useSafeAreaInsets(); @@ -186,7 +189,7 @@ const SelectedUsersView = () => { item._id} - renderItem={({ item }) => { + renderItem={({ item, index }) => { const name = useRealName && item.fname ? item.fname : item.name; const username = item.search ? (item.username as string) : item.name; return ( @@ -198,13 +201,15 @@ const SelectedUsersView = () => { icon={isChecked(username) ? 'checkbox-checked' : 'checkbox-unchecked'} iconColor={isChecked(username) ? colors.fontHint : colors.strokeLight} isChecked={isChecked(username)} + isFirst={index === 0} + isLast={index === data.length - 1} /> ); }} - ItemSeparatorComponent={List.Separator} + ItemSeparatorComponent={RowSeparator} ListFooterComponent={searching ? : } ListHeaderComponent={
} - contentContainerStyle={{ backgroundColor: colors.surfaceRoom, paddingBottom: bottom }} + contentContainerStyle={{ backgroundColor: listBackgroundColor, paddingBottom: bottom }} keyboardShouldPersistTaps='always' /> diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index 4d2964d22a4..697eb0b1949 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -13,6 +13,7 @@ import { Component, createRef } from 'react'; import database from '~/lib/database'; import I18n from '~/i18n'; import DirectoryItem, { ROW_HEIGHT } from '~/containers/DirectoryItem'; +import RowSeparator from '~/containers/NativeListRow/Separator'; import ServerItem from '~/containers/ServerItem'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import ActivityIndicator from '~/containers/ActivityIndicator'; @@ -405,8 +406,9 @@ class ShareListView extends Component { ); }; - renderItem = ({ item }: { item: TSubscriptionModel }) => { - const { serverInfo } = this.state; + renderItem = ({ item, index }: { item: TSubscriptionModel; index: number }) => { + const { serverInfo, chats, searchResults, searching } = this.state; + const rowCount = searching ? searchResults.length : chats.length; let description; switch (item.t) { case 'c': @@ -431,6 +433,8 @@ class ShareListView extends Component { onPress={() => this.shareMessage(item)} testID={`share-extension-item-${item.name}`} teamMain={item.teamMain} + isFirst={index === 0} + isLast={index === rowCount - 1} /> ); }; @@ -524,7 +528,7 @@ class ShareListView extends Component { contentContainerStyle={{ paddingBottom: insets.bottom }} renderItem={this.renderItem} getItemLayout={getItemLayout} - ItemSeparatorComponent={List.Separator} + ItemSeparatorComponent={RowSeparator} ListHeaderComponent={this.renderHeader} ListFooterComponent={!searching || searchResults.length > 0 ? : null} ListEmptyComponent={this.renderEmptyComponent} diff --git a/app/views/SidebarView/components/Admin.tsx b/app/views/SidebarView/components/Admin.tsx index 5d0418b5d98..a8ea1a1683a 100644 --- a/app/views/SidebarView/components/Admin.tsx +++ b/app/views/SidebarView/components/Admin.tsx @@ -6,9 +6,7 @@ import { usePermissions } from '~/lib/hooks/usePermissions'; import { useTheme } from '~/theme'; import { sidebarNavigate } from '../methods/sidebarNavigate'; -const Admin = ({ currentScreen }: { currentScreen: string | null }) => { - const isMasterDetail = useMasterDetail(); - const { colors } = useTheme(); +export const useIsAdmin = () => { const [ viewStatisticsPermission, viewRoomAdministrationPermission, @@ -32,10 +30,19 @@ const Admin = ({ currentScreen }: { currentScreen: string | null }) => { ] ); + return isAdmin; +}; + +export const useAdminRoute = () => (useMasterDetail() ? 'AdminPanelView' : 'AdminPanelStackNavigator'); + +const Admin = ({ currentScreen }: { currentScreen: string | null }) => { + const routeName = useAdminRoute(); + const { colors } = useTheme(); + const isAdmin = useIsAdmin(); + if (!isAdmin) { return null; } - const routeName = isMasterDetail ? 'AdminPanelView' : 'AdminPanelStackNavigator'; return ( <> + Boolean(useAppSelector(state => state.settings.Accounts_AllowUserStatusMessageChange)); + const CustomStatus = () => { const { colors } = useTheme(); const { status: userStatus, statusText } = useAppSelector(getUserSelector, shallowEqual); const presenceBroadcastDisabled = useAppSelector(state => state.settings.Presence_broadcast_disabled) as boolean; const notificationPresenceCap = useAppSelector(state => state.app.notificationPresenceCap); - const allowStatusMessage = useAppSelector(state => state.settings.Accounts_AllowUserStatusMessageChange); + const allowStatusMessage = useIsCustomStatusVisible(); const dispatch = useDispatch(); const onPressPresenceLearnMore = () => { @@ -77,4 +81,4 @@ const CustomStatus = () => { ); }; -export default memo(CustomStatus); +export default asNativeListRow(memo(CustomStatus)); diff --git a/app/views/SidebarView/components/Profile.ios.tsx b/app/views/SidebarView/components/Profile.ios.tsx new file mode 100644 index 00000000000..1c4a9297102 --- /dev/null +++ b/app/views/SidebarView/components/Profile.ios.tsx @@ -0,0 +1,43 @@ +import { memo } from 'react'; +import { type DrawerNavigationProp } from '@react-navigation/drawer'; +import { shallowEqual } from 'react-redux'; + +import Avatar from '~/containers/Avatar'; +import { getUserSelector } from '~/selectors/login'; +import { type DrawerParamList } from '~/stacks/types'; +import * as List from '~/containers/List'; +import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { useAppSelector } from '~/lib/hooks/useAppSelector'; +import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; + +const PROFILE_ROW_HEIGHT = 68; + +const Profile = ({ navigation }: { navigation: DrawerNavigationProp }) => { + const isMasterDetail = useMasterDetail(); + const { username, name, statusText } = useAppSelector(getUserSelector, shallowEqual); + const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name); + const siteName = useAppSelector(state => state.settings.Site_Name) as string; + + const onPressUser = () => { + if (isMasterDetail) { + return; + } + navigation.closeDrawer(); + }; + + return ( + } + heightContainer={PROFILE_ROW_HEIGHT} + onPress={onPressUser} + testID='sidebar-close-drawer' + numberOfLines={1} + /> + ); +}; + +export default asNativeListRow(memo(Profile)); diff --git a/app/views/SidebarView/components/Profile.tsx b/app/views/SidebarView/components/Profile.tsx index 0520dd79f84..1914c5ffc58 100644 --- a/app/views/SidebarView/components/Profile.tsx +++ b/app/views/SidebarView/components/Profile.tsx @@ -11,10 +11,12 @@ import { type DrawerParamList } from '~/stacks/types'; import * as List from '~/containers/List'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; +import { useNativeListMode } from '~/containers/List/native/context'; const Profile = ({ navigation }: { navigation: DrawerNavigationProp }) => { const { colors } = useTheme(); const isMasterDetail = useMasterDetail(); + const isInNativeList = useNativeListMode(); const { username, name } = useAppSelector(getUserSelector, shallowEqual); const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name); const server = useAppSelector(state => state.server.server); @@ -31,7 +33,7 @@ const Profile = ({ navigation }: { navigation: DrawerNavigationProp - + diff --git a/app/views/SidebarView/components/StackItem.tsx b/app/views/SidebarView/components/StackItem.tsx new file mode 100644 index 00000000000..fd5a4ab03e4 --- /dev/null +++ b/app/views/SidebarView/components/StackItem.tsx @@ -0,0 +1,20 @@ +import { useTheme } from '~/theme'; +import * as List from '~/containers/List'; +import { type IStackItem } from './useStackItems'; + +const StackItem = ({ item }: { item: IStackItem }) => { + const { colors } = useTheme(); + + return ( + } + onPress={item.onPress} + backgroundColor={item.selected ? colors.strokeLight : undefined} + testID={item.testID} + disabled={item.disabled} + /> + ); +}; + +export default StackItem; diff --git a/app/views/SidebarView/components/Stacks.tsx b/app/views/SidebarView/components/Stacks.tsx index 36b6beeed7e..bab9c345a9f 100644 --- a/app/views/SidebarView/components/Stacks.tsx +++ b/app/views/SidebarView/components/Stacks.tsx @@ -1,64 +1,20 @@ -import { useTheme } from '~/theme'; +import { Fragment } from 'react'; + import * as List from '~/containers/List'; -import { sidebarNavigate } from '../methods/sidebarNavigate'; -import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; -import { useNewMediaCall } from '~/lib/hooks/useNewMediaCall'; +import StackItem from './StackItem'; +import { useStackItems } from './useStackItems'; const Stacks = ({ currentScreen }: { currentScreen: string | null }) => { - const { colors } = useTheme(); - const isMasterDetail = useMasterDetail(); - const { openNewMediaCall, hasMediaCallPermission, isInActiveCall } = useNewMediaCall(); - - if (isMasterDetail) { - return null; - } + const items = useStackItems(currentScreen); return ( <> - } - onPress={() => sidebarNavigate('ChatsStackNavigator')} - backgroundColor={currentScreen === 'ChatsStackNavigator' ? colors.strokeLight : undefined} - testID='sidebar-chats' - /> - - {hasMediaCallPermission ? ( - <> - } - onPress={openNewMediaCall} - testID='sidebar-media-call' - disabled={isInActiveCall} - /> + {items.map(item => ( + + - - ) : null} - } - onPress={() => sidebarNavigate('ProfileStackNavigator')} - backgroundColor={currentScreen === 'ProfileStackNavigator' ? colors.strokeLight : undefined} - testID='sidebar-profile' - /> - - } - onPress={() => sidebarNavigate('AccessibilityStackNavigator')} - backgroundColor={currentScreen === 'AccessibilityStackNavigator' ? colors.strokeLight : undefined} - testID='sidebar-accessibility' - /> - - } - onPress={() => sidebarNavigate('SettingsStackNavigator')} - backgroundColor={currentScreen === 'SettingsStackNavigator' ? colors.strokeLight : undefined} - testID='sidebar-settings' - /> - + + ))} ); }; diff --git a/app/views/SidebarView/components/SupportedVersionsWarnItem.tsx b/app/views/SidebarView/components/SupportedVersionsWarnItem.tsx index 333199ed308..1e945c2c689 100644 --- a/app/views/SidebarView/components/SupportedVersionsWarnItem.tsx +++ b/app/views/SidebarView/components/SupportedVersionsWarnItem.tsx @@ -3,15 +3,18 @@ import { memo } from 'react'; import { useTheme } from '~/theme'; import { CustomIcon } from '~/containers/CustomIcon'; import * as List from '~/containers/List'; +import { asNativeListRow } from '~/containers/List/native/rowMarkers'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { showActionSheetRef } from '~/containers/ActionSheet'; import Navigation from '~/lib/navigation/appNavigation'; import { SupportedVersionsWarning } from '~/containers/SupportedVersions'; +export const useIsSupportedVersionsWarnVisible = () => useAppSelector(state => state.supportedVersions.status) === 'warn'; + const SupportedVersionsWarnItem = () => { const { colors } = useTheme(); - const supportedVersionsStatus = useAppSelector(state => state.supportedVersions.status); + const isVisible = useIsSupportedVersionsWarnVisible(); const isMasterDetail = useMasterDetail(); const onPressSupportedVersionsWarning = () => { @@ -22,7 +25,7 @@ const SupportedVersionsWarnItem = () => { } }; - if (supportedVersionsStatus === 'warn') { + if (isVisible) { return ( <> { return null; }; -export default memo(SupportedVersionsWarnItem); +export default asNativeListRow(memo(SupportedVersionsWarnItem)); diff --git a/app/views/SidebarView/components/__tests__/getSidebarSelection.test.ts b/app/views/SidebarView/components/__tests__/getSidebarSelection.test.ts new file mode 100644 index 00000000000..8c555e92226 --- /dev/null +++ b/app/views/SidebarView/components/__tests__/getSidebarSelection.test.ts @@ -0,0 +1,40 @@ +import { getSidebarSelection } from '../getSidebarSelection'; +import { type IStackItem } from '../useStackItems'; + +const item = (testID: string, selected: boolean, route?: string): IStackItem => ({ + title: testID, + icon: 'message', + testID, + onPress: jest.fn(), + selected, + route +}); + +const stackItems = [ + item('sidebar-chats', false, 'ChatsStackNavigator'), + item('sidebar-media-call', false), + item('sidebar-profile', true, 'ProfileStackNavigator') +]; + +describe('getSidebarSelection', () => { + it('selects the stack item of the current route ', () => { + expect(getSidebarSelection(stackItems, null, 'ProfileStackNavigator')).toEqual({ selectedTag: 'sidebar-profile' }); + }); + + it('selects admin when the current route is the admin route', () => { + const items = stackItems.map(stackItem => ({ ...stackItem, selected: false })); + expect(getSidebarSelection(items, 'AdminPanelStackNavigator', 'AdminPanelStackNavigator')).toEqual({ + selectedTag: 'sidebar-admin' + }); + }); + + it('never selects a row without a route', () => { + const items = [item('sidebar-media-call', true)]; + expect(getSidebarSelection(items, null, null).selectedTag).toBeNull(); + }); + + it('selects nothing when the current route has no row', () => { + const items = stackItems.map(stackItem => ({ ...stackItem, selected: false })); + expect(getSidebarSelection(items, null, 'Other').selectedTag).toBeNull(); + }); +}); diff --git a/app/views/SidebarView/components/getSidebarSelection.ts b/app/views/SidebarView/components/getSidebarSelection.ts new file mode 100644 index 00000000000..3abf8be7694 --- /dev/null +++ b/app/views/SidebarView/components/getSidebarSelection.ts @@ -0,0 +1,14 @@ +import { type IListSelection } from '~/containers/List/ListContainer.ios'; +import { type IStackItem } from './useStackItems'; + +export const ADMIN_SELECTION_TAG = 'sidebar-admin'; + +export const getSidebarSelection = ( + stackItems: IStackItem[], + adminRoute: string | null, + currentScreen: string | null +): IListSelection => { + const selectedItem = stackItems.find(item => item.route && item.selected); + const isAdminSelected = adminRoute !== null && currentScreen === adminRoute; + return { selectedTag: selectedItem?.testID ?? (isAdminSelected ? ADMIN_SELECTION_TAG : null) }; +}; diff --git a/app/views/SidebarView/components/useStackItems.ts b/app/views/SidebarView/components/useStackItems.ts new file mode 100644 index 00000000000..894b79db1fd --- /dev/null +++ b/app/views/SidebarView/components/useStackItems.ts @@ -0,0 +1,53 @@ +import { type TIconsName } from '~/containers/CustomIcon'; +import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; +import { useNewMediaCall } from '~/lib/hooks/useNewMediaCall'; +import { sidebarNavigate } from '../methods/sidebarNavigate'; + +export interface IStackItem { + title: string; + icon: TIconsName; + testID: string; + onPress: () => void; + selected: boolean; + route?: string; + disabled?: boolean; +} + +export const useStackItems = (currentScreen: string | null): IStackItem[] => { + const isMasterDetail = useMasterDetail(); + const { openNewMediaCall, hasMediaCallPermission, isInActiveCall } = useNewMediaCall(); + + if (isMasterDetail) { + return []; + } + + const routeItem = (title: string, icon: TIconsName, testID: string, route: string): IStackItem => ({ + title, + icon, + testID, + onPress: () => sidebarNavigate(route), + selected: currentScreen === route, + route + }); + + const mediaCallItems: IStackItem[] = hasMediaCallPermission + ? [ + { + title: 'Voice_call', + icon: 'phone', + testID: 'sidebar-media-call', + onPress: openNewMediaCall, + selected: false, + disabled: isInActiveCall + } + ] + : []; + + return [ + routeItem('Chats', 'message', 'sidebar-chats', 'ChatsStackNavigator'), + ...mediaCallItems, + routeItem('Profile', 'user', 'sidebar-profile', 'ProfileStackNavigator'), + routeItem('Accessibility_and_Appearance', 'accessibility', 'sidebar-accessibility', 'AccessibilityStackNavigator'), + routeItem('Settings', 'administration', 'sidebar-settings', 'SettingsStackNavigator') + ]; +}; diff --git a/app/views/SidebarView/index.ios.tsx b/app/views/SidebarView/index.ios.tsx new file mode 100644 index 00000000000..407f8401e6c --- /dev/null +++ b/app/views/SidebarView/index.ios.tsx @@ -0,0 +1,80 @@ +import { useEffect, useState } from 'react'; +import { type DrawerNavigationProp } from '@react-navigation/drawer'; +import { View } from 'react-native'; + +import * as List from '~/containers/List'; +import ListContainer from '~/containers/List/ListContainer.ios'; +import styles from './styles'; +import { type DrawerParamList } from '~/stacks/types'; +import SupportedVersionsWarnItem, { useIsSupportedVersionsWarnVisible } from './components/SupportedVersionsWarnItem'; +import CustomStatus, { useIsCustomStatusVisible } from './components/CustomStatus'; +import { useStackItems } from './components/useStackItems'; +import { ADMIN_SELECTION_TAG, getSidebarSelection } from './components/getSidebarSelection'; +import { useAdminRoute, useIsAdmin } from './components/Admin'; +import { sidebarNavigate } from './methods/sidebarNavigate'; +import Profile from './components/Profile'; + +const currentRouteName = (navigation: DrawerNavigationProp) => { + const state = navigation.getState(); + return state.routes[state.index].name; +}; + +const SidebarView = ({ navigation }: { navigation: DrawerNavigationProp }) => { + const [currentScreen, setCurrentScreen] = useState(() => currentRouteName(navigation)); + const isSupportedVersionsWarnVisible = useIsSupportedVersionsWarnVisible(); + const isCustomStatusVisible = useIsCustomStatusVisible(); + const stackItems = useStackItems(currentScreen); + const isAdmin = useIsAdmin(); + const adminRoute = useAdminRoute(); + const selection = getSidebarSelection(stackItems, isAdmin ? adminRoute : null, currentScreen); + + useEffect(() => { + const unsubscribe = navigation.addListener('state', () => { + setCurrentScreen(currentRouteName(navigation)); + }); + + return unsubscribe; + }, [navigation]); + + return ( + + + + + + {isSupportedVersionsWarnVisible || isCustomStatusVisible ? ( + + {isSupportedVersionsWarnVisible ? : null} + {isCustomStatusVisible ? : null} + + ) : null} + {stackItems.length ? ( + + {stackItems.map(item => ( + } + onPress={item.onPress} + testID={item.testID} + disabled={item.disabled} + /> + ))} + + ) : null} + {isAdmin ? ( + + } + onPress={() => sidebarNavigate(adminRoute)} + /> + + ) : null} + + + ); +}; + +export default SidebarView; diff --git a/app/views/SidebarView/methods/__tests__/sidebarNavigate.test.ts b/app/views/SidebarView/methods/__tests__/sidebarNavigate.test.ts new file mode 100644 index 00000000000..d1dda82be21 --- /dev/null +++ b/app/views/SidebarView/methods/__tests__/sidebarNavigate.test.ts @@ -0,0 +1,29 @@ +import { sidebarNavigate } from '../sidebarNavigate'; +import Navigation from '~/lib/navigation/appNavigation'; + +jest.mock('~/lib/methods/helpers/log', () => ({ + events: {}, + logEvent: jest.fn() +})); + +const mockNavigate = jest.fn(); +const mockDispatch = jest.fn(); + +describe('sidebarNavigate', () => { + beforeEach(() => { + jest.clearAllMocks(); + Navigation.navigationRef.current = { navigate: mockNavigate, dispatch: mockDispatch } as any; + }); + + it('navigates to the route', () => { + sidebarNavigate('AccessibilityStackNavigator'); + + expect(mockNavigate).toHaveBeenCalledWith('AccessibilityStackNavigator', undefined); + }); + + it('closes the drawer even when navigating to the focused route', () => { + sidebarNavigate('AccessibilityStackNavigator'); + + expect(mockDispatch).toHaveBeenCalledWith(expect.objectContaining({ type: 'CLOSE_DRAWER' })); + }); +}); diff --git a/app/views/SidebarView/methods/sidebarNavigate.ts b/app/views/SidebarView/methods/sidebarNavigate.ts index 07cc3fc6f11..064ba9fb57f 100644 --- a/app/views/SidebarView/methods/sidebarNavigate.ts +++ b/app/views/SidebarView/methods/sidebarNavigate.ts @@ -1,3 +1,5 @@ +import { DrawerActions } from '@react-navigation/native'; + import Navigation from '~/lib/navigation/appNavigation'; import { events, logEvent } from '~/lib/methods/helpers/log'; @@ -5,4 +7,5 @@ export const sidebarNavigate = (route: string) => { // @ts-ignore logEvent(events[`SIDEBAR_GO_${route.replace('StackNavigator', '').replace('View', '').toUpperCase()}`]); Navigation.navigate(route); + Navigation.dispatch(DrawerActions.closeDrawer()); }; diff --git a/app/views/StatusView/ClearAfterPicker/ClearAfterNativeRow.tsx b/app/views/StatusView/ClearAfterPicker/ClearAfterNativeRow.tsx new file mode 100644 index 00000000000..85ff24bd7f5 --- /dev/null +++ b/app/views/StatusView/ClearAfterPicker/ClearAfterNativeRow.tsx @@ -0,0 +1,50 @@ +import { useActionSheet } from '~/containers/ActionSheet'; +import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import NativeListPicker from '~/containers/List/native/Picker'; +import I18n from '~/i18n'; +import DatePickerSheetContent from './DatePickerSheetContent'; +import { CLEAR_AFTER_OPTIONS, type ClearAfterValue } from './types'; + +interface IClearAfterNativeRowProps { + value: ClearAfterValue; + customDate: Date | null; + customDateLabel: string | null; + onChange: (value: ClearAfterValue, date: Date | null) => void; +} + +const isClearAfterValue = (value: string): value is ClearAfterValue => CLEAR_AFTER_OPTIONS.some(option => option.value === value); + +const ClearAfterNativeRow = ({ value, customDate, customDateLabel, onChange }: IClearAfterNativeRowProps) => { + const { showActionSheet } = useActionSheet(); + + const options = CLEAR_AFTER_OPTIONS.map(option => ({ + label: option.value === 'custom' && customDateLabel ? customDateLabel : I18n.t(option.labelKey), + value: option.value, + testID: `status-clear-after-${option.value || 'never'}` + })); + + const handleSelectionChange = (selected: string) => { + if (!isClearAfterValue(selected)) { + return; + } + if (selected === 'custom') { + showActionSheet({ + children: onChange('custom', date)} /> + }); + return; + } + onChange(selected, null); + }; + + return ( + + ); +}; + +export default asNativeListRow(ClearAfterNativeRow); diff --git a/app/views/StatusView/ClearAfterPicker/index.tsx b/app/views/StatusView/ClearAfterPicker/index.tsx index 01d36f5c64a..f388e740258 100644 --- a/app/views/StatusView/ClearAfterPicker/index.tsx +++ b/app/views/StatusView/ClearAfterPicker/index.tsx @@ -3,9 +3,12 @@ import { Text } from 'react-native'; import { useActionSheet } from '~/containers/ActionSheet'; import * as List from '~/containers/List'; +import { asNativeListSection } from '~/containers/List/native/rowMarkers'; +import { useNativeListMode } from '~/containers/List/native/context'; import I18n from '~/i18n'; import dayjs from '~/lib/dayjs'; import { useTheme } from '~/theme'; +import ClearAfterNativeRow from './ClearAfterNativeRow'; import ClearAfterSheetContent from './ClearAfterSheetContent'; import styles from './styles'; import { CLEAR_AFTER_OPTIONS, type ClearAfterValue } from './types'; @@ -22,10 +25,12 @@ interface IClearAfterPickerProps { const ClearAfterPicker = ({ value, customDate, onChange }: IClearAfterPickerProps): ReactElement => { const { showActionSheet } = useActionSheet(); const { colors } = useTheme(); + const nativeListMode = useNativeListMode(); + const customDateLabel = customDate ? dayjs(customDate).format('LL LT') : null; const getDisplayLabel = (): string => { - if (value === 'custom' && customDate) { - return dayjs(customDate).format('LL LT'); + if (value === 'custom' && customDateLabel) { + return customDateLabel; } if (value === 'custom' && !customDate) { return I18n.t('Status_dont_clear'); @@ -40,6 +45,15 @@ const ClearAfterPicker = ({ value, customDate, onChange }: IClearAfterPickerProp }); }; + if (nativeListMode === 'native') { + return ( + + + + + ); + } + return ( <> ({ useActionSheet: () => ({ showActionSheet: mockShowActionSheet, hideActionSheet: mockHideActionSheet }) })); +const mockPicker = jest.fn(); +jest.mock('~/containers/List/native/Picker', () => { + const { View } = require('react-native'); + return { + __esModule: true, + default: (props: INativeListPicker) => { + mockPicker(props); + return ; + } + }; +}); + +const latestPicker = (): INativeListPicker => mockPicker.mock.calls[mockPicker.mock.calls.length - 1][0]; + const Wrapper = ({ children }: { children: ReactNode }) => {children}; const renderStatusView = () => render(, { wrapper: Wrapper }); @@ -267,12 +282,7 @@ describe('StatusView', () => { renderStatusView(); - fireEvent.press(screen.getByTestId('status-view-clear-after')); - - expect(mockShowActionSheet).toHaveBeenCalled(); - const { children } = mockShowActionSheet.mock.calls[0][0] as { children: any }; - const { onConfirm } = children.props; - act(() => onConfirm('30', null)); + act(() => latestPicker().onSelectionChange('30')); fireEvent.press(screen.getByTestId('status-view-submit')); @@ -281,5 +291,18 @@ describe('StatusView', () => { jest.useRealTimers(); } }); + + it('should open the date picker sheet when custom is selected', () => { + mockedStore.dispatch(setUser({ id: 'user-id', username: 'user', status: 'online', statusText: '' })); + mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '8.6.0', name: 'Test' })); + mockedStore.dispatch(addSettings({ Accounts_AllowInvisibleStatusOption: true })); + + renderStatusView(); + + act(() => latestPicker().onSelectionChange('custom')); + + expect(mockShowActionSheet).toHaveBeenCalledTimes(1); + expect(latestPicker().selection).toBe(''); + }); }); }); diff --git a/app/views/StatusView/index.tsx b/app/views/StatusView/index.tsx index 5865b009077..1ac2d33a1a8 100644 --- a/app/views/StatusView/index.tsx +++ b/app/views/StatusView/index.tsx @@ -1,5 +1,5 @@ import { useNavigation } from '@react-navigation/native'; -import { type ReactElement, useEffect, useMemo, useRef, useState } from 'react'; +import { Fragment, type ReactElement, useEffect, useMemo, useRef, useState } from 'react'; import { FlatList, StyleSheet } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; import { useForm } from 'react-hook-form'; @@ -21,11 +21,12 @@ import I18n from '~/i18n'; import { showToast } from '~/lib/methods/helpers/showToast'; import { setUserStatus } from '~/lib/services/restApi'; import { getUserSelector } from '~/selectors/login'; -import { showErrorAlertWithEMessage, compareServerVersion } from '~/lib/methods/helpers'; +import { showErrorAlertWithEMessage, compareServerVersion, isIOS } from '~/lib/methods/helpers'; import log, { events, logEvent } from '~/lib/methods/helpers/log'; import { useTheme } from '~/theme'; import { USER_STATUS_TEXT_MAX_LENGTH } from '~/lib/constants/maxLength'; -import { type ClearAfterValue, computeExpiresAt, getInitialClearAfterState } from './ClearAfterPicker'; +import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import ClearAfterPicker, { type ClearAfterValue, computeExpiresAt, getInitialClearAfterState } from './ClearAfterPicker'; import FooterComponent from './FooterComponent'; const validationSchema = yup.object().shape({ @@ -71,38 +72,6 @@ const styles = StyleSheet.create({ } }); -const Status = ({ - statusType, - status, - setStatus -}: { - statusType: IStatus; - status: TUserStatus; - setStatus: (status: TUserStatus) => void; -}) => { - const { id, name } = statusType; - return ( - <> - { - const key = `STATUS_${id.toUpperCase()}` as keyof typeof events; - logEvent(events[key]); - if (status !== id) { - setStatus(id); - } - }} - testID={`status-view-${id}`} - value={statusType.id} - left={() => } - /> - - - ); -}; - const StatusView = (): ReactElement => { const user = useSelector((state: IApplicationState) => getUserSelector(state)); const isMasterDetail = useMasterDetail(); @@ -135,6 +104,7 @@ const StatusView = (): ReactElement => { const dispatch = useDispatch(); const { setOptions, goBack } = useNavigation(); const { colors } = useTheme(); + const listBackgroundColor = useListBackgroundColor(colors.surfaceTint); const { bottom } = useSafeAreaInsets(); const submit = async () => { @@ -160,8 +130,11 @@ const StatusView = (): ReactElement => { setHeader(); }, [isMasterDetail]); - const setStatus = (updatedStatus: TUserStatus) => { - setValue('status', updatedStatus); + const selectStatus = (id: TUserStatus) => { + logEvent(events[`STATUS_${id.toUpperCase()}` as keyof typeof events]); + if (inputValues.status !== id) { + setValue('status', id); + } }; const setCustomStatus = async (status: TUserStatus, statusText: string, expiresAt?: string | null) => { @@ -209,37 +182,80 @@ const StatusView = (): ReactElement => { if (date) setClearAfterDate(date); }; + const statusInput = ( + + ); + + const renderStatus = ({ id, name }: IStatus) => ( + selectStatus(id)} + testID={`status-view-${id}`} + value={id} + left={() => } + /> + ); + + const renderFooter = (showClearAfterPicker: boolean) => ( + + ); + + if (isIOS) { + return ( + + {statusInput} + + + {statusType.map(status => ( + {renderStatus(status)} + ))} + + {supportsStatusExpiry ? ( + + ) : null} + + {renderFooter(false)} + + ); + } + return ( item.id} - renderItem={({ item }) => } + renderItem={({ item }) => ( + <> + {renderStatus(item)} + + + )} ListHeaderComponent={ <> - + {statusInput} } - ListFooterComponent={ - - } + ListFooterComponent={renderFooter(supportsStatusExpiry)} style={{ backgroundColor: colors.surfaceTint }} contentContainerStyle={{ paddingBottom: bottom }} /> diff --git a/app/views/ThemeView.tsx b/app/views/ThemeView.tsx index a62caf2314e..ac8bd4f58fe 100644 --- a/app/views/ThemeView.tsx +++ b/app/views/ThemeView.tsx @@ -1,4 +1,4 @@ -import { type ReactElement, useLayoutEffect } from 'react'; +import { Fragment, type ReactElement, useLayoutEffect } from 'react'; import { useNavigation } from '@react-navigation/native'; import * as List from '../containers/List'; @@ -56,19 +56,6 @@ interface ITheme { group: string; } -const Item = ({ onPress, item, isSelected }: { onPress: () => void; item: ITheme; isSelected: boolean }) => ( - <> - - - -); - const ThemeView = (): ReactElement => { const { themePreferences, setTheme } = useTheme(); const { setOptions } = useNavigation(); @@ -120,7 +107,16 @@ const ThemeView = (): ReactElement => { <> {themeGroup.map(theme => ( - onClick(theme)} item={theme} isSelected={!!isSelected(theme)} key={theme.label} /> + + onClick(theme)} + testID={`theme-view-${theme.value}`} + /> + + ))} @@ -128,7 +124,16 @@ const ThemeView = (): ReactElement => { <> {darkGroup.map(theme => ( - onClick(theme)} item={theme} isSelected={!!isSelected(theme)} key={theme.label} /> + + onClick(theme)} + testID={`theme-view-${theme.value}`} + /> + + ))} diff --git a/app/views/UserNotificationPreferencesView/ListPicker.tsx b/app/views/UserNotificationPreferencesView/ListPicker.tsx index c1b8f0ef464..c093b821642 100644 --- a/app/views/UserNotificationPreferencesView/ListPicker.tsx +++ b/app/views/UserNotificationPreferencesView/ListPicker.tsx @@ -2,6 +2,9 @@ import { StyleSheet, Text, View } from 'react-native'; import { Fragment, type ReactElement } from 'react'; import * as List from '~/containers/List'; +import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { useNativeListMode } from '~/containers/List/native/context'; +import NativeListPicker from '~/containers/List/native/Picker'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; @@ -35,6 +38,7 @@ const ListPicker = ({ } & IBaseParams) => { const { showActionSheet, hideActionSheet } = useActionSheet(); const { colors } = useTheme(); + const nativeListMode = useNativeListMode(); const option = value ? OPTIONS[preference].find(option => option.value === value) : OPTIONS[preference][0]; const getOptions = (): ReactElement => ( @@ -60,6 +64,22 @@ const ListPicker = ({ const label = option?.label ? I18n.t(option?.label, { defaultValue: option?.label }) : option?.label; + if (nativeListMode === 'native') { + return ( + ({ + label: I18n.t(i.label, { defaultValue: i.label }), + value: i.value.toString(), + testID: `notification-preferences-${preference}-${i.value}` + }))} + selection={option?.value.toString() ?? ''} + onSelectionChange={selected => onChangeValue({ [preference]: selected })} + /> + ); + } + return ( { const { showActionSheet, hideActionSheet } = useActionSheet(); const { colors } = useTheme(); + const nativeListMode = useNativeListMode(); const [option, setOption] = useState( value ? OPTIONS[preference].find(option => option.value === value) : OPTIONS[preference][0] ); + const selectOption = (selectedValue: string) => { + const previous = option; + onChangeValue({ [preference]: selectedValue }, () => setOption(previous)); + setOption(OPTIONS[preference].find(i => i.value === selectedValue)); + }; + const getOptions = (): TActionSheetOptionsItem[] => OPTIONS[preference].map(i => ({ title: I18n.t(i.label, { defaultValue: i.label }), onPress: () => { hideActionSheet(); - onChangeValue({ [preference]: i.value.toString() }, () => setOption(option)); - setOption(i); + selectOption(i.value); }, right: option?.value === i.value ? () => : undefined })); const label = option?.label ? I18n.t(option?.label, { defaultValue: option?.label }) : option?.label; + if (nativeListMode === 'native') { + return ( + ({ label: I18n.t(i.label, { defaultValue: i.label }), value: i.value }))} + selection={option?.value ?? ''} + onSelectionChange={selectOption} + /> + ); + } + return ( { it('keeps enableMobileRinging enabled while enabling convertAsciiEmoji', async () => { const { getByTestId } = renderWithPreferences({ convertAsciiEmoji: false, enableMobileRinging: true }); - fireEvent(getByTestId('preferences-view-convert-ascii-to-emoji'), 'valueChange', true); + fireEvent(getByTestId('preferences-view-convert-ascii-to-emoji'), 'isOnChange', { nativeEvent: { isOn: true } }); const dispatchedUser = dispatch.mock.calls[0][0].user; expect(dispatchedUser.settings.preferences).toEqual({ @@ -78,7 +78,7 @@ describe('UserPreferencesView', () => { it('keeps enableMobileRinging disabled while disabling convertAsciiEmoji', async () => { const { getByTestId } = renderWithPreferences({ convertAsciiEmoji: true, enableMobileRinging: false }); - fireEvent(getByTestId('preferences-view-convert-ascii-to-emoji'), 'valueChange', false); + fireEvent(getByTestId('preferences-view-convert-ascii-to-emoji'), 'isOnChange', { nativeEvent: { isOn: false } }); const dispatchedUser = dispatch.mock.calls[0][0].user; expect(dispatchedUser.settings.preferences).toEqual({ @@ -91,7 +91,7 @@ describe('UserPreferencesView', () => { it('keeps convertAsciiEmoji enabled while enabling enableMobileRinging', async () => { const { getByTestId } = renderWithPreferences({ convertAsciiEmoji: true, enableMobileRinging: false }); - fireEvent(getByTestId('preferences-view-enable-mobile-ringing'), 'valueChange', true); + fireEvent(getByTestId('preferences-view-enable-mobile-ringing'), 'isOnChange', { nativeEvent: { isOn: true } }); const dispatchedUser = dispatch.mock.calls[0][0].user; expect(dispatchedUser.settings.preferences).toEqual({ @@ -104,7 +104,7 @@ describe('UserPreferencesView', () => { it('keeps convertAsciiEmoji disabled while disabling enableMobileRinging', async () => { const { getByTestId } = renderWithPreferences({ convertAsciiEmoji: false, enableMobileRinging: true }); - fireEvent(getByTestId('preferences-view-enable-mobile-ringing'), 'valueChange', false); + fireEvent(getByTestId('preferences-view-enable-mobile-ringing'), 'isOnChange', { nativeEvent: { isOn: false } }); const dispatchedUser = dispatch.mock.calls[0][0].user; expect(dispatchedUser.settings.preferences).toEqual({ diff --git a/app/views/UserPreferencesView/__tests__/ListPicker.test.tsx b/app/views/UserPreferencesView/__tests__/ListPicker.test.tsx new file mode 100644 index 00000000000..c863ac76d48 --- /dev/null +++ b/app/views/UserPreferencesView/__tests__/ListPicker.test.tsx @@ -0,0 +1,55 @@ +import { act, render } from '@testing-library/react-native'; + +import { NativeListContext } from '~/containers/List/native/context'; +import { type INativeListPicker } from '~/containers/List/native/types'; +import ListPicker from '../ListPicker'; + +const mockPicker = jest.fn(); +jest.mock('~/containers/List/native/Picker', () => ({ + __esModule: true, + default: (props: INativeListPicker) => { + mockPicker(props); + return null; + } +})); + +const latestPicker = (): INativeListPicker => mockPicker.mock.calls[mockPicker.mock.calls.length - 1][0]; + +const renderNative = (onChangeValue: jest.Mock) => + render( + row }}> + + + ); + +describe('UserPreferencesView ListPicker in a native list', () => { + beforeEach(() => mockPicker.mockClear()); + + it('selects the current value and saves a new selection', () => { + const onChangeValue = jest.fn(); + renderNative(onChangeValue); + expect(latestPicker().selection).toBe('always'); + expect(latestPicker().testID).toBe('preferences-view-enable-message-parser'); + + act(() => latestPicker().onSelectionChange('never')); + + expect(onChangeValue).toHaveBeenCalledWith({ alsoSendThreadToChannel: 'never' }, expect.any(Function)); + expect(latestPicker().selection).toBe('never'); + }); + + it('restores the previous value when saving fails', () => { + const onChangeValue = jest.fn(); + renderNative(onChangeValue); + + act(() => latestPicker().onSelectionChange('never')); + act(() => onChangeValue.mock.calls[0][1]()); + + expect(latestPicker().selection).toBe('always'); + }); +}); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f2a0eb6a6a2..8adaa2f6e8a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -131,6 +131,10 @@ PODS: - ExpoModulesCore - ExpoSystemUI (57.0.4): - ExpoModulesCore + - ExpoUI (57.0.19): + - ExpoModulesCore + - ExpoModulesWorklets + - React-RCTFabric - ExpoVideo (57.0.4): - ExpoModulesCore - ExpoVideoThumbnails (57.0.2): @@ -3020,6 +3024,7 @@ DEPENDENCIES: - ExpoNotifications (from `../node_modules/expo-notifications/ios`) - "ExpoQuickLook (from `../node_modules/@magrinj/expo-quick-look/ios`)" - ExpoSystemUI (from `../node_modules/expo-system-ui/ios`) + - "ExpoUI (from `../node_modules/@expo/ui/ios`)" - ExpoVideo (from `../node_modules/expo-video/ios`) - ExpoVideoThumbnails (from `../node_modules/expo-video-thumbnails/ios`) - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) @@ -3223,6 +3228,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@magrinj/expo-quick-look/ios" ExpoSystemUI: :path: "../node_modules/expo-system-ui/ios" + ExpoUI: + :path: "../node_modules/@expo/ui/ios" ExpoVideo: :path: "../node_modules/expo-video/ios" ExpoVideoThumbnails: @@ -3481,6 +3488,7 @@ SPEC CHECKSUMS: ExpoNotifications: 098f80e610228da37ee98c0828931337cf5b1106 ExpoQuickLook: 7ac65cd559a74619ea41e13df20e9d7a61e79ea6 ExpoSystemUI: 05ee8d4aaba1b6af330f3621e19b39ff9f21e718 + ExpoUI: 98d4650d9d32daaf772597adc76525c645b1ff76 ExpoVideo: 56ed2863b981dd5ae946d8ef23ecfcae855863a0 ExpoVideoThumbnails: da5d471c170bf4110f681b6649524eace9c95b5b ExpoWebBrowser: a4d7c382116fbe33c1ec7f60293f02b7e97042e4 diff --git a/package.json b/package.json index ca9e103cd16..e4ec368a696 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "@bugsnag/react-native": "8.4.0", + "@expo/ui": "~57.0.19", "@expo/vector-icons": "^15.0.3", "@hookform/resolvers": "^2.9.10", "@lodev09/react-native-true-sheet": "~3.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f24110a636..adc0cb44141 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: '@bugsnag/react-native': specifier: 8.4.0 version: 8.4.0 + '@expo/ui': + specifier: ~57.0.19 + version: 57.0.19(@babel/core@7.29.7)(@types/react@19.2.18)(expo@57.0.23)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@expo/vector-icons': specifier: ^15.0.3 version: 15.0.3(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -1716,6 +1719,23 @@ packages: '@expo/sudo-prompt@9.3.2': resolution: {integrity: sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==} + '@expo/ui@57.0.19': + resolution: {integrity: sha512-WB95zQPge5nb04zgY3O2NmMtxg6G+qFH3yOAHcBkTUrJXoBReQoyY3cfp/8N5Q/azcfHJcBE3Jy0Rwq9OoRrfA==} + peerDependencies: + '@babel/core': '*' + expo: '*' + react: '*' + react-dom: '*' + react-native: '*' + react-native-worklets: '*' + peerDependenciesMeta: + '@babel/core': + optional: true + react-dom: + optional: true + react-native-worklets: + optional: true + '@expo/vector-icons@15.0.3': resolution: {integrity: sha512-SBUyYKphmlfUBqxSfDdJ3jAdEVSALS2VUPOUyqn48oZmb2TL/O7t7/PQm5v4NQujYEPLPMTLn9KVw6H7twwbTA==} peerDependencies: @@ -2466,6 +2486,168 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@radix-ui/primitive@1.1.7': + resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} + + '@radix-ui/react-compose-refs@1.1.5': + resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.2.2': + resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.23': + resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.19': + resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.6': + resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.16': + resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.4': + resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-portal@1.1.17': + resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.10': + resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.10': + resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.3.3': + resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.4': + resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.6': + resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.5': + resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.4': + resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@react-native-async-storage/async-storage@2.2.0': resolution: {integrity: sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==} peerDependencies: @@ -3522,6 +3704,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -4245,6 +4431,9 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4962,6 +5151,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -6898,6 +7091,36 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react-test-renderer@19.2.3: resolution: {integrity: sha512-TMR1LnSFiWZMJkCgNf5ATSvAheTT2NvKIwiVwdBPHxjBI7n/JbWd4gaZ16DVd9foAXdvDz+sB5yxZTwMjPRxpw==} peerDependencies: @@ -7741,6 +7964,16 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + use-debounce@9.0.4: resolution: {integrity: sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==} engines: {node: '>= 10.0.0'} @@ -7758,6 +7991,16 @@ packages: peerDependencies: react: '>=16.8' + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -7793,6 +8036,12 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vaul@1.1.2: + resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} @@ -9714,6 +9963,21 @@ snapshots: '@expo/sudo-prompt@9.3.2': {} + '@expo/ui@57.0.19(@babel/core@7.29.7)(@types/react@19.2.18)(expo@57.0.23)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': + dependencies: + expo: 57.0.23(8132ea204d39ca81423aaa5e2384dd28) + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + sf-symbols-typescript: 2.2.0 + vaul: 1.1.2(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + optionalDependencies: + '@babel/core': 7.29.7 + react-dom: 19.2.3(react@19.2.3) + react-native-worklets: 0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + '@expo/vector-icons@15.0.3(expo-font@57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': dependencies: expo-font: 57.0.4(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -10526,6 +10790,137 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@radix-ui/primitive@1.1.7': {} + + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.18)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-context@1.2.2(@types/react@19.2.18)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-dialog@1.1.23(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-focus-scope': 1.1.16(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-portal': 1.1.17(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.10(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.10(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.3) + aria-hidden: 1.2.6 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-dismissable-layer@1.1.19(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.10(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.18)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-focus-scope@1.1.16(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.10(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-id@1.1.4(@types/react@19.2.18)(react@19.2.3)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-portal@1.1.17(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-presence@1.1.10(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-primitive@2.1.10(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-slot@1.3.3(@types/react@19.2.18)(react@19.2.3)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.18)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.18)(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.18)(react@19.2.3)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.18 + + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.18)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.18 + '@react-native-async-storage/async-storage@2.2.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))': dependencies: merge-options: 3.0.4 @@ -11717,6 +12112,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -12584,6 +12983,8 @@ snapshots: detect-newline@3.1.0: {} + detect-node-es@1.1.0: {} + diff-sequences@29.6.3: {} dnssd-advertise@1.1.6: {} @@ -13459,6 +13860,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-package-type@0.1.0: {} get-proto@1.0.1: @@ -15910,6 +16313,33 @@ snapshots: react-refresh@0.14.2: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.3): + dependencies: + react: 19.2.3 + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.3) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.18 + + react-remove-scroll@2.7.2(@types/react@19.2.18)(react@19.2.3): + dependencies: + react: 19.2.3 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.18)(react@19.2.3) + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.3) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.18)(react@19.2.3) + use-sidecar: 1.1.3(@types/react@19.2.18)(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.18 + + react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.2.3): + dependencies: + get-nonce: 1.0.1 + react: 19.2.3 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.18 + react-test-renderer@19.2.3(react@19.2.3): dependencies: react: 19.2.3 @@ -16873,6 +17303,13 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 + use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.2.3): + dependencies: + react: 19.2.3 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.18 + use-debounce@9.0.4(react@19.2.3): dependencies: react: 19.2.3 @@ -16888,6 +17325,14 @@ snapshots: dependencies: react: 19.2.3 + use-sidecar@1.1.3(@types/react@19.2.18)(react@19.2.3): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.3 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.18 + use-sync-external-store@1.6.0(react@19.2.3): dependencies: react: 19.2.3 @@ -16921,6 +17366,15 @@ snapshots: vary@1.1.2: {} + vaul@1.1.2(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@radix-ui/react-dialog': 1.1.23(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + vlq@1.0.1: {} w3c-xmlserializer@4.0.0: From 083f64cfa5af74b5f863536cb6016a72e440a2ff Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 13:52:51 -0300 Subject: [PATCH 091/120] fix(ios): scope liquid glass input to StatusView and fix its borders --- app/containers/List/ListContainer.ios.tsx | 19 ++++++++++++++++--- app/containers/List/ListContainer.tsx | 3 ++- app/containers/SearchBox/index.tsx | 5 +---- app/containers/TextInput/FormTextInput.tsx | 13 +------------ .../StatusView}/GlassBackground.ios.tsx | 9 ++++++++- .../StatusView}/GlassBackground.tsx | 0 app/views/StatusView/index.tsx | 12 +++++++++--- 7 files changed, 37 insertions(+), 24 deletions(-) rename app/{containers/TextInput => views/StatusView}/GlassBackground.ios.tsx (74%) rename app/{containers/TextInput => views/StatusView}/GlassBackground.tsx (100%) diff --git a/app/containers/List/ListContainer.ios.tsx b/app/containers/List/ListContainer.ios.tsx index 82bf1e96dce..fc53f9978a6 100644 --- a/app/containers/List/ListContainer.ios.tsx +++ b/app/containers/List/ListContainer.ios.tsx @@ -2,7 +2,15 @@ import { useState, type ReactElement } from 'react'; import { StyleSheet, View } from 'react-native'; import { Host } from '@expo/ui'; import { Group, List, RNHostView } from '@expo/ui/swift-ui'; -import { alignmentGuide, frame, listRowInsets, listStyle, onGeometryChange, tag } from '@expo/ui/swift-ui/modifiers'; +import { + alignmentGuide, + frame, + listRowInsets, + listStyle, + onGeometryChange, + scrollContentBackground, + tag +} from '@expo/ui/swift-ui/modifiers'; import { useTheme } from '~/theme'; import ListSection from './ListSection'; @@ -21,6 +29,7 @@ const styles = StyleSheet.create({ const insetGroupedModifiers = [listStyle('insetGrouped')]; const sidebarModifiers = [listStyle('sidebar')]; +const hiddenBackgroundModifiers = [scrollContentBackground('hidden')]; export interface IListSelection { selectedTag: string | null; @@ -30,6 +39,7 @@ interface IListContainer { children: (ReactElement | null)[] | ReactElement | null; testID?: string; selection?: IListSelection; + backgroundHidden?: boolean; } const isSection = (element: ReactElement) => element.type === ListSection || isNativeListSection(element.type); @@ -43,7 +53,7 @@ const rowSelectionTag = (element: ReactElement) => { const selectedTags = ({ selectedTag }: IListSelection) => (selectedTag ? [selectedTag] : []); -const ListContainer = ({ children, testID, selection }: IListContainer) => { +const ListContainer = ({ children, testID, selection, backgroundHidden }: IListContainer) => { const { theme } = useTheme(); const [rowWidth, setRowWidth] = useState(0); @@ -82,7 +92,10 @@ const ListContainer = ({ children, testID, selection }: IListContainer) => { {flattenListChildren(children) diff --git a/app/containers/List/ListContainer.tsx b/app/containers/List/ListContainer.tsx index df3c14bdc1c..60fcf2995e2 100644 --- a/app/containers/List/ListContainer.tsx +++ b/app/containers/List/ListContainer.tsx @@ -13,8 +13,9 @@ const styles = StyleSheet.create({ interface IListContainer extends ScrollViewProps { children: (ReactElement | null)[] | ReactElement | null; testID?: string; + backgroundHidden?: boolean; } -const ListContainer = ({ children, ...props }: IListContainer) => { +const ListContainer = ({ children, backgroundHidden: _backgroundHidden, ...props }: IListContainer) => { const { bottom } = useSafeAreaInsets(); return ( diff --git a/app/containers/SearchBox/index.tsx b/app/containers/SearchBox/index.tsx index 94f5db5e82b..6baece8e494 100644 --- a/app/containers/SearchBox/index.tsx +++ b/app/containers/SearchBox/index.tsx @@ -4,7 +4,6 @@ import { StyleSheet, type TextInputProps, View } from 'react-native'; import { useTheme } from '~/theme'; import I18n from '~/i18n'; import { FormTextInput } from '../TextInput'; -import { supportsLiquidGlass } from '../TextInput/GlassBackground'; import { useListBackgroundColor } from '../NativeListRow/useListBackgroundColor'; const styles = StyleSheet.create({ @@ -40,9 +39,7 @@ const SearchBox = ({ onChangeText, onSubmitEditing, testID }: TextInputProps) => value={text} testID={testID} onClearInput={() => internalOnChangeText('')} - iconLeft={supportsLiquidGlass ? 'search' : undefined} - iconRight={supportsLiquidGlass ? undefined : 'search'} - glass + iconRight={'search'} /> ); diff --git a/app/containers/TextInput/FormTextInput.tsx b/app/containers/TextInput/FormTextInput.tsx index e20236bcea8..b76f2388db0 100644 --- a/app/containers/TextInput/FormTextInput.tsx +++ b/app/containers/TextInput/FormTextInput.tsx @@ -19,7 +19,6 @@ import { CustomIcon, type TIconsName } from '../CustomIcon'; import { TextInput } from './TextInput'; import { isIOS } from '~/lib/methods/helpers'; import Touch from '../Touch'; -import { GlassBackground, supportsLiquidGlass } from './GlassBackground'; const styles = StyleSheet.create({ error: { @@ -73,11 +72,6 @@ const styles = StyleSheet.create({ iconRight: { right: 12 }, - glassInput: { - borderWidth: 0, - borderRadius: 999, - backgroundColor: 'transparent' - }, clearInputIcon: { width: 20, height: 20 @@ -97,7 +91,6 @@ export interface IRCTextInputProps extends TextInputProps { iconRight?: TIconsName; left?: ReactElement; onClearInput?: () => void; - glass?: boolean; } const getInputError = (error: unknown): string => { @@ -127,14 +120,12 @@ export const FormTextInput = ({ placeholder, accessibilityLabel, showErrorMessage = true, - glass = false, ...inputProps }: IRCTextInputProps): ReactElement => { const { colors } = useTheme(); const [showPassword, setShowPassword] = useState(false); const showClearInput = onClearInput && value && value.length > 0; const inputError = getInputError(error); - const showGlass = glass && supportsLiquidGlass; // iOS 26 surfaces a system "Save Password?" sheet asynchronously after any // credential-classified field submit. It overlays the app and blocks // XCUITest hit-testing, breaking Maestro flows that interact with the @@ -170,7 +161,6 @@ export const FormTextInput = ({ ) : null} - {showGlass ? : null} { const { theme } = useTheme(); return ( - + ); diff --git a/app/containers/TextInput/GlassBackground.tsx b/app/views/StatusView/GlassBackground.tsx similarity index 100% rename from app/containers/TextInput/GlassBackground.tsx rename to app/views/StatusView/GlassBackground.tsx diff --git a/app/views/StatusView/index.tsx b/app/views/StatusView/index.tsx index 1ac2d33a1a8..9bd6664e9fc 100644 --- a/app/views/StatusView/index.tsx +++ b/app/views/StatusView/index.tsx @@ -28,6 +28,7 @@ import { USER_STATUS_TEXT_MAX_LENGTH } from '~/lib/constants/maxLength'; import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; import ClearAfterPicker, { type ClearAfterValue, computeExpiresAt, getInitialClearAfterState } from './ClearAfterPicker'; import FooterComponent from './FooterComponent'; +import { GlassBackground, supportsLiquidGlass } from './GlassBackground'; const validationSchema = yup.object().shape({ statusText: yup @@ -69,6 +70,11 @@ const styles = StyleSheet.create({ borderRadius: 0, borderTopWidth: 1, borderBottomWidth: 1 + }, + glassInputStyle: { + borderWidth: 0, + borderRadius: 999, + backgroundColor: 'transparent' } }); @@ -189,9 +195,9 @@ const StatusView = (): ReactElement => { label={I18n.t('Status')} value={statusText} containerStyle={styles.inputContainer} - inputStyle={styles.inputStyle} + inputStyle={supportsLiquidGlass ? styles.glassInputStyle : styles.inputStyle} + left={supportsLiquidGlass ? : undefined} testID='status-view-input' - glass error={errors.statusText?.message} /> ); @@ -223,7 +229,7 @@ const StatusView = (): ReactElement => { return ( {statusInput} - + {statusType.map(status => ( {renderStatus(status)} From 0a5f5263e747588249d9ea14ef48f66bd639fc13 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 13:52:59 -0300 Subject: [PATCH 092/120] fix(ios): align rooms list large title and match its background --- .../components/LargeTitleBackdrop.tsx | 26 +++++++ .../RoomsListView/components/ListHeader.tsx | 2 + patches/react-native-screens+4.28.0.patch | 71 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 app/views/RoomsListView/components/LargeTitleBackdrop.tsx diff --git a/app/views/RoomsListView/components/LargeTitleBackdrop.tsx b/app/views/RoomsListView/components/LargeTitleBackdrop.tsx new file mode 100644 index 00000000000..1bbb190c7d7 --- /dev/null +++ b/app/views/RoomsListView/components/LargeTitleBackdrop.tsx @@ -0,0 +1,26 @@ +import { StyleSheet, useWindowDimensions, View } from 'react-native'; + +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; +import { useTheme } from '~/theme'; + +const styles = StyleSheet.create({ + backdrop: { + position: 'absolute', + bottom: '100%', + left: 0, + right: 0 + } +}); + +const LargeTitleBackdrop = () => { + const { colors } = useTheme(); + const { height } = useWindowDimensions(); + + if (!hasNativeHeaderBar) { + return null; + } + + return ; +}; + +export default LargeTitleBackdrop; diff --git a/app/views/RoomsListView/components/ListHeader.tsx b/app/views/RoomsListView/components/ListHeader.tsx index d03ae4d1ff8..a00ce033940 100644 --- a/app/views/RoomsListView/components/ListHeader.tsx +++ b/app/views/RoomsListView/components/ListHeader.tsx @@ -10,6 +10,7 @@ import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { useTheme } from '~/theme'; import { RoomsSearchContext } from '../contexts/RoomsSearchProvider'; +import LargeTitleBackdrop from './LargeTitleBackdrop'; export type TEncryptionBanner = 'REQUEST_PASSWORD' | 'SAVE_PASSWORD'; @@ -38,6 +39,7 @@ const ListHeader = () => { return ( <> + {encryptionBanner ? ( <> = margins.trailing) { ++ return; ++ } ++ UINavigationItem *item = top.navigationItem; ++ UINavigationItemLargeTitleDisplayMode mode = item.largeTitleDisplayMode; ++ item.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever; ++ item.largeTitleDisplayMode = mode; ++ titleView.insetsLayoutMarginsFromSafeArea = !titleView.insetsLayoutMarginsFromSafeArea; ++ titleView.insetsLayoutMarginsFromSafeArea = !titleView.insetsLayoutMarginsFromSafeArea; ++ titleView.preservesSuperviewLayoutMargins = !titleView.preservesSuperviewLayoutMargins; ++ titleView.preservesSuperviewLayoutMargins = !titleView.preservesSuperviewLayoutMargins; ++ margins = titleView.directionalLayoutMargins; ++ if (margins.leading < margins.trailing) { ++ margins.leading = margins.trailing; ++ titleView.directionalLayoutMargins = margins; ++ } ++ [titleView setNeedsLayout]; ++ [titleView layoutIfNeeded]; ++ if ([titleView.superview isKindOfClass:[UIScrollView class]]) { ++ UIScrollView *scrollView = (UIScrollView *)titleView.superview; ++ [top setContentScrollView:nil forEdge:NSDirectionalRectEdgeTop]; ++ [top setContentScrollView:scrollView forEdge:NSDirectionalRectEdgeTop]; ++ [controller.navigationBar setNeedsLayout]; ++ [controller.navigationBar layoutIfNeeded]; ++ } ++} ++ + @implementation RNSNavigationController + + #if !TARGET_OS_TV +@@ -59,6 +111,7 @@ namespace react = facebook::react; + - (void)viewDidLayoutSubviews + { + [super viewDidLayoutSubviews]; ++ RNSAlignLargeTitleMargins(self); + if ([self.topViewController isKindOfClass:[RNSScreen class]]) { + RNSScreen *screenController = (RNSScreen *)self.topViewController; + BOOL isNotDismissingModal = screenController.presentedViewController == nil || From f9acdf020db729df9d0982920d9e872802c25041 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 14:04:16 -0300 Subject: [PATCH 093/120] feat(ios): default avatar border radius to 8 on iOS 26 --- app/containers/Avatar/Avatar.tsx | 3 ++- app/lib/methods/helpers/deviceInfo.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx index 926a74d841e..15c1dddd9fc 100644 --- a/app/containers/Avatar/Avatar.tsx +++ b/app/containers/Avatar/Avatar.tsx @@ -10,6 +10,7 @@ import { SubscriptionType } from '~/definitions'; import { type IAvatar } from './interfaces'; import I18n from '~/i18n'; import Touch from '../Touch'; +import { isIOS26OrLater } from '~/lib/methods/helpers/deviceInfo'; const Avatar = memo( ({ @@ -28,7 +29,7 @@ const Avatar = memo( serverVersion, text, size = 25, - borderRadius = 4, + borderRadius = isIOS26OrLater ? 8 : 4, type = SubscriptionType.DIRECT, avatarExternalProviderUrl, roomAvatarExternalProviderUrl, diff --git a/app/lib/methods/helpers/deviceInfo.ts b/app/lib/methods/helpers/deviceInfo.ts index 5e16e945afe..c17b352ecc9 100644 --- a/app/lib/methods/helpers/deviceInfo.ts +++ b/app/lib/methods/helpers/deviceInfo.ts @@ -17,4 +17,6 @@ export const supportSystemTheme = (): boolean => { // Tablet info export const isTablet = DeviceInfo.isTablet(); -export const hasNativeHeaderBar = isIOS && parseInt(DeviceInfo.getSystemVersion(), 10) >= 26; +export const isIOS26OrLater = isIOS && parseInt(DeviceInfo.getSystemVersion(), 10) >= 26; + +export const hasNativeHeaderBar = isIOS26OrLater; From 48af59813b0b24506a49fb09e813060b293eba25 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 14:10:24 -0300 Subject: [PATCH 094/120] fix(ios): add vertical padding to room actions info row on iOS 26 --- app/views/RoomActionsView/styles.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/RoomActionsView/styles.ts b/app/views/RoomActionsView/styles.ts index 983a6d5478b..e79db3879fc 100644 --- a/app/views/RoomActionsView/styles.ts +++ b/app/views/RoomActionsView/styles.ts @@ -1,12 +1,13 @@ import { I18nManager, StyleSheet } from 'react-native'; import { PADDING_HORIZONTAL } from '~/containers/List/constants'; +import { isIOS26OrLater } from '~/lib/methods/helpers/deviceInfo'; import sharedStyles from '../Styles'; export default StyleSheet.create({ roomInfoContainer: { paddingHorizontal: PADDING_HORIZONTAL, - paddingVertical: 4, + paddingVertical: isIOS26OrLater ? 12 : 4, flexDirection: 'row', alignItems: 'center' }, From 80d91181e1cd1bbdef440e378f72e01da050c93a Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 17:50:30 -0300 Subject: [PATCH 095/120] fix(ios): use native header search and list indicators on members, directory and add users --- .maestro/helpers/clear-search-field.yaml | 18 +++ .maestro/helpers/select-header-filter.yaml | 37 ++++++ .maestro/helpers/tap-search-field.yaml | 33 +++++ .maestro/tests/assorted/broadcast.yaml | 6 +- .../tests/assorted/join-from-directory.yaml | 58 ++++----- .../tests/e2ee/flows/create-e2ee-room.yaml | 6 +- .maestro/tests/room/create-dm-group.yaml | 16 +-- .maestro/tests/room/create-room.yaml | 10 +- .maestro/tests/room/discussion.yaml | 10 +- .maestro/tests/room/room-actions.yaml | 41 +++---- .maestro/tests/room/search-member.yaml | 53 ++++---- .maestro/tests/teams/team.yaml | 46 +++---- .../List/__snapshots__/List.test.tsx.snap | 9 +- app/containers/List/native/Accessory.ios.tsx | 3 + app/containers/List/native/Indicator.ios.tsx | 26 ++++ app/containers/List/native/Item.ios.tsx | 6 +- .../__tests__/describeAccessory.test.tsx | 11 +- .../List/native/describeAccessory.ts | 18 +-- app/containers/NativeListRow/index.tsx | 8 +- app/sagas/deepLinking.js | 2 + app/sagas/init.js | 11 +- app/sagas/preloadNativeHeaderIcons.js | 16 +++ app/views/DirectoryView/index.tsx | 93 ++++++++++++-- app/views/DirectoryView/styles.ts | 1 + .../components/ActionsSection.ios.tsx | 40 ++++++ .../components/ActionsSection.tsx | 115 +++--------------- .../components/useMemberActions.ts | 101 +++++++++++++++ app/views/RoomMembersView/index.tsx | 52 +++++++- app/views/SelectedUsersView/Header.tsx | 5 +- app/views/SelectedUsersView/index.tsx | 98 ++++++++------- app/views/ThreadMessagesView/index.tsx | 2 - app/views/ThreadMessagesView/styles.ts | 1 + 32 files changed, 622 insertions(+), 330 deletions(-) create mode 100644 .maestro/helpers/clear-search-field.yaml create mode 100644 .maestro/helpers/select-header-filter.yaml create mode 100644 .maestro/helpers/tap-search-field.yaml create mode 100644 app/containers/List/native/Indicator.ios.tsx create mode 100644 app/sagas/preloadNativeHeaderIcons.js create mode 100644 app/views/RoomMembersView/components/ActionsSection.ios.tsx create mode 100644 app/views/RoomMembersView/components/useMemberActions.ts diff --git a/.maestro/helpers/clear-search-field.yaml b/.maestro/helpers/clear-search-field.yaml new file mode 100644 index 00000000000..3ee551c0df0 --- /dev/null +++ b/.maestro/helpers/clear-search-field.yaml @@ -0,0 +1,18 @@ +appId: ${APP_ID} +name: Clear search field +tags: + - 'util' +--- +- runFlow: + when: + platform: iOS + commands: + - tapOn: + id: 'magnifyingglass' + - eraseText +- runFlow: + when: + platform: android + commands: + - tapOn: + id: clear-text-input diff --git a/.maestro/helpers/select-header-filter.yaml b/.maestro/helpers/select-header-filter.yaml new file mode 100644 index 00000000000..7a577c1cd3d --- /dev/null +++ b/.maestro/helpers/select-header-filter.yaml @@ -0,0 +1,37 @@ +appId: ${APP_ID} +name: Select header filter +tags: + - 'util' +--- +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: + text: 'Filter' + timeout: 60000 + - tapOn: + text: 'Filter' + - extendedWaitUntil: + visible: + text: ${IOS_OPTION} + timeout: 60000 + - tapOn: + text: ${IOS_OPTION} +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + visible: + id: ${ANDROID_FILTER_ID} + timeout: 60000 + - tapOn: + id: ${ANDROID_FILTER_ID} + - extendedWaitUntil: + visible: + id: ${ANDROID_OPTION_ID} + timeout: 60000 + - tapOn: + id: ${ANDROID_OPTION_ID} diff --git a/.maestro/helpers/tap-search-field.yaml b/.maestro/helpers/tap-search-field.yaml new file mode 100644 index 00000000000..a2f14c3f691 --- /dev/null +++ b/.maestro/helpers/tap-search-field.yaml @@ -0,0 +1,33 @@ +appId: ${APP_ID} +name: Tap search field +tags: + - 'util' +--- +- runFlow: + when: + platform: iOS + commands: + - runFlow: + when: + notVisible: + id: 'magnifyingglass' + commands: + - swipe: + start: 50%, 30% + end: 50%, 60% + - extendedWaitUntil: + visible: + id: 'magnifyingglass' + timeout: 60000 + - tapOn: + id: 'magnifyingglass' +- runFlow: + when: + platform: android + commands: + - extendedWaitUntil: + visible: + id: ${SEARCH_ID} + timeout: 60000 + - tapOn: + id: ${SEARCH_ID} diff --git a/.maestro/tests/assorted/broadcast.yaml b/.maestro/tests/assorted/broadcast.yaml index 4626b35b9c6..fd9347c288e 100644 --- a/.maestro/tests/assorted/broadcast.yaml +++ b/.maestro/tests/assorted/broadcast.yaml @@ -38,8 +38,10 @@ tags: visible: id: 'select-users-view' timeout: 60000 -- tapOn: - id: 'select-users-view-search' +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' - inputText: ${output.otherUser.username} - extendedWaitUntil: visible: diff --git a/.maestro/tests/assorted/join-from-directory.yaml b/.maestro/tests/assorted/join-from-directory.yaml index d18f1f3d063..c9673976a5b 100644 --- a/.maestro/tests/assorted/join-from-directory.yaml +++ b/.maestro/tests/assorted/join-from-directory.yaml @@ -35,12 +35,10 @@ tags: timeout: 60000 # should search public channel and navigate -- extendedWaitUntil: - visible: - id: 'directory-view-search' - timeout: 60000 -- tapOn: - id: 'directory-view-search' +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'directory-view-search' - inputText: join-from-directory - extendedWaitUntil: visible: @@ -96,20 +94,16 @@ tags: visible: id: 'directory-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'directory-view-filter' - timeout: 60000 -- tapOn: - id: 'directory-view-filter' -- extendedWaitUntil: - visible: - id: 'directory-switch-users' - timeout: 60000 -- tapOn: - id: 'directory-switch-users' -- tapOn: - id: 'directory-view-search' +- runFlow: + file: '../../helpers/select-header-filter.yaml' + env: + ANDROID_FILTER_ID: 'directory-view-filter' + ANDROID_OPTION_ID: 'directory-switch-users' + IOS_OPTION: 'Users' +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'directory-view-search' - inputText: ${output.otherUser.username} - runFlow: when: @@ -142,20 +136,16 @@ tags: visible: id: 'directory-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'directory-view-filter' - timeout: 60000 -- tapOn: - id: 'directory-view-filter' -- extendedWaitUntil: - visible: - id: 'directory-switch-teams' - timeout: 60000 -- tapOn: - id: 'directory-switch-teams' -- tapOn: - id: 'directory-view-search' +- runFlow: + file: '../../helpers/select-header-filter.yaml' + env: + ANDROID_FILTER_ID: 'directory-view-filter' + ANDROID_OPTION_ID: 'directory-switch-teams' + IOS_OPTION: 'Teams' +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'directory-view-search' - inputText: ${output.team} - extendedWaitUntil: visible: diff --git a/.maestro/tests/e2ee/flows/create-e2ee-room.yaml b/.maestro/tests/e2ee/flows/create-e2ee-room.yaml index 3d02dca47d7..4c88fb8f384 100644 --- a/.maestro/tests/e2ee/flows/create-e2ee-room.yaml +++ b/.maestro/tests/e2ee/flows/create-e2ee-room.yaml @@ -22,8 +22,10 @@ tags: visible: id: 'select-users-view' timeout: 60000 -- tapOn: - id: 'select-users-view-search' +- runFlow: + file: '../../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' - inputText: ${USER_TO_ADD} - extendedWaitUntil: visible: diff --git a/.maestro/tests/room/create-dm-group.yaml b/.maestro/tests/room/create-dm-group.yaml index e8e5d1deeef..340078ba699 100644 --- a/.maestro/tests/room/create-dm-group.yaml +++ b/.maestro/tests/room/create-dm-group.yaml @@ -45,12 +45,10 @@ tags: id: 'new-message-view-create-direct-message' # should add users -- extendedWaitUntil: - visible: - id: 'select-users-view-search' - timeout: 60000 -- tapOn: - id: 'select-users-view-search' +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' - inputText: 'rocket.cat' - extendedWaitUntil: visible: @@ -58,8 +56,10 @@ tags: timeout: 60000 - tapOn: id: 'select-users-view-item-rocket.cat' -- tapOn: - id: 'select-users-view-search' +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' - eraseText - inputText: ${output.otherUser.username} - extendedWaitUntil: diff --git a/.maestro/tests/room/create-room.yaml b/.maestro/tests/room/create-room.yaml index 396b8dcbbcc..fc8219920eb 100644 --- a/.maestro/tests/room/create-room.yaml +++ b/.maestro/tests/room/create-room.yaml @@ -104,12 +104,10 @@ tags: timeout: 60000 # should search users -- extendedWaitUntil: - visible: - id: 'select-users-view-search' - timeout: 60000 -- tapOn: - id: 'select-users-view-search' +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' - inputText: 'rocket.cat' - extendedWaitUntil: visible: diff --git a/.maestro/tests/room/discussion.yaml b/.maestro/tests/room/discussion.yaml index 66695b626de..380306dec08 100644 --- a/.maestro/tests/room/discussion.yaml +++ b/.maestro/tests/room/discussion.yaml @@ -45,12 +45,10 @@ tags: id: 'select-users-view' timeout: 60000 # Select users in SelectedUsersView -- extendedWaitUntil: - visible: - id: 'select-users-view-search' - timeout: 60000 -- tapOn: - id: 'select-users-view-search' +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' - inputText: ${output.selectUser} - extendedWaitUntil: visible: diff --git a/.maestro/tests/room/room-actions.yaml b/.maestro/tests/room/room-actions.yaml index fdff575b0e3..da3f531a434 100644 --- a/.maestro/tests/room/room-actions.yaml +++ b/.maestro/tests/room/room-actions.yaml @@ -364,12 +364,10 @@ tags: visible: id: 'selected-user-rocket.cat' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'select-users-view-search' - timeout: 60000 -- tapOn: - id: select-users-view-search +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' - inputText: ${output.otherUser.username} - extendedWaitUntil: visible: @@ -396,18 +394,12 @@ tags: visible: id: 'room-members-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-members-view-filter' - timeout: 60000 -- tapOn: - id: 'room-members-view-filter' -- extendedWaitUntil: - visible: - id: 'room-members-view-toggle-status-all' - timeout: 60000 -- tapOn: - id: 'room-members-view-toggle-status-all' +- runFlow: + file: '../../helpers/select-header-filter.yaml' + env: + ANDROID_FILTER_ID: 'room-members-view-filter' + ANDROID_OPTION_ID: 'room-members-view-toggle-status-all' + IOS_OPTION: 'All' - extendedWaitUntil: notVisible: id: 'room-members-view-toggle-status-all' @@ -425,12 +417,10 @@ tags: visible: id: 'room-members-view' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'room-members-view-search' - timeout: 60000 -- tapOn: - id: room-members-view-search +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'room-members-view-search' - inputText: 'rocket' - tapOn: id: room-members-view @@ -442,8 +432,7 @@ tags: notVisible: id: 'room-members-view-item-${output.otherUser.username}' timeout: 60000 -- tapOn: - id: clear-text-input +- runFlow: '../../helpers/clear-search-field.yaml' - extendedWaitUntil: visible: id: 'room-members-view-item-rocket.cat' diff --git a/.maestro/tests/room/search-member.yaml b/.maestro/tests/room/search-member.yaml index baeda26a88b..9fe42658adc 100644 --- a/.maestro/tests/room/search-member.yaml +++ b/.maestro/tests/room/search-member.yaml @@ -23,14 +23,12 @@ tags: - runFlow: '../../helpers/open-room-actions.yaml' - tapOn: id: 'room-actions-members' -- extendedWaitUntil: - visible: - id: 'room-members-view-search' - timeout: 60000 # should search in all users -- tapOn: - id: room-members-view-search +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'room-members-view-search' - inputText: rohit.bansal - extendedWaitUntil: visible: @@ -38,40 +36,37 @@ tags: timeout: 60000 # use online status and it should use the text filter -- tapOn: - id: room-members-view-filter -- extendedWaitUntil: - visible: - id: 'room-members-view-toggle-status-online' - timeout: 60000 -- tapOn: - id: room-members-view-toggle-status-online +- runFlow: + file: '../../helpers/select-header-filter.yaml' + env: + ANDROID_FILTER_ID: 'room-members-view-filter' + ANDROID_OPTION_ID: 'room-members-view-toggle-status-online' + IOS_OPTION: 'Online' - extendedWaitUntil: visible: text: 'No members found' timeout: 60000 # use all status again and it should use text filter -- tapOn: - id: room-members-view-filter -- extendedWaitUntil: - visible: - id: 'room-members-view-toggle-status-all' - timeout: 60000 -- tapOn: - id: room-members-view-toggle-status-all +- runFlow: + file: '../../helpers/select-header-filter.yaml' + env: + ANDROID_FILTER_ID: 'room-members-view-filter' + ANDROID_OPTION_ID: 'room-members-view-toggle-status-all' + IOS_OPTION: 'All' - extendedWaitUntil: visible: id: 'room-members-view-item-rohit.bansal' timeout: 60000 -- tapOn: - id: clear-text-input +- runFlow: '../../helpers/clear-search-field.yaml' - evalScript: ${output.secondUser = output.utils.createUser()} # should search for new user in all list -- tapOn: - id: room-members-view-search +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'room-members-view-search' - inputText: ${output.secondUser.username} - extendedWaitUntil: visible: @@ -79,8 +74,10 @@ tags: timeout: 60000 # Verify "No members found" message appears correctly when search returns no results -- tapOn: - id: room-members-view-search +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'room-members-view-search' - inputText: nonexistentuser12345 - extendedWaitUntil: visible: diff --git a/.maestro/tests/teams/team.yaml b/.maestro/tests/teams/team.yaml index e5251a7caff..4b64887b3ce 100644 --- a/.maestro/tests/teams/team.yaml +++ b/.maestro/tests/teams/team.yaml @@ -114,13 +114,11 @@ tags: # Create new channel for team - tapOn: id: add-channel-team-view-create-channel -- assertVisible: - id: select-users-view-search -- tapOn: - id: select-users-view-search -- inputText: - id: select-users-view-search - text: rocket.cat +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' +- inputText: rocket.cat - extendedWaitUntil: visible: id: select-users-view-item-rocket.cat @@ -364,10 +362,10 @@ tags: timeout: 60000 - tapOn: id: room-actions-add-user -- assertVisible: - id: select-users-view-search -- tapOn: - id: select-users-view-search +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' - inputText: rocket.cat - runFlow: '../../helpers/hide-keyboard.yaml' - extendedWaitUntil: @@ -380,12 +378,10 @@ tags: visible: id: selected-user-rocket.cat timeout: 60000 -- extendedWaitUntil: - visible: - id: select-users-view-search - timeout: 60000 -- tapOn: - id: select-users-view-search +- runFlow: + file: '../../helpers/tap-search-field.yaml' + env: + SEARCH_ID: 'select-users-view-search' - eraseText - inputText: ${output.secondUser.username} - runFlow: '../../helpers/hide-keyboard.yaml' @@ -404,16 +400,12 @@ tags: - tapOn: id: selected-users-view-submit -- assertVisible: - id: room-members-view-filter -- tapOn: - id: room-members-view-filter -- extendedWaitUntil: - visible: - id: room-members-view-toggle-status-all - timeout: 60000 -- tapOn: - id: room-members-view-toggle-status-all +- runFlow: + file: '../../helpers/select-header-filter.yaml' + env: + ANDROID_FILTER_ID: 'room-members-view-filter' + ANDROID_OPTION_ID: 'room-members-view-toggle-status-all' + IOS_OPTION: 'All' - extendedWaitUntil: visible: id: room-members-view-item-${output.secondUser.username} diff --git a/app/containers/List/__snapshots__/List.test.tsx.snap b/app/containers/List/__snapshots__/List.test.tsx.snap index dbfd22d3ca5..08576eb2236 100644 --- a/app/containers/List/__snapshots__/List.test.tsx.snap +++ b/app/containers/List/__snapshots__/List.test.tsx.snap @@ -7971,21 +7971,20 @@ exports[`Story Snapshots: WithIcon should match snapshot 1`] = ` [ { "$type": "font", - "family": "custom", - "size": 24, - "textStyle": "body", + "textStyle": "footnote", + "weight": "semibold", }, { "$type": "foregroundStyle", "style": { - "color": "#2F343D", + "color": "#6C727A", "type": "color", }, }, ] } onGlobalEvent={[Function]} - text="" + systemName="chevron.forward" /> diff --git a/app/containers/List/native/Accessory.ios.tsx b/app/containers/List/native/Accessory.ios.tsx index 62f73fadd8f..9da0409592b 100644 --- a/app/containers/List/native/Accessory.ios.tsx +++ b/app/containers/List/native/Accessory.ios.tsx @@ -4,6 +4,7 @@ import { disabled, font, foregroundStyle, labelsHidden, lineLimit, tint } from ' import { useTheme } from '~/theme'; import NativeListIcon from './Icon.ios'; +import NativeListIndicator from './Indicator.ios'; import NativeListStatus from './Status.ios'; import { type TNativeListAccessory } from './describeAccessory'; @@ -13,6 +14,8 @@ const NativeListAccessory = ({ accessory }: { accessory: TNativeListAccessory }) switch (accessory.kind) { case 'icon': return ; + case 'indicator': + return ; case 'check': return ( ['systemName']> = { + disclosure: 'chevron.forward', + external: 'arrow.up.forward' +}; + +const NativeListIndicator = ({ indicator }: { indicator: TNativeListIndicator }) => { + const { colors } = useTheme(); + + return ( + + ); +}; + +export default NativeListIndicator; diff --git a/app/containers/List/native/Item.ios.tsx b/app/containers/List/native/Item.ios.tsx index 51f99f8466e..166333486ed 100644 --- a/app/containers/List/native/Item.ios.tsx +++ b/app/containers/List/native/Item.ios.tsx @@ -1,5 +1,4 @@ import { type ReactNode } from 'react'; -import { I18nManager } from 'react-native'; import { Button, HStack, RNHostView, Spacer, Text, VStack } from '@expo/ui/swift-ui'; import { accessibilityLabel, @@ -18,14 +17,13 @@ import { useTheme } from '~/theme'; import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; import NativeListAccessory from './Accessory.ios'; import NativeListIcon from './Icon.ios'; +import NativeListIndicator from './Indicator.ios'; import { type IListItem } from '../ListItem'; import { type INativeListItem } from './types'; import { describeNativeListAccessory } from './describeAccessory'; import { BASE_HEIGHT, PADDING_HORIZONTAL } from '../constants'; import { nativeListItemAccessibilityLabel, nativeListItemSubtitle, nativeListItemTitle, pressNativeListItem } from './itemProps'; -const CHEVRON = I18nManager.isRTL ? 'chevron-left' : 'chevron-right'; - const NativeListItemTitle = ({ item }: { item: IListItem }) => { const { colors } = useTheme(); const title = nativeListItemTitle(item); @@ -93,7 +91,7 @@ const NativeListItem = ({ item }: INativeListItem) => { {trailing ? : null} - {item.showActionIndicator ? : null} + {item.showActionIndicator ? : null} ); }; diff --git a/app/containers/List/native/__tests__/describeAccessory.test.tsx b/app/containers/List/native/__tests__/describeAccessory.test.tsx index 247e6ff41b6..3b8bbfe8d51 100644 --- a/app/containers/List/native/__tests__/describeAccessory.test.tsx +++ b/app/containers/List/native/__tests__/describeAccessory.test.tsx @@ -32,8 +32,15 @@ describe('describeNativeListAccessory', () => { }); }); - it('maps the external link icon to the new-window glyph', () => { - expect(describeNativeListAccessory()).toEqual({ kind: 'icon', name: 'new-window' }); + it('maps the external link icon to the external indicator', () => { + expect(describeNativeListAccessory()).toEqual({ kind: 'indicator', indicator: 'external' }); + }); + + it('maps the chevron icon to the disclosure indicator', () => { + expect(describeNativeListAccessory()).toEqual({ + kind: 'indicator', + indicator: 'disclosure' + }); }); it('shows a checkmark only for a checked radio', () => { diff --git a/app/containers/List/native/describeAccessory.ts b/app/containers/List/native/describeAccessory.ts index 78ff82da22e..4836fa55740 100644 --- a/app/containers/List/native/describeAccessory.ts +++ b/app/containers/List/native/describeAccessory.ts @@ -13,6 +13,7 @@ import ListIcon from '../ListIcon'; export type TNativeListAccessory = | { kind: 'icon'; name: TIconsName; color?: string; size?: number } | { kind: 'check' } + | { kind: 'indicator'; indicator: 'disclosure' | 'external' } | { kind: 'status'; status: TUserStatus } | { kind: 'toggle'; isOn: boolean; onValueChange?: (value: boolean) => void; disabled: boolean; testID?: string } | { kind: 'checkbox'; value: boolean; onValueChange: (value: boolean) => void; testID?: string } @@ -25,12 +26,15 @@ interface IIconProps { size?: number; } -const iconAccessory = (name: TIconsName, { color, size }: Partial): TNativeListAccessory => ({ - kind: 'icon', - name, - color: color || undefined, - size -}); +const iconAccessory = (name: TIconsName, { color, size }: Partial): TNativeListAccessory => { + if (name === 'chevron-right') { + return { kind: 'indicator', indicator: 'disclosure' }; + } + if (name === 'new-window') { + return { kind: 'indicator', indicator: 'external' }; + } + return { kind: 'icon', name, color: color || undefined, size }; +}; const textContent = (children: ReactNode) => { const parts = Children.toArray(children); @@ -70,7 +74,7 @@ const describers = new Map([ [ListCheckbox, describeCheckbox], [ListIcon, props => iconAccessory(props.name, props)], [CustomIcon, props => iconAccessory(props.name, props)], - [NewWindowIcon, props => iconAccessory('new-window', props)], + [NewWindowIcon, () => ({ kind: 'indicator', indicator: 'external' })], [Radio, ({ check }) => (check ? { kind: 'check' } : null)], [Status, ({ status }) => ({ kind: 'status', status: status ?? 'offline' })], [Switch, describeToggle], diff --git a/app/containers/NativeListRow/index.tsx b/app/containers/NativeListRow/index.tsx index 48097bf67d5..78656289d2b 100644 --- a/app/containers/NativeListRow/index.tsx +++ b/app/containers/NativeListRow/index.tsx @@ -11,6 +11,7 @@ import { foregroundStyle, lineLimit, onLongPressGesture, + opacity, padding } from '@expo/ui/swift-ui/modifiers'; @@ -59,6 +60,7 @@ export interface INativeListRow { isSelected?: boolean; isFirst?: boolean; isLast?: boolean; + disabled?: boolean; } const NativeListRow = ({ @@ -74,7 +76,8 @@ const NativeListRow = ({ accessibilityLabel, isSelected, isFirst, - isLast + isLast, + disabled }: INativeListRow) => { const { colors, theme } = useTheme(); const { fontScale } = useResponsiveLayout(); @@ -82,7 +85,8 @@ const NativeListRow = ({ const rowModifiers = [ accessibilityLabelModifier(accessibilityLabel), ...(isSelected ? [accessibilityAddTraits(['isSelected'])] : []), - ...(onLongPress ? [onLongPressGesture(onLongPress)] : []) + ...(onLongPress ? [onLongPressGesture(onLongPress)] : []), + ...(disabled ? [disabledModifier(true), opacity(0.3)] : []) ]; const titleText = ( diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index a55b9227e9e..d6a32ae75e9 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -28,6 +28,7 @@ import { videoConfJoin } from '../lib/methods/videoConf'; import { loginOAuthOrSso } from '../lib/services/connect'; import { notifyUser } from '../lib/services/restApi'; import sdk from '../lib/services/sdk'; +import { preloadNativeHeaderIcons } from './preloadNativeHeaderIcons'; import Navigation, { waitForNavigationReady } from '../lib/navigation/appNavigation'; import { resetVoipState } from '../lib/services/voip/resetVoipState'; @@ -325,6 +326,7 @@ const handleOpenDifferentServer = function* handleOpenDifferentServer({ params, }; const handleOpen = function* handleOpen({ params }) { + yield call(preloadNativeHeaderIcons); if (params.type === 'shareextension') { yield handleShareExtension({ params }); return; diff --git a/app/sagas/init.js b/app/sagas/init.js index 957d61f3ea6..4701a0723f8 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -10,7 +10,6 @@ import { selectServerRequest } from '../actions/server'; import { setAllPreferences } from '../actions/sortPreferences'; import { APP } from '../actions/actionsTypes'; import log from '../lib/methods/helpers/log'; -import { isIOS } from '../lib/methods/helpers'; import { localAuthenticate, UserCanceledError } from '../lib/methods/helpers/localAuthentication'; import { runBiometricTrustMigration } from '../lib/biometricTrustStore/migration'; import { appReady, appStart } from '../actions/app'; @@ -18,7 +17,7 @@ import { RootEnum } from '../definitions'; import { getSortPreferences } from '../lib/methods/userPreferencesMethods'; import { deepLinkingClickCallPush } from '../actions/deepLinking'; import { getServerById } from '../lib/database/services/Server'; -import { preloadHeaderIcons } from '../lib/methods/helpers/navigation/headerIcon'; +import { preloadNativeHeaderIcons } from './preloadNativeHeaderIcons'; const PUSH_NOTIFICATION_KEY = 'pushNotification'; @@ -75,13 +74,7 @@ const deliverPendingPushNotification = function* deliverPendingPushNotification( const restore = function* restore() { yield call(runBiometricTrustMigration); - if (isIOS) { - try { - yield call(preloadHeaderIcons); - } catch (e) { - log(e); - } - } + yield call(preloadNativeHeaderIcons); const restoredServer = yield* getServerToRestore(); diff --git a/app/sagas/preloadNativeHeaderIcons.js b/app/sagas/preloadNativeHeaderIcons.js new file mode 100644 index 00000000000..01aecbeac3b --- /dev/null +++ b/app/sagas/preloadNativeHeaderIcons.js @@ -0,0 +1,16 @@ +import { call } from 'redux-saga/effects'; + +import { isIOS } from '../lib/methods/helpers'; +import log from '../lib/methods/helpers/log'; +import { preloadHeaderIcons } from '../lib/methods/helpers/navigation/headerIcon'; + +export const preloadNativeHeaderIcons = function* preloadNativeHeaderIcons() { + if (!isIOS) { + return; + } + try { + yield call(preloadHeaderIcons); + } catch (e) { + log(e); + } +}; diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index e5b06a65611..1bae02a84ed 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -2,13 +2,16 @@ import { useLayoutEffect, type ReactElement } from 'react'; import { FlatList, type ListRenderItem } from 'react-native'; import { shallowEqual } from 'react-redux'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { type NativeStackNavigationOptions, type NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { + type NativeStackHeaderItemMenuAction, + type NativeStackNavigationOptions, + type NativeStackNavigationProp +} from '@react-navigation/native-stack'; import { type CompositeNavigationProp } from '@react-navigation/native'; import { useActionSheet } from '~/containers/ActionSheet'; import { type ChatsStackParamList } from '~/stacks/types'; import { type MasterDetailInsideStackParamList } from '~/stacks/MasterDetailStack/types'; -import * as List from '~/containers/List'; import DirectoryItem from '~/containers/DirectoryItem'; import sharedStyles from '../Styles'; import I18n from '~/i18n'; @@ -27,6 +30,8 @@ import { getSubscriptionByRoomId } from '~/lib/database/services/Subscription'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { useDirectorySearch } from './hooks/useDirectorySearch'; +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; +import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; import RowSeparator from '~/containers/NativeListRow/Separator'; import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; @@ -74,14 +79,72 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { }); }; - const options: NativeStackNavigationOptions = { - title: I18n.t('Directory'), - headerRight: () => ( - - - - ) - }; + const typeAction = ( + itemType: string, + title: string, + icon: NativeStackHeaderItemMenuAction['icon'] + ): NativeStackHeaderItemMenuAction => ({ + type: 'action', + label: I18n.t(title), + icon, + state: type === itemType ? 'on' : 'off', + onPress: () => changeType(itemType) + }); + + const options: NativeStackNavigationOptions = hasNativeHeaderBar + ? { + title: I18n.t('Directory'), + headerRight: undefined, + headerTransparent: true, + headerSearchBarOptions: { + placement: 'stacked', + placeholder: I18n.t('Search'), + onChangeText: event => onSearchChangeText(event.nativeEvent.text), + onSearchButtonPress: () => search(), + onCancelButtonPress: () => onSearchChangeText('') + }, + unstable_headerRightItems: () => [ + { + type: 'menu', + label: I18n.t('Filter'), + accessibilityLabel: I18n.t('Filter'), + icon: headerIcon('filter'), + menu: { + items: [ + { + type: 'submenu', + label: I18n.t('Filter'), + inline: true, + items: [ + typeAction('channels', 'Channels', { type: 'sfSymbol', name: 'number' }), + typeAction('users', 'Users', { type: 'sfSymbol', name: 'person' }), + typeAction('teams', 'Teams', { type: 'sfSymbol', name: 'person.3' }) + ] + }, + ...(isFederationEnabled + ? [ + { + type: 'action' as const, + label: I18n.t('Search_global_users'), + description: I18n.t('Search_global_users_description'), + state: globalUsers ? ('on' as const) : ('off' as const), + onPress: toggleWorkspace + } + ] + : []) + ] + } + } + ] + } + : { + title: I18n.t('Directory'), + headerRight: () => ( + + + + ) + }; if (isMasterDetail) { options.headerLeft = () => ; } @@ -96,7 +159,9 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { changeType, toggleWorkspace, showActionSheet, - hideActionSheet + hideActionSheet, + onSearchChangeText, + search ]); const goRoom = (item: TGoRoomItem) => { @@ -199,11 +264,13 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { return ( - - + {hasNativeHeaderBar ? null : ( + + )} + {actions.map((action, index) => ( + + {index > 0 ? : null} + } + trailing={} + /> + + ))} + + ); +} diff --git a/app/views/RoomMembersView/components/ActionsSection.tsx b/app/views/RoomMembersView/components/ActionsSection.tsx index b54db44fca7..db0c677c890 100644 --- a/app/views/RoomMembersView/components/ActionsSection.tsx +++ b/app/views/RoomMembersView/components/ActionsSection.tsx @@ -1,114 +1,29 @@ -import { type CompositeNavigationProp, useNavigation } from '@react-navigation/native'; -import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { View } from 'react-native'; -import { useDispatch } from 'react-redux'; import { type ReactElement } from 'react'; -import { setLoading } from '~/actions/selectedUsers'; import * as List from '~/containers/List'; -import { type TSubscriptionModel } from '~/definitions'; -import i18n from '~/i18n'; -import { usePermissions } from '~/lib/hooks/usePermissions'; -import log, { events, logEvent } from '~/lib/methods/helpers/log'; -import { addUsersToRoom } from '~/lib/services/restApi'; -import { type MasterDetailInsideStackParamList } from '~/stacks/MasterDetailStack/types'; -import { type ChatsStackParamList } from '~/stacks/types'; +import { type IActionsSection, useMemberActions } from './useMemberActions'; -type TNavigation = CompositeNavigationProp< - NativeStackNavigationProp, - NativeStackNavigationProp ->; - -interface IActionsSection { - rid: TSubscriptionModel['rid']; - t: TSubscriptionModel['t']; - joined: boolean; - abacAttributes: TSubscriptionModel['abacAttributes']; -} - -export default function ActionsSection({ rid, t, joined, abacAttributes }: IActionsSection): ReactElement { - const { navigate, pop } = useNavigation(); - const dispatch = useDispatch(); - const [addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission, createInviteLinksPermission] = - usePermissions(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room', 'create-invite-links'], rid); - - const canAddUser = - (joined && addUserToJoinedRoomPermission) || - (t === 'c' && addUserToAnyCRoomPermission) || - (t === 'p' && addUserToAnyPRoomPermission) || - false; - - const canInviteUser = createInviteLinksPermission; - - const handleOnPress = ({ - route, - params - }: { - route: keyof ChatsStackParamList; - params: ChatsStackParamList[keyof ChatsStackParamList]; - }) => { - // @ts-ignore - navigate(route, params); - // @ts-ignore - logEvent(events[`RM_GO_${route.replace('View', '').toUpperCase()}`]); - }; - - const addUser = async () => { - try { - dispatch(setLoading(true)); - await addUsersToRoom(rid); - pop(); - } catch (e) { - log(e); - } finally { - dispatch(setLoading(false)); - } - }; +export default function ActionsSection(props: IActionsSection): ReactElement { + const actions = useMemberActions(props); return ( - - {['c', 'p'].includes(t) && canAddUser ? ( - <> - - - handleOnPress({ - route: 'SelectedUsersView', - params: { - title: i18n.t('Add_users'), - nextAction: addUser, - showSkipText: false - } - }) - } - testID='room-actions-add-user' - left={() => } - showActionIndicator - /> - - - ) : null} - - {['c', 'p'].includes(t) && canInviteUser ? ( - <> + + {actions.map((action, index) => ( + + {index === 0 ? : null} - handleOnPress({ - route: 'InviteUsersView', - params: { rid } - }) - } - testID='room-actions-invite-user' - left={() => } + title={action.title} + onPress={action.onPress} + testID={action.testID} + left={() => } showActionIndicator - disabled={!!abacAttributes} - disabledReason={abacAttributes ? i18n.t('ABAC_disabled_action_reason') : undefined} + disabled={action.disabled} + disabledReason={action.disabledReason} /> - - ) : null} + + ))} ); } diff --git a/app/views/RoomMembersView/components/useMemberActions.ts b/app/views/RoomMembersView/components/useMemberActions.ts new file mode 100644 index 00000000000..225074bafa1 --- /dev/null +++ b/app/views/RoomMembersView/components/useMemberActions.ts @@ -0,0 +1,101 @@ +import { type CompositeNavigationProp, useNavigation } from '@react-navigation/native'; +import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useDispatch } from 'react-redux'; + +import { setLoading } from '~/actions/selectedUsers'; +import { type TIconsName } from '~/containers/CustomIcon'; +import { type TSubscriptionModel } from '~/definitions'; +import i18n from '~/i18n'; +import { usePermissions } from '~/lib/hooks/usePermissions'; +import log, { events, logEvent } from '~/lib/methods/helpers/log'; +import { addUsersToRoom } from '~/lib/services/restApi'; +import { type MasterDetailInsideStackParamList } from '~/stacks/MasterDetailStack/types'; +import { type ChatsStackParamList } from '~/stacks/types'; + +type TNavigation = CompositeNavigationProp< + NativeStackNavigationProp, + NativeStackNavigationProp +>; + +export interface IActionsSection { + rid: TSubscriptionModel['rid']; + t: TSubscriptionModel['t']; + joined: boolean; + abacAttributes: TSubscriptionModel['abacAttributes']; +} + +export interface IMemberAction { + title: string; + icon: TIconsName; + onPress: () => void; + testID: string; + disabled?: boolean; + disabledReason?: string; +} + +export const useMemberActions = ({ rid, t, joined, abacAttributes }: IActionsSection): IMemberAction[] => { + const { navigate, pop } = useNavigation(); + const dispatch = useDispatch(); + const [addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission, createInviteLinksPermission] = + usePermissions(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room', 'create-invite-links'], rid); + + if (!['c', 'p'].includes(t)) { + return []; + } + + const canAddUser = + (joined && addUserToJoinedRoomPermission) || + (t === 'c' && addUserToAnyCRoomPermission) || + (t === 'p' && addUserToAnyPRoomPermission) || + false; + + const handleOnPress = ({ + route, + params + }: { + route: keyof ChatsStackParamList; + params: ChatsStackParamList[keyof ChatsStackParamList]; + }) => { + // @ts-ignore + navigate(route, params); + // @ts-ignore + logEvent(events[`RM_GO_${route.replace('View', '').toUpperCase()}`]); + }; + + const addUser = async () => { + try { + dispatch(setLoading(true)); + await addUsersToRoom(rid); + pop(); + } catch (e) { + log(e); + } finally { + dispatch(setLoading(false)); + } + }; + + const actions: IMemberAction[] = []; + if (canAddUser) { + actions.push({ + title: 'Add_users', + icon: 'add', + testID: 'room-actions-add-user', + onPress: () => + handleOnPress({ + route: 'SelectedUsersView', + params: { title: i18n.t('Add_users'), nextAction: addUser, showSkipText: false } + }) + }); + } + if (createInviteLinksPermission) { + actions.push({ + title: 'Invite_users', + icon: 'user-add', + testID: 'room-actions-invite-user', + onPress: () => handleOnPress({ route: 'InviteUsersView', params: { rid } }), + disabled: !!abacAttributes, + disabledReason: abacAttributes ? i18n.t('ABAC_disabled_action_reason') : undefined + }); + } + return actions; +}; diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index aa74a531e7a..94a94928acb 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -1,5 +1,5 @@ import { type NavigationProp, type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; -import { type ReactElement, useCallback, useEffect, useReducer, useRef } from 'react'; +import { type ReactElement, useCallback, useEffect, useLayoutEffect, useReducer, useRef } from 'react'; import { FlatList, Text } from 'react-native'; import { shallowEqual } from 'react-redux'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -20,7 +20,8 @@ import I18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { usePermissions } from '~/lib/hooks/usePermissions'; -import { compareServerVersion, getRoomTitle, isGroupChat, useDebounce } from '~/lib/methods/helpers'; +import { compareServerVersion, getRoomTitle, hasNativeHeaderBar, isGroupChat, useDebounce } from '~/lib/methods/helpers'; +import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; import { handleIgnore } from '~/lib/methods/helpers/handleIgnore'; import { showConfirmationAlert } from '~/lib/methods/helpers/info'; import log from '~/lib/methods/helpers/log'; @@ -127,7 +128,7 @@ const RoomMembersView = (): ReactElement => { viewAllTeamsPermission ] = usePermissions(['mute-user', 'set-leader', 'set-owner', 'set-moderator', 'remove-user', ...teamPermissions], params.rid); - useEffect(() => { + useLayoutEffect(() => { const subscription = params?.room?.observe && params.room.observe().subscribe(changes => updateState({ room: changes })); setHeader(true); return () => subscription?.unsubscribe(); @@ -252,6 +253,44 @@ const RoomMembersView = (): ReactElement => { }; const setHeader = (allUsers: boolean) => { + if (hasNativeHeaderBar) { + navigation.setOptions({ + title: I18n.t('Members'), + headerRight: undefined, + headerTransparent: true, + headerSearchBarOptions: { + placement: 'stacked', + placeholder: I18n.t('Search'), + onChangeText: (event: { nativeEvent: { text: string } }) => debounceFilterChange(event.nativeEvent.text), + onCancelButtonPress: () => debounceFilterChange('') + }, + unstable_headerRightItems: () => [ + { + type: 'menu', + label: I18n.t('Filter'), + accessibilityLabel: I18n.t('Filter'), + icon: headerIcon('filter'), + menu: { + items: [ + { + type: 'action', + label: I18n.t('Online'), + state: allUsers ? 'off' : 'on', + onPress: () => toggleStatus(false) + }, + { + type: 'action', + label: I18n.t('All'), + state: allUsers ? 'on' : 'off', + onPress: () => toggleStatus(true) + } + ] + } + } + ] + }); + return; + } navigation.setOptions({ title: I18n.t('Members'), headerRight: () => ( @@ -431,6 +470,7 @@ const RoomMembersView = (): ReactElement => { ( { /> )} style={[styles.list, { backgroundColor: listBackgroundColor }]} - contentContainerStyle={{ paddingBottom: bottom }} + contentContainerStyle={{ flexGrow: 1, paddingBottom: bottom }} keyExtractor={item => item._id} ItemSeparatorComponent={RowSeparator} ListHeaderComponent={ @@ -454,7 +494,9 @@ const RoomMembersView = (): ReactElement => { t={state.room.t} abacAttributes={state.room.abacAttributes} /> - debounceFilterChange(text)} testID='room-members-view-search' /> + {hasNativeHeaderBar ? null : ( + debounceFilterChange(text)} testID='room-members-view-search' /> + )} } ListFooterComponent={() => (state.isLoading ? : null)} diff --git a/app/views/SelectedUsersView/Header.tsx b/app/views/SelectedUsersView/Header.tsx index 361c2dd2fb0..8b2fc8ebfb9 100644 --- a/app/views/SelectedUsersView/Header.tsx +++ b/app/views/SelectedUsersView/Header.tsx @@ -8,6 +8,7 @@ import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import Chip from '~/containers/Chip'; +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; const styles = StyleSheet.create({ @@ -43,7 +44,9 @@ const Header = ({ return ( - onChangeText(text)} testID='select-users-view-search' /> + {hasNativeHeaderBar ? null : ( + onChangeText(text)} testID='select-users-view-search' /> + )} {users.length === 0 ? null : ( diff --git a/app/views/SelectedUsersView/index.tsx b/app/views/SelectedUsersView/index.tsx index 34a4da2e8e9..b3f518543a1 100644 --- a/app/views/SelectedUsersView/index.tsx +++ b/app/views/SelectedUsersView/index.tsx @@ -27,7 +27,7 @@ import { useTheme } from '~/theme'; import { showErrorAlert } from '~/lib/methods/helpers/info'; import log, { events, logEvent } from '~/lib/methods/helpers/log'; import { search as runSearch, type TSearch } from '~/lib/methods/search'; -import { isGroupChat as isGroupChatMethod } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar, isGroupChat as isGroupChatMethod } from '~/lib/methods/helpers'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import Header from './Header'; @@ -78,23 +78,61 @@ const SelectedUsersView = () => { return showSkipText ? I18n.t('Skip') : ''; }; + const handleSearch = useCallback(async (text: string) => { + searchId.current += 1; + const currentSearchId = searchId.current; + const isStale = () => currentSearchId !== searchId.current; + + setSearching(true); + + try { + // Paint local results immediately while the backend request is still in flight + const result = await runSearch({ + text, + filterRooms: false, + onLocal: localData => { + if (isStale()) return; + setSearch(localData); + } + }); + if (isStale()) return; + setSearch(result); + } catch (e) { + log(e); + } finally { + // Only the latest search clears the flag, so a stale request never hides an in-flight newer one + if (!isStale()) setSearching(false); + } + }, []); + useLayoutEffect(() => { const titleHeader = title ?? I18n.t('Select_Members'); const buttonTextHeader = buttonText || I18n.t('Next'); const nextActionHeader = nextAction || (() => {}); const buttonTitle = handleButtonTitle(buttonTextHeader); - const options = { + const showHeaderButton = (!maxUsers || showButton || (isGroupChat() && users.length > 1)) && !!buttonTitle; + navigation.setOptions({ title: titleHeader, - headerRight: () => - (!maxUsers || showButton || (isGroupChat() && users.length > 1)) && - !!buttonTitle && ( - - - - ) - }; - navigation.setOptions(options); - }, [navigation, users.length, maxUsers, buttonText, nextAction]); + headerRight: showHeaderButton + ? () => ( + + + + ) + : () => null, + ...(hasNativeHeaderBar + ? { + headerTransparent: true, + headerSearchBarOptions: { + placement: 'stacked' as const, + placeholder: I18n.t('Search'), + onChangeText: (event: { nativeEvent: { text: string } }) => handleSearch(event.nativeEvent.text), + onCancelButtonPress: () => handleSearch('') + } + } + : {}) + }); + }, [navigation, users.length, maxUsers, buttonText, nextAction, handleSearch]); useEffect(() => { if (isGroupChat() && user.username) { @@ -127,33 +165,6 @@ const SelectedUsersView = () => { }; }, [dispatch]); - const handleSearch = useCallback(async (text: string) => { - searchId.current += 1; - const currentSearchId = searchId.current; - const isStale = () => currentSearchId !== searchId.current; - - setSearching(true); - - try { - // Paint local results immediately while the backend request is still in flight - const result = await runSearch({ - text, - filterRooms: false, - onLocal: localData => { - if (isStale()) return; - setSearch(localData); - } - }); - if (isStale()) return; - setSearch(result); - } catch (e) { - log(e); - } finally { - // Only the latest search clears the flag, so a stale request never hides an in-flight newer one - if (!isStale()) setSearching(false); - } - }, []); - const toggleUser = (userItem: ISelectedUser) => { // Disallow removing self user from the direct message group if (isGroupChat() && user.username === userItem.name) { @@ -188,6 +199,7 @@ const SelectedUsersView = () => { item._id} renderItem={({ item, index }) => { const name = useRealName && item.fname ? item.fname : item.name; @@ -207,9 +219,13 @@ const SelectedUsersView = () => { ); }} ItemSeparatorComponent={RowSeparator} - ListFooterComponent={searching ? : } + ListFooterComponent={searching ? : hasNativeHeaderBar ? null : } ListHeaderComponent={
} - contentContainerStyle={{ backgroundColor: listBackgroundColor, paddingBottom: bottom }} + contentContainerStyle={{ + backgroundColor: listBackgroundColor, + paddingTop: hasNativeHeaderBar ? 16 : 0, + paddingBottom: bottom + }} keyboardShouldPersistTaps='always' /> diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 7081d896c5b..2840f97d858 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -125,9 +125,7 @@ class ThreadMessagesView extends Component Date: Fri, 25 Sep 2026 17:55:10 -0300 Subject: [PATCH 096/120] fix(ios): drop large titles and align list screens with native headers --- app/views/CannedResponsesListView/index.tsx | 1 - app/views/DiscussionsView/index.tsx | 1 - app/views/NewMessageView/ButtonCreate.ios.tsx | 7 +-- .../NotificationPreferencesView/index.tsx | 2 +- .../components/CustomListSection.tsx | 57 ------------------- .../components/DeviceNotificationSettings.tsx | 20 +++++-- .../components/PushGatewayConnection.tsx | 14 +++-- app/views/PushTroubleshootView/index.tsx | 2 +- .../RoomView/hooks/useNativeBackButton.ts | 5 +- .../hooks/__tests__/useHeader.test.tsx | 2 - app/views/RoomsListView/hooks/useHeader.tsx | 2 - app/views/ShareListView/index.tsx | 1 - app/views/StatusView/GlassBackground.ios.tsx | 29 ---------- app/views/StatusView/GlassBackground.tsx | 3 - app/views/StatusView/index.tsx | 27 ++++++--- app/views/TeamChannelsView.tsx | 1 - patches/react-native-screens+4.28.0.patch | 8 ++- 17 files changed, 56 insertions(+), 126 deletions(-) delete mode 100644 app/views/PushTroubleshootView/components/CustomListSection.tsx delete mode 100644 app/views/StatusView/GlassBackground.ios.tsx delete mode 100644 app/views/StatusView/GlassBackground.tsx diff --git a/app/views/CannedResponsesListView/index.tsx b/app/views/CannedResponsesListView/index.tsx index 9e6b5c6e5fb..0e0c27185cf 100644 --- a/app/views/CannedResponsesListView/index.tsx +++ b/app/views/CannedResponsesListView/index.tsx @@ -214,7 +214,6 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView const getHeader = (): NativeStackNavigationOptions => { if (hasNativeHeaderBar) { return { - headerLargeTitle: true, headerLeft: () => null, headerTitle: I18n.t('Canned_Responses'), headerSearchBarOptions: { diff --git a/app/views/DiscussionsView/index.tsx b/app/views/DiscussionsView/index.tsx index 3ec429e544c..9b6afa40988 100644 --- a/app/views/DiscussionsView/index.tsx +++ b/app/views/DiscussionsView/index.tsx @@ -109,7 +109,6 @@ const DiscussionsView = () => { if (hasNativeHeaderBar) { options = { - headerLargeTitle: true, headerLeft: undefined, headerTitle: I18n.t('Discussions'), headerSearchBarOptions: { diff --git a/app/views/NewMessageView/ButtonCreate.ios.tsx b/app/views/NewMessageView/ButtonCreate.ios.tsx index cccb1db9c22..44ea8ea843d 100644 --- a/app/views/NewMessageView/ButtonCreate.ios.tsx +++ b/app/views/NewMessageView/ButtonCreate.ios.tsx @@ -1,6 +1,5 @@ -import { I18nManager } from 'react-native'; import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; -import NativeListIcon from '~/containers/List/native/Icon.ios'; +import NativeListIndicator from '~/containers/List/native/Indicator.ios'; import NativeListRow from '~/containers/NativeListRow'; import { PlainSeparator } from '~/containers/NativeListRow/Separator'; import I18n from '~/i18n'; @@ -15,8 +14,6 @@ interface IButton { isLast?: boolean; } -const CHEVRON = I18nManager.isRTL ? 'chevron-left' : 'chevron-right'; - const ButtonCreate = ({ onPress, testID, title, icon, isFirst, isLast }: IButton) => { const { colors } = useTheme(); const translatedTitle = I18n.t(title); @@ -31,7 +28,7 @@ const ButtonCreate = ({ onPress, testID, title, icon, isFirst, isLast }: IButton isFirst={isFirst} isLast={isLast} leading={} - trailing={} + trailing={} /> {isLast ? null : } diff --git a/app/views/NotificationPreferencesView/index.tsx b/app/views/NotificationPreferencesView/index.tsx index 437b15ee7e0..c60872f819f 100644 --- a/app/views/NotificationPreferencesView/index.tsx +++ b/app/views/NotificationPreferencesView/index.tsx @@ -162,7 +162,7 @@ const NotificationPreferencesView = (): ReactElement => { }; return ( - + diff --git a/app/views/PushTroubleshootView/components/CustomListSection.tsx b/app/views/PushTroubleshootView/components/CustomListSection.tsx deleted file mode 100644 index 4707fbd7074..00000000000 --- a/app/views/PushTroubleshootView/components/CustomListSection.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { StyleSheet, useWindowDimensions, View } from 'react-native'; -import { type ReactElement } from 'react'; - -import { Header } from '~/containers/List'; - -const styles = StyleSheet.create({ - container: { - marginBottom: 16 - }, - headerContainer: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center' - }, - statusContainer: { - marginRight: 12 - } -}); - -interface ICustomListSection { - children: (ReactElement | null)[] | ReactElement | null; - title: string; - translateTitle?: boolean; - statusColor?: string; -} - -const CustomHeader = ({ - title, - translateTitle, - statusColor -}: { - title: string; - translateTitle?: boolean; - statusColor?: string; -}) => { - const { fontScale } = useWindowDimensions(); - const statusSize = { - width: 10 * fontScale, - height: 10 * fontScale, - borderRadius: 5 * fontScale - }; - return ( - -
- {statusColor ? : null} - - ); -}; - -const CustomListSection = ({ children, title, translateTitle, statusColor }: ICustomListSection) => ( - - {title ? : null} - {children} - -); - -export default CustomListSection; diff --git a/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx b/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx index c1ab52d07e1..07a81cfb55b 100644 --- a/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx +++ b/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx @@ -6,9 +6,10 @@ import i18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { isIOS, showErrorAlert } from '~/lib/methods/helpers'; import { useTheme } from '~/theme'; -import CustomListSection from './CustomListSection'; +import { CustomIcon } from '~/containers/CustomIcon'; +import { asNativeListSection } from '~/containers/List/native/rowMarkers'; -export default function DeviceNotificationSettings(): ReactElement { +function DeviceNotificationSettings(): ReactElement { const { colors } = useTheme(); const { deviceNotificationEnabled } = useAppSelector(state => ({ deviceNotificationEnabled: state.troubleshootingNotification.deviceNotificationEnabled @@ -32,17 +33,24 @@ export default function DeviceNotificationSettings(): ReactElement { }; return ( - + ( + + )} /> - + ); } + +export default asNativeListSection(DeviceNotificationSettings); diff --git a/app/views/PushTroubleshootView/components/PushGatewayConnection.tsx b/app/views/PushTroubleshootView/components/PushGatewayConnection.tsx index f688fe57ff1..17d04d2dede 100644 --- a/app/views/PushTroubleshootView/components/PushGatewayConnection.tsx +++ b/app/views/PushTroubleshootView/components/PushGatewayConnection.tsx @@ -8,9 +8,10 @@ import { usePermissions } from '~/lib/hooks/usePermissions'; import { compareServerVersion, showErrorAlertWithEMessage } from '~/lib/methods/helpers'; import { pushTest } from '~/lib/services/restApi'; import { useTheme } from '~/theme'; -import CustomListSection from './CustomListSection'; +import { CustomIcon } from '~/containers/CustomIcon'; +import { asNativeListSection } from '~/containers/List/native/rowMarkers'; -export default function PushGatewayConnection(): ReactElement | null { +function PushGatewayConnection(): ReactElement | null { const [loading, setLoading] = useState(false); const { colors } = useTheme(); const [testPushNotificationsPermission] = usePermissions(['test-push-notifications']); @@ -48,18 +49,19 @@ export default function PushGatewayConnection(): ReactElement | null { } return ( - + } /> - + ); } + +export default asNativeListSection(PushGatewayConnection); diff --git a/app/views/PushTroubleshootView/index.tsx b/app/views/PushTroubleshootView/index.tsx index 6e2d38fccec..e49964b3332 100644 --- a/app/views/PushTroubleshootView/index.tsx +++ b/app/views/PushTroubleshootView/index.tsx @@ -33,7 +33,7 @@ const PushTroubleshootView = ({ navigation }: IPushTroubleshootViewProps) => { }, [navigation]); return ( - + {/* */} diff --git a/app/views/RoomView/hooks/useNativeBackButton.ts b/app/views/RoomView/hooks/useNativeBackButton.ts index 6208b48f4b1..0b5c8a620a5 100644 --- a/app/views/RoomView/hooks/useNativeBackButton.ts +++ b/app/views/RoomView/hooks/useNativeBackButton.ts @@ -3,6 +3,7 @@ import { useNavigation } from '@react-navigation/native'; import { type NativeStackHeaderItem } from '@react-navigation/native-stack'; import i18n from '~/i18n'; +import { useTheme } from '~/theme'; import { type IRoomViewProps } from '../definitions'; import { useUnreadsCount } from './useUnreadsCount'; @@ -15,6 +16,7 @@ export const formatUnreadsCount = (unreadsCount: number | null) => { export const useNativeBackButton = (enabled: boolean, rid?: string) => { const navigation = useNavigation(); + const { colors } = useTheme(); const unreadsLabel = formatUnreadsCount(useUnreadsCount(enabled ? rid : undefined)); useLayoutEffect(() => { @@ -26,9 +28,10 @@ export const useNativeBackButton = (enabled: boolean, rid?: string) => { label: unreadsLabel, icon: { type: 'sfSymbol', name: 'chevron.backward' }, showsLabelWithIcon: true, + tintColor: colors.fontDefault, accessibilityLabel: unreadsLabel ? `${i18n.t('Back')}, ${unreadsLabel} ${i18n.t('Unread')}` : i18n.t('Back'), onPress: () => navigation.goBack() }; navigation.setOptions({ headerBackVisible: false, unstable_headerLeftItems: () => [backItem] }); - }, [enabled, navigation, unreadsLabel]); + }, [colors.fontDefault, enabled, navigation, unreadsLabel]); }; diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 6b80d8580e9..53bae02eb34 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -108,7 +108,6 @@ describe('RoomsListView useHeader', () => { expect(typeof options.headerTitle).toBe('string'); expect(options.headerTitle).toBe('Rocket.Chat'); expect(options.headerSubtitle).toBe('open.rocket.chat'); - expect(options.headerLargeTitle).toBe(true); }); it.each([ @@ -273,7 +272,6 @@ describe('RoomsListView useHeader', () => { renderUseHeader(); const options = mockSetOptions.mock.calls[0][0]; - expect(options.headerLargeTitle).toBe(true); expect(options.headerTitle).toBe('Rocket.Chat'); }); }); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index 4443e421a9b..e14ef79c860 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -193,10 +193,8 @@ export const useHeader = () => { ).filter(action => action.present); navigation.setOptions({ - headerLargeTitle: true, headerTransparent: true, headerStyle: { backgroundColor: `${colors.surfaceNeutral}B3` }, - headerLargeStyle: { backgroundColor: 'transparent' }, headerBlurEffect: 'regular', headerTitle: serverName, headerSubtitle: nativeHeaderSubtitle, diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index 697eb0b1949..6aa0f4dde25 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -199,7 +199,6 @@ class ShareListView extends Component { if (hasNativeHeaderBar) { navigation.setOptions({ - headerLargeTitle: true, headerLeft: () => ( diff --git a/app/views/StatusView/GlassBackground.ios.tsx b/app/views/StatusView/GlassBackground.ios.tsx deleted file mode 100644 index 22e182330f6..00000000000 --- a/app/views/StatusView/GlassBackground.ios.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Platform, StyleSheet } from 'react-native'; -import { Host } from '@expo/ui'; -import { Capsule } from '@expo/ui/swift-ui'; -import { foregroundStyle, glassEffect } from '@expo/ui/swift-ui/modifiers'; - -import { useTheme } from '~/theme'; - -export const supportsLiquidGlass = Number(Platform.Version) >= 26; - -const capsuleModifiers = [ - foregroundStyle('clear'), - glassEffect({ glass: { variant: 'regular', interactive: true }, shape: 'capsule' }) -]; - -const styles = StyleSheet.create({ - background: { - ...StyleSheet.absoluteFill, - zIndex: -1 - } -}); - -export const GlassBackground = () => { - const { theme } = useTheme(); - return ( - - - - ); -}; diff --git a/app/views/StatusView/GlassBackground.tsx b/app/views/StatusView/GlassBackground.tsx deleted file mode 100644 index 808cc0d6d6b..00000000000 --- a/app/views/StatusView/GlassBackground.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export const supportsLiquidGlass = false; - -export const GlassBackground = () => null; diff --git a/app/views/StatusView/index.tsx b/app/views/StatusView/index.tsx index 9bd6664e9fc..1dae150af5d 100644 --- a/app/views/StatusView/index.tsx +++ b/app/views/StatusView/index.tsx @@ -28,7 +28,6 @@ import { USER_STATUS_TEXT_MAX_LENGTH } from '~/lib/constants/maxLength'; import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; import ClearAfterPicker, { type ClearAfterValue, computeExpiresAt, getInitialClearAfterState } from './ClearAfterPicker'; import FooterComponent from './FooterComponent'; -import { GlassBackground, supportsLiquidGlass } from './GlassBackground'; const validationSchema = yup.object().shape({ statusText: yup @@ -71,9 +70,11 @@ const styles = StyleSheet.create({ borderTopWidth: 1, borderBottomWidth: 1 }, - glassInputStyle: { + rowInputContainer: { + marginBottom: 0 + }, + rowInputStyle: { borderWidth: 0, - borderRadius: 999, backgroundColor: 'transparent' } }); @@ -195,8 +196,20 @@ const StatusView = (): ReactElement => { label={I18n.t('Status')} value={statusText} containerStyle={styles.inputContainer} - inputStyle={supportsLiquidGlass ? styles.glassInputStyle : styles.inputStyle} - left={supportsLiquidGlass ? : undefined} + inputStyle={styles.inputStyle} + testID='status-view-input' + error={errors.statusText?.message} + /> + ); + + const statusRowInput = ( + @@ -227,9 +240,9 @@ const StatusView = (): ReactElement => { if (isIOS) { return ( - - {statusInput} + + {statusRowInput} {statusType.map(status => ( {renderStatus(status)} diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 5167198368d..08f1b5b6594 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -233,7 +233,6 @@ class TeamChannelsView extends Component Date: Fri, 25 Sep 2026 17:55:25 -0300 Subject: [PATCH 097/120] chore(ios): update Xcode project --- ios/RocketChat.xcodeproj/project.pbxproj | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ios/RocketChat.xcodeproj/project.pbxproj b/ios/RocketChat.xcodeproj/project.pbxproj index 3dfbd7ab355..0737dba7ad7 100644 --- a/ios/RocketChat.xcodeproj/project.pbxproj +++ b/ios/RocketChat.xcodeproj/project.pbxproj @@ -143,6 +143,7 @@ 65B9A71B2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; 66C2701C2EBBCB570062725F /* MMKVKeyManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 66C2701A2EBBCB570062725F /* MMKVKeyManager.mm */; }; 66C2701D2EBBCB570062725F /* MMKVKeyManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 66C2701A2EBBCB570062725F /* MMKVKeyManager.mm */; }; + 6F204E445C3D44699793D4F8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680FA18DB475479CAD48F334 /* SceneDelegate.swift */; }; 6FA7E2392F28A9D300A1D45E /* ExternalInputModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FA7E2372F28A9D300A1D45E /* ExternalInputModule.m */; }; 782E0520ED599FF8DDC2318F /* Pods_defaults_Rocket_Chat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88ED4E003393D3A9256F4EB3 /* Pods_defaults_Rocket_Chat.framework */; }; 79D8C97F8CE2EC1B6882826B /* SecureStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B215A42CFB843397273C7EA /* SecureStorage.m */; }; @@ -154,8 +155,6 @@ 7A0129EA2C6E921600F84A97 /* MainInterface.storyboard in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6ACB522CB9FC300A41C61 /* MainInterface.storyboard */; }; 7A14FCF4257FEB59005BDCD4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A14FCF3257FEB59005BDCD4 /* Assets.xcassets */; }; 7A1B58412F5F58FF002A6BDE /* VoipPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B58402F5F58FF002A6BDE /* VoipPayload.swift */; }; - A1C0E0022F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; - A1C0E0032F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; 7A1B58452F5F63DB002A6BDE /* AppDelegate+Voip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B58432F5F63DB002A6BDE /* AppDelegate+Voip.swift */; }; 7A3704572F7DB36E009085FC /* VoipPerCallDdpRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3704552F7DB36E009085FC /* VoipPerCallDdpRegistry.swift */; }; 7A37D6FF2F896C360095EBA1 /* MediaCallsAnswerRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83F98EE33D91A93DF8E69F34 /* MediaCallsAnswerRequest.swift */; }; @@ -213,10 +212,11 @@ 7AC0A1E02C000011001CAA11 /* Challenge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0A1E02C000002001CAA11 /* Challenge.mm */; }; 7AC0A1E02C000012001CAA11 /* Challenge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0A1E02C000002001CAA11 /* Challenge.mm */; }; 7ACFE7D92DDE48760090D9BC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACFE7D82DDE48760090D9BC /* AppDelegate.swift */; }; - 6F204E445C3D44699793D4F8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680FA18DB475479CAD48F334 /* SceneDelegate.swift */; }; 7AE10C0828A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 7AVR00022F5F5900002A6BDE /* VoipRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AVR00002F5F5900002A6BDE /* VoipRegion.swift */; }; 85160EB6C143E0493FE5F014 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */; }; + A1C0E0022F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; + A1C0E0032F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; A48B46DA2D3FFBD200945489 /* A11yFlowModule.m in Sources */ = {isa = PBXBuildFile; fileRef = A48B46D82D3FFBD200945489 /* A11yFlowModule.m */; }; B1E1F0022F03A00000EXT002 /* ExternalInputModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B1E1F0042F03A00000EXT004 /* ExternalInputModule.m */; }; B1E1F0062F03A00000EXT006 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1E1F0072F03A00000EXT007 /* GameController.framework */; }; @@ -431,6 +431,7 @@ 65B9A7192AFC24190088956F /* ringtone.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringtone.mp3; sourceTree = ""; }; 66C270192EBBCB570062725F /* MMKVKeyManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMKVKeyManager.h; sourceTree = ""; }; 66C2701A2EBBCB570062725F /* MMKVKeyManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MMKVKeyManager.mm; sourceTree = ""; }; + 680FA18DB475479CAD48F334 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 6FA7E2362F28A9D300A1D45E /* ExternalInputModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExternalInputModule.h; sourceTree = ""; }; 6FA7E2372F28A9D300A1D45E /* ExternalInputModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExternalInputModule.m; sourceTree = ""; }; 7A0000032F1BAFA700B6B4BD /* VoipService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipService.swift; sourceTree = ""; }; @@ -440,7 +441,6 @@ 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 7A14FCF3257FEB59005BDCD4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7A1B58402F5F58FF002A6BDE /* VoipPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipPayload.swift; sourceTree = ""; }; - A1C0E0012F90000000C0FFEE /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; 7A1B58432F5F63DB002A6BDE /* AppDelegate+Voip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Voip.swift"; sourceTree = ""; }; 7A3704552F7DB36E009085FC /* VoipPerCallDdpRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipPerCallDdpRegistry.swift; sourceTree = ""; }; 7A610CD127ECE38100B8ABDD /* custom.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = custom.ttf; sourceTree = ""; }; @@ -454,7 +454,6 @@ 7AC0A1E02C000002001CAA11 /* Challenge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Challenge.mm; sourceTree = ""; }; 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 7ACFE7D82DDE48760090D9BC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 680FA18DB475479CAD48F334 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 7AE10C0528A59530003593CB /* Inter.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Inter.ttf; sourceTree = ""; }; 7AVR00002F5F5900002A6BDE /* VoipRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipRegion.swift; sourceTree = ""; }; 83F98EE33D91A93DF8E69F34 /* MediaCallsAnswerRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaCallsAnswerRequest.swift; sourceTree = ""; }; @@ -464,6 +463,7 @@ 9BE2F3DC02A264F204E3EDE3 /* DDPClient.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DDPClient.swift; sourceTree = ""; }; 9E19E897C464DE3A91E1DAFD /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = ""; }; 9F66BEF98C5CF2CFDD4B4C87 /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = ""; }; + A1C0E0012F90000000C0FFEE /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; A48B46D72D3FFBD200945489 /* A11yFlowModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = A11yFlowModule.h; sourceTree = ""; }; A48B46D82D3FFBD200945489 /* A11yFlowModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = A11yFlowModule.m; sourceTree = ""; }; B179038FDD7AAF285047814B /* SecureStorage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = SecureStorage.h; sourceTree = ""; }; @@ -2384,7 +2384,10 @@ "$(inherited)", "-DRCT_REMOVE_LEGACY_ARCH=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -2472,7 +2475,10 @@ "$(inherited)", "-DRCT_REMOVE_LEGACY_ARCH=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; From 394166389a41807ba6e96ce007f122d02771a7cb Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 18:07:06 -0300 Subject: [PATCH 098/120] fix(ios): match room back chevron weight to the system back button --- patches/react-native-screens+4.28.0.patch | 24 ++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch index 1ad237f86a6..70329e2af5b 100644 --- a/patches/react-native-screens+4.28.0.patch +++ b/patches/react-native-screens+4.28.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt -index fa1b773..41866aa 100644 +index fa1b77317f..41866aa0ee 100644 --- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt @@ -3,6 +3,7 @@ package com.swmansion.rnscreens @@ -43,7 +43,7 @@ index fa1b773..41866aa 100644 view: ScreenStackHeaderConfig?, value: String?, diff --git a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm -index 64beb49..14bb54a 100644 +index 64beb499a6..14bb54aa48 100644 --- a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm +++ b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm @@ -13,6 +13,15 @@ @@ -79,10 +79,10 @@ index 64beb49..14bb54a 100644 size:imageSource.size scale:imageSource.scale diff --git a/node_modules/react-native-screens/ios/RNSBarButtonItem.mm b/node_modules/react-native-screens/ios/RNSBarButtonItem.mm -index 76a39d4..fc43d6c 100644 +index 76a39d4df4..160a373016 100644 --- a/node_modules/react-native-screens/ios/RNSBarButtonItem.mm +++ b/node_modules/react-native-screens/ios/RNSBarButtonItem.mm -@@ -126,10 +126,32 @@ static UIMenuOptions RNSMakeUIMenuOptionsFromConfig(NSDictionary *config); +@@ -126,10 +126,34 @@ static UIMenuOptions RNSMakeUIMenuOptionsFromConfig(NSDictionary *config); self.action = @selector(handleBarButtonItemPress:); _itemAction = action; _buttonId = buttonId; @@ -102,8 +102,10 @@ index 76a39d4..fc43d6c 100644 + if (self.tintColor != nil) { + configuration.baseForegroundColor = self.tintColor; + } -+ configuration.preferredSymbolConfigurationForImage = -+ [UIImageSymbolConfiguration configurationWithTextStyle:UIFontTextStyleBody scale:UIImageSymbolScaleLarge]; ++ configuration.preferredSymbolConfigurationForImage = [[UIImageSymbolConfiguration ++ configurationWithTextStyle:UIFontTextStyleBody ++ scale:UIImageSymbolScaleLarge] ++ configurationByApplyingConfiguration:[UIImageSymbolConfiguration configurationWithWeight:UIImageSymbolWeightSemibold]]; + UIButton *button = [UIButton buttonWithConfiguration:configuration primaryAction:nil]; + button.automaticallyUpdatesConfiguration = NO; + [button addTarget:self action:@selector(handleBarButtonItemPress:) forControlEvents:UIControlEventPrimaryActionTriggered]; @@ -116,7 +118,7 @@ index 76a39d4..fc43d6c 100644 imageLoader:(RCTImageLoader *)imageLoader completionBlock:(void (^)(UIImage *_Nullable))completionBlock diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h -index 8a41697..c6f3c20 100644 +index 8a41697223..c6f3c20ce4 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h +++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h @@ -8,10 +8,14 @@ @@ -151,7 +153,7 @@ index 8a41697..c6f3c20 100644 @property (nonatomic, retain) NSString *backTitleFontFamily; @property (nonatomic, retain) NSNumber *backTitleFontSize; diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm -index a54cb63..592a6c2 100644 +index a54cb632e7..592a6c2fbd 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm @@ -1,4 +1,5 @@ @@ -860,7 +862,7 @@ index a54cb63..592a6c2 100644 _color = RCTUIColorFromSharedColor(newScreenProps.color); _backgroundColor = RCTUIColorFromSharedColor(newScreenProps.backgroundColor); diff --git a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts -index da28c51..0803e9e 100644 +index da28c51c6f..0803e9e879 100644 --- a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts +++ b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts @@ -4,6 +4,7 @@ import { codegenNativeComponent } from 'react-native'; @@ -897,7 +899,7 @@ index da28c51..0803e9e 100644 backButtonDisplayMode?: CT.WithDefault; hideBackButton?: boolean | undefined; diff --git a/node_modules/react-native-screens/src/types.tsx b/node_modules/react-native-screens/src/types.tsx -index 8594b73..5afaf41 100644 +index 8594b733fa..5afaf41504 100644 --- a/node_modules/react-native-screens/src/types.tsx +++ b/node_modules/react-native-screens/src/types.tsx @@ -827,10 +827,65 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { @@ -967,7 +969,7 @@ index 8594b73..5afaf41 100644 * Customize font family to be used for the title. */ diff --git a/node_modules/react-native-screens/ios/RNSScreenStack.mm b/node_modules/react-native-screens/ios/RNSScreenStack.mm -index c233036..5d3e28d 100644 +index c233036b56..5d3e28d3d6 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStack.mm +++ b/node_modules/react-native-screens/ios/RNSScreenStack.mm @@ -38,6 +38,58 @@ namespace react = facebook::react; From b32094895fd695d6a7a3690d393feea82b31f78d Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 25 Sep 2026 18:22:44 -0300 Subject: [PATCH 099/120] ci: add preloadNativeHeaderIcons saga to test map --- .sniffler/config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.sniffler/config.json b/.sniffler/config.json index f7726236c8e..0fc3c6ca471 100644 --- a/.sniffler/config.json +++ b/.sniffler/config.json @@ -28,6 +28,7 @@ "app/lib/methods/getUsersPresence.ts", "app/sagas/index.js", "app/sagas/init.js", + "app/sagas/preloadNativeHeaderIcons.js", "app/lib/methods/loggedInServer.ts", "app/sagas/state.js", "app/sagas/selectServer.ts", From a271f00335eb9af06686d98dc6412d2afc5d071f Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 28 Sep 2026 10:32:14 -0300 Subject: [PATCH 100/120] perf(ios): render directory with FlashList and a React Native list row --- .../__snapshots__/DirectoryItem.test.tsx.snap | 2703 ++++++++--------- app/containers/DirectoryItem/index.ios.tsx | 6 +- app/containers/DirectoryItem/styles.ts | 6 + .../NativeListRow/Disclosure.ios.tsx | 11 + app/containers/NativeListRow/index.tsx | 163 +- app/views/DirectoryView/DirectoryRow.tsx | 78 + app/views/DirectoryView/index.tsx | 140 +- app/views/NewMessageView/ButtonCreate.ios.tsx | 4 +- app/views/NewMessageView/Item.test.tsx | 8 +- .../__snapshots__/Item.test.tsx.snap | 1292 ++++---- .../components/ActionsSection.ios.tsx | 8 +- package.json | 1 + pnpm-lock.yaml | 16 + 13 files changed, 2244 insertions(+), 2192 deletions(-) create mode 100644 app/containers/NativeListRow/Disclosure.ios.tsx create mode 100644 app/views/DirectoryView/DirectoryRow.tsx diff --git a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap index 30a6d630432..897f0520962 100644 --- a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap +++ b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap @@ -14,6 +14,7 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -23,226 +24,211 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - -  - - - - - - - - - - - + ], + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + Styled Room + + + + Custom style applied + + + + @@ -262,6 +248,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -271,226 +258,211 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - -  - - - - - - - - - - - + ], + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + General + + + + A public room + + + + @@ -510,6 +482,7 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -519,185 +492,178 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - - - - - - + Alice Johnson + + + + Hey there! + + + + @@ -717,6 +683,7 @@ exports[`Story Snapshots: LongRoomName should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -726,254 +693,236 @@ exports[`Story Snapshots: LongRoomName should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - -  - - - - - - - - - - 9999 members - - - - - - +  + + + This is a very very very very very very very long room name that should be truncated + + + + Long name test + + + + 9999 members + + + @@ -993,6 +942,7 @@ exports[`Story Snapshots: OnlyTitle should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -1002,144 +952,139 @@ exports[`Story Snapshots: OnlyTitle should match snapshot 1`] = ` ] } > - - - - - - - - - -  - - - - - - - - - - + ], + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + Just a title + + + + + @@ -1159,6 +1104,7 @@ exports[`Story Snapshots: TeamMain should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -1168,226 +1114,211 @@ exports[`Story Snapshots: TeamMain should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - -  - - - - - - - - - - - + ], + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + Engineering Team + + + + Main team room + + + + @@ -1407,6 +1338,7 @@ exports[`Story Snapshots: WithRightLabel should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -1416,254 +1348,236 @@ exports[`Story Snapshots: WithRightLabel should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - -  - - - - - - - - - - 123 members - - - - - - +  + + + General + + + + Room with label + + + + 123 members + + + @@ -1683,6 +1597,7 @@ exports[`Story Snapshots: WithoutDescription should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -1692,202 +1607,196 @@ exports[`Story Snapshots: WithoutDescription should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - -  - - - - - - - - - - + ], + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + No description room + + + + + diff --git a/app/containers/DirectoryItem/index.ios.tsx b/app/containers/DirectoryItem/index.ios.tsx index dd34d9eb901..cc1492c1c51 100644 --- a/app/containers/DirectoryItem/index.ios.tsx +++ b/app/containers/DirectoryItem/index.ios.tsx @@ -51,10 +51,12 @@ const DirectoryItem = ({ isFirst={isFirst} isLast={isLast} leading={} - titleLeading={type !== 'd' ? : undefined} + titleLeading={type !== 'd' ? : undefined} trailing={ rightLabel ? ( - {rightLabel} + + {rightLabel} + ) : undefined } /> diff --git a/app/containers/DirectoryItem/styles.ts b/app/containers/DirectoryItem/styles.ts index 23298b6f5c8..8f7af1781c0 100644 --- a/app/containers/DirectoryItem/styles.ts +++ b/app/containers/DirectoryItem/styles.ts @@ -31,6 +31,12 @@ export default StyleSheet.create({ fontSize: 14, ...sharedStyles.textRegular }, + titleIcon: { + marginRight: 0 + }, + nativeRowLabel: { + marginTop: StyleSheet.hairlineWidth + }, directoryItemLabel: { fontSize: 14, paddingLeft: 10, diff --git a/app/containers/NativeListRow/Disclosure.ios.tsx b/app/containers/NativeListRow/Disclosure.ios.tsx new file mode 100644 index 00000000000..aecd8926d2b --- /dev/null +++ b/app/containers/NativeListRow/Disclosure.ios.tsx @@ -0,0 +1,11 @@ +import { Host } from '@expo/ui/swift-ui'; + +import NativeListIndicator from '~/containers/List/native/Indicator.ios'; + +const Disclosure = () => ( + + + +); + +export default Disclosure; diff --git a/app/containers/NativeListRow/index.tsx b/app/containers/NativeListRow/index.tsx index 78656289d2b..b2170946b7c 100644 --- a/app/containers/NativeListRow/index.tsx +++ b/app/containers/NativeListRow/index.tsx @@ -1,29 +1,18 @@ import { type ReactElement, type ReactNode } from 'react'; -import { PixelRatio, StyleSheet, View } from 'react-native'; -import { Host, ListItem } from '@expo/ui'; -import { Button, HStack, RNHostView, Text } from '@expo/ui/swift-ui'; -import { - accessibilityAddTraits, - accessibilityLabel as accessibilityLabelModifier, - buttonStyle, - disabled as disabledModifier, - font, - foregroundStyle, - lineLimit, - onLongPressGesture, - opacity, - padding -} from '@expo/ui/swift-ui/modifiers'; +import { PixelRatio, Pressable, StyleSheet, Text, View } from 'react-native'; -import { type TIconsName } from '~/containers/CustomIcon'; -import NativeListIcon from '~/containers/List/native/Icon.ios'; +import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; import { useTheme } from '~/theme'; import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; import { CONTENT_SPACING, ROW_HEIGHT, ROW_MARGIN_HORIZONTAL, ROW_PADDING_HORIZONTAL, ROW_RADIUS } from './constants'; +const TRAILING_ACTION_HIT_SLOP = 12; +const SPACER_MIN_LENGTH = 8; + const styles = StyleSheet.create({ card: { - marginHorizontal: ROW_MARGIN_HORIZONTAL + marginHorizontal: ROW_MARGIN_HORIZONTAL, + overflow: 'hidden' }, firstCard: { borderTopLeftRadius: ROW_RADIUS, @@ -33,8 +22,41 @@ const styles = StyleSheet.create({ borderBottomLeftRadius: ROW_RADIUS, borderBottomRightRadius: ROW_RADIUS }, - host: { - width: '100%' + row: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: ROW_PADDING_HORIZONTAL, + gap: CONTENT_SPACING + }, + pressable: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + alignSelf: 'stretch', + gap: CONTENT_SPACING + }, + pressed: { + opacity: 0.75 + }, + disabled: { + opacity: 0.3 + }, + texts: { + flex: 1, + marginRight: SPACER_MIN_LENGTH + CONTENT_SPACING + }, + titleRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 4 + }, + title: { + flexShrink: 1, + fontSize: 17, + fontWeight: '500' + }, + subtitle: { + fontSize: 15 } }); @@ -79,65 +101,58 @@ const NativeListRow = ({ isLast, disabled }: INativeListRow) => { - const { colors, theme } = useTheme(); + const { colors } = useTheme(); const { fontScale } = useResponsiveLayout(); const height = PixelRatio.roundToNearestPixel(ROW_HEIGHT * fontScale); - const rowModifiers = [ - accessibilityLabelModifier(accessibilityLabel), - ...(isSelected ? [accessibilityAddTraits(['isSelected'])] : []), - ...(onLongPress ? [onLongPressGesture(onLongPress)] : []), - ...(disabled ? [disabledModifier(true), opacity(0.3)] : []) - ]; - const titleText = ( - - {title} - - ); return ( - - - - {subtitle} - - ) : undefined - }> - {titleLeading ? ( - - {titleLeading} - {titleText} - - ) : ( - titleText - )} - - {trailingAction ? ( - - ) : null} - - + + {({ pressed }) => ( + <> + + {leading} + + + {titleLeading} + + {title} + + + {subtitle ? ( + + {subtitle} + + ) : null} + + {trailing} + + {trailingAction ? ( + + + + ) : null} + + )} + ); }; diff --git a/app/views/DirectoryView/DirectoryRow.tsx b/app/views/DirectoryView/DirectoryRow.tsx new file mode 100644 index 00000000000..2f0633583d5 --- /dev/null +++ b/app/views/DirectoryView/DirectoryRow.tsx @@ -0,0 +1,78 @@ +import { memo } from 'react'; + +import DirectoryItem from '~/containers/DirectoryItem'; +import I18n from '~/i18n'; +import { type IServerRoom } from '~/definitions'; +import { useTheme } from '~/theme'; +import sharedStyles from '../Styles'; + +interface IDirectoryRow { + item: IServerRoom; + type: string; + isFirst: boolean; + isLast: boolean; + onPressItem: (item: IServerRoom) => void; +} + +const DirectoryRow = ({ item, type, isFirst, isLast, onPressItem }: IDirectoryRow) => { + const { colors } = useTheme(); + const title = item.name as string; + const onPress = () => onPressItem(item); + const testID = `directory-view-item-${item.name}`; + const style = isLast ? { ...sharedStyles.separatorBottom, borderColor: colors.strokeLight } : undefined; + + if (type === 'users') { + return ( + + ); + } + + if (type === 'teams') { + return ( + + ); + } + + return ( + + ); +}; + +export default memo(DirectoryRow); diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index 1bae02a84ed..744c2f6ec5e 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -1,5 +1,5 @@ -import { useLayoutEffect, type ReactElement } from 'react'; -import { FlatList, type ListRenderItem } from 'react-native'; +import { useCallback, useLayoutEffect, type ReactElement } from 'react'; +import { FlashList, type ListRenderItem } from '@shopify/flash-list'; import { shallowEqual } from 'react-redux'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { @@ -12,8 +12,6 @@ import { type CompositeNavigationProp } from '@react-navigation/native'; import { useActionSheet } from '~/containers/ActionSheet'; import { type ChatsStackParamList } from '~/stacks/types'; import { type MasterDetailInsideStackParamList } from '~/stacks/MasterDetailStack/types'; -import DirectoryItem from '~/containers/DirectoryItem'; -import sharedStyles from '../Styles'; import I18n from '~/i18n'; import SearchBox from '~/containers/SearchBox'; import ActivityIndicator from '~/containers/ActivityIndicator'; @@ -24,6 +22,7 @@ import { goRoom as goRoomMethod, type TGoRoomItem } from '~/lib/methods/helpers/ import { type IServerRoom, SubscriptionType } from '~/definitions'; import styles from './styles'; import Options from './Options'; +import DirectoryRow from './DirectoryRow'; import { getRoomByTypeAndName } from '~/lib/services/restApi'; import { createDirectMessage } from '~/lib/methods/createDirectMessage'; import { getSubscriptionByRoomId } from '~/lib/database/services/Subscription'; @@ -164,103 +163,55 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { search ]); - const goRoom = (item: TGoRoomItem) => { - goRoomMethod({ item, isMasterDetail }); - }; - - const onPressItem = async (item: IServerRoom) => { - try { - if (type === 'users') { - const result = await createDirectMessage(item.username as string); - if (result.success) { - goRoom({ rid: result.room._id, name: item.username, t: SubscriptionType.DIRECT }); + const onPressItem = useCallback( + async (item: IServerRoom) => { + const goRoom = (goRoomItem: TGoRoomItem) => { + goRoomMethod({ item: goRoomItem, isMasterDetail }); + }; + try { + if (type === 'users') { + const result = await createDirectMessage(item.username as string); + if (result.success) { + goRoom({ rid: result.room._id, name: item.username, t: SubscriptionType.DIRECT }); + } + return; } - return; - } - const subscription = await getSubscriptionByRoomId(item._id); - if (subscription) { - goRoom(subscription); - return; - } - if (['p', 'c'].includes(item.t) && !item.teamMain) { - const result = await getRoomByTypeAndName(item.t, item.name || item.fname); - if (result) { + const subscription = await getSubscriptionByRoomId(item._id); + if (subscription) { + goRoom(subscription); + return; + } + if (['p', 'c'].includes(item.t) && !item.teamMain) { + const result = await getRoomByTypeAndName(item.t, item.name || item.fname); + if (result) { + goRoom({ + rid: item._id, + name: item.name, + joinCodeRequired: result.joinCodeRequired, + t: item.t as SubscriptionType, + search: true + }); + } + } else { goRoom({ rid: item._id, name: item.name, - joinCodeRequired: result.joinCodeRequired, t: item.t as SubscriptionType, - search: true + search: true, + teamMain: item.teamMain, + teamId: item.teamId }); } - } else { - goRoom({ - rid: item._id, - name: item.name, - t: item.t as SubscriptionType, - search: true, - teamMain: item.teamMain, - teamId: item.teamId - }); + } catch { + // do nothing } - } catch { - // do nothing - } - }; - - const renderItem: ListRenderItem = ({ item, index }) => { - let style; - if (index === data.length - 1) { - style = { - ...sharedStyles.separatorBottom, - borderColor: colors.strokeLight - }; - } - - const commonProps = { - title: item.name as string, - onPress: () => onPressItem(item), - testID: `directory-view-item-${item.name}`, - style, - rid: item._id, - isFirst: index === 0, - isLast: index === data.length - 1 - }; - - if (type === 'users') { - return ( - - ); - } + }, + [type, isMasterDetail] + ); - if (type === 'teams') { - return ( - - ); - } - return ( - - ); - }; + const renderItem: ListRenderItem = ({ item, index }) => ( + + ); return ( @@ -268,7 +219,7 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { )} - { ItemSeparatorComponent={RowSeparator} keyboardShouldPersistTaps='always' ListFooterComponent={loading ? : null} - onEndReached={() => loadMore()} + onEndReached={loadMore} + onEndReachedThreshold={2} /> ); diff --git a/app/views/NewMessageView/ButtonCreate.ios.tsx b/app/views/NewMessageView/ButtonCreate.ios.tsx index 44ea8ea843d..ac4ae361383 100644 --- a/app/views/NewMessageView/ButtonCreate.ios.tsx +++ b/app/views/NewMessageView/ButtonCreate.ios.tsx @@ -1,6 +1,6 @@ import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; -import NativeListIndicator from '~/containers/List/native/Indicator.ios'; import NativeListRow from '~/containers/NativeListRow'; +import Disclosure from '~/containers/NativeListRow/Disclosure.ios'; import { PlainSeparator } from '~/containers/NativeListRow/Separator'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; @@ -28,7 +28,7 @@ const ButtonCreate = ({ onPress, testID, title, icon, isFirst, isLast }: IButton isFirst={isFirst} isLast={isLast} leading={} - trailing={} + trailing={} /> {isLast ? null : } diff --git a/app/views/NewMessageView/Item.test.tsx b/app/views/NewMessageView/Item.test.tsx index e766c121658..87ca0096029 100644 --- a/app/views/NewMessageView/Item.test.tsx +++ b/app/views/NewMessageView/Item.test.tsx @@ -58,12 +58,12 @@ describe('NewMessageView Item', () => { }); it('should display the name', () => { - const { UNSAFE_getByProps } = render( + const { getByText } = render( {}} testID='new-message-view-item-john.doe' /> ); - expect(UNSAFE_getByProps({ text: 'John Doe' })).toBeTruthy(); + expect(getByText('John Doe')).toBeTruthy(); }); it('should call onPress when main item is pressed', () => { @@ -150,9 +150,7 @@ describe('NewMessageView Item', () => { {}} testID='new-message-view-item-john.doe' /> ); - expect(getByTestId('new-message-view-item-john.doe').props.modifiers).toContainEqual( - expect.objectContaining({ $type: 'accessibilityLabel', label: 'John Doe' }) - ); + expect(getByTestId('new-message-view-item-john.doe')).toHaveAccessibleName('John Doe'); }); it('should match snapshot when hasMediaCallPermission is false', () => { diff --git a/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap b/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap index 7485a8313fb..c4bf5d4101d 100644 --- a/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap +++ b/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap @@ -6,6 +6,7 @@ exports[`NewMessageView Item should match snapshot when hasMediaCallPermission i [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -15,161 +16,163 @@ exports[`NewMessageView Item should match snapshot when hasMediaCallPermission i ] } > - - - - + + + - - - - - - - - - - - - - + John Doe + + + + + `; @@ -188,6 +191,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -197,209 +201,225 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - - - - + + + + + - - - - +  + + + `; @@ -419,6 +439,7 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -428,204 +449,225 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - - - - + + + + + - - - - +  + + + `; @@ -645,6 +687,7 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` [ { "marginHorizontal": 16, + "overflow": "hidden", }, { "backgroundColor": "#FFFFFF", @@ -654,204 +697,225 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` ] } > - - - - + + + - - - - - - - - - - - - + + + + + - - - - +  + + + `; diff --git a/app/views/RoomMembersView/components/ActionsSection.ios.tsx b/app/views/RoomMembersView/components/ActionsSection.ios.tsx index b22261f777f..24b502aa2a8 100644 --- a/app/views/RoomMembersView/components/ActionsSection.ios.tsx +++ b/app/views/RoomMembersView/components/ActionsSection.ios.tsx @@ -2,8 +2,8 @@ import { View } from 'react-native'; import { type ReactElement } from 'react'; import NativeListRow from '~/containers/NativeListRow'; -import NativeListIcon from '~/containers/List/native/Icon.ios'; -import NativeListIndicator from '~/containers/List/native/Indicator.ios'; +import { CustomIcon } from '~/containers/CustomIcon'; +import Disclosure from '~/containers/NativeListRow/Disclosure.ios'; import RowSeparator from '~/containers/NativeListRow/Separator'; import i18n from '~/i18n'; import { useTheme } from '~/theme'; @@ -30,8 +30,8 @@ export default function ActionsSection(props: IActionsSection): ReactElement | n disabled={action.disabled} isFirst={index === 0} isLast={index === actions.length - 1} - leading={} - trailing={} + leading={} + trailing={} /> ))} diff --git a/package.json b/package.json index e4ec368a696..7bbd24342df 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto#main", "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#ecf4fedcf8ab6121e62a6b95bb48823afd805afd", "@rocket.chat/ui-kit": "^0.39.0", + "@shopify/flash-list": "2.3.2", "@zoontek/react-native-navigation-bar": "^1.1.1", "axios": "0.30.3", "bytebuffer": "~5.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index adc0cb44141..470c9d1101a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,6 +105,9 @@ importers: '@rocket.chat/ui-kit': specifier: ^0.39.0 version: 0.39.0(@rocket.chat/icons@0.47.0)(@types/node@25.0.3)(typescript@7.0.2) + '@shopify/flash-list': + specifier: 2.3.2 + version: 2.3.2(@babel/runtime@7.25.9)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@zoontek/react-native-navigation-bar': specifier: ^1.1.1 version: 1.1.2(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -2976,6 +2979,13 @@ packages: '@samchon/openapi@4.7.2': resolution: {integrity: sha512-yj6kGWHtKK85wfJXrSmWqLWjfCd98SAvCTsVDlej2s7OfXXHqA4hmgPRNrAPIQRVi00xn26qL1PChjq1MhzlRQ==} + '@shopify/flash-list@2.3.2': + resolution: {integrity: sha512-vQnd0y0Ag11yzS30llaeCrtIU3xYTMe7KEOP3dveLImnVjQEUw6BEg1+Ztja6aODlVNz1BpvxtlQ5eZGxiLwKw==} + peerDependencies: + '@babel/runtime': '*' + react: '*' + react-native: '*' + '@sideway/address@4.1.5': resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} @@ -11437,6 +11447,12 @@ snapshots: '@samchon/openapi@4.7.2': {} + '@shopify/flash-list@2.3.2(@babel/runtime@7.25.9)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.25.9 + react: 19.2.3 + react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) + '@sideway/address@4.1.5': dependencies: '@hapi/hoek': 9.3.0 From c1ac40b98fcd1407c4e7778dbc1fd9867d6bcec2 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 28 Sep 2026 12:18:01 -0300 Subject: [PATCH 101/120] refactor(ios): split list row trailing action and build header items from one table --- .../__snapshots__/DirectoryItem.test.tsx.snap | 1722 ++++++++--------- app/containers/NativeListRow/index.tsx | 79 +- app/views/DirectoryView/DirectoryRow.tsx | 56 +- .../__snapshots__/Item.test.tsx.snap | 958 +++++---- .../RightButtons/useHeaderCallPress.ts | 3 - .../RightButtons/useRoomRightButtonsData.ts | 8 +- .../RoomView/hooks/useRoomHeaderRightItems.ts | 117 +- 7 files changed, 1402 insertions(+), 1541 deletions(-) diff --git a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap index 897f0520962..e3b93fff7d4 100644 --- a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap +++ b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap @@ -16,8 +16,15 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -60,173 +67,157 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="directory-item-style" > - - - + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> + + - - -  - - - Styled Room - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - Custom style applied + Styled Room + + Custom style applied + @@ -250,8 +241,15 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -294,173 +292,157 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="directory-item" > - - - + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> + + - - -  - - - General - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - A public room + General + + A public room + @@ -484,8 +466,15 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -528,140 +517,124 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="directory-item-dm" > - - - + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> + + - - - Alice Johnson - - - Hey there! + Alice Johnson + + Hey there! + @@ -685,8 +658,15 @@ exports[`Story Snapshots: LongRoomName should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -729,189 +709,148 @@ exports[`Story Snapshots: LongRoomName should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="directory-item-long" > - - - + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> + + - - -  - - - This is a very very very very very very very long room name that should be truncated - - - +  + + - Long name test + This is a very very very very very very very long room name that should be truncated - 9999 members + Long name test + + 9999 members + @@ -944,8 +908,15 @@ exports[`Story Snapshots: OnlyTitle should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -988,100 +959,84 @@ exports[`Story Snapshots: OnlyTitle should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="directory-item-title" > - - -  - - - Just a title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + Just a title + @@ -1106,8 +1061,15 @@ exports[`Story Snapshots: TeamMain should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -1150,173 +1112,157 @@ exports[`Story Snapshots: TeamMain should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="directory-item-team" > - - - + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> + + - - -  - - - Engineering Team - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - Main team room + Engineering Team + + Main team room + @@ -1340,8 +1286,15 @@ exports[`Story Snapshots: WithRightLabel should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -1384,189 +1337,148 @@ exports[`Story Snapshots: WithRightLabel should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="directory-item-label" > - - - + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> + + - - -  - - - General - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - Room with label + General - 123 members + Room with label + + 123 members + @@ -1599,8 +1536,15 @@ exports[`Story Snapshots: WithoutDescription should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -1643,157 +1587,141 @@ exports[`Story Snapshots: WithoutDescription should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="directory-item-no-desc" > - - - + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> + + - - -  - - - No description room - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + No description room + diff --git a/app/containers/NativeListRow/index.tsx b/app/containers/NativeListRow/index.tsx index b2170946b7c..1b7e6a59820 100644 --- a/app/containers/NativeListRow/index.tsx +++ b/app/containers/NativeListRow/index.tsx @@ -28,7 +28,7 @@ const styles = StyleSheet.create({ paddingHorizontal: ROW_PADDING_HORIZONTAL, gap: CONTENT_SPACING }, - pressable: { + content: { flex: 1, flexDirection: 'row', alignItems: 'center', @@ -106,7 +106,14 @@ const NativeListRow = ({ const height = PixelRatio.roundToNearestPixel(ROW_HEIGHT * fontScale); return ( - + - {({ pressed }) => ( - <> - - {leading} - - - {titleLeading} - - {title} - - - {subtitle ? ( - - {subtitle} - - ) : null} - - {trailing} - - {trailingAction ? ( - - - - ) : null} - - )} + style={({ pressed }) => [styles.content, pressed && styles.pressed, disabled && styles.disabled]}> + {leading} + + + {titleLeading} + + {title} + + + {subtitle ? ( + + {subtitle} + + ) : null} + + {trailing} + {trailingAction ? ( + + + + ) : null} ); }; diff --git a/app/views/DirectoryView/DirectoryRow.tsx b/app/views/DirectoryView/DirectoryRow.tsx index 2f0633583d5..179d0dc3764 100644 --- a/app/views/DirectoryView/DirectoryRow.tsx +++ b/app/views/DirectoryView/DirectoryRow.tsx @@ -14,6 +14,16 @@ interface IDirectoryRow { onPressItem: (item: IServerRoom) => void; } +const getRowDetails = (item: IServerRoom, type: string) => { + if (type === 'users') { + return { avatar: item.username, description: item.username, rightLabel: item.federation?.peer }; + } + if (type === 'teams') { + return { avatar: item.name, description: item.name, rightLabel: I18n.t('N_channels', { n: item.roomsCount }) }; + } + return { avatar: item.name, description: item.topic, rightLabel: I18n.t('N_users', { n: item.usersCount }) }; +}; + const DirectoryRow = ({ item, type, isFirst, isLast, onPressItem }: IDirectoryRow) => { const { colors } = useTheme(); const title = item.name as string; @@ -21,42 +31,7 @@ const DirectoryRow = ({ item, type, isFirst, isLast, onPressItem }: IDirectoryRo const testID = `directory-view-item-${item.name}`; const style = isLast ? { ...sharedStyles.separatorBottom, borderColor: colors.strokeLight } : undefined; - if (type === 'users') { - return ( - - ); - } - - if (type === 'teams') { - return ( - - ); - } + const { avatar, description, rightLabel } = getRowDetails(item, type); return ( ); }; diff --git a/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap b/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap index c4bf5d4101d..1bca23cc902 100644 --- a/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap +++ b/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap @@ -8,8 +8,15 @@ exports[`NewMessageView Item should match snapshot when hasMediaCallPermission i "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -50,6 +57,133 @@ exports[`NewMessageView Item should match snapshot when hasMediaCallPermission i onStartShouldSetResponder={[Function]} style={ [ + { + "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, + "flexDirection": "row", + "gap": 12, + }, + false, + undefined, + ] + } + testID="new-message-view-item-john.doe" + > + + + + + + + John Doe + + + + + +`; + +exports[`Story Snapshots: Default should match snapshot 1`] = ` + + - - -`; - -exports[`Story Snapshots: Default should match snapshot 1`] = ` - - - - - - - - - - John Doe - - - - - - -  - - +  + @@ -441,8 +423,15 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -485,188 +474,172 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="new-message-view-item-long.name" > - - - + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> + + - - - Very Long Display Name That Might Get Truncated - - + Very Long Display Name That Might Get Truncated + - + - + -  - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + @@ -689,8 +662,15 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` "marginHorizontal": 16, "overflow": "hidden", }, + { + "alignItems": "center", + "flexDirection": "row", + "gap": 12, + "paddingHorizontal": 16, + }, { "backgroundColor": "#FFFFFF", + "height": 54, }, undefined, undefined, @@ -733,188 +713,172 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` [ { "alignItems": "center", + "alignSelf": "stretch", + "flex": 1, "flexDirection": "row", "gap": 12, - "paddingHorizontal": 16, - }, - { - "height": 54, }, + false, + undefined, ] } testID="new-message-view-item-john.doe" > - - - + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={30} + /> + + - - - John Doe - - + John Doe + - + - + -  - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + diff --git a/app/views/RoomView/components/RightButtons/useHeaderCallPress.ts b/app/views/RoomView/components/RightButtons/useHeaderCallPress.ts index cefdef7f63b..1a8645379ef 100644 --- a/app/views/RoomView/components/RightButtons/useHeaderCallPress.ts +++ b/app/views/RoomView/components/RightButtons/useHeaderCallPress.ts @@ -41,7 +41,6 @@ export const useHeaderCallPress = (rid: string) => { if (hasMediaCallPermission) { return { callPresent: true, - hasMediaCallPermission, isCallDisabled: isInActiveCall, onPressCall: onPressMediaCall }; @@ -50,7 +49,6 @@ export const useHeaderCallPress = (rid: string) => { if (callEnabled) { return { callPresent: true, - hasMediaCallPermission, isCallDisabled: disabledTooltip || isInActiveCall, onPressCall: showInitCallActionSheet }; @@ -58,7 +56,6 @@ export const useHeaderCallPress = (rid: string) => { return { callPresent: false, - hasMediaCallPermission, isCallDisabled: true, onPressCall: () => {} }; diff --git a/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts b/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts index 2f20dd11151..e87de9e3bce 100644 --- a/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts +++ b/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts @@ -2,7 +2,7 @@ import { useStore } from 'zustand'; import { useShallow } from 'zustand/react/shallow'; import { useNavigation } from '@react-navigation/native'; -import { type ISubscription, type SubscriptionType, type TSubscriptionModel, type TUserStatus } from '~/definitions'; +import { type SubscriptionType, type TUserStatus } from '~/definitions'; import { isSubscriptionModel } from '~/definitions/TRoom'; import i18n from '~/i18n'; import getRoomAccessibilityLabel from '~/lib/helpers/getRoomAccessibilityLabel'; @@ -37,10 +37,10 @@ export const useRoomRightButtonsData = (rid: string, roomStore: RoomStore) => { t: room.t as SubscriptionType, status: fromSubscription(r => r.status, undefined)(s), roomName: getRoomTitle(room), - roomIsGroupChat: isGroupChat(room as ISubscription), + roomIsGroupChat: isGroupChat(room), teamMain: fromSubscription(r => !!r.teamMain, false)(s), encrypted: fromSubscription(r => r.encrypted, undefined)(s), - disableNotifications: (room as ISubscription).disableNotifications + disableNotifications: fromSubscription(r => r.disableNotifications, undefined)(s) }; }) ); @@ -63,7 +63,7 @@ export const useRoomRightButtonsData = (rid: string, roomStore: RoomStore) => { navigation, isMasterDetail, screen: 'NotificationPrefView', - params: { rid, room: room as TSubscriptionModel } + params: { rid, room } }); } else { navigateToScreen({ navigation, isMasterDetail, screen: 'PushTroubleshootView' }); diff --git a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts index d0753f74c97..dfe598ad898 100644 --- a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts +++ b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts @@ -4,6 +4,7 @@ import { useStore } from 'zustand'; import { useShallow } from 'zustand/react/shallow'; import i18n from '~/i18n'; +import { type TIconsName } from '~/containers/CustomIcon'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; @@ -28,6 +29,16 @@ import { useRoomRightButtonsData } from '../components/RightButtons/useRoomRight import { useHeaderCallPress } from '../components/RightButtons/useHeaderCallPress'; const EMPTY_ITEMS: NativeStackHeaderItem[] = []; +const VISIBLE_ORDER: TRoomHeaderActionKey[] = ['encryption', 'notifications', 'call', 'threads']; + +interface IRoomHeaderAction { + label: string; + icon: TIconsName; + disabled: boolean; + badge?: { value: number; style: { backgroundColor: string } }; + tintColor?: string; + onPress: () => void; +} const getTunreadBadgeColor = ( tunreadUser: string[], @@ -168,32 +179,46 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): encryption: hasE2EEWarning, notifications: issuesWithNotifications || disableNotifications }); - const isVisible = (key: TRoomHeaderActionKey) => visibleKeys.includes(key); const tunreadBadge = threadsEnabled && tunread.length ? { value: tunread.length, style: { backgroundColor: getTunreadBadgeColor(tunreadUser ?? [], tunreadGroup ?? [], colors) } } : undefined; + const actions: Record = { + threads: { + label: threadsAccessibilityLabel, + icon: 'threads', + disabled: hasE2EEWarning, + badge: tunreadBadge, + onPress: goThreadsView + }, + call: { + label: callAccessibilityLabel, + icon: 'phone', + disabled: hasE2EEWarning || isCallDisabled, + onPress: onPressCall + }, + encryption: { + label: i18n.t('Encrypted'), + icon: 'encrypted', + disabled: !canToggleEncryption, + onPress: goE2EEToggleRoomView + }, + notifications: { + label: i18n.t('Troubleshooting'), + icon: 'notification-disabled', + tintColor: issuesWithNotifications ? colors.fontDanger : undefined, + disabled: hasE2EEWarning, + onPress: navigateToNotificationOrPushTroubleshoot + } + }; + const overflowActions: NativeStackHeaderItemMenuAction[] = [ - ...overflowKeys.map( - (key): NativeStackHeaderItemMenuAction => - key === 'encryption' - ? { - type: 'action', - label: i18n.t('Encrypted'), - icon: headerIcon('encrypted'), - disabled: !canToggleEncryption, - onPress: goE2EEToggleRoomView - } - : { - type: 'action', - label: i18n.t('Troubleshooting'), - icon: headerIcon('notification-disabled'), - disabled: hasE2EEWarning, - onPress: navigateToNotificationOrPushTroubleshoot - } - ), + ...overflowKeys.map((key): NativeStackHeaderItemMenuAction => { + const { label, icon, disabled, onPress } = actions[key]; + return { type: 'action', label, icon: headerIcon(icon), disabled, onPress }; + }), { type: 'action', label: i18n.t('Search_Messages'), @@ -207,49 +232,19 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): return EMPTY_ITEMS; } - const items: NativeStackHeaderItem[] = []; - if (isVisible('encryption')) { - items.push({ - type: 'button', - label: i18n.t('Encrypted'), - accessibilityLabel: i18n.t('Encrypted'), - icon: headerIcon('encrypted'), - disabled: !canToggleEncryption, - onPress: goE2EEToggleRoomView - }); - } - if (isVisible('notifications')) { - items.push({ - type: 'button', - label: i18n.t('Troubleshooting'), - accessibilityLabel: i18n.t('Troubleshooting'), - icon: headerIcon('notification-disabled'), - tintColor: issuesWithNotifications ? colors.fontDanger : undefined, - disabled: hasE2EEWarning, - onPress: navigateToNotificationOrPushTroubleshoot - }); - } - if (isVisible('call')) { - items.push({ - type: 'button', - label: callAccessibilityLabel, - accessibilityLabel: callAccessibilityLabel, - icon: headerIcon('phone'), - disabled: hasE2EEWarning || isCallDisabled, - onPress: onPressCall - }); - } - if (isVisible('threads')) { - items.push({ + const items: NativeStackHeaderItem[] = VISIBLE_ORDER.filter(key => visibleKeys.includes(key)).map(key => { + const { label, icon, disabled, badge, tintColor, onPress } = actions[key]; + return { type: 'button', - label: threadsAccessibilityLabel, - accessibilityLabel: threadsAccessibilityLabel, - icon: headerIcon('threads'), - disabled: hasE2EEWarning, - badge: tunreadBadge, - onPress: goThreadsView - }); - } + label, + accessibilityLabel: label, + icon: headerIcon(icon), + disabled, + badge, + tintColor, + onPress + }; + }); items.push({ type: 'menu', label: i18n.t('More'), From c2d0e03bce93398616d2636001dc9355e5568b28 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 28 Sep 2026 12:57:07 -0300 Subject: [PATCH 102/120] fix(ios): match room actions info row disclosure and background to native rows --- app/containers/NativeListRow/Disclosure.tsx | 9 +++++++++ app/views/RoomActionsView/index.tsx | 6 ++++-- app/views/RoomActionsView/styles.ts | 5 +---- 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 app/containers/NativeListRow/Disclosure.tsx diff --git a/app/containers/NativeListRow/Disclosure.tsx b/app/containers/NativeListRow/Disclosure.tsx new file mode 100644 index 00000000000..214ae769553 --- /dev/null +++ b/app/containers/NativeListRow/Disclosure.tsx @@ -0,0 +1,9 @@ +import { I18nManager } from 'react-native'; + +import ListIcon from '~/containers/List/ListIcon'; + +const Disclosure = () => ( + +); + +export default Disclosure; diff --git a/app/views/RoomActionsView/index.tsx b/app/views/RoomActionsView/index.tsx index 2fd9d029016..148ee5ea253 100644 --- a/app/views/RoomActionsView/index.tsx +++ b/app/views/RoomActionsView/index.tsx @@ -12,6 +12,7 @@ import { leaveRoom } from '~/actions/room'; import Avatar from '~/containers/Avatar'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import * as List from '~/containers/List'; +import Disclosure from '~/containers/NativeListRow/Disclosure'; import { MarkdownPreview } from '~/containers/markdown'; import RoomTypeIcon from '~/containers/RoomTypeIcon'; import SafeAreaView from '~/containers/SafeAreaView'; @@ -49,6 +50,7 @@ import { hasPermission, isGroupChat, compareServerVersion, + isIOS, isTeamRoom } from '~/lib/methods/helpers'; import { @@ -787,7 +789,7 @@ class RoomActionsView extends Component @@ -832,7 +834,7 @@ class RoomActionsView extends Component )} - {isGroupChatHandler ? null : } + {isGroupChatHandler ? null : } diff --git a/app/views/RoomActionsView/styles.ts b/app/views/RoomActionsView/styles.ts index e79db3879fc..7794f72a8f8 100644 --- a/app/views/RoomActionsView/styles.ts +++ b/app/views/RoomActionsView/styles.ts @@ -1,4 +1,4 @@ -import { I18nManager, StyleSheet } from 'react-native'; +import { StyleSheet } from 'react-native'; import { PADDING_HORIZONTAL } from '~/containers/List/constants'; import { isIOS26OrLater } from '~/lib/methods/helpers/deviceInfo'; @@ -29,8 +29,5 @@ export default StyleSheet.create({ paddingRight: 16, flexDirection: 'row', alignItems: 'center' - }, - actionIndicator: { - ...(I18nManager.isRTL ? { transform: [{ rotate: '180deg' }] } : {}) } }); From 459b4102816e39e92711af4dfe5c52d343f59ac0 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 28 Sep 2026 14:19:04 -0300 Subject: [PATCH 103/120] fix(ios): show connection state in DM header and share room type icon names --- .maestro/helpers/assert-room-title.yaml | 2 - .maestro/helpers/go-back.yaml | 3 - .maestro/helpers/open-room-actions.yaml | 2 - .../RoomTypeIcon/OmnichannelRoomIcon.tsx | 26 +++------ app/containers/RoomTypeIcon/index.tsx | 18 +----- .../RoomTypeIcon/roomTypeIconName.ts | 52 +++++++++++++++++ app/lib/methods/helpers/navigation/index.ts | 27 ++++++++- app/views/CannedResponsesListView/index.tsx | 11 ++-- app/views/DirectoryView/index.tsx | 9 +-- app/views/DiscussionsView/index.tsx | 11 ++-- app/views/RoomMembersView/index.tsx | 8 +-- .../RightButtons/RoomRightButtons.tsx | 3 +- .../RightButtons/useRoomRightButtonsData.ts | 40 ++++++------- .../hooks/__tests__/useHeader.test.tsx | 7 +++ .../__tests__/useNativeBackButton.test.ts | 4 +- app/views/RoomView/hooks/useHeader.tsx | 9 +-- .../RoomView/hooks/useNativeBackButton.ts | 6 +- .../RoomView/hooks/useNativeRoomHeader.ts | 58 ++++++++----------- app/views/RoomsListView/hooks/useHeader.tsx | 19 ++---- app/views/SelectedUsersView/index.tsx | 8 +-- app/views/ShareListView/index.tsx | 11 ++-- app/views/TeamChannelsView.tsx | 11 ++-- app/views/ThreadMessagesView/index.tsx | 11 ++-- ios/RocketChat.xcodeproj/project.pbxproj | 2 - 24 files changed, 187 insertions(+), 171 deletions(-) create mode 100644 app/containers/RoomTypeIcon/roomTypeIconName.ts diff --git a/.maestro/helpers/assert-room-title.yaml b/.maestro/helpers/assert-room-title.yaml index da6244ed013..183c4e2556a 100644 --- a/.maestro/helpers/assert-room-title.yaml +++ b/.maestro/helpers/assert-room-title.yaml @@ -4,8 +4,6 @@ tags: - 'util' --- -# On iOS 26+ the room title is the native stack's headerTitle text with no accessibilityIdentifier, -# so it is matched by its visible text there. Android keeps the JS-rendered title, matched by testID. - runFlow: when: platform: Android diff --git a/.maestro/helpers/go-back.yaml b/.maestro/helpers/go-back.yaml index fe41d560f2e..78d6f80eaa1 100644 --- a/.maestro/helpers/go-back.yaml +++ b/.maestro/helpers/go-back.yaml @@ -15,9 +15,6 @@ tags: when: platform: iOS commands: - # The native stack's back button carries no accessibilityIdentifier, only - # the "Back" accessibility label (headerBackButtonDisplayMode: 'minimal' - # hides the title text but keeps that label for VoiceOver/XCUITest). - tapOn: text: 'Back' retryTapIfNoChange: true diff --git a/.maestro/helpers/open-room-actions.yaml b/.maestro/helpers/open-room-actions.yaml index ecb67ef40e3..66138ed7f2b 100644 --- a/.maestro/helpers/open-room-actions.yaml +++ b/.maestro/helpers/open-room-actions.yaml @@ -8,8 +8,6 @@ tags: when: platform: iOS commands: - # The native header's overflow ("More") item carries only a label, no accessibilityIdentifier. - # It opens a JS action sheet whose "Actions" entry navigates to Room Actions. - tapOn: text: 'More' retryTapIfNoChange: true diff --git a/app/containers/RoomTypeIcon/OmnichannelRoomIcon.tsx b/app/containers/RoomTypeIcon/OmnichannelRoomIcon.tsx index d9b5c8288b1..88d3707d9ac 100644 --- a/app/containers/RoomTypeIcon/OmnichannelRoomIcon.tsx +++ b/app/containers/RoomTypeIcon/OmnichannelRoomIcon.tsx @@ -1,24 +1,12 @@ import { useState } from 'react'; import { Image, type ImageStyle } from 'expo-image'; -import { OmnichannelSourceType, type IOmnichannelSource, type TUserStatus } from '~/definitions'; +import { type IOmnichannelSource, type TUserStatus } from '~/definitions'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; -import { CustomIcon, type TIconsName } from '../CustomIcon'; +import { CustomIcon } from '../CustomIcon'; +import { getOmnichannelIconName, getOmnichannelSidebarIconUri } from './roomTypeIconName'; import { useUserStatusColor } from '~/lib/hooks/useUserStatusColor'; -interface IIconMap { - [key: string]: TIconsName; -} - -const iconMap: IIconMap = { - widget: 'livechat-monochromatic', - email: 'mail', - sms: 'sms', - app: 'omnichannel', - api: 'omnichannel', - other: 'omnichannel' -}; - interface IOmnichannelRoomIconProps { size: number; type: string; @@ -34,15 +22,15 @@ export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnich const connected = useAppSelector(state => state.meteor?.connected); const userStatusColor = useUserStatusColor(status || 'offline'); - // @ts-ignore - const customIcon = ; + const customIcon = ; + const sidebarIconUri = connected ? getOmnichannelSidebarIconUri(baseUrl, sourceType) : undefined; - if (!svgError && sourceType?.type === OmnichannelSourceType.APP && sourceType.id && sourceType.sidebarIcon && connected) { + if (!svgError && sidebarIconUri) { return ( <> setSvgError(true)} onLoad={() => setLoading(false)} diff --git a/app/containers/RoomTypeIcon/index.tsx b/app/containers/RoomTypeIcon/index.tsx index be9873fe321..e100eb6ebc6 100644 --- a/app/containers/RoomTypeIcon/index.tsx +++ b/app/containers/RoomTypeIcon/index.tsx @@ -3,7 +3,8 @@ import { StyleSheet, type ViewStyle } from 'react-native'; import { type ImageStyle } from 'expo-image'; import { OmnichannelRoomIcon } from './OmnichannelRoomIcon'; -import { CustomIcon, type TIconsName } from '../CustomIcon'; +import { CustomIcon } from '../CustomIcon'; +import { getRoomTypeIconName } from './roomTypeIconName'; import { themes } from '~/lib/constants/colors'; import Status from '../Status'; import { useTheme } from '~/theme'; @@ -48,21 +49,8 @@ const RoomTypeIcon = memo( ); } - // TODO: move this to a separate function - let icon: TIconsName = 'channel-private'; - if (abacAttributes?.length) { - icon = teamMain ? 'team-shield' : 'hash-shield'; - } else if (teamMain) { - icon = `teams${type === 'p' ? '-private' : ''}`; - } else if (type === 'discussion') { - icon = 'discussions'; - } else if (type === 'c') { - icon = 'channel-public'; - } else if (type === 'd' && isGroupChat) { - icon = 'message'; - } + const icon = getRoomTypeIconName({ type, teamMain, isDiscussion: type === 'discussion', isGroupChat, abacAttributes }); - // @ts-ignore return ; } ); diff --git a/app/containers/RoomTypeIcon/roomTypeIconName.ts b/app/containers/RoomTypeIcon/roomTypeIconName.ts new file mode 100644 index 00000000000..85e572c0460 --- /dev/null +++ b/app/containers/RoomTypeIcon/roomTypeIconName.ts @@ -0,0 +1,52 @@ +import { OmnichannelSourceType, type IOmnichannelSource, type ISubscription } from '~/definitions'; +import { type TIconsName } from '../CustomIcon'; + +const omnichannelSourceIcons: Record = { + widget: 'livechat-monochromatic', + email: 'mail', + sms: 'sms', + app: 'omnichannel', + api: 'omnichannel', + other: 'omnichannel' +}; + +export const getOmnichannelIconName = (sourceType?: IOmnichannelSource): TIconsName => + omnichannelSourceIcons[sourceType?.type || 'other'] ?? 'omnichannel'; + +export const getOmnichannelSidebarIconUri = (server: string, sourceType?: IOmnichannelSource) => + sourceType?.type === OmnichannelSourceType.APP && sourceType.id && sourceType.sidebarIcon + ? `${server}/api/apps/public/${sourceType.id}/get-sidebar-icon?icon=${sourceType.sidebarIcon}` + : undefined; + +interface IRoomTypeIconName { + type?: string; + teamMain?: boolean; + isDiscussion?: boolean; + isGroupChat?: boolean; + abacAttributes?: ISubscription['abacAttributes']; +} + +export const getRoomTypeIconName = ({ + type, + teamMain, + isDiscussion, + isGroupChat, + abacAttributes +}: IRoomTypeIconName): TIconsName => { + if (abacAttributes?.length) { + return teamMain ? 'team-shield' : 'hash-shield'; + } + if (teamMain) { + return type === 'p' ? 'teams-private' : 'teams'; + } + if (isDiscussion) { + return 'discussions'; + } + if (type === 'c') { + return 'channel-public'; + } + if (type === 'd' && isGroupChat) { + return 'message'; + } + return 'channel-private'; +}; diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 4668d0f3780..55697b58fba 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -1,6 +1,7 @@ -import { createElement, type ReactElement } from 'react'; +import { createElement, type ReactElement, type RefObject } from 'react'; import { DarkTheme, DefaultTheme } from '@react-navigation/native'; import { type NativeStackHeaderProps, type NativeStackNavigationOptions } from '@react-navigation/native-stack'; +import { type SearchBarCommands, type SearchBarProps } from 'react-native-screens'; import { themes } from '~/lib/constants/colors'; import { type TSupportedThemes } from '~/theme'; @@ -39,6 +40,30 @@ export const outsideHeaderRightLegal = ( headerRight: (): ReactElement => createElement(HeaderButton.Legal, { testID, navigation }) }; +interface IStackedSearchBarOptions { + ref?: RefObject; + onFocus?: () => void; + onChangeText: (text: string) => void; + onCancel?: () => void; + onSearch?: () => void; +} + +export const stackedSearchBarOptions = ({ + ref, + onFocus, + onChangeText, + onCancel, + onSearch +}: IStackedSearchBarOptions): SearchBarProps => ({ + ref, + placement: 'stacked', + placeholder: I18n.t('Search'), + onFocus, + onChangeText: event => onChangeText(event.nativeEvent.text), + onSearchButtonPress: onSearch, + onCancelButtonPress: onCancel ?? (() => onChangeText('')) +}); + export const outsideHeaderLeftClose = (onPress: () => void, testID: string): NativeStackNavigationOptions => hasNativeHeaderBar ? { diff --git a/app/views/CannedResponsesListView/index.tsx b/app/views/CannedResponsesListView/index.tsx index 0e0c27185cf..428e90cdfb9 100644 --- a/app/views/CannedResponsesListView/index.tsx +++ b/app/views/CannedResponsesListView/index.tsx @@ -7,6 +7,7 @@ import { type SearchBarCommands } from 'react-native-screens'; import database from '~/lib/database'; import I18n from '~/i18n'; +import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import { hideActionSheetRef, showActionSheetRef } from '~/containers/ActionSheet'; import SafeAreaView from '~/containers/SafeAreaView'; import ActivityIndicator from '~/containers/ActivityIndicator'; @@ -216,16 +217,14 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView return { headerLeft: () => null, headerTitle: I18n.t('Canned_Responses'), - headerSearchBarOptions: { + headerSearchBarOptions: stackedSearchBarOptions({ ref: searchBarRef, - placement: 'stacked', - placeholder: I18n.t('Search'), - onChangeText: (event: { nativeEvent: { text: string } }) => onChangeText(event.nativeEvent.text), - onCancelButtonPress: () => { + onChangeText, + onCancel: () => { onChangeText(''); searchBarRef.current?.clearText(); } - }, + }), headerRight: () => ( diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index 744c2f6ec5e..b07c4f04efe 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -13,6 +13,7 @@ import { useActionSheet } from '~/containers/ActionSheet'; import { type ChatsStackParamList } from '~/stacks/types'; import { type MasterDetailInsideStackParamList } from '~/stacks/MasterDetailStack/types'; import I18n from '~/i18n'; +import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import SearchBox from '~/containers/SearchBox'; import ActivityIndicator from '~/containers/ActivityIndicator'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; @@ -95,13 +96,7 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { title: I18n.t('Directory'), headerRight: undefined, headerTransparent: true, - headerSearchBarOptions: { - placement: 'stacked', - placeholder: I18n.t('Search'), - onChangeText: event => onSearchChangeText(event.nativeEvent.text), - onSearchButtonPress: () => search(), - onCancelButtonPress: () => onSearchChangeText('') - }, + headerSearchBarOptions: stackedSearchBarOptions({ onChangeText: onSearchChangeText, onSearch: search }), unstable_headerRightItems: () => [ { type: 'menu', diff --git a/app/views/DiscussionsView/index.tsx b/app/views/DiscussionsView/index.tsx index 9b6afa40988..06f225182d7 100644 --- a/app/views/DiscussionsView/index.tsx +++ b/app/views/DiscussionsView/index.tsx @@ -10,6 +10,7 @@ import { type IMessageFromServer, type TThreadModel } from '~/definitions'; import { type ChatsStackParamList } from '~/stacks/types'; import ActivityIndicator from '~/containers/ActivityIndicator'; import I18n from '~/i18n'; +import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import log from '~/lib/methods/helpers/log'; import { hasNativeHeaderBar, isIOS, useDebounce } from '~/lib/methods/helpers'; import SafeAreaView from '~/containers/SafeAreaView'; @@ -111,14 +112,12 @@ const DiscussionsView = () => { options = { headerLeft: undefined, headerTitle: I18n.t('Discussions'), - headerSearchBarOptions: { + headerSearchBarOptions: stackedSearchBarOptions({ ref: searchBarRef, - placement: 'stacked', - placeholder: I18n.t('Search'), onFocus: onSearchPress, - onChangeText: (event: { nativeEvent: { text: string } }) => onSearchChangeText(event.nativeEvent.text), - onCancelButtonPress: onCancelSearchPress - }, + onChangeText: onSearchChangeText, + onCancel: onCancelSearchPress + }), headerRight: () => null }; if (isMasterDetail) { diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 94a94928acb..8d253e44834 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -17,6 +17,7 @@ import UserItem from '~/containers/UserItem'; import Radio from '~/containers/Radio'; import { type IGetRoomRoles, type TSubscriptionModel, type TUserModel } from '~/definitions'; import I18n from '~/i18n'; +import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { usePermissions } from '~/lib/hooks/usePermissions'; @@ -258,12 +259,7 @@ const RoomMembersView = (): ReactElement => { title: I18n.t('Members'), headerRight: undefined, headerTransparent: true, - headerSearchBarOptions: { - placement: 'stacked', - placeholder: I18n.t('Search'), - onChangeText: (event: { nativeEvent: { text: string } }) => debounceFilterChange(event.nativeEvent.text), - onCancelButtonPress: () => debounceFilterChange('') - }, + headerSearchBarOptions: stackedSearchBarOptions({ onChangeText: debounceFilterChange }), unstable_headerRightItems: () => [ { type: 'menu', diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx index 1db452ca03c..d28c9b69013 100644 --- a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx @@ -2,6 +2,7 @@ import { type ReactElement } from 'react'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import i18n from '~/i18n'; +import { useTheme } from '~/theme'; import { type RoomStore } from '~/views/RoomView/definitions'; import { HeaderCallButton } from './HeaderCallButton'; import { useRoomRightButtonsData } from './useRoomRightButtonsData'; @@ -12,8 +13,8 @@ interface IRoomRightButtonsProps { } export const RoomRightButtons = ({ rid, roomStore }: IRoomRightButtonsProps): ReactElement => { + const { colors } = useTheme(); const { - colors, threadsEnabled, issuesWithNotifications, disableNotifications, diff --git a/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts b/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts index e87de9e3bce..1ffecb0d94e 100644 --- a/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts +++ b/app/views/RoomView/components/RightButtons/useRoomRightButtonsData.ts @@ -13,17 +13,28 @@ import { useSetting } from '~/lib/hooks/useSetting'; import { getRoomTitle, isGroupChat } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; import { getUserSelector } from '~/selectors/login'; -import { useTheme } from '~/theme'; import { type RoomStore } from '~/views/RoomView/definitions'; import { useE2EEStatus } from '~/views/RoomView/hooks/useE2EEStatus'; import { useSubscriptionUnreads } from '~/views/RoomView/hooks/useSubscriptionUnreads'; import { fromSubscription } from '~/views/RoomView/stores/RoomStoreContext'; import { navigateToScreen, type TRoomStackNavigation } from '~/views/RoomView/services/navigateToScreen'; +const getThreadsAccessibilityLabel = (tunread: string[], tunreadUser?: string[], tunreadGroup?: string[]) => { + if (!tunread.length) { + return i18n.t('Threads'); + } + if (tunreadUser?.length) { + return i18n.t('Threads_dm_unread', { unread: tunreadUser.length }); + } + if (tunreadGroup?.length) { + return i18n.t('Threads_group_unread', { unread: tunreadGroup.length }); + } + return i18n.t('Threads_unread', { unread: tunread.length }); +}; + export const useRoomRightButtonsData = (rid: string, roomStore: RoomStore) => { const navigation = useNavigation(); const isMasterDetail = useMasterDetail(); - const { colors } = useTheme(); const userId = useAppSelector(state => getUserSelector(state).id); const threadsEnabled = useSetting('Threads_enabled') as boolean; @@ -54,19 +65,18 @@ export const useRoomRightButtonsData = (rid: string, roomStore: RoomStore) => { }; const navigateToNotificationOrPushTroubleshoot = () => { - const room = roomStore.getState().room; - if (!isSubscriptionModel(room)) { + if (issuesWithNotifications) { + navigateToScreen({ navigation, isMasterDetail, screen: 'PushTroubleshootView' }); return; } - if (!issuesWithNotifications) { + const room = roomStore.getState().room; + if (isSubscriptionModel(room)) { navigateToScreen({ navigation, isMasterDetail, screen: 'NotificationPrefView', params: { rid, room } }); - } else { - navigateToScreen({ navigation, isMasterDetail, screen: 'PushTroubleshootView' }); } }; @@ -85,19 +95,6 @@ export const useRoomRightButtonsData = (rid: string, roomStore: RoomStore) => { navigateToScreen({ navigation, isMasterDetail, screen: 'E2EEToggleRoomView', params: { rid } }); }; - const threadsAccessibilityLabel = () => { - if (!tunread.length) { - return i18n.t('Threads'); - } - if (tunreadUser?.length) { - return i18n.t('Threads_dm_unread', { unread: tunreadUser?.length }); - } - if (tunreadGroup?.length) { - return i18n.t('Threads_group_unread', { unread: tunreadGroup?.length }); - } - return i18n.t('Threads_unread', { unread: tunread?.length }); - }; - const accessibilityRoomName = !roomIsGroupChat && t === 'd' && !!userId ? roomName @@ -105,7 +102,6 @@ export const useRoomRightButtonsData = (rid: string, roomStore: RoomStore) => { const callAccessibilityLabel = i18n.t('Call_room_name', { roomName: accessibilityRoomName }); return { - colors, threadsEnabled, issuesWithNotifications, disableNotifications, @@ -120,6 +116,6 @@ export const useRoomRightButtonsData = (rid: string, roomStore: RoomStore) => { navigateToNotificationOrPushTroubleshoot, goSearchView, goE2EEToggleRoomView, - threadsAccessibilityLabel: threadsAccessibilityLabel() + threadsAccessibilityLabel: getThreadsAccessibilityLabel(tunread, tunreadUser, tunreadGroup) }; }; diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index bf2a673c931..11b232cf492 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -5,6 +5,7 @@ import { createStore } from 'zustand'; import { type RoomState, type RoomStore } from '~/views/RoomView/definitions'; import { useHeader } from '../useHeader'; import { useNativeRoomHeader } from '../useNativeRoomHeader'; +import { useNativeBackButton } from '../useNativeBackButton'; jest.mock('../useNativeRoomHeader', () => ({ useNativeRoomHeader: jest.fn() })); jest.mock('../useNativeBackButton', () => ({ useNativeBackButton: jest.fn() })); @@ -159,6 +160,12 @@ describe('useHeader', () => { expect(avatarItem.element.type).toBe('LeftButtons'); }); + it('disables the native back button while the avatar holds the left slot', () => { + renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + + expect(useNativeBackButton).toHaveBeenLastCalledWith(false, 'rid-1'); + }); + it('keeps the native back button on a thread', () => { renderHook(() => useHeader({ rid: 'rid-1', tmid: 'tmid-1', name: 'Thread', roomStore: mockTestStore })); diff --git a/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts b/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts index 60379d053b8..c640b2bae32 100644 --- a/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts +++ b/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts @@ -58,8 +58,8 @@ it('does nothing when disabled', () => { expect(mockSetOptions).not.toHaveBeenCalled(); }); -it('keeps the default header when it cannot go back', () => { +it('clears custom left items when it cannot go back', () => { mockCanGoBack = false; renderHook(() => useNativeBackButton(true, 'rid')); - expect(mockSetOptions).not.toHaveBeenCalled(); + expect(mockSetOptions).toHaveBeenCalledWith({ unstable_headerLeftItems: undefined }); }); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index 8e197ab8c35..a7be644d1ca 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -72,9 +72,10 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa const roomUserId = useStore(roomStore, s => s.roomUserId); const goRoomActionsView = useGoRoomActionsView(roomStore); useNativeRoomHeader(!!rid && hasNativeHeaderBar, headerFields, tmid, roomUserId, goRoomActionsView); - useNativeBackButton(!!rid && hasNativeHeaderBar, rid); - const nativeRightItems = useRoomHeaderRightItems(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); const isMasterDetail = useMasterDetail(); + const showsAvatar = isMasterDetail && !tmid; + useNativeBackButton(!!rid && hasNativeHeaderBar && !showsAvatar, rid); + const nativeRightItems = useRoomHeaderRightItems(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); useLayoutEffect(() => { if (!rid && hasNativeHeaderBar) { @@ -94,7 +95,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa hidesSharedBackground: true }; navigation.setOptions( - isMasterDetail && !tmid + showsAvatar ? { unstable_headerLeftItems: () => [avatarItem], unstable_headerRightItems: () => nativeRightItems } : { unstable_headerRightItems: () => nativeRightItems } ); @@ -105,7 +106,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa headerLeft: () => , headerRight: () => }); - }, [rid, tmid, navigation, roomStore, nativeRightItems, isMasterDetail]); + }, [rid, tmid, navigation, roomStore, nativeRightItems, showsAvatar]); useLayoutEffect(() => { if (!rid || hasNativeHeaderBar) { diff --git a/app/views/RoomView/hooks/useNativeBackButton.ts b/app/views/RoomView/hooks/useNativeBackButton.ts index 0b5c8a620a5..ca3afe5c0c3 100644 --- a/app/views/RoomView/hooks/useNativeBackButton.ts +++ b/app/views/RoomView/hooks/useNativeBackButton.ts @@ -20,7 +20,11 @@ export const useNativeBackButton = (enabled: boolean, rid?: string) => { const unreadsLabel = formatUnreadsCount(useUnreadsCount(enabled ? rid : undefined)); useLayoutEffect(() => { - if (!enabled || !navigation.canGoBack()) { + if (!enabled) { + return; + } + if (!navigation.canGoBack()) { + navigation.setOptions({ unstable_headerLeftItems: undefined }); return; } const backItem: NativeStackHeaderItem = { diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts index 5012c4746f0..27f7d6b6ed7 100644 --- a/app/views/RoomView/hooks/useNativeRoomHeader.ts +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -3,6 +3,11 @@ import { shallowEqual } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; import { hasIcon, type TIconsName } from '~/containers/CustomIcon'; +import { + getOmnichannelIconName, + getOmnichannelSidebarIconUri, + getRoomTypeIconName +} from '~/containers/RoomTypeIcon/roomTypeIconName'; import { STATUS_I18N_KEYS, type TUserStatus } from '~/definitions'; import { type IActiveUser } from '~/reducers/activeUsers'; import I18n from '~/i18n'; @@ -19,34 +24,21 @@ import { useHeaderIconImage, useHeaderRemoteImage } from './useHeaderImage'; const TITLE_FONT_SIZE = 17; const SUBTITLE_FONT_SIZE = 12; -const sourceIcons: Record = { - widget: 'livechat-monochromatic', - email: 'mail', - sms: 'sms', - app: 'omnichannel', - api: 'omnichannel', - other: 'omnichannel' -}; - const getRoomIcon = (fields: IHeaderFields, isDirectMessage: boolean, status: TUserStatus): TIconsName => { - let roomIcon: TIconsName = 'channel-private'; if (isDirectMessage) { const statusIcon = `status-${status}`; - roomIcon = hasIcon(statusIcon) ? (statusIcon as TIconsName) : 'status-offline'; - } else if (fields.type === 'l' && !fields.prid) { - roomIcon = sourceIcons[fields.sourceType?.type ?? 'other'] ?? 'omnichannel'; - } else if (fields.abacAttributes?.length) { - roomIcon = fields.teamMain ? 'team-shield' : 'hash-shield'; - } else if (fields.teamMain) { - roomIcon = fields.type === 'p' ? 'teams-private' : 'teams'; - } else if (fields.prid) { - roomIcon = 'discussions'; - } else if (fields.type === 'c') { - roomIcon = 'channel-public'; - } else if (fields.type === 'd' && fields.isGroupChat) { - roomIcon = 'message'; + return hasIcon(statusIcon) ? (statusIcon as TIconsName) : 'status-offline'; + } + if (fields.type === 'l' && !fields.prid) { + return getOmnichannelIconName(fields.sourceType); } - return roomIcon; + return getRoomTypeIconName({ + type: fields.type, + teamMain: fields.teamMain, + isDiscussion: !!fields.prid, + isGroupChat: fields.isGroupChat, + abacAttributes: fields.abacAttributes + }); }; const getSubtitle = ({ @@ -71,17 +63,20 @@ const getSubtitle = ({ const names = usersTyping.join(usersTyping.length === 2 ? ` ${I18n.t('and')} ` : ', '); return `${names} ${I18n.t(usersTyping.length > 1 ? 'are_typing' : 'is_typing')}...`; } + if (connecting) { + return I18n.t('Connecting'); + } + if (!connected) { + return I18n.t('Waiting_for_network'); + } if (fields.type === 'd') { - if (!connected || !activeUser) { + if (!activeUser) { return undefined; } const presenceKey = STATUS_I18N_KEYS[activeUser.status]; return activeUser.statusText || (presenceKey ? I18n.t(presenceKey) : undefined); } - if (connecting) { - return I18n.t('Connecting'); - } - return connected ? fields.subtitle : I18n.t('Waiting_for_network'); + return fields.subtitle; }; const useRoomHeaderPresence = (enabled: boolean, fields: IHeaderFields, roomUserId?: string | null) => { @@ -142,11 +137,8 @@ export const useNativeRoomHeader = ( TITLE_FONT_SIZE ); const server = useAppSelector(state => state.server.server); - const source = fields.sourceType; const remoteUri = - enabled && connected && fields.type === 'l' && source?.type === 'app' && source.id && source.sidebarIcon - ? `${server}/api/apps/public/${source.id}/get-sidebar-icon?icon=${source.sidebarIcon}` - : undefined; + enabled && connected && fields.type === 'l' ? getOmnichannelSidebarIconUri(server, fields.sourceType) : undefined; const remoteImage = useHeaderRemoteImage(remoteUri); const roomImage = remoteImage ?? glyphImage; const clockImage = useHeaderIconImage(enabled && showClock ? 'clock' : undefined, colors.fontSecondaryInfo, SUBTITLE_FONT_SIZE); diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index e14ef79c860..e587823e617 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -85,17 +85,7 @@ export const useHeader = () => { const disabled = supportedVersionsStatus === 'expired' || requirePasswordChange; - const getBadge = useCallback(() => { - if (supportedVersionsStatus === 'warn') { - return ; - } - if (notificationPresenceCap) { - return ; - } - return null; - }, [supportedVersionsStatus, notificationPresenceCap, colors]); - - const nativeBadgeColor = + const badgeColor = supportedVersionsStatus === 'warn' ? colors.buttonBackgroundDangerDefault : notificationPresenceCap @@ -140,7 +130,7 @@ export const useHeader = () => { ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) : () => navigation.toggleDrawer() } - badge={getBadge} + badge={() => (badgeColor ? : null)} disabled={disabled} /> ); @@ -215,7 +205,7 @@ export const useHeader = () => { accessibilityLabel: i18n.t('Menu'), icon: headerIcon('hamburguer'), disabled, - badge: nativeBadgeColor ? { value: '', style: { backgroundColor: nativeBadgeColor } } : undefined, + badge: badgeColor ? { value: '', style: { backgroundColor: badgeColor } } : undefined, onPress: isMasterDetail ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) : () => navigation.toggleDrawer() @@ -298,14 +288,13 @@ export const useHeader = () => { searchEnabled, goDirectory, navigateToPushTroubleshootView, - getBadge, goToNewMessage, startSearch, stopSearch, search, serverName, nativeHeaderSubtitle, - nativeBadgeColor + badgeColor ]); useEffect(() => { diff --git a/app/views/SelectedUsersView/index.tsx b/app/views/SelectedUsersView/index.tsx index b3f518543a1..697458ef494 100644 --- a/app/views/SelectedUsersView/index.tsx +++ b/app/views/SelectedUsersView/index.tsx @@ -17,6 +17,7 @@ import SafeAreaView from '~/containers/SafeAreaView'; import RowSeparator from '~/containers/NativeListRow/Separator'; import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; import I18n from '~/i18n'; +import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import database from '~/lib/database'; import UserItem from '~/containers/UserItem'; import { type ISelectedUser } from '~/reducers/selectedUsers'; @@ -123,12 +124,7 @@ const SelectedUsersView = () => { ...(hasNativeHeaderBar ? { headerTransparent: true, - headerSearchBarOptions: { - placement: 'stacked' as const, - placeholder: I18n.t('Search'), - onChangeText: (event: { nativeEvent: { text: string } }) => handleSearch(event.nativeEvent.text), - onCancelButtonPress: () => handleSearch('') - } + headerSearchBarOptions: stackedSearchBarOptions({ onChangeText: handleSearch }) } : {}) }); diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index 6aa0f4dde25..596398e2ed2 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -12,6 +12,7 @@ import { Component, createRef } from 'react'; import database from '~/lib/database'; import I18n from '~/i18n'; +import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import DirectoryItem, { ROW_HEIGHT } from '~/containers/DirectoryItem'; import RowSeparator from '~/containers/NativeListRow/Separator'; import ServerItem from '~/containers/ServerItem'; @@ -207,14 +208,12 @@ class ShareListView extends Component { headerTitle: I18n.t('Send_to'), headerSearchBarOptions: this.airGappedReadOnly ? undefined - : { + : stackedSearchBarOptions({ ref: this.searchBarRef, - placement: 'stacked', - placeholder: I18n.t('Search'), onFocus: this.initSearch, - onChangeText: (event: { nativeEvent: { text: string } }) => this.search(event.nativeEvent.text), - onCancelButtonPress: this.cancelSearch - }, + onChangeText: this.search, + onCancel: this.cancelSearch + }), headerRight: () => null }); return; diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 08f1b5b6594..915530a0796 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -23,6 +23,7 @@ import { withDimensions } from '../lib/hooks/withDimensions'; import { withMasterDetail } from '../lib/hooks/useMasterDetail'; import { BASE_ROW_HEIGHT, BASE_ROW_HEIGHT_CONDENSED } from '../lib/hooks/useResponsiveLayout/useResponsiveLayout'; import I18n from '../i18n'; +import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import database from '../lib/database'; import { CustomIcon } from '../containers/CustomIcon'; import RoomItem from '../containers/RoomItem'; @@ -237,14 +238,12 @@ class TeamChannelsView extends Component this.goRoomActionsView(), - headerSearchBarOptions: { + headerSearchBarOptions: stackedSearchBarOptions({ ref: this.searchBarRef, - placement: 'stacked', - placeholder: I18n.t('Search'), onFocus: this.onSearchPress, - onChangeText: (event: { nativeEvent: { text: string } }) => this.onSearchChangeText(event.nativeEvent.text), - onCancelButtonPress: this.onCancelSearchPress - }, + onChangeText: this.onSearchChangeText, + onCancel: this.onCancelSearchPress + }), headerRight: () => ( {showCreate ? ( diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 2840f97d858..4449a5b1338 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -12,6 +12,7 @@ import { showActionSheetRef } from '~/containers/ActionSheet'; import { CustomIcon } from '~/containers/CustomIcon'; import ActivityIndicator from '~/containers/ActivityIndicator'; import I18n from '~/i18n'; +import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import database from '~/lib/database'; import { sanitizeLikeString } from '~/lib/database/utils'; import buildMessage from '~/lib/methods/helpers/buildMessage'; @@ -127,14 +128,12 @@ class ThreadMessagesView extends Component this.onSearchChangeText(event.nativeEvent.text), - onCancelButtonPress: this.onCancelSearchPress - }, + onChangeText: this.onSearchChangeText, + onCancel: this.onCancelSearchPress + }), unstable_headerRightItems: () => [ { type: 'menu', diff --git a/ios/RocketChat.xcodeproj/project.pbxproj b/ios/RocketChat.xcodeproj/project.pbxproj index 0737dba7ad7..d1ac063b069 100644 --- a/ios/RocketChat.xcodeproj/project.pbxproj +++ b/ios/RocketChat.xcodeproj/project.pbxproj @@ -216,7 +216,6 @@ 7AVR00022F5F5900002A6BDE /* VoipRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AVR00002F5F5900002A6BDE /* VoipRegion.swift */; }; 85160EB6C143E0493FE5F014 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */; }; A1C0E0022F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; - A1C0E0032F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; A48B46DA2D3FFBD200945489 /* A11yFlowModule.m in Sources */ = {isa = PBXBuildFile; fileRef = A48B46D82D3FFBD200945489 /* A11yFlowModule.m */; }; B1E1F0022F03A00000EXT002 /* ExternalInputModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B1E1F0042F03A00000EXT004 /* ExternalInputModule.m */; }; B1E1F0062F03A00000EXT006 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1E1F0072F03A00000EXT007 /* GameController.framework */; }; @@ -1206,7 +1205,6 @@ 1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */, 1E044F992B92798E00BCA2FD /* Localizable.xcstrings in Resources */, 65AD383B2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */, - A1C0E0032F90000000C0FFEE /* AppIcon.icon in Resources */, 1ED038952B507B4D00C007D4 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; From c81a519eedc00d2049a106a3b1b976adc2de88e3 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 28 Sep 2026 16:10:15 -0300 Subject: [PATCH 104/120] fix(ios): use Icon Composer app icon for the Watch target --- ios/RocketChat.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ios/RocketChat.xcodeproj/project.pbxproj b/ios/RocketChat.xcodeproj/project.pbxproj index d1ac063b069..3b0064fceb3 100644 --- a/ios/RocketChat.xcodeproj/project.pbxproj +++ b/ios/RocketChat.xcodeproj/project.pbxproj @@ -216,6 +216,7 @@ 7AVR00022F5F5900002A6BDE /* VoipRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AVR00002F5F5900002A6BDE /* VoipRegion.swift */; }; 85160EB6C143E0493FE5F014 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */; }; A1C0E0022F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; + A1C0E0032F90000000C0FFEE /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = A1C0E0012F90000000C0FFEE /* AppIcon.icon */; }; A48B46DA2D3FFBD200945489 /* A11yFlowModule.m in Sources */ = {isa = PBXBuildFile; fileRef = A48B46D82D3FFBD200945489 /* A11yFlowModule.m */; }; B1E1F0022F03A00000EXT002 /* ExternalInputModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B1E1F0042F03A00000EXT004 /* ExternalInputModule.m */; }; B1E1F0062F03A00000EXT006 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1E1F0072F03A00000EXT007 /* GameController.framework */; }; @@ -1202,6 +1203,7 @@ buildActionMask = 2147483647; files = ( 1ED1ECEA2B869A4A00F6620C /* Assets.xcassets in Resources */, + A1C0E0032F90000000C0FFEE /* AppIcon.icon in Resources */, 1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */, 1E044F992B92798E00BCA2FD /* Localizable.xcstrings in Resources */, 65AD383B2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */, From a94e0f156df9a84cc5bc86469ca4298c73605e5d Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 28 Sep 2026 16:40:26 -0300 Subject: [PATCH 105/120] fix(ios): present modals as native page sheets --- app/stacks/InsideStack.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/stacks/InsideStack.tsx b/app/stacks/InsideStack.tsx index d497134dfb3..1f8b581893c 100644 --- a/app/stacks/InsideStack.tsx +++ b/app/stacks/InsideStack.tsx @@ -328,8 +328,10 @@ const E2EEnterYourPasswordStack = createNativeStackNavigator({ return ; }); +const fullScreenPresentation = isIOS ? 'fullScreenModal' : 'containedModal'; + const InsideStack = createNativeStackNavigator({ - screenOptions: { ...defaultHeader, presentation: 'containedModal' }, + screenOptions: { ...defaultHeader, presentation: isIOS ? 'modal' : 'containedModal' }, screens: { DrawerNavigator: createNativeStackScreen({ screen: DrawerStack, @@ -347,16 +349,22 @@ const InsideStack = createNativeStackNavigator({ screen: E2EEnterYourPasswordStack, options: { headerShown: false } }), - AttachmentView: AttachmentViewScreen, + AttachmentView: createNativeStackScreen({ + screen: AttachmentViewScreen, + options: { presentation: fullScreenPresentation } + }), StatusView: StatusViewScreen, - ShareView: ShareViewScreen, + ShareView: createNativeStackScreen({ + screen: ShareViewScreen, + options: { presentation: fullScreenPresentation } + }), ModalBlockView: createNativeStackScreen({ screen: ModalBlockViewScreen, options: (args: any): NativeStackNavigationOptions => (ModalBlockView as any).navigationOptions(args) }), CallView: createNativeStackScreen({ screen: CallViewScreen, - options: { headerShown: false } + options: { headerShown: false, presentation: fullScreenPresentation } }) } }).with(({ Navigator }) => { From 989de54f238cf0cbda2cf1da8f69eee8cb202771 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 28 Sep 2026 17:28:43 -0300 Subject: [PATCH 106/120] chore: tidy RN 0.86 upgrade leftovers --- __mocks__/react-native-mmkv.js | 5 ----- android/settings.gradle | 2 -- app/containers/UIKit/UiKitModal.test.tsx | 7 +++++++ .../components/stories/__tests__/leaves.test.tsx | 7 +++++++ app/lib/methods/helpers/fetch.ts | 6 ------ ios/Podfile | 7 +++++-- ios/Podfile.lock | 2 +- jest.setup.js | 10 ---------- package.json | 1 - pnpm-lock.yaml | 1 - 10 files changed, 20 insertions(+), 28 deletions(-) diff --git a/__mocks__/react-native-mmkv.js b/__mocks__/react-native-mmkv.js index 4ca7eabb782..2426b2b20e6 100644 --- a/__mocks__/react-native-mmkv.js +++ b/__mocks__/react-native-mmkv.js @@ -1,7 +1,3 @@ -// Mock for react-native-mmkv. -// Covers only the surface app/lib/methods/userPreferences.ts uses: createMMKV, useMMKVString, -// and the instance methods it calls. Keep this in sync with that module — a method that exists -// here but not on the real MMKV instance makes Jest pass while the device fails. const { useState, useEffect, useCallback } = require('react'); // Shared storage between instances with the same id @@ -65,7 +61,6 @@ class MMKVInstance { clearAll() { this.storage.clear(); - // The real listener contract passes undefined to mean "everything changed" this.notifyListeners(undefined); } diff --git a/android/settings.gradle b/android/settings.gradle index 291937214cc..20c52556af3 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -14,6 +14,4 @@ include ':watermelondb-jsi' project(':watermelondb-jsi').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android-jsi') include ':app' -includeBuild('../node_modules/@react-native/gradle-plugin') - expoAutolinking.useExpoModules() diff --git a/app/containers/UIKit/UiKitModal.test.tsx b/app/containers/UIKit/UiKitModal.test.tsx index 6e94984375d..b93f37bba60 100644 --- a/app/containers/UIKit/UiKitModal.test.tsx +++ b/app/containers/UIKit/UiKitModal.test.tsx @@ -1,4 +1,11 @@ import { generateSnapshots } from '~/.rnstorybook/generateSnapshots'; import * as stories from './UiKitModal.stories'; +jest.mock('~/lib/methods/handleMediaDownload.ts', () => ({ + ...jest.requireActual('~/lib/methods/handleMediaDownload.ts'), + getMediaCache: jest.fn(() => Promise.resolve({ exists: false })), + downloadMediaFile: jest.fn(() => Promise.resolve('')), + isDownloadActive: jest.fn(() => false) +})); + generateSnapshots(stories); diff --git a/app/containers/message/components/stories/__tests__/leaves.test.tsx b/app/containers/message/components/stories/__tests__/leaves.test.tsx index ccf68731b89..4a969b2e400 100644 --- a/app/containers/message/components/stories/__tests__/leaves.test.tsx +++ b/app/containers/message/components/stories/__tests__/leaves.test.tsx @@ -21,6 +21,13 @@ import * as pinned from '../RightIcons/Pinned.stories'; import * as readReceipt from '../RightIcons/ReadReceipt.stories'; import * as translated from '../RightIcons/Translated.stories'; +jest.mock('~/lib/methods/handleMediaDownload.ts', () => ({ + ...jest.requireActual('~/lib/methods/handleMediaDownload.ts'), + getMediaCache: jest.fn(() => Promise.resolve({ exists: false })), + downloadMediaFile: jest.fn(() => Promise.resolve('')), + isDownloadActive: jest.fn(() => false) +})); + generateSnapshots(attachments); generateSnapshots(broadcast); generateSnapshots(callButton); diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index 33e68813f70..a6f31906f6b 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -47,11 +47,5 @@ export default (url: string, options: IOptions = {}): Promise => { ...options, headers: withoutEmptyValues({ ...options.headers, ...RocketChatSettings.customHeaders }) }; - // TODO: Check if this really works and if anyone else has complained about this problem. - // if (RocketChat.controller) { - // // @ts-ignore - // const { signal } = RocketChat.controller; - // customOptions = { ...customOptions, signal }; - // } return fetch(url, customOptions); }; diff --git a/ios/Podfile b/ios/Podfile index 9ad0df8bc26..f73893b1e2f 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -40,12 +40,15 @@ end $static_framework = [ 'WatermelonDB', - 'simdjson' + 'simdjson', + 'RNFBApp', + 'RNFBAnalytics', + 'RNFBCrashlytics' ] pre_install do |installer| Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} installer.pod_targets.each do |pod| - if $static_framework.include?(pod.name) || pod.name.start_with?('RNFB') + if $static_framework.include?(pod.name) def pod.build_type; Pod::BuildType.static_library # >= 1.9 end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f2a0eb6a6a2..93a1b65ffb4 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3623,6 +3623,6 @@ SPEC CHECKSUMS: Yoga: 269d501078efa6b25cd57f7f326fc3da213580be ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: b39e033fd5ceefcceb991d1d93ed4ea05c5752e0 +PODFILE CHECKSUM: 3ef18da69ac141a16cded1b74c0bc71b2f7216a7 COCOAPODS: 1.15.2 diff --git a/jest.setup.js b/jest.setup.js index 300cbd57106..506242c12db 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -6,16 +6,6 @@ import { Image } from 'expo-image'; jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage); -const resolveExpoLazyFetchBeforeReactNativeMocks = () => globalThis.fetch; -resolveExpoLazyFetchBeforeReactNativeMocks(); - -jest.mock('./app/lib/methods/handleMediaDownload', () => ({ - ...jest.requireActual('./app/lib/methods/handleMediaDownload'), - getMediaCache: jest.fn(() => Promise.resolve({ exists: false })), - downloadMediaFile: jest.fn(() => Promise.resolve('')), - isDownloadActive: jest.fn(() => false) -})); - jest.mock('react-native-safe-area-context', () => { const inset = { top: 0, right: 0, bottom: 0, left: 0 }; return { diff --git a/package.json b/package.json index ca9e103cd16..db1b1ad61ff 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,6 @@ "ua-parser-js": "1.0.2", "jpeg-js": "0.4.4", "typescript": "7.0.2", - "react-native-keyboard-controller": "1.18.5", "react-native-nitro-modules": "^0.37.1" }, "onlyBuiltDependencies": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f24110a636..b97a73ed1b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,7 +8,6 @@ overrides: ua-parser-js: 1.0.2 jpeg-js: 0.4.4 typescript: 7.0.2 - react-native-keyboard-controller: 1.18.5 react-native-nitro-modules: ^0.37.1 importers: From 681d520f15030c99297c1877f2386e047ae1d23f Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 29 Sep 2026 10:29:59 -0300 Subject: [PATCH 107/120] chore(deps): upgrade react-native-gesture-handler to 3.3.0 (#7726) * chore(deps): bump react-native-gesture-handler to 3.3.0 * refactor: migrate gestures to the gesture handler 3 hook API * refactor: replace deprecated gesture handler buttons with Touchable * chore(ios): update Podfile.lock for react-native-gesture-handler 3.3.0 * fix: restore disabled state on Touch after gesture handler 3 migration * fix(android): enable nested scroll on reactions lists inside the action sheet * fix(android): activate focused gesture-handler buttons on hardware Enter/D-pad --- app/containers/ActionSheet/Handle.tsx | 6 +- app/containers/AudioPlayer/Seek.test.tsx | 12 +- app/containers/AudioPlayer/Seek.tsx | 27 +- .../Avatar/__snapshots__/Avatar.test.tsx.snap | 99 +- app/containers/Button/Button.test.tsx | 2 +- .../Button/__snapshots__/Button.test.tsx.snap | 521 +- app/containers/Button/index.tsx | 22 +- app/containers/CallHeader.tsx | 4 +- .../__snapshots__/DirectoryItem.test.tsx.snap | 792 +- .../GestureButtons/BorderlessButton.tsx | 44 + app/containers/GestureButtons/RectButton.tsx | 50 + app/containers/GestureButtons/index.ts | 2 + app/containers/GestureButtons/types.ts | 29 + .../HeaderButton/HeaderButtonItem.tsx | 4 +- .../__snapshots__/HeaderButtons.test.tsx.snap | 1903 +- app/containers/ImageViewer/ImageViewer.tsx | 51 +- .../NotifierComponent.test.tsx.snap | 812 +- app/containers/List/ListItem.tsx | 2 +- .../List/__snapshots__/List.test.tsx.snap | 2394 +- .../__snapshots__/LoginServices.test.tsx.snap | 1378 +- .../MediaCallHeader.test.tsx.snap | 660 +- .../MessageComposer/MessageComposer.test.tsx | 8 +- .../MessageComposer.test.tsx.snap | 6288 +- .../AttachmentActionSheet.test.tsx.snap | 380 +- .../Autocomplete/AutocompleteItem.tsx | 2 +- .../Autocomplete/AutocompletePreview.tsx | 2 +- .../components/Buttons/BaseButton.tsx | 2 +- .../components/RecordAudio/ReviewButton.tsx | 2 +- .../components/SendThreadToChannel.tsx | 6 +- app/containers/NewMediaCall/SelectedPeer.tsx | 2 +- .../__snapshots__/SelectedPeer.test.tsx.snap | 336 +- app/containers/Passcode/Base/Button.tsx | 4 +- .../Base/__snapshots__/Base.test.tsx.snap | 6740 +- app/containers/Passcode/PasscodeChoose.tsx | 3 +- app/containers/Passcode/PasscodeEnter.tsx | 3 +- app/containers/ReactionsList/AllTab.tsx | 2 + app/containers/ReactionsList/UsersList.tsx | 2 + .../__snapshots__/ReactionsList.test.tsx.snap | 2 + app/containers/RoomHeader/RoomHeader.tsx | 8 +- .../__snapshots__/RoomHeader.test.tsx.snap | 7123 +- app/containers/RoomItem/Actions.tsx | 2 +- app/containers/RoomItem/Touchable.tsx | 39 +- .../__snapshots__/RoomItem.test.tsx.snap | 54322 ++++++++-------- .../SwipeableDeleteItem/Actions.tsx | 2 +- .../SwipeableDeleteItem/Touchable.tsx | 24 +- .../__snapshots__/ServerItem.test.tsx.snap | 1732 +- app/containers/TabView/index.tsx | 6 +- .../__snapshots__/TextInput.test.tsx.snap | 198 +- app/containers/Thumbs.tsx | 2 +- app/containers/Touch.tsx | 22 +- app/containers/UIKit/MultiSelect/Input.tsx | 2 +- .../__snapshots__/UiKitMessage.test.tsx.snap | 395 +- .../__snapshots__/UiKitModal.test.tsx.snap | 866 +- .../__snapshots__/Message.test.tsx.snap | 45048 ++++++------- .../__snapshots__/leaves.test.tsx.snap | 198 +- .../CannedResponseItem.test.tsx.snap | 370 +- .../__snapshots__/Item.test.tsx.snap | 792 +- app/views/NewMessageView/Item.tsx | 4 +- .../__snapshots__/Item.test.tsx.snap | 456 +- .../ServersHistoryItem.test.tsx.snap | 4076 +- .../__snapshots__/CallSection.test.tsx.snap | 299 +- app/views/RoomActionsView/index.tsx | 2 +- .../RoomInfoView/components/BaseButton.tsx | 4 +- .../RoomInfoButtons.test.tsx.snap | 1342 +- .../RoomInfoViewAvatar.test.tsx.snap | 80 +- app/views/RoomView/LoadMore/index.tsx | 2 +- app/views/RoomView/components/Banner.tsx | 3 +- app/views/RoomsListView/components/Header.tsx | 8 +- app/views/StatusView/index.test.tsx | 28 +- .../__snapshots__/Item.test.tsx.snap | 2376 +- .../ChangePasscodeView.test.tsx.snap | 2277 +- .../ScreenLockedView.test.tsx.snap | 4752 +- ios/Podfile.lock | 5 +- jest.config.js | 1 + jest.setup.js | 22 +- package.json | 2 +- ... react-native-gesture-handler+3.3.0.patch} | 32 +- pnpm-lock.yaml | 77 +- 78 files changed, 78025 insertions(+), 71572 deletions(-) create mode 100644 app/containers/GestureButtons/BorderlessButton.tsx create mode 100644 app/containers/GestureButtons/RectButton.tsx create mode 100644 app/containers/GestureButtons/index.ts create mode 100644 app/containers/GestureButtons/types.ts rename patches/{react-native-gesture-handler+2.32.0.patch => react-native-gesture-handler+3.3.0.patch} (54%) diff --git a/app/containers/ActionSheet/Handle.tsx b/app/containers/ActionSheet/Handle.tsx index 874a655028c..3d4d23a1192 100644 --- a/app/containers/ActionSheet/Handle.tsx +++ b/app/containers/ActionSheet/Handle.tsx @@ -1,7 +1,7 @@ -import { forwardRef, type ComponentType, type Ref } from 'react'; +import { forwardRef } from 'react'; import { View } from 'react-native'; -import { RectButton } from 'react-native-gesture-handler'; +import { RectButton } from '~/containers/GestureButtons'; import styles from './styles'; import { themes } from '~/lib/constants/colors'; import { useTheme } from '~/theme'; @@ -13,7 +13,7 @@ export const Handle = forwardRef void }>(({ onPress }, re // We should use RectButton from gesture-handler to avoid issues with the keyboard return ( } + ref={ref} onPress={onPress} style={styles.handle} testID='action-sheet-handle' diff --git a/app/containers/AudioPlayer/Seek.test.tsx b/app/containers/AudioPlayer/Seek.test.tsx index 7ba004704f3..489f489763d 100644 --- a/app/containers/AudioPlayer/Seek.test.tsx +++ b/app/containers/AudioPlayer/Seek.test.tsx @@ -1,22 +1,26 @@ import { act, render, renderHook } from '@testing-library/react-native'; -import { Gesture, State } from 'react-native-gesture-handler'; +import { State, usePanGesture } from 'react-native-gesture-handler'; import { fireGestureHandler } from 'react-native-gesture-handler/jest-utils'; import { useSharedValue } from 'react-native-reanimated'; import Seek from './Seek'; jest.mock('~/theme', () => ({ useTheme: () => ({ colors: {} }) })); +jest.mock('react-native-gesture-handler', () => { + const actual = jest.requireActual('react-native-gesture-handler'); + return { ...actual, usePanGesture: jest.fn(actual.usePanGesture) }; +}); const setup = () => { - const pan = jest.spyOn(Gesture, 'Pan'); const { result } = renderHook(() => ({ currentTime: useSharedValue(30), duration: useSharedValue(120) })); const { currentTime, duration } = result.current; const onChangeTime = jest.fn(); render(); - return { gesture: pan.mock.results[0].value, currentTime, onChangeTime }; + const { results } = jest.mocked(usePanGesture).mock; + return { gesture: results[results.length - 1].value, currentTime, onChangeTime }; }; -afterEach(() => jest.restoreAllMocks()); +afterEach(() => jest.clearAllMocks()); test('a tap that never starts dragging preserves the paused position', async () => { const { gesture, currentTime, onChangeTime } = setup(); diff --git a/app/containers/AudioPlayer/Seek.tsx b/app/containers/AudioPlayer/Seek.tsx index ebef805872b..31c884f93f7 100644 --- a/app/containers/AudioPlayer/Seek.tsx +++ b/app/containers/AudioPlayer/Seek.tsx @@ -1,5 +1,5 @@ import { type LayoutChangeEvent, View, TextInput, type TextInputProps, TouchableNativeFeedback } from 'react-native'; -import { Gesture, GestureDetector } from 'react-native-gesture-handler'; +import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; import Animated, { type SharedValue, useAnimatedProps, @@ -65,31 +65,32 @@ const Seek = ({ currentTime, duration, loaded = false, onChangeTime }: ISeek) => maxWidth.value = width; }; - const panGesture = Gesture.Pan() - .enabled(loaded) - .activeOffsetX([-ACTIVE_OFFSET_X, ACTIVE_OFFSET_X]) - .onStart(() => { + const panGesture = usePanGesture({ + enabled: loaded, + activeOffsetX: [-ACTIVE_OFFSET_X, ACTIVE_OFFSET_X], + onActivate: () => { isPanning.value = true; contextX.value = translateX.value; savedTranslateX.value = translateX.value; savedCurrentTime.value = currentTime.value; scale.value = withTiming(1.3, { duration: 150 }); - }) - .onUpdate(event => { + }, + onUpdate: event => { const newX = contextX.value + event.translationX; translateX.value = clamp(newX, 0, maxWidth.value); - }) - .onFinalize((_, didSucceed) => { + }, + onFinalize: event => { if (!isPanning.value) return; isPanning.value = false; scale.value = withTiming(1, { duration: 150 }); - if (didSucceed) { - scheduleOnRN(onChangeTime, currentTime.value); - } else { + if (event.canceled) { translateX.value = savedTranslateX.value; currentTime.value = savedCurrentTime.value; + } else { + scheduleOnRN(onChangeTime, currentTime.value); } - }); + } + }); useDerivedValue(() => { if (isPanning.value) { diff --git a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap index 24c8f2149b2..7be341954a9 100644 --- a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap +++ b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap @@ -926,60 +926,63 @@ exports[`Story Snapshots: Touchable should match snapshot 1`] = ` ] } > - - - + `; diff --git a/app/containers/Button/Button.test.tsx b/app/containers/Button/Button.test.tsx index 06829aa76b3..c36468daeff 100644 --- a/app/containers/Button/Button.test.tsx +++ b/app/containers/Button/Button.test.tsx @@ -63,7 +63,7 @@ describe('ButtonTests', () => { test('disabled button is in disabled state', async () => { const { findByTestId } = render(); const button = await findByTestId(testProps.testID); - expect(button.props.enabled).toBe(false); + expect(button).toBeDisabled(); }); test('should trigger onPress function on button press', async () => { diff --git a/app/containers/Button/__snapshots__/Button.test.tsx.snap b/app/containers/Button/__snapshots__/Button.test.tsx.snap index 1b0415bde25..e9de7a7a3aa 100644 --- a/app/containers/Button/__snapshots__/Button.test.tsx.snap +++ b/app/containers/Button/__snapshots__/Button.test.tsx.snap @@ -1,58 +1,63 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Story Snapshots: CustomButton should match snapshot 1`] = ` - - Press me! - + `; exports[`Story Snapshots: DisabledButton should match snapshot 1`] = ` - - Press me! - + `; exports[`Story Snapshots: DisabledLoadingButton should match snapshot 1`] = ` - - - + `; exports[`Story Snapshots: LoadingButton should match snapshot 1`] = ` - - - + `; exports[`Story Snapshots: PrimaryButton should match snapshot 1`] = ` - - Press me! - + `; exports[`Story Snapshots: SecondaryButton should match snapshot 1`] = ` - - Press me! - + `; exports[`Story Snapshots: SmallButton should match snapshot 1`] = ` - - Press me! - + `; diff --git a/app/containers/Button/index.tsx b/app/containers/Button/index.tsx index bbd625b5f1d..ac78814931b 100644 --- a/app/containers/Button/index.tsx +++ b/app/containers/Button/index.tsx @@ -1,13 +1,16 @@ import { type FC } from 'react'; -import { type StyleProp, StyleSheet, Text, type TextStyle, type ViewStyle } from 'react-native'; -import { RectButton, type RectButtonProps } from 'react-native-gesture-handler'; +import { type Insets, type StyleProp, StyleSheet, Text, type TextStyle, type ViewStyle } from 'react-native'; import { useTheme } from '~/theme'; import sharedStyles from '~/views/Styles'; import ActivityIndicator from '../ActivityIndicator'; +import { RectButton } from '../GestureButtons'; -interface IButtonProps extends Omit { +interface IButtonProps { title: string; + testID?: string; + hitSlop?: number | Insets; + accessibilityLabel?: string; onPress: () => void; type?: 'primary' | 'secondary'; backgroundColor?: string; @@ -61,7 +64,9 @@ const Button: FC = ({ style, styleText, small, - ...otherProps + testID, + hitSlop, + accessibilityLabel }) => { const { colors } = useTheme(); const isPrimary = type === 'primary'; @@ -90,11 +95,12 @@ const Button: FC = ({ return ( + testID={testID} + hitSlop={hitSlop} + accessibilityLabel={accessibilityLabel ?? title} + accessibilityRole='button'> {loading ? : {title}} ); diff --git a/app/containers/CallHeader.tsx b/app/containers/CallHeader.tsx index 51ccf7e3eb9..c2e13b71bdc 100644 --- a/app/containers/CallHeader.tsx +++ b/app/containers/CallHeader.tsx @@ -54,7 +54,7 @@ export const CallHeader = ({ mic, cam, setCam, setMic, title, avatar, uid, name, onPress={() => setCam(!cam)} style={[style.iconCallContainerRight, { backgroundColor: handleColors(cam).button }]} hitSlop={BUTTON_HIT_SLOP} - enabled={!calling}> + disabled={calling}> @@ -64,7 +64,7 @@ export const CallHeader = ({ mic, cam, setCam, setMic, title, avatar, uid, name, onPress={() => setMic(!mic)} style={[style.iconCallContainer, { backgroundColor: handleColors(mic).button }]} hitSlop={BUTTON_HIT_SLOP} - enabled={!calling}> + disabled={calling}> diff --git a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap index d5761084a08..1bc1299cc61 100644 --- a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap +++ b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap @@ -37,60 +37,63 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` ] } > - - - + @@ -331,60 +334,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - + @@ -623,60 +629,63 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` ] } > - - - + @@ -884,60 +893,63 @@ exports[`Story Snapshots: LongRoomName should match snapshot 1`] = ` ] } > - - - + @@ -1198,60 +1210,63 @@ exports[`Story Snapshots: OnlyTitle should match snapshot 1`] = ` ] } > - - - + @@ -1392,60 +1407,63 @@ exports[`Story Snapshots: TeamMain should match snapshot 1`] = ` ] } > - - - + @@ -1684,60 +1702,63 @@ exports[`Story Snapshots: WithRightLabel should match snapshot 1`] = ` ] } > - - - + @@ -1998,60 +2019,63 @@ exports[`Story Snapshots: WithoutDescription should match snapshot 1`] = ` ] } > - - - + diff --git a/app/containers/GestureButtons/BorderlessButton.tsx b/app/containers/GestureButtons/BorderlessButton.tsx new file mode 100644 index 00000000000..a960f434169 --- /dev/null +++ b/app/containers/GestureButtons/BorderlessButton.tsx @@ -0,0 +1,44 @@ +import { Touchable } from 'react-native-gesture-handler'; + +import { isAndroid } from '~/lib/methods/helpers/deviceInfo'; +import { type IGestureButtonProps } from './types'; + +const BorderlessButton = ({ + children, + ref, + onPress, + onLongPress, + disabled, + style, + hitSlop, + testID, + accessible, + accessibilityLabel, + accessibilityHint, + accessibilityRole, + accessibilityState, + rippleColor, + foreground, + activeOpacity = 0.3 +}: IGestureButtonProps) => ( + + {children} + +); + +export default BorderlessButton; diff --git a/app/containers/GestureButtons/RectButton.tsx b/app/containers/GestureButtons/RectButton.tsx new file mode 100644 index 00000000000..65da8156168 --- /dev/null +++ b/app/containers/GestureButtons/RectButton.tsx @@ -0,0 +1,50 @@ +import { Touchable } from 'react-native-gesture-handler'; + +import { isAndroid } from '~/lib/methods/helpers/deviceInfo'; +import { type IGestureButtonProps } from './types'; + +interface IRectButtonProps extends IGestureButtonProps { + underlayColor?: string; +} + +const RectButton = ({ + children, + ref, + onPress, + onLongPress, + disabled, + style, + hitSlop, + testID, + accessible, + accessibilityLabel, + accessibilityHint, + accessibilityRole, + accessibilityState, + rippleColor, + foreground, + underlayColor = 'black', + activeOpacity = 0.105 +}: IRectButtonProps) => ( + + {children} + +); + +export default RectButton; diff --git a/app/containers/GestureButtons/index.ts b/app/containers/GestureButtons/index.ts new file mode 100644 index 00000000000..868743c5260 --- /dev/null +++ b/app/containers/GestureButtons/index.ts @@ -0,0 +1,2 @@ +export { default as RectButton } from './RectButton'; +export { default as BorderlessButton } from './BorderlessButton'; diff --git a/app/containers/GestureButtons/types.ts b/app/containers/GestureButtons/types.ts new file mode 100644 index 00000000000..a9060235ddf --- /dev/null +++ b/app/containers/GestureButtons/types.ts @@ -0,0 +1,29 @@ +import { type ReactNode, type Ref } from 'react'; +import { type TouchableProps } from 'react-native-gesture-handler'; +import { + type AccessibilityRole, + type AccessibilityState, + type Insets, + type StyleProp, + type View, + type ViewStyle +} from 'react-native'; + +export interface IGestureButtonProps { + children?: ReactNode; + ref?: Ref; + onPress?: TouchableProps['onPress']; + onLongPress?: TouchableProps['onLongPress']; + disabled?: boolean; + style?: StyleProp; + hitSlop?: number | Insets; + testID?: string; + accessible?: boolean; + accessibilityLabel?: string; + accessibilityHint?: string; + accessibilityRole?: AccessibilityRole; + accessibilityState?: AccessibilityState; + rippleColor?: string; + foreground?: boolean; + activeOpacity?: number; +} diff --git a/app/containers/Header/components/HeaderButton/HeaderButtonItem.tsx b/app/containers/Header/components/HeaderButton/HeaderButtonItem.tsx index e79b2ed757a..54394ce983b 100644 --- a/app/containers/Header/components/HeaderButton/HeaderButtonItem.tsx +++ b/app/containers/Header/components/HeaderButton/HeaderButtonItem.tsx @@ -1,7 +1,7 @@ import { memo, type ReactElement } from 'react'; import { Platform, StyleSheet, Text, View } from 'react-native'; -import { BorderlessButton } from 'react-native-gesture-handler'; +import { BorderlessButton } from '~/containers/GestureButtons'; import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; import { useTheme } from '~/theme'; import sharedStyles from '~/views/Styles'; @@ -55,7 +55,7 @@ const Item = memo( }: IHeaderButtonItem): ReactElement => { const { colors } = useTheme(); return ( - + - - - + - - + - + - - + @@ -758,14 +813,29 @@ exports[`Story Snapshots: Common should match snapshot 1`] = ` ] } > - - + @@ -974,14 +1042,29 @@ exports[`Story Snapshots: Common should match snapshot 1`] = ` ] } > - - + - - + - - + @@ -1640,14 +1749,29 @@ exports[`Story Snapshots: Common should match snapshot 1`] = ` ] } > - - + @@ -1849,14 +1972,29 @@ exports[`Story Snapshots: Common should match snapshot 1`] = ` ] } > - - + @@ -2058,14 +2195,29 @@ exports[`Story Snapshots: Common should match snapshot 1`] = ` ] } > - - + @@ -2234,14 +2384,29 @@ exports[`Story Snapshots: Icons should match snapshot 1`] = ` ] } > - - + - - + @@ -2529,15 +2707,30 @@ exports[`Story Snapshots: Icons should match snapshot 1`] = ` ] } > - - - + - + - - - + - + @@ -3011,14 +3245,29 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` ] } > - - + - - + - - - + - + @@ -3562,14 +3851,29 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` ] } > - - + - - + - - - + - + @@ -4113,14 +4457,29 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` ] } > - - + - - + - - - + - + @@ -4623,14 +5022,29 @@ exports[`Story Snapshots: Title should match snapshot 1`] = ` ] } > - - + - - + @@ -4908,14 +5335,29 @@ exports[`Story Snapshots: Title should match snapshot 1`] = ` ] } > - - - + - + - - - + - + diff --git a/app/containers/ImageViewer/ImageViewer.tsx b/app/containers/ImageViewer/ImageViewer.tsx index 5fcb5585e93..f34e3ae7af5 100644 --- a/app/containers/ImageViewer/ImageViewer.tsx +++ b/app/containers/ImageViewer/ImageViewer.tsx @@ -1,6 +1,12 @@ import { useRef, useState, type ReactElement } from 'react'; import { type LayoutChangeEvent, StyleSheet, type StyleProp, type ViewStyle, View } from 'react-native'; -import { Gesture, GestureDetector } from 'react-native-gesture-handler'; +import { + GestureDetector, + usePanGesture, + usePinchGesture, + useSimultaneousGestures, + useTapGesture +} from 'react-native-gesture-handler'; import Animated, { withTiming, useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated'; import { Image, type ImageStyle } from 'expo-image'; @@ -91,36 +97,38 @@ export const ImageViewer = ({ uri = '', width, height, altText, isAnimated, ...p return Math.max(Math.min(value, max), min); }; - const pinchGesture = Gesture.Pinch() - .onUpdate(event => { + const pinchGesture = usePinchGesture({ + onUpdate: event => { scale.value = clamp(scaleOffset.value * (event.scale > 0 ? event.scale : 1), 1, 4); - }) - .onEnd(() => { + }, + onDeactivate: () => { scaleOffset.value = scale.value > 0 ? scale.value : 1; - }); + } + }); - const panGesture = Gesture.Pan() - .maxPointers(2) - .onStart(() => { + const panGesture = usePanGesture({ + maxPointers: 2, + onActivate: () => { translationX.value = offsetX.value; translationY.value = offsetY.value; - }) - .onUpdate(event => { + }, + onUpdate: event => { const scaleFactor = scale.value - 1; translationX.value = clamp(event.translationX + offsetX.value, -scaleFactor * centerX, scaleFactor * centerX); translationY.value = clamp(event.translationY + offsetY.value, -scaleFactor * centerY, scaleFactor * centerY); - }) - .onEnd(() => { + }, + onDeactivate: () => { offsetX.value = translationX.value; offsetY.value = translationY.value; if (scale.value === 1) resetScaleAnimation(); - }); + } + }); - const doubleTapGesture = Gesture.Tap() - .numberOfTaps(2) - .maxDelay(120) - .maxDistance(70) - .onEnd(event => { + const doubleTapGesture = useTapGesture({ + numberOfTaps: 2, + maxDelay: 120, + maxDistance: 70, + onDeactivate: event => { if (scaleOffset.value > 1) resetScaleAnimation(); else { scale.value = withTiming(2, { duration: 200 }); @@ -128,9 +136,10 @@ export const ImageViewer = ({ uri = '', width, height, altText, isAnimated, ...p offsetX.value = centerX - event.x; scaleOffset.value = 2; } - }); + } + }); - const gesture = Gesture.Simultaneous(pinchGesture, panGesture, doubleTapGesture); + const gesture = useSimultaneousGestures(pinchGesture, panGesture, doubleTapGesture); const { colors } = useTheme(); diff --git a/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap b/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap index c6e4ff178e4..5b926331829 100644 --- a/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap +++ b/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap @@ -46,13 +46,29 @@ exports[`Story Snapshots: ChannelMessage should match snapshot 1`] = ` ] } > - - - + - - @@ -350,7 +357,7 @@ exports[`Story Snapshots: ChannelMessage should match snapshot 1`] = `  - + `; @@ -401,13 +408,29 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` ] } > - - - + - - @@ -705,7 +719,7 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = `  - + `; @@ -756,13 +770,29 @@ exports[`Story Snapshots: WithBlackTheme should match snapshot 1`] = ` ] } > - - - + - - @@ -1060,7 +1081,7 @@ exports[`Story Snapshots: WithBlackTheme should match snapshot 1`] = `  - + `; @@ -1111,13 +1132,29 @@ exports[`Story Snapshots: WithDarkTheme should match snapshot 1`] = ` ] } > - - - + - - @@ -1415,7 +1443,7 @@ exports[`Story Snapshots: WithDarkTheme should match snapshot 1`] = `  - + `; diff --git a/app/containers/List/ListItem.tsx b/app/containers/List/ListItem.tsx index fb028afabd6..ed783e558b0 100644 --- a/app/containers/List/ListItem.tsx +++ b/app/containers/List/ListItem.tsx @@ -242,7 +242,7 @@ const Button = memo(({ onPress, backgroundColor, underlayColor, style, ...props onPress={handlePress} style={[{ backgroundColor: backgroundColor || colors.surfaceRoom }, style]} underlayColor={underlayColor} - enabled={!props.disabled || !!props.disabledReason}> + disabled={props.disabled && !props.disabledReason}> ); diff --git a/app/containers/List/__snapshots__/List.test.tsx.snap b/app/containers/List/__snapshots__/List.test.tsx.snap index 09d7aab5b6d..e13bf9441eb 100644 --- a/app/containers/List/__snapshots__/List.test.tsx.snap +++ b/app/containers/List/__snapshots__/List.test.tsx.snap @@ -753,60 +753,63 @@ exports[`Story Snapshots: ListItemWithRightContainerStyle should match snapshot ] } > - - @@ -993,7 +996,7 @@ exports[`Story Snapshots: ListItemWithRightContainerStyle should match snapshot - + `; @@ -1053,60 +1056,63 @@ exports[`Story Snapshots: Pressable should match snapshot 1`] = ` ] } > - - @@ -1173,7 +1179,7 @@ exports[`Story Snapshots: Pressable should match snapshot 1`] = ` - + - - @@ -1332,7 +1341,7 @@ exports[`Story Snapshots: Pressable should match snapshot 1`] = ` - + - - @@ -1576,7 +1588,7 @@ exports[`Story Snapshots: Radio should match snapshot 1`] = ` - + - - @@ -1782,7 +1797,7 @@ exports[`Story Snapshots: Radio should match snapshot 1`] = ` - + - - @@ -1988,7 +2006,7 @@ exports[`Story Snapshots: Radio should match snapshot 1`] = ` - + - - @@ -2216,7 +2237,7 @@ exports[`Story Snapshots: Radio should match snapshot 1`] = ` - + - - @@ -3982,7 +4006,7 @@ exports[`Story Snapshots: WithBiggerFont should match snapshot 1`] = ` - + - - @@ -4258,7 +4285,7 @@ exports[`Story Snapshots: WithBiggerFont should match snapshot 1`] = ` - + - - @@ -4615,7 +4645,7 @@ exports[`Story Snapshots: WithBiggerFont should match snapshot 1`] = ` - + - - @@ -4891,7 +4924,7 @@ exports[`Story Snapshots: WithBiggerFont should match snapshot 1`] = ` - + - - @@ -5295,7 +5331,7 @@ exports[`Story Snapshots: WithBlackTheme should match snapshot 1`] = ` - + - - @@ -5571,7 +5610,7 @@ exports[`Story Snapshots: WithBlackTheme should match snapshot 1`] = ` - + - - @@ -5928,7 +5970,7 @@ exports[`Story Snapshots: WithBlackTheme should match snapshot 1`] = ` - + - - @@ -6204,7 +6249,7 @@ exports[`Story Snapshots: WithBlackTheme should match snapshot 1`] = ` - + - - @@ -6515,7 +6563,7 @@ exports[`Story Snapshots: WithCustomColors should match snapshot 1`] = ` - + - - @@ -6886,7 +6937,7 @@ exports[`Story Snapshots: WithDarkTheme should match snapshot 1`] = ` - + - - @@ -7162,7 +7216,7 @@ exports[`Story Snapshots: WithDarkTheme should match snapshot 1`] = ` - + - - @@ -7519,7 +7576,7 @@ exports[`Story Snapshots: WithDarkTheme should match snapshot 1`] = ` - + - - @@ -7795,7 +7855,7 @@ exports[`Story Snapshots: WithDarkTheme should match snapshot 1`] = ` - + - - @@ -9867,7 +9930,7 @@ exports[`Story Snapshots: WithSmallFont should match snapshot 1`] = ` - + - - @@ -10143,7 +10209,7 @@ exports[`Story Snapshots: WithSmallFont should match snapshot 1`] = ` - + - - @@ -10500,7 +10569,7 @@ exports[`Story Snapshots: WithSmallFont should match snapshot 1`] = ` - + - - @@ -10776,7 +10848,7 @@ exports[`Story Snapshots: WithSmallFont should match snapshot 1`] = ` - + - More options - , + , , - - Less options - , + , - Login on web - , + , - - - + , - - - + , - - - + , ] `; exports[`Story Snapshots: ServiceListUncollapsed should match snapshot 1`] = ` [ - - Login on web - , + , - - - + , - - - + , - - - + , - - - + , ] `; @@ -1613,60 +1661,64 @@ exports[`Story Snapshots: ServicesList should match snapshot 1`] = ` ] } > - - - + , - - - + , - - - + , - - - + , ] `; diff --git a/app/containers/MediaCallHeader/__snapshots__/MediaCallHeader.test.tsx.snap b/app/containers/MediaCallHeader/__snapshots__/MediaCallHeader.test.tsx.snap index 362aca97c9c..3c6d903cd60 100644 --- a/app/containers/MediaCallHeader/__snapshots__/MediaCallHeader.test.tsx.snap +++ b/app/containers/MediaCallHeader/__snapshots__/MediaCallHeader.test.tsx.snap @@ -46,14 +46,29 @@ exports[`Story Snapshots: ActiveCall should match snapshot 1`] = ` ] } > - - + - - + @@ -377,14 +403,29 @@ exports[`Story Snapshots: Collapsed should match snapshot 1`] = ` ] } > - - + - - + @@ -708,14 +760,29 @@ exports[`Story Snapshots: ConnectingCall should match snapshot 1`] = ` ] } > - - + - - + @@ -1059,14 +1137,29 @@ exports[`Story Snapshots: Focused should match snapshot 1`] = ` ] } > - - + - - + @@ -1410,14 +1514,29 @@ exports[`Story Snapshots: WithRemoteHeld should match snapshot 1`] = ` ] } > - - + - - + @@ -1765,14 +1895,29 @@ exports[`Story Snapshots: WithRemoteMuted should match snapshot 1`] = ` ] } > - - + - - + diff --git a/app/containers/MessageComposer/MessageComposer.test.tsx b/app/containers/MessageComposer/MessageComposer.test.tsx index c63cc090e1d..56dce8186ae 100644 --- a/app/containers/MessageComposer/MessageComposer.test.tsx +++ b/app/containers/MessageComposer/MessageComposer.test.tsx @@ -876,11 +876,13 @@ describe('MessageComposer', () => { await screen.findByTestId('message-composer-attachment-0'); await screen.findByTestId('composer-quote-abc'); await fireEvent.changeText(screen.getByTestId('message-composer-input'), 'caption'); - const sendPromise = user.press(screen.getByTestId('message-composer-send')); + await user.press(screen.getByTestId('message-composer-send')); await waitFor(() => expect(sendFileMessage).toHaveBeenCalled()); await fireEvent.changeText(screen.getByTestId('message-composer-input'), 'typed while uploading'); - resolveUpload(); - await sendPromise; + await act(() => { + resolveUpload(); + return Promise.resolve(); + }); await waitFor(() => expect(composerRef.current?.getText()).toBe('')); expect(screen.queryByTestId('composer-quote-abc')).not.toBeOnTheScreen(); diff --git a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap index 3c76e098373..087e172b4a8 100644 --- a/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap +++ b/app/containers/MessageComposer/__snapshots__/MessageComposer.test.tsx.snap @@ -77,13 +77,29 @@ exports[`MessageComposer Audio tap record 1`] = ` } } > - - + - - + , - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + , - - + - - + - - + - - + - - + - @@ -3213,7 +3681,7 @@ exports[`MessageComposer Toolbar Markdown tap code 1`] = `  - + - - + , - - + - - + - - + - - + - - + - - + - - + , - - + - - + - - + - - + - - + - - + - - + , - - + - - + - - + - - + - - + - - + - - + , - - + - - + - - + - - + - - + - - + - - + , - - + - - + - - + - - + - @@ -6280,7 +7360,7 @@ exports[`MessageComposer Toolbar Markdown type test and tap bold 1`] = `  - + - - + - - + , - - + - - + - - + - - + - - + - - + - - + , - - + - - + - - + - - + - - + - - + - - + , - - + - - + - - + - - + - - + - - + - - + , - - + - - + - - + - - + - - + - - + - - + , - - + - - + - - + - @@ -9347,7 +11039,7 @@ exports[`MessageComposer Toolbar tap actions 1`] = `  - + - - + , - - + - - + - - + - - + - - + , - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - - -  - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - + - - + - - + - - + - - + - - + , - - Save - + `; @@ -267,56 +273,62 @@ exports[`Story Snapshots: Image should match snapshot 1`] = ` } /> - - Save - + `; @@ -424,56 +436,62 @@ exports[`Story Snapshots: ImageOnLegacyServer should match snapshot 1`] = ` } /> - - Save - + `; @@ -581,56 +599,62 @@ exports[`Story Snapshots: ImageWithPrefilledAltText should match snapshot 1`] = } /> - - Save - + `; @@ -768,56 +792,62 @@ exports[`Story Snapshots: Video should match snapshot 1`] = ` - - Save - + `; diff --git a/app/containers/MessageComposer/components/Autocomplete/AutocompleteItem.tsx b/app/containers/MessageComposer/components/Autocomplete/AutocompleteItem.tsx index b07277ec248..39ad2a018b6 100644 --- a/app/containers/MessageComposer/components/Autocomplete/AutocompleteItem.tsx +++ b/app/containers/MessageComposer/components/Autocomplete/AutocompleteItem.tsx @@ -1,6 +1,6 @@ import { View } from 'react-native'; -import { RectButton } from 'react-native-gesture-handler'; +import { RectButton } from '~/containers/GestureButtons'; import i18n from '~/i18n'; import { type IAutocompleteItemProps, type TAutocompleteItem } from '~/containers/MessageComposer/interfaces'; import { AutocompleteUserRoom } from './AutocompleteUserRoom'; diff --git a/app/containers/MessageComposer/components/Autocomplete/AutocompletePreview.tsx b/app/containers/MessageComposer/components/Autocomplete/AutocompletePreview.tsx index ef4cf1981f9..cb6476d1bb4 100644 --- a/app/containers/MessageComposer/components/Autocomplete/AutocompletePreview.tsx +++ b/app/containers/MessageComposer/components/Autocomplete/AutocompletePreview.tsx @@ -1,6 +1,6 @@ -import { RectButton } from 'react-native-gesture-handler'; import { Image } from 'expo-image'; +import { RectButton } from '~/containers/GestureButtons'; import { type IAutocompleteItemProps } from '~/containers/MessageComposer/interfaces'; import { CustomIcon } from '~/containers/CustomIcon'; import { AutocompleteItemLoading } from './AutocompleteItemLoading'; diff --git a/app/containers/MessageComposer/components/Buttons/BaseButton.tsx b/app/containers/MessageComposer/components/Buttons/BaseButton.tsx index e62678de629..4de6290c412 100644 --- a/app/containers/MessageComposer/components/Buttons/BaseButton.tsx +++ b/app/containers/MessageComposer/components/Buttons/BaseButton.tsx @@ -1,6 +1,6 @@ -import { BorderlessButton } from 'react-native-gesture-handler'; import { View, StyleSheet, useWindowDimensions } from 'react-native'; +import { BorderlessButton } from '~/containers/GestureButtons'; import I18n from '~/i18n'; import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; import { useMessageComposerApi } from '~/containers/MessageComposer/context'; diff --git a/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx b/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx index 852f48c2080..91ea88ce580 100644 --- a/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx +++ b/app/containers/MessageComposer/components/RecordAudio/ReviewButton.tsx @@ -1,7 +1,7 @@ import { StyleSheet, View } from 'react-native'; import { type ReactElement } from 'react'; -import { BorderlessButton } from 'react-native-gesture-handler'; +import { BorderlessButton } from '~/containers/GestureButtons'; import i18n from '~/i18n'; import { useTheme } from '~/theme'; import { CustomIcon } from '~/containers/CustomIcon'; diff --git a/app/containers/MessageComposer/components/SendThreadToChannel.tsx b/app/containers/MessageComposer/components/SendThreadToChannel.tsx index 416cc6e4b23..1ed5aa4fe11 100644 --- a/app/containers/MessageComposer/components/SendThreadToChannel.tsx +++ b/app/containers/MessageComposer/components/SendThreadToChannel.tsx @@ -1,4 +1,4 @@ -import { TouchableWithoutFeedback } from 'react-native-gesture-handler'; +import { Touchable } from 'react-native-gesture-handler'; import { StyleSheet, Text } from 'react-native'; import { useEffect, useRef, type ReactElement } from 'react'; import { type Subscription } from 'rxjs'; @@ -68,7 +68,7 @@ export const SendThreadToChannel = (): ReactElement | null => { } return ( - setAlsoSendThreadToChannel(!alsoSendThreadToChannel)} testID='message-composer-send-to-channel'> @@ -79,7 +79,7 @@ export const SendThreadToChannel = (): ReactElement | null => { color={alsoSendThreadToChannel ? colors.buttonBackgroundPrimaryDefault : colors.fontDefault} /> {I18n.t('Message_composer_Send_to_channel')} - + ); }; diff --git a/app/containers/NewMediaCall/SelectedPeer.tsx b/app/containers/NewMediaCall/SelectedPeer.tsx index 611593265ae..c95bbdb8b0e 100644 --- a/app/containers/NewMediaCall/SelectedPeer.tsx +++ b/app/containers/NewMediaCall/SelectedPeer.tsx @@ -1,6 +1,6 @@ import { View, StyleSheet } from 'react-native'; -import { BorderlessButton } from 'react-native-gesture-handler'; +import { BorderlessButton } from '~/containers/GestureButtons'; import { useTheme } from '~/theme'; import { CustomIcon } from '../CustomIcon'; import I18n from '~/i18n'; diff --git a/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap b/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap index c2250ce3384..bdccb07cec3 100644 --- a/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap +++ b/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap @@ -192,33 +192,45 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` - @@ -248,7 +260,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` >  - + - @@ -424,7 +448,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` >  - + - @@ -665,7 +701,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` >  - + @@ -863,33 +899,45 @@ exports[`Story Snapshots: LongUsername should match snapshot 1`] = ` - @@ -919,7 +967,7 @@ exports[`Story Snapshots: LongUsername should match snapshot 1`] = ` >  - + @@ -1052,33 +1100,45 @@ exports[`Story Snapshots: Sip should match snapshot 1`] = ` - @@ -1108,7 +1168,7 @@ exports[`Story Snapshots: Sip should match snapshot 1`] = ` >  - + @@ -1306,33 +1366,45 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` - @@ -1362,7 +1434,7 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` >  - + diff --git a/app/containers/Passcode/Base/Button.tsx b/app/containers/Passcode/Base/Button.tsx index 15c3dccbc2f..cd61ab491a8 100644 --- a/app/containers/Passcode/Base/Button.tsx +++ b/app/containers/Passcode/Base/Button.tsx @@ -9,13 +9,12 @@ import { useTheme } from '~/theme'; interface IPasscodeButton { text?: string; icon?: TIconsName; - disabled?: boolean; onPress?: Function; style?: StyleProp; testID?: string; } -const Button = memo(({ style, text, disabled, onPress, icon, testID }: IPasscodeButton) => { +const Button = memo(({ style, text, onPress, icon, testID }: IPasscodeButton) => { const { colors } = useTheme(); const press = () => onPress && onPress(text); @@ -26,7 +25,6 @@ const Button = memo(({ style, text, disabled, onPress, icon, testID }: IPasscode style={[styles.buttonView, { backgroundColor: 'transparent' }, style]} underlayColor={colors.buttonBackgroundSecondaryDefault} rippleColor={colors.buttonBackgroundSecondaryPress} - enabled={!disabled} onPress={press}> {icon ? ( diff --git a/app/containers/Passcode/Base/__snapshots__/Base.test.tsx.snap b/app/containers/Passcode/Base/__snapshots__/Base.test.tsx.snap index a88d06d121c..e514a45890f 100644 --- a/app/containers/Passcode/Base/__snapshots__/Base.test.tsx.snap +++ b/app/containers/Passcode/Base/__snapshots__/Base.test.tsx.snap @@ -317,60 +317,63 @@ exports[`Story Snapshots: ChooseType should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -704,60 +713,63 @@ exports[`Story Snapshots: ChooseType should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -1091,60 +1109,63 @@ exports[`Story Snapshots: ChooseType should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -1491,60 +1518,63 @@ exports[`Story Snapshots: ChooseType should match snapshot 1`] = ` ] } > - - - + - - - + @@ -2034,60 +2067,63 @@ exports[`Story Snapshots: ConfirmType should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -2421,60 +2463,63 @@ exports[`Story Snapshots: ConfirmType should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -2808,60 +2859,63 @@ exports[`Story Snapshots: ConfirmType should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -3208,60 +3268,63 @@ exports[`Story Snapshots: ConfirmType should match snapshot 1`] = ` ] } > - - - + - - - + @@ -3751,60 +3817,63 @@ exports[`Story Snapshots: EnterType should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -4138,60 +4213,63 @@ exports[`Story Snapshots: EnterType should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -4525,60 +4609,63 @@ exports[`Story Snapshots: EnterType should match snapshot 1`] = ` ] } > - - - + - - + > - + - - - + @@ -4925,60 +5018,63 @@ exports[`Story Snapshots: EnterType should match snapshot 1`] = ` ] } > - - - + - - - + @@ -5508,60 +5607,63 @@ exports[`Story Snapshots: EnterWithSubtitleAndBiometry should match snapshot 1`] ] } > - - - + - - - + - - - + @@ -5895,60 +6003,63 @@ exports[`Story Snapshots: EnterWithSubtitleAndBiometry should match snapshot 1`] ] } > - - - + - - - + - - - + @@ -6282,60 +6399,63 @@ exports[`Story Snapshots: EnterWithSubtitleAndBiometry should match snapshot 1`] ] } > - - - + - - - + - - - + @@ -6669,60 +6795,63 @@ exports[`Story Snapshots: EnterWithSubtitleAndBiometry should match snapshot 1`] ] } > - - - + - - - + - - - + @@ -7342,60 +7477,63 @@ exports[`Story Snapshots: WithBiometry should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -7729,60 +7873,63 @@ exports[`Story Snapshots: WithBiometry should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -8116,60 +8269,63 @@ exports[`Story Snapshots: WithBiometry should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -8503,60 +8665,63 @@ exports[`Story Snapshots: WithBiometry should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -9216,60 +9387,63 @@ exports[`Story Snapshots: WithSubtitle should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -9603,60 +9783,63 @@ exports[`Story Snapshots: WithSubtitle should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -9990,60 +10179,63 @@ exports[`Story Snapshots: WithSubtitle should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -10390,60 +10588,63 @@ exports[`Story Snapshots: WithSubtitle should match snapshot 1`] = ` ] } > - - - + - - - + diff --git a/app/containers/Passcode/PasscodeChoose.tsx b/app/containers/Passcode/PasscodeChoose.tsx index 268809313e3..55587369df4 100644 --- a/app/containers/Passcode/PasscodeChoose.tsx +++ b/app/containers/Passcode/PasscodeChoose.tsx @@ -1,6 +1,5 @@ import { useRef, useState } from 'react'; import * as Haptics from 'expo-haptics'; -import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; import Base, { type IBase } from './Base'; import { TYPE } from './constants'; @@ -62,4 +61,4 @@ const PasscodeChoose = ({ finishProcess, force = false }: IPasscodeChoose) => { ); }; -export default gestureHandlerRootHOC(PasscodeChoose); +export default PasscodeChoose; diff --git a/app/containers/Passcode/PasscodeEnter.tsx b/app/containers/Passcode/PasscodeEnter.tsx index e7a211e1b98..2b5b28e12e8 100644 --- a/app/containers/Passcode/PasscodeEnter.tsx +++ b/app/containers/Passcode/PasscodeEnter.tsx @@ -1,6 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useAsyncStorage } from '@react-native-async-storage/async-storage'; -import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; import * as Haptics from 'expo-haptics'; import { sha256 } from 'js-sha256'; @@ -136,4 +135,4 @@ const PasscodeEnter = ({ hasBiometry: initialHasBiometry, reason: initialReason, ); }; -export default gestureHandlerRootHOC(PasscodeEnter); +export default PasscodeEnter; diff --git a/app/containers/ReactionsList/AllTab.tsx b/app/containers/ReactionsList/AllTab.tsx index d120a1f5f08..6bb8e909d34 100644 --- a/app/containers/ReactionsList/AllTab.tsx +++ b/app/containers/ReactionsList/AllTab.tsx @@ -5,6 +5,7 @@ import Emoji from '../message/components/Emoji'; import { useTheme } from '~/theme'; import { type IReaction } from '~/definitions'; import I18n from '~/i18n'; +import { isAndroid } from '~/lib/methods/helpers'; import styles from './styles'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; @@ -61,6 +62,7 @@ const AllTab = ({ reactions }: IAllTabProps): ReactElement => ( } keyExtractor={item => item.emoji} /> diff --git a/app/containers/ReactionsList/UsersList.tsx b/app/containers/ReactionsList/UsersList.tsx index 1292bdc9fb0..3390dc4ecde 100644 --- a/app/containers/ReactionsList/UsersList.tsx +++ b/app/containers/ReactionsList/UsersList.tsx @@ -5,6 +5,7 @@ import { useSelector } from 'react-redux'; import { useTheme } from '~/theme'; import { type IApplicationState } from '~/definitions'; import Avatar from '../Avatar'; +import { isAndroid } from '~/lib/methods/helpers'; import styles from './styles'; const UsersList = ({ emoji, usernames, names }: { emoji: string; usernames: string[]; names: string[] }): ReactElement => { @@ -20,6 +21,7 @@ const UsersList = ({ emoji, usernames, names }: { emoji: string; usernames: stri diff --git a/app/containers/ReactionsList/__snapshots__/ReactionsList.test.tsx.snap b/app/containers/ReactionsList/__snapshots__/ReactionsList.test.tsx.snap index 9ee970be89c..27dac4757df 100644 --- a/app/containers/ReactionsList/__snapshots__/ReactionsList.test.tsx.snap +++ b/app/containers/ReactionsList/__snapshots__/ReactionsList.test.tsx.snap @@ -656,6 +656,7 @@ exports[`Story Snapshots: ReactionsListFullName should match snapshot 1`] = ` getItem={[Function]} getItemCount={[Function]} keyExtractor={[Function]} + nestedScrollEnabled={false} onContentSizeChange={[Function]} onLayout={[Function]} onMomentumScrollBegin={[Function]} @@ -1803,6 +1804,7 @@ exports[`Story Snapshots: ReactionsListStory should match snapshot 1`] = ` getItem={[Function]} getItemCount={[Function]} keyExtractor={[Function]} + nestedScrollEnabled={false} onContentSizeChange={[Function]} onLayout={[Function]} onMomentumScrollBegin={[Function]} diff --git a/app/containers/RoomHeader/RoomHeader.tsx b/app/containers/RoomHeader/RoomHeader.tsx index 06b833796c9..df29c682607 100644 --- a/app/containers/RoomHeader/RoomHeader.tsx +++ b/app/containers/RoomHeader/RoomHeader.tsx @@ -1,6 +1,6 @@ import { memo, type ReactElement } from 'react'; import { StyleSheet, Text, useWindowDimensions, View } from 'react-native'; -import { TouchableOpacity } from 'react-native-gesture-handler'; +import { Touchable } from 'react-native-gesture-handler'; import { KeyboardFocusView } from 'react-native-external-keyboard'; import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; @@ -234,7 +234,9 @@ const Header = ({ focusable={!disabled} canBeFocused={!disabled} style={[styles.container, { opacity: disabled ? 0.5 : 1, height: 36.9 * fontScale }]}> - - + ); }; diff --git a/app/containers/RoomHeader/__snapshots__/RoomHeader.test.tsx.snap b/app/containers/RoomHeader/__snapshots__/RoomHeader.test.tsx.snap index 05864785722..5a1550ad232 100644 --- a/app/containers/RoomHeader/__snapshots__/RoomHeader.test.tsx.snap +++ b/app/containers/RoomHeader/__snapshots__/RoomHeader.test.tsx.snap @@ -42,13 +42,36 @@ exports[`Story Snapshots: DM_Status should match snapshot 1`] = ` ] } > - - - -  - - - John Doe - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - Online + John Doe - + + Online + + , - - - -  - - - John Doe - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - Away + John Doe - + + Away + + , - - - -  - - - John Doe - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - Busy + John Doe - + + Busy + + , - - - -  - - - John Doe - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - Offline + John Doe - + + Offline + + , - - - -  - - - John Doe - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - In a meeting + John Doe - + + In a meeting + + , - - - -  - - - John Doe - - - - + -  - - + John Doe + + + + - In a meeting - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + In a meeting + - + , - - - -  - - - John Doe - - - - + -  - - + John Doe + + + + - having lunch (clock icon should be visible) - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + having lunch (clock icon should be visible) + - + , - - - -  - - - John Doe - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - In a meeting (past expiry — clock icon should be hidden) + John Doe - + + In a meeting (past expiry — clock icon should be hidden) + + , ] @@ -1697,13 +1753,36 @@ exports[`Story Snapshots: Icons should match snapshot 1`] = ` ] } > - - - -  - - - private channel - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + private channel + - + , - - - -  - - - public channel - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + public channel + - + , - - - -  - - - discussion - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + discussion + - + , - - - -  - - - omnichannel - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + omnichannel + - + , - - - -  - - - private team - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + private team + - + , - - - -  - - - public team - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + public team + - + , - - - -  - - - group dm - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + group dm + - + , - - - -  - - - online dm - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + online dm + - + , - - - -  - - - away dm - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + away dm + - + , - - - -  - - - busy dm - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + busy dm + - + , - - - -  - - - loading dm - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + loading dm + - + , - - - -  - - - offline dm - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + offline dm + - + , - - - -  - - - classified - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + classified + - + , - - - -  - - - classified - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + classified + - + , ] @@ -3804,13 +3958,36 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` ] } > - - - -  - - - title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - subtitle + title - + + subtitle + + , - - - -  - - - title - - - +  + + - subtitle + title - + + subtitle + + , - - - -  - - - title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - subtitle + title - + + subtitle + + , ] @@ -4373,13 +4548,36 @@ exports[`Story Snapshots: Thread should match snapshot 1`] = ` ] } > - - - - title - - - + + - + -  - - - parent title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + parent title + - + , - - - - markdown preview #3 4 5 - - - - + + + -  - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + - + , ] @@ -4778,13 +4967,36 @@ exports[`Story Snapshots: TitleSubtitle should match snapshot 1`] = ` ] } > - - - -  - - - title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + title + - + , - - - -  - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + - + , - - - -  - - - title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - subtitle + title - + + subtitle + + , - - - -  - - - title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + title - + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + , - - - -  - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - - + > +  + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + , ] @@ -5646,13 +5870,36 @@ exports[`Story Snapshots: Typing should match snapshot 1`] = ` ] } > - - - -  - - - title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - user 1 - - - is typing - ... + title - + + + user 1 + + + is typing + ... + + , - - - -  - - - title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - user 1 and user 2 - - - are typing - ... + title - + + + user 1 and user 2 + + + are typing + ... + + , - - - -  - - - title - - + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - user 1, user 2, user 3, user 4, user 5 - - - are typing - ... + title - + + + user 1, user 2, user 3, user 4, user 5 + + + are typing + ... + + , ] diff --git a/app/containers/RoomItem/Actions.tsx b/app/containers/RoomItem/Actions.tsx index ed14ae1e18a..c215627cfed 100644 --- a/app/containers/RoomItem/Actions.tsx +++ b/app/containers/RoomItem/Actions.tsx @@ -7,10 +7,10 @@ import Animated, { useAnimatedReaction, useSharedValue } from 'react-native-reanimated'; -import { RectButton } from 'react-native-gesture-handler'; import * as Haptics from 'expo-haptics'; import { scheduleOnRN } from 'react-native-worklets'; +import { RectButton } from '~/containers/GestureButtons'; import { CustomIcon } from '../CustomIcon'; import { DisplayMode } from '~/lib/constants/constantDisplayMode'; import styles, { ACTION_WIDTH, LONG_SWIPE } from './styles'; diff --git a/app/containers/RoomItem/Touchable.tsx b/app/containers/RoomItem/Touchable.tsx index 0db3467dc3b..bf828e93013 100644 --- a/app/containers/RoomItem/Touchable.tsx +++ b/app/containers/RoomItem/Touchable.tsx @@ -1,10 +1,11 @@ import { useRef, memo, type ReactElement } from 'react'; import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated'; import { - Gesture, GestureDetector, - type GestureUpdateEvent, - type PanGestureHandlerEventPayload + type PanGestureActiveEvent, + useCompetingGestures, + useLongPressGesture, + usePanGesture } from 'react-native-gesture-handler'; import { scheduleOnRN } from 'react-native-worklets'; @@ -90,7 +91,7 @@ const Touchable = ({ } }; - const handleRelease = (event: GestureUpdateEvent) => { + const handleRelease = (event: PanGestureActiveEvent) => { const { translationX } = event; valueRef.current += translationX; let toValue = 0; @@ -169,27 +170,29 @@ const Touchable = ({ valueRef.current = toValue; }; - const longPressGesture = Gesture.LongPress() - .minDuration(500) - .onStart(() => { + const longPressGesture = useLongPressGesture({ + minDuration: 500, + onActivate: () => { scheduleOnRN(handleLongPress); - }); + } + }); - const panGesture = Gesture.Pan() - .activeOffsetX([-10, 10]) // More sensitive horizontal detection - .failOffsetY([-20, 20]) // Fail on vertical movement to distinguish scrolling - .enabled(swipeEnabled) - .onUpdate(event => { + const panGesture = usePanGesture({ + activeOffsetX: [-10, 10], // More sensitive horizontal detection + failOffsetY: [-20, 20], // Fail on vertical movement to distinguish scrolling + enabled: swipeEnabled, + onUpdate: event => { transX.value = event.translationX + rowOffSet.value; if (transX.value > 2 * width) transX.value = 2 * width; - }) - .onEnd(event => { + }, + onDeactivate: event => { scheduleOnRN(handleRelease, event); - }); + } + }); - // Use Race instead of Simultaneous to prevent conflicts + // Use competing gestures instead of simultaneous to prevent conflicts // Pan gesture will take priority over long press for horizontal swipes - const composedGesture = Gesture.Race(panGesture, longPressGesture); + const composedGesture = useCompetingGestures(panGesture, longPressGesture); const animatedStyles = useAnimatedStyle(() => ({ transform: [{ translateX: transX.value }] diff --git a/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap b/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap index cd35cf933d4..ec31857a13f 100644 --- a/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap +++ b/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap @@ -3,46 +3,41 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ - + } + > + @@ -55,563 +50,542 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - + > +  + + rocket.cat + + + - 10:00 - + [ + { + "backgroundColor": "transparent", + "fontFamily": "Inter", + "fontVariant": [ + "tabular-nums", + ], + "fontWeight": "600", + "textAlign": "left", + }, + { + "color": "#1D74F5", + }, + ], + ] + } + > + 10:00 + + - - + + , - + } + > + @@ -712,563 +722,542 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - - - - -  - - + + + - unread - - + > +  + - 10:00 + unread - 1 + 10:00 + + + 1 + + - - + + , - + } + > + @@ -1413,563 +1438,542 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - unread - - + > +  + - 10:00 + unread - +999 + 10:00 - - - - - - - - - - , - - + + +999 + + + + + + + + + + + + , + + } + > + + + + + +  + + + + + + @@ -2111,28 +2278,53 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - -  - - - - +  + + + + + - - -  - - - - - - - - - - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - user mentions - - + > +  + - 10:00 + user mentions - 1 + 10:00 + + + 1 + + - - + + , - + } + > + @@ -2815,563 +2870,542 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - group mentions - - + > +  + - 10:00 + group mentions - 1 + 10:00 + + + 1 + + - - + + , - + } + > + @@ -3516,563 +3586,542 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - - - - - +  + + + + + - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - thread unread - - + > +  + - 10:00 + thread unread - 1 + 10:00 + + + 1 + + - - + + , - + } + > + @@ -4217,563 +4302,542 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - - - - -  - - + + + - thread unread user - - + > +  + - 10:00 + thread unread user - 1 + 10:00 + + + 1 + + - - + + , - + } + > + @@ -4918,563 +5018,542 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - thread unread group - - + > +  + - 10:00 + thread unread group - 1 + 10:00 - - - - - - - - - - , - - + + 1 + + + + + + + + + + + + , + + } + > + + + + + +  + + + + + + @@ -5616,28 +5858,53 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - -  - - - - +  + + + + + - - -  - - - - - - - - - - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - user mentions priority 1 - - + > +  + - 10:00 + user mentions priority 1 - 1 + 10:00 + + + 1 + + - - + + , - + } + > + @@ -6320,563 +6450,542 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - group mentions priority 2 - - + > +  + - 10:00 + group mentions priority 2 - 1 + 10:00 + + + 1 + + - - + + , - + } + > + @@ -7021,563 +7166,542 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - - - - - +  + + + + + - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - thread unread priority 3 - - + > +  + - 10:00 + thread unread priority 3 - 1 + 10:00 + + + 1 + + - - + + , ] @@ -7674,46 +7839,168 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` exports[`Story Snapshots: Basic should match snapshot 1`] = ` - + } +> + + + + + +  + + + + + + @@ -7723,28 +8010,53 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - -  - - + +  + + + - - -  - - - - - - - - - - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - -  - - - rocket.cat - - +  + + - 10:00 + rocket.cat + + + 10:00 + + - - + + `; @@ -8314,46 +8494,41 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` [ - + } + > + @@ -8366,563 +8541,542 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 60, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 24, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={36} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 36, + "width": 36, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={36} + /> + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat - 1 + 10:00 + + + 1 + + - - + + , - + } + > + @@ -9067,563 +9257,542 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 60, }, + [Function], ] } > - - + -  - - - - - - + [ + { + "lineHeight": 24, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={36} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 36, + "width": 36, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={36} + /> + - + -  - - - unread - - + > +  + - 10:00 + unread - +999 + 10:00 + + + +999 + + - - + + , - + @@ -9768,594 +9973,542 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 60, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 24, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - - - - - +  + + + + + - - - + - - - - -  - - - rocket.cat - + symbolSize={null} + symbolWeight={null} + transition={null} + width={36} + /> + + - Auto-join +  - - - 10:00 + rocket.cat + + + Auto-join + + + + + 10:00 + + - - + + , ] @@ -10396,46 +10621,41 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` exports[`Story Snapshots: CondensedRoomItemWithoutAvatar should match snapshot 1`] = ` [ - + } + > + @@ -10448,412 +10668,416 @@ exports[`Story Snapshots: CondensedRoomItemWithoutAvatar should match snapshot 1 "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 60, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 24, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - -  - - - rocket.cat - - - 10:00 +  - + rocket.cat + + - 1 + 10:00 + + + 1 + + - - + + , - + } + > + @@ -11090,412 +11325,416 @@ exports[`Story Snapshots: CondensedRoomItemWithoutAvatar should match snapshot 1 "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 60, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 24, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + + + + + +  + + + + + + @@ -11664,28 +12041,53 @@ exports[`Story Snapshots: CondensedRoomItemWithoutAvatar should match snapshot 1 { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 60, }, + [Function], ] } > - - -  +  - + - - - - - - -  - - + +  + + + - - -  - - - - - - - - - - -  - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - - Auto-join +  - - - 10:00 + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + Auto-join + + + + + 10:00 + + - - + + , ] @@ -12236,46 +12506,41 @@ exports[`Story Snapshots: CondensedRoomItemWithoutAvatar should match snapshot 1 exports[`Story Snapshots: ExpandedRoomItemWithoutAvatar should match snapshot 1`] = ` [ - + } + > + @@ -12288,406 +12553,413 @@ exports[`Story Snapshots: ExpandedRoomItemWithoutAvatar should match snapshot 1` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + +  + + + -  - - - - - - rocket.cat - - - 10:00 - - - - + rocket.cat + + - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - + ] + } + > + 10:00 + + - 1 + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + 1 + + - - + + , - + @@ -12982,406 +13262,413 @@ exports[`Story Snapshots: ExpandedRoomItemWithoutAvatar should match snapshot 1` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - -  - - - - - rocket.cat +  - + + + - 10:00 - - - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - + ] + } + > + rocket.cat + + + 10:00 + + - 1 + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + 1 + + - - + + , - + } + > + @@ -13676,406 +13971,413 @@ exports[`Story Snapshots: ExpandedRoomItemWithoutAvatar should match snapshot 1` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + +  + + + -  - - - - - - rocket.cat - - - 10:00 - - - - + rocket.cat + + + 10:00 + + + - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + - - + + , ] @@ -14279,46 +14594,41 @@ exports[`Story Snapshots: ExpandedRoomItemWithoutAvatar should match snapshot 1` exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` [ - + @@ -14331,658 +14641,673 @@ exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + +  + + + rocket.cat + + +  + + -  - - - rocket.cat - - -  - - - - 10:00 - + + 10:00 + + - - + + , - + } + > + @@ -14995,583 +15320,542 @@ exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 60, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 24, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={36} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 36, + "width": 36, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={36} + /> + - -  - - - rocket.cat - - -  - - + > +  + - 10:00 + rocket.cat + + +  + + + 10:00 + + - - + + , ] @@ -15612,46 +15957,41 @@ exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` [ - + } + > + @@ -15664,667 +16004,682 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - - - - - -  - - - rocket.cat - - - 10:00 - + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> - - No message - - - - - - - - - , - - - +  + + + rocket.cat + + + 10:00 + + + + + No message + + + + + + + + + + , + + + @@ -16337,667 +16692,682 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - - - - - -  - - - rocket.cat - - - 10:00 - + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> - + +  + + + rocket.cat + + + 10:00 + + + - 2 - + + 2 + + - - + + , - + } + > + @@ -17010,667 +17380,682 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - - 10:00 - - - - +  + + + rocket.cat + + + 10:00 + + + - You: 1 - + + You: 1 + + - - + + , - + } + > + @@ -17683,667 +18068,1562 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - + +  + + + + + + + -  - - - - - - + + + + + + + +  + + + rocket.cat + + + 10:00 + + + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + + + + + + + + , + + + + + + + +  + + + + + + + + + +  + + + + + + +  + + + + + + + + + + + + + + + +  + + + rocket.cat + + + 10:00 + + + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + + 1 + + + + + + + + + + + , + + + - - - - - - - - -  - - - rocket.cat - - - 10:00 - - - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - - - + } + > +  + - - - - , - - + + @@ -18353,28 +19633,53 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - - - - - +  + + + + + - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - +  + + + rocket.cat + + - 10:00 - - - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - + ] + } + > + 10:00 + + - 1 + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + +999 + + - - + + , - + } + > + + + + + + +  + + + + + @@ -19091,28 +20386,53 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - +  + + + rocket.cat + + + 10:00 + + + - rocket.cat - - - 10:00 - - - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - - - - +999 + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + 1 + + - - + + , +] +`; + +exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` +[ - + } + > + + + + + +  + + + + + + @@ -19829,28 +21144,53 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - + +  + + + - - - - - - + - - - - - -  - - - rocket.cat - - - 10:00 - + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - +  + + - 1 + rocket.cat + + + 10:00 + + - - + + , -] -`; - -exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` -[ - + } + > + @@ -20575,555 +21671,542 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - - - - -  - - + + + - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -21211,555 +22322,542 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - - -  - - - - - - - - - - -  - - + + +  + + + + - - -  - - - - - - - +  + + + + - - - - - + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + + + + + - -  - - + + + - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - - - , - - + + + + , + + + + + + + +  + + + + + @@ -21844,28 +23097,53 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -22483,555 +23624,542 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - -  - - - rocket.cat - - + +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -23119,555 +24275,542 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - - - - - - - - - -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + - - -  - - - - - - - +  + + + + - - +  + + + + + + + + - - - - -  - - + + + - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + + + + + + +  + + + + + @@ -23752,28 +25050,53 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -24391,555 +25577,542 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -25027,555 +26228,542 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - - - - -  - - + + + - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -25663,555 +26879,542 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - - - - - - - - - -  - - + "color": "#FFFFFF", + "fontSize": 28, + }, + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + + - - -  - - - - - - - +  + + + + - - +  + + + + + + + + - - - - -  - - + + + - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , +] +`; + +exports[`Story Snapshots: Tag should match snapshot 1`] = ` +[ - + } + > + + + + + + +  + + + + + @@ -26296,28 +27659,53 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - - - - - +  + + + + + - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + Auto-join + + + + + 10:00 + + - - + + , -] -`; - -exports[`Story Snapshots: Tag should match snapshot 1`] = ` -[ - + } + > + @@ -26940,564 +28225,602 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - - - - -  - - + - rocket.cat - + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + + +  + + + rocket.cat + + + + Auto-join + + + - Auto-join + 10:00 - 10:00 + No message - - + + , - + } + > + + + + + +  + + + + + + @@ -27612,28 +29077,53 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - + +  + + + - - - - - - + - - - - - -  - - - rocket.cat - + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + + +  + + - Auto-join + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - - - 10:00 - - - - + + Auto-join + + + - No message - + "alignItems": "flex-end", + } + } + > + + 10:00 + + + - - + + , - + } + > + @@ -28328,564 +29643,602 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - - - -  - - - - - -  - - - - - - - +  + + + + - - +  + + + + + + + + - - - - -  - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + + +  + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + + Auto-join + + + - Auto-join + 10:00 - 10:00 + No message - - + + , - +] +`; + +exports[`Story Snapshots: Touch should match snapshot 1`] = ` + + - - - + @@ -29124,54 +30513,54 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` ] } > - - + } + underlayColor="black" + > - + - - - + - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + rocket.cat - Auto-join - - - - 10:00 - - - - - No message - - - - - - - - - , -] -`; - -exports[`Story Snapshots: Touch should match snapshot 1`] = ` - - - - - - - -  - - - + "fontWeight": "400", + "marginLeft": 4, + "textAlign": "left", + }, + { + "color": "#2F343D", + }, + undefined, + ] + } + > + 10:00 + + + + + + + + + +`; + +exports[`Story Snapshots: Type should match snapshot 1`] = ` +[ - - + + - - -  - - - - - - -  - - - - - - - - - - - - - - -  - - - rocket.cat - - - - 10:00 - - - + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + - - - - -`; - -exports[`Story Snapshots: Type should match snapshot 1`] = ` -[ - - + @@ -30357,28 +31154,53 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - - -  - - - - - - - - - +  + + + + + + + + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -31004,555 +31689,542 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - - - - -  - - + + + - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -31640,555 +32340,542 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - -  - - - - - - - - - - -  - - + "max": undefined, + "min": undefined, + "now": undefined, + "text": undefined, + } + } + accessible={false} + activeUnderlayOpacity={0.105} + animationDuration={0} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + style={ + { + "alignItems": "center", + "height": "100%", + "justifyContent": "center", + "width": 80, + } + } + underlayColor="black" + > + +  + + + + - - -  - - - - - - - +  + + + + - - +  + + + + + + + + - - - - -  - - + + + - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - - - , - - + + + + , + + + + + + + +  + + + + + @@ -32273,28 +33115,53 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - - - - - +  + + + + + - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -32912,555 +33642,542 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - -  - - - - - +  + + + + - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -33548,555 +34293,542 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - - - -  - - - - - -  - - - - - - - +  + + + + - - +  + + + + + + + + - - - - -  - - + + + - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + + + + + +  + + + + + + @@ -34181,28 +35068,53 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - + +  + + + - - - - - - - + + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -34820,555 +35595,542 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - - -  - - - - - - -  - - - - - - - +  + + + + - - +  + + + + + + + + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , - + } + > + @@ -35456,555 +36246,542 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` "right": 0, "top": 0, }, + { + "backgroundColor": "#1D74F5", + "right": "100%", + "width": 1500, + }, { "height": 75, }, + [Function], ] } > - - + -  - - + [ + { + "lineHeight": 28, + }, + undefined, + ], + { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + {}, + ] + } + > +  + + + - - - - - -  - - - - - - -  - - - - - - - +  + + + + - - +  + + + + + + + + - - - - -  - - + + + - rocket.cat - - + > +  + - 10:00 + rocket.cat + + + 10:00 + + - - + + , ] @@ -36045,46 +36855,168 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` exports[`Story Snapshots: User should match snapshot 1`] = ` [ - + } + > + + + + + +  + + + + + + @@ -36094,28 +37026,53 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - -  - - + +  + + + - - -  - - - - - - - - - - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - diego.mello - - + > +  + - 10:00 + diego.mello + + + 10:00 + + - - - - , - - + + + + , + + + + + + + +  + + + + + @@ -36738,28 +37685,53 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` { "justifyContent": "center", "position": "absolute", - "right": 0, "top": 0, }, + { + "backgroundColor": "#8E6300", + "left": "100%", + "width": 750, + }, { "height": 75, }, + [Function], ] } > - - -  +  - + - - - - - - - -  - - - - - - -  - - + +  + + + - - - - - - + - + - - + height={48} + nativeViewRef={{"current":"NativeComponent"}} + onError={[Function]} + onLoad={[Function]} + onLoadStart={[Function]} + onProgress={[Function]} + placeholder={[]} + priority="high" + sfEffect={null} + source={ + [ + { + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, + "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", + }, + ] + } + style={ + { + "borderRadius": 4, + "height": 48, + "width": 48, + } + } + symbolSize={null} + symbolWeight={null} + transition={null} + width={48} + /> + - + -  - - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries - - + > +  + - 10:00 + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + 10:00 + + - - + + , ] diff --git a/app/containers/ServerItem/SwipeableDeleteItem/Actions.tsx b/app/containers/ServerItem/SwipeableDeleteItem/Actions.tsx index 5e8cffaac88..d7c245982a9 100644 --- a/app/containers/ServerItem/SwipeableDeleteItem/Actions.tsx +++ b/app/containers/ServerItem/SwipeableDeleteItem/Actions.tsx @@ -8,10 +8,10 @@ import Animated, { useSharedValue, type SharedValue } from 'react-native-reanimated'; -import { RectButton } from 'react-native-gesture-handler'; import * as Haptics from 'expo-haptics'; import { scheduleOnRN } from 'react-native-worklets'; +import { RectButton } from '~/containers/GestureButtons'; import { CustomIcon } from '~/containers/CustomIcon'; import { useTheme } from '~/theme'; import I18n from '~/i18n'; diff --git a/app/containers/ServerItem/SwipeableDeleteItem/Touchable.tsx b/app/containers/ServerItem/SwipeableDeleteItem/Touchable.tsx index 1b3b90af205..3b003d77230 100644 --- a/app/containers/ServerItem/SwipeableDeleteItem/Touchable.tsx +++ b/app/containers/ServerItem/SwipeableDeleteItem/Touchable.tsx @@ -1,11 +1,6 @@ import { useRef, memo, type ReactElement } from 'react'; import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated'; -import { - Gesture, - GestureDetector, - type GestureUpdateEvent, - type PanGestureHandlerEventPayload -} from 'react-native-gesture-handler'; +import { GestureDetector, type PanGestureActiveEvent, usePanGesture } from 'react-native-gesture-handler'; import { View, type AccessibilityActionEvent } from 'react-native'; import { scheduleOnRN } from 'react-native-worklets'; @@ -81,7 +76,7 @@ const SwipeableDeleteTouchable = ({ } }; - const handleRelease = (event: GestureUpdateEvent) => { + const handleRelease = (event: PanGestureActiveEvent) => { const { translationX } = event; valueRef.current += translationX; let toValue = 0; @@ -147,10 +142,10 @@ const SwipeableDeleteTouchable = ({ valueRef.current = toValue; }; - const panGesture = Gesture.Pan() - .activeOffsetX([-10, 10]) // More sensitive horizontal detection - .failOffsetY([-20, 20]) // Fail on vertical movement to distinguish scrolling - .onUpdate(event => { + const panGesture = usePanGesture({ + activeOffsetX: [-10, 10], // More sensitive horizontal detection + failOffsetY: [-20, 20], // Fail on vertical movement to distinguish scrolling + onUpdate: event => { const newValue = event.translationX + rowOffSet.value; if (I18n.isRTL) { @@ -171,10 +166,11 @@ const SwipeableDeleteTouchable = ({ // Limit how far left it can stretch if (transX.value < -width) transX.value = -width; } - }) - .onEnd(event => { + }, + onDeactivate: event => { scheduleOnRN(handleRelease, event); - }); + } + }); const animatedStyles = useAnimatedStyle(() => ({ transform: [{ translateX: transX.value }] diff --git a/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap b/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap index 1a4b92c41a3..1a20d4ca521 100644 --- a/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap +++ b/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap @@ -24,61 +24,64 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` ] } > - - - + , - - - + , - - - + , ] `; @@ -728,739 +737,777 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` [ - + } + > + - - -  - - + +  + + + - - - - - - - - - Rocket.Chat - - - https://open.rocket.chat/ - - - - + + + Rocket.Chat + + + https://open.rocket.chat/ + + + -  - + +  + + - - + + , - + } + > + - - -  - - + +  + + + - - - - - - - - - Another Server - - - https://example.com/ - - - - + + + Another Server + + + https://example.com/ + + + -  - + +  + + - - + + , ] @@ -1490,61 +1537,64 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` ] } > - - - + , - - - + , - - - + , ] `; diff --git a/app/containers/TabView/index.tsx b/app/containers/TabView/index.tsx index cf724e0fc46..c82de5dc7c5 100644 --- a/app/containers/TabView/index.tsx +++ b/app/containers/TabView/index.tsx @@ -1,7 +1,7 @@ import { useCallback, useState, type ReactNode } from 'react'; import { View } from 'react-native'; import { TabView as ReanimatedTabView, type Route, type NavigationState } from 'reanimated-tab-view'; -import { TouchableOpacity } from 'react-native-gesture-handler'; +import { Touchable } from 'react-native-gesture-handler'; import styles from './styles'; import { useTheme } from '~/theme'; @@ -28,9 +28,9 @@ export const TabView = ({ routes, renderTabItem, renderScene }: TabViewProps) => {routes.map((tab: Route, index: number) => ( - jumpTo(tab.key)} hitSlop={10}> + jumpTo(tab.key)} hitSlop={10} activeOpacity={0.2} animationDuration={{ in: 0, out: 150 }}> {renderTabItem(tab, routeIndex === index ? colors.strokeHighlight : colors.fontSecondaryInfo)} - + - - - + @@ -1298,60 +1301,63 @@ exports[`Story Snapshots: SecureTextEntry should match snapshot 1`] = ` ] } > - - - + diff --git a/app/containers/Thumbs.tsx b/app/containers/Thumbs.tsx index 7b2a3ad7099..8c7968d081d 100644 --- a/app/containers/Thumbs.tsx +++ b/app/containers/Thumbs.tsx @@ -1,7 +1,7 @@ import { memo } from 'react'; import { FlatList, Image, StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native'; -import { RectButton } from 'react-native-gesture-handler'; +import { RectButton } from '~/containers/GestureButtons'; import { BUTTON_HIT_SLOP } from './message/utils'; import { CustomIcon } from './CustomIcon'; import { useTheme } from '../theme'; diff --git a/app/containers/Touch.tsx b/app/containers/Touch.tsx index 10c50bb8dff..1f414232df9 100644 --- a/app/containers/Touch.tsx +++ b/app/containers/Touch.tsx @@ -1,5 +1,5 @@ import { forwardRef, type ReactNode } from 'react'; -import { RectButton, type RectButtonProps } from 'react-native-gesture-handler'; +import { Touchable, type TouchableProps } from 'react-native-gesture-handler'; import { View, StyleSheet, @@ -11,8 +11,10 @@ import { import { withKeyboardFocus } from 'react-native-external-keyboard'; import { useTheme } from '../theme'; +import { isAndroid } from '../lib/methods/helpers/deviceInfo'; -export interface ITouchProps extends RectButtonProps { +export interface ITouchProps extends TouchableProps { + rippleColor?: string; children: ReactNode; accessible?: boolean; accessibilityLabel?: string; @@ -24,7 +26,7 @@ export interface ITouchProps extends RectButtonProps { disabled?: boolean; } -const KeyboardRectButton = withKeyboardFocus(RectButton); +const KeyboardTouchable = withKeyboardFocus(Touchable); const Touch = forwardRef( ( @@ -32,6 +34,7 @@ const Touch = forwardRef( children, onPress, underlayColor, + rippleColor, accessible, accessibilityLabel, accessibilityHint, @@ -77,17 +80,18 @@ const Touch = forwardRef( marginTop }; return ( - + disabled={disabled}> ( style={viewStyle}> {children} - + ); } ); diff --git a/app/containers/UIKit/MultiSelect/Input.tsx b/app/containers/UIKit/MultiSelect/Input.tsx index 893b4520745..fdb5ab206ae 100644 --- a/app/containers/UIKit/MultiSelect/Input.tsx +++ b/app/containers/UIKit/MultiSelect/Input.tsx @@ -25,7 +25,7 @@ const Input = ({ children, onPress, loading, inputStyle, placeholder, disabled, onPress={onPress} testID={testID} style={[{ backgroundColor: colors.surfaceRoom }, styles.inputBorder, inputStyle]} - enabled={!disabled}> + disabled={disabled}> {placeholder ? {placeholder} : children} {loading ? ( diff --git a/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap b/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap index 5d55ef5e549..6bdb0962905 100644 --- a/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap +++ b/app/containers/UIKit/__snapshots__/UiKitMessage.test.tsx.snap @@ -527,56 +527,60 @@ exports[`Story Snapshots: ActionButton should match snapshot 1`] = ` , - - Show more - , + , ] `; @@ -5732,56 +5736,60 @@ exports[`Story Snapshots: SectionDatePicker should match snapshot 1`] = ` - - Select a date - + `; @@ -6753,56 +6761,60 @@ exports[`Story Snapshots: SectionMultiSelect should match snapshot 1`] = ` - - 2 selecteds - + , - - 0 selecteds - + , ] `; @@ -7091,13 +7107,29 @@ exports[`Story Snapshots: SectionOverflow should match snapshot 1`] = ` ] } > - - - + `; diff --git a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap index c49be48fe7e..c720f83525b 100644 --- a/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap +++ b/app/containers/UIKit/__snapshots__/UiKitModal.test.tsx.snap @@ -1675,56 +1675,60 @@ exports[`Story Snapshots: ModalActionsWithShowMore should match snapshot 1`] = ` , - - Show more - , + , - - - + , - - - + , - - - + , ] @@ -2697,60 +2710,63 @@ exports[`Story Snapshots: ModalDatePickerWithError should match snapshot 1`] = ` ] } > - - @@ -2823,7 +2839,7 @@ exports[`Story Snapshots: ModalDatePickerWithError should match snapshot 1`] = ` - + - - @@ -3248,7 +3267,7 @@ exports[`Story Snapshots: ModalFormInput should match snapshot 1`] = ` - + , ] @@ -4597,60 +4616,63 @@ exports[`Story Snapshots: ModalMultiSelect should match snapshot 1`] = ` ] } > - - @@ -4728,7 +4750,7 @@ exports[`Story Snapshots: ModalMultiSelect should match snapshot 1`] = ` - + - - @@ -4913,60 +4938,63 @@ exports[`Story Snapshots: ModalMultiSelect should match snapshot 1`] = ` ] } > - - - + - + - - - + @@ -605,60 +608,63 @@ exports[`Story Snapshots: ArchivedLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -1071,60 +1077,63 @@ exports[`Story Snapshots: AttachmentWithTextAndLink should match snapshot 1`] = ] } > - - - + @@ -1877,60 +1886,63 @@ exports[`Story Snapshots: AttachmentWithTextAndLinkLargeFont should match snapsh ] } > - - - + @@ -2682,137 +2694,140 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -3589,60 +3607,63 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` ] } > - - - + @@ -4034,60 +4055,63 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` ] } > - - - + @@ -4495,60 +4519,63 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` ] } > - - - + @@ -4969,60 +4996,63 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` ] } > - - - + @@ -5422,60 +5452,63 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` ] } > - - - + @@ -5888,60 +5921,63 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -6340,137 +6376,140 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -7291,137 +7333,140 @@ Testing block quote." undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -8327,137 +8375,140 @@ Testing block quote." undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -9467,137 +9521,140 @@ exports[`Story Snapshots: BroadcastLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -10664,137 +10724,140 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -12119,137 +12185,140 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -13576,137 +13648,140 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -15189,137 +15267,140 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -17467,137 +17551,140 @@ exports[`Story Snapshots: ColoredAttachmentsLargeFont should match snapshot 1`] undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -19577,137 +19667,140 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -21545,137 +21641,140 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -23199,137 +23301,140 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -24441,137 +24549,140 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -25428,137 +25542,140 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -26670,137 +26790,140 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -27715,137 +27841,140 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -28877,137 +29009,140 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -30050,137 +30188,140 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -31212,137 +31356,140 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -32556,137 +32706,140 @@ exports[`Story Snapshots: EditedLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -33774,137 +33930,140 @@ exports[`Story Snapshots: EditingLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -34692,137 +34854,140 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -35671,137 +35839,140 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -36603,137 +36777,140 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -37587,137 +37767,140 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -38566,137 +38752,140 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -39498,137 +39690,140 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -40763,137 +40961,140 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -42124,137 +42328,140 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -43021,137 +43231,140 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -44386,137 +44602,140 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -46188,60 +46410,63 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -46481,60 +46706,63 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -46972,60 +47200,63 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -47378,60 +47609,63 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -47937,60 +48171,63 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -48662,60 +48899,63 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` ] } > - - - + @@ -49106,137 +49346,140 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -50009,137 +50255,140 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -51146,137 +51398,140 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -52342,137 +52600,140 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -54086,137 +54350,140 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -55282,137 +55552,140 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -57187,137 +57463,140 @@ exports[`Story Snapshots: FullName should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -58119,137 +58401,140 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -59449,137 +59737,140 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -60368,137 +60662,140 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -61717,60 +62017,63 @@ exports[`Story Snapshots: Ignored should match snapshot 1`] = ` ] } > - - - + @@ -62035,60 +62338,63 @@ exports[`Story Snapshots: IgnoredLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -62348,60 +62654,63 @@ exports[`Story Snapshots: InlineKatex should match snapshot 1`] = ` ] } > - - - + @@ -62800,60 +63109,63 @@ exports[`Story Snapshots: InlineKatexLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -63244,60 +63556,63 @@ exports[`Story Snapshots: JitsiCall should match snapshot 1`] = ` ] } > - - - + @@ -63772,60 +64087,63 @@ exports[`Story Snapshots: JitsiCallLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -64307,137 +64625,140 @@ exports[`Story Snapshots: Katex should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -65135,137 +65459,140 @@ exports[`Story Snapshots: KatexArrayLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -65965,137 +66295,140 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -67219,137 +67555,140 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -68648,137 +68990,140 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -70023,137 +70371,140 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -70965,137 +71319,140 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -71859,137 +72219,140 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -72869,137 +73235,140 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -73882,137 +74254,140 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -75130,137 +75508,140 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -76072,137 +76456,140 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -76966,137 +77356,140 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -77976,137 +78372,140 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -78989,137 +79391,140 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -80237,137 +80645,140 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -81529,137 +81943,140 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -82821,137 +83241,140 @@ exports[`Story Snapshots: Message should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -83983,137 +84409,140 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -85424,30 +85856,47 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -85611,137 +86060,140 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -87237,137 +87692,140 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -88704,137 +89165,140 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -90171,137 +90638,140 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -91767,137 +92240,140 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -93466,137 +93945,140 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -94815,137 +95300,140 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -96230,137 +96721,140 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -97739,137 +98236,140 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -98557,60 +99074,63 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot ] } > - - - + @@ -99264,137 +99784,140 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -100816,137 +101342,140 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -102308,137 +102840,140 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = undefined, ] } - > - - - - - - - - - - - - + + + + + + + + + + + - - - + @@ -103765,60 +104303,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -104137,60 +104678,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -104569,60 +105113,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -105001,60 +105548,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -105433,60 +105983,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -105865,60 +106418,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -106297,60 +106853,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -106729,60 +107288,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -107161,60 +107723,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -107593,60 +108158,63 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` ] } > - - - + @@ -107911,60 +108479,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -108478,60 +109049,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -108850,60 +109424,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -109282,60 +109859,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -109714,60 +110294,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -110146,60 +110729,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -110578,60 +111164,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -111010,60 +111599,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -111442,60 +112034,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -111874,60 +112469,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -112306,60 +112904,63 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = ] } > - - - + @@ -112623,60 +113224,63 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` ] } > - - - + @@ -112995,60 +113599,63 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` ] } > - - - + @@ -113682,60 +114289,63 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` ] } > - - - + @@ -114054,60 +114664,63 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` ] } > - - - + @@ -114741,60 +115354,63 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` ] } > - - - + @@ -115113,60 +115729,63 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` ] } > - - - + @@ -115813,60 +116432,63 @@ exports[`Story Snapshots: Pinned should match snapshot 1`] = ` ] } > - - - + @@ -116551,60 +117173,63 @@ exports[`Story Snapshots: PinnedLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -117289,60 +117914,63 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` ] } > - - - + @@ -118301,60 +118929,63 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` ] } > - - - + @@ -119800,60 +120431,63 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -120812,60 +121446,63 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -122312,60 +122949,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m ] } > - - - + @@ -122879,60 +123519,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m ] } > - - - + @@ -123128,60 +123771,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m ] } > - - - + @@ -123437,60 +124083,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m ] } > - - - + @@ -123746,60 +124395,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m ] } > - - - + @@ -124064,60 +124716,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont ] } > - - - + @@ -124631,60 +125286,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont ] } > - - - + @@ -124880,60 +125538,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont ] } > - - - + @@ -125189,60 +125850,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont ] } > - - - + @@ -125498,60 +126162,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont ] } > - - - + @@ -125943,60 +126610,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma ] } > - - - + @@ -126252,60 +126922,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma ] } > - - - + @@ -126561,60 +127234,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma ] } > - - - + @@ -126870,60 +127546,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma ] } > - - - + @@ -127315,60 +127994,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont ] } > - - - + @@ -127624,60 +128306,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont ] } > - - - + @@ -127933,60 +128618,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont ] } > - - - + @@ -128242,60 +128930,63 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont ] } > - - - + @@ -128559,60 +129250,63 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` ] } > - - - + @@ -128860,56 +129554,60 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` - - Text button - + @@ -129088,60 +129786,63 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` ] } > - - - + @@ -129614,56 +130315,60 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` - - Text button - + @@ -129855,60 +130560,63 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot ] } > - - - + @@ -130156,56 +130864,60 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot - - Text button - + @@ -130384,60 +131096,63 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot ] } > - - - + @@ -130910,56 +131625,60 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot - - Text button - + @@ -131151,60 +131870,63 @@ exports[`Story Snapshots: StaticAvatar should match snapshot 1`] = ` ] } > - - - + @@ -131617,60 +132339,63 @@ exports[`Story Snapshots: StaticAvatarLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -132009,60 +132734,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -132252,60 +132980,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -132495,60 +133226,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -132741,60 +133475,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -132984,60 +133721,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -133227,60 +133967,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -133470,60 +134213,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -133713,60 +134459,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -133956,60 +134705,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -134199,60 +134951,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -134442,60 +135197,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -134685,60 +135443,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -134928,60 +135689,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -135171,60 +135935,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -135414,60 +136181,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -135657,60 +136427,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -135900,60 +136673,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -136143,60 +136919,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -136386,60 +137165,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -136629,60 +137411,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -136872,60 +137657,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -137115,60 +137903,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -137358,60 +138149,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -137601,60 +138395,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -137844,60 +138641,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -138087,60 +138887,63 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` ] } > - - - + @@ -138318,60 +139121,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -138561,60 +139367,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -138804,60 +139613,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -139050,60 +139862,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -139293,60 +140108,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -139536,60 +140354,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -139779,60 +140600,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -140022,60 +140846,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -140265,60 +141092,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -140508,60 +141338,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -140751,60 +141584,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -140994,60 +141830,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -141237,60 +142076,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -141480,60 +142322,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -141723,60 +142568,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -141966,60 +142814,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -142209,60 +143060,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -142452,60 +143306,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -142695,60 +143552,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -142938,60 +143798,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -143181,60 +144044,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -143424,60 +144290,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -143667,60 +144536,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -143910,60 +144782,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -144153,60 +145028,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -144396,60 +145274,63 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -144693,60 +145574,63 @@ exports[`Story Snapshots: Temp should match snapshot 1`] = ` ] } > - - - + @@ -145155,60 +146039,63 @@ exports[`Story Snapshots: TempLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -145625,60 +146512,63 @@ exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` ] } > - - - + @@ -146314,60 +147204,63 @@ exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] ] } > - - - + @@ -147003,60 +147896,63 @@ exports[`Story Snapshots: TimeFormat should match snapshot 1`] = ` ] } > - - - + @@ -147469,60 +148365,63 @@ exports[`Story Snapshots: TimeFormatLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -147935,60 +148834,63 @@ exports[`Story Snapshots: Translated should match snapshot 1`] = ` ] } > - - - + @@ -148436,60 +149338,63 @@ exports[`Story Snapshots: TranslatedLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -148937,60 +149842,63 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdown should match snapshot ] } > - - - + @@ -150237,60 +151145,63 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdownLargeFont should match ] } > - - - + @@ -151537,60 +152448,63 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` ] } > - - - + @@ -152132,60 +153046,63 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` ] } > - - - + @@ -152999,60 +153916,63 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` ] } > - - - + @@ -153490,60 +154410,63 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` ] } > - - - + @@ -153931,60 +154854,63 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -154422,60 +155348,63 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -154863,60 +155792,63 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -155458,60 +156390,63 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -156325,60 +157260,63 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` ] } > - - - + @@ -156697,60 +157635,63 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` ] } > - - - + @@ -157384,60 +158325,63 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` ] } > - - - + @@ -157756,60 +158700,63 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` ] } > - - - + @@ -158443,60 +159390,63 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` ] } > - - - + @@ -158815,60 +159765,63 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` ] } > - - - + @@ -159515,60 +160468,63 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` ] } > - - - + @@ -159990,60 +160946,63 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` ] } > - - - + @@ -160478,60 +161437,63 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -160953,60 +161915,63 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -161441,60 +162406,63 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` ] } > - - - + @@ -161997,30 +162965,47 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -162441,30 +163426,47 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -162897,30 +163899,47 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -163133,30 +164152,47 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -163523,30 +164559,47 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -163867,30 +164920,47 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -164103,30 +165173,47 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -164306,60 +165393,63 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -164862,30 +165952,47 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -165518,30 +166625,47 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -165908,30 +167032,47 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -166364,30 +167505,47 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -166708,30 +167866,47 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -166944,30 +168119,47 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` /> + + "bottom": 12, + "left": 8, + "right": 8, + "top": 12, + } + } + style={ + [ + { + "borderRadius": 6, + "height": 12, + "position": "absolute", + "width": 12, + }, + { + "backgroundColor": "#D1EBFE", + }, + [Function], + ] + } + /> + @@ -167147,60 +168339,63 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` ] } > - - - + @@ -168480,60 +169675,63 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -169556,60 +170754,63 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` ] } > - - - + @@ -170228,60 +171429,63 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` ] } > - - - + @@ -171404,60 +172608,63 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` ] } > - - - + @@ -172187,60 +173394,63 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` ] } > - - - + @@ -172859,60 +174069,63 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -173868,60 +175081,63 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -174651,60 +175867,63 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -175323,60 +176542,63 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` ] } > - - - + @@ -176295,60 +177517,63 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` ] } > - - - + @@ -177023,60 +178248,63 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -177653,60 +178881,63 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` ] } > - - - + @@ -178175,60 +179406,63 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` ] } > - - - + diff --git a/app/containers/message/components/stories/__tests__/__snapshots__/leaves.test.tsx.snap b/app/containers/message/components/stories/__tests__/__snapshots__/leaves.test.tsx.snap index 7fbe6b02650..f8255beaa5f 100644 --- a/app/containers/message/components/stories/__tests__/__snapshots__/leaves.test.tsx.snap +++ b/app/containers/message/components/stories/__tests__/__snapshots__/leaves.test.tsx.snap @@ -1263,60 +1263,63 @@ exports[`Story Snapshots: MessageAvatar should match snapshot 1`] = ` ] } > - - - + @@ -5116,60 +5119,63 @@ exports[`Story Snapshots: Thread should match snapshot 1`] = ` ] } > - - - + diff --git a/app/views/CannedResponsesListView/__snapshots__/CannedResponseItem.test.tsx.snap b/app/views/CannedResponsesListView/__snapshots__/CannedResponseItem.test.tsx.snap index b347ea76688..189ac2f0425 100644 --- a/app/views/CannedResponsesListView/__snapshots__/CannedResponseItem.test.tsx.snap +++ b/app/views/CannedResponsesListView/__snapshots__/CannedResponseItem.test.tsx.snap @@ -24,60 +24,63 @@ exports[`Story Snapshots: Itens should match snapshot 1`] = ` ] } > - - - - Use - + - + , - - - - Use - + - + , ] `; diff --git a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap index 5c38e00c82b..12c8d9bd482 100644 --- a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap +++ b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap @@ -38,60 +38,63 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` ] } > - - - + - - - + - - - + - - - + - - - + - - - + - - - + - - - + - - + `; exports[`Story Snapshots: Default should match snapshot 1`] = ` @@ -167,53 +163,48 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` } } > - - - @@ -381,9 +379,9 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` >  - + - + `; @@ -397,52 +395,48 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` } } > - - - @@ -610,9 +611,9 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` >  - + - + `; @@ -626,52 +627,48 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` } } > - - - @@ -839,8 +843,8 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` >  - + - + `; diff --git a/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap b/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap index d90887f8d1c..ae4264989b0 100644 --- a/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap +++ b/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap @@ -3,975 +3,1032 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ - + } + > + - - -  - - + +  + + + - - - - - - - - - https://open.rocket.chat - - + - john.doe - + + https://open.rocket.chat + + + john.doe + + - - + + , - + } + > + - - -  - - + +  + + + - - - - - - - - - https://superlongservername.tologintoasuperlongservername.rocket.chat - - + - very.long.username.here - + + https://superlongservername.tologintoasuperlongservername.rocket.chat + + + very.long.username.here + + - - + + , - + } + > + - - -  - - + +  + + + - - - - - - + - - - - https://stable.rocket.chat - - + - admin - + + https://stable.rocket.chat + + + admin + + - - + + , ] @@ -980,651 +1037,689 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` [ - + } + > + - - -  - - + +  + + + - - - - - - - - - https://open.rocket.chat - - + - john.doe - + + https://open.rocket.chat + + + john.doe + + - - + + , - + } + > + - - -  - - + +  + + + - - - - - - - - - https://example.com - - + - user123 - + + https://example.com + + + user123 + + - - + + , ] @@ -1633,975 +1728,1032 @@ exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` exports[`Story Snapshots: Themes should match snapshot 1`] = ` [ - + } + > + - - -  - - + +  + + + - - - - - - - - - https://open.rocket.chat - - + - john.doe - + + https://open.rocket.chat + + + john.doe + + - - + + , - + } + > + - - -  - - + +  + + + - - - - - - - - - https://open.rocket.chat - - + - john.doe - + + https://open.rocket.chat + + + john.doe + + - - + + , - + } + > + - - -  - - + +  + + + - - - - - - - - - https://open.rocket.chat - - + - john.doe - + + https://open.rocket.chat + + + john.doe + + - - + + , ] diff --git a/app/views/RoomActionsView/components/__snapshots__/CallSection.test.tsx.snap b/app/views/RoomActionsView/components/__snapshots__/CallSection.test.tsx.snap index 240cacd0add..17486017a39 100644 --- a/app/views/RoomActionsView/components/__snapshots__/CallSection.test.tsx.snap +++ b/app/views/RoomActionsView/components/__snapshots__/CallSection.test.tsx.snap @@ -51,60 +51,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - @@ -267,7 +270,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` - + - - @@ -555,7 +561,7 @@ exports[`Story Snapshots: Disabled should match snapshot 1`] = ` - + - - @@ -841,7 +850,7 @@ exports[`Story Snapshots: NoVoiceCall should match snapshot 1`] = ` - + diff --git a/app/views/RoomInfoView/components/BaseButton.tsx b/app/views/RoomInfoView/components/BaseButton.tsx index 4e0401d5cc1..b4e837688ae 100644 --- a/app/views/RoomInfoView/components/BaseButton.tsx +++ b/app/views/RoomInfoView/components/BaseButton.tsx @@ -1,7 +1,7 @@ import { Text } from 'react-native'; -import { BorderlessButton } from 'react-native-gesture-handler'; import { type ReactElement } from 'react'; +import { BorderlessButton } from '~/containers/GestureButtons'; import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; import { useTheme } from '~/theme'; import styles from '../styles'; @@ -26,7 +26,7 @@ export function BaseButton({ if (showIcon) return ( - + {label} diff --git a/app/views/RoomInfoView/components/__snapshots__/RoomInfoButtons.test.tsx.snap b/app/views/RoomInfoView/components/__snapshots__/RoomInfoButtons.test.tsx.snap index 635478e56d7..5c1f6caa659 100644 --- a/app/views/RoomInfoView/components/__snapshots__/RoomInfoButtons.test.tsx.snap +++ b/app/views/RoomInfoView/components/__snapshots__/RoomInfoButtons.test.tsx.snap @@ -17,32 +17,45 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` } } > - @@ -87,33 +100,46 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` > Message - - + @@ -158,33 +184,46 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` > Voice call - - + @@ -229,33 +268,46 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` > Video call - - + @@ -300,7 +352,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` > More - + `; @@ -322,32 +374,45 @@ exports[`Story Snapshots: ItsMe should match snapshot 1`] = ` } } > - @@ -392,33 +457,46 @@ exports[`Story Snapshots: ItsMe should match snapshot 1`] = ` > Message - - + @@ -463,7 +541,7 @@ exports[`Story Snapshots: ItsMe should match snapshot 1`] = ` > Video call - + `; @@ -485,32 +563,45 @@ exports[`Story Snapshots: NoVoiceCall should match snapshot 1`] = ` } } > - @@ -555,33 +646,46 @@ exports[`Story Snapshots: NoVoiceCall should match snapshot 1`] = ` > Message - - + @@ -626,33 +730,46 @@ exports[`Story Snapshots: NoVoiceCall should match snapshot 1`] = ` > Voice call - - + @@ -697,33 +814,46 @@ exports[`Story Snapshots: NoVoiceCall should match snapshot 1`] = ` > Video call - - + @@ -768,7 +898,7 @@ exports[`Story Snapshots: NoVoiceCall should match snapshot 1`] = ` > More - + `; @@ -790,32 +920,45 @@ exports[`Story Snapshots: OlderServer should match snapshot 1`] = ` } } > - @@ -860,33 +1003,46 @@ exports[`Story Snapshots: OlderServer should match snapshot 1`] = ` > Message - - + @@ -931,33 +1087,46 @@ exports[`Story Snapshots: OlderServer should match snapshot 1`] = ` > Voice call - - + @@ -1002,33 +1171,46 @@ exports[`Story Snapshots: OlderServer should match snapshot 1`] = ` > Video call - - + @@ -1073,7 +1255,7 @@ exports[`Story Snapshots: OlderServer should match snapshot 1`] = ` > Block - + `; @@ -1095,32 +1277,45 @@ exports[`Story Snapshots: WithBlockedUser should match snapshot 1`] = ` } } > - @@ -1165,33 +1360,46 @@ exports[`Story Snapshots: WithBlockedUser should match snapshot 1`] = ` > Message - - + @@ -1236,33 +1444,46 @@ exports[`Story Snapshots: WithBlockedUser should match snapshot 1`] = ` > Voice call - - + @@ -1307,33 +1528,46 @@ exports[`Story Snapshots: WithBlockedUser should match snapshot 1`] = ` > Video call - - + @@ -1378,7 +1612,7 @@ exports[`Story Snapshots: WithBlockedUser should match snapshot 1`] = ` > More - + `; @@ -1400,32 +1634,45 @@ exports[`Story Snapshots: WithRoomFromRid should match snapshot 1`] = ` } } > - @@ -1470,33 +1717,46 @@ exports[`Story Snapshots: WithRoomFromRid should match snapshot 1`] = ` > Message - - + @@ -1541,33 +1801,46 @@ exports[`Story Snapshots: WithRoomFromRid should match snapshot 1`] = ` > Voice call - - + @@ -1612,33 +1885,46 @@ exports[`Story Snapshots: WithRoomFromRid should match snapshot 1`] = ` > Block - - + @@ -1683,7 +1969,7 @@ exports[`Story Snapshots: WithRoomFromRid should match snapshot 1`] = ` > Report - + `; diff --git a/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap b/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap index b6b2cbca761..34601700160 100644 --- a/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap +++ b/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap @@ -143,15 +143,31 @@ exports[`Story Snapshots: WithEdit should match snapshot 1`] = ` width={120} /> - - Edit - + `; diff --git a/app/views/RoomView/LoadMore/index.tsx b/app/views/RoomView/LoadMore/index.tsx index ab6ebbf56be..0b152d97374 100644 --- a/app/views/RoomView/LoadMore/index.tsx +++ b/app/views/RoomView/LoadMore/index.tsx @@ -66,7 +66,7 @@ const LoadMore = memo( return ( <> - + {loading ? ( ) : ( diff --git a/app/views/RoomView/components/Banner.tsx b/app/views/RoomView/components/Banner.tsx index 3db22cc62c1..8d6623c00fe 100644 --- a/app/views/RoomView/components/Banner.tsx +++ b/app/views/RoomView/components/Banner.tsx @@ -1,8 +1,9 @@ import { memo, useState } from 'react'; import { Text } from 'react-native'; -import { BorderlessButton, GestureHandlerRootView, ScrollView } from 'react-native-gesture-handler'; +import { GestureHandlerRootView, ScrollView } from 'react-native-gesture-handler'; import Modal from 'react-native-modal'; +import { BorderlessButton } from '~/containers/GestureButtons'; import Markdown, { MarkdownPreview } from '~/containers/markdown'; import { CustomIcon } from '~/containers/CustomIcon'; import styles from '../styles'; diff --git a/app/views/RoomsListView/components/Header.tsx b/app/views/RoomsListView/components/Header.tsx index 288e581f621..f5eed0fb3b6 100644 --- a/app/views/RoomsListView/components/Header.tsx +++ b/app/views/RoomsListView/components/Header.tsx @@ -1,6 +1,6 @@ import { memo } from 'react'; import { StyleSheet, Text, View, useWindowDimensions } from 'react-native'; -import { TouchableOpacity } from 'react-native-gesture-handler'; +import { Touchable } from 'react-native-gesture-handler'; import { showActionSheetRef } from '~/containers/ActionSheet'; import SearchHeader from '~/containers/SearchHeader'; @@ -65,7 +65,9 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string) } return ( - ) : null} - + ); }; diff --git a/app/views/StatusView/index.test.tsx b/app/views/StatusView/index.test.tsx index eb21fa35be1..0c74d256715 100644 --- a/app/views/StatusView/index.test.tsx +++ b/app/views/StatusView/index.test.tsx @@ -57,6 +57,8 @@ const Wrapper = ({ children }: { children: ReactNode }) => render(, { wrapper: Wrapper }); +const waitForSubmitEnabled = () => waitFor(() => expect(screen.getByTestId('status-view-submit')).not.toBeDisabled()); + describe('StatusView', () => { beforeAll(() => { initStore(mockedStore); @@ -132,12 +134,10 @@ describe('StatusView', () => { renderStatusView(); - const submit = screen.getByTestId('status-view-submit'); - // RectButton uses enabled prop — toBeDisabled() doesn't work with the mock - expect(submit.props.enabled).toBe(false); + expect(screen.getByTestId('status-view-submit')).toBeDisabled(); }); - it('should be enabled when status is changed', () => { + it('should be enabled when status is changed', async () => { mockedStore.dispatch(setUser({ id: 'user-id', username: 'user', status: 'online', statusText: '' })); mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '6.0.0', name: 'Test' })); mockedStore.dispatch(addSettings({ Accounts_AllowInvisibleStatusOption: true })); @@ -146,10 +146,10 @@ describe('StatusView', () => { fireEvent.press(screen.getByTestId('status-view-busy')); - expect(screen.getByTestId('status-view-submit')).not.toBeDisabled(); + await waitForSubmitEnabled(); }); - it('should be enabled when status text is changed', () => { + it('should be enabled when status text is changed', async () => { mockedStore.dispatch(setUser({ id: 'user-id', username: 'user', status: 'online', statusText: '' })); mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '6.0.0', name: 'Test' })); mockedStore.dispatch(addSettings({ Accounts_AllowInvisibleStatusOption: true })); @@ -158,12 +158,12 @@ describe('StatusView', () => { fireEvent.changeText(screen.getByTestId('status-view-input'), 'New status'); - expect(screen.getByTestId('status-view-submit')).not.toBeDisabled(); + await waitForSubmitEnabled(); }); }); describe('submit action', () => { - it('should call setUserStatus with status and statusText', () => { + it('should call setUserStatus with status and statusText', async () => { mockedStore.dispatch(setUser({ id: 'user-id', username: 'user', status: 'online', statusText: '' })); mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '6.0.0', name: 'Test' })); mockedStore.dispatch(addSettings({ Accounts_AllowInvisibleStatusOption: true })); @@ -172,12 +172,13 @@ describe('StatusView', () => { renderStatusView(); fireEvent.press(screen.getByTestId('status-view-busy')); + await waitForSubmitEnabled(); fireEvent.press(screen.getByTestId('status-view-submit')); expect(mockSetUserStatus).toHaveBeenCalledWith('busy', '', undefined); }); - it('should call setUserStatus on modern server with status change', () => { + it('should call setUserStatus on modern server with status change', async () => { mockedStore.dispatch(setUser({ id: 'user-id', username: 'user', status: 'online', statusText: '' })); mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '8.6.0', name: 'Test' })); mockedStore.dispatch(addSettings({ Accounts_AllowInvisibleStatusOption: true })); @@ -187,6 +188,7 @@ describe('StatusView', () => { fireEvent.press(screen.getByTestId('status-view-busy')); + await waitForSubmitEnabled(); fireEvent.press(screen.getByTestId('status-view-submit')); expect(mockSetUserStatus).toHaveBeenCalledWith('busy', '', undefined); @@ -217,6 +219,7 @@ describe('StatusView', () => { renderStatusView(); fireEvent.press(screen.getByTestId('status-view-busy')); + await waitForSubmitEnabled(); fireEvent.press(screen.getByTestId('status-view-submit')); await waitFor(() => expect(mockShowErrorAlertWithEMessage).toHaveBeenCalledWith(error)); @@ -234,7 +237,7 @@ describe('StatusView', () => { expect(screen.getByTestId('status-view-clear-after')).toBeOnTheScreen(); }); - it('should not pass statusExpiresAt on submit when picker was not touched', () => { + it('should not pass statusExpiresAt on submit when picker was not touched', async () => { mockedStore.dispatch( setUser({ id: 'user-id', @@ -251,12 +254,13 @@ describe('StatusView', () => { renderStatusView(); fireEvent.press(screen.getByTestId('status-view-online')); + await waitForSubmitEnabled(); fireEvent.press(screen.getByTestId('status-view-submit')); expect(mockSetUserStatus).toHaveBeenCalledWith('online', '', undefined); }); - it('should pass expiresAt on submit when picker is interacted with', () => { + it('should pass expiresAt on submit when picker is interacted with', async () => { jest.useFakeTimers(); jest.setSystemTime(new Date('2026-06-22T12:00:00.000Z')); try { @@ -274,6 +278,8 @@ describe('StatusView', () => { const { onConfirm } = children.props; act(() => onConfirm('30', null)); + await act(async () => {}); + expect(screen.getByTestId('status-view-submit')).not.toBeDisabled(); fireEvent.press(screen.getByTestId('status-view-submit')); expect(mockSetUserStatus).toHaveBeenCalledWith('online', '', '2026-06-22T12:30:00.000Z'); diff --git a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap index 7c912aaccbd..4b7c813051e 100644 --- a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap +++ b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap @@ -38,60 +38,63 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` ] } > - - - - - + - + - - - - - + - + - - - - - + - + - - - - - + - + - - - - - + - + - - - - - + - + - - - - - + - + - - - - - + - + - - - - - + - + - - - - - + - + - - - - - + - + - - - - - + - + - - - + - - - + - - - + @@ -807,60 +816,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -1194,60 +1212,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -1594,60 +1621,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - + - - - + @@ -1839,60 +1872,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - + @@ -2397,60 +2433,63 @@ exports[`Story Snapshots: Forced should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -2784,60 +2829,63 @@ exports[`Story Snapshots: Forced should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -3171,60 +3225,63 @@ exports[`Story Snapshots: Forced should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -3571,60 +3634,63 @@ exports[`Story Snapshots: Forced should match snapshot 1`] = ` ] } > - - - + - - - + diff --git a/app/views/__snapshots__/ScreenLockedView.test.tsx.snap b/app/views/__snapshots__/ScreenLockedView.test.tsx.snap index 6b786124828..730c6d5b776 100644 --- a/app/views/__snapshots__/ScreenLockedView.test.tsx.snap +++ b/app/views/__snapshots__/ScreenLockedView.test.tsx.snap @@ -421,60 +421,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -808,60 +817,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -1195,60 +1213,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -1595,60 +1622,63 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` ] } > - - - + - - - + @@ -2245,60 +2278,63 @@ exports[`Story Snapshots: WithBiometry should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -2632,60 +2674,63 @@ exports[`Story Snapshots: WithBiometry should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -3019,60 +3070,63 @@ exports[`Story Snapshots: WithBiometry should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -3406,60 +3466,63 @@ exports[`Story Snapshots: WithBiometry should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -4186,60 +4255,63 @@ exports[`Story Snapshots: WithBiometryAndClose should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -4573,60 +4651,63 @@ exports[`Story Snapshots: WithBiometryAndClose should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -4960,60 +5047,63 @@ exports[`Story Snapshots: WithBiometryAndClose should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -5347,60 +5443,63 @@ exports[`Story Snapshots: WithBiometryAndClose should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -5722,60 +5827,63 @@ exports[`Story Snapshots: WithBiometryAndClose should match snapshot 1`] = ` ] } > - - - + @@ -6241,60 +6349,63 @@ exports[`Story Snapshots: WithCloseButton should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -6628,60 +6745,63 @@ exports[`Story Snapshots: WithCloseButton should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -7015,60 +7141,63 @@ exports[`Story Snapshots: WithCloseButton should match snapshot 1`] = ` ] } > - - - + - - - + - - - + @@ -7415,60 +7550,63 @@ exports[`Story Snapshots: WithCloseButton should match snapshot 1`] = ` ] } > - - - + - - - + @@ -7660,60 +7801,63 @@ exports[`Story Snapshots: WithCloseButton should match snapshot 1`] = ` ] } > - - - + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 93a1b65ffb4..26f8830bd9c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2569,7 +2569,7 @@ PODS: - FirebaseCoreExtension - React-Core - RNFBApp - - RNGestureHandler (2.32.0): + - RNGestureHandler (3.3.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2591,6 +2591,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies + - RNWorklets (>= 0.8.0) - Yoga - RNImageCropPicker (0.51.1): - hermes-engine @@ -3604,7 +3605,7 @@ SPEC CHECKSUMS: RNFBAnalytics: 2e8b8ffcd2bb3d59a43ecbe09571c73c56edef7a RNFBApp: a448037d2df74af9d374a0b765be12ff1e844dc0 RNFBCrashlytics: c3bb5533f9957eddc88f3ea383583309a4ce9f89 - RNGestureHandler: 7b07d9192bc65c6883fb37fdecc05dc6b61c814f + RNGestureHandler: 85e32defcf23dd3b023356355c5c1dd1d9d51bde RNImageCropPicker: 5c9c8bef4c72d3f451b499aa0ecd90a644ea1bbd RNKeychain: d57edf101a72acf3ae85fe4d796eed1d951d2a90 RNLocalize: 17bce0556e2d33fe1201c8cef53b3b7786e07612 diff --git a/jest.config.js b/jest.config.js index 04500372058..7894a31d7b2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -24,5 +24,6 @@ module.exports = { '.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/__mocks__/fileMock.js', ...getAliasConfig().jest }, + setupFiles: ['react-native-gesture-handler/jestSetup.js'], setupFilesAfterEnv: ['./jest.setup.js'] }; diff --git a/jest.setup.js b/jest.setup.js index 506242c12db..ae1cffce138 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -67,20 +67,14 @@ jest.mock('expo-haptics', () => ({ } })); -jest.mock('react-native-gesture-handler', () => { - const { forwardRef } = require('react'); - const { View } = require('react-native'); - const GestureHandlerRootView = forwardRef(({ children, ...props }, ref) => ( - - {children} - - )); - GestureHandlerRootView.displayName = 'GestureHandlerRootView'; - return { - ...jest.requireActual('react-native-gesture-handler'), - GestureHandlerRootView, - gestureHandlerRootHOC: Component => Component - }; +jest.mock('react-native-gesture-handler/lib/module/v3/detectors/useEnsureGestureHandlerRootView', () => ({ + useEnsureGestureHandlerRootView: () => {} +})); + +jest.mock('react-native-gesture-handler/lib/module/v3/components/Touchable/Touchable', () => { + const { Pressable } = require('react-native'); + const Touchable = ({ children, ...props }) => {children}; + return { Touchable }; }); jest.mock('expo-font', () => ({ diff --git a/package.json b/package.json index db1b1ad61ff..01d89a1e918 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "react-native-easy-grid": "0.2.2", "react-native-easy-toast": "2.3.0", "react-native-external-keyboard": "^0.9.0", - "react-native-gesture-handler": "~2.32.0", + "react-native-gesture-handler": "~3.3.0", "react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker#47092e8c90550a54d45fe307f7cb2a24c9535ed5", "react-native-incall-manager": "^4.2.1", "react-native-katex": "git+https://github.com/RocketChat/react-native-katex.git", diff --git a/patches/react-native-gesture-handler+2.32.0.patch b/patches/react-native-gesture-handler+3.3.0.patch similarity index 54% rename from patches/react-native-gesture-handler+2.32.0.patch rename to patches/react-native-gesture-handler+3.3.0.patch index 4c16a356879..d5a28998eb6 100644 --- a/patches/react-native-gesture-handler+2.32.0.patch +++ b/patches/react-native-gesture-handler+3.3.0.patch @@ -1,9 +1,35 @@ +diff --git a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +index f964945..8df6494 100644 +--- a/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt ++++ b/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +@@ -1459,9 +1459,21 @@ class RNGestureHandlerButtonViewManager : + + override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { + receivedKeyEvent = true ++ if (isActivationKey(keyCode) && isEnabled && event?.isCanceled != true) { ++ performClick() ++ return true ++ } + return super.onKeyUp(keyCode, event) + } + ++ private fun isActivationKey(keyCode: Int) = when (keyCode) { ++ KeyEvent.KEYCODE_DPAD_CENTER, ++ KeyEvent.KEYCODE_ENTER, ++ KeyEvent.KEYCODE_NUMPAD_ENTER, ++ KeyEvent.KEYCODE_SPACE -> true ++ else -> false ++ } ++ + override fun performClick(): Boolean { + // don't perform click when a child button is pressed (mainly to prevent sound effect of + // a parent button from playing) diff --git a/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm b/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm -index 6b9fa9c..6d2ccea 100644 +index 994b3d3..c46411b 100644 --- a/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm +++ b/node_modules/react-native-gesture-handler/apple/RNGestureHandlerButton.mm -@@ -83,6 +83,52 @@ - (BOOL)shouldHandleTouch:(RNGHUIView *)view - } +@@ -1274,6 +1274,52 @@ static CATransform3D RNGHCenterScaleTransform(NSRect bounds, CGFloat scale) + #endif #if !TARGET_OS_OSX +- (BOOL)canBecomeFocused diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b97a73ed1b7..ef049482418 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,7 +25,7 @@ importers: version: 2.9.11(react-hook-form@7.34.2(react@19.2.3)) '@lodev09/react-native-true-sheet': specifier: ~3.7.0 - version: 3.7.3(559152e7857b125189ca42b7a151d6a4) + version: 3.7.3(4eac27da23d45fdf91d899c57069aba2) '@magrinj/expo-quick-look': specifier: 0.4.0 version: 0.4.0(expo@57.0.23)(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -76,7 +76,7 @@ importers: version: 0.86.3(@babel/core@7.29.7) '@react-navigation/drawer': specifier: ^7.14.1 - version: 7.14.2(dda132b9c8f311fd0053bf630a6001aa) + version: 7.14.2(2bc054b45d80945c6bf73a8ade7ad73e) '@react-navigation/elements': specifier: ^2.9.42 version: 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) @@ -237,8 +237,8 @@ importers: specifier: ^0.9.0 version: 0.9.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-gesture-handler: - specifier: ~2.32.0 - version: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + specifier: ~3.3.0 + version: 3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-image-crop-picker: specifier: RocketChat/react-native-image-crop-picker#47092e8c90550a54d45fe307f7cb2a24c9535ed5 version: https://codeload.github.com/RocketChat/react-native-image-crop-picker/tar.gz/47092e8c90550a54d45fe307f7cb2a24c9535ed5(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -277,7 +277,7 @@ importers: version: 0.37.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-notifier: specifier: 1.6.1 - version: 1.6.1(react-native-gesture-handler@2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + version: 1.6.1(react-native-gesture-handler@3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-picker-select: specifier: 9.0.1 version: 9.0.1(@react-native-picker/picker@2.11.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)) @@ -316,7 +316,7 @@ importers: version: 8.0.5(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(redux@4.2.0) reanimated-tab-view: specifier: ^0.3.0 - version: 0.3.0(435be115736f4209290803c40574d60f) + version: 0.3.0(106987a3d5ae1ef5de78591772a14578) redux: specifier: 4.2.0 version: 4.2.0 @@ -389,7 +389,7 @@ importers: version: 2.3.3 '@gorhom/bottom-sheet': specifier: ^5 - version: 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) + version: 5.2.8(6a38a83ca283ae4d61e53fcd24ae5b68) '@react-native-community/cli': specifier: 20.0.0 version: 20.0.0(typescript@7.0.2) @@ -413,7 +413,7 @@ importers: version: 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) '@storybook/react-native': specifier: ~9.0.9 - version: 9.0.18(abaa9f17cfa3ec17933d2333c1a01a26) + version: 9.0.18(3981ca50ab547f261d3351bbe700d2a7) '@testing-library/react-native': specifier: ^13.2.0 version: 13.3.3(jest@29.7.0(@types/node@25.0.3)(babel-plugin-macros@3.1.0))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3) @@ -1369,10 +1369,6 @@ packages: resolution: {integrity: sha512-DCCXhiY1CdCy3Eo6SS/qHnBuyrXY0jyefsJBpXemwI5eXEAR0KrhnhxbGU7Ga/8ysssD1A22J5488BYH1t4pgQ==} hasBin: true - '@egjs/hammerjs@2.0.17': - resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} - engines: {node: '>=0.8.0'} - '@emnapi/core@1.8.0': resolution: {integrity: sha512-ryJnSmj4UhrGLZZPJ6PKVb4wNPAIkW6iyLy+0TRwazd3L1u0wzMe8RfqevAh2HbcSkoeLiSYnOVDOys4JSGYyg==} @@ -2982,9 +2978,6 @@ packages: '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - '@types/hammerjs@2.0.46': - resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} - '@types/hast@2.3.10': resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} @@ -6689,8 +6682,8 @@ packages: react: '*' react-native: '*' - react-native-gesture-handler@2.32.0: - resolution: {integrity: sha512-uYIMOKlKENORq2SABE+jIjbPU+h5I/sQKcq2v16zRq848nwEp1fWRVwML4QWqijc8UcXJC25o54S8GQd4Mf2OA==} + react-native-gesture-handler@3.3.0: + resolution: {integrity: sha512-ZCTlJ09nE3GPbm7J74lvwpxmx1TQ/7MDlHZNLARVYTN82k21mJUJ7BmI9eWjEi8eevHiAudI5O1Cpnt0rJfoJA==} peerDependencies: react: '*' react-native: '*' @@ -9221,10 +9214,6 @@ snapshots: glob: 7.2.3 read-pkg-up: 7.0.1 - '@egjs/hammerjs@2.0.17': - dependencies: - '@types/hammerjs': 2.0.46 - '@emnapi/core@1.8.0': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -10049,13 +10038,13 @@ snapshots: '@firebase/webchannel-wrapper@1.0.3': {} - '@gorhom/bottom-sheet@5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e)': + '@gorhom/bottom-sheet@5.2.8(6a38a83ca283ae4d61e53fcd24ae5b68)': dependencies: '@gorhom/portal': 1.0.14(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) invariant: 2.2.4 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-gesture-handler: 3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) optionalDependencies: '@types/react': 19.2.18 @@ -10332,12 +10321,12 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@lodev09/react-native-true-sheet@3.7.3(559152e7857b125189ca42b7a151d6a4)': + '@lodev09/react-native-true-sheet@3.7.3(4eac27da23d45fdf91d899c57069aba2)': dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) optionalDependencies: - '@gorhom/bottom-sheet': 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) + '@gorhom/bottom-sheet': 5.2.8(6a38a83ca283ae4d61e53fcd24ae5b68) '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-worklets: 0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -10919,15 +10908,15 @@ snapshots: use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/drawer@7.14.2(dda132b9c8f311fd0053bf630a6001aa)': + '@react-navigation/drawer@7.14.2(2bc054b45d80945c6bf73a8ade7ad73e)': dependencies: '@react-navigation/elements': 2.9.43(322efc84ae2b5bd2002572a8329e2a5c) '@react-navigation/native': 7.4.1(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-drawer-layout: 4.2.10(435be115736f4209290803c40574d60f) - react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-drawer-layout: 4.2.10(106987a3d5ae1ef5de78591772a14578) + react-native-gesture-handler: 3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-screens: 4.28.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -11100,9 +11089,9 @@ snapshots: - react-dom - typescript - '@storybook/react-native-ui@9.1.4(c7e041ee7310df2b401daba6013bd73c)': + '@storybook/react-native-ui@9.1.4(db5a63f8584c69639086fd4f0ebb93bd)': dependencies: - '@gorhom/bottom-sheet': 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) + '@gorhom/bottom-sheet': 5.2.8(6a38a83ca283ae4d61e53fcd24ae5b68) '@storybook/react': 9.1.17(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) '@storybook/react-native-theming': 9.1.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) '@storybook/react-native-ui-common': 9.1.4(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) @@ -11112,7 +11101,7 @@ snapshots: polished: 4.3.1 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-gesture-handler: 3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-svg: 15.15.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) @@ -11122,12 +11111,12 @@ snapshots: - react-dom - typescript - '@storybook/react-native@9.0.18(abaa9f17cfa3ec17933d2333c1a01a26)': + '@storybook/react-native@9.0.18(3981ca50ab547f261d3351bbe700d2a7)': dependencies: '@storybook/global': 5.0.0 '@storybook/react': 9.0.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) '@storybook/react-native-theming': 9.1.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) - '@storybook/react-native-ui': 9.1.4(c7e041ee7310df2b401daba6013bd73c) + '@storybook/react-native-ui': 9.1.4(db5a63f8584c69639086fd4f0ebb93bd) '@storybook/react-native-ui-common': 9.1.4(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.7.4))(typescript@7.0.2) commander: 8.3.0 dedent: 1.6.0(babel-plugin-macros@3.1.0) @@ -11141,8 +11130,8 @@ snapshots: util: 0.12.5 ws: 8.18.3 optionalDependencies: - '@gorhom/bottom-sheet': 5.2.8(9a24a74e7d311c12f9fcd11ab8b8075e) - react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@gorhom/bottom-sheet': 5.2.8(6a38a83ca283ae4d61e53fcd24ae5b68) + react-native-gesture-handler: 3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: 5.10.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) transitivePeerDependencies: @@ -11271,8 +11260,6 @@ snapshots: dependencies: '@types/node': 25.0.3 - '@types/hammerjs@2.0.46': {} - '@types/hast@2.3.10': dependencies: '@types/unist': 2.0.11 @@ -15641,12 +15628,12 @@ snapshots: dependencies: react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-drawer-layout@4.2.10(435be115736f4209290803c40574d60f): + react-native-drawer-layout@4.2.10(106987a3d5ae1ef5de78591772a14578): dependencies: color: 4.2.3 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-gesture-handler: 3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) @@ -15665,11 +15652,9 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-gesture-handler@2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + react-native-gesture-handler@3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: - '@egjs/hammerjs': 2.0.17 '@types/react-test-renderer': 19.3.0 - hoist-non-react-statics: 3.3.2 invariant: 2.2.4 react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) @@ -15751,11 +15736,11 @@ snapshots: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-notifier@1.6.1(react-native-gesture-handler@2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + react-native-notifier@1.6.1(react-native-gesture-handler@3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-gesture-handler: 3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-picker-select@9.0.1(@react-native-picker/picker@2.11.4(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)): dependencies: @@ -15936,11 +15921,11 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - reanimated-tab-view@0.3.0(435be115736f4209290803c40574d60f): + reanimated-tab-view@0.3.0(106987a3d5ae1ef5de78591772a14578): dependencies: react: 19.2.3 react-native: 0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3) - react-native-gesture-handler: 2.32.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + react-native-gesture-handler: 3.3.0(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.6.0(react-native-worklets@0.12.2(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native-community/cli@20.0.0(typescript@7.0.2))(@react-native/jest-preset@0.86.3(@babel/core@7.29.7)(react@19.2.3))(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) recast@0.23.11: From e383ca486c293b7420799f0a0df4a9a6487338f6 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 29 Sep 2026 10:41:41 -0300 Subject: [PATCH 108/120] test: refresh audio button snapshots after gesture handler upgrade --- .../__snapshots__/CancelButton.test.tsx.snap | 96 +++++++++++++------ .../__snapshots__/ReviewButton.test.tsx.snap | 46 ++++++--- 2 files changed, 98 insertions(+), 44 deletions(-) diff --git a/app/containers/MessageComposer/components/RecordAudio/__tests__/__snapshots__/CancelButton.test.tsx.snap b/app/containers/MessageComposer/components/RecordAudio/__tests__/__snapshots__/CancelButton.test.tsx.snap index 75a95c41fb2..57b49fd2283 100644 --- a/app/containers/MessageComposer/components/RecordAudio/__tests__/__snapshots__/CancelButton.test.tsx.snap +++ b/app/containers/MessageComposer/components/RecordAudio/__tests__/__snapshots__/CancelButton.test.tsx.snap @@ -1,13 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`a11y label is cancel when cancelAndDelete 1`] = ` - - + `; exports[`a11y label is delete otherwise and fires onPress 1`] = ` - - + `; diff --git a/app/containers/MessageComposer/components/RecordAudio/__tests__/__snapshots__/ReviewButton.test.tsx.snap b/app/containers/MessageComposer/components/RecordAudio/__tests__/__snapshots__/ReviewButton.test.tsx.snap index 0856345019b..f9aa42ffea7 100644 --- a/app/containers/MessageComposer/components/RecordAudio/__tests__/__snapshots__/ReviewButton.test.tsx.snap +++ b/app/containers/MessageComposer/components/RecordAudio/__tests__/__snapshots__/ReviewButton.test.tsx.snap @@ -1,13 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders and fires onPress 1`] = ` - - + `; From e1fe84c3fd149f56211c9f2c09a5a80a80584377 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 29 Sep 2026 11:03:49 -0300 Subject: [PATCH 109/120] fix: restore React Native Gradle plugin composite build --- android/settings.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/settings.gradle b/android/settings.gradle index 20c52556af3..291937214cc 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -14,4 +14,6 @@ include ':watermelondb-jsi' project(':watermelondb-jsi').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android-jsi') include ':app' +includeBuild('../node_modules/@react-native/gradle-plugin') + expoAutolinking.useExpoModules() From 56ab7b23038de9280c4c06a84419bd08a08f3cee Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 29 Sep 2026 11:33:02 -0300 Subject: [PATCH 110/120] fix(ios): render sidebar sections inline in iPad settings --- app/views/SettingsView/index.tsx | 7 +- app/views/SidebarView/index.ios.tsx | 67 +---------------- .../SidebarView/useSidebarSections.ios.tsx | 75 +++++++++++++++++++ app/views/SidebarView/useSidebarSections.tsx | 13 ++++ 4 files changed, 94 insertions(+), 68 deletions(-) create mode 100644 app/views/SidebarView/useSidebarSections.ios.tsx create mode 100644 app/views/SidebarView/useSidebarSections.tsx diff --git a/app/views/SettingsView/index.tsx b/app/views/SettingsView/index.tsx index 8097918658f..45897777cc3 100644 --- a/app/views/SettingsView/index.tsx +++ b/app/views/SettingsView/index.tsx @@ -30,13 +30,14 @@ import { onReviewPress } from '~/lib/methods/helpers/review'; import { type SettingsStackParamList } from '~/stacks/types'; import { useTheme } from '~/theme'; import { disconnect } from '~/lib/services/connect'; -import SidebarView from '../SidebarView'; +import { useSidebarSections } from '../SidebarView/useSidebarSections'; type TLogScreenName = 'SE_GO_LANGUAGE' | 'SE_GO_DEFAULTBROWSER' | 'SE_GO_THEME' | 'SE_GO_PROFILE' | 'SE_GO_SECURITYPRIVACY'; const SettingsView = (): ReactElement => { const { colors, theme } = useTheme(); const navigation = useNavigation>(); + const { sections: sidebarSections } = useSidebarSections(navigation as any); const dispatch = useDispatch(); const isMasterDetail = useMasterDetail(); const { server, version } = useAppSelector(state => state.server); @@ -137,9 +138,7 @@ const SettingsView = (): ReactElement => { {isMasterDetail ? ( <> - - - + {sidebarSections} ) => { - const state = navigation.getState(); - return state.routes[state.index].name; -}; +import { useSidebarSections } from './useSidebarSections.ios'; const SidebarView = ({ navigation }: { navigation: DrawerNavigationProp }) => { - const [currentScreen, setCurrentScreen] = useState(() => currentRouteName(navigation)); - const isSupportedVersionsWarnVisible = useIsSupportedVersionsWarnVisible(); - const isCustomStatusVisible = useIsCustomStatusVisible(); - const stackItems = useStackItems(currentScreen); - const isAdmin = useIsAdmin(); - const adminRoute = useAdminRoute(); - const selection = getSidebarSelection(stackItems, isAdmin ? adminRoute : null, currentScreen); - - useEffect(() => { - const unsubscribe = navigation.addListener('state', () => { - setCurrentScreen(currentRouteName(navigation)); - }); - - return unsubscribe; - }, [navigation]); + const { sections, selection } = useSidebarSections(navigation); return ( - - - - - {isSupportedVersionsWarnVisible || isCustomStatusVisible ? ( - - {isSupportedVersionsWarnVisible ? : null} - {isCustomStatusVisible ? : null} - - ) : null} - {stackItems.length ? ( - - {stackItems.map(item => ( - } - onPress={item.onPress} - testID={item.testID} - disabled={item.disabled} - /> - ))} - - ) : null} - {isAdmin ? ( - - } - onPress={() => sidebarNavigate(adminRoute)} - /> - - ) : null} - + {sections} ); }; diff --git a/app/views/SidebarView/useSidebarSections.ios.tsx b/app/views/SidebarView/useSidebarSections.ios.tsx new file mode 100644 index 00000000000..b6b78bd6e5a --- /dev/null +++ b/app/views/SidebarView/useSidebarSections.ios.tsx @@ -0,0 +1,75 @@ +import { useEffect, useState } from 'react'; +import { type DrawerNavigationProp } from '@react-navigation/drawer'; + +import * as List from '~/containers/List'; +import { type DrawerParamList } from '~/stacks/types'; +import SupportedVersionsWarnItem, { useIsSupportedVersionsWarnVisible } from './components/SupportedVersionsWarnItem'; +import CustomStatus, { useIsCustomStatusVisible } from './components/CustomStatus'; +import { useStackItems } from './components/useStackItems'; +import { ADMIN_SELECTION_TAG, getSidebarSelection } from './components/getSidebarSelection'; +import { useAdminRoute, useIsAdmin } from './components/Admin'; +import { sidebarNavigate } from './methods/sidebarNavigate'; +import Profile from './components/Profile'; + +const currentRouteName = (navigation: DrawerNavigationProp) => { + const state = navigation.getState(); + return state.routes[state.index].name; +}; + +export const useSidebarSections = (navigation: DrawerNavigationProp) => { + const [currentScreen, setCurrentScreen] = useState(() => currentRouteName(navigation)); + const isSupportedVersionsWarnVisible = useIsSupportedVersionsWarnVisible(); + const isCustomStatusVisible = useIsCustomStatusVisible(); + const stackItems = useStackItems(currentScreen); + const isAdmin = useIsAdmin(); + const adminRoute = useAdminRoute(); + const selection = getSidebarSelection(stackItems, isAdmin ? adminRoute : null, currentScreen); + + useEffect(() => { + const unsubscribe = navigation.addListener('state', () => { + setCurrentScreen(currentRouteName(navigation)); + }); + + return unsubscribe; + }, [navigation]); + + const sections = ( + <> + + + + {isSupportedVersionsWarnVisible || isCustomStatusVisible ? ( + + {isSupportedVersionsWarnVisible ? : null} + {isCustomStatusVisible ? : null} + + ) : null} + {stackItems.length ? ( + + {stackItems.map(item => ( + } + onPress={item.onPress} + testID={item.testID} + disabled={item.disabled} + /> + ))} + + ) : null} + {isAdmin ? ( + + } + onPress={() => sidebarNavigate(adminRoute)} + /> + + ) : null} + + ); + + return { sections, selection }; +}; diff --git a/app/views/SidebarView/useSidebarSections.tsx b/app/views/SidebarView/useSidebarSections.tsx new file mode 100644 index 00000000000..7e4e81dcf7e --- /dev/null +++ b/app/views/SidebarView/useSidebarSections.tsx @@ -0,0 +1,13 @@ +import { type DrawerNavigationProp } from '@react-navigation/drawer'; + +import * as List from '~/containers/List'; +import { type DrawerParamList } from '~/stacks/types'; +import SidebarView from '.'; + +export const useSidebarSections = (navigation: DrawerNavigationProp) => ({ + sections: ( + + + + ) +}); From b95a70b6d7bffe4203d15a3038ab6f7d4782a4ec Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 29 Sep 2026 17:41:08 -0300 Subject: [PATCH 111/120] fix(ios): keep stacked search bars visible when lists scroll --- .maestro/helpers/close-search.yaml | 13 +++++++++++++ .maestro/tests/assorted/broadcast.yaml | 1 + app/lib/methods/helpers/navigation/index.ts | 1 + 3 files changed, 15 insertions(+) create mode 100644 .maestro/helpers/close-search.yaml diff --git a/.maestro/helpers/close-search.yaml b/.maestro/helpers/close-search.yaml new file mode 100644 index 00000000000..db6560964fd --- /dev/null +++ b/.maestro/helpers/close-search.yaml @@ -0,0 +1,13 @@ +appId: ${APP_ID} +name: Close search +tags: + - 'util' +--- +- runFlow: + when: + platform: iOS + visible: + text: 'Close' + commands: + - tapOn: + text: 'Close' diff --git a/.maestro/tests/assorted/broadcast.yaml b/.maestro/tests/assorted/broadcast.yaml index fd9347c288e..633472aa649 100644 --- a/.maestro/tests/assorted/broadcast.yaml +++ b/.maestro/tests/assorted/broadcast.yaml @@ -53,6 +53,7 @@ tags: visible: id: 'selected-user-${output.otherUser.username}' timeout: 60000 +- runFlow: '../../helpers/close-search.yaml' - assertVisible: id: 'selected-users-view-submit' - tapOn: diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 55697b58fba..3614e6b58c8 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -57,6 +57,7 @@ export const stackedSearchBarOptions = ({ }: IStackedSearchBarOptions): SearchBarProps => ({ ref, placement: 'stacked', + hideWhenScrolling: false, placeholder: I18n.t('Search'), onFocus, onChangeText: event => onChangeText(event.nativeEvent.text), From d8c47f42af57fbac59d6d09de614bd473aff3bcc Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 29 Sep 2026 17:41:14 -0300 Subject: [PATCH 112/120] test(ios): tap the room header title by testID to open room actions --- .maestro/helpers/open-room-actions.yaml | 32 ++--- .../RoomView/hooks/useNativeRoomHeader.ts | 1 + ...react-navigation+native-stack+7.19.2.patch | 52 +++++-- patches/react-native-screens+4.28.0.patch | 128 ++++++++++++------ 4 files changed, 137 insertions(+), 76 deletions(-) diff --git a/.maestro/helpers/open-room-actions.yaml b/.maestro/helpers/open-room-actions.yaml index 66138ed7f2b..e351514fbfc 100644 --- a/.maestro/helpers/open-room-actions.yaml +++ b/.maestro/helpers/open-room-actions.yaml @@ -4,30 +4,14 @@ tags: - 'util' --- -- runFlow: - when: - platform: iOS - commands: - - tapOn: - text: 'More' - retryTapIfNoChange: true - - extendedWaitUntil: - visible: - text: 'Actions' - timeout: 60000 - - tapOn: - text: 'Actions' -- runFlow: - when: - platform: Android - commands: - - assertVisible: - id: 'room-header' - index: 0 - - tapOn: - id: 'room-header' - index: 0 - retryTapIfNoChange: true +- extendedWaitUntil: + visible: + id: 'room-header' + timeout: 60000 +- tapOn: + id: 'room-header' + index: 0 + retryTapIfNoChange: true - extendedWaitUntil: visible: id: 'room-actions-view' diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts index 27f7d6b6ed7..04b94c7ae42 100644 --- a/app/views/RoomView/hooks/useNativeRoomHeader.ts +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -153,6 +153,7 @@ export const useNativeRoomHeader = ( headerSubtitleImageSource: subtitleImage, headerTitleStyle: { color: colors.fontTitlesLabels }, headerSubtitleColor: colors.fontSecondaryInfo, + headerTitleTestID: 'room-header', onHeaderTitlePress: onTitlePress && !fields.disabled ? () => onTitlePress() : undefined }); } diff --git a/patches/@react-navigation+native-stack+7.19.2.patch b/patches/@react-navigation+native-stack+7.19.2.patch index 204588af899..cca22e78ec9 100644 --- a/patches/@react-navigation+native-stack+7.19.2.patch +++ b/patches/@react-navigation+native-stack+7.19.2.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js -index a56cb12..6d4b38e 100644 +index a56cb12..3b65c3a 100644 --- a/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js +++ b/node_modules/@react-navigation/native-stack/lib/module/views/useHeaderConfigProps.js -@@ -149,7 +149,12 @@ export function useHeaderConfigProps({ +@@ -149,7 +149,13 @@ export function useHeaderConfigProps({ headerStyle, headerBlurEffect, headerTintColor, @@ -12,10 +12,11 @@ index a56cb12..6d4b38e 100644 headerTitle, + headerTitleImageSource, + onHeaderTitlePress, ++ headerTitleTestID, headerTitleAlign, headerTitleStyle, headerTransparent, -@@ -230,6 +235,15 @@ export function useHeaderConfigProps({ +@@ -230,6 +236,15 @@ export function useHeaderConfigProps({ tintColor, children: titleText }) : null; @@ -31,7 +32,7 @@ index a56cb12..6d4b38e 100644 const supportsHeaderSearchBar = typeof isSearchBarAvailableForCurrentPlatform === 'boolean' ? isSearchBarAvailableForCurrentPlatform : // Fallback for older versions of react-native-screens Platform.OS === 'ios' && SearchBar != null; -@@ -356,7 +370,13 @@ export function useHeaderConfigProps({ +@@ -356,7 +371,14 @@ export function useHeaderConfigProps({ largeTitleFontSize, largeTitleFontWeight, largeTitleHideShadow: headerLargeTitleShadowVisible === false, @@ -42,14 +43,15 @@ index a56cb12..6d4b38e 100644 + titleImageSource, + titlePressEnabled: titlePress != null, + onTitlePress: titlePress, ++ titleTestID: headerTitleTestID, titleColor, titleFontFamily, titleFontSize, diff --git a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts -index c32d1da..7109bb9 100644 +index c32d1da..c011b9b 100644 --- a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts +++ b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts -@@ -321,6 +321,64 @@ export type NativeStackNavigationOptions = { +@@ -321,6 +321,74 @@ export type NativeStackNavigationOptions = { */ tintColor?: string; }) => React.ReactNode); @@ -111,10 +113,20 @@ index c32d1da..7109bb9 100644 + * @supported iOS 26 or higher + */ + onHeaderTitlePress?: () => void; ++ /** ++ * Accessibility identifier of the native header title, for e2e tests. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerTitleTestID?: string; /** * How to align the the header title. * Defaults to `left` on platforms other than iOS. -@@ -937,6 +995,7 @@ export type NativeStackHeaderItemButton = SharedHeaderItem & { +@@ -937,6 +1005,7 @@ export type NativeStackHeaderItemButton = SharedHeaderItem & { * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/isselected */ selected?: boolean; @@ -123,10 +135,10 @@ index c32d1da..7109bb9 100644 /** * An action item in a menu. diff --git a/node_modules/@react-navigation/native-stack/src/types.tsx b/node_modules/@react-navigation/native-stack/src/types.tsx -index aeb4939..7016cb9 100644 +index aeb4939..3e02d47 100644 --- a/node_modules/@react-navigation/native-stack/src/types.tsx +++ b/node_modules/@react-navigation/native-stack/src/types.tsx -@@ -371,6 +371,64 @@ export type NativeStackNavigationOptions = { +@@ -371,6 +371,74 @@ export type NativeStackNavigationOptions = { */ tintColor?: string; }) => React.ReactNode); @@ -188,10 +200,20 @@ index aeb4939..7016cb9 100644 + * @supported iOS 26 or higher + */ + onHeaderTitlePress?: () => void; ++ /** ++ * Accessibility identifier of the native header title, for e2e tests. ++ * ++ * Only supported on iOS. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ headerTitleTestID?: string; /** * How to align the the header title. * Defaults to `left` on platforms other than iOS. -@@ -994,6 +1052,7 @@ export type NativeStackHeaderItemButton = SharedHeaderItem & { +@@ -994,6 +1062,7 @@ export type NativeStackHeaderItemButton = SharedHeaderItem & { * Read more: https://developer.apple.com/documentation/uikit/uibarbuttonitem/isselected */ selected?: boolean; @@ -200,10 +222,10 @@ index aeb4939..7016cb9 100644 /** diff --git a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx -index a7e3004..c57096f 100644 +index a7e3004..180964c 100644 --- a/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx +++ b/node_modules/@react-navigation/native-stack/src/views/useHeaderConfigProps.tsx -@@ -210,7 +210,12 @@ export function useHeaderConfigProps({ +@@ -210,7 +210,13 @@ export function useHeaderConfigProps({ headerStyle, headerBlurEffect, headerTintColor, @@ -213,10 +235,11 @@ index a7e3004..c57096f 100644 headerTitle, + headerTitleImageSource, + onHeaderTitlePress, ++ headerTitleTestID, headerTitleAlign, headerTitleStyle, headerTransparent, -@@ -330,6 +335,22 @@ export function useHeaderConfigProps({ +@@ -330,6 +336,22 @@ export function useHeaderConfigProps({ }) : null; @@ -239,7 +262,7 @@ index a7e3004..c57096f 100644 const supportsHeaderSearchBar = typeof isSearchBarAvailableForCurrentPlatform === 'boolean' ? isSearchBarAvailableForCurrentPlatform -@@ -522,7 +543,13 @@ export function useHeaderConfigProps({ +@@ -522,7 +544,14 @@ export function useHeaderConfigProps({ largeTitleFontSize, largeTitleFontWeight, largeTitleHideShadow: headerLargeTitleShadowVisible === false, @@ -250,6 +273,7 @@ index a7e3004..c57096f 100644 + titleImageSource, + titlePressEnabled: titlePress != null, + onTitlePress: titlePress, ++ titleTestID: headerTitleTestID, titleColor, titleFontFamily, titleFontSize, diff --git a/patches/react-native-screens+4.28.0.patch b/patches/react-native-screens+4.28.0.patch index 70329e2af5b..770f7cb0186 100644 --- a/patches/react-native-screens+4.28.0.patch +++ b/patches/react-native-screens+4.28.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt -index fa1b77317f..41866aa0ee 100644 +index fa1b773..8fe75d7 100644 --- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt @@ -3,6 +3,7 @@ package com.swmansion.rnscreens @@ -10,7 +10,7 @@ index fa1b77317f..41866aa0ee 100644 import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.StateWrapper -@@ -236,6 +237,31 @@ class ScreenStackHeaderConfigViewManager : +@@ -236,6 +237,36 @@ class ScreenStackHeaderConfigViewManager : value: String?, ) = Unit @@ -38,12 +38,17 @@ index fa1b77317f..41866aa0ee 100644 + view: ScreenStackHeaderConfig?, + value: Boolean, + ) = Unit ++ ++ override fun setTitleTestID( ++ view: ScreenStackHeaderConfig?, ++ value: String?, ++ ) = Unit + override fun setBackTitleFontFamily( view: ScreenStackHeaderConfig?, value: String?, diff --git a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm -index 64beb499a6..14bb54aa48 100644 +index 64beb49..14bb54a 100644 --- a/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm +++ b/node_modules/react-native-screens/ios/helpers/image/RNSImageLoadingHelper.mm @@ -13,6 +13,15 @@ @@ -79,7 +84,7 @@ index 64beb499a6..14bb54aa48 100644 size:imageSource.size scale:imageSource.scale diff --git a/node_modules/react-native-screens/ios/RNSBarButtonItem.mm b/node_modules/react-native-screens/ios/RNSBarButtonItem.mm -index 76a39d4df4..160a373016 100644 +index 76a39d4..160a373 100644 --- a/node_modules/react-native-screens/ios/RNSBarButtonItem.mm +++ b/node_modules/react-native-screens/ios/RNSBarButtonItem.mm @@ -126,10 +126,34 @@ static UIMenuOptions RNSMakeUIMenuOptionsFromConfig(NSDictionary *config); @@ -118,7 +123,7 @@ index 76a39d4df4..160a373016 100644 imageLoader:(RCTImageLoader *)imageLoader completionBlock:(void (^)(UIImage *_Nullable))completionBlock diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h -index 8a41697223..c6f3c20ce4 100644 +index 8a41697..6e1b3e3 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h +++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.h @@ -8,10 +8,14 @@ @@ -136,7 +141,7 @@ index 8a41697223..c6f3c20ce4 100644 @end @interface RNSScreenStackHeaderConfig : RNSReactBaseView -@@ -23,10 +27,15 @@ +@@ -23,10 +27,16 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, retain) NSString *title; @@ -149,11 +154,12 @@ index 8a41697223..c6f3c20ce4 100644 +@property (nonatomic, retain, nullable) RCTImageSource *titleImageSource; +@property (nonatomic, retain, nullable) RCTImageSource *subtitleImageSource; +@property (nonatomic) BOOL titlePressEnabled; ++@property (nonatomic, retain, nullable) NSString *titleTestID; @property (nonatomic, retain) NSString *backTitle; @property (nonatomic, retain) NSString *backTitleFontFamily; @property (nonatomic, retain) NSNumber *backTitleFontSize; diff --git a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm -index a54cb632e7..592a6c2fbd 100644 +index a54cb63..366121a 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +++ b/node_modules/react-native-screens/ios/RNSScreenStackHeaderConfig.mm @@ -1,4 +1,5 @@ @@ -178,14 +184,14 @@ index a54cb632e7..592a6c2fbd 100644 #import "RNSScreen.h" #import "RNSSearchBar.h" #import "UINavigationBar+RNSUtility.h" -@@ -28,11 +31,172 @@ namespace react = facebook::react; +@@ -28,11 +31,200 @@ namespace react = facebook::react; static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17; static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; +static const NSNumber *const DEFAULT_SUBTITLE_FONT_SIZE = @12; + +#define RNS_TITLE_GLYPH_PLACEHOLDER @"\uFFFC" -+#define RNS_TITLE_GLYPH_SEPARATOR @"\u2009" ++#define RNS_TITLE_GLYPH_SPACING_EM 0.2 @interface RCTImageLoader (Private) - (id)imageCache; @@ -214,6 +220,7 @@ index a54cb632e7..592a6c2fbd 100644 + RNSHeaderTitlePressHandler *handler = objc_getAssociatedObject(bar, RNSHeaderTitlePressHandlerKey); + if (handler != nil) { + handler.navigationController = navigationController; ++ [handler scheduleTitleIdentifier]; + return; + } + handler = [RNSHeaderTitlePressHandler new]; @@ -227,6 +234,33 @@ index a54cb632e7..592a6c2fbd 100644 + recognizer.delegate = handler; + [navigationController.view addGestureRecognizer:recognizer]; + objc_setAssociatedObject(bar, RNSHeaderTitlePressHandlerKey, handler, OBJC_ASSOCIATION_RETAIN_NONATOMIC); ++ [handler scheduleTitleIdentifier]; ++} ++ ++- (void)scheduleTitleIdentifier ++{ ++ __weak RNSHeaderTitlePressHandler *weakSelf = self; ++ dispatch_async(dispatch_get_main_queue(), ^{ ++ [weakSelf applyTitleIdentifier]; ++ }); ++ [self.navigationController.transitionCoordinator ++ animateAlongsideTransition:nil ++ completion:^(id context) { ++ [weakSelf applyTitleIdentifier]; ++ }]; ++} ++ ++- (void)applyTitleIdentifier ++{ ++ RNSScreenStackHeaderConfig *config = [self topConfig]; ++ if (config.titleTestID == nil) { ++ return; ++ } ++ for (UIView *view in [self titleViewsInBar:self.navigationController.navigationBar forConfig:config]) { ++ if ([view isKindOfClass:[UILabel class]]) { ++ view.accessibilityIdentifier = config.titleTestID; ++ } ++ } +} + +- (nullable RNSScreenStackHeaderConfig *)topConfig @@ -351,7 +385,7 @@ index a54cb632e7..592a6c2fbd 100644 @implementation NSString (RNSStringUtil) + (BOOL)rnscreens_isBlankOrNull:(NSString *)string -@@ -43,6 +207,105 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; +@@ -43,6 +235,105 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; return [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0; } @@ -361,7 +395,7 @@ index a54cb632e7..592a6c2fbd 100644 + return nil; + } + NSCharacterSet *glyphCharacters = -+ [NSCharacterSet characterSetWithCharactersInString:RNS_TITLE_GLYPH_PLACEHOLDER RNS_TITLE_GLYPH_SEPARATOR]; ++ [NSCharacterSet characterSetWithCharactersInString:RNS_TITLE_GLYPH_PLACEHOLDER]; + return [[string componentsSeparatedByCharactersInSet:glyphCharacters] componentsJoinedByString:@""]; +} + @@ -457,7 +491,7 @@ index a54cb632e7..592a6c2fbd 100644 @end @interface RNSScreenStackHeaderConfig () -@@ -59,6 +322,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; +@@ -59,6 +350,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; /// transaction via RCTMountingTransactionObserving protocol. bool _addedReactSubviewsInCurrentTransaction; RCTImageLoader *_imageLoader; @@ -466,7 +500,7 @@ index a54cb632e7..592a6c2fbd 100644 } - (instancetype)initWithFrame:(CGRect)frame -@@ -69,6 +334,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; +@@ -69,6 +362,8 @@ static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; _show = YES; _translucent = NO; _addedReactSubviewsInCurrentTransaction = false; @@ -475,7 +509,7 @@ index a54cb632e7..592a6c2fbd 100644 _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}, react::Point{}); [self initProps]; } -@@ -131,6 +398,56 @@ RNS_IGNORE_SUPER_CALL_END +@@ -131,6 +426,56 @@ RNS_IGNORE_SUPER_CALL_END return nil; } @@ -532,7 +566,7 @@ index a54cb632e7..592a6c2fbd 100644 - (void)updateViewControllerIfNeeded { UIViewController *vc = _screenView.controller; -@@ -181,10 +498,10 @@ RNS_IGNORE_SUPER_CALL_END +@@ -181,10 +526,10 @@ RNS_IGNORE_SUPER_CALL_END if (newState != _lastSendState) { _lastSendState = newState; @@ -547,7 +581,7 @@ index a54cb632e7..592a6c2fbd 100644 } } -@@ -293,10 +610,11 @@ RNS_IGNORE_SUPER_CALL_END +@@ -293,10 +638,11 @@ RNS_IGNORE_SUPER_CALL_END // in the image attribute not being updated. We manually set frame to the size of an image // in order to trigger proper reload that'd update the image attribute. RCTImageSource *imageSource = [RNSScreenStackHeaderConfig imageSourceFromImageView:imageView]; @@ -563,7 +597,7 @@ index a54cb632e7..592a6c2fbd 100644 } UIImage *image = imageView.image; -@@ -365,6 +683,81 @@ RNS_IGNORE_SUPER_CALL_END +@@ -365,6 +711,89 @@ RNS_IGNORE_SUPER_CALL_END } } @@ -628,15 +662,23 @@ index a54cb632e7..592a6c2fbd 100644 + CGSize intrinsicSize = glyph.size; + CGFloat aspectRatio = intrinsicSize.height > 0 ? intrinsicSize.width / intrinsicSize.height : 1; + ++ CGFloat glyphWidth = glyphHeight * aspectRatio; ++ CGFloat spacing = font.pointSize * RNS_TITLE_GLYPH_SPACING_EM; ++ UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat]; ++ format.opaque = NO; ++ UIImage *paddedGlyph = [[[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(glyphWidth + spacing, glyphHeight) ++ format:format] ++ imageWithActions:^(UIGraphicsImageRendererContext *context) { ++ [glyph drawInRect:CGRectMake(0, 0, glyphWidth, glyphHeight)]; ++ }]; ++ + NSTextAttachment *attachment = [NSTextAttachment new]; -+ attachment.image = [glyph imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; -+ attachment.bounds = -+ CGRectMake(0, (font.capHeight - glyphHeight) / 2, glyphHeight * aspectRatio, glyphHeight); ++ attachment.image = [paddedGlyph imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; ++ attachment.bounds = CGRectMake(0, (font.capHeight - glyphHeight) / 2, glyphWidth + spacing, glyphHeight); + + NSMutableAttributedString *composed = [[NSMutableAttributedString alloc] + initWithAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; -+ [composed appendAttributedString:[[NSAttributedString alloc] -+ initWithString:[RNS_TITLE_GLYPH_SEPARATOR stringByAppendingString:text]]]; ++ [composed appendAttributedString:[[NSAttributedString alloc] initWithString:text]]; + [composed addAttributes:textAttributes range:NSMakeRange(0, composed.length)]; + + return composed; @@ -645,7 +687,7 @@ index a54cb632e7..592a6c2fbd 100644 + (UINavigationBarAppearance *)buildAppearance:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config { UINavigationBarAppearance *appearance = [UINavigationBarAppearance new]; -@@ -380,8 +773,10 @@ RNS_IGNORE_SUPER_CALL_END +@@ -380,8 +809,10 @@ RNS_IGNORE_SUPER_CALL_END appearance.shadowColor = shadowColor; appearance.shadowImage = shadowImage; } @@ -657,7 +699,7 @@ index a54cb632e7..592a6c2fbd 100644 } // set background color if specified -@@ -408,59 +803,20 @@ RNS_IGNORE_SUPER_CALL_END +@@ -408,59 +839,20 @@ RNS_IGNORE_SUPER_CALL_END } if (config.titleFontFamily || config.titleFontSize || config.titleFontWeight || config.titleColor) { @@ -726,7 +768,7 @@ index a54cb632e7..592a6c2fbd 100644 } UIImage *backButtonImage = [config loadBackButtonImageInViewController:vc]; -@@ -507,6 +863,11 @@ RNS_IGNORE_SUPER_CALL_END +@@ -507,6 +899,11 @@ RNS_IGNORE_SUPER_CALL_END if (shouldHide) { navitem.title = config.title; @@ -738,7 +780,7 @@ index a54cb632e7..592a6c2fbd 100644 // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items. [navctr setNavigationBarHidden:YES animated:animated]; -@@ -514,6 +875,11 @@ RNS_IGNORE_SUPER_CALL_END +@@ -514,6 +911,11 @@ RNS_IGNORE_SUPER_CALL_END } navctr.navigationBar.overrideUserInterfaceStyle = config.userInterfaceStyle; @@ -750,7 +792,7 @@ index a54cb632e7..592a6c2fbd 100644 #if !TARGET_OS_TV [config configureBackItem:prevItem withPrevVC:prevVC]; -@@ -648,6 +1014,21 @@ RNS_IGNORE_SUPER_CALL_END +@@ -648,6 +1050,21 @@ RNS_IGNORE_SUPER_CALL_END // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug). // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments) navitem.title = config.title; @@ -772,7 +814,7 @@ index a54cb632e7..592a6c2fbd 100644 navitem.leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems withCurrentItems:navitem.leftBarButtonItems]; navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems -@@ -700,7 +1081,8 @@ RNS_IGNORE_SUPER_CALL_END +@@ -700,7 +1117,8 @@ RNS_IGNORE_SUPER_CALL_END const auto *config = self; const auto isBackTitleBlank = [NSString rnscreens_isBlankOrNull:config.backTitle] == YES; @@ -782,7 +824,7 @@ index a54cb632e7..592a6c2fbd 100644 // If previous screen controller was recreated (e.g. when you go back to tab with stack that has multiple screens), // its navigationItem may not have any information from screen's headerConfig, including the title. -@@ -858,12 +1240,13 @@ RNS_IGNORE_SUPER_CALL_END +@@ -858,12 +1276,13 @@ RNS_IGNORE_SUPER_CALL_END return; } @@ -802,7 +844,7 @@ index a54cb632e7..592a6c2fbd 100644 // [_reactSubviews insertObject:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; [self insertReactSubview:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index]; -@@ -987,6 +1370,8 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -987,6 +1406,8 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) { [super prepareForRecycle]; _initialPropsSet = NO; @@ -811,7 +853,7 @@ index a54cb632e7..592a6c2fbd 100644 _lastSendState = react::RNSScreenStackHeaderConfigState(react::Size{}, react::EdgeInsets{}, react::Point{}); } -@@ -1003,6 +1388,14 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -1003,6 +1424,14 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) return react::concreteComponentDescriptorProvider(); } @@ -826,7 +868,7 @@ index a54cb632e7..592a6c2fbd 100644 - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::Shared const &)oldProps { const auto &oldScreenProps = *std::static_pointer_cast(_props); -@@ -1025,6 +1418,7 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -1025,6 +1454,7 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) } _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title); @@ -834,12 +876,13 @@ index a54cb632e7..592a6c2fbd 100644 if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) { _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily); } -@@ -1064,6 +1458,26 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) +@@ -1064,6 +1494,27 @@ static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode) // We cannot compare SharedColor because it is shared value. // We could compare color value, but it is more performant to just assign new value _titleColor = RCTUIColorFromSharedColor(newScreenProps.titleColor); + _subtitleColor = RCTUIColorFromSharedColor(newScreenProps.subtitleColor); + _titlePressEnabled = newScreenProps.titlePressEnabled; ++ _titleTestID = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleTestID); + if (newScreenProps.titleImageSource != oldScreenProps.titleImageSource) { + const auto &titleImageSource = newScreenProps.titleImageSource; + _titleImageSource = titleImageSource.uri.empty() @@ -862,7 +905,7 @@ index a54cb632e7..592a6c2fbd 100644 _color = RCTUIColorFromSharedColor(newScreenProps.color); _backgroundColor = RCTUIColorFromSharedColor(newScreenProps.backgroundColor); diff --git a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts -index da28c51c6f..0803e9e879 100644 +index da28c51..017ca65 100644 --- a/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts +++ b/node_modules/react-native-screens/src/fabric/ScreenStackHeaderConfigNativeComponent.ts @@ -4,6 +4,7 @@ import { codegenNativeComponent } from 'react-native'; @@ -881,7 +924,7 @@ index da28c51c6f..0803e9e879 100644 // eslint-disable-next-line @typescript-eslint/ban-types type OnDetachedEvent = Readonly<{}>; -@@ -66,10 +68,16 @@ export interface NativeProps extends ViewProps { +@@ -66,10 +68,17 @@ export interface NativeProps extends ViewProps { largeTitleColor?: ColorValue | undefined; translucent?: boolean | undefined; title?: string | undefined; @@ -894,15 +937,16 @@ index da28c51c6f..0803e9e879 100644 + titleImageSource?: ImageSource | undefined; + subtitleImageSource?: ImageSource | undefined; + titlePressEnabled?: boolean | undefined; ++ titleTestID?: string | undefined; + onTitlePress?: CT.DirectEventHandler | undefined; disableBackButtonMenu?: boolean | undefined; backButtonDisplayMode?: CT.WithDefault; hideBackButton?: boolean | undefined; diff --git a/node_modules/react-native-screens/src/types.tsx b/node_modules/react-native-screens/src/types.tsx -index 8594b733fa..5afaf41504 100644 +index 8594b73..a10e423 100644 --- a/node_modules/react-native-screens/src/types.tsx +++ b/node_modules/react-native-screens/src/types.tsx -@@ -827,10 +827,65 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { +@@ -827,10 +827,73 @@ export interface ScreenStackHeaderConfigProps extends ViewProps { * String that can be displayed in the header as a fallback for `headerTitle`. */ title?: string | undefined; @@ -958,6 +1002,14 @@ index 8594b733fa..5afaf41504 100644 + */ + titlePressEnabled?: boolean | undefined; + /** ++ * Accessibility identifier of the native header title, for e2e tests. ++ * ++ * @platform ios ++ * ++ * @supported iOS 26 or higher ++ */ ++ titleTestID?: string | undefined; ++ /** + * Called when the native header title or subtitle is tapped and `titlePressEnabled` is true. + * + * @platform ios @@ -969,7 +1021,7 @@ index 8594b733fa..5afaf41504 100644 * Customize font family to be used for the title. */ diff --git a/node_modules/react-native-screens/ios/RNSScreenStack.mm b/node_modules/react-native-screens/ios/RNSScreenStack.mm -index c233036b56..5d3e28d3d6 100644 +index c233036..5d3e28d 100644 --- a/node_modules/react-native-screens/ios/RNSScreenStack.mm +++ b/node_modules/react-native-screens/ios/RNSScreenStack.mm @@ -38,6 +38,58 @@ namespace react = facebook::react; From 169272b3dd3dbc182eb409b53f104d64c97b4de5 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 29 Sep 2026 17:41:18 -0300 Subject: [PATCH 113/120] test(ios): adapt e2e helpers to the native header --- .maestro/helpers/go-back.yaml | 2 +- .maestro/helpers/open-servers-list.yaml | 11 +++-- .maestro/helpers/pop-to-rooms-list.yaml | 4 +- .../ToastsAndDialogs.yml | 46 +++++++++++++++---- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/.maestro/helpers/go-back.yaml b/.maestro/helpers/go-back.yaml index 78d6f80eaa1..81fd69def37 100644 --- a/.maestro/helpers/go-back.yaml +++ b/.maestro/helpers/go-back.yaml @@ -16,5 +16,5 @@ tags: platform: iOS commands: - tapOn: - text: 'Back' + text: 'Back(, .*)?' retryTapIfNoChange: true diff --git a/.maestro/helpers/open-servers-list.yaml b/.maestro/helpers/open-servers-list.yaml index 8bee46aae21..49052b5455d 100644 --- a/.maestro/helpers/open-servers-list.yaml +++ b/.maestro/helpers/open-servers-list.yaml @@ -8,14 +8,17 @@ tags: when: platform: iOS commands: - - tapOn: - text: 'Menu' - extendedWaitUntil: visible: - id: 'sidebar-workspaces' + text: 'Menu' timeout: 60000 - tapOn: - id: 'sidebar-workspaces' + text: '(?!\d{1,2}:\d{2}).+' + rightOf: + text: 'Menu' + leftOf: + text: 'Create new channel.*' + index: 0 - runFlow: when: diff --git a/.maestro/helpers/pop-to-rooms-list.yaml b/.maestro/helpers/pop-to-rooms-list.yaml index 2e0f2fd5e3f..1e8fb0abeb0 100644 --- a/.maestro/helpers/pop-to-rooms-list.yaml +++ b/.maestro/helpers/pop-to-rooms-list.yaml @@ -22,9 +22,9 @@ tags: when: platform: iOS visible: - text: 'Back' + text: 'Back(, .*)?' commands: - tapOn: - text: 'Back' + text: 'Back(, .*)?' - waitForAnimationToEnd: timeout: 5000 diff --git a/.maestro/tests/accessibilityAndAppearance/ToastsAndDialogs.yml b/.maestro/tests/accessibilityAndAppearance/ToastsAndDialogs.yml index a2bf50b79a6..17c2280bfdc 100644 --- a/.maestro/tests/accessibilityAndAppearance/ToastsAndDialogs.yml +++ b/.maestro/tests/accessibilityAndAppearance/ToastsAndDialogs.yml @@ -18,11 +18,27 @@ tags: # Show alerts as Toasts - tapOn: 'Menu' - tapOn: 'Accessibility & appearance' -- tapOn: '.*Show alerts as.*' -- assertVisible: 'Toasts. Dismissed automatically. Checked' -- assertVisible: 'Dialogs. Require manual dismissal. Unchecked' -- tapOn: - id: 'action-sheet-handle' +- runFlow: + when: + platform: iOS + commands: + - tapOn: 'Toasts' + - assertVisible: + text: 'Toasts' + selected: true + - assertVisible: + text: 'Dialogs' + selected: false + - tapOn: 'Toasts' +- runFlow: + when: + platform: Android + commands: + - tapOn: '.*Show alerts as.*' + - assertVisible: 'Toasts. Dismissed automatically. Checked' + - assertVisible: 'Dialogs. Require manual dismissal. Unchecked' + - tapOn: + id: 'action-sheet-handle' - waitForAnimationToEnd: timeout: 5000 - tapOn: 'Menu' @@ -37,9 +53,23 @@ tags: # Show alerts as Dialogs - tapOn: 'Accessibility & appearance' -- tapOn: '.*Show alerts as.*' -- assertVisible: 'Toasts. Dismissed automatically. Checked' -- tapOn: 'Dialogs. Require manual dismissal. Unchecked' +- runFlow: + when: + platform: iOS + commands: + - tapOn: 'Toasts' + - assertVisible: + text: 'Toasts' + selected: true + - tapOn: 'Dialogs' + - assertVisible: 'Dialogs' +- runFlow: + when: + platform: Android + commands: + - tapOn: '.*Show alerts as.*' + - assertVisible: 'Toasts. Dismissed automatically. Checked' + - tapOn: 'Dialogs. Require manual dismissal. Unchecked' - tapOn: 'Menu' - tapOn: 'Edit status' - tapOn: From 55df127d8115d3341cd2b6ac0f8b04ed0b884ea8 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 29 Sep 2026 17:51:50 -0300 Subject: [PATCH 114/120] test(ios): open the servers list from the rooms list title by testID --- .maestro/helpers/open-servers-list.yaml | 32 ++++----------------- app/views/RoomsListView/hooks/useHeader.tsx | 1 + 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/.maestro/helpers/open-servers-list.yaml b/.maestro/helpers/open-servers-list.yaml index 49052b5455d..d0fbb379500 100644 --- a/.maestro/helpers/open-servers-list.yaml +++ b/.maestro/helpers/open-servers-list.yaml @@ -4,29 +4,9 @@ tags: - 'util' --- -- runFlow: - when: - platform: iOS - commands: - - extendedWaitUntil: - visible: - text: 'Menu' - timeout: 60000 - - tapOn: - text: '(?!\d{1,2}:\d{2}).+' - rightOf: - text: 'Menu' - leftOf: - text: 'Create new channel.*' - index: 0 - -- runFlow: - when: - platform: Android - commands: - - extendedWaitUntil: - visible: - id: 'rooms-list-header-servers-list-button' - timeout: 60000 - - tapOn: - id: 'rooms-list-header-servers-list-button' +- extendedWaitUntil: + visible: + id: 'rooms-list-header-servers-list-button' + timeout: 60000 +- tapOn: + id: 'rooms-list-header-servers-list-button' diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index e587823e617..adf3e65bb4c 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -188,6 +188,7 @@ export const useHeader = () => { headerBlurEffect: 'regular', headerTitle: serverName, headerSubtitle: nativeHeaderSubtitle, + headerTitleTestID: 'rooms-list-header-servers-list-button', onHeaderTitlePress: () => showActionSheetRef({ children: , enableContentPanningGesture: false }), headerSearchBarOptions: { ref: searchBarRef, From 59679f19a8d4047e630498c25e05fa9475ce4185 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 30 Sep 2026 10:34:43 -0300 Subject: [PATCH 115/120] fix(ios): keep share list rows clear of the native header --- app/views/ShareListView/index.tsx | 16 +++++++++++----- app/views/ShareListView/styles.ts | 6 ++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index e02ec6986c2..08756b048ec 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -422,10 +422,15 @@ class ShareListView extends Component { return ( <> - + {I18n.t(header)} - + {hasNativeHeaderBar ? null : } ); }; @@ -473,7 +478,7 @@ class ShareListView extends Component { <> {this.renderSectionHeader('Select_Server')} navigation.navigate('SelectServerView')} item={serverInfo} /> - + {hasNativeHeaderBar ? null : } ); }; @@ -549,12 +554,13 @@ class ShareListView extends Component { data={searching ? searchResults : chats} keyExtractor={keyExtractor} style={[styles.flatlist, { backgroundColor: themes[theme].surfaceHover }]} - contentContainerStyle={{ paddingBottom: insets.bottom }} + contentContainerStyle={hasNativeHeaderBar ? searching && styles.nativeSearchContent : { paddingBottom: insets.bottom }} + contentInsetAdjustmentBehavior={hasNativeHeaderBar ? 'automatic' : undefined} renderItem={this.renderItem} getItemLayout={getItemLayout} ItemSeparatorComponent={RowSeparator} ListHeaderComponent={this.renderHeader} - ListFooterComponent={!searching || searchResults.length > 0 ? : null} + ListFooterComponent={!hasNativeHeaderBar && (!searching || searchResults.length > 0) ? : null} ListEmptyComponent={this.renderEmptyComponent} removeClippedSubviews keyboardShouldPersistTaps='always' diff --git a/app/views/ShareListView/styles.ts b/app/views/ShareListView/styles.ts index 89f178da561..5f651194fa9 100644 --- a/app/views/ShareListView/styles.ts +++ b/app/views/ShareListView/styles.ts @@ -36,6 +36,12 @@ export default StyleSheet.create({ paddingBottom: 10, paddingTop: 17 }, + nativeHeaderContainer: { + paddingBottom: 16 + }, + nativeSearchContent: { + paddingTop: 16 + }, headerText: { ...sharedStyles.textRegular, fontSize: 17, From bb7112d02d2cb0bda1a58cc8982c47b7a6b9aae9 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 30 Sep 2026 15:26:11 -0300 Subject: [PATCH 116/120] refactor: group List and NativeListRow into components, utils and hooks folders --- .../List/{ => components}/ListCheckbox.tsx | 0 .../{ => components}/ListContainer.ios.tsx | 8 ++++---- .../List/{ => components}/ListContainer.tsx | 0 .../List/{ => components}/ListHeader.tsx | 2 +- .../List/{ => components}/ListIcon.tsx | 4 ++-- .../List/{ => components}/ListInfo.tsx | 2 +- .../List/{ => components}/ListItem.tsx | 12 ++++++------ .../List/{ => components}/ListRadio.tsx | 4 ++-- .../List/{ => components}/ListSection.tsx | 4 ++-- .../List/{ => components}/ListSeparator.tsx | 2 +- .../__tests__/ListSection.test.tsx | 6 +++--- app/containers/List/index.ts | 18 +++++++++--------- .../native/{ => components}/Accessory.ios.tsx | 2 +- .../List/native/{ => components}/Icon.ios.tsx | 2 +- .../native/{ => components}/Indicator.ios.tsx | 0 .../List/native/{ => components}/Item.ios.tsx | 15 ++++++++++----- .../List/native/{ => components}/Item.tsx | 2 +- .../native/{ => components}/Picker.ios.tsx | 4 ++-- .../List/native/{ => components}/Picker.tsx | 2 +- .../native/{ => components}/Section.ios.tsx | 10 +++++----- .../List/native/{ => components}/Section.tsx | 2 +- .../native/{ => components}/Status.ios.tsx | 0 app/containers/List/native/types.ts | 2 +- .../__tests__/describeAccessory.test.tsx | 4 ++-- .../{ => utils}/__tests__/itemProps.test.ts | 2 +- .../native/{ => utils}/describeAccessory.ts | 4 ++-- .../List/native/{ => utils}/itemProps.ts | 2 +- .../List/native/{ => utils}/rowMarkers.ts | 0 .../List/{ => stories}/List.stories.tsx | 4 ++-- .../List/{ => stories/__tests__}/List.test.tsx | 2 +- .../__snapshots__/List.test.tsx.snap | 0 .../List/{ => utils}/listChildren.ts | 2 +- .../{ => components}/Disclosure.ios.tsx | 2 +- .../{ => components}/Disclosure.tsx | 2 +- .../{ => components}/Separator.ios.tsx | 2 +- .../{ => components}/Separator.tsx | 2 +- .../{ => hooks}/useListBackgroundColor.ios.ts | 0 .../{ => hooks}/useListBackgroundColor.ts | 0 app/containers/NewWindowIcon.tsx | 2 +- app/containers/SearchBox/index.tsx | 2 +- .../components/ListPicker.tsx | 4 ++-- app/views/AddExistingChannelView/index.tsx | 4 ++-- app/views/DirectoryView/index.tsx | 4 ++-- app/views/LanguageView/index.tsx | 2 +- app/views/MediaAutoDownloadView/ListPicker.tsx | 4 ++-- app/views/NewMessageView/ButtonCreate.ios.tsx | 4 ++-- app/views/NewMessageView/HeaderNewMessage.tsx | 2 +- app/views/NewMessageView/index.tsx | 4 ++-- .../NotificationPreferencesView/index.tsx | 4 ++-- app/views/PickerView/index.tsx | 4 ++-- app/views/ProfileView/index.tsx | 2 +- .../components/CommunityEditionPushQuota.tsx | 2 +- .../components/DeviceNotificationSettings.tsx | 2 +- .../components/NotificationDelay.tsx | 2 +- .../components/PushGatewayConnection.tsx | 2 +- .../RoomActionsView/components/CallSection.tsx | 2 +- app/views/RoomActionsView/index.tsx | 2 +- .../components/ActionsSection.ios.tsx | 4 ++-- app/views/RoomMembersView/index.tsx | 4 ++-- app/views/SelectListView/Item.ios.tsx | 2 +- app/views/SelectedUsersView/Header.tsx | 2 +- app/views/SelectedUsersView/index.tsx | 4 ++-- app/views/ShareListView/index.tsx | 2 +- .../SidebarView/components/CustomStatus.tsx | 2 +- .../SidebarView/components/Profile.ios.tsx | 2 +- .../components/SupportedVersionsWarnItem.tsx | 2 +- .../components/getSidebarSelection.ts | 2 +- app/views/SidebarView/index.ios.tsx | 2 +- .../ClearAfterPicker/ClearAfterNativeRow.tsx | 4 ++-- .../StatusView/ClearAfterPicker/index.tsx | 2 +- app/views/StatusView/index.test.tsx | 2 +- app/views/StatusView/index.tsx | 2 +- .../ListPicker.tsx | 4 ++-- app/views/UserPreferencesView/ListPicker.tsx | 4 ++-- .../__tests__/ListPicker.test.tsx | 2 +- 75 files changed, 119 insertions(+), 114 deletions(-) rename app/containers/List/{ => components}/ListCheckbox.tsx (100%) rename app/containers/List/{ => components}/ListContainer.ios.tsx (92%) rename app/containers/List/{ => components}/ListContainer.tsx (100%) rename app/containers/List/{ => components}/ListHeader.tsx (94%) rename app/containers/List/{ => components}/ListIcon.tsx (86%) rename app/containers/List/{ => components}/ListInfo.tsx (94%) rename app/containers/List/{ => components}/ListItem.tsx (95%) rename app/containers/List/{ => components}/ListRadio.tsx (90%) rename app/containers/List/{ => components}/ListSection.tsx (87%) rename app/containers/List/{ => components}/ListSeparator.tsx (92%) rename app/containers/List/{ => components}/__tests__/ListSection.test.tsx (87%) rename app/containers/List/native/{ => components}/Accessory.ios.tsx (96%) rename app/containers/List/native/{ => components}/Icon.ios.tsx (91%) rename app/containers/List/native/{ => components}/Indicator.ios.tsx (100%) rename app/containers/List/native/{ => components}/Item.ios.tsx (87%) rename app/containers/List/native/{ => components}/Item.tsx (63%) rename app/containers/List/native/{ => components}/Picker.ios.tsx (89%) rename app/containers/List/native/{ => components}/Picker.tsx (64%) rename app/containers/List/native/{ => components}/Section.ios.tsx (77%) rename app/containers/List/native/{ => components}/Section.tsx (64%) rename app/containers/List/native/{ => components}/Status.ios.tsx (100%) rename app/containers/List/native/{ => utils}/__tests__/describeAccessory.test.tsx (95%) rename app/containers/List/native/{ => utils}/__tests__/itemProps.test.ts (96%) rename app/containers/List/native/{ => utils}/describeAccessory.ts (95%) rename app/containers/List/native/{ => utils}/itemProps.ts (95%) rename app/containers/List/native/{ => utils}/rowMarkers.ts (100%) rename app/containers/List/{ => stories}/List.stories.tsx (98%) rename app/containers/List/{ => stories/__tests__}/List.test.tsx (69%) rename app/containers/List/{ => stories/__tests__}/__snapshots__/List.test.tsx.snap (100%) rename app/containers/List/{ => utils}/listChildren.ts (91%) rename app/containers/NativeListRow/{ => components}/Disclosure.ios.tsx (68%) rename app/containers/NativeListRow/{ => components}/Disclosure.tsx (77%) rename app/containers/NativeListRow/{ => components}/Separator.ios.tsx (94%) rename app/containers/NativeListRow/{ => components}/Separator.tsx (51%) rename app/containers/NativeListRow/{ => hooks}/useListBackgroundColor.ios.ts (100%) rename app/containers/NativeListRow/{ => hooks}/useListBackgroundColor.ts (100%) diff --git a/app/containers/List/ListCheckbox.tsx b/app/containers/List/components/ListCheckbox.tsx similarity index 100% rename from app/containers/List/ListCheckbox.tsx rename to app/containers/List/components/ListCheckbox.tsx diff --git a/app/containers/List/ListContainer.ios.tsx b/app/containers/List/components/ListContainer.ios.tsx similarity index 92% rename from app/containers/List/ListContainer.ios.tsx rename to app/containers/List/components/ListContainer.ios.tsx index fc53f9978a6..89a485a096f 100644 --- a/app/containers/List/ListContainer.ios.tsx +++ b/app/containers/List/components/ListContainer.ios.tsx @@ -16,10 +16,10 @@ import { useTheme } from '~/theme'; import ListSection from './ListSection'; import ListItem from './ListItem'; import ListRadio from './ListRadio'; -import { isNativeListRow, isNativeListSection } from './native/rowMarkers'; -import { flattenListChildren, isListSeparator } from './listChildren'; -import { NativeListContext } from './native/context'; -import { ICON_SIZE, PADDING_HORIZONTAL } from './constants'; +import { isNativeListRow, isNativeListSection } from '../native/utils/rowMarkers'; +import { flattenListChildren, isListSeparator } from '../utils/listChildren'; +import { NativeListContext } from '../native/context'; +import { ICON_SIZE, PADDING_HORIZONTAL } from '../constants'; const styles = StyleSheet.create({ host: { diff --git a/app/containers/List/ListContainer.tsx b/app/containers/List/components/ListContainer.tsx similarity index 100% rename from app/containers/List/ListContainer.tsx rename to app/containers/List/components/ListContainer.tsx diff --git a/app/containers/List/ListHeader.tsx b/app/containers/List/components/ListHeader.tsx similarity index 94% rename from app/containers/List/ListHeader.tsx rename to app/containers/List/components/ListHeader.tsx index f8ee321aed0..2583d206cbc 100644 --- a/app/containers/List/ListHeader.tsx +++ b/app/containers/List/components/ListHeader.tsx @@ -5,7 +5,7 @@ import sharedStyles from '~/views/Styles'; import { themes } from '~/lib/constants/colors'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; -import { PADDING_HORIZONTAL } from './constants'; +import { PADDING_HORIZONTAL } from '../constants'; const styles = StyleSheet.create({ container: { diff --git a/app/containers/List/ListIcon.tsx b/app/containers/List/components/ListIcon.tsx similarity index 86% rename from app/containers/List/ListIcon.tsx rename to app/containers/List/components/ListIcon.tsx index 2fa41cd25b8..46a76120beb 100644 --- a/app/containers/List/ListIcon.tsx +++ b/app/containers/List/components/ListIcon.tsx @@ -1,8 +1,8 @@ import { type ReactElement } from 'react'; import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'; -import { CustomIcon, type TIconsName } from '../CustomIcon'; -import { ICON_SIZE } from './constants'; +import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; +import { ICON_SIZE } from '../constants'; import { useTheme } from '~/theme'; export interface IListIcon { diff --git a/app/containers/List/ListInfo.tsx b/app/containers/List/components/ListInfo.tsx similarity index 94% rename from app/containers/List/ListInfo.tsx rename to app/containers/List/components/ListInfo.tsx index c91ee9e4814..181d98cac2b 100644 --- a/app/containers/List/ListInfo.tsx +++ b/app/containers/List/components/ListInfo.tsx @@ -4,7 +4,7 @@ import { StyleSheet, Text, View } from 'react-native'; import sharedStyles from '~/views/Styles'; import { themes } from '~/lib/constants/colors'; import { useTheme } from '~/theme'; -import { PADDING_HORIZONTAL } from './constants'; +import { PADDING_HORIZONTAL } from '../constants'; import I18n from '~/i18n'; const styles = StyleSheet.create({ diff --git a/app/containers/List/ListItem.tsx b/app/containers/List/components/ListItem.tsx similarity index 95% rename from app/containers/List/ListItem.tsx rename to app/containers/List/components/ListItem.tsx index 2e6cf08b963..eabe200baa5 100644 --- a/app/containers/List/ListItem.tsx +++ b/app/containers/List/components/ListItem.tsx @@ -11,18 +11,18 @@ import { type ViewStyle } from 'react-native'; -import Touch from '../Touch'; +import Touch from '~/containers/Touch'; import sharedStyles from '~/views/Styles'; import { useTheme } from '~/theme'; import I18n from '~/i18n'; import Icon from './ListIcon'; -import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants'; -import { useNativeListMode } from './native/context'; -import NativeListItem from './native/Item'; -import { CustomIcon } from '../CustomIcon'; +import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from '../constants'; +import { useNativeListMode } from '../native/context'; +import NativeListItem from '../native/components/Item'; +import { CustomIcon } from '~/containers/CustomIcon'; import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; import EventEmitter from '~/lib/methods/helpers/events'; -import { LISTENER } from '../Toast'; +import { LISTENER } from '~/containers/Toast'; import { isIOS } from '~/lib/methods/helpers'; // Maestro fail to click on child component when we enable accessibility in parent component on iOS diff --git a/app/containers/List/ListRadio.tsx b/app/containers/List/components/ListRadio.tsx similarity index 90% rename from app/containers/List/ListRadio.tsx rename to app/containers/List/components/ListRadio.tsx index 91eefff1124..101fea96d35 100644 --- a/app/containers/List/ListRadio.tsx +++ b/app/containers/List/components/ListRadio.tsx @@ -4,8 +4,8 @@ import i18n from '~/i18n'; import ListItem, { type IListItem } from './ListItem'; import ListIcon from './ListIcon'; import { useTheme } from '~/theme'; -import Radio from '../Radio'; -import { useNativeListMode } from './native/context'; +import Radio from '~/containers/Radio'; +import { useNativeListMode } from '../native/context'; interface IListRadio extends IListItem { value: any; diff --git a/app/containers/List/ListSection.tsx b/app/containers/List/components/ListSection.tsx similarity index 87% rename from app/containers/List/ListSection.tsx rename to app/containers/List/components/ListSection.tsx index 4520e4252e5..349229c78d1 100644 --- a/app/containers/List/ListSection.tsx +++ b/app/containers/List/components/ListSection.tsx @@ -2,8 +2,8 @@ import { type ReactNode } from 'react'; import { StyleSheet, View } from 'react-native'; import Header from './ListHeader'; -import NativeListSection from './native/Section'; -import { useNativeListMode } from './native/context'; +import NativeListSection from '../native/components/Section'; +import { useNativeListMode } from '../native/context'; const styles = StyleSheet.create({ container: { diff --git a/app/containers/List/ListSeparator.tsx b/app/containers/List/components/ListSeparator.tsx similarity index 92% rename from app/containers/List/ListSeparator.tsx rename to app/containers/List/components/ListSeparator.tsx index 8ca44fb24ee..d5bab0fcbf9 100644 --- a/app/containers/List/ListSeparator.tsx +++ b/app/containers/List/components/ListSeparator.tsx @@ -3,7 +3,7 @@ import { StyleSheet, View, type ViewStyle } from 'react-native'; import { themes } from '~/lib/constants/colors'; import { useTheme } from '~/theme'; -import { useNativeListMode } from './native/context'; +import { useNativeListMode } from '../native/context'; const styles = StyleSheet.create({ separator: { diff --git a/app/containers/List/__tests__/ListSection.test.tsx b/app/containers/List/components/__tests__/ListSection.test.tsx similarity index 87% rename from app/containers/List/__tests__/ListSection.test.tsx rename to app/containers/List/components/__tests__/ListSection.test.tsx index 31d55205429..0f0a3ce14a3 100644 --- a/app/containers/List/__tests__/ListSection.test.tsx +++ b/app/containers/List/components/__tests__/ListSection.test.tsx @@ -2,10 +2,10 @@ import { Text } from 'react-native'; import { render, screen } from '@testing-library/react-native'; import ListSection from '../ListSection'; -import { NativeListContext } from '../native/context'; -import { asNativeListSection, isNativeListRow, isNativeListSection } from '../native/rowMarkers'; +import { NativeListContext } from '~/containers/List/native/context'; +import { asNativeListSection, isNativeListRow, isNativeListSection } from '~/containers/List/native/utils/rowMarkers'; -jest.mock('../native/Section', () => { +jest.mock('~/containers/List/native/components/Section', () => { const { Text: MockText } = jest.requireActual('react-native'); return ({ title }: { title?: string }) => {`native section ${title}`}; }); diff --git a/app/containers/List/index.ts b/app/containers/List/index.ts index d893df9b54b..6dffdcc56d9 100644 --- a/app/containers/List/index.ts +++ b/app/containers/List/index.ts @@ -1,10 +1,10 @@ -export { default as Container } from './ListContainer'; -export { default as Item } from './ListItem'; -export { default as Radio } from './ListRadio'; -export { default as Section } from './ListSection'; -export { default as Icon } from './ListIcon'; -export { default as Checkbox } from './ListCheckbox'; -export { default as Separator } from './ListSeparator'; -export { default as Header } from './ListHeader'; -export { default as Info } from './ListInfo'; +export { default as Container } from './components/ListContainer'; +export { default as Item } from './components/ListItem'; +export { default as Radio } from './components/ListRadio'; +export { default as Section } from './components/ListSection'; +export { default as Icon } from './components/ListIcon'; +export { default as Checkbox } from './components/ListCheckbox'; +export { default as Separator } from './components/ListSeparator'; +export { default as Header } from './components/ListHeader'; +export { default as Info } from './components/ListInfo'; export * from './styles'; diff --git a/app/containers/List/native/Accessory.ios.tsx b/app/containers/List/native/components/Accessory.ios.tsx similarity index 96% rename from app/containers/List/native/Accessory.ios.tsx rename to app/containers/List/native/components/Accessory.ios.tsx index 9da0409592b..147d86e4ec8 100644 --- a/app/containers/List/native/Accessory.ios.tsx +++ b/app/containers/List/native/components/Accessory.ios.tsx @@ -6,7 +6,7 @@ import { useTheme } from '~/theme'; import NativeListIcon from './Icon.ios'; import NativeListIndicator from './Indicator.ios'; import NativeListStatus from './Status.ios'; -import { type TNativeListAccessory } from './describeAccessory'; +import { type TNativeListAccessory } from '../utils/describeAccessory'; const NativeListAccessory = ({ accessory }: { accessory: TNativeListAccessory }) => { const { colors } = useTheme(); diff --git a/app/containers/List/native/Icon.ios.tsx b/app/containers/List/native/components/Icon.ios.tsx similarity index 91% rename from app/containers/List/native/Icon.ios.tsx rename to app/containers/List/native/components/Icon.ios.tsx index f1f6d8609b5..5bbeb1e719c 100644 --- a/app/containers/List/native/Icon.ios.tsx +++ b/app/containers/List/native/components/Icon.ios.tsx @@ -3,7 +3,7 @@ import { font, foregroundStyle } from '@expo/ui/swift-ui/modifiers'; import { type TIconsName } from '~/containers/CustomIcon'; import { mappedIcons } from '~/containers/CustomIcon/mappedIcons'; -import { ICON_SIZE } from '../constants'; +import { ICON_SIZE } from '~/containers/List/constants'; interface INativeListIcon { name: TIconsName; diff --git a/app/containers/List/native/Indicator.ios.tsx b/app/containers/List/native/components/Indicator.ios.tsx similarity index 100% rename from app/containers/List/native/Indicator.ios.tsx rename to app/containers/List/native/components/Indicator.ios.tsx diff --git a/app/containers/List/native/Item.ios.tsx b/app/containers/List/native/components/Item.ios.tsx similarity index 87% rename from app/containers/List/native/Item.ios.tsx rename to app/containers/List/native/components/Item.ios.tsx index 166333486ed..c4618b6e3e7 100644 --- a/app/containers/List/native/Item.ios.tsx +++ b/app/containers/List/native/components/Item.ios.tsx @@ -18,11 +18,16 @@ import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsi import NativeListAccessory from './Accessory.ios'; import NativeListIcon from './Icon.ios'; import NativeListIndicator from './Indicator.ios'; -import { type IListItem } from '../ListItem'; -import { type INativeListItem } from './types'; -import { describeNativeListAccessory } from './describeAccessory'; -import { BASE_HEIGHT, PADDING_HORIZONTAL } from '../constants'; -import { nativeListItemAccessibilityLabel, nativeListItemSubtitle, nativeListItemTitle, pressNativeListItem } from './itemProps'; +import { type IListItem } from '~/containers/List/components/ListItem'; +import { type INativeListItem } from '../types'; +import { describeNativeListAccessory } from '../utils/describeAccessory'; +import { BASE_HEIGHT, PADDING_HORIZONTAL } from '~/containers/List/constants'; +import { + nativeListItemAccessibilityLabel, + nativeListItemSubtitle, + nativeListItemTitle, + pressNativeListItem +} from '../utils/itemProps'; const NativeListItemTitle = ({ item }: { item: IListItem }) => { const { colors } = useTheme(); diff --git a/app/containers/List/native/Item.tsx b/app/containers/List/native/components/Item.tsx similarity index 63% rename from app/containers/List/native/Item.tsx rename to app/containers/List/native/components/Item.tsx index 0a3a365637c..fa70f339efb 100644 --- a/app/containers/List/native/Item.tsx +++ b/app/containers/List/native/components/Item.tsx @@ -1,4 +1,4 @@ -import { type INativeListItem } from './types'; +import { type INativeListItem } from '../types'; const NativeListItem = (_: INativeListItem) => null; diff --git a/app/containers/List/native/Picker.ios.tsx b/app/containers/List/native/components/Picker.ios.tsx similarity index 89% rename from app/containers/List/native/Picker.ios.tsx rename to app/containers/List/native/components/Picker.ios.tsx index e910913d871..b45fce048c8 100644 --- a/app/containers/List/native/Picker.ios.tsx +++ b/app/containers/List/native/components/Picker.ios.tsx @@ -3,8 +3,8 @@ import { font, foregroundStyle, frame, listRowInsets, pickerStyle, tag, tint } f import { useTheme } from '~/theme'; import { useResponsiveLayout } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; -import { BASE_HEIGHT, PADDING_HORIZONTAL } from '../constants'; -import { type INativeListPicker } from './types'; +import { BASE_HEIGHT, PADDING_HORIZONTAL } from '~/containers/List/constants'; +import { type INativeListPicker } from '../types'; const NativeListPicker = ({ title, testID, options, selection, onSelectionChange }: INativeListPicker) => { const { colors } = useTheme(); diff --git a/app/containers/List/native/Picker.tsx b/app/containers/List/native/components/Picker.tsx similarity index 64% rename from app/containers/List/native/Picker.tsx rename to app/containers/List/native/components/Picker.tsx index aaa4cc894c6..a58d3a39071 100644 --- a/app/containers/List/native/Picker.tsx +++ b/app/containers/List/native/components/Picker.tsx @@ -1,4 +1,4 @@ -import { type INativeListPicker } from './types'; +import { type INativeListPicker } from '../types'; const NativeListPicker = (_: INativeListPicker) => null; diff --git a/app/containers/List/native/Section.ios.tsx b/app/containers/List/native/components/Section.ios.tsx similarity index 77% rename from app/containers/List/native/Section.ios.tsx rename to app/containers/List/native/components/Section.ios.tsx index 27ceb7f3d8b..949351955de 100644 --- a/app/containers/List/native/Section.ios.tsx +++ b/app/containers/List/native/components/Section.ios.tsx @@ -1,11 +1,11 @@ import { useContext, type ReactElement } from 'react'; import { Section, Text } from '@expo/ui/swift-ui'; -import ListInfo from '../ListInfo'; -import { flattenListChildren, isListSeparator } from '../listChildren'; -import { NativeListContext } from './context'; -import { translateListText } from './itemProps'; -import { type INativeListSection } from './types'; +import ListInfo from '~/containers/List/components/ListInfo'; +import { flattenListChildren, isListSeparator } from '~/containers/List/utils/listChildren'; +import { NativeListContext } from '../context'; +import { translateListText } from '../utils/itemProps'; +import { type INativeListSection } from '../types'; interface IInfoProps { info: string; diff --git a/app/containers/List/native/Section.tsx b/app/containers/List/native/components/Section.tsx similarity index 64% rename from app/containers/List/native/Section.tsx rename to app/containers/List/native/components/Section.tsx index 73581d1562f..33d2c909b34 100644 --- a/app/containers/List/native/Section.tsx +++ b/app/containers/List/native/components/Section.tsx @@ -1,4 +1,4 @@ -import { type INativeListSection } from './types'; +import { type INativeListSection } from '../types'; const NativeListSection = (_: INativeListSection) => null; diff --git a/app/containers/List/native/Status.ios.tsx b/app/containers/List/native/components/Status.ios.tsx similarity index 100% rename from app/containers/List/native/Status.ios.tsx rename to app/containers/List/native/components/Status.ios.tsx diff --git a/app/containers/List/native/types.ts b/app/containers/List/native/types.ts index 956d344c24d..0cc6ee5af21 100644 --- a/app/containers/List/native/types.ts +++ b/app/containers/List/native/types.ts @@ -1,6 +1,6 @@ import { type ReactNode } from 'react'; -import { type IListItem } from '../ListItem'; +import { type IListItem } from '../components/ListItem'; export interface INativeListItem { item: IListItem; diff --git a/app/containers/List/native/__tests__/describeAccessory.test.tsx b/app/containers/List/native/utils/__tests__/describeAccessory.test.tsx similarity index 95% rename from app/containers/List/native/__tests__/describeAccessory.test.tsx rename to app/containers/List/native/utils/__tests__/describeAccessory.test.tsx index 3b8bbfe8d51..cecd5fe53a8 100644 --- a/app/containers/List/native/__tests__/describeAccessory.test.tsx +++ b/app/containers/List/native/utils/__tests__/describeAccessory.test.tsx @@ -6,8 +6,8 @@ import NewWindowIcon from '~/containers/NewWindowIcon'; import Radio from '~/containers/Radio'; import Status from '~/containers/Status/Status'; import Switch from '~/containers/Switch'; -import ListCheckbox from '../../ListCheckbox'; -import ListIcon from '../../ListIcon'; +import ListCheckbox from '~/containers/List/components/ListCheckbox'; +import ListIcon from '~/containers/List/components/ListIcon'; import { describeNativeListAccessory } from '../describeAccessory'; describe('describeNativeListAccessory', () => { diff --git a/app/containers/List/native/__tests__/itemProps.test.ts b/app/containers/List/native/utils/__tests__/itemProps.test.ts similarity index 96% rename from app/containers/List/native/__tests__/itemProps.test.ts rename to app/containers/List/native/utils/__tests__/itemProps.test.ts index 9f6ff046daa..d77d6f08566 100644 --- a/app/containers/List/native/__tests__/itemProps.test.ts +++ b/app/containers/List/native/utils/__tests__/itemProps.test.ts @@ -1,6 +1,6 @@ import EventEmitter from '~/lib/methods/helpers/events'; import { LISTENER } from '~/containers/Toast'; -import { type IListItem } from '../../ListItem'; +import { type IListItem } from '~/containers/List/components/ListItem'; import { nativeListItemAccessibilityLabel, nativeListItemTitle, pressNativeListItem } from '../itemProps'; jest.mock('~/i18n', () => ({ t: (key: string) => `t:${key}` })); diff --git a/app/containers/List/native/describeAccessory.ts b/app/containers/List/native/utils/describeAccessory.ts similarity index 95% rename from app/containers/List/native/describeAccessory.ts rename to app/containers/List/native/utils/describeAccessory.ts index 4836fa55740..a1feb7393c6 100644 --- a/app/containers/List/native/describeAccessory.ts +++ b/app/containers/List/native/utils/describeAccessory.ts @@ -7,8 +7,8 @@ import NewWindowIcon from '~/containers/NewWindowIcon'; import Radio from '~/containers/Radio'; import Status from '~/containers/Status/Status'; import Switch from '~/containers/Switch'; -import ListCheckbox, { type IListCheckbox } from '../ListCheckbox'; -import ListIcon from '../ListIcon'; +import ListCheckbox, { type IListCheckbox } from '~/containers/List/components/ListCheckbox'; +import ListIcon from '~/containers/List/components/ListIcon'; export type TNativeListAccessory = | { kind: 'icon'; name: TIconsName; color?: string; size?: number } diff --git a/app/containers/List/native/itemProps.ts b/app/containers/List/native/utils/itemProps.ts similarity index 95% rename from app/containers/List/native/itemProps.ts rename to app/containers/List/native/utils/itemProps.ts index 8e4ed4ae02a..78dcf7a90b2 100644 --- a/app/containers/List/native/itemProps.ts +++ b/app/containers/List/native/utils/itemProps.ts @@ -1,7 +1,7 @@ import I18n from '~/i18n'; import EventEmitter from '~/lib/methods/helpers/events'; import { LISTENER } from '~/containers/Toast'; -import { type IListItem } from '../ListItem'; +import { type IListItem } from '~/containers/List/components/ListItem'; export const translateListText = (text: string, shouldTranslate = true) => (shouldTranslate ? I18n.t(text) : text); diff --git a/app/containers/List/native/rowMarkers.ts b/app/containers/List/native/utils/rowMarkers.ts similarity index 100% rename from app/containers/List/native/rowMarkers.ts rename to app/containers/List/native/utils/rowMarkers.ts diff --git a/app/containers/List/List.stories.tsx b/app/containers/List/stories/List.stories.tsx similarity index 98% rename from app/containers/List/List.stories.tsx rename to app/containers/List/stories/List.stories.tsx index 0aa5c9edd3c..50f2e53c20d 100644 --- a/app/containers/List/List.stories.tsx +++ b/app/containers/List/stories/List.stories.tsx @@ -1,7 +1,7 @@ import { FlatList } from 'react-native'; -import * as List from '.'; -import SafeAreaView from '../SafeAreaView'; +import * as List from '..'; +import SafeAreaView from '~/containers/SafeAreaView'; import { longText } from '~/.rnstorybook/utils'; import { ThemeContext, type TSupportedThemes } from '~/theme'; import { ResponsiveLayoutContext } from '~/lib/hooks/useResponsiveLayout/useResponsiveLayout'; diff --git a/app/containers/List/List.test.tsx b/app/containers/List/stories/__tests__/List.test.tsx similarity index 69% rename from app/containers/List/List.test.tsx rename to app/containers/List/stories/__tests__/List.test.tsx index 446c15169bd..eb8c47a1abd 100644 --- a/app/containers/List/List.test.tsx +++ b/app/containers/List/stories/__tests__/List.test.tsx @@ -1,4 +1,4 @@ import { generateSnapshots } from '~/.rnstorybook/generateSnapshots'; -import * as stories from './List.stories'; +import * as stories from '../List.stories'; generateSnapshots(stories); diff --git a/app/containers/List/__snapshots__/List.test.tsx.snap b/app/containers/List/stories/__tests__/__snapshots__/List.test.tsx.snap similarity index 100% rename from app/containers/List/__snapshots__/List.test.tsx.snap rename to app/containers/List/stories/__tests__/__snapshots__/List.test.tsx.snap diff --git a/app/containers/List/listChildren.ts b/app/containers/List/utils/listChildren.ts similarity index 91% rename from app/containers/List/listChildren.ts rename to app/containers/List/utils/listChildren.ts index 1419b952fed..048a0c25c03 100644 --- a/app/containers/List/listChildren.ts +++ b/app/containers/List/utils/listChildren.ts @@ -1,6 +1,6 @@ import { Children, cloneElement, Fragment, isValidElement, type ReactElement, type ReactNode } from 'react'; -import ListSeparator from './ListSeparator'; +import ListSeparator from '../components/ListSeparator'; export const flattenListChildren = (children: ReactNode, keyPrefix = ''): ReactElement[] => Children.toArray(children).flatMap(child => { diff --git a/app/containers/NativeListRow/Disclosure.ios.tsx b/app/containers/NativeListRow/components/Disclosure.ios.tsx similarity index 68% rename from app/containers/NativeListRow/Disclosure.ios.tsx rename to app/containers/NativeListRow/components/Disclosure.ios.tsx index aecd8926d2b..2db1fc6c103 100644 --- a/app/containers/NativeListRow/Disclosure.ios.tsx +++ b/app/containers/NativeListRow/components/Disclosure.ios.tsx @@ -1,6 +1,6 @@ import { Host } from '@expo/ui/swift-ui'; -import NativeListIndicator from '~/containers/List/native/Indicator.ios'; +import NativeListIndicator from '~/containers/List/native/components/Indicator.ios'; const Disclosure = () => ( diff --git a/app/containers/NativeListRow/Disclosure.tsx b/app/containers/NativeListRow/components/Disclosure.tsx similarity index 77% rename from app/containers/NativeListRow/Disclosure.tsx rename to app/containers/NativeListRow/components/Disclosure.tsx index 214ae769553..a86000ecb0d 100644 --- a/app/containers/NativeListRow/Disclosure.tsx +++ b/app/containers/NativeListRow/components/Disclosure.tsx @@ -1,6 +1,6 @@ import { I18nManager } from 'react-native'; -import ListIcon from '~/containers/List/ListIcon'; +import ListIcon from '~/containers/List/components/ListIcon'; const Disclosure = () => ( diff --git a/app/containers/NativeListRow/Separator.ios.tsx b/app/containers/NativeListRow/components/Separator.ios.tsx similarity index 94% rename from app/containers/NativeListRow/Separator.ios.tsx rename to app/containers/NativeListRow/components/Separator.ios.tsx index a1d9aece613..dc18aaa6bd1 100644 --- a/app/containers/NativeListRow/Separator.ios.tsx +++ b/app/containers/NativeListRow/components/Separator.ios.tsx @@ -1,7 +1,7 @@ import { StyleSheet, View } from 'react-native'; import { useTheme } from '~/theme'; -import { AVATAR_SIZE, CONTENT_SPACING, ROW_MARGIN_HORIZONTAL, ROW_PADDING_HORIZONTAL } from './constants'; +import { AVATAR_SIZE, CONTENT_SPACING, ROW_MARGIN_HORIZONTAL, ROW_PADDING_HORIZONTAL } from '../constants'; const styles = StyleSheet.create({ card: { diff --git a/app/containers/NativeListRow/Separator.tsx b/app/containers/NativeListRow/components/Separator.tsx similarity index 51% rename from app/containers/NativeListRow/Separator.tsx rename to app/containers/NativeListRow/components/Separator.tsx index 77c984db2d9..3876b84ea4e 100644 --- a/app/containers/NativeListRow/Separator.tsx +++ b/app/containers/NativeListRow/components/Separator.tsx @@ -1,4 +1,4 @@ -import ListSeparator from '../List/ListSeparator'; +import ListSeparator from '~/containers/List/components/ListSeparator'; export const PlainSeparator = ListSeparator; diff --git a/app/containers/NativeListRow/useListBackgroundColor.ios.ts b/app/containers/NativeListRow/hooks/useListBackgroundColor.ios.ts similarity index 100% rename from app/containers/NativeListRow/useListBackgroundColor.ios.ts rename to app/containers/NativeListRow/hooks/useListBackgroundColor.ios.ts diff --git a/app/containers/NativeListRow/useListBackgroundColor.ts b/app/containers/NativeListRow/hooks/useListBackgroundColor.ts similarity index 100% rename from app/containers/NativeListRow/useListBackgroundColor.ts rename to app/containers/NativeListRow/hooks/useListBackgroundColor.ts diff --git a/app/containers/NewWindowIcon.tsx b/app/containers/NewWindowIcon.tsx index 5203794bf0f..5290c5a5691 100644 --- a/app/containers/NewWindowIcon.tsx +++ b/app/containers/NewWindowIcon.tsx @@ -1,6 +1,6 @@ import { I18nManager } from 'react-native'; -import ListIcon, { type IListIcon } from './List/ListIcon'; +import ListIcon, { type IListIcon } from './List/components/ListIcon'; const NewWindowIcon = (props: Omit) => ( diff --git a/app/containers/SearchBox/index.tsx b/app/containers/SearchBox/index.tsx index 6baece8e494..c4da4e0ba8c 100644 --- a/app/containers/SearchBox/index.tsx +++ b/app/containers/SearchBox/index.tsx @@ -4,7 +4,7 @@ import { StyleSheet, type TextInputProps, View } from 'react-native'; import { useTheme } from '~/theme'; import I18n from '~/i18n'; import { FormTextInput } from '../TextInput'; -import { useListBackgroundColor } from '../NativeListRow/useListBackgroundColor'; +import { useListBackgroundColor } from '../NativeListRow/hooks/useListBackgroundColor'; const styles = StyleSheet.create({ inputContainer: { diff --git a/app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx b/app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx index ed8093eeea3..e6dc700a8f4 100644 --- a/app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx +++ b/app/views/AccessibilityAndAppearanceView/components/ListPicker.tsx @@ -3,9 +3,9 @@ import { StyleSheet, Text, View } from 'react-native'; import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; import { CustomIcon } from '~/containers/CustomIcon'; import * as List from '~/containers/List'; -import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { asNativeListRow } from '~/containers/List/native/utils/rowMarkers'; import { useNativeListMode } from '~/containers/List/native/context'; -import NativeListPicker from '~/containers/List/native/Picker'; +import NativeListPicker from '~/containers/List/native/components/Picker'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; import sharedStyles from '~/views/Styles'; diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index 60f0520bf85..f1f35a87dd1 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -22,8 +22,8 @@ import { addRoomsToTeam } from '~/lib/services/restApi'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import Navigation from '~/lib/navigation/appNavigation'; -import RowSeparator from '~/containers/NativeListRow/Separator'; -import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import RowSeparator from '~/containers/NativeListRow/components/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import ChannelItem from './ChannelItem'; type TNavigation = NativeStackNavigationProp; diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index b07c4f04efe..0eb6000ec1e 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -32,8 +32,8 @@ import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { useDirectorySearch } from './hooks/useDirectorySearch'; import { hasNativeHeaderBar } from '~/lib/methods/helpers'; import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; -import RowSeparator from '~/containers/NativeListRow/Separator'; -import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import RowSeparator from '~/containers/NativeListRow/components/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; interface IDirectoryViewProps { navigation: CompositeNavigationProp< diff --git a/app/views/LanguageView/index.tsx b/app/views/LanguageView/index.tsx index 8e9142a5796..e0ec3d7b101 100644 --- a/app/views/LanguageView/index.tsx +++ b/app/views/LanguageView/index.tsx @@ -6,7 +6,7 @@ import { useNavigation } from '@react-navigation/native'; import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import ListRadio from '~/containers/List/ListRadio'; +import ListRadio from '~/containers/List/components/ListRadio'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { appStart } from '~/actions/app'; import { setUser } from '~/actions/login'; diff --git a/app/views/MediaAutoDownloadView/ListPicker.tsx b/app/views/MediaAutoDownloadView/ListPicker.tsx index 5eb56804053..0cf42b8f758 100644 --- a/app/views/MediaAutoDownloadView/ListPicker.tsx +++ b/app/views/MediaAutoDownloadView/ListPicker.tsx @@ -3,9 +3,9 @@ import { StyleSheet, Text, View } from 'react-native'; import { useActionSheet } from '~/containers/ActionSheet'; import * as List from '~/containers/List'; -import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { asNativeListRow } from '~/containers/List/native/utils/rowMarkers'; import { useNativeListMode } from '~/containers/List/native/context'; -import NativeListPicker from '~/containers/List/native/Picker'; +import NativeListPicker from '~/containers/List/native/components/Picker'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; diff --git a/app/views/NewMessageView/ButtonCreate.ios.tsx b/app/views/NewMessageView/ButtonCreate.ios.tsx index ac4ae361383..d789c66441c 100644 --- a/app/views/NewMessageView/ButtonCreate.ios.tsx +++ b/app/views/NewMessageView/ButtonCreate.ios.tsx @@ -1,7 +1,7 @@ import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; import NativeListRow from '~/containers/NativeListRow'; -import Disclosure from '~/containers/NativeListRow/Disclosure.ios'; -import { PlainSeparator } from '~/containers/NativeListRow/Separator'; +import Disclosure from '~/containers/NativeListRow/components/Disclosure.ios'; +import { PlainSeparator } from '~/containers/NativeListRow/components/Separator'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; diff --git a/app/views/NewMessageView/HeaderNewMessage.tsx b/app/views/NewMessageView/HeaderNewMessage.tsx index da4350d9574..50508d7cdb8 100644 --- a/app/views/NewMessageView/HeaderNewMessage.tsx +++ b/app/views/NewMessageView/HeaderNewMessage.tsx @@ -15,7 +15,7 @@ import { type NewMessageStackParamList } from '~/stacks/types'; import { compareServerVersion } from '~/lib/methods/helpers'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { usePermissions } from '~/lib/hooks/usePermissions'; -import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import ButtonCreate from './ButtonCreate'; const styles = StyleSheet.create({ diff --git a/app/views/NewMessageView/index.tsx b/app/views/NewMessageView/index.tsx index c726096e86e..6fc16d46866 100644 --- a/app/views/NewMessageView/index.tsx +++ b/app/views/NewMessageView/index.tsx @@ -20,8 +20,8 @@ import { type NewMessageStackParamList } from '~/stacks/types'; import { search as runSearch } from '~/lib/methods/search'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; -import RowSeparator from '~/containers/NativeListRow/Separator'; -import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import RowSeparator from '~/containers/NativeListRow/components/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import Item from './Item'; import HeaderNewMessage from './HeaderNewMessage'; import { getUidDirectMessage } from '~/lib/methods/helpers/helpers'; diff --git a/app/views/NotificationPreferencesView/index.tsx b/app/views/NotificationPreferencesView/index.tsx index c60872f819f..97692cd51e0 100644 --- a/app/views/NotificationPreferencesView/index.tsx +++ b/app/views/NotificationPreferencesView/index.tsx @@ -6,9 +6,9 @@ import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; import { CustomIcon } from '~/containers/CustomIcon'; import * as List from '~/containers/List'; -import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { asNativeListRow } from '~/containers/List/native/utils/rowMarkers'; import { useNativeListMode } from '~/containers/List/native/context'; -import NativeListPicker from '~/containers/List/native/Picker'; +import NativeListPicker from '~/containers/List/native/components/Picker'; import SafeAreaView from '~/containers/SafeAreaView'; import { type IRoomNotifications, type TRoomNotificationsModel } from '~/definitions'; import I18n from '~/i18n'; diff --git a/app/views/PickerView/index.tsx b/app/views/PickerView/index.tsx index 6348d8a4696..e938bac7d55 100644 --- a/app/views/PickerView/index.tsx +++ b/app/views/PickerView/index.tsx @@ -12,8 +12,8 @@ import { useDebounce } from '~/lib/methods/helpers'; import { type TNavigation } from '~/stacks/stackType'; import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; -import { PlainSeparator } from '~/containers/NativeListRow/Separator'; -import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import { PlainSeparator } from '~/containers/NativeListRow/components/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import Item from './Item'; const styles = StyleSheet.create({ diff --git a/app/views/ProfileView/index.tsx b/app/views/ProfileView/index.tsx index 218f781fc1b..096bdb8cf2a 100644 --- a/app/views/ProfileView/index.tsx +++ b/app/views/ProfileView/index.tsx @@ -38,7 +38,7 @@ import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import useParsedCustomFields from '~/lib/hooks/useParsedCustomFields'; import CustomFields from '~/containers/CustomFields'; -import ListSeparator from '~/containers/List/ListSeparator'; +import ListSeparator from '~/containers/List/components/ListSeparator'; import handleSaveUserProfileError from '~/lib/methods/helpers/handleSaveUserProfileError'; import logoutOtherLocations from './methods/logoutOtherLocations'; import buildProfileParams from './methods/buildProfileParams'; diff --git a/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx b/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx index 92270a5a259..4ca1b47b616 100644 --- a/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx +++ b/app/views/PushTroubleshootView/components/CommunityEditionPushQuota.tsx @@ -2,7 +2,7 @@ import { Alert, StyleSheet, Text } from 'react-native'; import { type ReactElement } from 'react'; import * as List from '~/containers/List'; -import { asNativeListSection } from '~/containers/List/native/rowMarkers'; +import { asNativeListSection } from '~/containers/List/native/utils/rowMarkers'; import i18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useTheme } from '~/theme'; diff --git a/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx b/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx index 07a81cfb55b..d55625f4e25 100644 --- a/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx +++ b/app/views/PushTroubleshootView/components/DeviceNotificationSettings.tsx @@ -7,7 +7,7 @@ import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { isIOS, showErrorAlert } from '~/lib/methods/helpers'; import { useTheme } from '~/theme'; import { CustomIcon } from '~/containers/CustomIcon'; -import { asNativeListSection } from '~/containers/List/native/rowMarkers'; +import { asNativeListSection } from '~/containers/List/native/utils/rowMarkers'; function DeviceNotificationSettings(): ReactElement { const { colors } = useTheme(); diff --git a/app/views/PushTroubleshootView/components/NotificationDelay.tsx b/app/views/PushTroubleshootView/components/NotificationDelay.tsx index cbfd9ecd4e4..564b5f5f7e2 100644 --- a/app/views/PushTroubleshootView/components/NotificationDelay.tsx +++ b/app/views/PushTroubleshootView/components/NotificationDelay.tsx @@ -3,7 +3,7 @@ import { type ReactElement } from 'react'; import NewWindowIcon from '~/containers/NewWindowIcon'; import * as List from '~/containers/List'; -import { asNativeListSection } from '~/containers/List/native/rowMarkers'; +import { asNativeListSection } from '~/containers/List/native/utils/rowMarkers'; function NotificationDelay(): ReactElement { const openNotificationDocumentation = () => Linking.openURL('https://go.rocket.chat/i/push-notifications'); diff --git a/app/views/PushTroubleshootView/components/PushGatewayConnection.tsx b/app/views/PushTroubleshootView/components/PushGatewayConnection.tsx index 17d04d2dede..e0c45d1043a 100644 --- a/app/views/PushTroubleshootView/components/PushGatewayConnection.tsx +++ b/app/views/PushTroubleshootView/components/PushGatewayConnection.tsx @@ -9,7 +9,7 @@ import { compareServerVersion, showErrorAlertWithEMessage } from '~/lib/methods/ import { pushTest } from '~/lib/services/restApi'; import { useTheme } from '~/theme'; import { CustomIcon } from '~/containers/CustomIcon'; -import { asNativeListSection } from '~/containers/List/native/rowMarkers'; +import { asNativeListSection } from '~/containers/List/native/utils/rowMarkers'; function PushGatewayConnection(): ReactElement | null { const [loading, setLoading] = useState(false); diff --git a/app/views/RoomActionsView/components/CallSection.tsx b/app/views/RoomActionsView/components/CallSection.tsx index 61529bbbf8d..c8ded098aa4 100644 --- a/app/views/RoomActionsView/components/CallSection.tsx +++ b/app/views/RoomActionsView/components/CallSection.tsx @@ -1,7 +1,7 @@ import { type ReactElement, useEffect, useState } from 'react'; import * as List from '~/containers/List'; -import { asNativeListSection } from '~/containers/List/native/rowMarkers'; +import { asNativeListSection } from '~/containers/List/native/utils/rowMarkers'; import { useVideoConf } from '~/lib/hooks/useVideoConf'; import type { TSubscriptionModel } from '~/definitions'; import { useNewMediaCall } from '~/lib/hooks/useNewMediaCall'; diff --git a/app/views/RoomActionsView/index.tsx b/app/views/RoomActionsView/index.tsx index 0159276b592..277a20e5cf2 100644 --- a/app/views/RoomActionsView/index.tsx +++ b/app/views/RoomActionsView/index.tsx @@ -12,7 +12,7 @@ import { leaveRoom } from '~/actions/room'; import Avatar from '~/containers/Avatar'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import * as List from '~/containers/List'; -import Disclosure from '~/containers/NativeListRow/Disclosure'; +import Disclosure from '~/containers/NativeListRow/components/Disclosure'; import { MarkdownPreview } from '~/containers/markdown'; import RoomTypeIcon from '~/containers/RoomTypeIcon'; import SafeAreaView from '~/containers/SafeAreaView'; diff --git a/app/views/RoomMembersView/components/ActionsSection.ios.tsx b/app/views/RoomMembersView/components/ActionsSection.ios.tsx index 24b502aa2a8..77c8036d9f3 100644 --- a/app/views/RoomMembersView/components/ActionsSection.ios.tsx +++ b/app/views/RoomMembersView/components/ActionsSection.ios.tsx @@ -3,8 +3,8 @@ import { type ReactElement } from 'react'; import NativeListRow from '~/containers/NativeListRow'; import { CustomIcon } from '~/containers/CustomIcon'; -import Disclosure from '~/containers/NativeListRow/Disclosure.ios'; -import RowSeparator from '~/containers/NativeListRow/Separator'; +import Disclosure from '~/containers/NativeListRow/components/Disclosure.ios'; +import RowSeparator from '~/containers/NativeListRow/components/Separator'; import i18n from '~/i18n'; import { useTheme } from '~/theme'; import { type IActionsSection, useMemberActions } from './useMemberActions'; diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 8d253e44834..b085075e96d 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -10,8 +10,8 @@ import ActivityIndicator from '~/containers/ActivityIndicator'; import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import SafeAreaView from '~/containers/SafeAreaView'; -import RowSeparator from '~/containers/NativeListRow/Separator'; -import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import RowSeparator from '~/containers/NativeListRow/components/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import SearchBox from '~/containers/SearchBox'; import UserItem from '~/containers/UserItem'; import Radio from '~/containers/Radio'; diff --git a/app/views/SelectListView/Item.ios.tsx b/app/views/SelectListView/Item.ios.tsx index f761b7011dc..0efb8925f72 100644 --- a/app/views/SelectListView/Item.ios.tsx +++ b/app/views/SelectListView/Item.ios.tsx @@ -1,7 +1,7 @@ import * as List from '~/containers/List'; import { ICON_SIZE } from '~/containers/List/constants'; import NativeListRow from '~/containers/NativeListRow'; -import RowSeparator from '~/containers/NativeListRow/Separator'; +import RowSeparator from '~/containers/NativeListRow/components/Separator'; import { AVATAR_SIZE } from '~/containers/NativeListRow/constants'; import Radio from '~/containers/Radio'; import { useTheme } from '~/theme'; diff --git a/app/views/SelectedUsersView/Header.tsx b/app/views/SelectedUsersView/Header.tsx index 8b2fc8ebfb9..8864f76ac67 100644 --- a/app/views/SelectedUsersView/Header.tsx +++ b/app/views/SelectedUsersView/Header.tsx @@ -9,7 +9,7 @@ import sharedStyles from '../Styles'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import Chip from '~/containers/Chip'; import { hasNativeHeaderBar } from '~/lib/methods/helpers'; -import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; const styles = StyleSheet.create({ selectedText: { diff --git a/app/views/SelectedUsersView/index.tsx b/app/views/SelectedUsersView/index.tsx index 697458ef494..1a95a9e88a4 100644 --- a/app/views/SelectedUsersView/index.tsx +++ b/app/views/SelectedUsersView/index.tsx @@ -14,8 +14,8 @@ import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import * as List from '~/containers/List'; import { sendLoadingEvent } from '~/containers/Loading'; import SafeAreaView from '~/containers/SafeAreaView'; -import RowSeparator from '~/containers/NativeListRow/Separator'; -import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import RowSeparator from '~/containers/NativeListRow/components/Separator'; +import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import I18n from '~/i18n'; import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import database from '~/lib/database'; diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index 08756b048ec..3a7b4ef6a4e 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -14,7 +14,7 @@ import database from '~/lib/database'; import I18n from '~/i18n'; import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; import DirectoryItem, { ROW_HEIGHT } from '~/containers/DirectoryItem'; -import RowSeparator from '~/containers/NativeListRow/Separator'; +import RowSeparator from '~/containers/NativeListRow/components/Separator'; import ServerItem from '~/containers/ServerItem'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import ActivityIndicator from '~/containers/ActivityIndicator'; diff --git a/app/views/SidebarView/components/CustomStatus.tsx b/app/views/SidebarView/components/CustomStatus.tsx index d1869af795d..150e3ec9e2c 100644 --- a/app/views/SidebarView/components/CustomStatus.tsx +++ b/app/views/SidebarView/components/CustomStatus.tsx @@ -5,7 +5,7 @@ import { shallowEqual, useDispatch } from 'react-redux'; import { CustomIcon } from '~/containers/CustomIcon'; import Status from '~/containers/Status/Status'; import * as List from '~/containers/List'; -import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { asNativeListRow } from '~/containers/List/native/utils/rowMarkers'; import styles from '../styles'; import { useTheme } from '~/theme'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; diff --git a/app/views/SidebarView/components/Profile.ios.tsx b/app/views/SidebarView/components/Profile.ios.tsx index 1c4a9297102..60b1c98e533 100644 --- a/app/views/SidebarView/components/Profile.ios.tsx +++ b/app/views/SidebarView/components/Profile.ios.tsx @@ -6,7 +6,7 @@ import Avatar from '~/containers/Avatar'; import { getUserSelector } from '~/selectors/login'; import { type DrawerParamList } from '~/stacks/types'; import * as List from '~/containers/List'; -import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { asNativeListRow } from '~/containers/List/native/utils/rowMarkers'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; diff --git a/app/views/SidebarView/components/SupportedVersionsWarnItem.tsx b/app/views/SidebarView/components/SupportedVersionsWarnItem.tsx index 1e945c2c689..00b83c863b5 100644 --- a/app/views/SidebarView/components/SupportedVersionsWarnItem.tsx +++ b/app/views/SidebarView/components/SupportedVersionsWarnItem.tsx @@ -3,7 +3,7 @@ import { memo } from 'react'; import { useTheme } from '~/theme'; import { CustomIcon } from '~/containers/CustomIcon'; import * as List from '~/containers/List'; -import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { asNativeListRow } from '~/containers/List/native/utils/rowMarkers'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { showActionSheetRef } from '~/containers/ActionSheet'; diff --git a/app/views/SidebarView/components/getSidebarSelection.ts b/app/views/SidebarView/components/getSidebarSelection.ts index 3abf8be7694..3c39d22910f 100644 --- a/app/views/SidebarView/components/getSidebarSelection.ts +++ b/app/views/SidebarView/components/getSidebarSelection.ts @@ -1,4 +1,4 @@ -import { type IListSelection } from '~/containers/List/ListContainer.ios'; +import { type IListSelection } from '~/containers/List/components/ListContainer.ios'; import { type IStackItem } from './useStackItems'; export const ADMIN_SELECTION_TAG = 'sidebar-admin'; diff --git a/app/views/SidebarView/index.ios.tsx b/app/views/SidebarView/index.ios.tsx index 333b451a842..991f01e2d3f 100644 --- a/app/views/SidebarView/index.ios.tsx +++ b/app/views/SidebarView/index.ios.tsx @@ -1,7 +1,7 @@ import { type DrawerNavigationProp } from '@react-navigation/drawer'; import { View } from 'react-native'; -import ListContainer from '~/containers/List/ListContainer.ios'; +import ListContainer from '~/containers/List/components/ListContainer.ios'; import styles from './styles'; import { type DrawerParamList } from '~/stacks/types'; import { useSidebarSections } from './useSidebarSections.ios'; diff --git a/app/views/StatusView/ClearAfterPicker/ClearAfterNativeRow.tsx b/app/views/StatusView/ClearAfterPicker/ClearAfterNativeRow.tsx index 85ff24bd7f5..c00c1816d7a 100644 --- a/app/views/StatusView/ClearAfterPicker/ClearAfterNativeRow.tsx +++ b/app/views/StatusView/ClearAfterPicker/ClearAfterNativeRow.tsx @@ -1,6 +1,6 @@ import { useActionSheet } from '~/containers/ActionSheet'; -import { asNativeListRow } from '~/containers/List/native/rowMarkers'; -import NativeListPicker from '~/containers/List/native/Picker'; +import { asNativeListRow } from '~/containers/List/native/utils/rowMarkers'; +import NativeListPicker from '~/containers/List/native/components/Picker'; import I18n from '~/i18n'; import DatePickerSheetContent from './DatePickerSheetContent'; import { CLEAR_AFTER_OPTIONS, type ClearAfterValue } from './types'; diff --git a/app/views/StatusView/ClearAfterPicker/index.tsx b/app/views/StatusView/ClearAfterPicker/index.tsx index f388e740258..fb03f5be89f 100644 --- a/app/views/StatusView/ClearAfterPicker/index.tsx +++ b/app/views/StatusView/ClearAfterPicker/index.tsx @@ -3,7 +3,7 @@ import { Text } from 'react-native'; import { useActionSheet } from '~/containers/ActionSheet'; import * as List from '~/containers/List'; -import { asNativeListSection } from '~/containers/List/native/rowMarkers'; +import { asNativeListSection } from '~/containers/List/native/utils/rowMarkers'; import { useNativeListMode } from '~/containers/List/native/context'; import I18n from '~/i18n'; import dayjs from '~/lib/dayjs'; diff --git a/app/views/StatusView/index.test.tsx b/app/views/StatusView/index.test.tsx index 87f697ee106..37b65b2aaf8 100644 --- a/app/views/StatusView/index.test.tsx +++ b/app/views/StatusView/index.test.tsx @@ -55,7 +55,7 @@ jest.mock('~/containers/ActionSheet', () => ({ })); const mockPicker = jest.fn(); -jest.mock('~/containers/List/native/Picker', () => { +jest.mock('~/containers/List/native/components/Picker', () => { const { View } = require('react-native'); return { __esModule: true, diff --git a/app/views/StatusView/index.tsx b/app/views/StatusView/index.tsx index 1dae150af5d..df1a9879e9a 100644 --- a/app/views/StatusView/index.tsx +++ b/app/views/StatusView/index.tsx @@ -25,7 +25,7 @@ import { showErrorAlertWithEMessage, compareServerVersion, isIOS } from '~/lib/m import log, { events, logEvent } from '~/lib/methods/helpers/log'; import { useTheme } from '~/theme'; import { USER_STATUS_TEXT_MAX_LENGTH } from '~/lib/constants/maxLength'; -import { useListBackgroundColor } from '~/containers/NativeListRow/useListBackgroundColor'; +import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import ClearAfterPicker, { type ClearAfterValue, computeExpiresAt, getInitialClearAfterState } from './ClearAfterPicker'; import FooterComponent from './FooterComponent'; diff --git a/app/views/UserNotificationPreferencesView/ListPicker.tsx b/app/views/UserNotificationPreferencesView/ListPicker.tsx index c093b821642..7906749fba8 100644 --- a/app/views/UserNotificationPreferencesView/ListPicker.tsx +++ b/app/views/UserNotificationPreferencesView/ListPicker.tsx @@ -2,9 +2,9 @@ import { StyleSheet, Text, View } from 'react-native'; import { Fragment, type ReactElement } from 'react'; import * as List from '~/containers/List'; -import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { asNativeListRow } from '~/containers/List/native/utils/rowMarkers'; import { useNativeListMode } from '~/containers/List/native/context'; -import NativeListPicker from '~/containers/List/native/Picker'; +import NativeListPicker from '~/containers/List/native/components/Picker'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; diff --git a/app/views/UserPreferencesView/ListPicker.tsx b/app/views/UserPreferencesView/ListPicker.tsx index cb60a280d1c..2715e068985 100644 --- a/app/views/UserPreferencesView/ListPicker.tsx +++ b/app/views/UserPreferencesView/ListPicker.tsx @@ -4,9 +4,9 @@ import { StyleSheet, Text } from 'react-native'; import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; import { CustomIcon } from '~/containers/CustomIcon'; import * as List from '~/containers/List'; -import { asNativeListRow } from '~/containers/List/native/rowMarkers'; +import { asNativeListRow } from '~/containers/List/native/utils/rowMarkers'; import { useNativeListMode } from '~/containers/List/native/context'; -import NativeListPicker from '~/containers/List/native/Picker'; +import NativeListPicker from '~/containers/List/native/components/Picker'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; import sharedStyles from '../Styles'; diff --git a/app/views/UserPreferencesView/__tests__/ListPicker.test.tsx b/app/views/UserPreferencesView/__tests__/ListPicker.test.tsx index c863ac76d48..f569f8b53f1 100644 --- a/app/views/UserPreferencesView/__tests__/ListPicker.test.tsx +++ b/app/views/UserPreferencesView/__tests__/ListPicker.test.tsx @@ -5,7 +5,7 @@ import { type INativeListPicker } from '~/containers/List/native/types'; import ListPicker from '../ListPicker'; const mockPicker = jest.fn(); -jest.mock('~/containers/List/native/Picker', () => ({ +jest.mock('~/containers/List/native/components/Picker', () => ({ __esModule: true, default: (props: INativeListPicker) => { mockPicker(props); From 8a6af0e4cbc0150304e30b637290057d3cbc71a1 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 30 Sep 2026 15:48:39 -0300 Subject: [PATCH 117/120] refactor: dedupe native header and list helpers --- app/containers/DirectoryItem/index.ios.tsx | 18 +------ app/containers/DirectoryItem/index.tsx | 2 +- .../List/components/ListContainer.ios.tsx | 5 +- .../List/native/components/Status.ios.tsx | 9 +--- app/containers/Status/Status.tsx | 8 ++-- app/containers/Status/getStatusIconName.ts | 7 +++ app/containers/UserItem.ios.tsx | 19 +------- app/containers/UserItem.tsx | 2 +- .../methods/helpers/navigation/headerIcon.ts | 12 ++++- app/sagas/preloadNativeHeaderIcons.js | 4 +- app/views/CannedResponsesListView/index.tsx | 26 +++++----- app/views/NewMessageView/ButtonCreate.ios.tsx | 12 +---- app/views/NewMessageView/ButtonCreate.tsx | 2 +- app/views/NewMessageView/Item.ios.tsx | 12 +---- app/views/NewMessageView/Item.tsx | 2 +- .../NotificationPreferencesView/index.tsx | 16 +++++-- .../RoomView/helpers/roomHeaderActions.ts | 4 +- .../RoomView/hooks/useNativeBackButton.ts | 2 +- .../RoomView/hooks/useNativeRoomHeader.ts | 6 +-- .../RoomView/hooks/useRoomHeaderRightItems.ts | 18 +++---- app/views/RoomsListView/components/Header.tsx | 21 +-------- app/views/RoomsListView/hooks/useHeader.tsx | 20 +------- .../hooks/useRoomsListSubtitle.ts | 24 ++++++++++ app/views/ThemeView.tsx | 47 ++++++++----------- 24 files changed, 120 insertions(+), 178 deletions(-) create mode 100644 app/containers/Status/getStatusIconName.ts create mode 100644 app/views/RoomsListView/hooks/useRoomsListSubtitle.ts diff --git a/app/containers/DirectoryItem/index.ios.tsx b/app/containers/DirectoryItem/index.ios.tsx index cc1492c1c51..68dedcb848b 100644 --- a/app/containers/DirectoryItem/index.ios.tsx +++ b/app/containers/DirectoryItem/index.ios.tsx @@ -1,5 +1,5 @@ import { type ReactElement } from 'react'; -import { Text, type ViewStyle } from 'react-native'; +import { Text } from 'react-native'; import Avatar from '../Avatar'; import RoomTypeIcon from '../RoomTypeIcon'; @@ -8,24 +8,10 @@ import { AVATAR_SIZE } from '../NativeListRow/constants'; import styles from './styles'; import { useTheme } from '~/theme'; import usePreviewFormatText from '~/lib/hooks/usePreviewFormatText'; +import { type IDirectoryItem } from './index'; export { ROW_HEIGHT } from '../NativeListRow/constants'; -interface IDirectoryItem { - title: string; - description?: string; - avatar?: string; - type: string; - onPress(): void; - testID: string; - style?: ViewStyle; - rightLabel?: string; - rid?: string; - teamMain?: boolean; - isFirst?: boolean; - isLast?: boolean; -} - const DirectoryItem = ({ title, description, diff --git a/app/containers/DirectoryItem/index.tsx b/app/containers/DirectoryItem/index.tsx index 19066f29786..4572badd5a5 100644 --- a/app/containers/DirectoryItem/index.tsx +++ b/app/containers/DirectoryItem/index.tsx @@ -15,7 +15,7 @@ interface IDirectoryItemLabel { color: string; } -interface IDirectoryItem { +export interface IDirectoryItem { title: string; description?: string; avatar?: string; diff --git a/app/containers/List/components/ListContainer.ios.tsx b/app/containers/List/components/ListContainer.ios.tsx index 89a485a096f..2531903567f 100644 --- a/app/containers/List/components/ListContainer.ios.tsx +++ b/app/containers/List/components/ListContainer.ios.tsx @@ -46,10 +46,7 @@ const isSection = (element: ReactElement) => element.type === ListSection || isN const isNativeRow = (element: ReactElement) => element.type === ListItem || element.type === ListRadio || isNativeListRow(element.type); const hasLeftIcon = (element: ReactElement) => Boolean((element.props as { left?: unknown }).left); -const rowSelectionTag = (element: ReactElement) => { - const { selectionTag, testID } = element.props as { selectionTag?: string; testID?: string }; - return selectionTag ?? testID; -}; +const rowSelectionTag = (element: ReactElement) => (element.props as { testID?: string }).testID; const selectedTags = ({ selectedTag }: IListSelection) => (selectedTag ? [selectedTag] : []); diff --git a/app/containers/List/native/components/Status.ios.tsx b/app/containers/List/native/components/Status.ios.tsx index 95f2646d389..6bd2ed7570c 100644 --- a/app/containers/List/native/components/Status.ios.tsx +++ b/app/containers/List/native/components/Status.ios.tsx @@ -1,19 +1,14 @@ import { type TUserStatus } from '~/definitions'; import { useUserStatusColor } from '~/lib/hooks/useUserStatusColor'; import { useTheme } from '~/theme'; -import { hasIcon, type TIconsName } from '~/containers/CustomIcon'; +import { getStatusIconName } from '~/containers/Status/getStatusIconName'; import NativeListIcon from './Icon.ios'; -const statusIconName = (status: TUserStatus): TIconsName => { - const name = `status-${status}`; - return hasIcon(name) ? (name as TIconsName) : 'status-offline'; -}; - const NativeListStatus = ({ status }: { status: TUserStatus }) => { const { colors } = useTheme(); const statusColor = useUserStatusColor(status); - return ; + return ; }; export default NativeListStatus; diff --git a/app/containers/Status/Status.tsx b/app/containers/Status/Status.tsx index 1e813a0c94a..42ea610da6a 100644 --- a/app/containers/Status/Status.tsx +++ b/app/containers/Status/Status.tsx @@ -3,8 +3,9 @@ import { memo } from 'react'; import { type StyleProp, type TextStyle, useWindowDimensions } from 'react-native'; import { useTheme } from '~/theme'; -import { CustomIcon, hasIcon, type TIconsName } from '../CustomIcon'; +import { CustomIcon } from '../CustomIcon'; import { type IStatusComponentProps } from './definition'; +import { getStatusIconName } from './getStatusIconName'; import { useUserStatusColor } from '~/lib/hooks/useUserStatusColor'; const Status = memo(({ style, status = 'offline', size = 32, ...props }: IStatusComponentProps) => { @@ -13,9 +14,6 @@ const Status = memo(({ style, status = 'offline', size = 32, ...props }: IStatus const { fontScale } = useWindowDimensions(); - const name: TIconsName = `status-${status}`; - const isNameValid = hasIcon(name); - const iconName = isNameValid ? name : 'status-offline'; const calculatedStyle: StyleProp = [ { width: size * fontScale, @@ -31,7 +29,7 @@ const Status = memo(({ style, status = 'offline', size = 32, ...props }: IStatus {...props} style={calculatedStyle} size={size} - name={iconName} + name={getStatusIconName(status)} color={userStatusColor ?? colors.userPresenceOffline} /> ); diff --git a/app/containers/Status/getStatusIconName.ts b/app/containers/Status/getStatusIconName.ts new file mode 100644 index 00000000000..960f48df4f4 --- /dev/null +++ b/app/containers/Status/getStatusIconName.ts @@ -0,0 +1,7 @@ +import { type TUserStatus } from '~/definitions'; +import { hasIcon, type TIconsName } from '~/containers/CustomIcon'; + +export const getStatusIconName = (status: TUserStatus): TIconsName => { + const name = `status-${status}`; + return hasIcon(name) ? (name as TIconsName) : 'status-offline'; +}; diff --git a/app/containers/UserItem.ios.tsx b/app/containers/UserItem.ios.tsx index 9d190485cb6..bb061e88a4b 100644 --- a/app/containers/UserItem.ios.tsx +++ b/app/containers/UserItem.ios.tsx @@ -1,25 +1,10 @@ -import { type StyleProp, type ViewStyle } from 'react-native'; - import Avatar from './Avatar'; -import { CustomIcon, type TIconsName } from './CustomIcon'; +import { CustomIcon } from './CustomIcon'; import NativeListRow from './NativeListRow'; import { AVATAR_SIZE } from './NativeListRow/constants'; import { useTheme } from '../theme'; import i18n from '../i18n'; - -interface IUserItem { - name: string; - username: string; - onPress(): void; - testID: string; - onLongPress?: () => void; - style?: StyleProp; - icon?: TIconsName | null; - iconColor?: string; - isChecked?: boolean; - isFirst?: boolean; - isLast?: boolean; -} +import { type IUserItem } from './UserItem'; const UserItem = ({ name, username, onPress, testID, onLongPress, icon, iconColor, isChecked, isFirst, isLast }: IUserItem) => { const { colors } = useTheme(); diff --git a/app/containers/UserItem.tsx b/app/containers/UserItem.tsx index 9ff3d3be03d..b4ae5c0c456 100644 --- a/app/containers/UserItem.tsx +++ b/app/containers/UserItem.tsx @@ -34,7 +34,7 @@ const styles = StyleSheet.create({ } }); -interface IUserItem { +export interface IUserItem { name: string; username: string; onPress(): void; diff --git a/app/lib/methods/helpers/navigation/headerIcon.ts b/app/lib/methods/helpers/navigation/headerIcon.ts index ae0a0e648a4..df92d3f68b2 100644 --- a/app/lib/methods/helpers/navigation/headerIcon.ts +++ b/app/lib/methods/helpers/navigation/headerIcon.ts @@ -27,7 +27,9 @@ const HEADER_ICONS: TIconsName[] = [ const headerIconSources = new Map(); -export const preloadHeaderIcons = async () => { +let preloadPromise: Promise | undefined; + +const loadHeaderIcons = async () => { await Promise.all( HEADER_ICONS.map(async name => { const source = await IconSet.getImageSource(name, HEADER_ICON_SIZE, 'black'); @@ -38,6 +40,14 @@ export const preloadHeaderIcons = async () => { ); }; +export const preloadHeaderIcons = () => { + preloadPromise ??= loadHeaderIcons().catch(error => { + preloadPromise = undefined; + throw error; + }); + return preloadPromise; +}; + export const headerIcon = (name: TIconsName) => { const source = headerIconSources.get(name); return source ? { type: 'image' as const, source } : undefined; diff --git a/app/sagas/preloadNativeHeaderIcons.js b/app/sagas/preloadNativeHeaderIcons.js index 01aecbeac3b..b09eb21697a 100644 --- a/app/sagas/preloadNativeHeaderIcons.js +++ b/app/sagas/preloadNativeHeaderIcons.js @@ -1,11 +1,11 @@ import { call } from 'redux-saga/effects'; -import { isIOS } from '../lib/methods/helpers'; +import { hasNativeHeaderBar } from '../lib/methods/helpers'; import log from '../lib/methods/helpers/log'; import { preloadHeaderIcons } from '../lib/methods/helpers/navigation/headerIcon'; export const preloadNativeHeaderIcons = function* preloadNativeHeaderIcons() { - if (!isIOS) { + if (!hasNativeHeaderBar) { return; } try { diff --git a/app/views/CannedResponsesListView/index.tsx b/app/views/CannedResponsesListView/index.tsx index 428e90cdfb9..9d47729e7e4 100644 --- a/app/views/CannedResponsesListView/index.tsx +++ b/app/views/CannedResponsesListView/index.tsx @@ -212,6 +212,19 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView await handleGetListCannedResponse({ text: searchText, department: scope, depId: departmentId, debounced: false }); }; + const showFilters = () => { + showActionSheetRef({ + children: ( + + ), + enableContentPanningGesture: false + }); + }; + const getHeader = (): NativeStackNavigationOptions => { if (hasNativeHeaderBar) { return { @@ -274,19 +287,6 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView setHeader(); }, [isSearching, departments, currentDepartment]); - const showFilters = () => { - showActionSheetRef({ - children: ( - - ), - enableContentPanningGesture: false - }); - }; - const renderContent = () => { if (!cannedResponsesScopeName.length && !loading) { return ; diff --git a/app/views/NewMessageView/ButtonCreate.ios.tsx b/app/views/NewMessageView/ButtonCreate.ios.tsx index d789c66441c..b8718bf3341 100644 --- a/app/views/NewMessageView/ButtonCreate.ios.tsx +++ b/app/views/NewMessageView/ButtonCreate.ios.tsx @@ -1,18 +1,10 @@ -import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; +import { CustomIcon } from '~/containers/CustomIcon'; import NativeListRow from '~/containers/NativeListRow'; import Disclosure from '~/containers/NativeListRow/components/Disclosure.ios'; import { PlainSeparator } from '~/containers/NativeListRow/components/Separator'; import I18n from '~/i18n'; import { useTheme } from '~/theme'; - -interface IButton { - onPress: () => void; - testID: string; - title: string; - icon: TIconsName; - isFirst?: boolean; - isLast?: boolean; -} +import { type IButton } from './ButtonCreate'; const ButtonCreate = ({ onPress, testID, title, icon, isFirst, isLast }: IButton) => { const { colors } = useTheme(); diff --git a/app/views/NewMessageView/ButtonCreate.tsx b/app/views/NewMessageView/ButtonCreate.tsx index 671af7311f4..d7c5ca2fee5 100644 --- a/app/views/NewMessageView/ButtonCreate.tsx +++ b/app/views/NewMessageView/ButtonCreate.tsx @@ -3,7 +3,7 @@ import { themes } from '~/lib/constants/colors'; import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; import { useTheme } from '~/theme'; -interface IButton { +export interface IButton { onPress: () => void; testID: string; title: string; diff --git a/app/views/NewMessageView/Item.ios.tsx b/app/views/NewMessageView/Item.ios.tsx index c62d888563a..446db96fd25 100644 --- a/app/views/NewMessageView/Item.ios.tsx +++ b/app/views/NewMessageView/Item.ios.tsx @@ -6,17 +6,7 @@ import { useMediaCallPermission } from '~/lib/hooks/useMediaCallPermission'; import { useIsInActiveVoipCall } from '~/lib/services/voip/isInActiveVoipCall'; import { isSelfUserId } from '~/lib/services/voip/isSelfUserId'; import { useStartMediaCall } from './useStartMediaCall'; - -interface IItem { - userId: string; - name: string; - username: string; - onPress(): void; - testID: string; - onLongPress?: () => void; - isFirst?: boolean; - isLast?: boolean; -} +import { type IItem } from './Item'; const Item = ({ userId, name, username, onPress, testID, onLongPress, isFirst, isLast }: IItem) => { const hasMediaCallPermission = useMediaCallPermission(); diff --git a/app/views/NewMessageView/Item.tsx b/app/views/NewMessageView/Item.tsx index 12a4078ae32..52e3aff0393 100644 --- a/app/views/NewMessageView/Item.tsx +++ b/app/views/NewMessageView/Item.tsx @@ -11,7 +11,7 @@ import { useIsInActiveVoipCall } from '~/lib/services/voip/isInActiveVoipCall'; import { isSelfUserId } from '~/lib/services/voip/isSelfUserId'; import { useStartMediaCall } from './useStartMediaCall'; -interface IItem { +export interface IItem { userId: string; name: string; username: string; diff --git a/app/views/NotificationPreferencesView/index.tsx b/app/views/NotificationPreferencesView/index.tsx index 97692cd51e0..346d469bb25 100644 --- a/app/views/NotificationPreferencesView/index.tsx +++ b/app/views/NotificationPreferencesView/index.tsx @@ -54,12 +54,17 @@ const RenderListPicker = ({ const [option, setOption] = useState(pref); + const selectOption = (selected: NonNullable) => { + const previous = option; + onChangeValue(preference, { [preference]: selected.value.toString() }, () => setOption(previous)); + setOption(selected); + }; + const options: TActionSheetOptionsItem[] = OPTIONS[preference as TOptions].map(i => ({ title: I18n.t(i.label, { defaultValue: i.label, second: i.second }), onPress: () => { hideActionSheet(); - onChangeValue(preference, { [preference]: i.value.toString() }, () => setOption(option)); - setOption(i); + selectOption(i); }, right: option?.value === i.value ? () => : undefined })); @@ -77,9 +82,10 @@ const RenderListPicker = ({ }))} selection={option?.value.toString() ?? ''} onSelectionChange={selected => { - const previous = option; - onChangeValue(preference, { [preference]: selected }, () => setOption(previous)); - setOption(OPTIONS[preference as TOptions].find(i => i.value.toString() === selected)); + const selectedOption = OPTIONS[preference as TOptions].find(i => i.value.toString() === selected); + if (selectedOption) { + selectOption(selectedOption); + } }} /> ); diff --git a/app/views/RoomView/helpers/roomHeaderActions.ts b/app/views/RoomView/helpers/roomHeaderActions.ts index dd6e63fdf46..794264b553c 100644 --- a/app/views/RoomView/helpers/roomHeaderActions.ts +++ b/app/views/RoomView/helpers/roomHeaderActions.ts @@ -1,6 +1,6 @@ -export const ROOM_HEADER_ACTION_PRIORITY = ['threads', 'call', 'encryption', 'notifications'] as const; +const ROOM_HEADER_ACTION_PRIORITY = ['threads', 'call', 'encryption', 'notifications'] as const; export type TRoomHeaderActionKey = (typeof ROOM_HEADER_ACTION_PRIORITY)[number]; -export const MAX_VISIBLE_ROOM_HEADER_ACTIONS = 2; +const MAX_VISIBLE_ROOM_HEADER_ACTIONS = 2; export const splitRoomHeaderActions = ( present: Partial> diff --git a/app/views/RoomView/hooks/useNativeBackButton.ts b/app/views/RoomView/hooks/useNativeBackButton.ts index ca3afe5c0c3..3f18eec7f4c 100644 --- a/app/views/RoomView/hooks/useNativeBackButton.ts +++ b/app/views/RoomView/hooks/useNativeBackButton.ts @@ -7,7 +7,7 @@ import { useTheme } from '~/theme'; import { type IRoomViewProps } from '../definitions'; import { useUnreadsCount } from './useUnreadsCount'; -export const formatUnreadsCount = (unreadsCount: number | null) => { +const formatUnreadsCount = (unreadsCount: number | null) => { if (!unreadsCount) { return ''; } diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts index 04b94c7ae42..3f24490782e 100644 --- a/app/views/RoomView/hooks/useNativeRoomHeader.ts +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -2,7 +2,8 @@ import { useEffect, useLayoutEffect } from 'react'; import { shallowEqual } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; -import { hasIcon, type TIconsName } from '~/containers/CustomIcon'; +import { type TIconsName } from '~/containers/CustomIcon'; +import { getStatusIconName } from '~/containers/Status/getStatusIconName'; import { getOmnichannelIconName, getOmnichannelSidebarIconUri, @@ -26,8 +27,7 @@ const SUBTITLE_FONT_SIZE = 12; const getRoomIcon = (fields: IHeaderFields, isDirectMessage: boolean, status: TUserStatus): TIconsName => { if (isDirectMessage) { - const statusIcon = `status-${status}`; - return hasIcon(statusIcon) ? (statusIcon as TIconsName) : 'status-offline'; + return getStatusIconName(status); } if (fields.type === 'l' && !fields.prid) { return getOmnichannelIconName(fields.sourceType); diff --git a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts index dfe598ad898..076b2922e48 100644 --- a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts +++ b/app/views/RoomView/hooks/useRoomHeaderRightItems.ts @@ -16,6 +16,7 @@ import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; import { returnLivechat } from '~/lib/services/restApi'; import { getUserSelector } from '~/selectors/login'; import { useTheme } from '~/theme'; +import { getUnreadStyle } from '~/containers/UnreadBadge/getUnreadStyle'; import { type RoomStore } from '../definitions'; import { fromSubscription } from '../stores/RoomStoreContext'; import { closeLivechat } from '../services/closeLivechat'; @@ -40,16 +41,6 @@ interface IRoomHeaderAction { onPress: () => void; } -const getTunreadBadgeColor = ( - tunreadUser: string[], - tunreadGroup: string[], - colors: { badgeBackgroundLevel4: string; badgeBackgroundLevel3: string; fontInfo: string } -) => { - if (tunreadUser.length) return colors.badgeBackgroundLevel4; - if (tunreadGroup.length) return colors.badgeBackgroundLevel3; - return colors.fontInfo; -}; - const useOmnichannelRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): NativeStackHeaderItem[] => { const navigation = useNavigation(); const isMasterDetail = useMasterDetail(); @@ -152,7 +143,7 @@ const useThreadRightItems = (tmid: string | undefined, enabled: boolean): Native }; const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): NativeStackHeaderItem[] => { - const { colors } = useTheme(); + const { theme, colors } = useTheme(); const { threadsEnabled, issuesWithNotifications, @@ -182,7 +173,10 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): const tunreadBadge = threadsEnabled && tunread.length - ? { value: tunread.length, style: { backgroundColor: getTunreadBadgeColor(tunreadUser ?? [], tunreadGroup ?? [], colors) } } + ? { + value: tunread.length, + style: { backgroundColor: getUnreadStyle({ tunread, tunreadUser, tunreadGroup, theme }).backgroundColor as string } + } : undefined; const actions: Record = { diff --git a/app/views/RoomsListView/components/Header.tsx b/app/views/RoomsListView/components/Header.tsx index 93bdeb12fe3..c50a8ad75a2 100644 --- a/app/views/RoomsListView/components/Header.tsx +++ b/app/views/RoomsListView/components/Header.tsx @@ -4,11 +4,11 @@ import { Touchable } from 'react-native-gesture-handler'; import { showActionSheetRef } from '~/containers/ActionSheet'; import SearchHeader from '~/containers/SearchHeader'; -import I18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useTheme } from '~/theme'; import sharedStyles from '~/views/Styles'; import ServersList from './ServersList'; +import { useRoomsListSubtitle } from '../hooks/useRoomsListSubtitle'; const styles = StyleSheet.create({ container: { @@ -32,13 +32,8 @@ const styles = StyleSheet.create({ // search and searchEnabled need to be props because Header is used on react-navigation, which does not support context const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string) => void; searchEnabled: boolean }) => { - const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading); - const connected = useAppSelector(state => state.meteor.connected); - const isLoggingIn = useAppSelector(state => state.login.isFetching); - const isFetching = useAppSelector(state => state.rooms.isFetching); const serverName = useAppSelector(state => state.settings.Site_Name as string); - const server = useAppSelector(state => state.server.server); - const { status: supportedVersionsStatus } = useAppSelector(state => state.supportedVersions); + const subtitle = useRoomsListSubtitle(); const { colors } = useTheme(); const { fontScale } = useWindowDimensions(); @@ -51,18 +46,6 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string) const height = 37 * fontScale; return ; } - let subtitle; - if (supportedVersionsStatus === 'expired') { - subtitle = I18n.t('Cannot_connect'); - } else if (connecting || isLoggingIn) { - subtitle = I18n.t('Connecting'); - } else if (isFetching) { - subtitle = I18n.t('Updating'); - } else if (!connected) { - subtitle = I18n.t('Waiting_for_network'); - } else { - subtitle = server?.replace(/(^\w+:|^)\/\//, ''); - } return ( { const navigation = useNavigation(); const issuesWithNotifications = useAppSelector(state => state.troubleshootingNotification.issuesWithNotifications); const notificationPresenceCap = useAppSelector(state => state.app.notificationPresenceCap); - const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading || state.login.isFetching); - const connected = useAppSelector(state => state.meteor.connected); - const isFetchingRooms = useAppSelector(state => state.rooms.isFetching); const serverName = useAppSelector(state => state.settings.Site_Name as string); - const server = useAppSelector(state => state.server.server); const { colors } = useTheme(); - const nativeHeaderSubtitle = - supportedVersionsStatus === 'expired' - ? i18n.t('Cannot_connect') - : connecting - ? i18n.t('Connecting') - : isFetchingRooms - ? i18n.t('Updating') - : !connected - ? i18n.t('Waiting_for_network') - : server?.replace(/(^\w+:|^)\/\//, ''); + const nativeHeaderSubtitle = useRoomsListSubtitle(); const [ createPublicChannelPermission, createPrivateChannelPermission, @@ -156,7 +143,6 @@ export const useHeader = () => { const actions = ( [ { - key: 'create', present: canCreateRoom, icon: 'create', accessibilityLabel: i18n.t('Create_new_channel_team_dm_discussion'), @@ -164,7 +150,6 @@ export const useHeader = () => { onPress: goToNewMessage }, { - key: 'push-troubleshoot', present: issuesWithNotifications, icon: 'notification-disabled', accessibilityLabel: i18n.t('Troubleshooting'), @@ -172,7 +157,6 @@ export const useHeader = () => { onPress: navigateToPushTroubleshootView }, { - key: 'directory', present: true, icon: 'directory', accessibilityLabel: i18n.t('Directory'), diff --git a/app/views/RoomsListView/hooks/useRoomsListSubtitle.ts b/app/views/RoomsListView/hooks/useRoomsListSubtitle.ts new file mode 100644 index 00000000000..71e7302de40 --- /dev/null +++ b/app/views/RoomsListView/hooks/useRoomsListSubtitle.ts @@ -0,0 +1,24 @@ +import I18n from '~/i18n'; +import { useAppSelector } from '~/lib/hooks/useAppSelector'; + +export const useRoomsListSubtitle = (): string | undefined => { + const supportedVersionsStatus = useAppSelector(state => state.supportedVersions.status); + const connecting = useAppSelector(state => state.meteor.connecting || state.server.loading || state.login.isFetching); + const connected = useAppSelector(state => state.meteor.connected); + const isFetchingRooms = useAppSelector(state => state.rooms.isFetching); + const server = useAppSelector(state => state.server.server); + + if (supportedVersionsStatus === 'expired') { + return I18n.t('Cannot_connect'); + } + if (connecting) { + return I18n.t('Connecting'); + } + if (isFetchingRooms) { + return I18n.t('Updating'); + } + if (!connected) { + return I18n.t('Waiting_for_network'); + } + return server?.replace(/(^\w+:|^)\/\//, ''); +}; diff --git a/app/views/ThemeView.tsx b/app/views/ThemeView.tsx index ac8bd4f58fe..e32d40d6ce9 100644 --- a/app/views/ThemeView.tsx +++ b/app/views/ThemeView.tsx @@ -100,42 +100,33 @@ const ThemeView = (): ReactElement => { } }; + const renderThemeItems = (themes: ITheme[]) => ( + <> + {themes.map(theme => ( + + onClick(theme)} + testID={`theme-view-${theme.value}`} + /> + + + ))} + + ); + return ( - <> - {themeGroup.map(theme => ( - - onClick(theme)} - testID={`theme-view-${theme.value}`} - /> - - - ))} - + {renderThemeItems(themeGroup)} - <> - {darkGroup.map(theme => ( - - onClick(theme)} - testID={`theme-view-${theme.value}`} - /> - - - ))} - + {renderThemeItems(darkGroup)} From bae4bd8ca60cbbe3639c8f6b6c87af55b11d572a Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 30 Sep 2026 17:20:23 -0300 Subject: [PATCH 118/120] refactor: drive header actions from one native/JS adapter --- .maestro/helpers/wait-header-item.yaml | 23 + .maestro/tests/assorted/broadcast.yaml | 14 +- .../tests/e2ee/flows/create-e2ee-room.yaml | 7 +- .maestro/tests/room/create-dm-group.yaml | 16 +- .maestro/tests/room/create-room.yaml | 30 +- .maestro/tests/room/discussion.yaml | 7 +- .maestro/tests/room/room-actions.yaml | 7 +- .maestro/tests/teams/create-team.yaml | 7 +- .maestro/tests/teams/team.yaml | 52 +- .../tests/teams/utils/create-channel.yaml | 14 +- .../HeaderButton/HeaderButtonItemBadge.tsx | 25 +- .../__tests__/headerActions.test.tsx | 91 ++++ .../helpers/navigation/headerActions.tsx | 129 +++++ app/lib/methods/helpers/navigation/index.ts | 37 +- app/views/CannedResponsesListView/index.tsx | 47 +- app/views/DiscussionsView/index.tsx | 53 +-- app/views/RoomMembersView/index.tsx | 87 +--- .../RightButtons/HeaderCallButton.tsx | 30 -- .../RightButtons/OmnichannelRightButtons.tsx | 99 ---- .../components/RightButtons/RightButtons.tsx | 33 +- .../RightButtons/RoomRightButtons.tsx | 74 --- .../RightButtons/ThreadRightButtons.tsx | 34 -- .../__tests__/HeaderCallButton.test.tsx | 135 ------ .../OmnichannelRightButtons.test.tsx | 190 -------- .../__tests__/RightButtons.test.tsx | 446 ++++++++++++------ .../__tests__/RoomRightButtons.test.tsx | 215 --------- .../__tests__/ThreadRightButtons.test.tsx | 95 ---- .../__tests__/RightButtons.test.tsx | 261 ---------- .../__snapshots__/RightButtons.test.tsx.snap | 259 ---------- .../hooks/__tests__/useHeader.test.tsx | 13 +- .../__tests__/useRoomHeaderActions.test.ts | 438 +++++++++++++++++ .../__tests__/useRoomHeaderRightItems.test.ts | 99 ---- app/views/RoomView/hooks/useHeader.tsx | 14 +- ...rRightItems.ts => useRoomHeaderActions.ts} | 227 ++++----- .../hooks/__tests__/useHeader.test.tsx | 14 +- app/views/RoomsListView/hooks/useHeader.tsx | 202 ++++---- app/views/SelectedUsersView/index.tsx | 12 +- app/views/ShareListView/index.tsx | 53 +-- app/views/TeamChannelsView.tsx | 92 ++-- app/views/ThreadMessagesView/index.tsx | 120 ++--- 40 files changed, 1497 insertions(+), 2304 deletions(-) create mode 100644 .maestro/helpers/wait-header-item.yaml create mode 100644 app/lib/methods/helpers/navigation/__tests__/headerActions.test.tsx create mode 100644 app/lib/methods/helpers/navigation/headerActions.tsx delete mode 100644 app/views/RoomView/components/RightButtons/HeaderCallButton.tsx delete mode 100644 app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx delete mode 100644 app/views/RoomView/components/RightButtons/RoomRightButtons.tsx delete mode 100644 app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx delete mode 100644 app/views/RoomView/components/RightButtons/__tests__/HeaderCallButton.test.tsx delete mode 100644 app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx delete mode 100644 app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx delete mode 100644 app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx delete mode 100644 app/views/RoomView/components/__tests__/RightButtons.test.tsx delete mode 100644 app/views/RoomView/components/__tests__/__snapshots__/RightButtons.test.tsx.snap create mode 100644 app/views/RoomView/hooks/__tests__/useRoomHeaderActions.test.ts delete mode 100644 app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts rename app/views/RoomView/hooks/{useRoomHeaderRightItems.ts => useRoomHeaderActions.ts} (51%) diff --git a/.maestro/helpers/wait-header-item.yaml b/.maestro/helpers/wait-header-item.yaml new file mode 100644 index 00000000000..7046e125b3c --- /dev/null +++ b/.maestro/helpers/wait-header-item.yaml @@ -0,0 +1,23 @@ +appId: ${APP_ID} +name: Wait for header item +tags: + - 'util' + +--- +- runFlow: + when: + platform: Android + commands: + - extendedWaitUntil: + visible: + id: ${ANDROID_ID} + timeout: 60000 + +- runFlow: + when: + platform: iOS + commands: + - extendedWaitUntil: + visible: + text: ${IOS_LABEL} + timeout: 60000 diff --git a/.maestro/tests/assorted/broadcast.yaml b/.maestro/tests/assorted/broadcast.yaml index 633472aa649..7afd49459b2 100644 --- a/.maestro/tests/assorted/broadcast.yaml +++ b/.maestro/tests/assorted/broadcast.yaml @@ -54,10 +54,16 @@ tags: id: 'selected-user-${output.otherUser.username}' timeout: 60000 - runFlow: '../../helpers/close-search.yaml' -- assertVisible: - id: 'selected-users-view-submit' -- tapOn: - id: 'selected-users-view-submit' +- runFlow: + file: '../../helpers/wait-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' - extendedWaitUntil: visible: id: 'create-channel-view' diff --git a/.maestro/tests/e2ee/flows/create-e2ee-room.yaml b/.maestro/tests/e2ee/flows/create-e2ee-room.yaml index 4c88fb8f384..7d698cc833a 100644 --- a/.maestro/tests/e2ee/flows/create-e2ee-room.yaml +++ b/.maestro/tests/e2ee/flows/create-e2ee-room.yaml @@ -36,8 +36,11 @@ tags: - extendedWaitUntil: visible: id: 'selected-user-${USER_TO_ADD}' -- tapOn: - id: 'selected-users-view-submit' +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' - extendedWaitUntil: visible: id: 'create-channel-name' diff --git a/.maestro/tests/room/create-dm-group.yaml b/.maestro/tests/room/create-dm-group.yaml index 340078ba699..d00f676051b 100644 --- a/.maestro/tests/room/create-dm-group.yaml +++ b/.maestro/tests/room/create-dm-group.yaml @@ -68,12 +68,16 @@ tags: timeout: 60000 - tapOn: id: 'select-users-view-item-${output.otherUser.username}' -- extendedWaitUntil: - visible: - id: 'selected-users-view-submit' - timeout: 60000 -- tapOn: - id: 'selected-users-view-submit' +- runFlow: + file: '../../helpers/wait-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Create' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Create' # check Group DM exist - runFlow: diff --git a/.maestro/tests/room/create-room.yaml b/.maestro/tests/room/create-room.yaml index fc8219920eb..ce322467953 100644 --- a/.maestro/tests/room/create-room.yaml +++ b/.maestro/tests/room/create-room.yaml @@ -137,12 +137,16 @@ tags: timeout: 60000 # should navigate to create channel view -- extendedWaitUntil: - visible: - id: 'selected-users-view-submit' - timeout: 60000 -- tapOn: - id: 'selected-users-view-submit' +- runFlow: + file: '../../helpers/wait-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' - extendedWaitUntil: visible: id: 'create-channel-view' @@ -247,8 +251,11 @@ tags: visible: id: 'selected-user-rocket.cat' timeout: 60000 -- tapOn: - id: 'selected-users-view-submit' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' - extendedWaitUntil: visible: id: 'create-channel-view' @@ -306,8 +313,11 @@ tags: visible: id: 'select-users-view' timeout: 60000 -- tapOn: - id: 'selected-users-view-submit' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Skip' - extendedWaitUntil: visible: id: 'create-channel-view' diff --git a/.maestro/tests/room/discussion.yaml b/.maestro/tests/room/discussion.yaml index 380306dec08..da4a072996f 100644 --- a/.maestro/tests/room/discussion.yaml +++ b/.maestro/tests/room/discussion.yaml @@ -60,8 +60,11 @@ tags: visible: id: 'selected-user-${output.selectUser}' timeout: 60000 -- tapOn: - id: 'selected-users-view-submit' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' - extendedWaitUntil: visible: id: 'create-discussion-view' diff --git a/.maestro/tests/room/room-actions.yaml b/.maestro/tests/room/room-actions.yaml index da3f531a434..cb0110a1a34 100644 --- a/.maestro/tests/room/room-actions.yaml +++ b/.maestro/tests/room/room-actions.yaml @@ -379,8 +379,11 @@ tags: visible: id: 'selected-user-${output.otherUser.username}' timeout: 60000 -- tapOn: - id: selected-users-view-submit +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' - runFlow: '../../helpers/go-back.yaml' - extendedWaitUntil: visible: diff --git a/.maestro/tests/teams/create-team.yaml b/.maestro/tests/teams/create-team.yaml index fbc106dd3ff..1567dfdbab0 100644 --- a/.maestro/tests/teams/create-team.yaml +++ b/.maestro/tests/teams/create-team.yaml @@ -28,8 +28,11 @@ tags: id: 'new-message-view-create-team' - assertVisible: id: 'select-users-view' -- tapOn: - id: 'selected-users-view-submit' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Skip' - assertVisible: id: 'create-channel-view' - tapOn: diff --git a/.maestro/tests/teams/team.yaml b/.maestro/tests/teams/team.yaml index 4b64887b3ce..650b443a944 100644 --- a/.maestro/tests/teams/team.yaml +++ b/.maestro/tests/teams/team.yaml @@ -74,9 +74,20 @@ tags: - assertVisible: id: room-header label: Should have actions button -- assertVisible: - id: team-channels-view-create - label: Should have team channels button +- runFlow: + when: + platform: android + commands: + - assertVisible: + id: team-channels-view-create + label: Should have team channels button +- runFlow: + when: + platform: iOS + commands: + - assertVisible: + text: 'Add channel to team' + label: Should have team channels button - runFlow: when: platform: iOS @@ -93,8 +104,11 @@ tags: label: Should have search button # Team Channels Header Usage -- tapOn: - id: team-channels-view-create +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'team-channels-view-create' + IOS_LABEL: 'Add channel to team' - extendedWaitUntil: visible: id: add-channel-team-view @@ -129,8 +143,11 @@ tags: visible: id: selected-user-rocket.cat timeout: 60000 -- tapOn: - id: selected-users-view-submit +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' - extendedWaitUntil: visible: id: create-channel-view @@ -249,8 +266,11 @@ tags: id: team-channels-view timeout: 60000 label: Should navigate to team channels view -- tapOn: - id: team-channels-view-create +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'team-channels-view-create' + IOS_LABEL: 'Add channel to team' - extendedWaitUntil: visible: id: add-channel-team-view @@ -395,10 +415,16 @@ tags: visible: id: selected-user-${output.secondUser.username} timeout: 60000 -- assertVisible: - id: selected-users-view-submit -- tapOn: - id: selected-users-view-submit +- runFlow: + file: '../../helpers/wait-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' +- runFlow: + file: '../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Next' - runFlow: file: '../../helpers/select-header-filter.yaml' diff --git a/.maestro/tests/teams/utils/create-channel.yaml b/.maestro/tests/teams/utils/create-channel.yaml index 63cadb5feaf..18d8139ed92 100644 --- a/.maestro/tests/teams/utils/create-channel.yaml +++ b/.maestro/tests/teams/utils/create-channel.yaml @@ -22,10 +22,16 @@ tags: visible: id: select-users-view timeout: 60000 -- assertVisible: - id: selected-users-view-submit -- tapOn: - id: selected-users-view-submit +- runFlow: + file: '../../../helpers/wait-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Skip' +- runFlow: + file: '../../../helpers/tap-header-item.yaml' + env: + ANDROID_ID: 'selected-users-view-submit' + IOS_LABEL: 'Skip' - extendedWaitUntil: visible: id: create-channel-view diff --git a/app/containers/Header/components/HeaderButton/HeaderButtonItemBadge.tsx b/app/containers/Header/components/HeaderButton/HeaderButtonItemBadge.tsx index c11284c588f..ad5c0bf70a5 100644 --- a/app/containers/Header/components/HeaderButton/HeaderButtonItemBadge.tsx +++ b/app/containers/Header/components/HeaderButton/HeaderButtonItemBadge.tsx @@ -1,7 +1,9 @@ import { type ReactElement } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet, Text, useWindowDimensions, View } from 'react-native'; import UnreadBadge from '~/containers/UnreadBadge'; +import { useTheme } from '~/theme'; +import sharedStyles from '~/views/Styles'; const styles = StyleSheet.create({ badgeContainer: { @@ -12,6 +14,10 @@ const styles = StyleSheet.create({ borderRadius: 10, alignItems: 'center', justifyContent: 'center' + }, + countText: { + fontSize: 10, + ...sharedStyles.textSemibold } }); @@ -20,3 +26,20 @@ export const BadgeUnread = ({ ...props }): ReactElement => ( ); + +export const BadgeCount = ({ value, color }: { value: number; color: string }): ReactElement => { + const { colors } = useTheme(); + const { fontScale } = useWindowDimensions(); + const text = value >= 100 ? '+99' : String(value); + return ( + + + {text} + + + ); +}; diff --git a/app/lib/methods/helpers/navigation/__tests__/headerActions.test.tsx b/app/lib/methods/helpers/navigation/__tests__/headerActions.test.tsx new file mode 100644 index 00000000000..dbbbc578c68 --- /dev/null +++ b/app/lib/methods/helpers/navigation/__tests__/headerActions.test.tsx @@ -0,0 +1,91 @@ +import { fireEvent, render, screen } from '@testing-library/react-native'; +import { type NativeStackHeaderItemButton, type NativeStackHeaderItemMenu } from '@react-navigation/native-stack'; + +import { showActionSheetRef, type TActionSheetOptionsItem } from '~/containers/ActionSheet'; +import { HeaderActions, type IHeaderAction, nativeHeaderItems } from '../headerActions'; + +jest.mock('../headerIcon', () => ({ headerIcon: (name: string) => ({ type: 'image', source: name }) })); +jest.mock('~/containers/ActionSheet', () => ({ showActionSheetRef: jest.fn() })); + +const filterAction: IHeaderAction = { + label: 'Filter', + icon: 'filter', + testID: 'filter', + menu: [ + { label: 'All', checked: true, onPress: jest.fn() }, + { label: 'Close', destructive: true, onPress: jest.fn() } + ] +}; +const searchAction: IHeaderAction = { + label: 'Search', + icon: 'search', + testID: 'search', + legacyHeaderOnly: true, + onPress: jest.fn() +}; + +const openedSheetOptions = () => (showActionSheetRef as jest.Mock).mock.calls[0][0].options as TActionSheetOptionsItem[]; + +describe('nativeHeaderItems', () => { + it('leaves out actions that only exist on the legacy header', () => { + const items = nativeHeaderItems([filterAction, searchAction]) as NativeStackHeaderItemMenu[]; + + expect(items.map(item => item.label)).toEqual(['Filter']); + }); + + it('turns a menu into native menu actions with radio state only on checkable items', () => { + const [menu] = nativeHeaderItems([filterAction]) as NativeStackHeaderItemMenu[]; + + expect(menu.type).toBe('menu'); + expect(menu.icon).toEqual({ type: 'image', source: 'filter' }); + expect(menu.menu.items).toEqual([ + expect.objectContaining({ type: 'action', label: 'All', state: 'on' }), + expect.objectContaining({ type: 'action', label: 'Close', state: undefined, destructive: true }) + ]); + }); + + it('renders a badge without a value as a dot in the given color', () => { + const [button] = nativeHeaderItems([{ label: 'Menu', icon: 'hamburguer', badge: { color: 'red' }, onPress: jest.fn() }]); + + expect((button as NativeStackHeaderItemButton).badge).toEqual({ value: '', style: { backgroundColor: 'red' } }); + }); +}); + +describe('HeaderActions', () => { + beforeEach(() => jest.clearAllMocks()); + + it('renders nothing without actions', () => { + render(); + + expect(screen.toJSON()).toBeNull(); + }); + + it('shows legacy-only actions and uses the label as text when there is no icon', () => { + render(); + + expect(screen.getByTestId('search')).toBeOnTheScreen(); + expect(screen.getByText('Next')).toBeOnTheScreen(); + }); + + it('opens the menu in an action sheet, marking checkable items with a radio', () => { + render(); + + fireEvent.press(screen.getByTestId('filter')); + + const [all, close] = openedSheetOptions(); + expect(all.title).toBe('All'); + expect(all.right).toBeDefined(); + expect(close.right).toBeUndefined(); + expect(close.danger).toBe(true); + }); + + it('caps a numeric badge at +99', () => { + render( + + ); + + expect(screen.getByText('+99')).toBeOnTheScreen(); + }); +}); diff --git a/app/lib/methods/helpers/navigation/headerActions.tsx b/app/lib/methods/helpers/navigation/headerActions.tsx new file mode 100644 index 00000000000..5034967e54e --- /dev/null +++ b/app/lib/methods/helpers/navigation/headerActions.tsx @@ -0,0 +1,129 @@ +import { type ReactElement } from 'react'; +import { + type NativeStackHeaderItem, + type NativeStackHeaderItemMenuAction, + type NativeStackNavigationOptions +} from '@react-navigation/native-stack'; + +import { showActionSheetRef } from '~/containers/ActionSheet'; +import { type TIconsName } from '~/containers/CustomIcon'; +import * as HeaderButton from '~/containers/Header/components/HeaderButton'; +import Radio from '~/containers/Radio'; +import { hasNativeHeaderBar } from '~/lib/methods/helpers/deviceInfo'; +import { headerIcon } from './headerIcon'; + +export interface IHeaderMenuItem { + label: string; + icon?: TIconsName; + checked?: boolean; + destructive?: boolean; + disabled?: boolean; + testID?: string; + onPress: () => void; +} + +export interface IHeaderActionBadge { + value?: number; + color: string; +} + +export interface IHeaderAction { + label: string; + icon?: TIconsName; + testID?: string; + disabled?: boolean; + tintColor?: string; + badge?: IHeaderActionBadge; + legacyHeaderOnly?: boolean; + onPress?: () => void; + menu?: IHeaderMenuItem[]; +} + +const toNativeMenuAction = ({ + label, + icon, + checked, + destructive, + disabled, + onPress +}: IHeaderMenuItem): NativeStackHeaderItemMenuAction => ({ + type: 'action', + label, + icon: icon && headerIcon(icon), + state: checked === undefined ? undefined : checked ? 'on' : 'off', + destructive, + disabled, + onPress +}); + +export const nativeHeaderItems = (actions: IHeaderAction[]): NativeStackHeaderItem[] => + actions + .filter(action => !action.legacyHeaderOnly) + .map(({ label, icon, disabled, tintColor, badge, onPress, menu }): NativeStackHeaderItem => { + const item = { + label, + accessibilityLabel: label, + icon: icon && headerIcon(icon), + disabled, + tintColor, + badge: badge && { value: badge.value ?? '', style: { backgroundColor: badge.color } } + }; + if (menu) { + return { ...item, type: 'menu', menu: { items: menu.map(toNativeMenuAction) } }; + } + return { ...item, type: 'button', onPress: onPress ?? (() => {}) }; + }); + +const showMenu = (menu: IHeaderMenuItem[]) => + showActionSheetRef({ + options: menu.map(({ label, icon, checked, destructive, disabled, testID, onPress }) => ({ + title: label, + icon, + danger: destructive, + enabled: !disabled, + testID, + onPress, + right: checked === undefined ? undefined : () => + })) + }); + +const renderBadge = ({ value, color }: IHeaderActionBadge) => + value === undefined ? : ; + +export const HeaderActions = ({ actions, left = false }: { actions: IHeaderAction[]; left?: boolean }): ReactElement | null => { + if (!actions.length) { + return null; + } + return ( + + {actions.map(({ label, icon, testID, disabled, tintColor, badge, onPress, menu }) => ( + renderBadge(badge) : undefined} + onPress={() => { + onPress?.(); + if (menu) { + showMenu(menu); + } + }} + /> + ))} + + ); +}; + +export const headerRightActions = (actions: IHeaderAction[]): NativeStackNavigationOptions => + hasNativeHeaderBar + ? { headerRight: undefined, unstable_headerRightItems: () => nativeHeaderItems(actions) } + : { headerRight: () => }; + +export const headerLeftActions = (actions: IHeaderAction[]): NativeStackNavigationOptions => + hasNativeHeaderBar + ? { headerLeft: undefined, unstable_headerLeftItems: () => nativeHeaderItems(actions) } + : { headerLeft: () => }; diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 3614e6b58c8..a22fa65631a 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -7,10 +7,9 @@ import { themes } from '~/lib/constants/colors'; import { type TSupportedThemes } from '~/theme'; import sharedStyles from '~/views/Styles'; import Header from '~/containers/Header'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import I18n from '~/i18n'; import { hasNativeHeaderBar } from '~/lib/methods/helpers'; -import { headerIcon } from './headerIcon'; +import { headerLeftActions, headerRightActions } from './headerActions'; export const defaultHeader: NativeStackNavigationOptions = hasNativeHeaderBar ? { @@ -24,21 +23,7 @@ export const outsideHeaderRightLegal = ( navigation: { navigate: (screen: 'LegalView') => void }, testID: string ): NativeStackNavigationOptions => - hasNativeHeaderBar - ? { - unstable_headerRightItems: () => [ - { - type: 'button', - label: I18n.t('More'), - accessibilityLabel: I18n.t('More'), - icon: headerIcon('kebab'), - onPress: () => navigation.navigate('LegalView') - } - ] - } - : { - headerRight: (): ReactElement => createElement(HeaderButton.Legal, { testID, navigation }) - }; + headerRightActions([{ label: I18n.t('More'), icon: 'kebab', testID, onPress: () => navigation.navigate('LegalView') }]); interface IStackedSearchBarOptions { ref?: RefObject; @@ -65,22 +50,8 @@ export const stackedSearchBarOptions = ({ onCancelButtonPress: onCancel ?? (() => onChangeText('')) }); -export const outsideHeaderLeftClose = (onPress: () => void, testID: string): NativeStackNavigationOptions => - hasNativeHeaderBar - ? { - unstable_headerLeftItems: () => [ - { - type: 'button', - label: I18n.t('Close'), - accessibilityLabel: I18n.t('Close'), - icon: headerIcon('close'), - onPress - } - ] - } - : { - headerLeft: (): ReactElement => createElement(HeaderButton.CloseModal, { onPress, testID }) - }; +export const outsideHeaderLeftClose = (onPress: () => void, testID?: string): NativeStackNavigationOptions => + headerLeftActions([{ label: I18n.t('Close'), icon: 'close', testID, onPress }]); export const themedHeader = (theme: TSupportedThemes): NativeStackNavigationOptions => ({ headerStyle: { diff --git a/app/views/CannedResponsesListView/index.tsx b/app/views/CannedResponsesListView/index.tsx index 9d47729e7e4..6b39184b220 100644 --- a/app/views/CannedResponsesListView/index.tsx +++ b/app/views/CannedResponsesListView/index.tsx @@ -8,6 +8,7 @@ import { type SearchBarCommands } from 'react-native-screens'; import database from '~/lib/database'; import I18n from '~/i18n'; import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { headerRightActions } from '~/lib/methods/helpers/navigation/headerActions'; import { hideActionSheetRef, showActionSheetRef } from '~/containers/ActionSheet'; import SafeAreaView from '~/containers/SafeAreaView'; import ActivityIndicator from '~/containers/ActivityIndicator'; @@ -226,27 +227,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView }; const getHeader = (): NativeStackNavigationOptions => { - if (hasNativeHeaderBar) { - return { - headerLeft: () => null, - headerTitle: I18n.t('Canned_Responses'), - headerSearchBarOptions: stackedSearchBarOptions({ - ref: searchBarRef, - onChangeText, - onCancel: () => { - onChangeText(''); - searchBarRef.current?.clearText(); - } - }), - headerRight: () => ( - - - - ) - }; - } - - if (isSearching) { + if (isSearching && !hasNativeHeaderBar) { return { headerLeft: () => ( @@ -264,18 +245,24 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView }; } - const options: NativeStackNavigationOptions = { + return { headerLeft: () => null, headerTitle: I18n.t('Canned_Responses'), - headerRight: () => ( - - - setIsSearching(true)} /> - - ) + ...(hasNativeHeaderBar && { + headerSearchBarOptions: stackedSearchBarOptions({ + ref: searchBarRef, + onChangeText, + onCancel: () => { + onChangeText(''); + searchBarRef.current?.clearText(); + } + }) + }), + ...headerRightActions([ + { label: I18n.t('Filter'), icon: 'filter', onPress: showFilters }, + { label: I18n.t('Search'), icon: 'search', legacyHeaderOnly: true, onPress: () => setIsSearching(true) } + ]) }; - - return options; }; const setHeader = () => { diff --git a/app/views/DiscussionsView/index.tsx b/app/views/DiscussionsView/index.tsx index 06f225182d7..43dc83cb381 100644 --- a/app/views/DiscussionsView/index.tsx +++ b/app/views/DiscussionsView/index.tsx @@ -10,7 +10,8 @@ import { type IMessageFromServer, type TThreadModel } from '~/definitions'; import { type ChatsStackParamList } from '~/stacks/types'; import ActivityIndicator from '~/containers/ActivityIndicator'; import I18n from '~/i18n'; -import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { outsideHeaderLeftClose, stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { headerRightActions } from '~/lib/methods/helpers/navigation/headerActions'; import log from '~/lib/methods/helpers/log'; import { hasNativeHeaderBar, isIOS, useDebounce } from '~/lib/methods/helpers'; import SafeAreaView from '~/containers/SafeAreaView'; @@ -105,29 +106,9 @@ const DiscussionsView = () => { setIsSearching(true); }; - const setHeader = () => { - let options: Partial; - - if (hasNativeHeaderBar) { - options = { - headerLeft: undefined, - headerTitle: I18n.t('Discussions'), - headerSearchBarOptions: stackedSearchBarOptions({ - ref: searchBarRef, - onFocus: onSearchPress, - onChangeText: onSearchChangeText, - onCancel: onCancelSearchPress - }), - headerRight: () => null - }; - if (isMasterDetail) { - options.headerLeft = () => ; - } - return options; - } - - if (isSearching) { - options = { + const setHeader = (): Partial => { + if (isSearching && !hasNativeHeaderBar) { + return { headerLeft: () => ( @@ -138,23 +119,21 @@ const DiscussionsView = () => { ), headerRight: () => null }; - return options; } - options = { - headerLeft: undefined, + return { headerTitle: I18n.t('Discussions'), - headerRight: () => ( - - - - ) + ...(hasNativeHeaderBar && { + headerSearchBarOptions: stackedSearchBarOptions({ + ref: searchBarRef, + onFocus: onSearchPress, + onChangeText: onSearchChangeText, + onCancel: onCancelSearchPress + }) + }), + ...(isMasterDetail ? outsideHeaderLeftClose(() => navigation.pop()) : { headerLeft: undefined }), + ...headerRightActions([{ label: I18n.t('Search'), icon: 'search', legacyHeaderOnly: true, onPress: onSearchPress }]) }; - - if (isMasterDetail) { - options.headerLeft = () => ; - } - return options; }; useEffect(() => { diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index b085075e96d..7d449988e67 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -8,21 +8,19 @@ import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/Actio import { sendLoadingEvent } from '~/containers/Loading'; import ActivityIndicator from '~/containers/ActivityIndicator'; import { CustomIcon, type TIconsName } from '~/containers/CustomIcon'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import SafeAreaView from '~/containers/SafeAreaView'; import RowSeparator from '~/containers/NativeListRow/components/Separator'; import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import SearchBox from '~/containers/SearchBox'; import UserItem from '~/containers/UserItem'; -import Radio from '~/containers/Radio'; import { type IGetRoomRoles, type TSubscriptionModel, type TUserModel } from '~/definitions'; import I18n from '~/i18n'; import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { headerRightActions } from '~/lib/methods/helpers/navigation/headerActions'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { usePermissions } from '~/lib/hooks/usePermissions'; import { compareServerVersion, getRoomTitle, hasNativeHeaderBar, isGroupChat, useDebounce } from '~/lib/methods/helpers'; -import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; import { handleIgnore } from '~/lib/methods/helpers/handleIgnore'; import { showConfirmationAlert } from '~/lib/methods/helpers/info'; import log from '~/lib/methods/helpers/log'; @@ -254,68 +252,33 @@ const RoomMembersView = (): ReactElement => { }; const setHeader = (allUsers: boolean) => { - if (hasNativeHeaderBar) { - navigation.setOptions({ - title: I18n.t('Members'), - headerRight: undefined, - headerTransparent: true, - headerSearchBarOptions: stackedSearchBarOptions({ onChangeText: debounceFilterChange }), - unstable_headerRightItems: () => [ - { - type: 'menu', - label: I18n.t('Filter'), - accessibilityLabel: I18n.t('Filter'), - icon: headerIcon('filter'), - menu: { - items: [ - { - type: 'action', - label: I18n.t('Online'), - state: allUsers ? 'off' : 'on', - onPress: () => toggleStatus(false) - }, - { - type: 'action', - label: I18n.t('All'), - state: allUsers ? 'on' : 'off', - onPress: () => toggleStatus(true) - } - ] - } - } - ] - }); - return; - } navigation.setOptions({ title: I18n.t('Members'), - headerRight: () => ( - - - showActionSheet({ - options: [ - { - title: I18n.t('Online'), - onPress: () => toggleStatus(false), - right: () => , - testID: 'room-members-view-toggle-status-online' - }, - { - title: I18n.t('All'), - onPress: () => toggleStatus(true), - right: () => , - testID: 'room-members-view-toggle-status-all' - } - ], - enableContentPanningGesture: false - }) + ...(hasNativeHeaderBar && { + headerTransparent: true, + headerSearchBarOptions: stackedSearchBarOptions({ onChangeText: debounceFilterChange }) + }), + ...headerRightActions([ + { + label: I18n.t('Filter'), + icon: 'filter', + testID: 'room-members-view-filter', + menu: [ + { + label: I18n.t('Online'), + checked: !allUsers, + testID: 'room-members-view-toggle-status-online', + onPress: () => toggleStatus(false) + }, + { + label: I18n.t('All'), + checked: allUsers, + testID: 'room-members-view-toggle-status-all', + onPress: () => toggleStatus(true) } - testID='room-members-view-filter' - /> - - ) + ] + } + ]) }); }; diff --git a/app/views/RoomView/components/RightButtons/HeaderCallButton.tsx b/app/views/RoomView/components/RightButtons/HeaderCallButton.tsx deleted file mode 100644 index c884c12b757..00000000000 --- a/app/views/RoomView/components/RightButtons/HeaderCallButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { type ReactElement } from 'react'; - -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import { useHeaderCallPress } from './useHeaderCallPress'; - -export const HeaderCallButton = ({ - rid, - disabled, - accessibilityLabel -}: { - rid: string; - disabled: boolean; - accessibilityLabel: string; -}): ReactElement | null => { - const { callPresent, isCallDisabled, onPressCall } = useHeaderCallPress(rid); - - if (!callPresent) { - return null; - } - - return ( - - ); -}; diff --git a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx b/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx deleted file mode 100644 index da8d0a635b8..00000000000 --- a/app/views/RoomView/components/RightButtons/OmnichannelRightButtons.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { type ReactElement } from 'react'; -import { useStore } from 'zustand'; -import { useNavigation } from '@react-navigation/native'; - -import { type TActionSheetOptionsItem, useActionSheet } from '~/containers/ActionSheet'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import i18n from '~/i18n'; -import { showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; -import { events, logEvent } from '~/lib/methods/helpers/log'; -import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; -import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; -import { useSetting } from '~/lib/hooks/useSetting'; -import { returnLivechat } from '~/lib/services/restApi'; -import { type RoomStore } from '~/views/RoomView/definitions'; -import { fromSubscription } from '~/views/RoomView/stores/RoomStoreContext'; -import { useCanPlaceLivechatOnHold } from '~/views/RoomView/hooks/useCanPlaceLivechatOnHold'; -import { navigateToScreen, type TRoomStackNavigation } from '~/views/RoomView/services/navigateToScreen'; -import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; -import { placeLivechatOnHold } from '~/views/RoomView/services/placeLivechatOnHold'; - -interface IOmnichannelRightButtonsProps { - rid: string; - roomStore: RoomStore; -} - -export const OmnichannelRightButtons = ({ rid, roomStore }: IOmnichannelRightButtonsProps): ReactElement => { - const navigation = useNavigation(); - const isMasterDetail = useMasterDetail(); - const { showActionSheet } = useActionSheet(); - - const livechatRequestComment = useSetting('Livechat_request_comment_when_closing_conversation') as boolean; - - const departmentId = useStore( - roomStore, - fromSubscription(room => room.departmentId, undefined) - ); - const canForwardGuest = useStore(roomStore, s => s.canForwardGuest); - const canReturnQueue = useCanReturnQueue(true); - const canPlaceLivechatOnHold = useCanPlaceLivechatOnHold(roomStore); - - const handleReturnLivechat = () => { - showConfirmationAlert({ - message: i18n.t('Would_you_like_to_return_the_inquiry'), - confirmationText: i18n.t('Yes'), - onPress: async () => { - try { - await returnLivechat(rid, departmentId); - } catch (e: any) { - showErrorAlert(e.reason, i18n.t('Oops')); - } - } - }); - }; - - const showMoreActions = () => { - logEvent(events.ROOM_SHOW_MORE_ACTIONS); - const options = [] as TActionSheetOptionsItem[]; - if (canPlaceLivechatOnHold) { - options.push({ - title: i18n.t('Place_chat_on_hold'), - icon: 'pause', - onPress: () => placeLivechatOnHold({ rid, navigation }) - }); - } - - if (canForwardGuest) { - options.push({ - title: i18n.t('Forward_Chat'), - icon: 'chat-forward', - onPress: () => { - navigateToScreen({ navigation, isMasterDetail, screen: 'ForwardLivechatView', params: { rid } }); - } - }); - } - - if (canReturnQueue) { - options.push({ - title: i18n.t('Return_to_waiting_line'), - icon: 'move-to-the-queue', - onPress: () => handleReturnLivechat() - }); - } - - options.push({ - title: i18n.t('Close'), - icon: 'chat-close', - onPress: () => closeLivechat({ rid, departmentId, isMasterDetail, livechatRequestComment, navigation }), - danger: true - }); - - showActionSheet({ options }); - }; - - return ( - - - - ); -}; diff --git a/app/views/RoomView/components/RightButtons/RightButtons.tsx b/app/views/RoomView/components/RightButtons/RightButtons.tsx index aac8363051d..04c8537edc4 100644 --- a/app/views/RoomView/components/RightButtons/RightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/RightButtons.tsx @@ -1,13 +1,8 @@ import { type ReactElement } from 'react'; -import { useStore } from 'zustand'; -import { useShallow } from 'zustand/react/shallow'; +import { HeaderActions } from '~/lib/methods/helpers/navigation/headerActions'; import { type RoomStore } from '~/views/RoomView/definitions'; -import { fromSubscription } from '~/views/RoomView/stores/RoomStoreContext'; -import { getRoomHeaderMode } from '~/views/RoomView/helpers/getRoomHeaderMode'; -import { OmnichannelRightButtons } from './OmnichannelRightButtons'; -import { RoomRightButtons } from './RoomRightButtons'; -import { ThreadRightButtons } from './ThreadRightButtons'; +import { useRoomHeaderActions } from '~/views/RoomView/hooks/useRoomHeaderActions'; interface IRightButtonsProps { rid?: string; @@ -16,28 +11,8 @@ interface IRightButtonsProps { } const RightButtons = ({ rid, tmid, roomStore }: IRightButtonsProps): ReactElement | null => { - const { t, status, membership } = useStore( - roomStore, - useShallow(s => ({ - t: s.room.t, - status: fromSubscription(room => room.status, undefined)(s), - membership: s.membership - })) - ); - - const mode = getRoomHeaderMode({ rid, tmid, t, status, membership }); - - if (!rid || mode === 'none') { - return null; - } - if (mode === 'omnichannel') { - return ; - } - if (mode === 'thread' && tmid) { - return ; - } - - return ; + const actions = useRoomHeaderActions(rid, tmid, roomStore); + return ; }; export default RightButtons; diff --git a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx b/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx deleted file mode 100644 index d28c9b69013..00000000000 --- a/app/views/RoomView/components/RightButtons/RoomRightButtons.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { type ReactElement } from 'react'; - -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import i18n from '~/i18n'; -import { useTheme } from '~/theme'; -import { type RoomStore } from '~/views/RoomView/definitions'; -import { HeaderCallButton } from './HeaderCallButton'; -import { useRoomRightButtonsData } from './useRoomRightButtonsData'; - -interface IRoomRightButtonsProps { - rid: string; - roomStore: RoomStore; -} - -export const RoomRightButtons = ({ rid, roomStore }: IRoomRightButtonsProps): ReactElement => { - const { colors } = useTheme(); - const { - threadsEnabled, - issuesWithNotifications, - disableNotifications, - hasE2EEWarning, - canToggleEncryption, - isSelfDm, - tunread, - tunreadUser, - tunreadGroup, - callAccessibilityLabel, - goThreadsView, - navigateToNotificationOrPushTroubleshoot, - goSearchView, - goE2EEToggleRoomView, - threadsAccessibilityLabel - } = useRoomRightButtonsData(rid, roomStore); - - return ( - - {hasE2EEWarning ? ( - - ) : null} - {issuesWithNotifications || disableNotifications ? ( - - ) : null} - {!isSelfDm ? : null} - {threadsEnabled ? ( - } - disabled={hasE2EEWarning} - /> - ) : null} - - - ); -}; diff --git a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx b/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx deleted file mode 100644 index f1029284d86..00000000000 --- a/app/views/RoomView/components/RightButtons/ThreadRightButtons.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { type ReactElement } from 'react'; - -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; -import i18n from '~/i18n'; -import { events, logEvent } from '~/lib/methods/helpers/log'; -import { useAppSelector } from '~/lib/hooks/useAppSelector'; -import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; -import { getUserSelector } from '~/selectors/login'; -import { useThreadFollowing } from '~/views/RoomView/hooks/useThreadFollowing'; - -interface IThreadRightButtonsProps { - tmid: string; -} - -export const ThreadRightButtons = ({ tmid }: IThreadRightButtonsProps): ReactElement => { - const userId = useAppSelector(state => getUserSelector(state).id); - const isFollowingThread = useThreadFollowing(tmid, userId); - - const onToggleFollowThread = () => { - logEvent(events.ROOM_TOGGLE_FOLLOW_THREADS); - toggleFollowThread(tmid, isFollowingThread); - }; - - return ( - - - - ); -}; diff --git a/app/views/RoomView/components/RightButtons/__tests__/HeaderCallButton.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/HeaderCallButton.test.tsx deleted file mode 100644 index 9fb8b7d378d..00000000000 --- a/app/views/RoomView/components/RightButtons/__tests__/HeaderCallButton.test.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { act, fireEvent, render, screen } from '@testing-library/react-native'; - -import { HeaderCallButton } from '../HeaderCallButton'; - -const mockVideoConf = { - showInitCallActionSheet: jest.fn(), - callEnabled: false, - disabledTooltip: false -}; -jest.mock('~/lib/hooks/useVideoConf', () => ({ - useVideoConf: () => mockVideoConf -})); - -const mockMediaCall = { - openNewMediaCall: jest.fn(), - startCallImmediate: jest.fn(), - hasMediaCallPermission: false, - isInActiveCall: false -}; -jest.mock('~/lib/hooks/useNewMediaCall', () => ({ - useNewMediaCall: () => mockMediaCall -})); - -jest.mock('~/containers/Header/components/HeaderButton', () => { - const ReactActual = jest.requireActual('react'); - return { - Item: ({ - accessibilityLabel, - disabled, - iconName, - onPress, - testID - }: { - accessibilityLabel: string; - disabled: boolean; - iconName: string; - onPress: () => void; - testID: string; - }) => ReactActual.createElement('Item', { accessibilityLabel, disabled, iconName, onPress, testID }) - }; -}); - -const renderCallButton = (disabled = false) => - render(); - -describe('HeaderCallButton', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.useFakeTimers(); - mockVideoConf.callEnabled = false; - mockVideoConf.disabledTooltip = false; - mockMediaCall.hasMediaCallPermission = false; - mockMediaCall.isInActiveCall = false; - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it('renders nothing without media call permission and with calls disabled', () => { - renderCallButton(); - - expect(screen.queryByTestId('room-view-header-call')).not.toBeOnTheScreen(); - }); - - it('renders the media call button with media call permission', () => { - mockMediaCall.hasMediaCallPermission = true; - - renderCallButton(); - - const callButton = screen.getByTestId('room-view-header-call'); - expect(callButton).toHaveProp('iconName', 'phone'); - expect(callButton).toHaveProp('accessibilityLabel', 'Call Room Title'); - expect(callButton).toHaveProp('disabled', false); - }); - - it('disables the media call button during an active call', () => { - mockMediaCall.hasMediaCallPermission = true; - mockMediaCall.isInActiveCall = true; - - renderCallButton(); - - expect(screen.getByTestId('room-view-header-call')).toHaveProp('disabled', true); - }); - - it('opens the media call sheet after the double tap window on a single tap', () => { - mockMediaCall.hasMediaCallPermission = true; - - renderCallButton(); - fireEvent.press(screen.getByTestId('room-view-header-call')); - - expect(mockMediaCall.openNewMediaCall).not.toHaveBeenCalled(); - act(() => jest.advanceTimersByTime(300)); - expect(mockMediaCall.openNewMediaCall).toHaveBeenCalled(); - expect(mockMediaCall.startCallImmediate).not.toHaveBeenCalled(); - }); - - it('starts the call immediately on a double tap', () => { - mockMediaCall.hasMediaCallPermission = true; - - renderCallButton(); - fireEvent.press(screen.getByTestId('room-view-header-call')); - fireEvent.press(screen.getByTestId('room-view-header-call')); - - expect(mockMediaCall.startCallImmediate).toHaveBeenCalled(); - act(() => jest.advanceTimersByTime(300)); - expect(mockMediaCall.openNewMediaCall).not.toHaveBeenCalled(); - }); - - it('shows the video conference sheet when calls are enabled without media call permission', () => { - mockVideoConf.callEnabled = true; - - renderCallButton(); - fireEvent.press(screen.getByTestId('room-view-header-call')); - - expect(mockVideoConf.showInitCallActionSheet).toHaveBeenCalled(); - }); - - it('disables the video conference button when the tooltip is disabled', () => { - mockVideoConf.callEnabled = true; - mockVideoConf.disabledTooltip = true; - - renderCallButton(); - - expect(screen.getByTestId('room-view-header-call')).toHaveProp('disabled', true); - }); - - it('disables the video conference button when the header disables it', () => { - mockVideoConf.callEnabled = true; - - renderCallButton(true); - - expect(screen.getByTestId('room-view-header-call')).toHaveProp('disabled', true); - }); -}); diff --git a/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx deleted file mode 100644 index e8579d87263..00000000000 --- a/app/views/RoomView/components/RightButtons/__tests__/OmnichannelRightButtons.test.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react-native'; - -import { type TActionSheetOptionsItem } from '~/containers/ActionSheet'; -import { showConfirmationAlert } from '~/lib/methods/helpers'; -import { returnLivechat } from '~/lib/services/restApi'; -import { type RoomStore } from '~/views/RoomView/definitions'; -import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; -import { placeLivechatOnHold } from '~/views/RoomView/services/placeLivechatOnHold'; -import { OmnichannelRightButtons } from '../OmnichannelRightButtons'; - -const mockNavigation = { navigate: jest.fn(), push: jest.fn() }; -jest.mock('@react-navigation/native', () => ({ - useNavigation: () => mockNavigation -})); - -const mockShowActionSheet = jest.fn(); -jest.mock('~/containers/ActionSheet', () => ({ - useActionSheet: () => ({ showActionSheet: mockShowActionSheet }) -})); - -let mockIsMasterDetail = false; -jest.mock('~/lib/hooks/useMasterDetail', () => ({ - useMasterDetail: () => mockIsMasterDetail -})); - -let mockLivechatRequestComment = false; -jest.mock('~/lib/hooks/useSetting', () => ({ - useSetting: () => mockLivechatRequestComment -})); - -let mockRoomState = { - room: { rid: 'rid-1', t: 'l', id: 'rid-1', departmentId: 'department-1' } as Record, - canForwardGuest: false -}; -jest.mock('zustand', () => ({ - useStore: (_store: unknown, selector: (state: typeof mockRoomState) => unknown) => selector(mockRoomState) -})); - -let mockCanReturnQueue = false; -jest.mock('~/ee/omnichannel/hooks/useCanReturnQueue', () => ({ - useCanReturnQueue: () => mockCanReturnQueue -})); - -let mockCanPlaceLivechatOnHold = false; -jest.mock('~/views/RoomView/hooks/useCanPlaceLivechatOnHold', () => ({ - useCanPlaceLivechatOnHold: () => mockCanPlaceLivechatOnHold -})); - -jest.mock('~/views/RoomView/services/closeLivechat', () => ({ closeLivechat: jest.fn() })); -jest.mock('~/views/RoomView/services/placeLivechatOnHold', () => ({ placeLivechatOnHold: jest.fn() })); -jest.mock('~/lib/services/restApi', () => ({ returnLivechat: jest.fn() })); -jest.mock('~/lib/methods/helpers', () => ({ - ...jest.requireActual('~/lib/methods/helpers'), - showConfirmationAlert: jest.fn(), - showErrorAlert: jest.fn() -})); - -jest.mock('~/containers/Header/components/HeaderButton', () => { - const ReactActual = jest.requireActual('react'); - return { - Container: ({ children }: { children: unknown }) => ReactActual.createElement('Container', null, children), - Item: ({ iconName, onPress, testID }: { iconName: string; onPress: () => void; testID: string }) => - ReactActual.createElement('Item', { iconName, onPress, testID }) - }; -}); - -const roomStore = {} as RoomStore; - -const openKebab = (): TActionSheetOptionsItem[] => { - render(); - fireEvent.press(screen.getByTestId('room-view-header-omnichannel-kebab')); - return mockShowActionSheet.mock.calls[0][0].options as TActionSheetOptionsItem[]; -}; - -const titlesOf = (options: TActionSheetOptionsItem[]) => options.map(option => option.title); - -describe('OmnichannelRightButtons', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockIsMasterDetail = false; - mockLivechatRequestComment = false; - mockRoomState = { - room: { rid: 'rid-1', t: 'l', id: 'rid-1', departmentId: 'department-1' }, - canForwardGuest: false - }; - mockCanReturnQueue = false; - mockCanPlaceLivechatOnHold = false; - }); - - it('renders the kebab button', () => { - render(); - - expect(screen.getByTestId('room-view-header-omnichannel-kebab')).toHaveProp('iconName', 'kebab'); - }); - - it('offers only the close option when no capability is granted', () => { - const options = openKebab(); - - expect(titlesOf(options)).toEqual(['Close']); - expect(options[0].icon).toBe('chat-close'); - expect(options[0].danger).toBe(true); - }); - - it('offers the on-hold option when the chat can be placed on hold', () => { - mockCanPlaceLivechatOnHold = true; - - const options = openKebab(); - - expect(titlesOf(options)).toEqual(['Place chat on hold', 'Close']); - expect(options[0].icon).toBe('pause'); - }); - - it('offers the forward option when the agent can forward the guest', () => { - mockRoomState = { ...mockRoomState, canForwardGuest: true }; - - const options = openKebab(); - - expect(titlesOf(options)).toEqual(['Forward chat', 'Close']); - expect(options[0].icon).toBe('chat-forward'); - }); - - it('offers the return-to-queue option when the agent can return to the queue', () => { - mockCanReturnQueue = true; - - const options = openKebab(); - - expect(titlesOf(options)).toEqual(['Return to waiting line', 'Close']); - expect(options[0].icon).toBe('move-to-the-queue'); - }); - - it('offers every option in order when all capabilities are granted', () => { - mockCanPlaceLivechatOnHold = true; - mockCanReturnQueue = true; - mockRoomState = { ...mockRoomState, canForwardGuest: true }; - - expect(titlesOf(openKebab())).toEqual(['Place chat on hold', 'Forward chat', 'Return to waiting line', 'Close']); - }); - - it('places the chat on hold through the service', () => { - mockCanPlaceLivechatOnHold = true; - - openKebab()[0].onPress?.(); - - expect(placeLivechatOnHold).toHaveBeenCalledWith({ rid: 'rid-1', navigation: mockNavigation }); - }); - - it('navigates to the forward screen on stack mode', () => { - mockRoomState = { ...mockRoomState, canForwardGuest: true }; - - openKebab()[0].onPress?.(); - - expect(mockNavigation.navigate).toHaveBeenCalledWith('ForwardLivechatView', { rid: 'rid-1' }); - }); - - it('navigates to the forward screen through the modal stack on master-detail mode', () => { - mockRoomState = { ...mockRoomState, canForwardGuest: true }; - mockIsMasterDetail = true; - - openKebab()[0].onPress?.(); - - expect(mockNavigation.navigate).toHaveBeenCalledWith('ModalStackNavigator', { - screen: 'ForwardLivechatView', - params: { rid: 'rid-1' } - }); - }); - - it('returns the inquiry only after the confirmation is accepted', async () => { - mockCanReturnQueue = true; - - openKebab()[0].onPress?.(); - - expect(returnLivechat).not.toHaveBeenCalled(); - await (showConfirmationAlert as jest.Mock).mock.calls[0][0].onPress(); - expect(returnLivechat).toHaveBeenCalledWith('rid-1', 'department-1'); - }); - - it('closes the chat with the room department and the request comment setting', () => { - mockLivechatRequestComment = true; - - openKebab()[0].onPress?.(); - - expect(closeLivechat).toHaveBeenCalledWith({ - rid: 'rid-1', - departmentId: 'department-1', - isMasterDetail: false, - livechatRequestComment: true, - navigation: mockNavigation - }); - }); -}); diff --git a/app/views/RoomView/components/RightButtons/__tests__/RightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/RightButtons.test.tsx index c2708e826c0..a9b5fa9e1d0 100644 --- a/app/views/RoomView/components/RightButtons/__tests__/RightButtons.test.tsx +++ b/app/views/RoomView/components/RightButtons/__tests__/RightButtons.test.tsx @@ -1,193 +1,361 @@ -import { act, render, screen } from '@testing-library/react-native'; +import { act, fireEvent, render, screen } from '@testing-library/react-native'; import { createStore } from 'zustand'; +import { showActionSheetRef, type TActionSheetOptionsItem } from '~/containers/ActionSheet'; +import { events, logEvent } from '~/lib/methods/helpers/log'; +import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; import { type RoomMembership, type RoomStore } from '~/views/RoomView/definitions'; +import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; import RightButtons from '../RightButtons'; -jest.mock('../OmnichannelRightButtons', () => { - const ReactActual = jest.requireActual('react'); - const mounts = { count: 0 }; - return { - mounts, - OmnichannelRightButtons: ({ rid }: { rid: string }) => { - ReactActual.useEffect(() => { - mounts.count += 1; - }, []); - return ReactActual.createElement('OmnichannelRightButtons', { rid, testID: 'omnichannel-right-buttons-stub' }); - } - }; -}); - -jest.mock('../ThreadRightButtons', () => { - const ReactActual = jest.requireActual('react'); - const mounts = { count: 0 }; - return { - mounts, - ThreadRightButtons: ({ tmid }: { tmid: string }) => { - ReactActual.useEffect(() => { - mounts.count += 1; - }, []); - return ReactActual.createElement('ThreadRightButtons', { tmid, testID: 'thread-right-buttons-stub' }); - } - }; -}); - -jest.mock('../RoomRightButtons', () => { +const mockNavigation = { navigate: jest.fn(), push: jest.fn() }; +jest.mock('@react-navigation/native', () => ({ useNavigation: () => mockNavigation })); +jest.mock('~/containers/ActionSheet', () => ({ showActionSheetRef: jest.fn() })); + +let mockIsMasterDetail = false; +jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => mockIsMasterDetail })); +jest.mock('~/theme', () => ({ useTheme: () => ({ theme: 'light', colors: { fontDanger: '#f00' } }) })); +jest.mock('~/lib/helpers/getRoomAccessibilityLabel', () => ({ __esModule: true, default: () => 'label' })); +jest.mock('~/lib/methods/helpers', () => ({ + ...jest.requireActual('~/lib/methods/helpers'), + getRoomTitle: () => 'Room Title', + isGroupChat: () => false, + hasNativeHeaderBar: false +})); +jest.mock('~/lib/methods/helpers/log', () => ({ + ...jest.requireActual('~/lib/methods/helpers/log'), + logEvent: jest.fn() +})); + +const mockVideoConf = { showInitCallActionSheet: jest.fn(), callEnabled: true, disabledTooltip: false }; +jest.mock('~/lib/hooks/useVideoConf', () => ({ useVideoConf: () => mockVideoConf })); +jest.mock('~/lib/hooks/useNewMediaCall', () => ({ + useNewMediaCall: () => ({ + openNewMediaCall: jest.fn(), + startCallImmediate: jest.fn(), + hasMediaCallPermission: false, + isInActiveCall: false + }) +})); + +let mockAppState = { + login: { user: { id: 'u1', username: 'user', token: 'tok' } }, + settings: { Threads_enabled: true, Livechat_request_comment_when_closing_conversation: false }, + troubleshootingNotification: { issuesWithNotifications: false } +}; +jest.mock('~/lib/hooks/useAppSelector', () => ({ + useAppSelector: (selector: (state: typeof mockAppState) => unknown) => selector(mockAppState) +})); +jest.mock('~/ee/omnichannel/hooks/useCanReturnQueue', () => ({ useCanReturnQueue: () => false })); +jest.mock('~/views/RoomView/hooks/useCanPlaceLivechatOnHold', () => ({ useCanPlaceLivechatOnHold: () => false })); +jest.mock('~/views/RoomView/services/closeLivechat', () => ({ closeLivechat: jest.fn() })); +jest.mock('~/lib/methods/toggleFollowThread', () => ({ toggleFollowThread: jest.fn() })); + +let mockE2EEStatus = { showMissingE2EEKey: false, showE2EEDisabledRoom: false, hasE2EEWarning: false }; +jest.mock('~/views/RoomView/hooks/useE2EEStatus', () => ({ useE2EEStatus: () => mockE2EEStatus })); + +let mockHeaderHooks = { + isFollowingThread: false, + tunread: [] as string[], + tunreadUser: [] as string[], + tunreadGroup: [] as string[], + isSelfDm: false, + canToggleEncryption: false +}; +jest.mock('~/views/RoomView/hooks/useThreadFollowing', () => ({ useThreadFollowing: () => mockHeaderHooks.isFollowingThread })); +jest.mock('~/views/RoomView/hooks/useSubscriptionUnreads', () => ({ + useSubscriptionUnreads: () => { + const { tunread, tunreadUser, tunreadGroup, isSelfDm } = mockHeaderHooks; + return { tunread, tunreadUser, tunreadGroup, isSelfDm }; + } +})); +jest.mock('~/lib/hooks/usePermissions', () => ({ usePermissions: () => [mockHeaderHooks.canToggleEncryption] })); + +jest.mock('~/containers/Header/components/HeaderButton', () => { const ReactActual = jest.requireActual('react'); - const mounts = { count: 0 }; return { - mounts, - RoomRightButtons: ({ rid }: { rid: string }) => { - ReactActual.useEffect(() => { - mounts.count += 1; - }, []); - return ReactActual.createElement('RoomRightButtons', { rid, testID: 'room-right-buttons-stub' }); - } + Container: ({ children }: { children: unknown }) => ReactActual.createElement('Container', null, children), + Item: ({ + accessibilityLabel, + badge, + color, + disabled, + iconName, + onPress, + testID + }: { + accessibilityLabel?: string; + badge?: () => unknown; + color?: string; + disabled?: boolean; + iconName?: string; + onPress: () => void; + testID?: string; + }) => ReactActual.createElement('Item', { accessibilityLabel, badge, color, disabled, iconName, onPress, testID }), + BadgeWarn: ({ color }: { color: string }) => ReactActual.createElement('BadgeWarn', { color }), + BadgeCount: ({ value, color }: { value: number; color: string }) => ReactActual.createElement('BadgeCount', { value, color }) }; }); -const omnichannelMock = jest.requireMock('../OmnichannelRightButtons') as { mounts: { count: number } }; -const threadMock = jest.requireMock('../ThreadRightButtons') as { mounts: { count: number } }; -const roomMock = jest.requireMock('../RoomRightButtons') as { mounts: { count: number } }; - -const stubIDs = ['omnichannel-right-buttons-stub', 'thread-right-buttons-stub', 'room-right-buttons-stub']; - -const expectOnlyStub = (present?: string) => { - stubIDs.forEach(id => { - if (id === present) { - expect(screen.getByTestId(id)).toBeOnTheScreen(); - } else { - expect(screen.queryByTestId(id)).not.toBeOnTheScreen(); - } - }); +const allTestIDs = [ + 'room-view-search', + 'room-view-header-threads', + 'room-view-header-call', + 'room-view-header-encryption', + 'room-view-push-troubleshoot', + 'room-view-header-omnichannel-kebab', + 'room-view-header-follow', + 'room-view-header-unfollow' +]; + +const expectOnly = (present: string[]) => { + present.forEach(id => expect(screen.getByTestId(id)).toBeOnTheScreen()); + allTestIDs.filter(id => !present.includes(id)).forEach(id => expect(screen.queryByTestId(id)).not.toBeOnTheScreen()); }; +const renderedTestIDs = () => screen.UNSAFE_queryAllByType('Item' as never).map(item => item.props.testID); + const createRoomStore = (room: Record, membership: RoomMembership = 'subscribed') => { - const store = createStore(() => ({ room: { id: 'sub-1', ...room }, membership })); + const store = createStore(() => ({ + room: { id: 'sub-1', rid: 'rid-1', name: 'general', ...room }, + membership, + canForwardGuest: false + })); return store as typeof store & RoomStore; }; -describe('RightButtons routing', () => { +const ROOM_BUTTONS = ['room-view-header-call', 'room-view-header-threads', 'room-view-search']; + +describe('RightButtons', () => { beforeEach(() => { - omnichannelMock.mounts.count = 0; - threadMock.mounts.count = 0; - roomMock.mounts.count = 0; + jest.clearAllMocks(); + mockIsMasterDetail = false; + mockVideoConf.callEnabled = true; + mockAppState = { + login: { user: { id: 'u1', username: 'user', token: 'tok' } }, + settings: { Threads_enabled: true, Livechat_request_comment_when_closing_conversation: false }, + troubleshootingNotification: { issuesWithNotifications: false } + }; + mockE2EEStatus = { showMissingE2EEKey: false, showE2EEDisabledRoom: false, hasE2EEWarning: false }; + mockHeaderHooks = { + isFollowingThread: false, + tunread: [], + tunreadUser: [], + tunreadGroup: [], + isSelfDm: false, + canToggleEncryption: false + }; }); - it('renders nothing without a rid', () => { - const { toJSON } = render(); + describe('routing', () => { + it('renders nothing without a rid', () => { + const { toJSON } = render(); - expect(toJSON()).toBeNull(); - expectOnlyStub(); - }); + expect(toJSON()).toBeNull(); + }); - it('renders nothing for an invited room', () => { - const { toJSON } = render(); + it.each([ + ['an invited room', { t: 'c' }, 'invited'], + ['a queued omnichannel room', { t: 'l', status: 'queued' }, 'subscribed'], + ['an omnichannel room still in the preview window', { t: 'l' }, 'preview'], + ['an invited omnichannel room', { t: 'l' }, 'invited'] + ])('renders nothing for %s', (_case, room, membership) => { + const { toJSON } = render(); - expect(toJSON()).toBeNull(); - expectOnlyStub(); - }); + expect(toJSON()).toBeNull(); + }); - it('renders nothing for a queued omnichannel room', () => { - const { toJSON } = render( - - ); + it('renders only the kebab for an active omnichannel room even with a tmid', () => { + render(); - expect(toJSON()).toBeNull(); - expectOnlyStub(); - }); + expectOnly(['room-view-header-omnichannel-kebab']); + }); - it('renders nothing for an omnichannel room still in the preview window', () => { - const store = createStore(() => ({ room: { rid: 'rid-1', t: 'l' }, membership: 'preview' as RoomMembership })); + it('renders the thread buttons when a tmid is given', () => { + render(); - render(); + expectOnly(['room-view-header-follow']); + }); - expectOnlyStub(); - }); + it('renders call, threads and search in order for a regular channel', () => { + render(); - it('renders nothing for an invited omnichannel room before the queued check', () => { - render(); + expect(renderedTestIDs()).toEqual(ROOM_BUTTONS); + }); - expectOnlyStub(); - }); + it('swaps the room buttons for the omnichannel kebab when the room type changes in place', () => { + const roomStore = createRoomStore({ t: 'c' }); + render(); + expectOnly(ROOM_BUTTONS); - it('renders the omnichannel buttons for an active omnichannel room even with a tmid', () => { - render(); + act(() => roomStore.setState({ room: { id: 'sub-1', rid: 'rid-1', t: 'l' } as never })); - expectOnlyStub('omnichannel-right-buttons-stub'); - expect(screen.getByTestId('omnichannel-right-buttons-stub')).toHaveProp('rid', 'rid-1'); - }); + expectOnly(['room-view-header-omnichannel-kebab']); + }); - it('renders the thread buttons when a tmid is given', () => { - render(); + it('switches to the thread buttons when a tmid appears and back when it is cleared', () => { + const roomStore = createRoomStore({ t: 'c' }); + render(); - expectOnlyStub('thread-right-buttons-stub'); - expect(screen.getByTestId('thread-right-buttons-stub')).toHaveProp('tmid', 'tmid-1'); - }); + screen.rerender(); + expectOnly(['room-view-header-follow']); - it('renders the room buttons for a regular room', () => { - render(); + screen.rerender(); + expectOnly(ROOM_BUTTONS); + }); - expectOnlyStub('room-right-buttons-stub'); - expect(screen.getByTestId('room-right-buttons-stub')).toHaveProp('rid', 'rid-1'); + it.each([ + ['c', undefined, 'c', 'INVITED', []], + ['c', 'INVITED', 'c', undefined, ROOM_BUTTONS], + ['l', undefined, 'l', 'queued', []], + ['l', 'queued', 'l', undefined, ['room-view-header-omnichannel-kebab']], + ['c', undefined, 'l', undefined, ['room-view-header-omnichannel-kebab']] + ])('updates buttons when the same Room changes from %s/%s to %s/%s', (t, status, nextType, nextStatus, expected) => { + const room = { id: 'sub-1', rid: 'rid-1', t, status }; + const roomStore = createRoomStore(room, status === 'INVITED' ? 'invited' : 'subscribed'); + render(); + + act(() => { + Object.assign(room, { t: nextType, status: nextStatus }); + roomStore.setState({ room: room as never, membership: nextStatus === 'INVITED' ? 'invited' : 'subscribed' }); + }); + + expectOnly(expected); + }); }); - it('swaps the room buttons for the omnichannel buttons when the room type changes in place', () => { - const roomStore = createRoomStore({ rid: 'rid-1', t: 'c' }); - render(); + describe('room', () => { + it('enables encryption while the e2ee warning disables the other buttons', () => { + mockE2EEStatus = { showMissingE2EEKey: true, showE2EEDisabledRoom: false, hasE2EEWarning: true }; + mockHeaderHooks = { ...mockHeaderHooks, canToggleEncryption: true }; - expectOnlyStub('room-right-buttons-stub'); - expect(roomMock.mounts.count).toBe(1); + render(); - act(() => roomStore.setState({ room: { rid: 'rid-1', t: 'l' } })); + expectOnly(['room-view-header-encryption', ...ROOM_BUTTONS]); + expect(screen.getByTestId('room-view-header-encryption')).toHaveProp('disabled', false); + expect(screen.getByTestId('room-view-search')).toHaveProp('disabled', true); + }); - expectOnlyStub('omnichannel-right-buttons-stub'); - expect(omnichannelMock.mounts.count).toBe(1); - expect(roomMock.mounts.count).toBe(1); - }); + it.each([ + ['the user cannot toggle encryption', { showMissingE2EEKey: true, showE2EEDisabledRoom: false }], + ['the room has e2ee disabled', { showMissingE2EEKey: false, showE2EEDisabledRoom: true }] + ])('disables the encryption button when %s', (_case, status) => { + mockE2EEStatus = { ...status, hasE2EEWarning: true }; - it('remounts into the thread buttons when a tmid appears', () => { - const roomStore = createRoomStore({ rid: 'rid-1', t: 'c' }); - render(); + render(); - expectOnlyStub('room-right-buttons-stub'); - expect(roomMock.mounts.count).toBe(1); + expect(screen.getByTestId('room-view-header-encryption')).toHaveProp('disabled', true); + }); - screen.rerender(); + it('navigates to the encryption toggle', () => { + mockE2EEStatus = { showMissingE2EEKey: true, showE2EEDisabledRoom: false, hasE2EEWarning: true }; + mockHeaderHooks = { ...mockHeaderHooks, canToggleEncryption: true }; + render(); - expectOnlyStub('thread-right-buttons-stub'); - expect(threadMock.mounts.count).toBe(1); - expect(roomMock.mounts.count).toBe(1); - }); + fireEvent.press(screen.getByTestId('room-view-header-encryption')); - it.each([ - ['c', undefined, 'c', 'INVITED', undefined], - ['c', 'INVITED', 'c', undefined, 'room-right-buttons-stub'], - ['l', undefined, 'l', 'queued', undefined], - ['l', 'queued', 'l', undefined, 'omnichannel-right-buttons-stub'], - ['c', undefined, 'l', undefined, 'omnichannel-right-buttons-stub'] - ])('updates buttons when the same Room changes from %s/%s to %s/%s', (t, status, nextType, nextStatus, expected) => { - const room = { id: 'sub-1', rid: 'rid-1', t, status }; - const roomStore = createRoomStore(room, status === 'INVITED' ? 'invited' : 'subscribed'); - render(); + expect(logEvent).toHaveBeenCalledTimes(1); + expect(logEvent).toHaveBeenCalledWith(events.ROOM_GO_E2EE); + expect(mockNavigation.navigate).toHaveBeenCalledWith('E2EEToggleRoomView', { rid: 'rid-1' }); + }); + + it('tints the push troubleshoot button when there are notification issues', () => { + mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; + + render(); + + expect(renderedTestIDs()).toEqual(['room-view-push-troubleshoot', ...ROOM_BUTTONS]); + expect(screen.getByTestId('room-view-push-troubleshoot')).toHaveProp('color', '#f00'); + }); + + it('shows an untinted push troubleshoot button when notifications are disabled for the room', () => { + render(); + + expect(screen.getByTestId('room-view-push-troubleshoot')).toHaveProp('color', undefined); + }); + + it.each([false, true])('routes notification issues to push troubleshooting (master-detail: %s)', isMasterDetail => { + mockIsMasterDetail = isMasterDetail; + mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; + render(); + + fireEvent.press(screen.getByTestId('room-view-push-troubleshoot')); + + expect(mockNavigation.navigate).toHaveBeenCalledWith( + ...(isMasterDetail + ? ['ModalStackNavigator', { screen: 'PushTroubleshootView', params: undefined }] + : ['PushTroubleshootView', undefined]) + ); + }); + + it('hides the threads button when threads are disabled', () => { + mockAppState = { ...mockAppState, settings: { ...mockAppState.settings, Threads_enabled: false } }; + + render(); + + expectOnly(['room-view-header-call', 'room-view-search']); + }); + + it('hides the call button on a self DM', () => { + mockHeaderHooks = { ...mockHeaderHooks, isSelfDm: true }; + + render(); - act(() => { - Object.assign(room, { t: nextType, status: nextStatus }); - roomStore.setState({ room, membership: nextStatus === 'INVITED' ? 'invited' : 'subscribed' }); + expectOnly(['room-view-header-threads', 'room-view-search']); }); - expectOnlyStub(expected); + it('starts a video conference from the call button', () => { + render(); + + fireEvent.press(screen.getByTestId('room-view-header-call')); + + expect(mockVideoConf.showInitCallActionSheet).toHaveBeenCalled(); + }); + + it('navigates to the threads screen', () => { + render(); + + fireEvent.press(screen.getByTestId('room-view-header-threads')); + + expect(mockNavigation.navigate).toHaveBeenCalledWith('ThreadMessagesView', { rid: 'rid-1', t: 'c' }); + }); + + it('badges the threads button with the unread thread count', () => { + mockHeaderHooks = { ...mockHeaderHooks, tunread: ['t1', 't2'] }; + render(); + + const badge = screen.getByTestId('room-view-header-threads').props.badge(); + + expect(badge.props.value).toBe(2); + }); + }); + + describe('thread', () => { + it.each([ + [false, 'room-view-header-follow', 'Follow thread'], + [true, 'room-view-header-unfollow', 'Unfollow thread'] + ])('toggles the follow state of a thread (following: %s)', (isFollowingThread, testID, label) => { + mockHeaderHooks = { ...mockHeaderHooks, isFollowingThread }; + render(); + + expect(screen.getByTestId(testID)).toHaveProp('accessibilityLabel', label); + fireEvent.press(screen.getByTestId(testID)); + + expect(toggleFollowThread).toHaveBeenCalledWith('tmid-1', isFollowingThread); + }); }); - it('remounts the room buttons after the tmid is cleared', () => { - const roomStore = createRoomStore({ rid: 'rid-1', t: 'c' }); - render(); + describe('omnichannel', () => { + it('opens the more actions sheet and closes the chat from it', () => { + render(); - screen.rerender(); - screen.rerender(); + fireEvent.press(screen.getByTestId('room-view-header-omnichannel-kebab')); - expectOnlyStub('room-right-buttons-stub'); - expect(roomMock.mounts.count).toBe(2); - expect(threadMock.mounts.count).toBe(1); + expect(logEvent).toHaveBeenCalledWith(events.ROOM_SHOW_MORE_ACTIONS); + const { options } = (showActionSheetRef as jest.Mock).mock.calls[0][0] as { options: TActionSheetOptionsItem[] }; + expect(options).toEqual([expect.objectContaining({ title: 'Close', icon: 'chat-close', danger: true })]); + options[0].onPress(); + expect(closeLivechat).toHaveBeenCalledWith(expect.objectContaining({ rid: 'rid-1', departmentId: 'department-1' })); + }); }); }); diff --git a/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx deleted file mode 100644 index 02342646251..00000000000 --- a/app/views/RoomView/components/RightButtons/__tests__/RoomRightButtons.test.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react-native'; - -import { events, logEvent } from '~/lib/methods/helpers/log'; -import { type RoomStore } from '~/views/RoomView/definitions'; -import { RoomRightButtons } from '../RoomRightButtons'; - -const mockNavigation = { navigate: jest.fn(), push: jest.fn() }; -jest.mock('~/lib/methods/helpers/log', () => ({ - ...jest.requireActual('~/lib/methods/helpers/log'), - logEvent: jest.fn() -})); -jest.mock('@react-navigation/native', () => ({ - useNavigation: () => mockNavigation -})); - -let mockIsMasterDetail = false; -jest.mock('~/lib/hooks/useMasterDetail', () => ({ - useMasterDetail: () => mockIsMasterDetail -})); - -jest.mock('~/theme', () => ({ useTheme: () => ({ colors: { fontDanger: '#f00' } }) })); -jest.mock('~/lib/helpers/getRoomAccessibilityLabel', () => ({ __esModule: true, default: () => 'channel label' })); - -jest.mock('~/lib/methods/helpers', () => ({ - getRoomTitle: () => 'Room Title', - isGroupChat: () => false -})); - -let mockThreadsEnabled = true; -jest.mock('~/lib/hooks/useSetting', () => ({ - useSetting: () => mockThreadsEnabled -})); - -let mockAppState = { - login: { user: { id: 'u1', username: 'user', token: 'tok' } }, - troubleshootingNotification: { issuesWithNotifications: false } -}; -jest.mock('~/lib/hooks/useAppSelector', () => ({ - useAppSelector: (selector: (state: typeof mockAppState) => unknown) => selector(mockAppState) -})); - -let mockRoomState = { room: { rid: 'rid-1', t: 'c', name: 'general' } as Record }; -jest.mock('zustand', () => ({ - useStore: (_store: unknown, selector: (state: typeof mockRoomState) => unknown) => selector(mockRoomState) -})); - -let mockHasE2EEWarning = false; -jest.mock('~/views/RoomView/hooks/useE2EEStatus', () => ({ - useE2EEStatus: () => ({ - showMissingE2EEKey: mockHasE2EEWarning, - showE2EEDisabledRoom: false, - hasE2EEWarning: mockHasE2EEWarning - }) -})); - -let mockUnreads = { - tunread: [] as string[], - tunreadUser: [] as string[], - tunreadGroup: [] as string[], - isSelfDm: false -}; -jest.mock('~/views/RoomView/hooks/useSubscriptionUnreads', () => ({ - useSubscriptionUnreads: () => mockUnreads -})); - -let mockCanToggleEncryption = false; -jest.mock('~/lib/hooks/usePermissions', () => ({ - usePermissions: () => [mockCanToggleEncryption] -})); - -let mockCallEnabled = false; -jest.mock('~/lib/hooks/useVideoConf', () => ({ - useVideoConf: () => ({ showInitCallActionSheet: jest.fn(), callEnabled: mockCallEnabled, disabledTooltip: false }) -})); - -let mockHasMediaCallPermission = false; -jest.mock('~/lib/hooks/useNewMediaCall', () => ({ - useNewMediaCall: () => ({ - openNewMediaCall: jest.fn(), - startCallImmediate: jest.fn(), - hasMediaCallPermission: mockHasMediaCallPermission, - isInActiveCall: false - }) -})); - -jest.mock('~/containers/Header/components/HeaderButton', () => { - const ReactActual = jest.requireActual('react'); - return { - Container: ({ children }: { children: unknown }) => ReactActual.createElement('Container', null, children), - Item: ({ - accessibilityLabel, - color, - disabled, - iconName, - onPress, - testID - }: { - accessibilityLabel?: string; - color?: string; - disabled?: boolean; - iconName: string; - onPress: () => void; - testID: string; - }) => - ReactActual.createElement('Item', { - accessibilityLabel, - color, - disabled, - iconName, - onPress, - testID - }), - BadgeUnread: () => null - }; -}); -jest.mock('../HeaderCallButton', () => { - const ReactActual = jest.requireActual('react'); - return { - HeaderCallButton: ({ rid, disabled, accessibilityLabel }: { rid: string; disabled: boolean; accessibilityLabel: string }) => - ReactActual.createElement('CallButton', { rid, disabled, accessibilityLabel, testID: 'header-call-button-stub' }) - }; -}); - -const roomStore = { getState: () => mockRoomState } as unknown as RoomStore; - -const renderRoomRightButtons = () => render(); - -describe('RoomRightButtons', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockIsMasterDetail = false; - mockThreadsEnabled = true; - mockAppState = { - login: { user: { id: 'u1', username: 'user', token: 'tok' } }, - troubleshootingNotification: { issuesWithNotifications: false } - }; - mockRoomState = { room: { rid: 'rid-1', t: 'c', name: 'general' } }; - mockHasE2EEWarning = false; - mockUnreads = { tunread: [], tunreadUser: [], tunreadGroup: [], isSelfDm: false }; - mockCanToggleEncryption = false; - mockCallEnabled = true; - mockHasMediaCallPermission = false; - }); - - it('renders every present item directly with no overflow menu', () => { - mockHasE2EEWarning = true; - mockCanToggleEncryption = true; - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - - renderRoomRightButtons(); - - expect(screen.getByTestId('room-view-header-encryption')).toBeOnTheScreen(); - expect(screen.getByTestId('room-view-push-troubleshoot')).toBeOnTheScreen(); - expect(screen.getByTestId('room-view-header-threads')).toBeOnTheScreen(); - expect(screen.getByTestId('room-view-search')).toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-header-more')).not.toBeOnTheScreen(); - }); - - it('hides the call button on a self DM', () => { - mockRoomState = { room: { rid: 'rid-1', t: 'd', name: 'user' } }; - mockUnreads = { ...mockUnreads, isSelfDm: true }; - - renderRoomRightButtons(); - - expect(screen.queryByTestId('header-call-button-stub')).not.toBeOnTheScreen(); - }); - - it('hides the threads button when threads are disabled', () => { - mockThreadsEnabled = false; - - renderRoomRightButtons(); - - expect(screen.queryByTestId('room-view-header-threads')).not.toBeOnTheScreen(); - }); - - it.each([false, true])('routes notification issues to push troubleshooting (master-detail: %s)', isMasterDetail => { - mockIsMasterDetail = isMasterDetail; - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general' } }; - - renderRoomRightButtons(); - - fireEvent.press(screen.getByTestId('room-view-push-troubleshoot')); - expect(mockNavigation.navigate).toHaveBeenCalledWith( - ...(isMasterDetail - ? ['ModalStackNavigator', { screen: 'PushTroubleshootView', params: undefined }] - : ['PushTroubleshootView', undefined]) - ); - }); - - it('navigates to the threads screen', () => { - mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general', encrypted: true } }; - - renderRoomRightButtons(); - - fireEvent.press(screen.getByTestId('room-view-header-threads')); - expect(mockNavigation.navigate).toHaveBeenCalledWith('ThreadMessagesView', { rid: 'rid-1', t: 'c' }); - }); - - it('offers encryption navigation while other items are disabled', () => { - mockHasE2EEWarning = true; - mockCanToggleEncryption = true; - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - mockRoomState = { room: { id: 'rid-1', rid: 'rid-1', t: 'c', name: 'general' } }; - renderRoomRightButtons(); - - const encryptionButton = screen.getByTestId('room-view-header-encryption'); - expect(encryptionButton).toHaveProp('disabled', false); - - fireEvent.press(encryptionButton); - expect(logEvent).toHaveBeenCalledTimes(1); - expect(logEvent).toHaveBeenCalledWith(events.ROOM_GO_E2EE); - expect(mockNavigation.navigate).toHaveBeenCalledWith('E2EEToggleRoomView', { rid: 'rid-1' }); - }); -}); diff --git a/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx b/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx deleted file mode 100644 index 6e16f6df164..00000000000 --- a/app/views/RoomView/components/RightButtons/__tests__/ThreadRightButtons.test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react-native'; - -import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; -import { ThreadRightButtons } from '../ThreadRightButtons'; - -let mockAppState = { login: { user: { id: 'u1', username: 'user', token: 'tok' } } }; -jest.mock('~/lib/hooks/useAppSelector', () => ({ - useAppSelector: (selector: (state: typeof mockAppState) => unknown) => selector(mockAppState) -})); - -let mockFollowersByThread: Record = {}; -jest.mock('~/views/RoomView/hooks/useThreadFollowing', () => ({ - useThreadFollowing: (tmid: string, userId: string) => mockFollowersByThread[tmid]?.includes(userId) ?? false -})); - -jest.mock('~/lib/methods/toggleFollowThread', () => ({ toggleFollowThread: jest.fn() })); - -jest.mock('~/containers/Header/components/HeaderButton', () => { - const ReactActual = jest.requireActual('react'); - return { - Container: ({ children }: { children: unknown }) => ReactActual.createElement('Container', null, children), - Item: ({ - accessibilityLabel, - iconName, - onPress, - testID - }: { - accessibilityLabel: string; - iconName: string; - onPress: () => void; - testID: string; - }) => ReactActual.createElement('Item', { accessibilityLabel, iconName, onPress, testID }) - }; -}); - -describe('ThreadRightButtons', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockFollowersByThread = {}; - mockAppState = { login: { user: { id: 'u1', username: 'user', token: 'tok' } } }; - }); - - it('renders the follow button when the thread is not followed', () => { - render(); - - const followButton = screen.getByTestId('room-view-header-follow'); - expect(followButton).toHaveProp('accessibilityLabel', 'Follow thread'); - expect(followButton).toHaveProp('iconName', 'notification-disabled'); - expect(screen.queryByTestId('room-view-header-unfollow')).not.toBeOnTheScreen(); - }); - - it('renders the unfollow button when the thread is followed', () => { - mockFollowersByThread = { 'tmid-1': ['u1'] }; - - render(); - - const unfollowButton = screen.getByTestId('room-view-header-unfollow'); - expect(unfollowButton).toHaveProp('accessibilityLabel', 'Unfollow thread'); - expect(unfollowButton).toHaveProp('iconName', 'notification'); - expect(screen.queryByTestId('room-view-header-follow')).not.toBeOnTheScreen(); - }); - - it('shows the follow state for the displayed Thread and current user', () => { - mockFollowersByThread = { 'tmid-1': ['u1'] }; - render(); - expect(screen.getByTestId('room-view-header-unfollow')).toBeOnTheScreen(); - - screen.rerender(); - expect(screen.getByTestId('room-view-header-follow')).toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-header-unfollow')).not.toBeOnTheScreen(); - - mockAppState = { login: { user: { id: 'u2', username: 'other', token: 'tok' } } }; - screen.rerender(); - expect(screen.getByTestId('room-view-header-follow')).toBeOnTheScreen(); - expect(screen.queryByTestId('room-view-header-unfollow')).not.toBeOnTheScreen(); - }); - - it('follows the thread when it is not followed yet', () => { - render(); - - fireEvent.press(screen.getByTestId('room-view-header-follow')); - - expect(toggleFollowThread).toHaveBeenCalledWith('tmid-1', false); - }); - - it('unfollows the thread when it is followed', () => { - mockFollowersByThread = { 'tmid-1': ['u1'] }; - - render(); - - fireEvent.press(screen.getByTestId('room-view-header-unfollow')); - - expect(toggleFollowThread).toHaveBeenCalledWith('tmid-1', true); - }); -}); diff --git a/app/views/RoomView/components/__tests__/RightButtons.test.tsx b/app/views/RoomView/components/__tests__/RightButtons.test.tsx deleted file mode 100644 index 58ffd041956..00000000000 --- a/app/views/RoomView/components/__tests__/RightButtons.test.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import { render } from '@testing-library/react-native'; - -import { type RoomMembership } from '~/views/RoomView/definitions'; -import RightButtons from '../RightButtons/RightButtons'; - -const mockNavigation = { navigate: jest.fn(), push: jest.fn() }; -jest.mock('@react-navigation/native', () => ({ - useNavigation: () => mockNavigation -})); -jest.mock('~/containers/ActionSheet', () => ({ - useActionSheet: () => ({ showActionSheet: jest.fn() }) -})); -jest.mock('~/lib/hooks/useMasterDetail', () => ({ - ...jest.requireActual('~/lib/hooks/useMasterDetail'), - useMasterDetail: () => false -})); -jest.mock('~/theme', () => ({ useTheme: () => ({ colors: { fontDanger: '#f00' } }) })); -jest.mock('~/lib/helpers/getRoomAccessibilityLabel', () => ({ __esModule: true, default: () => 'label' })); -jest.mock('~/lib/methods/helpers', () => ({ - ...jest.requireActual('~/lib/methods/helpers'), - getRoomTitle: () => 'Room Title', - isGroupChat: () => false, - isTablet: true, - hasNativeHeaderBar: false -})); -jest.mock('~/lib/hooks/useVideoConf', () => ({ - useVideoConf: () => ({ showInitCallActionSheet: jest.fn(), callEnabled: false, disabledTooltip: false }) -})); -jest.mock('~/lib/hooks/useNewMediaCall', () => ({ - useNewMediaCall: () => ({ - openNewMediaCall: jest.fn(), - startCallImmediate: jest.fn(), - hasMediaCallPermission: false, - isInActiveCall: false - }) -})); -jest.mock('~/views/RoomView/hooks/useGoRoomActionsView', () => ({ - useGoRoomActionsView: () => jest.fn() -})); - -let mockAppState = { - login: { user: { id: 'u1', username: 'user', token: 'tok' } }, - settings: { Threads_enabled: true, Livechat_request_comment_when_closing_conversation: false }, - troubleshootingNotification: { issuesWithNotifications: false }, - permissions: { 'toggle-room-e2e-encryption': ['perm'] } -}; -jest.mock('~/lib/hooks/useAppSelector', () => ({ - useAppSelector: (selector: (state: typeof mockAppState) => unknown) => selector(mockAppState) -})); - -let mockRoomState = { - room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general' } as any, - membership: 'subscribed' as RoomMembership, - canForwardGuest: false -}; -jest.mock('zustand', () => ({ - useStore: (_store: unknown, selector: (state: typeof mockRoomState) => unknown) => selector(mockRoomState) -})); -jest.mock('~/ee/omnichannel/hooks/useCanReturnQueue', () => ({ useCanReturnQueue: () => false })); -jest.mock('~/views/RoomView/hooks/useCanPlaceLivechatOnHold', () => ({ useCanPlaceLivechatOnHold: () => false })); - -let mockE2EEStatus = { showMissingE2EEKey: false, showE2EEDisabledRoom: false, hasE2EEWarning: false }; -jest.mock('~/views/RoomView/hooks/useE2EEStatus', () => ({ useE2EEStatus: () => mockE2EEStatus })); - -let mockHeaderHooks = { - isFollowingThread: false, - tunread: [] as string[], - tunreadUser: [] as string[], - tunreadGroup: [] as string[], - isSelfDm: false, - canToggleEncryption: false -}; -jest.mock('~/views/RoomView/hooks/useThreadFollowing', () => ({ useThreadFollowing: () => mockHeaderHooks.isFollowingThread })); -jest.mock('~/views/RoomView/hooks/useSubscriptionUnreads', () => ({ - useSubscriptionUnreads: () => { - const { tunread, tunreadUser, tunreadGroup, isSelfDm } = mockHeaderHooks; - return { tunread, tunreadUser, tunreadGroup, isSelfDm }; - } -})); -jest.mock('~/lib/hooks/usePermissions', () => ({ - usePermissions: () => [mockHeaderHooks.canToggleEncryption] -})); - -jest.mock('~/containers/Header/components/HeaderButton', () => { - const ReactActual = jest.requireActual('react'); - return { - Container: ({ children }: any) => ReactActual.createElement('Container', null, children), - Item: (props: any) => ReactActual.createElement('Item', props), - BadgeUnread: () => null - }; -}); -jest.mock('../RightButtons/HeaderCallButton', () => { - const ReactActual = jest.requireActual('react'); - return { - HeaderCallButton: ({ rid, disabled, accessibilityLabel }: { rid: string; disabled: boolean; accessibilityLabel: string }) => - ReactActual.createElement('CallButton', { rid, disabled, accessibilityLabel, testID: 'header-call-button-stub' }) - }; -}); - -const allTestIDs = [ - 'room-view-search', - 'room-view-header-threads', - 'header-call-button-stub', - 'room-view-header-encryption', - 'room-view-push-troubleshoot', - 'room-view-header-omnichannel-kebab', - 'room-view-header-follow', - 'room-view-header-unfollow' -]; - -describe('RightButtons', () => { - const roomStore = { getState: () => mockRoomState } as any; - - const expectOnly = (queryByTestId: (id: string) => unknown, present: string[]) => { - present.forEach(id => expect(queryByTestId(id)).toBeTruthy()); - allTestIDs.filter(id => !present.includes(id)).forEach(id => expect(queryByTestId(id)).toBeNull()); - }; - - beforeEach(() => { - jest.clearAllMocks(); - mockAppState = { - login: { user: { id: 'u1', username: 'user', token: 'tok' } }, - settings: { Threads_enabled: true, Livechat_request_comment_when_closing_conversation: false }, - troubleshootingNotification: { issuesWithNotifications: false }, - permissions: { 'toggle-room-e2e-encryption': ['perm'] } - }; - mockRoomState = { - room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general' }, - membership: 'subscribed', - canForwardGuest: false - }; - mockE2EEStatus = { showMissingE2EEKey: false, showE2EEDisabledRoom: false, hasE2EEWarning: false }; - mockHeaderHooks = { - isFollowingThread: false, - tunread: [], - tunreadUser: [], - tunreadGroup: [], - isSelfDm: false, - canToggleEncryption: false - }; - }); - - it('renders only the kebab for an active omnichannel room', () => { - mockRoomState = { ...mockRoomState, room: { id: 'sub-1', rid: 'rid-1', t: 'l', name: 'chat' } }; - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, ['room-view-header-omnichannel-kebab']); - expect(toJSON()).toMatchSnapshot(); - }); - - it('renders the unfollow button for a followed thread', () => { - mockHeaderHooks = { ...mockHeaderHooks, isFollowingThread: true }; - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, ['room-view-header-unfollow']); - expect(queryByTestId('room-view-header-unfollow')).toHaveProp('accessibilityLabel', 'Unfollow thread'); - expect(toJSON()).toMatchSnapshot(); - }); - - it('renders the follow button for an unfollowed thread', () => { - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, ['room-view-header-follow']); - expect(queryByTestId('room-view-header-follow')).toHaveProp('accessibilityLabel', 'Follow thread'); - expect(toJSON()).toMatchSnapshot(); - }); - - it('renders call, threads and search for a regular channel', () => { - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, ['header-call-button-stub', 'room-view-header-threads', 'room-view-search']); - expect(toJSON()).toMatchSnapshot(); - }); - - it('enables the encryption button when the user can toggle encryption', () => { - mockRoomState = { ...mockRoomState, room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', encrypted: true } }; - mockE2EEStatus = { showMissingE2EEKey: true, showE2EEDisabledRoom: false, hasE2EEWarning: true }; - mockHeaderHooks = { ...mockHeaderHooks, canToggleEncryption: true }; - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, [ - 'room-view-header-encryption', - 'header-call-button-stub', - 'room-view-header-threads', - 'room-view-search' - ]); - expect(queryByTestId('room-view-header-encryption')).toHaveProp('disabled', false); - expect(queryByTestId('room-view-search')).toHaveProp('disabled', true); - expect(toJSON()).toMatchSnapshot(); - }); - - it('disables the encryption button when the user cannot toggle encryption', () => { - mockRoomState = { ...mockRoomState, room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', encrypted: true } }; - mockE2EEStatus = { showMissingE2EEKey: true, showE2EEDisabledRoom: false, hasE2EEWarning: true }; - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, [ - 'room-view-header-encryption', - 'header-call-button-stub', - 'room-view-header-threads', - 'room-view-search' - ]); - expect(queryByTestId('room-view-header-encryption')).toHaveProp('disabled', true); - expect(toJSON()).toMatchSnapshot(); - }); - - it('renders the encryption button when the room has e2ee disabled', () => { - mockRoomState = { ...mockRoomState, room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', encrypted: true } }; - mockE2EEStatus = { showMissingE2EEKey: false, showE2EEDisabledRoom: true, hasE2EEWarning: true }; - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, [ - 'room-view-header-encryption', - 'header-call-button-stub', - 'room-view-header-threads', - 'room-view-search' - ]); - expect(queryByTestId('room-view-header-encryption')).toHaveProp('disabled', true); - expect(toJSON()).toMatchSnapshot(); - }); - - it('renders the push troubleshoot button when there are notification issues', () => { - mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, [ - 'room-view-push-troubleshoot', - 'header-call-button-stub', - 'room-view-header-threads', - 'room-view-search' - ]); - expect(queryByTestId('room-view-push-troubleshoot')).toHaveProp('color', '#f00'); - expect(toJSON()).toMatchSnapshot(); - }); - - it('renders the push troubleshoot button when notifications are disabled for the room', () => { - mockRoomState = { - ...mockRoomState, - room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', disableNotifications: true } - }; - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, [ - 'room-view-push-troubleshoot', - 'header-call-button-stub', - 'room-view-header-threads', - 'room-view-search' - ]); - expect(queryByTestId('room-view-push-troubleshoot')).toHaveProp('color', ''); - expect(toJSON()).toMatchSnapshot(); - }); - - it('hides the threads button when threads are disabled', () => { - mockAppState = { - ...mockAppState, - settings: { Threads_enabled: false, Livechat_request_comment_when_closing_conversation: false } - }; - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, ['header-call-button-stub', 'room-view-search']); - expect(toJSON()).toMatchSnapshot(); - }); - - it('hides the call button on a self DM', () => { - mockRoomState = { ...mockRoomState, room: { id: 'sub-1', rid: 'rid-1', t: 'd', name: 'user' } }; - mockHeaderHooks = { ...mockHeaderHooks, isSelfDm: true }; - const { queryByTestId, toJSON } = render(); - expectOnly(queryByTestId, ['room-view-header-threads', 'room-view-search']); - expect(toJSON()).toMatchSnapshot(); - }); -}); diff --git a/app/views/RoomView/components/__tests__/__snapshots__/RightButtons.test.tsx.snap b/app/views/RoomView/components/__tests__/__snapshots__/RightButtons.test.tsx.snap deleted file mode 100644 index 2f2c9265c67..00000000000 --- a/app/views/RoomView/components/__tests__/__snapshots__/RightButtons.test.tsx.snap +++ /dev/null @@ -1,259 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RightButtons disables the encryption button when the user cannot toggle encryption 1`] = ` - - - - - - -`; - -exports[`RightButtons enables the encryption button when the user can toggle encryption 1`] = ` - - - - - - -`; - -exports[`RightButtons hides the call button on a self DM 1`] = ` - - - - -`; - -exports[`RightButtons hides the threads button when threads are disabled 1`] = ` - - - - -`; - -exports[`RightButtons renders call, threads and search for a regular channel 1`] = ` - - - - - -`; - -exports[`RightButtons renders only the kebab for an active omnichannel room 1`] = ` - - - -`; - -exports[`RightButtons renders the encryption button when the room has e2ee disabled 1`] = ` - - - - - - -`; - -exports[`RightButtons renders the follow button for an unfollowed thread 1`] = ` - - - -`; - -exports[`RightButtons renders the push troubleshoot button when notifications are disabled for the room 1`] = ` - - - - - - -`; - -exports[`RightButtons renders the push troubleshoot button when there are notification issues 1`] = ` - - - - - - -`; - -exports[`RightButtons renders the unfollow button for a followed thread 1`] = ` - - - -`; diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx index 11b232cf492..d5c83fde15d 100644 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx @@ -9,8 +9,9 @@ import { useNativeBackButton } from '../useNativeBackButton'; jest.mock('../useNativeRoomHeader', () => ({ useNativeRoomHeader: jest.fn() })); jest.mock('../useNativeBackButton', () => ({ useNativeBackButton: jest.fn() })); -const mockNativeRightItems: unknown[] = []; -jest.mock('../useRoomHeaderRightItems', () => ({ useRoomHeaderRightItems: jest.fn(() => mockNativeRightItems) })); +const mockThreadsPress = jest.fn(); +const mockRightActions = [{ label: 'Threads', icon: 'threads', onPress: mockThreadsPress }]; +jest.mock('../useRoomHeaderActions', () => ({ useRoomHeaderActions: jest.fn(() => mockRightActions) })); let mockTestStore: RoomStore; let mockIsIOS = false; @@ -130,8 +131,10 @@ describe('useHeader', () => { renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); const sideOptions = mockSetOptions.mock.calls[0][0]; - expect(typeof sideOptions.unstable_headerRightItems).toBe('function'); - expect(sideOptions.unstable_headerRightItems()).toBe(mockNativeRightItems); + const [threadsItem] = sideOptions.unstable_headerRightItems(); + expect(threadsItem).toMatchObject({ type: 'button', label: 'Threads', accessibilityLabel: 'Threads' }); + threadsItem.onPress(); + expect(mockThreadsPress).toHaveBeenCalled(); expect(sideOptions).not.toHaveProperty('headerLeft'); expect(sideOptions).not.toHaveProperty('headerRight'); }); @@ -147,7 +150,7 @@ describe('useHeader', () => { renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); const sideOptions = mockSetOptions.mock.calls[0][0]; - expect(sideOptions.unstable_headerRightItems()).toBe(mockNativeRightItems); + expect(sideOptions.unstable_headerRightItems()).toEqual([expect.objectContaining({ type: 'button', label: 'Threads' })]); expect(sideOptions).not.toHaveProperty('headerRight'); }); diff --git a/app/views/RoomView/hooks/__tests__/useRoomHeaderActions.test.ts b/app/views/RoomView/hooks/__tests__/useRoomHeaderActions.test.ts new file mode 100644 index 00000000000..59a4b2a00d2 --- /dev/null +++ b/app/views/RoomView/hooks/__tests__/useRoomHeaderActions.test.ts @@ -0,0 +1,438 @@ +import { act, renderHook } from '@testing-library/react-native'; + +import { type IHeaderAction } from '~/lib/methods/helpers/navigation/headerActions'; +import { events, logEvent } from '~/lib/methods/helpers/log'; +import { showConfirmationAlert } from '~/lib/methods/helpers'; +import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; +import { returnLivechat } from '~/lib/services/restApi'; +import { type RoomStore } from '~/views/RoomView/definitions'; +import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; +import { placeLivechatOnHold } from '~/views/RoomView/services/placeLivechatOnHold'; +import { useRoomHeaderActions } from '../useRoomHeaderActions'; + +const mockNavigation = { navigate: jest.fn(), push: jest.fn() }; +jest.mock('@react-navigation/native', () => ({ useNavigation: () => mockNavigation })); + +let mockIsMasterDetail = false; +jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => mockIsMasterDetail })); + +let mockLivechatRequestComment = false; +jest.mock('~/lib/hooks/useSetting', () => ({ useSetting: () => mockLivechatRequestComment })); + +let mockUserId = 'user-1'; +jest.mock('~/lib/hooks/useAppSelector', () => ({ useAppSelector: () => mockUserId })); +jest.mock('~/theme', () => ({ useTheme: () => ({ theme: 'light', colors: { fontDanger: '#f00' } }) })); +jest.mock('~/lib/methods/helpers/log', () => ({ + ...jest.requireActual('~/lib/methods/helpers/log'), + logEvent: jest.fn() +})); + +let mockHasNativeHeaderBar = true; +jest.mock('~/lib/methods/helpers', () => + Object.defineProperty( + { ...jest.requireActual('~/lib/methods/helpers'), showConfirmationAlert: jest.fn(), showErrorAlert: jest.fn() }, + 'hasNativeHeaderBar', + { get: () => mockHasNativeHeaderBar } + ) +); + +let mockRoomState: Record; +jest.mock('zustand', () => ({ + useStore: (_store: unknown, selector: (state: Record) => unknown) => selector(mockRoomState) +})); +jest.mock('zustand/react/shallow', () => ({ useShallow: (selector: unknown) => selector })); + +let mockCanReturnQueue = false; +let mockCanPlaceLivechatOnHold = false; +jest.mock('~/ee/omnichannel/hooks/useCanReturnQueue', () => ({ useCanReturnQueue: () => mockCanReturnQueue })); +jest.mock('../useCanPlaceLivechatOnHold', () => ({ useCanPlaceLivechatOnHold: () => mockCanPlaceLivechatOnHold })); + +let mockFollowersByThread: Record = {}; +jest.mock('../useThreadFollowing', () => ({ + useThreadFollowing: (tmid: string, userId: string) => mockFollowersByThread[tmid]?.includes(userId) ?? false +})); +jest.mock('~/lib/methods/toggleFollowThread', () => ({ toggleFollowThread: jest.fn() })); +jest.mock('~/views/RoomView/services/closeLivechat', () => ({ closeLivechat: jest.fn() })); +jest.mock('~/views/RoomView/services/placeLivechatOnHold', () => ({ placeLivechatOnHold: jest.fn() })); +jest.mock('~/lib/services/restApi', () => ({ returnLivechat: jest.fn() })); + +const mockGoThreadsView = jest.fn(); +const mockGoSearchView = jest.fn(); +let mockButtonsData: Record; +jest.mock('../../components/RightButtons/useRoomRightButtonsData', () => ({ + useRoomRightButtonsData: () => mockButtonsData +})); + +const mockVideoConf = { showInitCallActionSheet: jest.fn(), callEnabled: false, disabledTooltip: false }; +jest.mock('~/lib/hooks/useVideoConf', () => ({ useVideoConf: () => mockVideoConf })); + +const mockMediaCall = { + openNewMediaCall: jest.fn(), + startCallImmediate: jest.fn(), + hasMediaCallPermission: false, + isInActiveCall: false +}; +jest.mock('~/lib/hooks/useNewMediaCall', () => ({ useNewMediaCall: () => mockMediaCall })); + +const roomStore = {} as RoomStore; + +const renderActions = (tmid?: string) => renderHook(() => useRoomHeaderActions('rid-1', tmid, roomStore)).result.current; + +const labelsOf = (actions: { label: string }[]) => actions.map(action => action.label); + +const moreMenuOf = (actions: IHeaderAction[]) => { + const more = actions[actions.length - 1]; + expect(more.label).toBe('More'); + expect(more.icon).toBe('kebab'); + return more.menu ?? []; +}; + +const actionByTestID = (actions: IHeaderAction[], testID: string) => actions.find(action => action.testID === testID); + +describe('useRoomHeaderActions', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockHasNativeHeaderBar = true; + mockIsMasterDetail = false; + mockLivechatRequestComment = false; + mockUserId = 'user-1'; + mockCanReturnQueue = false; + mockCanPlaceLivechatOnHold = false; + mockFollowersByThread = {}; + mockRoomState = { + room: { id: 'sub-1', t: 'c', departmentId: 'department-1' }, + membership: 'subscribed', + canForwardGuest: false + }; + mockButtonsData = { + threadsEnabled: false, + issuesWithNotifications: false, + disableNotifications: false, + hasE2EEWarning: false, + canToggleEncryption: false, + isSelfDm: false, + tunread: [], + tunreadUser: [], + tunreadGroup: [], + threadsAccessibilityLabel: 'Threads', + callAccessibilityLabel: 'Call', + goThreadsView: mockGoThreadsView, + goSearchView: mockGoSearchView, + goE2EEToggleRoomView: jest.fn(), + navigateToNotificationOrPushTroubleshoot: jest.fn() + }; + mockVideoConf.callEnabled = false; + mockVideoConf.disabledTooltip = false; + mockMediaCall.hasMediaCallPermission = false; + mockMediaCall.isInActiveCall = false; + }); + + it.each([ + ['without a rid', undefined, { room: { t: 'c' }, membership: 'subscribed' }], + ['for an invited room', 'rid-1', { room: { t: 'c' }, membership: 'invited' }], + ['for a queued omnichannel room', 'rid-1', { room: { id: 'sub-1', t: 'l', status: 'queued' }, membership: 'subscribed' }], + ['for an omnichannel room in preview', 'rid-1', { room: { t: 'l' }, membership: 'preview' }] + ])('returns no actions %s', (_case, rid, state) => { + mockRoomState = { ...state, canForwardGuest: false }; + + const { result } = renderHook(() => useRoomHeaderActions(rid, undefined, roomStore)); + + expect(result.current).toEqual([]); + }); + + describe('room on the native header bar', () => { + it('lists search in the more menu and runs the chosen action', () => { + const menu = moreMenuOf(renderActions()); + + expect(labelsOf(menu)).toEqual(['Search messages']); + menu[0].onPress(); + expect(mockGoSearchView).toHaveBeenCalled(); + }); + + it('keeps the two highest priority actions visible and moves the rest into the menu, keeping disabled state', () => { + mockButtonsData = { ...mockButtonsData, threadsEnabled: true, hasE2EEWarning: true, canToggleEncryption: false }; + mockMediaCall.hasMediaCallPermission = true; + + const actions = renderActions(); + + expect(labelsOf(actions)).toEqual(['Call', 'Threads', 'More']); + expect(moreMenuOf(actions).map(item => [item.label, !!item.disabled])).toEqual([ + ['Encrypted', true], + ['Search messages', true] + ]); + }); + }); + + describe('room on the JS header', () => { + beforeEach(() => { + mockHasNativeHeaderBar = false; + }); + + it('shows every present action followed by search, with no more menu', () => { + mockButtonsData = { + ...mockButtonsData, + threadsEnabled: true, + hasE2EEWarning: true, + canToggleEncryption: true, + issuesWithNotifications: true + }; + mockMediaCall.hasMediaCallPermission = true; + + const actions = renderActions(); + + expect(actions.map(action => action.testID)).toEqual([ + 'room-view-header-encryption', + 'room-view-push-troubleshoot', + 'room-view-header-call', + 'room-view-header-threads', + 'room-view-search' + ]); + expect(actions.some(action => action.menu)).toBe(false); + }); + + it('offers encryption while the other actions are disabled by the e2ee warning', () => { + mockButtonsData = { ...mockButtonsData, threadsEnabled: true, hasE2EEWarning: true, canToggleEncryption: true }; + + const actions = renderActions(); + + expect(actionByTestID(actions, 'room-view-header-encryption')?.disabled).toBe(false); + expect(actionByTestID(actions, 'room-view-header-threads')?.disabled).toBe(true); + expect(actionByTestID(actions, 'room-view-search')?.disabled).toBe(true); + }); + + it('disables encryption when the user cannot toggle it', () => { + mockButtonsData = { ...mockButtonsData, hasE2EEWarning: true, canToggleEncryption: false }; + + expect(actionByTestID(renderActions(), 'room-view-header-encryption')?.disabled).toBe(true); + }); + + it('tints the troubleshoot action only when notifications have issues', () => { + mockButtonsData = { ...mockButtonsData, issuesWithNotifications: true }; + expect(actionByTestID(renderActions(), 'room-view-push-troubleshoot')?.tintColor).toBe('#f00'); + + mockButtonsData = { ...mockButtonsData, issuesWithNotifications: false, disableNotifications: true }; + const troubleshoot = actionByTestID(renderActions(), 'room-view-push-troubleshoot'); + expect(troubleshoot).toBeDefined(); + expect(troubleshoot?.tintColor).toBeUndefined(); + }); + + it('hides the threads action when threads are disabled', () => { + expect(actionByTestID(renderActions(), 'room-view-header-threads')).toBeUndefined(); + }); + + it('hides the call action on a self DM', () => { + mockButtonsData = { ...mockButtonsData, isSelfDm: true }; + mockMediaCall.hasMediaCallPermission = true; + + expect(actionByTestID(renderActions(), 'room-view-header-call')).toBeUndefined(); + }); + + it('badges threads with the unread thread count in the unread style color', () => { + mockButtonsData = { ...mockButtonsData, threadsEnabled: true, tunread: ['t1', 't2'], tunreadUser: ['t1'] }; + + const threads = actionByTestID(renderActions(), 'room-view-header-threads'); + + expect(threads?.badge?.value).toBe(2); + expect(threads?.badge?.color).toEqual(expect.any(String)); + threads?.onPress?.(); + expect(mockGoThreadsView).toHaveBeenCalled(); + }); + + it('leaves threads without a badge when nothing is unread', () => { + mockButtonsData = { ...mockButtonsData, threadsEnabled: true }; + + expect(actionByTestID(renderActions(), 'room-view-header-threads')?.badge).toBeUndefined(); + }); + }); + + describe('call action', () => { + beforeEach(() => { + mockHasNativeHeaderBar = false; + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const callAction = () => actionByTestID(renderActions(), 'room-view-header-call'); + + it('is absent without media call permission and with calls disabled', () => { + expect(callAction()).toBeUndefined(); + }); + + it('is enabled with media call permission', () => { + mockMediaCall.hasMediaCallPermission = true; + + expect(callAction()).toMatchObject({ icon: 'phone', label: 'Call', disabled: false }); + }); + + it('is disabled during an active call', () => { + mockMediaCall.hasMediaCallPermission = true; + mockMediaCall.isInActiveCall = true; + + expect(callAction()?.disabled).toBe(true); + }); + + it('opens the media call sheet after the double tap window on a single tap', () => { + mockMediaCall.hasMediaCallPermission = true; + + callAction()?.onPress?.(); + + expect(mockMediaCall.openNewMediaCall).not.toHaveBeenCalled(); + act(() => jest.advanceTimersByTime(300)); + expect(mockMediaCall.openNewMediaCall).toHaveBeenCalled(); + expect(mockMediaCall.startCallImmediate).not.toHaveBeenCalled(); + }); + + it('starts the call immediately on a double tap', () => { + mockMediaCall.hasMediaCallPermission = true; + const onPress = callAction()?.onPress; + + onPress?.(); + onPress?.(); + + expect(mockMediaCall.startCallImmediate).toHaveBeenCalled(); + act(() => jest.advanceTimersByTime(300)); + expect(mockMediaCall.openNewMediaCall).not.toHaveBeenCalled(); + }); + + it('shows the video conference sheet when calls are enabled without media call permission', () => { + mockVideoConf.callEnabled = true; + + callAction()?.onPress?.(); + + expect(mockVideoConf.showInitCallActionSheet).toHaveBeenCalled(); + }); + + it('is disabled when the video conference tooltip is disabled', () => { + mockVideoConf.callEnabled = true; + mockVideoConf.disabledTooltip = true; + + expect(callAction()?.disabled).toBe(true); + }); + + it('is disabled by the e2ee warning', () => { + mockVideoConf.callEnabled = true; + mockButtonsData = { ...mockButtonsData, hasE2EEWarning: true }; + + expect(callAction()?.disabled).toBe(true); + }); + }); + + describe('thread', () => { + it('offers to follow a thread that is not followed', () => { + const [follow] = renderActions('tmid-1'); + + expect(follow).toMatchObject({ + label: 'Follow thread', + icon: 'notification-disabled', + testID: 'room-view-header-follow' + }); + follow.onPress?.(); + expect(logEvent).toHaveBeenCalledWith(events.ROOM_TOGGLE_FOLLOW_THREADS); + expect(toggleFollowThread).toHaveBeenCalledWith('tmid-1', false); + }); + + it('offers to unfollow a followed thread', () => { + mockFollowersByThread = { 'tmid-1': ['user-1'] }; + + const [unfollow] = renderActions('tmid-1'); + + expect(unfollow).toMatchObject({ label: 'Unfollow thread', icon: 'notification', testID: 'room-view-header-unfollow' }); + unfollow.onPress?.(); + expect(toggleFollowThread).toHaveBeenCalledWith('tmid-1', true); + }); + + it('reflects the follow state of the displayed thread and current user', () => { + mockFollowersByThread = { 'tmid-1': ['user-1'] }; + expect(renderActions('tmid-1')[0].testID).toBe('room-view-header-unfollow'); + expect(renderActions('tmid-2')[0].testID).toBe('room-view-header-follow'); + + mockUserId = 'user-2'; + expect(renderActions('tmid-1')[0].testID).toBe('room-view-header-follow'); + }); + }); + + describe('omnichannel', () => { + beforeEach(() => { + mockRoomState = { ...mockRoomState, room: { id: 'sub-1', t: 'l', departmentId: 'department-1' } }; + }); + + const menuOf = (tmid?: string) => { + const actions = renderActions(tmid); + expect(actions).toHaveLength(1); + return actions[0].menu ?? []; + }; + + it('shows a single more menu that logs when opened, even inside a thread', () => { + const [more] = renderActions('tmid-1'); + + expect(more).toMatchObject({ label: 'More', icon: 'kebab', testID: 'room-view-header-omnichannel-kebab' }); + more.onPress?.(); + expect(logEvent).toHaveBeenCalledWith(events.ROOM_SHOW_MORE_ACTIONS); + }); + + it('offers only the destructive close option when no capability is granted', () => { + const menu = menuOf(); + + expect(labelsOf(menu)).toEqual(['Close']); + expect(menu[0]).toMatchObject({ icon: 'chat-close', destructive: true }); + }); + + it('offers every option in order when all capabilities are granted', () => { + mockCanPlaceLivechatOnHold = true; + mockCanReturnQueue = true; + mockRoomState = { ...mockRoomState, canForwardGuest: true }; + + const menu = menuOf(); + + expect(labelsOf(menu)).toEqual(['Place chat on hold', 'Forward chat', 'Return to waiting line', 'Close']); + expect(menu.map(item => item.icon)).toEqual(['pause', 'chat-forward', 'move-to-the-queue', 'chat-close']); + }); + + it('places the chat on hold through the service', () => { + mockCanPlaceLivechatOnHold = true; + + menuOf()[0].onPress(); + + expect(placeLivechatOnHold).toHaveBeenCalledWith({ rid: 'rid-1', navigation: mockNavigation }); + }); + + it.each([ + [false, ['ForwardLivechatView', { rid: 'rid-1' }]], + [true, ['ModalStackNavigator', { screen: 'ForwardLivechatView', params: { rid: 'rid-1' } }]] + ])('navigates to the forward screen (master-detail: %s)', (isMasterDetail, expected) => { + mockIsMasterDetail = isMasterDetail; + mockRoomState = { ...mockRoomState, canForwardGuest: true }; + + menuOf()[0].onPress(); + + expect(mockNavigation.navigate).toHaveBeenCalledWith(...expected); + }); + + it('returns the inquiry only after the confirmation is accepted', async () => { + mockCanReturnQueue = true; + + menuOf()[0].onPress(); + + expect(returnLivechat).not.toHaveBeenCalled(); + await (showConfirmationAlert as jest.Mock).mock.calls[0][0].onPress(); + expect(returnLivechat).toHaveBeenCalledWith('rid-1', 'department-1'); + }); + + it('closes the chat with the room department and the request comment setting', () => { + mockLivechatRequestComment = true; + + menuOf()[0].onPress(); + + expect(closeLivechat).toHaveBeenCalledWith({ + rid: 'rid-1', + departmentId: 'department-1', + isMasterDetail: false, + livechatRequestComment: true, + navigation: mockNavigation + }); + }); + }); +}); diff --git a/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts b/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts deleted file mode 100644 index ac8cf10c0f6..00000000000 --- a/app/views/RoomView/hooks/__tests__/useRoomHeaderRightItems.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { renderHook } from '@testing-library/react-native'; -import { type NativeStackHeaderItem, type NativeStackHeaderItemMenu } from '@react-navigation/native-stack'; - -import { type RoomStore } from '~/views/RoomView/definitions'; -import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; -import { useRoomHeaderRightItems } from '../useRoomHeaderRightItems'; - -jest.mock('@react-navigation/native', () => ({ useNavigation: () => ({ navigate: jest.fn() }) })); -jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => false })); -jest.mock('~/lib/hooks/useSetting', () => ({ useSetting: () => false })); -jest.mock('~/lib/hooks/useAppSelector', () => ({ useAppSelector: () => 'user-1' })); -jest.mock('~/theme', () => ({ useTheme: () => ({ colors: {} }) })); -jest.mock('~/lib/methods/helpers/navigation/headerIcon', () => ({ - headerIcon: (name: string) => ({ type: 'image', source: name }) -})); - -let mockRoomState: Record; -jest.mock('zustand', () => ({ - useStore: (_store: unknown, selector: (state: Record) => unknown) => selector(mockRoomState) -})); -jest.mock('zustand/react/shallow', () => ({ useShallow: (selector: unknown) => selector })); - -let mockCanPlaceLivechatOnHold = false; -jest.mock('~/ee/omnichannel/hooks/useCanReturnQueue', () => ({ useCanReturnQueue: () => false })); -jest.mock('../useCanPlaceLivechatOnHold', () => ({ useCanPlaceLivechatOnHold: () => mockCanPlaceLivechatOnHold })); -jest.mock('../useThreadFollowing', () => ({ useThreadFollowing: () => false })); -jest.mock('~/views/RoomView/services/closeLivechat', () => ({ closeLivechat: jest.fn() })); -jest.mock('~/views/RoomView/services/placeLivechatOnHold', () => ({ placeLivechatOnHold: jest.fn() })); -jest.mock('~/lib/services/restApi', () => ({ returnLivechat: jest.fn() })); - -const mockGoSearchView = jest.fn(); -let mockButtonsData: Record; -jest.mock('../../components/RightButtons/useRoomRightButtonsData', () => ({ - useRoomRightButtonsData: () => mockButtonsData -})); -let mockCallPresent = false; -jest.mock('../../components/RightButtons/useHeaderCallPress', () => ({ - useHeaderCallPress: () => ({ callPresent: mockCallPresent, isCallDisabled: true, onPressCall: jest.fn() }) -})); - -const roomStore = {} as RoomStore; - -const moreMenuOf = (items: NativeStackHeaderItem[]) => { - const more = items[items.length - 1] as NativeStackHeaderItemMenu; - expect(more.type).toBe('menu'); - return more.menu.items.map(item => item as Extract); -}; - -describe('useRoomHeaderRightItems', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockCanPlaceLivechatOnHold = false; - mockCallPresent = false; - mockRoomState = { room: { t: 'c' }, membership: 'subscribed', canForwardGuest: false }; - mockButtonsData = { - threadsEnabled: false, - hasE2EEWarning: false, - tunread: [], - threadsAccessibilityLabel: 'Threads', - callAccessibilityLabel: 'Call', - goSearchView: mockGoSearchView - }; - }); - - it('lists search in the room more menu and runs the chosen action', () => { - const { result } = renderHook(() => useRoomHeaderRightItems('rid-1', undefined, roomStore)); - const actions = moreMenuOf(result.current); - - expect(actions.map(action => action.label)).toEqual(['Search messages']); - actions[0].onPress(); - expect(mockGoSearchView).toHaveBeenCalled(); - }); - - it('moves overflowing header actions into the menu, keeping disabled state', () => { - mockButtonsData = { ...mockButtonsData, threadsEnabled: true, hasE2EEWarning: true, canToggleEncryption: false }; - mockCallPresent = true; - - const { result } = renderHook(() => useRoomHeaderRightItems('rid-1', undefined, roomStore)); - const actions = moreMenuOf(result.current); - - expect(actions.map(action => [action.label, !!action.disabled])).toEqual([ - ['Encrypted', true], - ['Search messages', true] - ]); - }); - - it('shows omnichannel options with close as the destructive last item', () => { - mockRoomState = { room: { t: 'l' }, membership: 'subscribed', canForwardGuest: true }; - mockCanPlaceLivechatOnHold = true; - - const { result } = renderHook(() => useRoomHeaderRightItems('rid-1', undefined, roomStore)); - const actions = moreMenuOf(result.current); - - expect(actions.map(action => action.label)).toEqual(['Place chat on hold', 'Forward chat', 'Close']); - expect(actions[2].destructive).toBe(true); - actions[2].onPress(); - expect(closeLivechat).toHaveBeenCalledWith(expect.objectContaining({ rid: 'rid-1' })); - }); -}); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx index a7be644d1ca..57968be7143 100644 --- a/app/views/RoomView/hooks/useHeader.tsx +++ b/app/views/RoomView/hooks/useHeader.tsx @@ -9,6 +9,7 @@ import RoomHeader from '~/containers/RoomHeader'; import { getRoomTitle, hasNativeHeaderBar, isGroupChat } from '~/lib/methods/helpers'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; +import { nativeHeaderItems } from '~/lib/methods/helpers/navigation/headerActions'; import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; import LeftButtons from '../components/LeftButtons'; import RightButtons from '../components/RightButtons/RightButtons'; @@ -18,7 +19,7 @@ import { fromSubscription } from '../stores/RoomStoreContext'; import { useGoRoomActionsView } from './useGoRoomActionsView'; import { useNativeRoomHeader } from './useNativeRoomHeader'; import { useNativeBackButton } from './useNativeBackButton'; -import { useRoomHeaderRightItems } from './useRoomHeaderRightItems'; +import { useRoomHeaderActions } from './useRoomHeaderActions'; interface IUseHeaderParams { rid?: string; @@ -75,7 +76,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa const isMasterDetail = useMasterDetail(); const showsAvatar = isMasterDetail && !tmid; useNativeBackButton(!!rid && hasNativeHeaderBar && !showsAvatar, rid); - const nativeRightItems = useRoomHeaderRightItems(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); + const nativeRightActions = useRoomHeaderActions(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); useLayoutEffect(() => { if (!rid && hasNativeHeaderBar) { @@ -96,8 +97,11 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa }; navigation.setOptions( showsAvatar - ? { unstable_headerLeftItems: () => [avatarItem], unstable_headerRightItems: () => nativeRightItems } - : { unstable_headerRightItems: () => nativeRightItems } + ? { + unstable_headerLeftItems: () => [avatarItem], + unstable_headerRightItems: () => nativeHeaderItems(nativeRightActions) + } + : { unstable_headerRightItems: () => nativeHeaderItems(nativeRightActions) } ); return; } @@ -106,7 +110,7 @@ export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderPa headerLeft: () => , headerRight: () => }); - }, [rid, tmid, navigation, roomStore, nativeRightItems, showsAvatar]); + }, [rid, tmid, navigation, roomStore, nativeRightActions, showsAvatar]); useLayoutEffect(() => { if (!rid || hasNativeHeaderBar) { diff --git a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts b/app/views/RoomView/hooks/useRoomHeaderActions.ts similarity index 51% rename from app/views/RoomView/hooks/useRoomHeaderRightItems.ts rename to app/views/RoomView/hooks/useRoomHeaderActions.ts index 076b2922e48..cb1c2257888 100644 --- a/app/views/RoomView/hooks/useRoomHeaderRightItems.ts +++ b/app/views/RoomView/hooks/useRoomHeaderActions.ts @@ -1,17 +1,15 @@ import { useNavigation } from '@react-navigation/native'; -import { type NativeStackHeaderItem, type NativeStackHeaderItemMenuAction } from '@react-navigation/native-stack'; import { useStore } from 'zustand'; import { useShallow } from 'zustand/react/shallow'; import i18n from '~/i18n'; -import { type TIconsName } from '~/containers/CustomIcon'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { useCanReturnQueue } from '~/ee/omnichannel/hooks/useCanReturnQueue'; import { useSetting } from '~/lib/hooks/useSetting'; -import { showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; +import { hasNativeHeaderBar, showConfirmationAlert, showErrorAlert } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; -import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; +import { type IHeaderAction, type IHeaderMenuItem } from '~/lib/methods/helpers/navigation/headerActions'; import { toggleFollowThread } from '~/lib/methods/toggleFollowThread'; import { returnLivechat } from '~/lib/services/restApi'; import { getUserSelector } from '~/selectors/login'; @@ -29,19 +27,10 @@ import { useThreadFollowing } from './useThreadFollowing'; import { useRoomRightButtonsData } from '../components/RightButtons/useRoomRightButtonsData'; import { useHeaderCallPress } from '../components/RightButtons/useHeaderCallPress'; -const EMPTY_ITEMS: NativeStackHeaderItem[] = []; +const EMPTY_ACTIONS: IHeaderAction[] = []; const VISIBLE_ORDER: TRoomHeaderActionKey[] = ['encryption', 'notifications', 'call', 'threads']; -interface IRoomHeaderAction { - label: string; - icon: TIconsName; - disabled: boolean; - badge?: { value: number; style: { backgroundColor: string } }; - tintColor?: string; - onPress: () => void; -} - -const useOmnichannelRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): NativeStackHeaderItem[] => { +const useOmnichannelActions = (rid: string, roomStore: RoomStore, enabled: boolean): IHeaderAction[] => { const navigation = useNavigation(); const isMasterDetail = useMasterDetail(); const livechatRequestComment = useSetting('Livechat_request_comment_when_closing_conversation') as boolean; @@ -54,7 +43,11 @@ const useOmnichannelRightItems = (rid: string, roomStore: RoomStore, enabled: bo const canReturnQueue = useCanReturnQueue(enabled); const canPlaceLivechatOnHold = useCanPlaceLivechatOnHold(roomStore); - const handleReturnLivechat = () => { + if (!enabled) { + return EMPTY_ACTIONS; + } + + const returnInquiry = () => { showConfirmationAlert({ message: i18n.t('Would_you_like_to_return_the_inquiry'), confirmationText: i18n.t('Yes'), @@ -68,81 +61,65 @@ const useOmnichannelRightItems = (rid: string, roomStore: RoomStore, enabled: bo }); }; - const moreActions: NativeStackHeaderItemMenuAction[] = []; - if (canPlaceLivechatOnHold) { - moreActions.push({ - type: 'action', - label: i18n.t('Place_chat_on_hold'), - icon: headerIcon('pause'), - onPress: () => placeLivechatOnHold({ rid, navigation }) - }); - } - if (canForwardGuest) { - moreActions.push({ - type: 'action', - label: i18n.t('Forward_Chat'), - icon: headerIcon('chat-forward'), - onPress: () => navigateToScreen({ navigation, isMasterDetail, screen: 'ForwardLivechatView', params: { rid } }) - }); - } - if (canReturnQueue) { - moreActions.push({ - type: 'action', - label: i18n.t('Return_to_waiting_line'), - icon: headerIcon('move-to-the-queue'), - onPress: handleReturnLivechat - }); - } - moreActions.push({ - type: 'action', - label: i18n.t('Close'), - icon: headerIcon('chat-close'), - destructive: true, - onPress: () => closeLivechat({ rid, departmentId, isMasterDetail, livechatRequestComment, navigation }) - }); - - if (!enabled) { - return EMPTY_ITEMS; - } + const menu: IHeaderMenuItem[] = [ + ...(canPlaceLivechatOnHold + ? [{ label: i18n.t('Place_chat_on_hold'), icon: 'pause' as const, onPress: () => placeLivechatOnHold({ rid, navigation }) }] + : []), + ...(canForwardGuest + ? [ + { + label: i18n.t('Forward_Chat'), + icon: 'chat-forward' as const, + onPress: () => navigateToScreen({ navigation, isMasterDetail, screen: 'ForwardLivechatView', params: { rid } }) + } + ] + : []), + ...(canReturnQueue + ? [{ label: i18n.t('Return_to_waiting_line'), icon: 'move-to-the-queue' as const, onPress: returnInquiry }] + : []), + { + label: i18n.t('Close'), + icon: 'chat-close', + destructive: true, + onPress: () => closeLivechat({ rid, departmentId, isMasterDetail, livechatRequestComment, navigation }) + } + ]; return [ { - type: 'menu', label: i18n.t('More'), - accessibilityLabel: i18n.t('More'), - icon: headerIcon('kebab'), - menu: { items: moreActions } + icon: 'kebab', + testID: 'room-view-header-omnichannel-kebab', + onPress: () => logEvent(events.ROOM_SHOW_MORE_ACTIONS), + menu } ]; }; -const useThreadRightItems = (tmid: string | undefined, enabled: boolean): NativeStackHeaderItem[] => { +const useThreadActions = (tmid: string | undefined, enabled: boolean): IHeaderAction[] => { const userId = useAppSelector(state => getUserSelector(state).id); const isFollowingThread = useThreadFollowing(tmid, userId); - const onToggleFollowThread = () => { - logEvent(events.ROOM_TOGGLE_FOLLOW_THREADS); - if (tmid) { - toggleFollowThread(tmid, isFollowingThread); - } - }; - if (!enabled) { - return EMPTY_ITEMS; + return EMPTY_ACTIONS; } return [ { - type: 'button', label: i18n.t(isFollowingThread ? 'Unfollow_thread' : 'Follow_thread'), - accessibilityLabel: i18n.t(isFollowingThread ? 'Unfollow_thread' : 'Follow_thread'), - icon: headerIcon(isFollowingThread ? 'notification' : 'notification-disabled'), - onPress: onToggleFollowThread + icon: isFollowingThread ? 'notification' : 'notification-disabled', + testID: isFollowingThread ? 'room-view-header-unfollow' : 'room-view-header-follow', + onPress: () => { + logEvent(events.ROOM_TOGGLE_FOLLOW_THREADS); + if (tmid) { + toggleFollowThread(tmid, isFollowingThread); + } + } } ]; }; -const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): NativeStackHeaderItem[] => { +const useRoomActions = (rid: string, roomStore: RoomStore, enabled: boolean): IHeaderAction[] => { const { theme, colors } = useTheme(); const { threadsEnabled, @@ -161,100 +138,92 @@ const useRoomRightItems = (rid: string, roomStore: RoomStore, enabled: boolean): goE2EEToggleRoomView, threadsAccessibilityLabel } = useRoomRightButtonsData(rid, roomStore); - const { callPresent: callPresentRaw, isCallDisabled, onPressCall } = useHeaderCallPress(rid); - const callPresent = !isSelfDm && callPresentRaw; + const { callPresent, isCallDisabled, onPressCall } = useHeaderCallPress(rid); - const { visibleKeys, overflowKeys } = splitRoomHeaderActions({ + if (!enabled) { + return EMPTY_ACTIONS; + } + + const present: Partial> = { threads: threadsEnabled, - call: callPresent, + call: !isSelfDm && callPresent, encryption: hasE2EEWarning, notifications: issuesWithNotifications || disableNotifications - }); - - const tunreadBadge = - threadsEnabled && tunread.length - ? { - value: tunread.length, - style: { backgroundColor: getUnreadStyle({ tunread, tunreadUser, tunreadGroup, theme }).backgroundColor as string } - } - : undefined; + }; - const actions: Record = { + const actions: Record = { threads: { label: threadsAccessibilityLabel, icon: 'threads', + testID: 'room-view-header-threads', disabled: hasE2EEWarning, - badge: tunreadBadge, + badge: tunread.length + ? { + value: tunread.length, + color: getUnreadStyle({ tunread, tunreadUser, tunreadGroup, theme }).backgroundColor as string + } + : undefined, onPress: goThreadsView }, call: { label: callAccessibilityLabel, icon: 'phone', + testID: 'room-view-header-call', disabled: hasE2EEWarning || isCallDisabled, onPress: onPressCall }, encryption: { label: i18n.t('Encrypted'), icon: 'encrypted', + testID: 'room-view-header-encryption', disabled: !canToggleEncryption, onPress: goE2EEToggleRoomView }, notifications: { label: i18n.t('Troubleshooting'), icon: 'notification-disabled', + testID: 'room-view-push-troubleshoot', tintColor: issuesWithNotifications ? colors.fontDanger : undefined, disabled: hasE2EEWarning, onPress: navigateToNotificationOrPushTroubleshoot } }; - const overflowActions: NativeStackHeaderItemMenuAction[] = [ - ...overflowKeys.map((key): NativeStackHeaderItemMenuAction => { - const { label, icon, disabled, onPress } = actions[key]; - return { type: 'action', label, icon: headerIcon(icon), disabled, onPress }; - }), - { - type: 'action', - label: i18n.t('Search_Messages'), - icon: headerIcon('search'), - disabled: hasE2EEWarning, - onPress: goSearchView - } - ]; + const searchAction: IHeaderAction = { + label: i18n.t('Search_Messages'), + icon: 'search', + testID: 'room-view-search', + disabled: hasE2EEWarning, + onPress: goSearchView + }; - if (!enabled) { - return EMPTY_ITEMS; + if (!hasNativeHeaderBar) { + return [...VISIBLE_ORDER.filter(key => present[key]).map(key => actions[key]), searchAction]; } - const items: NativeStackHeaderItem[] = VISIBLE_ORDER.filter(key => visibleKeys.includes(key)).map(key => { - const { label, icon, disabled, badge, tintColor, onPress } = actions[key]; - return { - type: 'button', - label, - accessibilityLabel: label, - icon: headerIcon(icon), - disabled, - badge, - tintColor, - onPress - }; - }); - items.push({ - type: 'menu', - label: i18n.t('More'), - accessibilityLabel: i18n.t('More'), - icon: headerIcon('kebab'), - menu: { items: overflowActions } + const { visibleKeys, overflowKeys } = splitRoomHeaderActions(present); + const toMenuItem = ({ label, icon, disabled, onPress }: IHeaderAction): IHeaderMenuItem => ({ + label, + icon, + disabled, + onPress: onPress ?? (() => {}) }); - return items; + return [ + ...VISIBLE_ORDER.filter(key => visibleKeys.includes(key)).map(key => actions[key]), + { + label: i18n.t('More'), + icon: 'kebab', + menu: [...overflowKeys.map(key => toMenuItem(actions[key])), toMenuItem(searchAction)] + } + ]; }; -export const useRoomHeaderRightItems = ( +export const useRoomHeaderActions = ( rid: string | undefined, tmid: string | undefined, roomStore: RoomStore -): NativeStackHeaderItem[] => { +): IHeaderAction[] => { const { t, status, membership } = useStore( roomStore, useShallow(s => ({ @@ -266,18 +235,18 @@ export const useRoomHeaderRightItems = ( const mode = getRoomHeaderMode({ rid, tmid, t, status, membership }); - const omnichannelItems = useOmnichannelRightItems(rid ?? '', roomStore, mode === 'omnichannel'); - const threadItems = useThreadRightItems(tmid, mode === 'thread'); - const roomItems = useRoomRightItems(rid ?? '', roomStore, mode === 'room'); + const omnichannelActions = useOmnichannelActions(rid ?? '', roomStore, mode === 'omnichannel'); + const threadActions = useThreadActions(tmid, mode === 'thread'); + const roomActions = useRoomActions(rid ?? '', roomStore, mode === 'room'); if (mode === 'omnichannel') { - return omnichannelItems; + return omnichannelActions; } if (mode === 'thread') { - return threadItems; + return threadActions; } if (mode === 'room') { - return roomItems; + return roomActions; } - return EMPTY_ITEMS; + return EMPTY_ACTIONS; }; diff --git a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx index 53bae02eb34..fe1bb12d2c5 100644 --- a/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx +++ b/app/views/RoomsListView/hooks/__tests__/useHeader.test.tsx @@ -23,6 +23,16 @@ jest.mock('~/lib/methods/helpers', () => ) ); +jest.mock('~/lib/methods/helpers/deviceInfo', () => + Object.defineProperties( + { ...jest.requireActual('~/lib/methods/helpers/deviceInfo') }, + { + hasNativeHeaderBar: { get: () => mockIsIOS, configurable: true }, + isTablet: { get: () => mockIsTablet, configurable: true } + } + ) +); + jest.mock('~/lib/methods/helpers/navigation/headerIcon', () => ({ headerIcon: (name: string) => ({ type: 'image', source: { uri: name } }) })); @@ -127,7 +137,7 @@ describe('RoomsListView useHeader', () => { expect(options.headerSubtitle).toBe(expectedSubtitle); }); - it('builds the right cluster in create, push-troubleshoot, directory order', () => { + it('builds the right cluster in push-troubleshoot, create, directory order', () => { mockAppState = { ...mockAppState, troubleshootingNotification: { issuesWithNotifications: true } }; renderUseHeader(); @@ -135,7 +145,7 @@ describe('RoomsListView useHeader', () => { const options = mockSetOptions.mock.calls[0][0]; const rightItems = options.unstable_headerRightItems(); const labels = rightItems.map((item: { accessibilityLabel: string }) => item.accessibilityLabel); - expect(labels).toEqual(['Create new channel, team, direct message or discussion', 'Troubleshooting', 'Directory']); + expect(labels).toEqual(['Troubleshooting', 'Create new channel, team, direct message or discussion', 'Directory']); }); it('renders only buttons when push troubleshooting is absent', () => { diff --git a/app/views/RoomsListView/hooks/useHeader.tsx b/app/views/RoomsListView/hooks/useHeader.tsx index d7e39088f4a..7717e32170d 100644 --- a/app/views/RoomsListView/hooks/useHeader.tsx +++ b/app/views/RoomsListView/hooks/useHeader.tsx @@ -5,7 +5,6 @@ import { type KeyboardFocus } from 'react-native-external-keyboard'; import { type SearchBarCommands } from 'react-native-screens'; import { showActionSheetRef } from '~/containers/ActionSheet'; -import { type TIconsName } from '~/containers/CustomIcon'; import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import i18n from '~/i18n'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; @@ -14,7 +13,7 @@ import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; import { usePermissions } from '~/lib/hooks/usePermissions'; import { hasNativeHeaderBar, isTablet } from '~/lib/methods/helpers'; import { events, logEvent } from '~/lib/methods/helpers/log'; -import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; +import { headerLeftActions, headerRightActions, type IHeaderAction } from '~/lib/methods/helpers/navigation/headerActions'; import { getUserSelector } from '~/selectors/login'; import { useTheme } from '~/theme'; import RoomsListHeaderView from '../components/Header'; @@ -22,15 +21,59 @@ import ServersList from '../components/ServersList'; import { RoomsSearchContext } from '../contexts/RoomsSearchProvider'; import { useRoomsListSubtitle } from './useRoomsListSubtitle'; -interface IHeaderRightAction { - present: boolean; - icon: TIconsName; - accessibilityLabel: string; - tintColor?: string; +interface IRightActionsParams { + issuesWithNotifications?: boolean; + canCreateRoom: boolean; disabled?: boolean; - onPress: () => void; + dangerColor: string; + onTroubleshoot: () => void; + onCreate: () => void; + onSearch: () => void; + onDirectory: () => void; } +const getRightActions = ({ + issuesWithNotifications, + canCreateRoom, + disabled, + dangerColor, + onTroubleshoot, + onCreate, + onSearch, + onDirectory +}: IRightActionsParams): IHeaderAction[] => { + const troubleshoot: IHeaderAction = { + label: i18n.t('Troubleshooting'), + icon: 'notification-disabled', + tintColor: dangerColor, + testID: 'rooms-list-view-push-troubleshoot', + onPress: onTroubleshoot + }; + const create: IHeaderAction = { + label: i18n.t('Create_new_channel_team_dm_discussion'), + icon: hasNativeHeaderBar ? 'create' : 'add', + testID: 'rooms-list-view-create-channel', + disabled, + onPress: onCreate + }; + const search: IHeaderAction = { + label: i18n.t('Search'), + icon: 'search', + testID: 'rooms-list-view-search', + disabled, + legacyHeaderOnly: true, + onPress: onSearch + }; + const directory: IHeaderAction = { + label: i18n.t('Directory'), + icon: 'directory', + testID: 'rooms-list-view-directory', + disabled, + onPress: onDirectory + }; + return [...(issuesWithNotifications ? [troubleshoot] : []), ...(canCreateRoom ? [create] : []), search, directory]; +}; + const getScreenFocusNavigation = (navigation: any, isMasterDetail: boolean) => { if (!isMasterDetail) { return navigation; @@ -107,20 +150,9 @@ export const useHeader = () => { }, [isMasterDetail, navigation]); useLayoutEffect(() => { - const headerLeft = () => ( - navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) - : () => navigation.toggleDrawer() - } - badge={() => (badgeColor ? : null)} - disabled={disabled} - /> - ); + const onDrawerPress = isMasterDetail + ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) + : () => navigation.toggleDrawer(); if (searchEnabled && !hasNativeHeaderBar) { const searchOptions = { @@ -139,33 +171,26 @@ export const useHeader = () => { return; } - if (hasNativeHeaderBar) { - const actions = ( - [ - { - present: canCreateRoom, - icon: 'create', - accessibilityLabel: i18n.t('Create_new_channel_team_dm_discussion'), - disabled, - onPress: goToNewMessage - }, - { - present: issuesWithNotifications, - icon: 'notification-disabled', - accessibilityLabel: i18n.t('Troubleshooting'), - tintColor: colors.fontDanger, - onPress: navigateToPushTroubleshootView - }, - { - present: true, - icon: 'directory', - accessibilityLabel: i18n.t('Directory'), - disabled, - onPress: goDirectory - } - ] satisfies IHeaderRightAction[] - ).filter(action => action.present); + const rightActions = getRightActions({ + issuesWithNotifications, + canCreateRoom, + disabled, + dangerColor: colors.fontDanger, + onTroubleshoot: navigateToPushTroubleshootView, + onCreate: goToNewMessage, + onSearch: startSearch, + onDirectory: goDirectory + }); + if (hasNativeHeaderBar) { + const drawerAction: IHeaderAction = { + label: i18n.t('Menu'), + icon: 'hamburguer', + disabled, + badge: badgeColor ? { color: badgeColor } : undefined, + onPress: onDrawerPress + }; + const cancelSearchAction: IHeaderAction = { label: i18n.t('Cancel'), onPress: stopSearch }; navigation.setOptions({ headerTransparent: true, headerStyle: { backgroundColor: `${colors.surfaceNeutral}B3` }, @@ -183,80 +208,25 @@ export const useHeader = () => { onChangeText: (event: { nativeEvent: { text: string } }) => search(event.nativeEvent.text), onCancelButtonPress: stopSearch }, - unstable_headerLeftItems: () => [ - { - type: 'button', - label: i18n.t('Menu'), - accessibilityLabel: i18n.t('Menu'), - icon: headerIcon('hamburguer'), - disabled, - badge: badgeColor ? { value: '', style: { backgroundColor: badgeColor } } : undefined, - onPress: isMasterDetail - ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) - : () => navigation.toggleDrawer() - } - ], - unstable_headerRightItems: () => - isTablet && searchEnabled - ? [ - { - type: 'button' as const, - label: i18n.t('Cancel'), - accessibilityLabel: i18n.t('Cancel'), - onPress: stopSearch - } - ] - : actions.map(action => ({ - type: 'button' as const, - label: action.accessibilityLabel, - accessibilityLabel: action.accessibilityLabel, - icon: headerIcon(action.icon), - tintColor: action.tintColor, - disabled: action.disabled, - onPress: action.onPress - })) + ...headerLeftActions([drawerAction]), + ...headerRightActions(isTablet && searchEnabled ? [cancelSearchAction] : rightActions) }); return; } const options = { - headerLeft, + headerLeft: () => ( + (badgeColor ? : null)} + disabled={disabled} + /> + ), headerTitle: () => , - headerRight: () => ( - - {issuesWithNotifications ? ( - - ) : null} - {canCreateRoom ? ( - - ) : null} - - - - ) + ...headerRightActions(rightActions) }; navigation.setOptions(options); diff --git a/app/views/SelectedUsersView/index.tsx b/app/views/SelectedUsersView/index.tsx index 1a95a9e88a4..d73d2982571 100644 --- a/app/views/SelectedUsersView/index.tsx +++ b/app/views/SelectedUsersView/index.tsx @@ -10,7 +10,6 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { addUser, removeUser, reset } from '~/actions/selectedUsers'; import ActivityIndicator from '~/containers/ActivityIndicator'; -import * as HeaderButton from '~/containers/Header/components/HeaderButton'; import * as List from '~/containers/List'; import { sendLoadingEvent } from '~/containers/Loading'; import SafeAreaView from '~/containers/SafeAreaView'; @@ -18,6 +17,7 @@ import RowSeparator from '~/containers/NativeListRow/components/Separator'; import { useListBackgroundColor } from '~/containers/NativeListRow/hooks/useListBackgroundColor'; import I18n from '~/i18n'; import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { headerRightActions } from '~/lib/methods/helpers/navigation/headerActions'; import database from '~/lib/database'; import UserItem from '~/containers/UserItem'; import { type ISelectedUser } from '~/reducers/selectedUsers'; @@ -114,13 +114,9 @@ const SelectedUsersView = () => { const showHeaderButton = (!maxUsers || showButton || (isGroupChat() && users.length > 1)) && !!buttonTitle; navigation.setOptions({ title: titleHeader, - headerRight: showHeaderButton - ? () => ( - - - - ) - : () => null, + ...headerRightActions( + showHeaderButton ? [{ label: buttonTitle, testID: 'selected-users-view-submit', onPress: nextActionHeader }] : [] + ), ...(hasNativeHeaderBar ? { headerTransparent: true, diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index 3a7b4ef6a4e..0afbbfc9d0f 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -12,7 +12,8 @@ import { Component, createRef } from 'react'; import database from '~/lib/database'; import I18n from '~/i18n'; -import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { outsideHeaderLeftClose, stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { headerRightActions } from '~/lib/methods/helpers/navigation/headerActions'; import DirectoryItem, { ROW_HEIGHT } from '~/containers/DirectoryItem'; import RowSeparator from '~/containers/NativeListRow/components/Separator'; import ServerItem from '~/containers/ServerItem'; @@ -219,28 +220,7 @@ class ShareListView extends Component { const { searching } = this.state; const { navigation } = this.props; - if (hasNativeHeaderBar) { - navigation.setOptions({ - headerLeft: () => ( - - - - ), - headerTitle: I18n.t('Send_to'), - headerSearchBarOptions: this.airGappedReadOnly - ? undefined - : stackedSearchBarOptions({ - ref: this.searchBarRef, - onFocus: this.initSearch, - onChangeText: this.search, - onCancel: this.cancelSearch - }), - headerRight: () => null - }); - return; - } - - if (searching) { + if (searching && !hasNativeHeaderBar) { navigation.setOptions({ headerLeft: () => ( @@ -254,18 +234,23 @@ class ShareListView extends Component { } navigation.setOptions({ - headerLeft: () => ( - - - - ), + ...outsideHeaderLeftClose(this.closeShareExtension, 'share-extension-close'), headerTitle: I18n.t('Send_to'), - headerRight: () => - this.airGappedReadOnly ? null : ( - - - - ) + ...(hasNativeHeaderBar && { + headerSearchBarOptions: this.airGappedReadOnly + ? undefined + : stackedSearchBarOptions({ + ref: this.searchBarRef, + onFocus: this.initSearch, + onChangeText: this.search, + onCancel: this.cancelSearch + }) + }), + ...headerRightActions( + this.airGappedReadOnly + ? [] + : [{ label: I18n.t('Search'), icon: 'search', legacyHeaderOnly: true, onPress: this.initSearch }] + ) }); }; diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 915530a0796..c3c2885686b 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -24,6 +24,7 @@ import { withMasterDetail } from '../lib/hooks/useMasterDetail'; import { BASE_ROW_HEIGHT, BASE_ROW_HEIGHT_CONDENSED } from '../lib/hooks/useResponsiveLayout/useResponsiveLayout'; import I18n from '../i18n'; import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { headerRightActions, type IHeaderAction } from '~/lib/methods/helpers/navigation/headerActions'; import database from '../lib/database'; import { CustomIcon } from '../containers/CustomIcon'; import RoomItem from '../containers/RoomItem'; @@ -232,37 +233,7 @@ class TeamChannelsView extends Component this.goRoomActionsView(), - headerSearchBarOptions: stackedSearchBarOptions({ - ref: this.searchBarRef, - onFocus: this.onSearchPress, - onChangeText: this.onSearchChangeText, - onCancel: this.onCancelSearchPress - }), - headerRight: () => ( - - {showCreate ? ( - - navigation.navigate('AddChannelTeamView', { teamId: this.teamId, rid: this.team.rid, t: this.team.t as any }) - } - /> - ) : null} - - ) - }; - navigation.setOptions(options); - return; - } - - if (isSearching) { + if (isSearching && !hasNativeHeaderBar) { const options: NativeStackNavigationOptions = { headerLeft: () => ( @@ -277,28 +248,47 @@ class TeamChannelsView extends Component ( - - ), - headerRight: () => ( - - {showCreate ? ( - - navigation.navigate('AddChannelTeamView', { teamId: this.teamId, rid: this.team.rid, t: this.team.t as any }) - } - /> - ) : null} - - - ) + const createAction: IHeaderAction = { + label: I18n.t('Add_Channel_to_Team'), + icon: 'create', + testID: 'team-channels-view-create', + onPress: () => navigation.navigate('AddChannelTeamView', { teamId: this.teamId, rid: this.team.rid, t: this.team.t as any }) + }; + const searchAction: IHeaderAction = { + label: I18n.t('Search'), + icon: 'search', + testID: 'team-channels-view-search', + legacyHeaderOnly: true, + onPress: this.onSearchPress }; - navigation.setOptions(options); + navigation.setOptions({ + headerLeft: undefined, + ...(hasNativeHeaderBar + ? { + headerTitle: getRoomTitle(team), + headerSubtitle: team.topic, + onHeaderTitlePress: () => this.goRoomActionsView(), + headerSearchBarOptions: stackedSearchBarOptions({ + ref: this.searchBarRef, + onFocus: this.onSearchPress, + onChangeText: this.onSearchChangeText, + onCancel: this.onCancelSearchPress + }) + } + : { + headerTitle: () => ( + + ) + }), + ...headerRightActions(showCreate ? [createAction, searchAction] : [searchAction]) + }); }; onSearchPress = () => { diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 4449a5b1338..9df5a8509a4 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -8,11 +8,10 @@ import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-cont import { type SearchBarCommands } from 'react-native-screens'; import { Component, createRef } from 'react'; -import { showActionSheetRef } from '~/containers/ActionSheet'; -import { CustomIcon } from '~/containers/CustomIcon'; import ActivityIndicator from '~/containers/ActivityIndicator'; import I18n from '~/i18n'; -import { stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { outsideHeaderLeftClose, stackedSearchBarOptions } from '~/lib/methods/helpers/navigation'; +import { headerRightActions } from '~/lib/methods/helpers/navigation/headerActions'; import database from '~/lib/database'; import { sanitizeLikeString } from '~/lib/database/utils'; import buildMessage from '~/lib/methods/helpers/buildMessage'; @@ -46,7 +45,6 @@ import { toggleFollowThread as toggleFollowThreadService } from '~/lib/methods/t import UserPreferences from '~/lib/methods/userPreferences'; import Navigation from '~/lib/navigation/appNavigation'; import { withMasterDetail } from '~/lib/hooks/useMasterDetail'; -import { headerIcon } from '~/lib/methods/helpers/navigation/headerIcon'; const API_FETCH_COUNT = 50; const THREADS_FILTER = 'threadsFilter'; @@ -124,40 +122,7 @@ class ThreadMessagesView extends Component [ - { - type: 'menu', - label: I18n.t('Filter'), - accessibilityLabel: I18n.t('Filter'), - icon: headerIcon('filter'), - menu: { - items: [Filter.All, Filter.Following, Filter.Unread].map(filter => ({ - type: 'action' as const, - label: I18n.t(filter), - state: currentFilter === filter ? ('on' as const) : ('off' as const), - onPress: () => this.onFilterSelected(filter) - })) - } - } - ] - }; - if (isMasterDetail) { - options.headerLeft = () => ; - } - return options; - } - - if (isSearching) { + if (isSearching && !hasNativeHeaderBar) { return { headerLeft: () => ( @@ -171,34 +136,38 @@ class ThreadMessagesView extends Component ( - - - currentFilter !== Filter.All ? : null - } - /> - - - ) + ...(hasNativeHeaderBar && { + headerTransparent: true, + headerSearchBarOptions: stackedSearchBarOptions({ + ref: this.searchBarRef, + onFocus: this.onSearchPress, + onChangeText: this.onSearchChangeText, + onCancel: this.onCancelSearchPress + }) + }), + ...(isMasterDetail ? outsideHeaderLeftClose(() => navigation.pop()) : { headerLeft: undefined }), + ...headerRightActions([ + { + label: I18n.t('Filter'), + icon: 'filter', + badge: currentFilter !== Filter.All ? { color: colors[theme].buttonBackgroundDangerDefault } : undefined, + menu: [Filter.All, Filter.Following, Filter.Unread].map(filter => ({ + label: I18n.t(filter), + checked: currentFilter === filter, + onPress: () => this.onFilterSelected(filter) + })) + }, + { + label: I18n.t('Search'), + icon: 'search', + testID: 'thread-messages-view-search-icon', + legacyHeaderOnly: true, + onPress: this.onSearchPress + } + ]) }; - - if (isMasterDetail) { - options.headerLeft = () => ; - } - - return options; }; setHeader = () => { @@ -478,29 +447,6 @@ class ThreadMessagesView extends Component { - const { currentFilter } = this.state; - showActionSheetRef({ - options: [ - { - title: I18n.t(Filter.All), - right: currentFilter === Filter.All ? () => : undefined, - onPress: () => this.onFilterSelected(Filter.All) - }, - { - title: I18n.t(Filter.Following), - right: currentFilter === Filter.Following ? () => : undefined, - onPress: () => this.onFilterSelected(Filter.Following) - }, - { - title: I18n.t(Filter.Unread), - right: currentFilter === Filter.Unread ? () => : undefined, - onPress: () => this.onFilterSelected(Filter.Unread) - } - ] - }); - }; - onFilterSelected = (filter: Filter) => { const { messages, subscription } = this.state; const displayingThreads = this.getFilteredThreads(messages, subscription, filter); From 209f19cff5d29abf7c1609c01d2c69c6ff57fe4b Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 1 Oct 2026 10:19:55 -0300 Subject: [PATCH 119/120] refactor: move RoomView header hooks into leaf components --- .../RoomView/__tests__/RoomView.test.tsx | 2 +- .../__tests__/roomAndThreadScreens.test.tsx | 2 +- .../components/RightButtons/RightButtons.tsx | 9 +- .../RoomView/components/RoomViewContent.tsx | 62 ++++++ .../RoomViewHeader/NativeHeaderItems.tsx | 40 ++++ .../RoomViewHeader/RoomHeaderActions.tsx | 50 +++++ .../RoomViewHeader/RoomViewHeader.tsx | 42 ++++ .../__tests__/RoomViewHeader.test.tsx | 176 ++++++++++++++++ .../hooks/__tests__/useHeader.test.tsx | 191 ------------------ .../hooks/__tests__/useJsRoomHeader.test.tsx | 99 +++++++++ .../__tests__/useNativeBackButton.test.ts | 11 +- .../__tests__/useNativeRoomHeader.test.ts | 26 +-- .../__tests__/useRoomHeaderActions.test.ts | 77 ++++--- app/views/RoomView/hooks/useHeader.tsx | 142 ------------- app/views/RoomView/hooks/useHeaderFields.ts | 47 +++++ app/views/RoomView/hooks/useJsRoomHeader.tsx | 66 ++++++ .../RoomView/hooks/useNativeBackButton.ts | 9 +- .../RoomView/hooks/useNativeHeaderItems.tsx | 26 +++ .../RoomView/hooks/useNativeRoomHeader.ts | 42 ++-- .../RoomView/hooks/useRoomHeaderActions.ts | 51 +---- app/views/RoomView/index.tsx | 59 +----- 21 files changed, 711 insertions(+), 518 deletions(-) create mode 100644 app/views/RoomView/components/RoomViewContent.tsx create mode 100644 app/views/RoomView/components/RoomViewHeader/NativeHeaderItems.tsx create mode 100644 app/views/RoomView/components/RoomViewHeader/RoomHeaderActions.tsx create mode 100644 app/views/RoomView/components/RoomViewHeader/RoomViewHeader.tsx create mode 100644 app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx delete mode 100644 app/views/RoomView/hooks/__tests__/useHeader.test.tsx create mode 100644 app/views/RoomView/hooks/__tests__/useJsRoomHeader.test.tsx delete mode 100644 app/views/RoomView/hooks/useHeader.tsx create mode 100644 app/views/RoomView/hooks/useHeaderFields.ts create mode 100644 app/views/RoomView/hooks/useJsRoomHeader.tsx create mode 100644 app/views/RoomView/hooks/useNativeHeaderItems.tsx diff --git a/app/views/RoomView/__tests__/RoomView.test.tsx b/app/views/RoomView/__tests__/RoomView.test.tsx index 29631bbaae8..db38cd6b187 100644 --- a/app/views/RoomView/__tests__/RoomView.test.tsx +++ b/app/views/RoomView/__tests__/RoomView.test.tsx @@ -29,7 +29,7 @@ jest.mock('../components/EncryptedRoom', () => { const { View: RNView } = require('react-native'); return { EncryptedRoom: () => createElement(RNView, { testID: 'encrypted-screen' }) }; }); -jest.mock('../hooks/useHeader', () => ({ useHeader: jest.fn() })); +jest.mock('../components/RoomViewHeader/RoomViewHeader', () => ({ RoomViewHeader: () => null })); jest.mock('../hooks/useE2EEStatus', () => ({ useE2EEStatus: jest.fn(() => ({ showMissingE2EEKey: false, showE2EEDisabledRoom: false, hasE2EEWarning: false })) })); diff --git a/app/views/RoomView/__tests__/roomAndThreadScreens.test.tsx b/app/views/RoomView/__tests__/roomAndThreadScreens.test.tsx index 7698be00c29..64a0ad7793a 100644 --- a/app/views/RoomView/__tests__/roomAndThreadScreens.test.tsx +++ b/app/views/RoomView/__tests__/roomAndThreadScreens.test.tsx @@ -74,7 +74,7 @@ jest.mock('~/containers/MessageComposer', () => ({ MessageComposerContainer: 'Me jest.mock('../components/RoomMessageActions', () => ({ RoomMessageActions: 'RoomMessageActions' })); jest.mock('../components/UploadProgress', () => ({ __esModule: true, default: 'UploadProgress' })); jest.mock('../hooks/useRoomMessageHandlers', () => ({ useRoomMessageHandlers: () => ({}) })); -jest.mock('../hooks/useHeader', () => ({ useHeader: jest.fn() })); +jest.mock('../components/RoomViewHeader/RoomViewHeader', () => ({ RoomViewHeader: () => null })); jest.mock('../hooks/useRoomSubscription', () => ({ useRoomSubscription: jest.fn() })); jest.mock('../hooks/useRoomAudioLifecycle', () => ({ useRoomAudioLifecycle: jest.fn() })); jest.mock('../hooks/useRoomRemoved', () => ({ useRoomRemoved: jest.fn() })); diff --git a/app/views/RoomView/components/RightButtons/RightButtons.tsx b/app/views/RoomView/components/RightButtons/RightButtons.tsx index 04c8537edc4..edef03cdad6 100644 --- a/app/views/RoomView/components/RightButtons/RightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/RightButtons.tsx @@ -2,7 +2,7 @@ import { type ReactElement } from 'react'; import { HeaderActions } from '~/lib/methods/helpers/navigation/headerActions'; import { type RoomStore } from '~/views/RoomView/definitions'; -import { useRoomHeaderActions } from '~/views/RoomView/hooks/useRoomHeaderActions'; +import { RoomHeaderActions } from '~/views/RoomView/components/RoomViewHeader/RoomHeaderActions'; interface IRightButtonsProps { rid?: string; @@ -10,9 +10,8 @@ interface IRightButtonsProps { roomStore: RoomStore; } -const RightButtons = ({ rid, tmid, roomStore }: IRightButtonsProps): ReactElement | null => { - const actions = useRoomHeaderActions(rid, tmid, roomStore); - return ; -}; +const RightButtons = ({ rid, tmid, roomStore }: IRightButtonsProps): ReactElement => ( + +); export default RightButtons; diff --git a/app/views/RoomView/components/RoomViewContent.tsx b/app/views/RoomView/components/RoomViewContent.tsx new file mode 100644 index 00000000000..4e2189512bc --- /dev/null +++ b/app/views/RoomView/components/RoomViewContent.tsx @@ -0,0 +1,62 @@ +import { useStore } from 'zustand'; +import { useShallow } from 'zustand/react/shallow'; + +import { getRoomTitle } from '~/lib/methods/helpers'; +import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; +import { getInvitationActions, getInvitationText } from '~/lib/methods/getInvitationData'; +import { type IInviteSubscription } from '~/definitions'; +import { isSubscriptionModel } from '~/definitions/TRoom'; +import { type IRoomViewProps, type RoomStore } from '../definitions'; +import RoomScreen from '../RoomScreen'; +import { useE2EEStatus } from '../hooks/useE2EEStatus'; +import { EncryptedRoom } from './EncryptedRoom'; +import { InvitedRoomScreen } from './InvitedRoomScreen'; +import { MissingRoomE2EEKey } from './MissingRoomE2EEKey'; +import { RoomBackground } from './RoomBackground'; + +interface IRoomViewContentProps extends IRoomViewProps { + rid?: string; + t?: string; + tmid?: string; + roomStore: RoomStore; + ready: boolean; +} + +export const RoomViewContent = ({ route, navigation, rid, t, tmid, roomStore, ready }: IRoomViewContentProps) => { + const isEncryptable = useStore(roomStore, s => isSubscriptionModel(s.room)); + const invitation = useStore( + roomStore, + useShallow(s => (isSubscriptionModel(s.room) && isInviteSubscription(s.room) ? getInvitationText(s.room) : null)) + ); + const roomTitle = useStore(roomStore, s => getRoomTitle(s.room)); + + const { showMissingE2EEKey, showE2EEDisabledRoom } = useE2EEStatus(roomStore); + + if (!rid || !t) { + return ; + } + + if (invitation) { + return ( + getInvitationActions(roomStore.getState().room as IInviteSubscription).accept()} + onReject={() => getInvitationActions(roomStore.getState().room as IInviteSubscription).reject()} + /> + ); + } + + if (isEncryptable) { + if (showMissingE2EEKey) { + return ; + } + + if (showE2EEDisabledRoom) { + return ; + } + } + + return ; +}; diff --git a/app/views/RoomView/components/RoomViewHeader/NativeHeaderItems.tsx b/app/views/RoomView/components/RoomViewHeader/NativeHeaderItems.tsx new file mode 100644 index 00000000000..0da00938a91 --- /dev/null +++ b/app/views/RoomView/components/RoomViewHeader/NativeHeaderItems.tsx @@ -0,0 +1,40 @@ +import { useStore } from 'zustand'; + +import { type IHeaderAction } from '~/lib/methods/helpers/navigation/headerActions'; +import { type RoomStore } from '~/views/RoomView/definitions'; +import { useGoRoomActionsView } from '~/views/RoomView/hooks/useGoRoomActionsView'; +import { useHeaderFields } from '~/views/RoomView/hooks/useHeaderFields'; +import { useNativeAvatarItem, useNativeRightItems } from '~/views/RoomView/hooks/useNativeHeaderItems'; +import { useNativeBackButton } from '~/views/RoomView/hooks/useNativeBackButton'; +import { useNativeRoomHeader } from '~/views/RoomView/hooks/useNativeRoomHeader'; + +export const NativeRoomTitle = ({ + tmid, + threadName, + roomStore +}: { + tmid?: string; + threadName?: string; + roomStore: RoomStore; +}) => { + const fields = useHeaderFields(roomStore, tmid, threadName); + const roomUserId = useStore(roomStore, s => s.roomUserId); + const goRoomActionsView = useGoRoomActionsView(roomStore); + useNativeRoomHeader(fields, tmid, roomUserId, goRoomActionsView); + return null; +}; + +export const NativeBackButton = ({ rid }: { rid: string }) => { + useNativeBackButton(rid); + return null; +}; + +export const NativeAvatarItem = ({ rid, roomStore }: { rid: string; roomStore: RoomStore }) => { + useNativeAvatarItem(rid, roomStore); + return null; +}; + +export const NativeRightItems = ({ actions }: { actions: IHeaderAction[] }) => { + useNativeRightItems(actions); + return null; +}; diff --git a/app/views/RoomView/components/RoomViewHeader/RoomHeaderActions.tsx b/app/views/RoomView/components/RoomViewHeader/RoomHeaderActions.tsx new file mode 100644 index 00000000000..4e0b5d7da17 --- /dev/null +++ b/app/views/RoomView/components/RoomViewHeader/RoomHeaderActions.tsx @@ -0,0 +1,50 @@ +import { type ComponentType, type ReactElement } from 'react'; + +import { type IHeaderAction } from '~/lib/methods/helpers/navigation/headerActions'; +import { type RoomStore } from '~/views/RoomView/definitions'; +import { + EMPTY_ACTIONS, + useOmnichannelActions, + useRoomActions, + useRoomHeaderMode, + useThreadActions +} from '~/views/RoomView/hooks/useRoomHeaderActions'; + +type THeaderActionsSink = ComponentType<{ actions: IHeaderAction[] }>; + +interface IRoomHeaderActionsProps { + rid?: string; + tmid?: string; + roomStore: RoomStore; + Sink: THeaderActionsSink; +} + +const OmnichannelActions = ({ rid, roomStore, Sink }: { rid: string; roomStore: RoomStore; Sink: THeaderActionsSink }) => { + const actions = useOmnichannelActions(rid, roomStore); + return ; +}; + +const ThreadActions = ({ tmid, Sink }: { tmid: string; Sink: THeaderActionsSink }) => { + const actions = useThreadActions(tmid); + return ; +}; + +const RoomActions = ({ rid, roomStore, Sink }: { rid: string; roomStore: RoomStore; Sink: THeaderActionsSink }) => { + const actions = useRoomActions(rid, roomStore); + return ; +}; + +export const RoomHeaderActions = ({ rid, tmid, roomStore, Sink }: IRoomHeaderActionsProps): ReactElement => { + const mode = useRoomHeaderMode(rid, tmid, roomStore); + + if (rid && mode === 'omnichannel') { + return ; + } + if (tmid && mode === 'thread') { + return ; + } + if (rid && mode === 'room') { + return ; + } + return ; +}; diff --git a/app/views/RoomView/components/RoomViewHeader/RoomViewHeader.tsx b/app/views/RoomView/components/RoomViewHeader/RoomViewHeader.tsx new file mode 100644 index 00000000000..0b42445501f --- /dev/null +++ b/app/views/RoomView/components/RoomViewHeader/RoomViewHeader.tsx @@ -0,0 +1,42 @@ +import { hasNativeHeaderBar } from '~/lib/methods/helpers'; +import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; +import { type RoomStore } from '~/views/RoomView/definitions'; +import { useJsRoomHeader } from '~/views/RoomView/hooks/useJsRoomHeader'; +import { NativeAvatarItem, NativeBackButton, NativeRightItems, NativeRoomTitle } from './NativeHeaderItems'; +import { RoomHeaderActions } from './RoomHeaderActions'; + +interface IRoomViewHeaderProps { + rid?: string; + tmid?: string; + name?: string; + roomStore: RoomStore; +} + +const NativeRoomHeader = ({ rid, tmid, name, roomStore }: IRoomViewHeaderProps & { rid: string }) => { + const showsAvatar = useMasterDetail() && !tmid; + + return ( + <> + + {showsAvatar ? : } + + + ); +}; + +const JsRoomHeader = ({ rid, tmid, name, roomStore }: IRoomViewHeaderProps) => { + useJsRoomHeader({ rid, tmid, name, roomStore }); + return null; +}; + +// rid/tmid/name come from the screen's mount-time snapshot: route.params can be wiped to undefined +// while this RoomView is retained below the stack top, which would break the header permanently. +export const RoomViewHeader = ({ rid, tmid, name, roomStore }: IRoomViewHeaderProps) => { + if (!hasNativeHeaderBar) { + return ; + } + if (!rid) { + return null; + } + return ; +}; diff --git a/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx b/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx new file mode 100644 index 00000000000..a7eb7306ca6 --- /dev/null +++ b/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx @@ -0,0 +1,176 @@ +import { act, render } from '@testing-library/react-native'; +import { createStore } from 'zustand'; + +import { type IHeaderAction } from '~/lib/methods/helpers/navigation/headerActions'; +import { type RoomState, type RoomStore } from '~/views/RoomView/definitions'; +import { useOmnichannelActions, useRoomActions, useThreadActions } from '~/views/RoomView/hooks/useRoomHeaderActions'; +import { RoomViewHeader } from '../RoomViewHeader'; + +const mockSetOptions = jest.fn(); +jest.mock('@react-navigation/native', () => ({ + useNavigation: () => ({ setOptions: mockSetOptions, goBack: jest.fn(), canGoBack: () => true }) +})); + +let mockHasNativeHeaderBar = true; +jest.mock('~/lib/methods/helpers', () => + Object.defineProperty({ ...jest.requireActual('~/lib/methods/helpers'), getRoomTitle: () => 'general' }, 'hasNativeHeaderBar', { + get: () => mockHasNativeHeaderBar + }) +); + +let mockIsMasterDetail = false; +jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => mockIsMasterDetail })); +jest.mock('~/theme', () => ({ useTheme: () => ({ colors: { fontDefault: 'default' } }) })); + +jest.mock('~/views/RoomView/hooks/useNativeRoomHeader', () => ({ useNativeRoomHeader: jest.fn() })); +jest.mock('~/views/RoomView/hooks/useGoRoomActionsView', () => ({ useGoRoomActionsView: () => jest.fn() })); +jest.mock('~/views/RoomView/hooks/useUnreadsCount', () => ({ useUnreadsCount: () => 4 })); +jest.mock('~/views/RoomView/components/LeftButtons', () => ({ __esModule: true, default: 'LeftButtons' })); +jest.mock('~/views/RoomView/components/RightButtons/RightButtons', () => ({ __esModule: true, default: 'RightButtons' })); +jest.mock('~/containers/RoomHeader', () => ({ __esModule: true, default: 'RoomHeader' })); + +jest.mock('~/views/RoomView/components/RightButtons/useRoomRightButtonsData', () => ({ useRoomRightButtonsData: jest.fn() })); +jest.mock('~/views/RoomView/components/RightButtons/useHeaderCallPress', () => ({ useHeaderCallPress: jest.fn() })); + +const mockActionsStore = createStore<{ roomActions: IHeaderAction[] }>(() => ({ + roomActions: [{ label: 'Threads', icon: 'threads', onPress: jest.fn() }] +})); +jest.mock('~/views/RoomView/hooks/useRoomHeaderActions', () => { + const { useStore } = jest.requireActual('zustand'); + return { + ...jest.requireActual('~/views/RoomView/hooks/useRoomHeaderActions'), + useRoomActions: jest.fn(() => useStore(mockActionsStore, (s: { roomActions: IHeaderAction[] }) => s.roomActions)), + useThreadActions: jest.fn(() => [{ label: 'Follow_thread', icon: 'notification-disabled', onPress: jest.fn() }]), + useOmnichannelActions: jest.fn(() => [{ label: 'More', icon: 'kebab', menu: [] }]) + }; +}); + +const makeRoomStore = (room: Partial = {}): RoomStore => + createStore(() => ({ + room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', ...room } as RoomState['room'], + membership: 'subscribed', + member: {}, + roomUserId: null, + canAutoTranslate: false, + canForwardGuest: false, + canViewCannedResponse: false, + init: jest.fn(), + join: jest.fn(), + joinRoom: jest.fn(() => Promise.resolve()), + resumeRoom: jest.fn(() => Promise.resolve()) + })); + +const optionsWith = (key: string) => mockSetOptions.mock.calls.map(([options]) => options).filter(options => key in options); +const lastOptionsWith = (key: string) => optionsWith(key).at(-1); + +beforeEach(() => { + jest.clearAllMocks(); + mockHasNativeHeaderBar = true; + mockIsMasterDetail = false; + mockActionsStore.setState({ roomActions: [{ label: 'Threads', icon: 'threads', onPress: jest.fn() }] }); +}); + +describe('on the JS header', () => { + beforeEach(() => { + mockHasNativeHeaderBar = false; + }); + + it('renders the JS header buttons and title without native items', () => { + render(); + + expect(lastOptionsWith('headerLeft').headerLeft().type).toBe('LeftButtons'); + expect(lastOptionsWith('headerRight').headerRight().type).toBe('RightButtons'); + expect(lastOptionsWith('headerTitle').headerTitle().type).toBe('RoomHeader'); + expect(optionsWith('unstable_headerRightItems')).toHaveLength(0); + expect(optionsWith('unstable_headerLeftItems')).toHaveLength(0); + }); + + it('does not mount any header action hook in the screen header', () => { + render(); + + expect(useRoomActions).not.toHaveBeenCalled(); + expect(useThreadActions).not.toHaveBeenCalled(); + expect(useOmnichannelActions).not.toHaveBeenCalled(); + }); +}); + +describe('on the native header bar', () => { + it('leaves the header untouched without a rid', () => { + render(); + + expect(mockSetOptions).not.toHaveBeenCalled(); + }); + + it('shows the room actions on the right and an unread back button on the left', () => { + render(); + + expect(lastOptionsWith('unstable_headerRightItems').unstable_headerRightItems()).toEqual([ + expect.objectContaining({ type: 'button', label: 'Threads' }) + ]); + const backOptions = lastOptionsWith('unstable_headerLeftItems'); + expect(backOptions.headerBackVisible).toBe(false); + expect(backOptions.unstable_headerLeftItems()).toEqual([expect.objectContaining({ label: '4' })]); + expect(optionsWith('headerRight')).toHaveLength(0); + }); + + it.each([ + ['room', undefined, {}, useRoomActions], + ['thread', 'tmid-1', {}, useThreadActions], + ['omnichannel', undefined, { t: 'l' }, useOmnichannelActions] + ])('mounts only the %s action hook', (_mode, tmid, room, activeHook) => { + render(); + + [useRoomActions, useThreadActions, useOmnichannelActions].forEach(hook => + hook === activeHook ? expect(hook).toHaveBeenCalled() : expect(hook).not.toHaveBeenCalled() + ); + }); + + it('swaps the actions when an omnichannel chat is placed back in the queue', () => { + const roomStore = makeRoomStore({ t: 'l' }); + render(); + expect(lastOptionsWith('unstable_headerRightItems').unstable_headerRightItems()).toHaveLength(1); + + act(() => { + roomStore.setState({ room: { id: 'sub-1', rid: 'rid-1', t: 'l', status: 'queued' } as RoomState['room'] }); + }); + + expect(lastOptionsWith('unstable_headerRightItems').unstable_headerRightItems()).toEqual([]); + }); + + describe('on a master-detail layout', () => { + beforeEach(() => { + mockIsMasterDetail = true; + }); + + it('shows the room avatar without the shared glass background instead of the back button', () => { + render(); + + const [avatarItem] = lastOptionsWith('unstable_headerLeftItems').unstable_headerLeftItems(); + expect(avatarItem).toMatchObject({ type: 'custom', hidesSharedBackground: true }); + expect(avatarItem.element.type).toBe('LeftButtons'); + expect(optionsWith('headerBackVisible')).toHaveLength(0); + }); + + it('updates the right items without re-sending the avatar', () => { + render(); + const leftUpdates = optionsWith('unstable_headerLeftItems').length; + + act(() => { + mockActionsStore.setState({ roomActions: [{ label: 'Call', icon: 'phone', onPress: jest.fn() }] }); + }); + + expect(lastOptionsWith('unstable_headerRightItems').unstable_headerRightItems()).toEqual([ + expect.objectContaining({ label: 'Call' }) + ]); + expect(optionsWith('unstable_headerLeftItems')).toHaveLength(leftUpdates); + }); + + it('keeps the back button on a thread', () => { + render(); + + expect(lastOptionsWith('unstable_headerLeftItems').unstable_headerLeftItems()).toEqual([ + expect.objectContaining({ label: '4', icon: { type: 'sfSymbol', name: 'chevron.backward' } }) + ]); + }); + }); +}); diff --git a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useHeader.test.tsx deleted file mode 100644 index d5c83fde15d..00000000000 --- a/app/views/RoomView/hooks/__tests__/useHeader.test.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import { act, renderHook } from '@testing-library/react-native'; - -import { createStore } from 'zustand'; - -import { type RoomState, type RoomStore } from '~/views/RoomView/definitions'; -import { useHeader } from '../useHeader'; -import { useNativeRoomHeader } from '../useNativeRoomHeader'; -import { useNativeBackButton } from '../useNativeBackButton'; - -jest.mock('../useNativeRoomHeader', () => ({ useNativeRoomHeader: jest.fn() })); -jest.mock('../useNativeBackButton', () => ({ useNativeBackButton: jest.fn() })); -const mockThreadsPress = jest.fn(); -const mockRightActions = [{ label: 'Threads', icon: 'threads', onPress: mockThreadsPress }]; -jest.mock('../useRoomHeaderActions', () => ({ useRoomHeaderActions: jest.fn(() => mockRightActions) })); - -let mockTestStore: RoomStore; -let mockIsIOS = false; - -jest.mock('../useGoRoomActionsView', () => ({ useGoRoomActionsView: jest.fn(() => jest.fn()) })); -jest.mock('~/views/RoomView/components/LeftButtons', () => ({ __esModule: true, default: 'LeftButtons' })); -jest.mock('~/views/RoomView/components/RightButtons/RightButtons', () => ({ __esModule: true, default: 'RightButtons' })); -jest.mock('~/containers/RoomHeader', () => ({ __esModule: true, default: 'RoomHeader' })); -let mockIsTablet = false; -jest.mock('~/lib/methods/helpers', () => ({ - getRoomTitle: jest.fn(() => 'Room Title'), - isGroupChat: jest.fn(() => false), - get hasNativeHeaderBar() { - return mockIsIOS; - }, - get isTablet() { - return mockIsTablet; - } -})); -jest.mock('~/lib/hooks/useMasterDetail', () => ({ useMasterDetail: () => mockIsTablet })); -jest.mock('~/lib/methods/isInviteSubscription', () => ({ - isInviteSubscription: jest.fn(() => false) -})); - -const mockSetOptions = jest.fn(); -const mockNavigation = { setOptions: mockSetOptions }; - -jest.mock('@react-navigation/native', () => ({ - useNavigation: () => mockNavigation -})); - -const makeRoomStore = (overrides: Partial = {}): RoomStore => - createStore(() => ({ - room: { rid: 'rid-1', t: 'c', name: 'general' }, - membership: 'subscribed', - member: {}, - roomUserId: null, - canAutoTranslate: false, - canForwardGuest: false, - canViewCannedResponse: false, - init: jest.fn(), - join: jest.fn(), - joinRoom: jest.fn(() => Promise.resolve()), - resumeRoom: jest.fn(() => Promise.resolve()), - ...overrides - })); - -describe('useHeader', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockTestStore = makeRoomStore(); - mockIsIOS = false; - mockIsTablet = false; - }); - - it('sets only the headerLeft spacer and returns when rid is missing', () => { - renderHook(() => useHeader({ rid: undefined, tmid: undefined, name: 'general', roomStore: mockTestStore })); - - expect(mockSetOptions).toHaveBeenCalledTimes(1); - const options = mockSetOptions.mock.calls[0][0]; - expect(typeof options.headerLeft).toBe('function'); - expect(options).not.toHaveProperty('headerTitle'); - expect(options).not.toHaveProperty('headerRight'); - }); - - it('re-fires the title effect when a selected field changes on a re-emitted room', () => { - mockTestStore = makeRoomStore({ - room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', topic: 'old' } as RoomState['room'] - }); - - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - expect(mockSetOptions).toHaveBeenCalledTimes(2); - - act(() => { - mockTestStore.setState({ room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', topic: 'new' } as RoomState['room'] }); - }); - expect(mockSetOptions).toHaveBeenCalledTimes(3); - expect(mockSetOptions.mock.calls[2][0]).toHaveProperty('headerTitle'); - expect(mockSetOptions.mock.calls[2][0].headerTitle().props.subtitle).toBe('new'); - }); - - it('keeps the thread title from the passed name when the observed room name changes', () => { - renderHook(() => useHeader({ rid: 'rid-1', tmid: 'tmid-1', name: 'Thread name', roomStore: mockTestStore })); - - const titleOptions = mockSetOptions.mock.calls[1][0]; - expect(titleOptions.headerTitle().props.title).toBe('Thread name'); - - act(() => { - mockTestStore.setState({ room: { rid: 'rid-1', t: 'c', name: 'parent-channel' } }); - }); - const nextTitleOptions = mockSetOptions.mock.calls[mockSetOptions.mock.calls.length - 1][0]; - expect(nextTitleOptions.headerTitle().props.title).toBe('Thread name'); - }); - - it('renders each header callback without throwing', () => { - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - - const sideOptions = mockSetOptions.mock.calls[0][0]; - const titleOptions = mockSetOptions.mock.calls[1][0]; - expect(() => sideOptions.headerLeft()).not.toThrow(); - expect(() => titleOptions.headerTitle()).not.toThrow(); - expect(() => sideOptions.headerRight()).not.toThrow(); - }); - - describe('on iOS', () => { - beforeEach(() => { - mockIsIOS = true; - }); - - it('leaves the native header empty when rid is missing', () => { - renderHook(() => useHeader({ rid: undefined, tmid: undefined, name: 'general', roomStore: mockTestStore })); - - expect(mockSetOptions).not.toHaveBeenCalled(); - }); - - it('sets unstable_headerRightItems and keeps the native back button instead of headerLeft/headerRight', () => { - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - - const sideOptions = mockSetOptions.mock.calls[0][0]; - const [threadsItem] = sideOptions.unstable_headerRightItems(); - expect(threadsItem).toMatchObject({ type: 'button', label: 'Threads', accessibilityLabel: 'Threads' }); - threadsItem.onPress(); - expect(mockThreadsPress).toHaveBeenCalled(); - expect(sideOptions).not.toHaveProperty('headerLeft'); - expect(sideOptions).not.toHaveProperty('headerRight'); - }); - }); - - describe('on iPad', () => { - beforeEach(() => { - mockIsIOS = true; - mockIsTablet = true; - }); - - it('uses the native right items like iPhone', () => { - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - - const sideOptions = mockSetOptions.mock.calls[0][0]; - expect(sideOptions.unstable_headerRightItems()).toEqual([expect.objectContaining({ type: 'button', label: 'Threads' })]); - expect(sideOptions).not.toHaveProperty('headerRight'); - }); - - it('shows the room avatar without the shared glass background', () => { - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - - const [avatarItem] = mockSetOptions.mock.calls[0][0].unstable_headerLeftItems(); - expect(avatarItem.type).toBe('custom'); - expect(avatarItem.hidesSharedBackground).toBe(true); - expect(avatarItem.element.type).toBe('LeftButtons'); - }); - - it('disables the native back button while the avatar holds the left slot', () => { - renderHook(() => useHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); - - expect(useNativeBackButton).toHaveBeenLastCalledWith(false, 'rid-1'); - }); - - it('keeps the native back button on a thread', () => { - renderHook(() => useHeader({ rid: 'rid-1', tmid: 'tmid-1', name: 'Thread', roomStore: mockTestStore })); - - expect(mockSetOptions.mock.calls[0][0]).not.toHaveProperty('unstable_headerLeftItems'); - }); - }); -}); - -describe('native title availability', () => { - it.each([ - [true, true], - [false, false] - ])('uses the native title when the native header bar is %s', (nativeHeaderBar, native) => { - jest.clearAllMocks(); - mockIsIOS = nativeHeaderBar; - renderHook(() => useHeader({ rid: 'rid-1', roomStore: makeRoomStore() })); - expect(useNativeRoomHeader).toHaveBeenCalledWith(native, expect.any(Object), undefined, null, expect.any(Function)); - expect(mockSetOptions.mock.calls.some(([options]) => typeof options.headerTitle === 'function')).toBe(!native); - }); -}); diff --git a/app/views/RoomView/hooks/__tests__/useJsRoomHeader.test.tsx b/app/views/RoomView/hooks/__tests__/useJsRoomHeader.test.tsx new file mode 100644 index 00000000000..9edc8dc1c5a --- /dev/null +++ b/app/views/RoomView/hooks/__tests__/useJsRoomHeader.test.tsx @@ -0,0 +1,99 @@ +import { act, renderHook } from '@testing-library/react-native'; + +import { createStore } from 'zustand'; + +import { type RoomState, type RoomStore } from '~/views/RoomView/definitions'; +import { useJsRoomHeader } from '../useJsRoomHeader'; + +let mockTestStore: RoomStore; + +jest.mock('../useGoRoomActionsView', () => ({ useGoRoomActionsView: jest.fn(() => jest.fn()) })); +jest.mock('~/views/RoomView/components/LeftButtons', () => ({ __esModule: true, default: 'LeftButtons' })); +jest.mock('~/views/RoomView/components/RightButtons/RightButtons', () => ({ __esModule: true, default: 'RightButtons' })); +jest.mock('~/containers/RoomHeader', () => ({ __esModule: true, default: 'RoomHeader' })); +jest.mock('~/lib/methods/helpers', () => ({ + getRoomTitle: jest.fn(() => 'Room Title'), + isGroupChat: jest.fn(() => false) +})); +jest.mock('~/lib/methods/isInviteSubscription', () => ({ + isInviteSubscription: jest.fn(() => false) +})); + +const mockSetOptions = jest.fn(); +const mockNavigation = { setOptions: mockSetOptions }; + +jest.mock('@react-navigation/native', () => ({ + useNavigation: () => mockNavigation +})); + +const makeRoomStore = (overrides: Partial = {}): RoomStore => + createStore(() => ({ + room: { rid: 'rid-1', t: 'c', name: 'general' }, + membership: 'subscribed', + member: {}, + roomUserId: null, + canAutoTranslate: false, + canForwardGuest: false, + canViewCannedResponse: false, + init: jest.fn(), + join: jest.fn(), + joinRoom: jest.fn(() => Promise.resolve()), + resumeRoom: jest.fn(() => Promise.resolve()), + ...overrides + })); + +describe('useJsRoomHeader', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockTestStore = makeRoomStore(); + }); + + it('sets only the headerLeft spacer and returns when rid is missing', () => { + renderHook(() => useJsRoomHeader({ rid: undefined, tmid: undefined, name: 'general', roomStore: mockTestStore })); + + expect(mockSetOptions).toHaveBeenCalledTimes(1); + const options = mockSetOptions.mock.calls[0][0]; + expect(typeof options.headerLeft).toBe('function'); + expect(options).not.toHaveProperty('headerTitle'); + expect(options).not.toHaveProperty('headerRight'); + }); + + it('re-fires the title effect when a selected field changes on a re-emitted room', () => { + mockTestStore = makeRoomStore({ + room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', topic: 'old' } as RoomState['room'] + }); + + renderHook(() => useJsRoomHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + expect(mockSetOptions).toHaveBeenCalledTimes(2); + + act(() => { + mockTestStore.setState({ room: { id: 'sub-1', rid: 'rid-1', t: 'c', name: 'general', topic: 'new' } as RoomState['room'] }); + }); + expect(mockSetOptions).toHaveBeenCalledTimes(3); + expect(mockSetOptions.mock.calls[2][0]).toHaveProperty('headerTitle'); + expect(mockSetOptions.mock.calls[2][0].headerTitle().props.subtitle).toBe('new'); + }); + + it('keeps the thread title from the passed name when the observed room name changes', () => { + renderHook(() => useJsRoomHeader({ rid: 'rid-1', tmid: 'tmid-1', name: 'Thread name', roomStore: mockTestStore })); + + const titleOptions = mockSetOptions.mock.calls[1][0]; + expect(titleOptions.headerTitle().props.title).toBe('Thread name'); + + act(() => { + mockTestStore.setState({ room: { rid: 'rid-1', t: 'c', name: 'parent-channel' } }); + }); + const nextTitleOptions = mockSetOptions.mock.calls[mockSetOptions.mock.calls.length - 1][0]; + expect(nextTitleOptions.headerTitle().props.title).toBe('Thread name'); + }); + + it('renders each header callback without throwing', () => { + renderHook(() => useJsRoomHeader({ rid: 'rid-1', tmid: undefined, name: 'general', roomStore: mockTestStore })); + + const sideOptions = mockSetOptions.mock.calls[0][0]; + const titleOptions = mockSetOptions.mock.calls[1][0]; + expect(() => sideOptions.headerLeft()).not.toThrow(); + expect(() => titleOptions.headerTitle()).not.toThrow(); + expect(() => sideOptions.headerRight()).not.toThrow(); + }); +}); diff --git a/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts b/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts index c640b2bae32..020e9fa03d6 100644 --- a/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts +++ b/app/views/RoomView/hooks/__tests__/useNativeBackButton.test.ts @@ -15,7 +15,7 @@ jest.mock('../useUnreadsCount', () => ({ useUnreadsCount: (rid?: string) => mock const renderBackItem = (unreads: number | null) => { mockUnreads = unreads; - renderHook(() => useNativeBackButton(true, 'rid')); + renderHook(() => useNativeBackButton('rid')); const options = mockSetOptions.mock.lastCall[0]; expect(options.headerBackVisible).toBe(false); const [backItem] = options.unstable_headerLeftItems(); @@ -51,15 +51,8 @@ it('goes back when pressed', () => { expect(mockGoBack).toHaveBeenCalledTimes(1); }); -it('does nothing when disabled', () => { - mockUnreads = 3; - renderHook(() => useNativeBackButton(false, 'rid')); - expect(mockUseUnreadsCount).toHaveBeenCalledWith(undefined); - expect(mockSetOptions).not.toHaveBeenCalled(); -}); - it('clears custom left items when it cannot go back', () => { mockCanGoBack = false; - renderHook(() => useNativeBackButton(true, 'rid')); + renderHook(() => useNativeBackButton('rid')); expect(mockSetOptions).toHaveBeenCalledWith({ unstable_headerLeftItems: undefined }); }); diff --git a/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts index 1531d94a1c6..125053c17f6 100644 --- a/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts +++ b/app/views/RoomView/hooks/__tests__/useNativeRoomHeader.test.ts @@ -1,9 +1,8 @@ import { act, renderHook, waitFor } from '@testing-library/react-native'; import { Image } from 'react-native'; -import { IconSet } from '~/containers/CustomIcon'; import { getUserPresence } from '~/lib/methods/getUsersPresence'; -import { type IHeaderFields } from '../useHeader'; +import { type IHeaderFields } from '../useHeaderFields'; import { useNativeRoomHeader } from '../useNativeRoomHeader'; const mockSetOptions = jest.fn(); @@ -48,28 +47,28 @@ beforeEach(() => { }); it('sets the title before the room icon is ready and adds the icon when it loads', async () => { - renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'p', title: 'Shown' })); + renderHook(() => useNativeRoomHeader({ ...fields, type: 'p', title: 'Shown' })); expect(mockSetOptions.mock.calls[0][0]).toMatchObject({ headerTitle: 'Shown', headerSubtitle: 'Topic' }); await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('channel-private:title')); expect(latestOptions().headerTitle).toBe('Shown'); }); it('shows plain topic text and the room icon', async () => { - renderHook(() => useNativeRoomHeader(true, fields)); + renderHook(() => useNativeRoomHeader(fields)); await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('channel-public:title')); expect(latestOptions()).toMatchObject({ headerTitle: 'Room', headerSubtitle: 'Topic', headerSubtitleImageSource: undefined }); }); it('keeps the parent subtitle and moves its icon below the Thread title while users type', async () => { mockState.usersTyping = ['Alice']; - renderHook(() => useNativeRoomHeader(true, { ...fields, title: '*Thread*', parentTitle: '*Parent*' }, 'thread')); + renderHook(() => useNativeRoomHeader({ ...fields, title: '*Thread*', parentTitle: '*Parent*' }, 'thread')); await waitFor(() => expect(latestOptions().headerSubtitleImageSource?.uri).toBe('channel-public:title')); expect(latestOptions()).toMatchObject({ headerTitle: 'Thread', headerSubtitle: '*Parent*', headerTitleImageSource: undefined }); }); it('clears the expiry clock during typing and updates the colored presence image', async () => { mockState.activeUsers.user = { status: 'online', statusText: 'Working', statusExpiresAt: 'future' }; - const { rerender } = renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'd' }, undefined, 'user')); + const { rerender } = renderHook(() => useNativeRoomHeader({ ...fields, type: 'd' }, undefined, 'user')); await waitFor(() => expect(latestOptions().headerSubtitleImageSource?.uri).toBe('clock:subtitle')); expect(latestOptions().headerTitleImageSource.uri).toBe('status-online:online'); mockState = { ...mockState, usersTyping: ['Alice', 'Bob'], activeUsers: { user: { status: 'busy' } } }; @@ -80,7 +79,7 @@ it('clears the expiry clock during typing and updates the colored presence image }); it('requests missing direct-message presence but respects disabled broadcasting', async () => { - const { rerender } = renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'd' }, undefined, 'user')); + const { rerender } = renderHook(() => useNativeRoomHeader({ ...fields, type: 'd' }, undefined, 'user')); await act(async () => {}); expect(getUserPresence).toHaveBeenCalledWith('user'); jest.mocked(getUserPresence).mockClear(); @@ -90,18 +89,11 @@ it('requests missing direct-message presence but respects disabled broadcasting' expect(getUserPresence).not.toHaveBeenCalled(); }); -it('does not configure or render native images when disabled', async () => { - renderHook(() => useNativeRoomHeader(false, fields)); - await act(async () => {}); - expect(mockSetOptions).not.toHaveBeenCalled(); - expect(IconSet.getImageSource).not.toHaveBeenCalled(); -}); - it('uses supported Omnichannel app images and falls back when decoding fails', async () => { const getSize = jest.spyOn(Image, 'getSize').mockImplementation(() => Promise.resolve({ width: 24, height: 24 })); mockState.server.server = 'https://chat.example'; let sourceType = { type: 'app', id: 'app-id', sidebarIcon: 'icon.png' } as IHeaderFields['sourceType']; - const { rerender } = renderHook(() => useNativeRoomHeader(true, { ...fields, type: 'l', sourceType })); + const { rerender } = renderHook(() => useNativeRoomHeader({ ...fields, type: 'l', sourceType })); await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toContain('get-sidebar-icon?icon=icon.png')); getSize.mockImplementation(() => Promise.reject(new Error('Unsupported image'))); sourceType = { ...sourceType, sidebarIcon: 'icon.svg' } as IHeaderFields['sourceType']; @@ -112,14 +104,14 @@ it('uses supported Omnichannel app images and falls back when decoding fails', a it('opens room actions when the title is pressed', async () => { const onTitlePress = jest.fn(); - renderHook(() => useNativeRoomHeader(true, fields, undefined, undefined, onTitlePress)); + renderHook(() => useNativeRoomHeader(fields, undefined, undefined, onTitlePress)); await waitFor(() => expect(latestOptions().onHeaderTitlePress).toBeInstanceOf(Function)); latestOptions().onHeaderTitlePress({ nativeEvent: {} }); expect(onTitlePress).toHaveBeenCalledWith(); }); it('does not make the title pressable for invite subscriptions', async () => { - renderHook(() => useNativeRoomHeader(true, { ...fields, disabled: true }, undefined, undefined, jest.fn())); + renderHook(() => useNativeRoomHeader({ ...fields, disabled: true }, undefined, undefined, jest.fn())); await waitFor(() => expect(latestOptions().headerTitleImageSource?.uri).toBe('channel-public:title')); expect(latestOptions().onHeaderTitlePress).toBeUndefined(); }); diff --git a/app/views/RoomView/hooks/__tests__/useRoomHeaderActions.test.ts b/app/views/RoomView/hooks/__tests__/useRoomHeaderActions.test.ts index 59a4b2a00d2..fc490c40e89 100644 --- a/app/views/RoomView/hooks/__tests__/useRoomHeaderActions.test.ts +++ b/app/views/RoomView/hooks/__tests__/useRoomHeaderActions.test.ts @@ -8,7 +8,7 @@ import { returnLivechat } from '~/lib/services/restApi'; import { type RoomStore } from '~/views/RoomView/definitions'; import { closeLivechat } from '~/views/RoomView/services/closeLivechat'; import { placeLivechatOnHold } from '~/views/RoomView/services/placeLivechatOnHold'; -import { useRoomHeaderActions } from '../useRoomHeaderActions'; +import { useOmnichannelActions, useRoomActions, useRoomHeaderMode, useThreadActions } from '../useRoomHeaderActions'; const mockNavigation = { navigate: jest.fn(), push: jest.fn() }; jest.mock('@react-navigation/native', () => ({ useNavigation: () => mockNavigation })); @@ -76,7 +76,9 @@ jest.mock('~/lib/hooks/useNewMediaCall', () => ({ useNewMediaCall: () => mockMed const roomStore = {} as RoomStore; -const renderActions = (tmid?: string) => renderHook(() => useRoomHeaderActions('rid-1', tmid, roomStore)).result.current; +const renderRoomActions = () => renderHook(() => useRoomActions('rid-1', roomStore)).result.current; +const renderThreadActions = (tmid: string) => renderHook(() => useThreadActions(tmid)).result.current; +const renderOmnichannelActions = () => renderHook(() => useOmnichannelActions('rid-1', roomStore)).result.current; const labelsOf = (actions: { label: string }[]) => actions.map(action => action.label); @@ -128,21 +130,36 @@ describe('useRoomHeaderActions', () => { }); it.each([ - ['without a rid', undefined, { room: { t: 'c' }, membership: 'subscribed' }], - ['for an invited room', 'rid-1', { room: { t: 'c' }, membership: 'invited' }], - ['for a queued omnichannel room', 'rid-1', { room: { id: 'sub-1', t: 'l', status: 'queued' }, membership: 'subscribed' }], - ['for an omnichannel room in preview', 'rid-1', { room: { t: 'l' }, membership: 'preview' }] - ])('returns no actions %s', (_case, rid, state) => { + ['none without a rid', undefined, undefined, { room: { t: 'c' }, membership: 'subscribed' }, 'none'], + ['none for an invited room', 'rid-1', undefined, { room: { t: 'c' }, membership: 'invited' }, 'none'], + [ + 'none for a queued omnichannel room', + 'rid-1', + undefined, + { room: { id: 'sub-1', t: 'l', status: 'queued' }, membership: 'subscribed' }, + 'none' + ], + ['none for an omnichannel room in preview', 'rid-1', undefined, { room: { t: 'l' }, membership: 'preview' }, 'none'], + [ + 'omnichannel for an active omnichannel room, even inside a thread', + 'rid-1', + 'tmid-1', + { room: { id: 'sub-1', t: 'l' }, membership: 'subscribed' }, + 'omnichannel' + ], + ['thread when a tmid is given', 'rid-1', 'tmid-1', { room: { t: 'c' }, membership: 'subscribed' }, 'thread'], + ['room for a regular channel', 'rid-1', undefined, { room: { t: 'c' }, membership: 'subscribed' }, 'room'] + ])('picks the %s', (_case, rid, tmid, state, mode) => { mockRoomState = { ...state, canForwardGuest: false }; - const { result } = renderHook(() => useRoomHeaderActions(rid, undefined, roomStore)); + const { result } = renderHook(() => useRoomHeaderMode(rid, tmid, roomStore)); - expect(result.current).toEqual([]); + expect(result.current).toBe(mode); }); describe('room on the native header bar', () => { it('lists search in the more menu and runs the chosen action', () => { - const menu = moreMenuOf(renderActions()); + const menu = moreMenuOf(renderRoomActions()); expect(labelsOf(menu)).toEqual(['Search messages']); menu[0].onPress(); @@ -153,7 +170,7 @@ describe('useRoomHeaderActions', () => { mockButtonsData = { ...mockButtonsData, threadsEnabled: true, hasE2EEWarning: true, canToggleEncryption: false }; mockMediaCall.hasMediaCallPermission = true; - const actions = renderActions(); + const actions = renderRoomActions(); expect(labelsOf(actions)).toEqual(['Call', 'Threads', 'More']); expect(moreMenuOf(actions).map(item => [item.label, !!item.disabled])).toEqual([ @@ -178,7 +195,7 @@ describe('useRoomHeaderActions', () => { }; mockMediaCall.hasMediaCallPermission = true; - const actions = renderActions(); + const actions = renderRoomActions(); expect(actions.map(action => action.testID)).toEqual([ 'room-view-header-encryption', @@ -193,7 +210,7 @@ describe('useRoomHeaderActions', () => { it('offers encryption while the other actions are disabled by the e2ee warning', () => { mockButtonsData = { ...mockButtonsData, threadsEnabled: true, hasE2EEWarning: true, canToggleEncryption: true }; - const actions = renderActions(); + const actions = renderRoomActions(); expect(actionByTestID(actions, 'room-view-header-encryption')?.disabled).toBe(false); expect(actionByTestID(actions, 'room-view-header-threads')?.disabled).toBe(true); @@ -203,34 +220,34 @@ describe('useRoomHeaderActions', () => { it('disables encryption when the user cannot toggle it', () => { mockButtonsData = { ...mockButtonsData, hasE2EEWarning: true, canToggleEncryption: false }; - expect(actionByTestID(renderActions(), 'room-view-header-encryption')?.disabled).toBe(true); + expect(actionByTestID(renderRoomActions(), 'room-view-header-encryption')?.disabled).toBe(true); }); it('tints the troubleshoot action only when notifications have issues', () => { mockButtonsData = { ...mockButtonsData, issuesWithNotifications: true }; - expect(actionByTestID(renderActions(), 'room-view-push-troubleshoot')?.tintColor).toBe('#f00'); + expect(actionByTestID(renderRoomActions(), 'room-view-push-troubleshoot')?.tintColor).toBe('#f00'); mockButtonsData = { ...mockButtonsData, issuesWithNotifications: false, disableNotifications: true }; - const troubleshoot = actionByTestID(renderActions(), 'room-view-push-troubleshoot'); + const troubleshoot = actionByTestID(renderRoomActions(), 'room-view-push-troubleshoot'); expect(troubleshoot).toBeDefined(); expect(troubleshoot?.tintColor).toBeUndefined(); }); it('hides the threads action when threads are disabled', () => { - expect(actionByTestID(renderActions(), 'room-view-header-threads')).toBeUndefined(); + expect(actionByTestID(renderRoomActions(), 'room-view-header-threads')).toBeUndefined(); }); it('hides the call action on a self DM', () => { mockButtonsData = { ...mockButtonsData, isSelfDm: true }; mockMediaCall.hasMediaCallPermission = true; - expect(actionByTestID(renderActions(), 'room-view-header-call')).toBeUndefined(); + expect(actionByTestID(renderRoomActions(), 'room-view-header-call')).toBeUndefined(); }); it('badges threads with the unread thread count in the unread style color', () => { mockButtonsData = { ...mockButtonsData, threadsEnabled: true, tunread: ['t1', 't2'], tunreadUser: ['t1'] }; - const threads = actionByTestID(renderActions(), 'room-view-header-threads'); + const threads = actionByTestID(renderRoomActions(), 'room-view-header-threads'); expect(threads?.badge?.value).toBe(2); expect(threads?.badge?.color).toEqual(expect.any(String)); @@ -241,7 +258,7 @@ describe('useRoomHeaderActions', () => { it('leaves threads without a badge when nothing is unread', () => { mockButtonsData = { ...mockButtonsData, threadsEnabled: true }; - expect(actionByTestID(renderActions(), 'room-view-header-threads')?.badge).toBeUndefined(); + expect(actionByTestID(renderRoomActions(), 'room-view-header-threads')?.badge).toBeUndefined(); }); }); @@ -255,7 +272,7 @@ describe('useRoomHeaderActions', () => { jest.useRealTimers(); }); - const callAction = () => actionByTestID(renderActions(), 'room-view-header-call'); + const callAction = () => actionByTestID(renderRoomActions(), 'room-view-header-call'); it('is absent without media call permission and with calls disabled', () => { expect(callAction()).toBeUndefined(); @@ -322,7 +339,7 @@ describe('useRoomHeaderActions', () => { describe('thread', () => { it('offers to follow a thread that is not followed', () => { - const [follow] = renderActions('tmid-1'); + const [follow] = renderThreadActions('tmid-1'); expect(follow).toMatchObject({ label: 'Follow thread', @@ -337,7 +354,7 @@ describe('useRoomHeaderActions', () => { it('offers to unfollow a followed thread', () => { mockFollowersByThread = { 'tmid-1': ['user-1'] }; - const [unfollow] = renderActions('tmid-1'); + const [unfollow] = renderThreadActions('tmid-1'); expect(unfollow).toMatchObject({ label: 'Unfollow thread', icon: 'notification', testID: 'room-view-header-unfollow' }); unfollow.onPress?.(); @@ -346,11 +363,11 @@ describe('useRoomHeaderActions', () => { it('reflects the follow state of the displayed thread and current user', () => { mockFollowersByThread = { 'tmid-1': ['user-1'] }; - expect(renderActions('tmid-1')[0].testID).toBe('room-view-header-unfollow'); - expect(renderActions('tmid-2')[0].testID).toBe('room-view-header-follow'); + expect(renderThreadActions('tmid-1')[0].testID).toBe('room-view-header-unfollow'); + expect(renderThreadActions('tmid-2')[0].testID).toBe('room-view-header-follow'); mockUserId = 'user-2'; - expect(renderActions('tmid-1')[0].testID).toBe('room-view-header-follow'); + expect(renderThreadActions('tmid-1')[0].testID).toBe('room-view-header-follow'); }); }); @@ -359,14 +376,14 @@ describe('useRoomHeaderActions', () => { mockRoomState = { ...mockRoomState, room: { id: 'sub-1', t: 'l', departmentId: 'department-1' } }; }); - const menuOf = (tmid?: string) => { - const actions = renderActions(tmid); + const menuOf = () => { + const actions = renderOmnichannelActions(); expect(actions).toHaveLength(1); return actions[0].menu ?? []; }; - it('shows a single more menu that logs when opened, even inside a thread', () => { - const [more] = renderActions('tmid-1'); + it('shows a single more menu that logs when opened', () => { + const [more] = renderOmnichannelActions(); expect(more).toMatchObject({ label: 'More', icon: 'kebab', testID: 'room-view-header-omnichannel-kebab' }); more.onPress?.(); diff --git a/app/views/RoomView/hooks/useHeader.tsx b/app/views/RoomView/hooks/useHeader.tsx deleted file mode 100644 index 57968be7143..00000000000 --- a/app/views/RoomView/hooks/useHeader.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { useLayoutEffect } from 'react'; -import { PixelRatio, View } from 'react-native'; -import { useNavigation } from '@react-navigation/native'; -import { type NativeStackHeaderItem } from '@react-navigation/native-stack'; -import { useShallow } from 'zustand/react/shallow'; -import { useStore } from 'zustand'; - -import RoomHeader from '~/containers/RoomHeader'; -import { getRoomTitle, hasNativeHeaderBar, isGroupChat } from '~/lib/methods/helpers'; -import { useMasterDetail } from '~/lib/hooks/useMasterDetail'; -import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; -import { nativeHeaderItems } from '~/lib/methods/helpers/navigation/headerActions'; -import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; -import LeftButtons from '../components/LeftButtons'; -import RightButtons from '../components/RightButtons/RightButtons'; -import { type IRoomViewProps } from '../definitions'; -import { type RoomStore } from '../definitions'; -import { fromSubscription } from '../stores/RoomStoreContext'; -import { useGoRoomActionsView } from './useGoRoomActionsView'; -import { useNativeRoomHeader } from './useNativeRoomHeader'; -import { useNativeBackButton } from './useNativeBackButton'; -import { useRoomHeaderActions } from './useRoomHeaderActions'; - -interface IUseHeaderParams { - rid?: string; - tmid?: string; - /** Thread name on a thread; only read when `tmid` is set, since the room title is derived from the room. */ - name?: string; - roomStore: RoomStore; -} - -export interface IHeaderFields { - prid?: string; - title: string; - parentTitle: string; - teamMain: boolean; - subtitle?: string; - type: string; - visitor?: IVisitor; - isGroupChat: boolean; - sourceType?: IOmnichannelSource; - abacAttributes: ISubscription['abacAttributes']; - disabled: boolean; -} - -// rid/tmid/name come from the screen's mount-time snapshot: route.params can be wiped to undefined -// while this RoomView is retained below the stack top, which would break the header permanently. -export const useHeader = ({ rid, tmid, name: roomName, roomStore }: IUseHeaderParams): void => { - const navigation = useNavigation(); - - const headerFields = useStore( - roomStore, - useShallow((s): IHeaderFields => { - const room = s.room; - const title = tmid ? (roomName ?? '') : getRoomTitle(room); - const parentTitle = tmid ? getRoomTitle(room) : ''; - - return { - prid: room?.prid, - title, - teamMain: fromSubscription(r => !!r.teamMain, false)(s), - parentTitle, - subtitle: fromSubscription(r => r.topic, undefined)(s), - type: room?.t, - visitor: fromSubscription(r => r.visitor, undefined)(s), - isGroupChat: fromSubscription(r => isGroupChat(r), false)(s), - sourceType: fromSubscription(r => r.source, undefined)(s), - abacAttributes: fromSubscription(r => r.abacAttributes, undefined)(s), - disabled: fromSubscription(r => isInviteSubscription(r), false)(s) - }; - }) - ); - const roomUserId = useStore(roomStore, s => s.roomUserId); - const goRoomActionsView = useGoRoomActionsView(roomStore); - useNativeRoomHeader(!!rid && hasNativeHeaderBar, headerFields, tmid, roomUserId, goRoomActionsView); - const isMasterDetail = useMasterDetail(); - const showsAvatar = isMasterDetail && !tmid; - useNativeBackButton(!!rid && hasNativeHeaderBar && !showsAvatar, rid); - const nativeRightActions = useRoomHeaderActions(hasNativeHeaderBar ? rid : undefined, tmid, roomStore); - - useLayoutEffect(() => { - if (!rid && hasNativeHeaderBar) { - return; - } - - if (!rid) { - const height = 37 * PixelRatio.getFontScale(); - navigation.setOptions({ headerLeft: () => }); - return; - } - - if (hasNativeHeaderBar) { - const avatarItem: NativeStackHeaderItem = { - type: 'custom', - element: , - hidesSharedBackground: true - }; - navigation.setOptions( - showsAvatar - ? { - unstable_headerLeftItems: () => [avatarItem], - unstable_headerRightItems: () => nativeHeaderItems(nativeRightActions) - } - : { unstable_headerRightItems: () => nativeHeaderItems(nativeRightActions) } - ); - return; - } - - navigation.setOptions({ - headerLeft: () => , - headerRight: () => - }); - }, [rid, tmid, navigation, roomStore, nativeRightActions, showsAvatar]); - - useLayoutEffect(() => { - if (!rid || hasNativeHeaderBar) { - return; - } - - navigation.setOptions({ - headerTitle: () => ( - - ) - }); - }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView]); -}; diff --git a/app/views/RoomView/hooks/useHeaderFields.ts b/app/views/RoomView/hooks/useHeaderFields.ts new file mode 100644 index 00000000000..a2ce4b61f21 --- /dev/null +++ b/app/views/RoomView/hooks/useHeaderFields.ts @@ -0,0 +1,47 @@ +import { useShallow } from 'zustand/react/shallow'; +import { useStore } from 'zustand'; + +import { getRoomTitle, isGroupChat } from '~/lib/methods/helpers'; +import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; +import { type IOmnichannelSource, type ISubscription, type IVisitor } from '~/definitions'; +import { type RoomStore } from '../definitions'; +import { fromSubscription } from '../stores/RoomStoreContext'; + +export interface IHeaderFields { + prid?: string; + title: string; + parentTitle: string; + teamMain: boolean; + subtitle?: string; + type: string; + visitor?: IVisitor; + isGroupChat: boolean; + sourceType?: IOmnichannelSource; + abacAttributes: ISubscription['abacAttributes']; + disabled: boolean; +} + +/** `threadName` is only read when `tmid` is set, since the room title is derived from the room. */ +export const useHeaderFields = (roomStore: RoomStore, tmid?: string, threadName?: string): IHeaderFields => + useStore( + roomStore, + useShallow((s): IHeaderFields => { + const room = s.room; + const title = tmid ? (threadName ?? '') : getRoomTitle(room); + const parentTitle = tmid ? getRoomTitle(room) : ''; + + return { + prid: room?.prid, + title, + teamMain: fromSubscription(r => !!r.teamMain, false)(s), + parentTitle, + subtitle: fromSubscription(r => r.topic, undefined)(s), + type: room?.t, + visitor: fromSubscription(r => r.visitor, undefined)(s), + isGroupChat: fromSubscription(r => isGroupChat(r), false)(s), + sourceType: fromSubscription(r => r.source, undefined)(s), + abacAttributes: fromSubscription(r => r.abacAttributes, undefined)(s), + disabled: fromSubscription(r => isInviteSubscription(r), false)(s) + }; + }) + ); diff --git a/app/views/RoomView/hooks/useJsRoomHeader.tsx b/app/views/RoomView/hooks/useJsRoomHeader.tsx new file mode 100644 index 00000000000..0536475f009 --- /dev/null +++ b/app/views/RoomView/hooks/useJsRoomHeader.tsx @@ -0,0 +1,66 @@ +import { useLayoutEffect } from 'react'; +import { PixelRatio, View } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { useStore } from 'zustand'; + +import RoomHeader from '~/containers/RoomHeader'; +import LeftButtons from '../components/LeftButtons'; +import RightButtons from '../components/RightButtons/RightButtons'; +import { type IRoomViewProps, type RoomStore } from '../definitions'; +import { useGoRoomActionsView } from './useGoRoomActionsView'; +import { useHeaderFields } from './useHeaderFields'; + +interface IUseJsRoomHeaderParams { + rid?: string; + tmid?: string; + name?: string; + roomStore: RoomStore; +} + +export const useJsRoomHeader = ({ rid, tmid, name: threadName, roomStore }: IUseJsRoomHeaderParams): void => { + const navigation = useNavigation(); + const headerFields = useHeaderFields(roomStore, tmid, threadName); + const roomUserId = useStore(roomStore, s => s.roomUserId); + const goRoomActionsView = useGoRoomActionsView(roomStore); + + useLayoutEffect(() => { + if (!rid) { + const height = 37 * PixelRatio.getFontScale(); + navigation.setOptions({ headerLeft: () => }); + return; + } + + navigation.setOptions({ + headerLeft: () => , + headerRight: () => + }); + }, [rid, tmid, navigation, roomStore]); + + useLayoutEffect(() => { + if (!rid) { + return; + } + + navigation.setOptions({ + headerTitle: () => ( + + ) + }); + }, [rid, tmid, headerFields, roomUserId, navigation, goRoomActionsView]); +}; diff --git a/app/views/RoomView/hooks/useNativeBackButton.ts b/app/views/RoomView/hooks/useNativeBackButton.ts index 3f18eec7f4c..5ea53b13840 100644 --- a/app/views/RoomView/hooks/useNativeBackButton.ts +++ b/app/views/RoomView/hooks/useNativeBackButton.ts @@ -14,15 +14,12 @@ const formatUnreadsCount = (unreadsCount: number | null) => { return unreadsCount > 99 ? '+99' : unreadsCount.toString(); }; -export const useNativeBackButton = (enabled: boolean, rid?: string) => { +export const useNativeBackButton = (rid: string) => { const navigation = useNavigation(); const { colors } = useTheme(); - const unreadsLabel = formatUnreadsCount(useUnreadsCount(enabled ? rid : undefined)); + const unreadsLabel = formatUnreadsCount(useUnreadsCount(rid)); useLayoutEffect(() => { - if (!enabled) { - return; - } if (!navigation.canGoBack()) { navigation.setOptions({ unstable_headerLeftItems: undefined }); return; @@ -37,5 +34,5 @@ export const useNativeBackButton = (enabled: boolean, rid?: string) => { onPress: () => navigation.goBack() }; navigation.setOptions({ headerBackVisible: false, unstable_headerLeftItems: () => [backItem] }); - }, [colors.fontDefault, enabled, navigation, unreadsLabel]); + }, [colors.fontDefault, navigation, unreadsLabel]); }; diff --git a/app/views/RoomView/hooks/useNativeHeaderItems.tsx b/app/views/RoomView/hooks/useNativeHeaderItems.tsx new file mode 100644 index 00000000000..9b394cddd59 --- /dev/null +++ b/app/views/RoomView/hooks/useNativeHeaderItems.tsx @@ -0,0 +1,26 @@ +import { useLayoutEffect } from 'react'; +import { useNavigation } from '@react-navigation/native'; + +import { type IHeaderAction, nativeHeaderItems } from '~/lib/methods/helpers/navigation/headerActions'; +import LeftButtons from '../components/LeftButtons'; +import { type IRoomViewProps, type RoomStore } from '../definitions'; + +export const useNativeAvatarItem = (rid: string, roomStore: RoomStore) => { + const navigation = useNavigation(); + + useLayoutEffect(() => { + navigation.setOptions({ + unstable_headerLeftItems: () => [ + { type: 'custom', element: , hidesSharedBackground: true } + ] + }); + }, [navigation, rid, roomStore]); +}; + +export const useNativeRightItems = (actions: IHeaderAction[]) => { + const navigation = useNavigation(); + + useLayoutEffect(() => { + navigation.setOptions({ unstable_headerRightItems: () => nativeHeaderItems(actions) }); + }, [navigation, actions]); +}; diff --git a/app/views/RoomView/hooks/useNativeRoomHeader.ts b/app/views/RoomView/hooks/useNativeRoomHeader.ts index 3f24490782e..675bce48bb1 100644 --- a/app/views/RoomView/hooks/useNativeRoomHeader.ts +++ b/app/views/RoomView/hooks/useNativeRoomHeader.ts @@ -19,7 +19,7 @@ import { getUserPresence } from '~/lib/methods/getUsersPresence'; import { formatStatusExpiry } from '~/lib/methods/helpers/formatStatusExpiry'; import { useTheme } from '~/theme'; import { type IRoomViewProps } from '../definitions'; -import { type IHeaderFields } from './useHeader'; +import { type IHeaderFields } from './useHeaderFields'; import { useHeaderIconImage, useHeaderRemoteImage } from './useHeaderImage'; const TITLE_FONT_SIZE = 17; @@ -79,7 +79,7 @@ const getSubtitle = ({ return fields.subtitle; }; -const useRoomHeaderPresence = (enabled: boolean, fields: IHeaderFields, roomUserId?: string | null) => { +const useRoomHeaderPresence = (fields: IHeaderFields, roomUserId?: string | null) => { const connected = useAppSelector(state => state.meteor.connected); const presenceDisabled = useAppSelector(state => state.settings.Presence_broadcast_disabled); const activeUser = useAppSelector(state => (roomUserId ? state.activeUsers[roomUserId] : undefined), shallowEqual); @@ -89,10 +89,10 @@ const useRoomHeaderPresence = (enabled: boolean, fields: IHeaderFields, roomUser const statusColor = useUserStatusColor(fields.type === 'l' ? (visitorStatus ?? 'offline') : status); useEffect(() => { - if (enabled && isDirectMessage && connected && status === 'loading' && roomUserId) { + if (isDirectMessage && connected && status === 'loading' && roomUserId) { getUserPresence(roomUserId); } - }, [enabled, isDirectMessage, connected, status, roomUserId]); + }, [isDirectMessage, connected, status, roomUserId]); return { connected, activeUser, isDirectMessage, status, statusColor }; }; @@ -118,7 +118,6 @@ const useRoomHeaderContent = ( }; export const useNativeRoomHeader = ( - enabled: boolean, fields: IHeaderFields, tmid?: string, roomUserId?: string | null, @@ -126,36 +125,33 @@ export const useNativeRoomHeader = ( ) => { const navigation = useNavigation(); const { colors } = useTheme(); - const { connected, activeUser, isDirectMessage, status, statusColor } = useRoomHeaderPresence(enabled, fields, roomUserId); + const { connected, activeUser, isDirectMessage, status, statusColor } = useRoomHeaderPresence(fields, roomUserId); const { title, subtitle, showClock } = useRoomHeaderContent(fields, tmid, activeUser, connected); const roomIcon = getRoomIcon(fields, isDirectMessage, status); const glyphImage = useHeaderIconImage( - enabled && fields.type ? roomIcon : undefined, + fields.type ? roomIcon : undefined, isDirectMessage || fields.type === 'l' ? statusColor : colors.fontTitlesLabels, TITLE_FONT_SIZE ); const server = useAppSelector(state => state.server.server); - const remoteUri = - enabled && connected && fields.type === 'l' ? getOmnichannelSidebarIconUri(server, fields.sourceType) : undefined; + const remoteUri = connected && fields.type === 'l' ? getOmnichannelSidebarIconUri(server, fields.sourceType) : undefined; const remoteImage = useHeaderRemoteImage(remoteUri); const roomImage = remoteImage ?? glyphImage; - const clockImage = useHeaderIconImage(enabled && showClock ? 'clock' : undefined, colors.fontSecondaryInfo, SUBTITLE_FONT_SIZE); + const clockImage = useHeaderIconImage(showClock ? 'clock' : undefined, colors.fontSecondaryInfo, SUBTITLE_FONT_SIZE); const subtitleImage = tmid ? roomImage : clockImage; useLayoutEffect(() => { - if (enabled) { - navigation.setOptions({ - headerTitle: title, - headerSubtitle: subtitle || undefined, - headerTitleImageSource: tmid ? undefined : roomImage, - headerSubtitleImageSource: subtitleImage, - headerTitleStyle: { color: colors.fontTitlesLabels }, - headerSubtitleColor: colors.fontSecondaryInfo, - headerTitleTestID: 'room-header', - onHeaderTitlePress: onTitlePress && !fields.disabled ? () => onTitlePress() : undefined - }); - } - }, [enabled, navigation, title, subtitle, tmid, roomImage, subtitleImage, colors, onTitlePress, fields.disabled]); + navigation.setOptions({ + headerTitle: title, + headerSubtitle: subtitle || undefined, + headerTitleImageSource: tmid ? undefined : roomImage, + headerSubtitleImageSource: subtitleImage, + headerTitleStyle: { color: colors.fontTitlesLabels }, + headerSubtitleColor: colors.fontSecondaryInfo, + headerTitleTestID: 'room-header', + onHeaderTitlePress: onTitlePress && !fields.disabled ? () => onTitlePress() : undefined + }); + }, [navigation, title, subtitle, tmid, roomImage, subtitleImage, colors, onTitlePress, fields.disabled]); }; diff --git a/app/views/RoomView/hooks/useRoomHeaderActions.ts b/app/views/RoomView/hooks/useRoomHeaderActions.ts index cb1c2257888..a96d210c95c 100644 --- a/app/views/RoomView/hooks/useRoomHeaderActions.ts +++ b/app/views/RoomView/hooks/useRoomHeaderActions.ts @@ -20,17 +20,17 @@ import { fromSubscription } from '../stores/RoomStoreContext'; import { closeLivechat } from '../services/closeLivechat'; import { placeLivechatOnHold } from '../services/placeLivechatOnHold'; import { navigateToScreen, type TRoomStackNavigation } from '../services/navigateToScreen'; -import { getRoomHeaderMode } from '../helpers/getRoomHeaderMode'; +import { getRoomHeaderMode, type TRoomHeaderMode } from '../helpers/getRoomHeaderMode'; import { splitRoomHeaderActions, type TRoomHeaderActionKey } from '../helpers/roomHeaderActions'; import { useCanPlaceLivechatOnHold } from './useCanPlaceLivechatOnHold'; import { useThreadFollowing } from './useThreadFollowing'; import { useRoomRightButtonsData } from '../components/RightButtons/useRoomRightButtonsData'; import { useHeaderCallPress } from '../components/RightButtons/useHeaderCallPress'; -const EMPTY_ACTIONS: IHeaderAction[] = []; +export const EMPTY_ACTIONS: IHeaderAction[] = []; const VISIBLE_ORDER: TRoomHeaderActionKey[] = ['encryption', 'notifications', 'call', 'threads']; -const useOmnichannelActions = (rid: string, roomStore: RoomStore, enabled: boolean): IHeaderAction[] => { +export const useOmnichannelActions = (rid: string, roomStore: RoomStore): IHeaderAction[] => { const navigation = useNavigation(); const isMasterDetail = useMasterDetail(); const livechatRequestComment = useSetting('Livechat_request_comment_when_closing_conversation') as boolean; @@ -40,13 +40,9 @@ const useOmnichannelActions = (rid: string, roomStore: RoomStore, enabled: boole fromSubscription(room => room.departmentId, undefined) ); const canForwardGuest = useStore(roomStore, s => s.canForwardGuest); - const canReturnQueue = useCanReturnQueue(enabled); + const canReturnQueue = useCanReturnQueue(true); const canPlaceLivechatOnHold = useCanPlaceLivechatOnHold(roomStore); - if (!enabled) { - return EMPTY_ACTIONS; - } - const returnInquiry = () => { showConfirmationAlert({ message: i18n.t('Would_you_like_to_return_the_inquiry'), @@ -96,14 +92,10 @@ const useOmnichannelActions = (rid: string, roomStore: RoomStore, enabled: boole ]; }; -const useThreadActions = (tmid: string | undefined, enabled: boolean): IHeaderAction[] => { +export const useThreadActions = (tmid: string): IHeaderAction[] => { const userId = useAppSelector(state => getUserSelector(state).id); const isFollowingThread = useThreadFollowing(tmid, userId); - if (!enabled) { - return EMPTY_ACTIONS; - } - return [ { label: i18n.t(isFollowingThread ? 'Unfollow_thread' : 'Follow_thread'), @@ -111,15 +103,13 @@ const useThreadActions = (tmid: string | undefined, enabled: boolean): IHeaderAc testID: isFollowingThread ? 'room-view-header-unfollow' : 'room-view-header-follow', onPress: () => { logEvent(events.ROOM_TOGGLE_FOLLOW_THREADS); - if (tmid) { - toggleFollowThread(tmid, isFollowingThread); - } + toggleFollowThread(tmid, isFollowingThread); } } ]; }; -const useRoomActions = (rid: string, roomStore: RoomStore, enabled: boolean): IHeaderAction[] => { +export const useRoomActions = (rid: string, roomStore: RoomStore): IHeaderAction[] => { const { theme, colors } = useTheme(); const { threadsEnabled, @@ -140,10 +130,6 @@ const useRoomActions = (rid: string, roomStore: RoomStore, enabled: boolean): IH } = useRoomRightButtonsData(rid, roomStore); const { callPresent, isCallDisabled, onPressCall } = useHeaderCallPress(rid); - if (!enabled) { - return EMPTY_ACTIONS; - } - const present: Partial> = { threads: threadsEnabled, call: !isSelfDm && callPresent, @@ -219,11 +205,7 @@ const useRoomActions = (rid: string, roomStore: RoomStore, enabled: boolean): IH ]; }; -export const useRoomHeaderActions = ( - rid: string | undefined, - tmid: string | undefined, - roomStore: RoomStore -): IHeaderAction[] => { +export const useRoomHeaderMode = (rid: string | undefined, tmid: string | undefined, roomStore: RoomStore): TRoomHeaderMode => { const { t, status, membership } = useStore( roomStore, useShallow(s => ({ @@ -233,20 +215,5 @@ export const useRoomHeaderActions = ( })) ); - const mode = getRoomHeaderMode({ rid, tmid, t, status, membership }); - - const omnichannelActions = useOmnichannelActions(rid ?? '', roomStore, mode === 'omnichannel'); - const threadActions = useThreadActions(tmid, mode === 'thread'); - const roomActions = useRoomActions(rid ?? '', roomStore, mode === 'room'); - - if (mode === 'omnichannel') { - return omnichannelActions; - } - if (mode === 'thread') { - return threadActions; - } - if (mode === 'room') { - return roomActions; - } - return EMPTY_ACTIONS; + return getRoomHeaderMode({ rid, tmid, t, status, membership }); }; diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 314215f61c6..6402bbffbd5 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -1,23 +1,11 @@ import { useEffect, useState } from 'react'; -import { useStore } from 'zustand'; -import { useShallow } from 'zustand/react/shallow'; -import { getRoomTitle } from '~/lib/methods/helpers'; -import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; -import { getInvitationActions, getInvitationText } from '~/lib/methods/getInvitationData'; -import { type IInviteSubscription } from '~/definitions'; -import { isSubscriptionModel } from '~/definitions/TRoom'; import { type IRoomViewProps } from './definitions'; -import { EncryptedRoom } from './components/EncryptedRoom'; -import { InvitedRoomScreen } from './components/InvitedRoomScreen'; -import { MissingRoomE2EEKey } from './components/MissingRoomE2EEKey'; -import { RoomBackground } from './components/RoomBackground'; -import RoomScreen from './RoomScreen'; +import { RoomViewContent } from './components/RoomViewContent'; +import { RoomViewHeader } from './components/RoomViewHeader/RoomViewHeader'; import { parseRoomRoute } from './services/parseRoomRoute'; import { createRoomStore, observeRoom } from './stores/RoomStore'; import { type RoomStore } from './definitions'; -import { useE2EEStatus } from './hooks/useE2EEStatus'; -import { useHeader } from './hooks/useHeader'; const RoomView = ({ route, navigation }: IRoomViewProps) => { const [input] = useState(() => parseRoomRoute(route.params)); @@ -26,44 +14,13 @@ const RoomView = ({ route, navigation }: IRoomViewProps) => { const [roomStore] = useState(() => createRoomStore({ rid, initialRoom, roomUserId })); const [ready, setReady] = useState(false); useEffect(() => observeRoom(rid, roomStore, () => setReady(true)), [rid, roomStore]); - const isEncryptable = useStore(roomStore, s => isSubscriptionModel(s.room)); - const invitation = useStore( - roomStore, - useShallow(s => (isSubscriptionModel(s.room) && isInviteSubscription(s.room) ? getInvitationText(s.room) : null)) - ); - const roomTitle = useStore(roomStore, s => getRoomTitle(s.room)); - - const { showMissingE2EEKey, showE2EEDisabledRoom } = useE2EEStatus(roomStore); - - useHeader({ rid, tmid, name, roomStore }); - - if (!rid || !t) { - return ; - } - if (invitation) { - return ( - getInvitationActions(roomStore.getState().room as IInviteSubscription).accept()} - onReject={() => getInvitationActions(roomStore.getState().room as IInviteSubscription).reject()} - /> - ); - } - - if (isEncryptable) { - if (showMissingE2EEKey) { - return ; - } - - if (showE2EEDisabledRoom) { - return ; - } - } - - return ; + return ( + <> + + + + ); }; export default RoomView; From 01ed90baf335492bd4874803d39547e961e0c2f3 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 1 Oct 2026 10:31:10 -0300 Subject: [PATCH 120/120] refactor: tidy RoomView header leaves and cover render isolation --- app/ee/omnichannel/hooks/useCanReturnQueue.ts | 2 +- .../RoomViewRenderIsolation.test.tsx | 70 +++++++++++++++++++ .../components/RightButtons/RightButtons.tsx | 2 +- .../{ => RoomViewContent}/RoomViewContent.tsx | 14 ++-- .../RoomViewHeader/NativeHeaderItems.tsx | 26 +++++-- .../RoomViewHeader/RoomHeaderActions.tsx | 42 +++++++---- .../RoomViewHeader/RoomViewHeader.tsx | 2 +- .../__tests__/RoomViewHeader.test.tsx | 32 ++++++++- app/views/RoomView/hooks/useHeaderFields.ts | 1 - .../RoomView/hooks/useNativeHeaderItems.tsx | 26 ------- .../RoomView/hooks/useRoomHeaderActions.ts | 2 +- app/views/RoomView/index.tsx | 2 +- 12 files changed, 161 insertions(+), 60 deletions(-) create mode 100644 app/views/RoomView/__tests__/RoomViewRenderIsolation.test.tsx rename app/views/RoomView/components/{ => RoomViewContent}/RoomViewContent.tsx (81%) delete mode 100644 app/views/RoomView/hooks/useNativeHeaderItems.tsx diff --git a/app/ee/omnichannel/hooks/useCanReturnQueue.ts b/app/ee/omnichannel/hooks/useCanReturnQueue.ts index 7085d1c8b0f..91603e2a586 100644 --- a/app/ee/omnichannel/hooks/useCanReturnQueue.ts +++ b/app/ee/omnichannel/hooks/useCanReturnQueue.ts @@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux'; import { routingConfigRequest } from '../actions/routingConfig'; import { useAppSelector } from '~/lib/hooks/useAppSelector'; -export function useCanReturnQueue(enabled: boolean): boolean { +export function useCanReturnQueue(enabled = true): boolean { const dispatch = useDispatch(); const returnQueue = useAppSelector(state => state.routingConfig.returnQueue); diff --git a/app/views/RoomView/__tests__/RoomViewRenderIsolation.test.tsx b/app/views/RoomView/__tests__/RoomViewRenderIsolation.test.tsx new file mode 100644 index 00000000000..715144516a5 --- /dev/null +++ b/app/views/RoomView/__tests__/RoomViewRenderIsolation.test.tsx @@ -0,0 +1,70 @@ +import { act, render } from '@testing-library/react-native'; +import { Provider } from 'react-redux'; +import { createStore as createReduxStore } from 'redux'; +import { createStore, type StoreApi } from 'zustand'; + +import RoomView from '../index'; +import { type IRoomViewProps, type RoomState } from '../definitions'; +import { useE2EEStatus } from '../hooks/useE2EEStatus'; + +const mockSetOptions = jest.fn(); +jest.mock('@react-navigation/native', () => ({ + useNavigation: () => ({ setOptions: mockSetOptions }) +})); +jest.mock('~/lib/methods/helpers', () => ({ + hasNativeHeaderBar: false, + getUidDirectMessage: jest.fn(), + getRoomTitle: () => 'general', + isGroupChat: () => false +})); +jest.mock('~/lib/methods/isInviteSubscription', () => ({ isInviteSubscription: () => false })); +jest.mock('../hooks/useE2EEStatus', () => ({ + useE2EEStatus: jest.fn(() => ({ showMissingE2EEKey: false, showE2EEDisabledRoom: false, hasE2EEWarning: false })) +})); +jest.mock('../hooks/useGoRoomActionsView', () => ({ useGoRoomActionsView: () => jest.fn() })); +jest.mock('../RoomScreen', () => ({ __esModule: true, default: () => null })); +jest.mock('../components/LeftButtons', () => ({ __esModule: true, default: 'LeftButtons' })); +jest.mock('../components/RightButtons/RightButtons', () => ({ __esModule: true, default: 'RightButtons' })); +jest.mock('~/containers/RoomHeader', () => ({ __esModule: true, default: 'RoomHeader' })); +jest.mock('../components/RightButtons/useRoomRightButtonsData', () => ({ useRoomRightButtonsData: jest.fn() })); +jest.mock('../components/RightButtons/useHeaderCallPress', () => ({ useHeaderCallPress: jest.fn() })); + +const mockRoomStore: { current: StoreApi> | null } = { current: null }; +jest.mock('../stores/RoomStore', () => ({ + createRoomStore: () => mockRoomStore.current, + observeRoom: (_rid: string, _store: unknown, onReady: () => void) => { + onReady(); + return jest.fn(); + } +})); + +const latestHeaderTitle = () => + mockSetOptions.mock.calls + .map(([options]) => options) + .filter(options => 'headerTitle' in options) + .at(-1) + .headerTitle(); + +it('updates the header without re-rendering the room content when only header data changes', () => { + mockRoomStore.current = createStore>(() => ({ + room: { id: 'sub-1', rid: 'rid-1', t: 'c', topic: 'Old topic' } as RoomState['room'], + roomUserId: null + })); + const route = { params: { rid: 'rid-1', t: 'c' } } as unknown as IRoomViewProps['route']; + const navigation = { setOptions: mockSetOptions } as unknown as IRoomViewProps['navigation']; + render( + ({ server: { version: '6.1.0' } }))}> + + + ); + const contentRenders = jest.mocked(useE2EEStatus).mock.calls.length; + + act(() => { + mockRoomStore.current?.setState({ + room: { id: 'sub-1', rid: 'rid-1', t: 'c', topic: 'New topic' } as RoomState['room'] + }); + }); + + expect(latestHeaderTitle().props.subtitle).toBe('New topic'); + expect(jest.mocked(useE2EEStatus).mock.calls.length).toBe(contentRenders); +}); diff --git a/app/views/RoomView/components/RightButtons/RightButtons.tsx b/app/views/RoomView/components/RightButtons/RightButtons.tsx index edef03cdad6..42c3350b533 100644 --- a/app/views/RoomView/components/RightButtons/RightButtons.tsx +++ b/app/views/RoomView/components/RightButtons/RightButtons.tsx @@ -11,7 +11,7 @@ interface IRightButtonsProps { } const RightButtons = ({ rid, tmid, roomStore }: IRightButtonsProps): ReactElement => ( - + ); export default RightButtons; diff --git a/app/views/RoomView/components/RoomViewContent.tsx b/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx similarity index 81% rename from app/views/RoomView/components/RoomViewContent.tsx rename to app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx index 4e2189512bc..7f4a608443c 100644 --- a/app/views/RoomView/components/RoomViewContent.tsx +++ b/app/views/RoomView/components/RoomViewContent/RoomViewContent.tsx @@ -6,13 +6,13 @@ import { isInviteSubscription } from '~/lib/methods/isInviteSubscription'; import { getInvitationActions, getInvitationText } from '~/lib/methods/getInvitationData'; import { type IInviteSubscription } from '~/definitions'; import { isSubscriptionModel } from '~/definitions/TRoom'; -import { type IRoomViewProps, type RoomStore } from '../definitions'; -import RoomScreen from '../RoomScreen'; -import { useE2EEStatus } from '../hooks/useE2EEStatus'; -import { EncryptedRoom } from './EncryptedRoom'; -import { InvitedRoomScreen } from './InvitedRoomScreen'; -import { MissingRoomE2EEKey } from './MissingRoomE2EEKey'; -import { RoomBackground } from './RoomBackground'; +import { type IRoomViewProps, type RoomStore } from '~/views/RoomView/definitions'; +import RoomScreen from '~/views/RoomView/RoomScreen'; +import { useE2EEStatus } from '~/views/RoomView/hooks/useE2EEStatus'; +import { EncryptedRoom } from '../EncryptedRoom'; +import { InvitedRoomScreen } from '../InvitedRoomScreen'; +import { MissingRoomE2EEKey } from '../MissingRoomE2EEKey'; +import { RoomBackground } from '../RoomBackground'; interface IRoomViewContentProps extends IRoomViewProps { rid?: string; diff --git a/app/views/RoomView/components/RoomViewHeader/NativeHeaderItems.tsx b/app/views/RoomView/components/RoomViewHeader/NativeHeaderItems.tsx index 0da00938a91..12703a28def 100644 --- a/app/views/RoomView/components/RoomViewHeader/NativeHeaderItems.tsx +++ b/app/views/RoomView/components/RoomViewHeader/NativeHeaderItems.tsx @@ -1,12 +1,14 @@ +import { useLayoutEffect } from 'react'; +import { useNavigation } from '@react-navigation/native'; import { useStore } from 'zustand'; -import { type IHeaderAction } from '~/lib/methods/helpers/navigation/headerActions'; -import { type RoomStore } from '~/views/RoomView/definitions'; +import { type IHeaderAction, nativeHeaderItems } from '~/lib/methods/helpers/navigation/headerActions'; +import { type IRoomViewProps, type RoomStore } from '~/views/RoomView/definitions'; import { useGoRoomActionsView } from '~/views/RoomView/hooks/useGoRoomActionsView'; import { useHeaderFields } from '~/views/RoomView/hooks/useHeaderFields'; -import { useNativeAvatarItem, useNativeRightItems } from '~/views/RoomView/hooks/useNativeHeaderItems'; import { useNativeBackButton } from '~/views/RoomView/hooks/useNativeBackButton'; import { useNativeRoomHeader } from '~/views/RoomView/hooks/useNativeRoomHeader'; +import LeftButtons from '../LeftButtons'; export const NativeRoomTitle = ({ tmid, @@ -30,11 +32,25 @@ export const NativeBackButton = ({ rid }: { rid: string }) => { }; export const NativeAvatarItem = ({ rid, roomStore }: { rid: string; roomStore: RoomStore }) => { - useNativeAvatarItem(rid, roomStore); + const navigation = useNavigation(); + + useLayoutEffect(() => { + navigation.setOptions({ + unstable_headerLeftItems: () => [ + { type: 'custom', element: , hidesSharedBackground: true } + ] + }); + }, [navigation, rid, roomStore]); + return null; }; export const NativeRightItems = ({ actions }: { actions: IHeaderAction[] }) => { - useNativeRightItems(actions); + const navigation = useNavigation(); + + useLayoutEffect(() => { + navigation.setOptions({ unstable_headerRightItems: () => nativeHeaderItems(actions) }); + }, [navigation, actions]); + return null; }; diff --git a/app/views/RoomView/components/RoomViewHeader/RoomHeaderActions.tsx b/app/views/RoomView/components/RoomViewHeader/RoomHeaderActions.tsx index 4e0b5d7da17..dc15c427df5 100644 --- a/app/views/RoomView/components/RoomViewHeader/RoomHeaderActions.tsx +++ b/app/views/RoomView/components/RoomViewHeader/RoomHeaderActions.tsx @@ -10,41 +10,57 @@ import { useThreadActions } from '~/views/RoomView/hooks/useRoomHeaderActions'; -type THeaderActionsSink = ComponentType<{ actions: IHeaderAction[] }>; +type TActionsRenderer = ComponentType<{ actions: IHeaderAction[] }>; interface IRoomHeaderActionsProps { rid?: string; tmid?: string; roomStore: RoomStore; - Sink: THeaderActionsSink; + ActionsRenderer: TActionsRenderer; } -const OmnichannelActions = ({ rid, roomStore, Sink }: { rid: string; roomStore: RoomStore; Sink: THeaderActionsSink }) => { +const OmnichannelActions = ({ + rid, + roomStore, + ActionsRenderer +}: { + rid: string; + roomStore: RoomStore; + ActionsRenderer: TActionsRenderer; +}) => { const actions = useOmnichannelActions(rid, roomStore); - return ; + return ; }; -const ThreadActions = ({ tmid, Sink }: { tmid: string; Sink: THeaderActionsSink }) => { +const ThreadActions = ({ tmid, ActionsRenderer }: { tmid: string; ActionsRenderer: TActionsRenderer }) => { const actions = useThreadActions(tmid); - return ; + return ; }; -const RoomActions = ({ rid, roomStore, Sink }: { rid: string; roomStore: RoomStore; Sink: THeaderActionsSink }) => { +const RoomActions = ({ + rid, + roomStore, + ActionsRenderer +}: { + rid: string; + roomStore: RoomStore; + ActionsRenderer: TActionsRenderer; +}) => { const actions = useRoomActions(rid, roomStore); - return ; + return ; }; -export const RoomHeaderActions = ({ rid, tmid, roomStore, Sink }: IRoomHeaderActionsProps): ReactElement => { +export const RoomHeaderActions = ({ rid, tmid, roomStore, ActionsRenderer }: IRoomHeaderActionsProps): ReactElement => { const mode = useRoomHeaderMode(rid, tmid, roomStore); if (rid && mode === 'omnichannel') { - return ; + return ; } if (tmid && mode === 'thread') { - return ; + return ; } if (rid && mode === 'room') { - return ; + return ; } - return ; + return ; }; diff --git a/app/views/RoomView/components/RoomViewHeader/RoomViewHeader.tsx b/app/views/RoomView/components/RoomViewHeader/RoomViewHeader.tsx index 0b42445501f..b31e229fdb2 100644 --- a/app/views/RoomView/components/RoomViewHeader/RoomViewHeader.tsx +++ b/app/views/RoomView/components/RoomViewHeader/RoomViewHeader.tsx @@ -19,7 +19,7 @@ const NativeRoomHeader = ({ rid, tmid, name, roomStore }: IRoomViewHeaderProps & <> {showsAvatar ? : } - + ); }; diff --git a/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx b/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx index a7eb7306ca6..e05230bbbe3 100644 --- a/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx +++ b/app/views/RoomView/components/RoomViewHeader/__tests__/RoomViewHeader.test.tsx @@ -4,6 +4,7 @@ import { createStore } from 'zustand'; import { type IHeaderAction } from '~/lib/methods/helpers/navigation/headerActions'; import { type RoomState, type RoomStore } from '~/views/RoomView/definitions'; import { useOmnichannelActions, useRoomActions, useThreadActions } from '~/views/RoomView/hooks/useRoomHeaderActions'; +import { useNativeRoomHeader } from '~/views/RoomView/hooks/useNativeRoomHeader'; import { RoomViewHeader } from '../RoomViewHeader'; const mockSetOptions = jest.fn(); @@ -28,6 +29,10 @@ jest.mock('~/views/RoomView/hooks/useUnreadsCount', () => ({ useUnreadsCount: () jest.mock('~/views/RoomView/components/LeftButtons', () => ({ __esModule: true, default: 'LeftButtons' })); jest.mock('~/views/RoomView/components/RightButtons/RightButtons', () => ({ __esModule: true, default: 'RightButtons' })); jest.mock('~/containers/RoomHeader', () => ({ __esModule: true, default: 'RoomHeader' })); +jest.mock('~/lib/methods/helpers/navigation/headerActions', () => ({ + ...jest.requireActual('~/lib/methods/helpers/navigation/headerActions'), + HeaderActions: 'HeaderActions' +})); jest.mock('~/views/RoomView/components/RightButtons/useRoomRightButtonsData', () => ({ useRoomRightButtonsData: jest.fn() })); jest.mock('~/views/RoomView/components/RightButtons/useHeaderCallPress', () => ({ useHeaderCallPress: jest.fn() })); @@ -85,16 +90,37 @@ describe('on the JS header', () => { expect(optionsWith('unstable_headerLeftItems')).toHaveLength(0); }); - it('does not mount any header action hook in the screen header', () => { - render(); + it('renders only the active mode actions in the JS header buttons', () => { + const { default: ActualRightButtons } = jest.requireActual('~/views/RoomView/components/RightButtons/RightButtons'); + render(); + expect(useThreadActions).not.toHaveBeenCalled(); + const { props } = lastOptionsWith('headerRight').headerRight(); + const { toJSON } = render(); + + expect(useThreadActions).toHaveBeenCalledWith('tmid-1'); expect(useRoomActions).not.toHaveBeenCalled(); - expect(useThreadActions).not.toHaveBeenCalled(); expect(useOmnichannelActions).not.toHaveBeenCalled(); + expect(toJSON()).toMatchObject({ + type: 'HeaderActions', + props: { actions: [expect.objectContaining({ label: 'Follow_thread' })] } + }); }); }); describe('on the native header bar', () => { + it('drives the title through the native header instead of a JS title', () => { + render(); + + expect(useNativeRoomHeader).toHaveBeenCalledWith( + expect.objectContaining({ title: 'general' }), + undefined, + null, + expect.any(Function) + ); + expect(optionsWith('headerTitle')).toHaveLength(0); + }); + it('leaves the header untouched without a rid', () => { render(); diff --git a/app/views/RoomView/hooks/useHeaderFields.ts b/app/views/RoomView/hooks/useHeaderFields.ts index a2ce4b61f21..03db365fbfe 100644 --- a/app/views/RoomView/hooks/useHeaderFields.ts +++ b/app/views/RoomView/hooks/useHeaderFields.ts @@ -21,7 +21,6 @@ export interface IHeaderFields { disabled: boolean; } -/** `threadName` is only read when `tmid` is set, since the room title is derived from the room. */ export const useHeaderFields = (roomStore: RoomStore, tmid?: string, threadName?: string): IHeaderFields => useStore( roomStore, diff --git a/app/views/RoomView/hooks/useNativeHeaderItems.tsx b/app/views/RoomView/hooks/useNativeHeaderItems.tsx deleted file mode 100644 index 9b394cddd59..00000000000 --- a/app/views/RoomView/hooks/useNativeHeaderItems.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useLayoutEffect } from 'react'; -import { useNavigation } from '@react-navigation/native'; - -import { type IHeaderAction, nativeHeaderItems } from '~/lib/methods/helpers/navigation/headerActions'; -import LeftButtons from '../components/LeftButtons'; -import { type IRoomViewProps, type RoomStore } from '../definitions'; - -export const useNativeAvatarItem = (rid: string, roomStore: RoomStore) => { - const navigation = useNavigation(); - - useLayoutEffect(() => { - navigation.setOptions({ - unstable_headerLeftItems: () => [ - { type: 'custom', element: , hidesSharedBackground: true } - ] - }); - }, [navigation, rid, roomStore]); -}; - -export const useNativeRightItems = (actions: IHeaderAction[]) => { - const navigation = useNavigation(); - - useLayoutEffect(() => { - navigation.setOptions({ unstable_headerRightItems: () => nativeHeaderItems(actions) }); - }, [navigation, actions]); -}; diff --git a/app/views/RoomView/hooks/useRoomHeaderActions.ts b/app/views/RoomView/hooks/useRoomHeaderActions.ts index a96d210c95c..f7551a6dde0 100644 --- a/app/views/RoomView/hooks/useRoomHeaderActions.ts +++ b/app/views/RoomView/hooks/useRoomHeaderActions.ts @@ -40,7 +40,7 @@ export const useOmnichannelActions = (rid: string, roomStore: RoomStore): IHeade fromSubscription(room => room.departmentId, undefined) ); const canForwardGuest = useStore(roomStore, s => s.canForwardGuest); - const canReturnQueue = useCanReturnQueue(true); + const canReturnQueue = useCanReturnQueue(); const canPlaceLivechatOnHold = useCanPlaceLivechatOnHold(roomStore); const returnInquiry = () => { diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 6402bbffbd5..34326d8831e 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { type IRoomViewProps } from './definitions'; -import { RoomViewContent } from './components/RoomViewContent'; +import { RoomViewContent } from './components/RoomViewContent/RoomViewContent'; import { RoomViewHeader } from './components/RoomViewHeader/RoomViewHeader'; import { parseRoomRoute } from './services/parseRoomRoute'; import { createRoomStore, observeRoom } from './stores/RoomStore';