Skip to content

Mobile: Upgrade to React Native 0.79 #12337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions .yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch

This file was deleted.

205 changes: 205 additions & 0 deletions .yarn/patches/react-native-npm-0.79.2-9db13eddfe.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# This patch fixes two issues:
# - Updates RCTDeviceInfo.m to match https://github.com/facebook/react-native/commit/0b8db7e5e814cfbf9974cc5b6ceb64e8006d8a3c.
# This fixes an issue in which useWindowDimensions returns incorrect
# values in landscape mode in iOS.
# This should be fixed in React Native 0.80. See https://github.com/facebook/react-native/issues/51086.
# - Updates NativeAnimatedModule.java to work around an Android 12-specific crash.
diff --git a/React/CoreModules/RCTDeviceInfo.mm b/React/CoreModules/RCTDeviceInfo.mm
index 6b4fcef852252e8d4ac2aceb12175fdfafb4def7..8ceab21e8653d429876d10e2d12ed1342780ad7d 100644
--- a/React/CoreModules/RCTDeviceInfo.mm
+++ b/React/CoreModules/RCTDeviceInfo.mm
@@ -14,9 +14,7 @@
#import <React/RCTEventDispatcherProtocol.h>
#import <React/RCTInitializing.h>
#import <React/RCTInvalidating.h>
-#import <React/RCTKeyWindowValuesProxy.h>
#import <React/RCTUtils.h>
-#import <React/RCTWindowSafeAreaProxy.h>
#import <atomic>

#import "CoreModulesPlugins.h"
@@ -31,8 +29,13 @@ using namespace facebook::react;
NSDictionary *_currentInterfaceDimensions;
BOOL _isFullscreen;
std::atomic<BOOL> _invalidated;
+ NSDictionary *_constants;
+
+ __weak UIWindow *_applicationWindow;
}

+static NSString *const kFrameKeyPath = @"frame";
+
@synthesize moduleRegistry = _moduleRegistry;

RCT_EXPORT_MODULE()
@@ -40,14 +43,26 @@ RCT_EXPORT_MODULE()
- (instancetype)init
{
if (self = [super init]) {
- [[RCTKeyWindowValuesProxy sharedInstance] startObservingWindowSizeIfNecessary];
+ _applicationWindow = RCTKeyWindow();
+ [_applicationWindow addObserver:self forKeyPath:kFrameKeyPath options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}

+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+{
+ if ([keyPath isEqualToString:kFrameKeyPath]) {
+ [self interfaceFrameDidChange];
+ [[NSNotificationCenter defaultCenter] postNotificationName:RCTWindowFrameDidChangeNotification object:self];
+ }
+}
+
+ (BOOL)requiresMainQueueSetup
{
- return NO;
+ return YES;
}

- (dispatch_queue_t)methodQueue
@@ -81,7 +96,7 @@ RCT_EXPORT_MODULE()

#if TARGET_OS_IOS

- _currentInterfaceOrientation = [RCTKeyWindowValuesProxy sharedInstance].currentInterfaceOrientation;
+ _currentInterfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation;

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(interfaceFrameDidChange)
@@ -98,6 +113,15 @@ RCT_EXPORT_MODULE()
selector:@selector(invalidate)
name:RCTBridgeWillInvalidateModulesNotification
object:nil];
+
+ _constants = @{
+ @"Dimensions" : [self _exportedDimensions],
+ // Note:
+ // This prop is deprecated and will be removed in a future release.
+ // Please use this only for a quick and temporary solution.
+ // Use <SafeAreaView> instead.
+ @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()),
+ };
}

- (void)invalidate
@@ -120,6 +144,8 @@ RCT_EXPORT_MODULE()

[[NSNotificationCenter defaultCenter] removeObserver:self name:RCTBridgeWillInvalidateModulesNotification object:nil];

+ [_applicationWindow removeObserver:self forKeyPath:kFrameKeyPath];
+
#if TARGET_OS_IOS
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
#endif
@@ -132,8 +158,13 @@ static BOOL RCTIsIPhoneNotched()

#if TARGET_OS_IOS
dispatch_once(&onceToken, ^{
+ RCTAssertMainQueue();
+
// 20pt is the top safeArea value in non-notched devices
- isIPhoneNotched = [RCTWindowSafeAreaProxy sharedInstance].currentSafeAreaInsets.top > 20;
+ UIWindow *keyWindow = RCTKeyWindow();
+ if (keyWindow) {
+ isIPhoneNotched = keyWindow.safeAreaInsets.top > 20;
+ }
});
#endif

@@ -142,11 +173,13 @@ static BOOL RCTIsIPhoneNotched()

static NSDictionary *RCTExportedDimensions(CGFloat fontScale)
{
+ RCTAssertMainQueue();
UIScreen *mainScreen = UIScreen.mainScreen;
CGSize screenSize = mainScreen.bounds.size;
+ UIView *mainWindow = RCTKeyWindow();

// We fallback to screen size if a key window is not found.
- CGSize windowSize = [RCTKeyWindowValuesProxy sharedInstance].windowSize;
+ CGSize windowSize = mainWindow ? mainWindow.bounds.size : screenSize;

NSDictionary<NSString *, NSNumber *> *dimsWindow = @{
@"width" : @(windowSize.width),
@@ -170,7 +203,10 @@ static NSDictionary *RCTExportedDimensions(CGFloat fontScale)
RCTAssert(_moduleRegistry, @"Failed to get exported dimensions: RCTModuleRegistry is nil");
RCTAccessibilityManager *accessibilityManager =
(RCTAccessibilityManager *)[_moduleRegistry moduleForName:"AccessibilityManager"];
- RCTAssert(accessibilityManager, @"Failed to get exported dimensions: AccessibilityManager is nil");
+ // TOOD(T225745315): For some reason, accessibilityManager is nil in some cases.
+ // We default the fontScale to 1.0 in this case. This should be okay: if we assume
+ // that accessibilityManager will eventually become available, js will eventually
+ // be updated with the correct fontScale.
CGFloat fontScale = accessibilityManager ? accessibilityManager.multiplier : 1.0;
return RCTExportedDimensions(fontScale);
}
@@ -182,14 +218,7 @@ static NSDictionary *RCTExportedDimensions(CGFloat fontScale)

- (NSDictionary<NSString *, id> *)getConstants
{
- return @{
- @"Dimensions" : [self _exportedDimensions],
- // Note:
- // This prop is deprecated and will be removed in a future release.
- // Please use this only for a quick and temporary solution.
- // Use <SafeAreaView> instead.
- @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()),
- };
+ return _constants;
}

- (void)didReceiveNewContentSizeMultiplier
@@ -209,10 +238,11 @@ static NSDictionary *RCTExportedDimensions(CGFloat fontScale)
- (void)interfaceOrientationDidChange
{
#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
- UIWindow *keyWindow = RCTKeyWindow();
- UIInterfaceOrientation nextOrientation = keyWindow.windowScene.interfaceOrientation;
+ UIApplication *application = RCTSharedApplication();
+ UIInterfaceOrientation nextOrientation = RCTKeyWindow().windowScene.interfaceOrientation;

- BOOL isRunningInFullScreen = CGRectEqualToRect(keyWindow.frame, keyWindow.screen.bounds);
+ BOOL isRunningInFullScreen =
+ CGRectEqualToRect(application.delegate.window.frame, application.delegate.window.screen.bounds);
// We are catching here two situations for multitasking view:
// a) The app is in Split View and the container gets resized -> !isRunningInFullScreen
// b) The app changes to/from fullscreen example: App runs in slide over mode and goes into fullscreen->
@@ -276,3 +306,4 @@ Class RCTDeviceInfoCls(void)
{
return RCTDeviceInfo.class;
}
+
diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
index cf14e51cf5f561b84f1b6ace8410fc77d626758e..abc8c64adf26fbf73429aee7fd4f76877e98849a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
@@ -42,6 +42,7 @@ import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;

/**
@@ -155,8 +156,15 @@ public class NativeAnimatedModule extends NativeAnimatedModuleSpec
}

private class ConcurrentOperationQueue {
- private final Queue<UIThreadOperation> mQueue = new ConcurrentLinkedQueue<>();
- @Nullable private UIThreadOperation mPeekedOperation = null;
+ // Patch: Use LinkedBlockingQueue instead of ConcurrentLinkedQueue.
+ // In some versions of Android, ConcurrentLinkedQueue is known to drop
+ // items, causing crashing. See https://github.com/laurent22/joplin/issues/8425
+ private final Queue<UIThreadOperation> mQueue = (
+ // The issue exists for Android 12, which corresponds to API levels 31 and 32.
+ Build.VERSION.SDK_INT == 31 || Build.VERSION.SDK_INT == 32
+ ) ? new LinkedBlockingQueue<>() : new ConcurrentLinkedQueue<>();
+
+ @Nullable private UIThreadOperation mPeekedOperation = null;

@AnyThread
boolean isEmpty() {
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@
"nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch",
"pdfjs-dist": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
"chokidar@^2.0.0": "3.5.3",
"[email protected]": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch",
"[email protected]": "patch:rn-fetch-blob@npm%3A0.12.0#./.yarn/patches/rn-fetch-blob-npm-0.12.0-cf02e3c544.patch",
"[email protected]": "patch:app-builder-lib@npm%3A26.0.0-alpha.7#./.yarn/patches/app-builder-lib-npm-26.0.0-alpha.7-e1b3dca119.patch",
"[email protected]": "patch:app-builder-lib@npm%3A24.13.3#./.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch",
"[email protected]": "patch:react-native-sqlite-storage@npm%3A6.0.1#./.yarn/patches/react-native-sqlite-storage-npm-6.0.1-8369d747bd.patch",
"[email protected]": "patch:react-native-paper@npm%3A5.13.1#./.yarn/patches/react-native-paper-npm-5.13.1-f153e542e2.patch",
"[email protected]": "patch:react-native-popup-menu@npm%3A0.17.0#./.yarn/patches/react-native-popup-menu-npm-0.17.0-8b745d88dd.patch"
"[email protected]": "patch:react-native-popup-menu@npm%3A0.17.0#./.yarn/patches/react-native-popup-menu-npm-0.17.0-8b745d88dd.patch",
"[email protected]": "patch:react-native@npm%3A0.79.2#./.yarn/patches/react-native-npm-0.79.2-9db13eddfe.patch"
}
}
2 changes: 1 addition & 1 deletion packages/app-mobile/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ buildscript {
minSdkVersion = 24

compileSdkVersion = 35
targetSdkVersion = 34
targetSdkVersion = 35

ndkVersion = "27.1.12297006"
kotlinVersion = "2.0.21"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
44 changes: 38 additions & 6 deletions packages/app-mobile/android/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,42 @@
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
plugins { id("com.facebook.react.settings") }
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
pluginManagement {
def reactNativeGradlePlugin = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
}.standardOutput.asText.get().trim()
).getParentFile().absolutePath
includeBuild(reactNativeGradlePlugin)

def expoPluginsPath = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })")
}.standardOutput.asText.get().trim(),
"../android/expo-gradle-plugin"
).absolutePath
includeBuild(expoPluginsPath)
}

plugins {
id("com.facebook.react.settings")
id("expo-autolinking-settings")
}

extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
ex.autolinkLibrariesFromCommand()
} else {
ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
}
}
expoAutolinking.useExpoModules()

rootProject.name = 'Joplin'

expoAutolinking.useExpoVersionCatalog()

include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')

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()
includeBuild(expoAutolinking.reactNativeGradlePlugin)
10 changes: 6 additions & 4 deletions packages/app-mobile/components/CameraView/Camera/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ const Camera = (props: Props, ref: ForwardedRef<CameraRef>) => {

useImperativeHandle(ref, () => ({
takePictureAsync: async () => {
logger.debug('Taking picture...');
const result = await cameraRef.current.takePictureAsync();
logger.debug('Took picture.');
return {
uri: result.uri,
type: 'image/jpg',
Expand Down Expand Up @@ -58,16 +60,16 @@ const Camera = (props: Props, ref: ForwardedRef<CameraRef>) => {
// iOS issue workaround: Since upgrading to Expo SDK 52, closing and reopening the camera on iOS
// never emits onCameraReady. As a workaround, call .resumePreview and wait for it to resolve,
// rather than relying on the CameraView's onCameraReady prop.
if (Platform.OS === 'ios') {
if (Platform.OS === 'ios' && camera) {
// Work around an issue on iOS where the onCameraReady callback is never called.
// Instead, wait for the preview to start using resumePreview:
await camera.resumePreview();
if (event.cancelled) return;
props.onCameraReady();
}
}, [camera, props.onCameraReady]);
}, [camera, hasPermission, props.onCameraReady]);

return <CameraView
return hasPermission ? <CameraView
ref={setCamera}
style={props.style}
facing={props.cameraType === CameraDirection.Front ? 'front' : 'back'}
Expand All @@ -77,7 +79,7 @@ const Camera = (props: Props, ref: ForwardedRef<CameraRef>) => {
animateShutter={false}
barcodeScannerSettings={barcodeScannerSettings}
onBarcodeScanned={props.codeScanner.onBarcodeScanned}
/>;
/> : null;
};

export default forwardRef(Camera);
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as React from 'react';
import CameraView from './CameraView';
import { CameraResult } from './types';
import { fireEvent, render, screen } from '@testing-library/react-native';
import '@testing-library/jest-native/extend-expect';
import createMockReduxStore from '../../utils/testing/createMockReduxStore';
import TestProviderStack from '../testing/TestProviderStack';

Expand Down
1 change: 0 additions & 1 deletion packages/app-mobile/components/Dropdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Text } from 'react-native';

import { describe, it, expect, jest } from '@jest/globals';
import { fireEvent, render, screen, waitFor } from '@testing-library/react-native';
import '@testing-library/jest-native';

import Dropdown, { DropdownListItem } from './Dropdown';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react-native';
import '@testing-library/jest-native/extend-expect';

import { Store } from 'redux';
import { AppState } from '../../utils/types';
Expand Down Expand Up @@ -46,11 +45,19 @@ const toggleSettingsItem = async (props: ToggleSettingItemProps) => {

const itemCheckbox = await screen.findByRole('checkbox', { name: props.name });
expect(itemCheckbox).toBeVisible();
expect(itemCheckbox).toHaveAccessibilityState({ checked: initialChecked });
if (initialChecked) {
expect(itemCheckbox).toBeChecked();
} else {
expect(itemCheckbox).not.toBeChecked();
}
Comment on lines -49 to +52
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new @testing-library/react-native version removed *AccessibilityState queries.

fireEvent.press(itemCheckbox);

await waitFor(() => {
expect(itemCheckbox).toHaveAccessibilityState({ checked: finalChecked });
if (finalChecked) {
expect(itemCheckbox).toBeChecked();
} else {
expect(itemCheckbox).not.toBeChecked();
}
});
};

Expand Down
Loading