From 33514b34178af476632f05da8ae13b7b753f8809 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Mon, 30 Jun 2025 13:55:58 +0300 Subject: [PATCH 01/12] Fix Constants.statusBarHeight on iOS --- src/commons/Constants.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/commons/Constants.ts b/src/commons/Constants.ts index 978d043813..e33c5dc202 100644 --- a/src/commons/Constants.ts +++ b/src/commons/Constants.ts @@ -41,17 +41,8 @@ isTablet = function setStatusBarHeight() { const {StatusBarManager} = NativeModules; - statusBarHeight = (StatusBar.currentHeight ?? StatusBarManager?.HEIGHT) || 0; - - if (isIOS && StatusBarManager) { - try { - // override guesstimate height with the actual height from StatusBarManager - StatusBarManager.getHeight((data:{height:number}) => (statusBarHeight = data.height)); - } catch (error) { - console.warn('Constants: StatusBarManager.getHeight not available in new architecture, using fallback'); - // Keep the fallback height we already set above - } - } + // override guesstimate height with the actual height from StatusBarManager + statusBarHeight = (StatusBar.currentHeight ?? StatusBarManager?.getConstants?.()?.HEIGHT) || 0; } function getAspectRatio() { From 500ad1ff56495572c5fb970ee1e2d2e4e9d435d7 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Mon, 30 Jun 2025 19:28:32 +0300 Subject: [PATCH 02/12] SafeAreaInsetsManager - add tests and small fixes --- .../SafeArea/SafeAreaInsetsManager.ts | 41 +-- .../__tests__/SafeAreaInsetsManager.spec.js | 271 ++++++++++++++++++ 2 files changed, 295 insertions(+), 17 deletions(-) create mode 100644 lib/components/SafeArea/__tests__/SafeAreaInsetsManager.spec.js diff --git a/lib/components/SafeArea/SafeAreaInsetsManager.ts b/lib/components/SafeArea/SafeAreaInsetsManager.ts index a3d80c5392..cb185c5478 100644 --- a/lib/components/SafeArea/SafeAreaInsetsManager.ts +++ b/lib/components/SafeArea/SafeAreaInsetsManager.ts @@ -3,20 +3,23 @@ import _ from 'lodash'; import {NativeModules, DeviceEventEmitter} from 'react-native'; -type SafeAreaInsetsType = { top: number; left: number; bottom: number; right: number; } | null; +export type SafeAreaInsetsType = {top: number; left: number; bottom: number; right: number} | null; +export type SafeAreaChangedDelegateType = { + onSafeAreaInsetsDidChangeEvent?: (insets: SafeAreaInsetsType) => void; +}; let SafeAreaInsetsCache: SafeAreaInsetsType = null; class SafeAreaInsetsManager { _defaultInsets: SafeAreaInsetsType = {top: 47, left: 0, bottom: 34, right: 0}; // Common iPhone safe area values - _safeAreaInsets: SafeAreaInsetsType = {top: 47, left: 0, bottom: 34, right: 0}; - _safeAreaChangedDelegates: Array = []; + _safeAreaInsets: SafeAreaInsetsType; + _safeAreaChangedDelegates: Array = []; _nativeModule: any = null; constructor() { // Initialize with default values this._safeAreaInsets = this._defaultInsets; - + // Try to connect to native module this.setupNativeConnection(); } @@ -25,12 +28,12 @@ class SafeAreaInsetsManager { try { // Access the native module directly without causing getConstants this._nativeModule = NativeModules.SafeAreaManager; - - if (this._nativeModule) { + + if (this._nativeModule) { // Set up event listener using DeviceEventEmitter instead of NativeEventEmitter // This avoids getConstants issues this.setupEventListener(); - + // Get initial safe area insets this.getInitialInsets(); } else { @@ -43,8 +46,8 @@ class SafeAreaInsetsManager { setupEventListener() { try { - // Use DeviceEventEmitter instead of NativeEventEmitter to avoid getConstants - DeviceEventEmitter.addListener('SafeAreaInsetsDidChangeEvent', (data) => { + // Use DeviceEventEmitter instead of NativeEventEmitter to avoid getConstants + DeviceEventEmitter.addListener('SafeAreaInsetsDidChangeEvent', (data: SafeAreaInsetsType) => { if (data) { SafeAreaInsetsCache = data; this._safeAreaInsets = data; @@ -63,7 +66,6 @@ class SafeAreaInsetsManager { try { const insets = await this._nativeModule.getSafeAreaInsets(); - if (insets) { SafeAreaInsetsCache = insets; this._safeAreaInsets = insets; @@ -75,7 +77,7 @@ class SafeAreaInsetsManager { } notifyDelegates(insets: SafeAreaInsetsType) { - _.forEach(this._safeAreaChangedDelegates, (delegate) => { + _.forEach(this._safeAreaChangedDelegates, (delegate: SafeAreaChangedDelegateType) => { if (delegate.onSafeAreaInsetsDidChangeEvent) { delegate.onSafeAreaInsetsDidChangeEvent(insets); } @@ -85,8 +87,13 @@ class SafeAreaInsetsManager { async _updateInsets() { if (this._nativeModule && SafeAreaInsetsCache === null) { try { - SafeAreaInsetsCache = await this._nativeModule.getSafeAreaInsets(); - this._safeAreaInsets = SafeAreaInsetsCache; + const insets = await this._nativeModule.getSafeAreaInsets(); + if (insets) { + SafeAreaInsetsCache = insets; + this._safeAreaInsets = SafeAreaInsetsCache; + } else { + this._safeAreaInsets = this._defaultInsets; + } } catch (error) { console.warn('SafeAreaInsetsManager: Failed to get native insets:', error); this._safeAreaInsets = this._defaultInsets; @@ -103,12 +110,12 @@ class SafeAreaInsetsManager { return this._safeAreaInsets; } - addSafeAreaChangedDelegate(delegate: any) { + addSafeAreaChangedDelegate(delegate: SafeAreaChangedDelegateType) { this._safeAreaChangedDelegates.push(delegate); } - removeSafeAreaChangedDelegate(delegateToRemove: any) { - _.remove(this._safeAreaChangedDelegates, (currentDelegate) => { + removeSafeAreaChangedDelegate(delegateToRemove: SafeAreaChangedDelegateType) { + _.remove(this._safeAreaChangedDelegates, (currentDelegate: SafeAreaChangedDelegateType) => { return currentDelegate === delegateToRemove; }); } @@ -122,7 +129,7 @@ class SafeAreaInsetsManager { const previousInsets = this._safeAreaInsets; SafeAreaInsetsCache = null; // Force refresh await this._updateInsets(); - + // Notify delegates if insets changed if (!_.isEqual(previousInsets, this._safeAreaInsets)) { this.notifyDelegates(this._safeAreaInsets); diff --git a/lib/components/SafeArea/__tests__/SafeAreaInsetsManager.spec.js b/lib/components/SafeArea/__tests__/SafeAreaInsetsManager.spec.js new file mode 100644 index 0000000000..472bc7a60e --- /dev/null +++ b/lib/components/SafeArea/__tests__/SafeAreaInsetsManager.spec.js @@ -0,0 +1,271 @@ +import {NativeModules, DeviceEventEmitter} from 'react-native'; + +describe('SafeAreaInsetsManager', () => { + beforeEach(() => { + // Reset mocks + jest.clearAllMocks(); + + // Reset the SafeAreaInsetsCache by creating a fresh instance + jest.resetModules(); + + // Spy on console methods to verify logging + jest.spyOn(console, 'log').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + // Restore console methods + jest.restoreAllMocks(); + }); + + describe('getSafeAreaInsets', () => { + it('should return default insets when native module is not available', async () => { + // Arrange + NativeModules.SafeAreaManager = null; + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Act + const result = await SafeAreaInsetsManager.getSafeAreaInsets(); + + // Assert + expect(result).toEqual({top: 47, left: 0, bottom: 34, right: 0}); + expect(console.log).toHaveBeenCalledWith('SafeAreaInsetsManager: Native SafeAreaManager not available, using defaults'); + }); + + it('should return insets from native module when available', async () => { + // Arrange + const mockInsets = {top: 50, left: 10, bottom: 30, right: 10}; + NativeModules.SafeAreaManager = { + getSafeAreaInsets: jest.fn().mockResolvedValue(mockInsets) + }; + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Act + const result = await SafeAreaInsetsManager.getSafeAreaInsets(); + + // Assert + expect(result).toEqual(mockInsets); + expect(NativeModules.SafeAreaManager.getSafeAreaInsets).toHaveBeenCalled(); + }); + + it.skip('should return cached insets on subsequent calls', async () => { + // Arrange + const mockInsets = {top: 44, left: 0, bottom: 34, right: 0}; + NativeModules.SafeAreaManager = { + getSafeAreaInsets: jest.fn().mockResolvedValue(mockInsets) + }; + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Act + const result1 = await SafeAreaInsetsManager.getSafeAreaInsets(); + const result2 = await SafeAreaInsetsManager.getSafeAreaInsets(); + + // Assert + expect(result1).toEqual(mockInsets); + expect(result2).toEqual(mockInsets); + expect(NativeModules.SafeAreaManager.getSafeAreaInsets).toHaveBeenCalledTimes(1); // Should only call native once due to caching + }); + + it('should handle native module errors gracefully', async () => { + // Arrange + const mockError = new Error('Native module error'); + NativeModules.SafeAreaManager = { + getSafeAreaInsets: jest.fn().mockRejectedValue(mockError) + }; + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Act + const result = await SafeAreaInsetsManager.getSafeAreaInsets(); + + // Assert + expect(result).toEqual({top: 47, left: 0, bottom: 34, right: 0}); // Should fallback to defaults + expect(console.warn).toHaveBeenCalledWith('SafeAreaInsetsManager: Failed to get initial insets:', mockError); + expect(console.warn).toHaveBeenCalledWith('SafeAreaInsetsManager: Failed to get native insets:', mockError); + }); + + it('should handle native module setup errors gracefully', async () => { + // Arrange + Object.defineProperty(NativeModules, 'SafeAreaManager', { + get: () => { + throw new Error('Setup error'); + } + }); + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Act + const result = await SafeAreaInsetsManager.getSafeAreaInsets(); + + // Assert + expect(result).toEqual({top: 47, left: 0, bottom: 34, right: 0}); // Should fallback to defaults + expect(console.warn).toHaveBeenCalledWith('SafeAreaInsetsManager: Failed to connect to native module:', expect.any(Error)); + }); + + it('should update insets when they change during the test', async () => { + // Arrange + const initialInsets = {top: 44, left: 0, bottom: 34, right: 0}; + const updatedInsets = {top: 50, left: 0, bottom: 40, right: 0}; + + NativeModules.SafeAreaManager = { + // TODO: this will need to be changed when the we get caching to work in tests ("should return cached insets on subsequent calls") + // getSafeAreaInsets: jest.fn().mockResolvedValueOnce(initialInsets).mockResolvedValueOnce(updatedInsets) + getSafeAreaInsets: jest + .fn() + .mockResolvedValueOnce(initialInsets) + .mockResolvedValueOnce(initialInsets) + .mockResolvedValueOnce(updatedInsets) + }; + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Act & Assert - Initial insets + const result1 = await SafeAreaInsetsManager.getSafeAreaInsets(); + expect(result1).toEqual(initialInsets); + + // Force refresh of insets + await SafeAreaInsetsManager.refreshSafeAreaInsets(); + + // Simulate insets change event from native side + DeviceEventEmitter.emit('SafeAreaInsetsDidChangeEvent', updatedInsets); + + // Get insets again - should reflect the change + const result2 = await SafeAreaInsetsManager.getSafeAreaInsets(); + expect(result2).toEqual(updatedInsets); + }); + + it('should notify delegates when insets change during the test', async () => { + // Arrange + const initialInsets = {top: 44, left: 0, bottom: 34, right: 0}; + const updatedInsets = {top: 50, left: 0, bottom: 40, right: 0}; + + NativeModules.SafeAreaManager = { + getSafeAreaInsets: jest.fn().mockResolvedValue(initialInsets) + }; + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Add a mock delegate + const mockDelegate = { + onSafeAreaInsetsDidChangeEvent: jest.fn() + }; + SafeAreaInsetsManager.addSafeAreaChangedDelegate(mockDelegate); + + // Act - Get initial insets + await SafeAreaInsetsManager.getSafeAreaInsets(); + + // Simulate insets change event from native side + DeviceEventEmitter.emit('SafeAreaInsetsDidChangeEvent', updatedInsets); + + // Assert - Delegate should be notified + expect(mockDelegate.onSafeAreaInsetsDidChangeEvent).toHaveBeenCalledWith(updatedInsets); + }); + + it('should handle refreshSafeAreaInsets correctly', async () => { + // Arrange + const initialInsets = {top: 44, left: 0, bottom: 34, right: 0}; + const refreshedInsets = {top: 48, left: 0, bottom: 36, right: 0}; + + NativeModules.SafeAreaManager = { + // TODO: this will need to be changed when the we get caching to work in tests ("should return cached insets on subsequent calls") + // getSafeAreaInsets: jest.fn().mockResolvedValueOnce(initialInsets).mockResolvedValueOnce(updatedInsets) + getSafeAreaInsets: jest + .fn() + .mockResolvedValueOnce(initialInsets) + .mockResolvedValueOnce(initialInsets) + .mockResolvedValueOnce(refreshedInsets) + }; + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Act + const result1 = await SafeAreaInsetsManager.getSafeAreaInsets(); + expect(result1).toEqual(initialInsets); + + // Refresh insets + await SafeAreaInsetsManager.refreshSafeAreaInsets(); + + const result2 = await SafeAreaInsetsManager.getSafeAreaInsets(); + + // Assert + expect(result2).toEqual(refreshedInsets); + // TODO: this will need to be changed when the we get caching to work in tests ("should return cached insets on subsequent calls") + expect(NativeModules.SafeAreaManager.getSafeAreaInsets).toHaveBeenCalledTimes(3); + }); + + it('should not notify delegates when insets remain the same after refresh', async () => { + // Arrange + const sameInsets = {top: 44, left: 0, bottom: 34, right: 0}; + + NativeModules.SafeAreaManager = { + getSafeAreaInsets: jest.fn().mockResolvedValue(sameInsets) + }; + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Add a mock delegate + const mockDelegate = { + onSafeAreaInsetsDidChangeEvent: jest.fn() + }; + SafeAreaInsetsManager.addSafeAreaChangedDelegate(mockDelegate); + + // Act + await SafeAreaInsetsManager.getSafeAreaInsets(); + await SafeAreaInsetsManager.refreshSafeAreaInsets(); + + // TODO: this will need to be changed when the we get caching to work in tests ("should return cached insets on subsequent calls") + expect(NativeModules.SafeAreaManager.getSafeAreaInsets).toHaveBeenCalledTimes(3); + + // Assert - Delegate should not be notified since insets didn't change + expect(mockDelegate.onSafeAreaInsetsDidChangeEvent).not.toHaveBeenCalled(); + }); + + it('should return default insets when native getSafeAreaInsets returns null', async () => { + // Arrange + NativeModules.SafeAreaManager = { + getSafeAreaInsets: jest.fn().mockResolvedValue(null) + }; + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + // Act + const result = await SafeAreaInsetsManager.getSafeAreaInsets(); + + // Assert + expect(result).toEqual({top: 47, left: 0, bottom: 34, right: 0}); + }); + + it('should properly manage delegate lifecycle', async () => { + // Arrange + NativeModules.SafeAreaManager = { + getSafeAreaInsets: jest.fn().mockResolvedValue({top: 44, left: 0, bottom: 34, right: 0}) + }; + + const SafeAreaInsetsManager = require('../SafeAreaInsetsManager').default; + + const mockDelegate1 = { + onSafeAreaInsetsDidChangeEvent: jest.fn() + }; + const mockDelegate2 = { + onSafeAreaInsetsDidChangeEvent: jest.fn() + }; + + // Act + SafeAreaInsetsManager.addSafeAreaChangedDelegate(mockDelegate1); + SafeAreaInsetsManager.addSafeAreaChangedDelegate(mockDelegate2); + + // Remove one delegate + SafeAreaInsetsManager.removeSafeAreaChangedDelegate(mockDelegate1); + + // Trigger notification + const newInsets = {top: 50, left: 0, bottom: 40, right: 0}; + SafeAreaInsetsManager.notifyDelegates(newInsets); + + // Assert + expect(mockDelegate1.onSafeAreaInsetsDidChangeEvent).not.toHaveBeenCalled(); + expect(mockDelegate2.onSafeAreaInsetsDidChangeEvent).toHaveBeenCalledWith(newInsets); + }); + }); +}); From c260b59a2d250dbc7ff6d37eccba76f0798a338f Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Tue, 1 Jul 2025 14:13:45 +0300 Subject: [PATCH 03/12] Fix typo --- src/components/segmentedControl/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/segmentedControl/index.tsx b/src/components/segmentedControl/index.tsx index a2e21137b1..4b667249ca 100644 --- a/src/components/segmentedControl/index.tsx +++ b/src/components/segmentedControl/index.tsx @@ -105,7 +105,7 @@ export type SegmentedControlProps = { labelProps?: TextProps; }; -const nonAreUndefined = (array: Array): array is Array => { +const noneAreUndefined = (array: Array): array is Array => { 'worklet'; for (const item of array) { if (item === undefined) { @@ -183,7 +183,7 @@ const SegmentedControl = (props: SegmentedControlProps) => { // } const {x, width} = event.nativeEvent.layout; segmentsDimensions.current[index] = {x, width}; - if (segmentsDimensions.current.length === segments.length && nonAreUndefined(segmentsDimensions.current)) { + if (segmentsDimensions.current.length === segments.length && noneAreUndefined(segmentsDimensions.current)) { segmentsStyle.value = [...segmentsDimensions.current]; // shouldResetOnDimensionsOnNextLayout.current = true;// in case onLayout will be called again (orientation change etc.) } @@ -198,7 +198,7 @@ const SegmentedControl = (props: SegmentedControlProps) => { const animatedStyle = useAnimatedStyle(() => { const {value} = segmentsStyle; const {value: height} = containerHeight; - if (height !== 0 && value.length === segments.length && nonAreUndefined(value)) { + if (height !== 0 && value.length === segments.length && noneAreUndefined(value)) { const isFirstElementSelected = animatedSelectedIndex.value === 0; const isLastElementSelected = animatedSelectedIndex.value === value.length - 1; const isMiddleSelected = !isFirstElementSelected && !isLastElementSelected; From 9464eac947d7d29de360cc6cd2716bf7418a8a60 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Tue, 1 Jul 2025 14:14:29 +0300 Subject: [PATCH 04/12] Fix type --- src/components/segmentedControl/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/segmentedControl/index.tsx b/src/components/segmentedControl/index.tsx index 4b667249ca..f0635f12b6 100644 --- a/src/components/segmentedControl/index.tsx +++ b/src/components/segmentedControl/index.tsx @@ -144,7 +144,7 @@ const SegmentedControl = (props: SegmentedControlProps) => { labelProps } = useSegmentedControlPreset(props); const animatedSelectedIndex = useSharedValue(initialIndex); - const segmentsStyle = useSharedValue([] as {x: number; width: number}[]); + const segmentsStyle = useSharedValue<{x: number; width: number}[]>([]); // const shouldResetOnDimensionsOnNextLayout = useRef(false); // use this flag if there bugs with onLayout being called more than once. const segmentsDimensions = useRef<{x: number; width: number}[]>([]); const containerHeight = useSharedValue(0); From 7905720de415a6c81955d7798611b3b09112b998 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Wed, 2 Jul 2025 15:52:07 +0300 Subject: [PATCH 05/12] Update iOS' project --- ios/rnuilib.xcodeproj/project.pbxproj | 68 +++++++++++++-------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/ios/rnuilib.xcodeproj/project.pbxproj b/ios/rnuilib.xcodeproj/project.pbxproj index 87336a18b1..3776104294 100644 --- a/ios/rnuilib.xcodeproj/project.pbxproj +++ b/ios/rnuilib.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0025F6AE1371A21B7A28048F /* libPods-rnuilib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B8ED5E0C1BDBAE86825D1D25 /* libPods-rnuilib.a */; }; 10C29C812DCE637B0050BB15 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 10C29C802DCE637B0050BB15 /* AppDelegate.mm */; }; 10C29C832DCE7AE00050BB15 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 10C29C822DCE7AE00050BB15 /* AppDelegate.mm */; }; 10C29C852DCE7AED0050BB15 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 10C29C842DCE7AED0050BB15 /* main.m */; }; @@ -16,7 +17,6 @@ 8E8B0D662744D9CD0026B520 /* void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E8B0D652744D9CD0026B520 /* void.swift */; }; 8EA1FC8C2519E7F7008B4B36 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8EA1FC8B2519E7F7008B4B36 /* LaunchScreen.storyboard */; }; BD943D6D9239B59015528C14 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = DED5A85D4BF49DFA47437893 /* PrivacyInfo.xcprivacy */; }; - C2BFF53A3D9C89E8C38C119D /* libPods-rnuilib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1BEF55D1269FAD80AF15FA /* libPods-rnuilib.a */; }; C41C90B146B08809BC045295 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 411699F1CD4A37F8779A4620 /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -49,16 +49,16 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = rnuilib/Info.plist; sourceTree = ""; }; 2D02E47B1E0B4A5D006451C7 /* rnuilib-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "rnuilib-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E4901E0B4A5D006451C7 /* rnuilib-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "rnuilib-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 3092D044D40F28F133D81A80 /* Pods-rnuilib.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-rnuilib.release.xcconfig"; path = "Target Support Files/Pods-rnuilib/Pods-rnuilib.release.xcconfig"; sourceTree = ""; }; - 3B1BEF55D1269FAD80AF15FA /* libPods-rnuilib.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-rnuilib.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 411699F1CD4A37F8779A4620 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = rnuilib/PrivacyInfo.xcprivacy; sourceTree = ""; }; 8E52CBDE2887DD21009D5EC5 /* DesignTokens.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DesignTokens.xcassets; sourceTree = ""; }; 8E8B0D652744D9CD0026B520 /* void.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = void.swift; sourceTree = ""; }; 8EA1FC8B2519E7F7008B4B36 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = rnuilib/LaunchScreen.storyboard; sourceTree = ""; }; - 961D3F437C09AAFC776D80CB /* Pods-rnuilib.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-rnuilib.debug.xcconfig"; path = "Target Support Files/Pods-rnuilib/Pods-rnuilib.debug.xcconfig"; sourceTree = ""; }; + B8ED5E0C1BDBAE86825D1D25 /* libPods-rnuilib.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-rnuilib.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DED5A85D4BF49DFA47437893 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = rnuilib/PrivacyInfo.xcprivacy; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; + F3B5DA6EFA700534B83CFD2F /* Pods-rnuilib.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-rnuilib.release.xcconfig"; path = "Target Support Files/Pods-rnuilib/Pods-rnuilib.release.xcconfig"; sourceTree = ""; }; + F9327185F12A295616AF2661 /* Pods-rnuilib.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-rnuilib.debug.xcconfig"; path = "Target Support Files/Pods-rnuilib/Pods-rnuilib.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -73,7 +73,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C2BFF53A3D9C89E8C38C119D /* libPods-rnuilib.a in Frameworks */, + 0025F6AE1371A21B7A28048F /* libPods-rnuilib.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -117,7 +117,7 @@ children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED2971642150620600B7C4FE /* JavaScriptCore.framework */, - 3B1BEF55D1269FAD80AF15FA /* libPods-rnuilib.a */, + B8ED5E0C1BDBAE86825D1D25 /* libPods-rnuilib.a */, ); name = Frameworks; sourceTree = ""; @@ -158,8 +158,8 @@ D99C980AB24A05601E0007F9 /* Pods */ = { isa = PBXGroup; children = ( - 961D3F437C09AAFC776D80CB /* Pods-rnuilib.debug.xcconfig */, - 3092D044D40F28F133D81A80 /* Pods-rnuilib.release.xcconfig */, + F9327185F12A295616AF2661 /* Pods-rnuilib.debug.xcconfig */, + F3B5DA6EFA700534B83CFD2F /* Pods-rnuilib.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -189,14 +189,14 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "rnuilib" */; buildPhases = ( - 1948831A105FB85C7FF19246 /* [CP] Check Pods Manifest.lock */, + 6BCBA2C4A6DF73CFB61A89F8 /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 2304E9EEAAF1BDEF6BC7EBEB /* [CP] Embed Pods Frameworks */, - 668BE1E2EE321ADFA49231D6 /* [CP] Copy Pods Resources */, + 072B17D178A7108519128960 /* [CP] Embed Pods Frameworks */, + 9EAD34A4665B9A7233E02B45 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -345,61 +345,61 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 1948831A105FB85C7FF19246 /* [CP] Check Pods Manifest.lock */ = { + 072B17D178A7108519128960 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-rnuilib/Pods-rnuilib-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-rnuilib-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-rnuilib/Pods-rnuilib-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 2304E9EEAAF1BDEF6BC7EBEB /* [CP] Embed Pods Frameworks */ = { + 2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-rnuilib/Pods-rnuilib-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - name = "[CP] Embed Pods Frameworks"; + name = "Bundle React Native Code And Images"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-rnuilib/Pods-rnuilib-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; }; - 2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = { + 6BCBA2C4A6DF73CFB61A89F8 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Bundle React Native Code And Images"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-rnuilib-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; - 668BE1E2EE321ADFA49231D6 /* [CP] Copy Pods Resources */ = { + 9EAD34A4665B9A7233E02B45 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -562,7 +562,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 961D3F437C09AAFC776D80CB /* Pods-rnuilib.debug.xcconfig */; + baseConfigurationReference = F9327185F12A295616AF2661 /* Pods-rnuilib.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -594,7 +594,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3092D044D40F28F133D81A80 /* Pods-rnuilib.release.xcconfig */; + baseConfigurationReference = F3B5DA6EFA700534B83CFD2F /* Pods-rnuilib.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; From 46cb881780efad144785405130aed2d095a2c731 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Wed, 2 Jul 2025 15:58:13 +0300 Subject: [PATCH 06/12] Fix reanimated interaction with RN's Modal on Android --- src/components/modal/index.tsx | 29 +++++++++++++++++++---------- src/components/modal/modal.api.json | 5 +++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/modal/index.tsx b/src/components/modal/index.tsx index ce8692b4ce..8bd465bc2a 100644 --- a/src/components/modal/index.tsx +++ b/src/components/modal/index.tsx @@ -57,6 +57,11 @@ export interface ModalProps extends RNModalProps { * Send additional props to the KeyboardAvoidingView (iOS only) */ keyboardAvoidingViewProps?: KeyboardAvoidingViewProps; + /** + * Fix RNModal's interaction with react-native-reanimated (Android only, default: true) + * See this https://github.com/software-mansion/react-native-reanimated/issues/6659#issuecomment-2704931585 + */ + fixReanimatedInteraction?: boolean; } /** @@ -105,6 +110,7 @@ class Modal extends Component { render() { const { + fixReanimatedInteraction = true, blurView, enableModalBlur, visible, @@ -122,18 +128,21 @@ class Modal extends Component { ? {behavior: 'padding', ...keyboardAvoidingViewProps, style: [styles.fill, keyboardAvoidingViewProps?.style]} : {}; const Container: any = blurView ? blurView : defaultContainer; + const HackContainer = fixReanimatedInteraction && Constants.isAndroid ? View : React.Fragment; return ( - - - - - {this.renderTouchableOverlay()} - {this.props.children} - - - - + + + + + + {this.renderTouchableOverlay()} + {this.props.children} + + + + + ); } } diff --git a/src/components/modal/modal.api.json b/src/components/modal/modal.api.json index 88208bab05..aa2cc0696e 100644 --- a/src/components/modal/modal.api.json +++ b/src/components/modal/modal.api.json @@ -38,6 +38,11 @@ "name": "keyboardAvoidingViewProps", "type": "object", "description": "Send additional props to the KeyboardAvoidingView (iOS only)" + }, + { + "name": "fixReanimatedInteraction", + "type": "boolean", + "description": "Fix RNModal's interaction with react-native-reanimated (Android only, default: true)" } ], "snippet": [ From dc7ea932f84bf87e12a1a2917e420dce423d7437 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Wed, 2 Jul 2025 16:05:20 +0300 Subject: [PATCH 07/12] Remove (unused) AppDelegate.swift --- ios/rnuilib/AppDelegate.swift | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 ios/rnuilib/AppDelegate.swift diff --git a/ios/rnuilib/AppDelegate.swift b/ios/rnuilib/AppDelegate.swift deleted file mode 100644 index eee91b256d..0000000000 --- a/ios/rnuilib/AppDelegate.swift +++ /dev/null @@ -1,30 +0,0 @@ -import UIKit -import React -import React_RCTAppDelegate -import ReactAppDependencyProvider - -@main -class AppDelegate: RCTAppDelegate { - override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - self.moduleName = "rnuilib" - self.dependencyProvider = RCTAppDependencyProvider() - - // You can add your custom initial props in the dictionary below. - // They will be passed down to the ViewController used by React Native. - self.initialProps = [:] - - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } - - override func sourceURL(for bridge: RCTBridge) -> URL? { - self.bundleURL() - } - - override func bundleURL() -> URL? { -#if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") -#else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") -#endif - } -} From 5c60668c7ea73e9e15a225dd9fc268578987bd6e Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Mon, 7 Jul 2025 17:06:58 +0300 Subject: [PATCH 08/12] Update react-native-navigation --- .../app/src/main/java/com/rnuilib/MainApplication.kt | 10 +--------- package.json | 2 +- yarn.lock | 10 +++++----- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/android/app/src/main/java/com/rnuilib/MainApplication.kt b/android/app/src/main/java/com/rnuilib/MainApplication.kt index 5ec088cead..775480e57d 100644 --- a/android/app/src/main/java/com/rnuilib/MainApplication.kt +++ b/android/app/src/main/java/com/rnuilib/MainApplication.kt @@ -36,13 +36,5 @@ class MainApplication : NavigationApplication() { override val reactHost: ReactHost get() = getDefaultReactHost(applicationContext, reactNativeHost) - - override fun onCreate() { - super.onCreate() - SoLoader.init(this, OpenSourceMergedSoMapping) - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - load() - } - } + } diff --git a/package.json b/package.json index 3741c43e30..0bf6e72ce4 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "react-native-haptic-feedback": "^1.11.0", "react-native-linear-gradient": "2.6.2", "react-native-mmkv": "3.2.0", - "react-native-navigation": "8.1.0-rc01-snapshot.1710", + "react-native-navigation": "8.1.0", "react-native-reanimated": "3.16.7", "react-native-shimmer-placeholder": "^2.0.6", "react-native-svg": "15.11.2", diff --git a/yarn.lock b/yarn.lock index ab01492044..022ae8bc93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10738,9 +10738,9 @@ __metadata: languageName: node linkType: hard -"react-native-navigation@npm:8.1.0-rc01-snapshot.1710": - version: 8.1.0-rc01-snapshot.1710 - resolution: "react-native-navigation@npm:8.1.0-rc01-snapshot.1710" +"react-native-navigation@npm:8.1.0": + version: 8.1.0 + resolution: "react-native-navigation@npm:8.1.0" dependencies: hoist-non-react-statics: 3.x.x lodash: 4.17.x @@ -10759,7 +10759,7 @@ __metadata: optional: true bin: rnn-link: autolink/postlink/run.js - checksum: 9535c77e5127e3ebe137fba783500ddb69359545d7f3dec71a86f1c6650f7205987e2ed25c1c5305046678659653d33b7d0ea2497c5a1693d540d5d0ba2c4e36 + checksum: 48bce9912b98b5139acda8be2e4a019aaa83035ba328fc487f6bf141fe25f42515ac1976c90fcefcf22ce4a9c948a6b1121bd2180fad09dd3983fe4c0bc23254 languageName: node linkType: hard @@ -10915,7 +10915,7 @@ __metadata: react-native-haptic-feedback: ^1.11.0 react-native-linear-gradient: 2.6.2 react-native-mmkv: 3.2.0 - react-native-navigation: 8.1.0-rc01-snapshot.1710 + react-native-navigation: 8.1.0 react-native-reanimated: 3.16.7 react-native-redash: ^12.0.3 react-native-shimmer-placeholder: ^2.0.6 From 36127bac960e2107750eae8257d931a0752ef053 Mon Sep 17 00:00:00 2001 From: M-i-k-e-l Date: Tue, 8 Jul 2025 19:28:29 +0300 Subject: [PATCH 09/12] Upgrade @react-native-community/datetimepicker (known issue in iOS) --- docs/getting-started/setup.md | 2 +- package.json | 2 +- webDemo/package.json | 2 +- yarn.lock | 20 +++++++++++++++----- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/getting-started/setup.md b/docs/getting-started/setup.md index b7f736ff61..d0655adb76 100644 --- a/docs/getting-started/setup.md +++ b/docs/getting-started/setup.md @@ -66,7 +66,7 @@ These packages are required for basic functionality: Install these based on the components you plan to use: ```bash "@react-native-community/blur": ">=4.4.1" -"@react-native-community/datetimepicker": "^3.4.6" +"@react-native-community/datetimepicker": "^8.2.0" "@react-native-community/netinfo": "^5.6.2" # Required for ConnectionStatusBar ``` diff --git a/package.json b/package.json index 0bf6e72ce4..e66f7fd1f8 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "@react-native-community/cli": "15.0.1", "@react-native-community/cli-platform-android": "15.0.1", "@react-native-community/cli-platform-ios": "15.0.1", - "@react-native-community/datetimepicker": "^3.4.6", + "@react-native-community/datetimepicker": "8.2.0", "@react-native-community/netinfo": "11.3.3", "@react-native/babel-preset": "0.77.2", "@react-native/eslint-config": "0.77.2", diff --git a/webDemo/package.json b/webDemo/package.json index b7391c324b..9a3f57b7cc 100644 --- a/webDemo/package.json +++ b/webDemo/package.json @@ -15,7 +15,7 @@ "start": "webpack-dev-server --config ./webpack.config.js" }, "dependencies": { - "@react-native-community/datetimepicker": "^3.4.6", + "@react-native-community/datetimepicker": "8.2.0", "@react-native-community/netinfo": "11.3.3", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/yarn.lock b/yarn.lock index 022ae8bc93..0ce80a7f97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3507,12 +3507,22 @@ __metadata: languageName: node linkType: hard -"@react-native-community/datetimepicker@npm:^3.4.6": - version: 3.5.2 - resolution: "@react-native-community/datetimepicker@npm:3.5.2" +"@react-native-community/datetimepicker@npm:8.2.0": + version: 8.2.0 + resolution: "@react-native-community/datetimepicker@npm:8.2.0" dependencies: invariant: ^2.2.4 - checksum: 8f0d3bc7014b17c696a415565c6fc591591b29ba5a438f2c1692a96ffc185693266eb950229ed3e9bf6b035d61a720ee402cbe9750d5bef81fd19e7647a24229 + peerDependencies: + expo: ">=50.0.0" + react: "*" + react-native: "*" + react-native-windows: "*" + peerDependenciesMeta: + expo: + optional: true + react-native-windows: + optional: true + checksum: 3adc9346f8948abed246da3bfa3a223e44c2c5903070178a82e583ce6f8273607fe5a542af834b3a27aff81980c555e5f72bf0e87c85e3f00856a0de21525374 languageName: node linkType: hard @@ -10861,7 +10871,7 @@ __metadata: "@react-native-community/cli": 15.0.1 "@react-native-community/cli-platform-android": 15.0.1 "@react-native-community/cli-platform-ios": 15.0.1 - "@react-native-community/datetimepicker": ^3.4.6 + "@react-native-community/datetimepicker": 8.2.0 "@react-native-community/netinfo": 11.3.3 "@react-native/babel-preset": 0.77.2 "@react-native/eslint-config": 0.77.2 From 083661ceeca5264e495598431cf6b617cfa4a7f6 Mon Sep 17 00:00:00 2001 From: Mark de Vocht Date: Wed, 9 Jul 2025 15:55:09 +0300 Subject: [PATCH 10/12] UI-LIB scrollview fix for new arch #3775 (https://github.com/wix/react-native-ui-lib/issues/3775) --- .../KeyboardTrackingViewTempManager.m | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m b/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m index 6d81773a4d..a714cdc9dc 100644 --- a/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m +++ b/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m @@ -11,11 +11,12 @@ #import "UIResponder+FirstResponderTemp.h" #import -#import + #import #import #import #import +#import #import @@ -70,7 +71,7 @@ -(instancetype)init if (self) { - [self addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; + [self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; _inputViewsMap = [NSMapTable weakToWeakObjectsMapTable]; _deferedInitializeAccessoryViewsCount = 0; @@ -93,20 +94,21 @@ -(instancetype)init return self; } --(RCTRootView*)getRootView +-(UIView*)getRootView { UIView *view = self; while (view.superview != nil) { view = view.superview; - if ([view isKindOfClass:[RCTRootView class]]) + if ([view isKindOfClass:[RCTSurfaceHostingView class]]) { break; + } } - if ([view isKindOfClass:[RCTRootView class]]) - { - return (RCTRootView*)view; + if ([view isKindOfClass:[RCTSurfaceHostingView class]]) { + return view; } + return nil; } @@ -169,10 +171,20 @@ -(void)layoutSubviews [self updateBottomViewFrame]; } +- (UIScrollView*)extractUIScrollView:(UIView*)view +{ + for (UIView* subview in view.subviews) { + if ([subview isKindOfClass:[UIScrollView class]]) { + return (UIScrollView*)subview; + } + } + + return nil; +} + - (void)initializeAccessoryViewsAndHandleInsets { NSArray* allSubviews = [self getBreadthFirstSubviewsForView:[self getRootView]]; - NSMutableArray* rctScrollViewsArray = [NSMutableArray array]; for (UIView* subview in allSubviews) { @@ -180,26 +192,29 @@ - (void)initializeAccessoryViewsAndHandleInsets { if(_scrollViewToManage == nil) { - if(_requiresSameParentToManageScrollView && [subview isKindOfClass:[RCTScrollView class]] && subview.superview == self.superview) - { - _scrollViewToManage = ((RCTScrollView*)subview).scrollView; - } - else if(!_requiresSameParentToManageScrollView && [subview isKindOfClass:[UIScrollView class]]) - { - _scrollViewToManage = (UIScrollView*)subview; + if ([NSStringFromClass([subview class]) isEqualToString:@"RCTScrollViewComponentView"]) { + UIScrollView *scrollView = [self extractUIScrollView:subview]; + + if ([scrollView isKindOfClass:[UIScrollView class]]) + { + if(_requiresSameParentToManageScrollView && subview.superview == self.superview) + { + _scrollViewToManage = scrollView; + } + else if(!_requiresSameParentToManageScrollView) + { + _scrollViewToManage = scrollView; + } + + if(_scrollViewToManage != nil) + { + _scrollIsInverted = CGAffineTransformEqualToTransform(_scrollViewToManage.superview.transform, CGAffineTransformMakeScale(1, -1)); + } + } } - - if(_scrollViewToManage != nil) - { - _scrollIsInverted = CGAffineTransformEqualToTransform(_scrollViewToManage.superview.transform, CGAffineTransformMakeScale(1, -1)); - } - } - - if([subview isKindOfClass:[RCTScrollView class]]) - { - [rctScrollViewsArray addObject:(RCTScrollView*)subview]; } } + if ([subview isKindOfClass:NSClassFromString(@"RCTTextField")]) { @@ -247,13 +262,11 @@ - (void)initializeAccessoryViewsAndHandleInsets } } - for (RCTScrollView *scrollView in rctScrollViewsArray) + if(_scrollViewToManage != nil) { - if(scrollView.scrollView == _scrollViewToManage) + if(_scrollViewToManage.delegate == nil) { - [scrollView removeScrollListener:self]; - [scrollView addScrollListener:self]; - break; + _scrollViewToManage.delegate = self; } } @@ -338,12 +351,12 @@ -(void)didMoveToWindow -(void)dealloc { - [self removeObserver:self forKeyPath:@"bounds"]; + [self removeObserver:self forKeyPath:@"frame"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - _ObservingInputAccessoryViewTemp.height = self.bounds.size.height; + _ObservingInputAccessoryViewTemp.height = self.frame.size.height; } - (void)ObservingInputAccessoryViewTempKeyboardWillDisappear:(ObservingInputAccessoryViewTemp *)ObservingInputAccessoryViewTemp From 662c14f033b02fb65c084c5de689b3fa332074ae Mon Sep 17 00:00:00 2001 From: Mark de Vocht Date: Wed, 9 Jul 2025 16:13:33 +0300 Subject: [PATCH 11/12] Revert "UI-LIB scrollview fix for new arch" This reverts commit 083661ceeca5264e495598431cf6b617cfa4a7f6. --- .../KeyboardTrackingViewTempManager.m | 77 ++++++++----------- 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m b/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m index a714cdc9dc..6d81773a4d 100644 --- a/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m +++ b/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m @@ -11,12 +11,11 @@ #import "UIResponder+FirstResponderTemp.h" #import - +#import #import #import #import #import -#import #import @@ -71,7 +70,7 @@ -(instancetype)init if (self) { - [self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; + [self addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; _inputViewsMap = [NSMapTable weakToWeakObjectsMapTable]; _deferedInitializeAccessoryViewsCount = 0; @@ -94,21 +93,20 @@ -(instancetype)init return self; } --(UIView*)getRootView +-(RCTRootView*)getRootView { UIView *view = self; while (view.superview != nil) { view = view.superview; - if ([view isKindOfClass:[RCTSurfaceHostingView class]]) { + if ([view isKindOfClass:[RCTRootView class]]) break; - } } - if ([view isKindOfClass:[RCTSurfaceHostingView class]]) { - return view; + if ([view isKindOfClass:[RCTRootView class]]) + { + return (RCTRootView*)view; } - return nil; } @@ -171,20 +169,10 @@ -(void)layoutSubviews [self updateBottomViewFrame]; } -- (UIScrollView*)extractUIScrollView:(UIView*)view -{ - for (UIView* subview in view.subviews) { - if ([subview isKindOfClass:[UIScrollView class]]) { - return (UIScrollView*)subview; - } - } - - return nil; -} - - (void)initializeAccessoryViewsAndHandleInsets { NSArray* allSubviews = [self getBreadthFirstSubviewsForView:[self getRootView]]; + NSMutableArray* rctScrollViewsArray = [NSMutableArray array]; for (UIView* subview in allSubviews) { @@ -192,29 +180,26 @@ - (void)initializeAccessoryViewsAndHandleInsets { if(_scrollViewToManage == nil) { - if ([NSStringFromClass([subview class]) isEqualToString:@"RCTScrollViewComponentView"]) { - UIScrollView *scrollView = [self extractUIScrollView:subview]; - - if ([scrollView isKindOfClass:[UIScrollView class]]) - { - if(_requiresSameParentToManageScrollView && subview.superview == self.superview) - { - _scrollViewToManage = scrollView; - } - else if(!_requiresSameParentToManageScrollView) - { - _scrollViewToManage = scrollView; - } - - if(_scrollViewToManage != nil) - { - _scrollIsInverted = CGAffineTransformEqualToTransform(_scrollViewToManage.superview.transform, CGAffineTransformMakeScale(1, -1)); - } - } + if(_requiresSameParentToManageScrollView && [subview isKindOfClass:[RCTScrollView class]] && subview.superview == self.superview) + { + _scrollViewToManage = ((RCTScrollView*)subview).scrollView; + } + else if(!_requiresSameParentToManageScrollView && [subview isKindOfClass:[UIScrollView class]]) + { + _scrollViewToManage = (UIScrollView*)subview; } + + if(_scrollViewToManage != nil) + { + _scrollIsInverted = CGAffineTransformEqualToTransform(_scrollViewToManage.superview.transform, CGAffineTransformMakeScale(1, -1)); + } + } + + if([subview isKindOfClass:[RCTScrollView class]]) + { + [rctScrollViewsArray addObject:(RCTScrollView*)subview]; } } - if ([subview isKindOfClass:NSClassFromString(@"RCTTextField")]) { @@ -262,11 +247,13 @@ - (void)initializeAccessoryViewsAndHandleInsets } } - if(_scrollViewToManage != nil) + for (RCTScrollView *scrollView in rctScrollViewsArray) { - if(_scrollViewToManage.delegate == nil) + if(scrollView.scrollView == _scrollViewToManage) { - _scrollViewToManage.delegate = self; + [scrollView removeScrollListener:self]; + [scrollView addScrollListener:self]; + break; } } @@ -351,12 +338,12 @@ -(void)didMoveToWindow -(void)dealloc { - [self removeObserver:self forKeyPath:@"frame"]; + [self removeObserver:self forKeyPath:@"bounds"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - _ObservingInputAccessoryViewTemp.height = self.frame.size.height; + _ObservingInputAccessoryViewTemp.height = self.bounds.size.height; } - (void)ObservingInputAccessoryViewTempKeyboardWillDisappear:(ObservingInputAccessoryViewTemp *)ObservingInputAccessoryViewTemp From b510acf00b3053a4080ade857596e77dd21ff766 Mon Sep 17 00:00:00 2001 From: "mark.dev" <50657916+markdevocht@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:16:17 +0300 Subject: [PATCH 12/12] UI-LIB scrollview fix for new arch #3775 (#3776) * UI-LIB scrollview fix for new arch #3775 * Update snapshot --------- Co-authored-by: M-i-k-e-l --- .../KeyboardTrackingViewTempManager.m | 77 +++++++++++-------- package.json | 2 +- yarn.lock | 10 +-- 3 files changed, 51 insertions(+), 38 deletions(-) diff --git a/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m b/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m index 6d81773a4d..f786e947c1 100644 --- a/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m +++ b/lib/ios/reactnativeuilib/keyboardtrackingview/KeyboardTrackingViewTempManager.m @@ -11,11 +11,12 @@ #import "UIResponder+FirstResponderTemp.h" #import -#import + #import #import #import #import +#import #import @@ -70,7 +71,7 @@ -(instancetype)init if (self) { - [self addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; + [self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; _inputViewsMap = [NSMapTable weakToWeakObjectsMapTable]; _deferedInitializeAccessoryViewsCount = 0; @@ -93,20 +94,21 @@ -(instancetype)init return self; } --(RCTRootView*)getRootView +-(UIView*)getRootView { UIView *view = self; while (view.superview != nil) { view = view.superview; - if ([view isKindOfClass:[RCTRootView class]]) + if ([view isKindOfClass:[RCTSurfaceHostingView class]]) { break; + } } - if ([view isKindOfClass:[RCTRootView class]]) - { - return (RCTRootView*)view; + if ([view isKindOfClass:[RCTSurfaceHostingView class]]) { + return view; } + return nil; } @@ -169,10 +171,20 @@ -(void)layoutSubviews [self updateBottomViewFrame]; } +- (UIScrollView*)extractUIScrollView:(UIView*)view +{ + for (UIView* subview in view.subviews) { + if ([subview isKindOfClass:[UIScrollView class]]) { + return (UIScrollView*)subview; + } + } + + return nil; +} + - (void)initializeAccessoryViewsAndHandleInsets { NSArray* allSubviews = [self getBreadthFirstSubviewsForView:[self getRootView]]; - NSMutableArray* rctScrollViewsArray = [NSMutableArray array]; for (UIView* subview in allSubviews) { @@ -180,26 +192,29 @@ - (void)initializeAccessoryViewsAndHandleInsets { if(_scrollViewToManage == nil) { - if(_requiresSameParentToManageScrollView && [subview isKindOfClass:[RCTScrollView class]] && subview.superview == self.superview) - { - _scrollViewToManage = ((RCTScrollView*)subview).scrollView; - } - else if(!_requiresSameParentToManageScrollView && [subview isKindOfClass:[UIScrollView class]]) - { - _scrollViewToManage = (UIScrollView*)subview; + if ([NSStringFromClass([subview class]) isEqualToString:@"RCTScrollViewComponentView"]) { + UIScrollView *scrollView = [self extractUIScrollView:subview]; + + if ([scrollView isKindOfClass:[UIScrollView class]]) + { + if(_requiresSameParentToManageScrollView && subview.superview == self.superview) + { + _scrollViewToManage = scrollView; + } + else if(!_requiresSameParentToManageScrollView) + { + _scrollViewToManage = scrollView; + } + + if(_scrollViewToManage != nil) + { + _scrollIsInverted = CGAffineTransformEqualToTransform(subview.superview.transform, CGAffineTransformMakeScale(1, -1)); + } + } } - - if(_scrollViewToManage != nil) - { - _scrollIsInverted = CGAffineTransformEqualToTransform(_scrollViewToManage.superview.transform, CGAffineTransformMakeScale(1, -1)); - } - } - - if([subview isKindOfClass:[RCTScrollView class]]) - { - [rctScrollViewsArray addObject:(RCTScrollView*)subview]; } } + if ([subview isKindOfClass:NSClassFromString(@"RCTTextField")]) { @@ -247,13 +262,11 @@ - (void)initializeAccessoryViewsAndHandleInsets } } - for (RCTScrollView *scrollView in rctScrollViewsArray) + if(_scrollViewToManage != nil) { - if(scrollView.scrollView == _scrollViewToManage) + if(_scrollViewToManage.delegate == nil) { - [scrollView removeScrollListener:self]; - [scrollView addScrollListener:self]; - break; + _scrollViewToManage.delegate = self; } } @@ -338,12 +351,12 @@ -(void)didMoveToWindow -(void)dealloc { - [self removeObserver:self forKeyPath:@"bounds"]; + [self removeObserver:self forKeyPath:@"frame"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - _ObservingInputAccessoryViewTemp.height = self.bounds.size.height; + _ObservingInputAccessoryViewTemp.height = self.frame.size.height; } - (void)ObservingInputAccessoryViewTempKeyboardWillDisappear:(ObservingInputAccessoryViewTemp *)ObservingInputAccessoryViewTemp diff --git a/package.json b/package.json index e66f7fd1f8..ef0bd562f0 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "react-native-redash": "^12.0.3", "semver": "^5.5.0", "tinycolor2": "^1.4.2", - "uilib-native": "5.0.0-snapshot.7216", + "uilib-native": "5.0.0-snapshot.7240", "url-parse": "^1.2.0", "wix-react-native-text-size": "1.0.9" }, diff --git a/yarn.lock b/yarn.lock index 0ce80a7f97..354848bca3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10938,7 +10938,7 @@ __metadata: shell-utils: ^1.0.10 tinycolor2: ^1.4.2 typescript: 5.0.4 - uilib-native: 5.0.0-snapshot.7216 + uilib-native: 5.0.0-snapshot.7240 url-parse: ^1.2.0 wix-react-native-text-size: 1.0.9 peerDependencies: @@ -12570,16 +12570,16 @@ __metadata: languageName: node linkType: hard -"uilib-native@npm:5.0.0-snapshot.7216": - version: 5.0.0-snapshot.7216 - resolution: "uilib-native@npm:5.0.0-snapshot.7216" +"uilib-native@npm:5.0.0-snapshot.7240": + version: 5.0.0-snapshot.7240 + resolution: "uilib-native@npm:5.0.0-snapshot.7240" dependencies: lodash: ^4.17.21 prop-types: ^15.5.10 peerDependencies: react: ">=17.0.1" react-native: ">=0.64.1" - checksum: 3ca207bae3865fc4275393135f5cd4a46c52678100d4308fe1008d0167d8d5ae8bd1cab558b364d7ad60e5c96cb38497cd10b0109e15376c623ef9849cfb3a32 + checksum: e18a273800b231479aea3fac4a43c7f2c4a6f74c995f44e7b9ab5642b666d94060ff09a2c0f76cf6e871cbd8fc57baaee92a4879c28e20be3358fbb4fd02dc0f languageName: node linkType: hard