diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 000000000..d137d242e --- /dev/null +++ b/.bundle/config @@ -0,0 +1,2 @@ +BUNDLE_PATH: "vendor/bundle" +BUNDLE_FORCE_RUBY_PLATFORM: 1 \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index 1be1e3871..0de081c36 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,18 +80,15 @@ commands: steps: - restore_cache: name: Restore pods - # TODO: CircleCI isn't caching Podfile.lock correctly, because checksum changes after install - # key: pods-v1-{{ checksum "ios/Podfile.lock" }} - key: pods-v1-{{ checksum "ios/Podfile" }} + key: pods-v1-{{ checksum "ios/Podfile.lock" }} - run: name: Install pods libs command: | - bundle exec pod install + bundle exec pod install --deployment working_directory: ios - save_cache: name: Save pods specs and pods cache - # key: pods-v1-{{ checksum "ios/Podfile.lock" }} - key: pods-v1-{{ checksum "ios/Podfile" }} + key: pods-v1-{{ checksum "ios/Podfile.lock" }} paths: - ~/.pods - ios/Pods @@ -114,7 +111,8 @@ commands: # echo -e "android.enableAapt2=false" >> ./gradle.properties echo -e "android.useAndroidX=true" >> ./gradle.properties echo -e "android.enableJetifier=true" >> ./gradle.properties - echo -e "FLIPPER_VERSION=0.51.0" >> ./gradle.properties + echo -e "newArchEnabled=false" >> ./gradle.properties + echo -e "FLIPPER_VERSION=0.125.0" >> ./gradle.properties echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties if [[ $CIRCLE_JOB == "android-build-official" ]]; then diff --git a/.gitignore b/.gitignore index efb752943..19f2bcab6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ DerivedData project.xcworkspace *.mobileprovision ios/Pods/ +/vendor/bundle/ # Android/IntelliJ # diff --git a/.prettierrc.js b/.prettierrc.js index 7321592a1..04f6b3a09 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,10 +1,10 @@ module.exports = { bracketSpacing: true, - jsxBracketSameLine: true, singleQuote: true, jsxSingleQuote: true, trailingComma: 'none', printWidth: 130, useTabs: true, - arrowParens: 'avoid' + arrowParens: 'avoid', + bracketSameLine: true }; diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..a4dd9dba4 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.7.4 diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..bbba14aff --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' +# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version +ruby '2.7.4' +gem 'cocoapods', '~> 1.11', '>= 1.11.2' \ No newline at end of file diff --git a/README.md b/README.md index 50e97ea19..0a5f6d766 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![CodeFactor](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative/badge)](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative) - **Supported server versions:** 0.70.0+ -- **Supported iOS versions**: 11+ +- **Supported iOS versions**: 12+ - **Supported Android versions**: 6.0+ ## Download diff --git a/__mocks__/expo-av.js b/__mocks__/expo-av.js deleted file mode 100644 index e20feac67..000000000 --- a/__mocks__/expo-av.js +++ /dev/null @@ -1,14 +0,0 @@ -export class Sound { - loadAsync = () => {}; - - playAsync = () => {}; - - pauseAsync = () => {}; - - stopAsync = () => {}; - - setOnPlaybackStatusUpdate = () => {}; - - setPositionAsync = () => {}; -} -export const Audio = { Sound }; diff --git a/__mocks__/expo-keep-awake.js b/__mocks__/expo-keep-awake.js deleted file mode 100644 index 4df99a606..000000000 --- a/__mocks__/expo-keep-awake.js +++ /dev/null @@ -1,4 +0,0 @@ -const activateKeepAwake = () => ''; -const deactivateKeepAwake = () => ''; - -export { activateKeepAwake, deactivateKeepAwake }; diff --git a/__mocks__/expo-web-browser.js b/__mocks__/expo-web-browser.js deleted file mode 100644 index 3e5611b84..000000000 --- a/__mocks__/expo-web-browser.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - openBrowserAsync: () => '' -}; diff --git a/__mocks__/react-native-mmkv-storage.js b/__mocks__/react-native-mmkv-storage.js new file mode 100644 index 000000000..e34fed189 --- /dev/null +++ b/__mocks__/react-native-mmkv-storage.js @@ -0,0 +1,17 @@ +export class MMKVLoader { + constructor() { + console.log('MMKVLoader constructor mock'); + } + + setProcessingMode = jest.fn().mockImplementation(() => ({ + withEncryption: jest.fn().mockImplementation(() => ({ + initialize: jest.fn().mockImplementation(() => {}) + })) + })); +} + +export const ProcessingModes = { + MULTI_PROCESS: '' +}; + +export const create = jest.fn(); diff --git a/__mocks__/rn-user-defaults.js b/__mocks__/rn-user-defaults.js deleted file mode 100644 index 71f26ca30..000000000 --- a/__mocks__/rn-user-defaults.js +++ /dev/null @@ -1,4 +0,0 @@ -export default { - set: () => '', - get: () => '' -}; diff --git a/android/app/build.gradle b/android/app/build.gradle index caff3ff8b..68e818f42 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -93,7 +93,6 @@ project.ext.react = [ ] apply from: "../../node_modules/react-native/react.gradle" -apply from: '../../node_modules/react-native-unimodules/gradle.groovy' /** * Set this to true to create two separate APKs instead of one: @@ -131,13 +130,17 @@ def jscFlavor = 'org.webkit:android-jsc:+' */ def enableHermes = project.ext.react.get("enableHermes", false); -android { - compileSdkVersion rootProject.ext.compileSdkVersion +/** + * Architectures to build native code for. + */ +def reactNativeArchitectures() { + def value = project.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } +android { + ndkVersion rootProject.ext.ndkVersion + compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { applicationId APPLICATION_ID @@ -153,6 +156,76 @@ android { resValue "string", "rn_config_reader_custom_package", "chat.rocket.reactnative" testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + if (isNewArchitectureEnabled()) { + // We configure the NDK build only if you decide to opt-in for the New Architecture. + externalNativeBuild { + ndkBuild { + arguments "APP_PLATFORM=android-21", + "APP_STL=c++_shared", + "NDK_TOOLCHAIN_VERSION=clang", + "GENERATED_SRC_DIR=$buildDir/generated/source", + "PROJECT_BUILD_DIR=$buildDir", + "REACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid", + "REACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build" + cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1" + cppFlags "-std=c++17" + // Make sure this target name is the same you specify inside the + // src/main/jni/Android.mk file for the `LOCAL_MODULE` variable. + targets "rndiffapp_appmodules" + // Fix for windows limit on number of character in file paths and in command lines + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + arguments "NDK_APP_SHORT_COMMANDS=true" + } + } + } + if (!enableSeparateBuildPerCPUArchitecture) { + ndk { + abiFilters (*reactNativeArchitectures()) + } + } + } + } + + if (isNewArchitectureEnabled()) { + // We configure the NDK build only if you decide to opt-in for the New Architecture. + externalNativeBuild { + ndkBuild { + path "$projectDir/src/main/jni/Android.mk" + } + } + def reactAndroidProjectDir = project(':ReactAndroid').projectDir + def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") + from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") + into("$buildDir/react-ndk/exported") + } + def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") + from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") + into("$buildDir/react-ndk/exported") + } + afterEvaluate { + // If you wish to add a custom TurboModule or component locally, + // you should uncomment this line. + // preBuild.dependsOn("generateCodegenArtifactsFromSchema") + preDebugBuild.dependsOn(packageReactNdkDebugLibs) + preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) + // Due to a bug inside AGP, we have to explicitly set a dependency + // between configureNdkBuild* tasks and the preBuild tasks. + // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732 + configureNdkBuildRelease.dependsOn(preReleaseBuild) + configureNdkBuildDebug.dependsOn(preDebugBuild) + reactNativeArchitectures().each { architecture -> + tasks.findByName("configureNdkBuildDebug[${architecture}]")?.configure { + dependsOn("preDebugBuild") + } + tasks.findByName("configureNdkBuildRelease[${architecture}]")?.configure { + dependsOn("preReleaseBuild") + } + } + } } signingConfigs { @@ -170,7 +243,7 @@ android { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK - include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" + include (*reactNativeArchitectures()) } } buildTypes { @@ -181,6 +254,8 @@ android { if (!isFoss) { firebaseCrashlytics { nativeSymbolUploadEnabled true + strippedNativeLibsDir 'build/intermediates/stripped_native_libs/release/out/lib' + unstrippedNativeLibsDir 'build/intermediates/merged_native_libs/release/out/lib' } } } @@ -275,7 +350,6 @@ android { } dependencies { - addUnimodulesDependencies() implementation project(':@react-native-community_viewpager') playImplementation project(':react-native-notifications') playImplementation 'com.google.firebase:firebase-core:16.0.0' @@ -286,14 +360,16 @@ dependencies { exclude group: 'com.facebook.react',module:'react-native-svg' } implementation fileTree(dir: "libs", include: ["*.jar"]) + //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { - exclude group:'com.facebook.fbjni' + exclude group:'com.facebook.fbjni' } debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' + exclude group:'com.squareup.okhttp3', module:'okhttp' } debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' @@ -307,20 +383,30 @@ dependencies { implementation jscFlavor } - implementation "com.google.code.gson:gson:2.8.5" + implementation "com.google.code.gson:gson:2.8.9" implementation "com.github.bumptech.glide:glide:4.9.0" annotationProcessor "com.github.bumptech.glide:compiler:4.9.0" - implementation "com.tencent:mmkv-static:1.2.1" - implementation 'com.squareup.okhttp3:okhttp:4.9.0' - implementation "com.squareup.okhttp3:okhttp-urlconnection:4.9.0" + implementation "com.tencent:mmkv-static:1.2.10" androidTestImplementation('com.wix:detox:+') { transitive = true } androidTestImplementation 'junit:junit:4.12' } +if (isNewArchitectureEnabled()) { + // If new architecture is enabled, we let you build RN from source + // Otherwise we fallback to a prebuilt .aar bundled in the NPM package. + // This will be applied to all the imported transtitive dependency. + configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("com.facebook.react:react-native")) + .using(project(":ReactAndroid")).because("On New Architecture we're building React Native from source") + } + } +} + // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { - from configurations.compile + from configurations.implementation into 'libs' } @@ -328,3 +414,11 @@ apply from: file("../../node_modules/@react-native-community/cli-platform-androi if (!isFoss) { apply plugin: 'com.google.gms.google-services' } + +def isNewArchitectureEnabled() { + // To opt-in for the New Architecture, you can either: + // - Set `newArchEnabled` to true inside the `gradle.properties` file + // - Invoke gradle with `-newArchEnabled=true` + // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index efbd3e20b..febfaf65f 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -8,7 +8,7 @@ tools:replace="android:name" tools:targetApi="28" android:networkSecurityConfig="@xml/network_security_config"> - + diff --git a/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java b/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java index 6ed1a4798..6d30e2d25 100644 --- a/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java +++ b/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java @@ -18,6 +18,7 @@ import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; import com.facebook.flipper.plugins.react.ReactFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; +import com.facebook.react.ReactInstanceEventListener; import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.ReactContext; import com.facebook.react.modules.network.NetworkingModule; @@ -47,7 +48,7 @@ public class ReactNativeFlipper { ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); if (reactContext == null) { reactInstanceManager.addReactInstanceEventListener( - new ReactInstanceManager.ReactInstanceEventListener() { + new ReactInstanceEventListener() { @Override public void onReactContextInitialized(ReactContext reactContext) { reactInstanceManager.removeReactInstanceEventListener(this); diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b5b6ec2c1..3ced55f71 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -17,7 +17,8 @@ tools:replace="android:allowBackup"> + android:theme="@style/BootTheme" + android:exported="true"> @@ -25,7 +26,7 @@ + android:theme="@style/AppTheme" + android:exported="true"> diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java index e67d24c91..fa292ee99 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java @@ -1,33 +1,18 @@ package chat.rocket.reactnative; import android.os.Bundle; -import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.content.SharedPreferences; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableMap; import com.facebook.react.ReactRootView; import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactFragmentActivity; +import com.facebook.react.ReactActivity; + +import expo.modules.ReactActivityDelegateWrapper; import com.zoontek.rnbootsplash.RNBootSplash; -import com.google.gson.Gson; -class ThemePreferences { - String currentTheme; - String darkLevel; -} - -class SortPreferences { - String sortBy; - Boolean groupByType; - Boolean showFavorites; - Boolean showUnread; -} - -public class MainActivity extends ReactFragmentActivity { +public class MainActivity extends ReactActivity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -58,5 +43,27 @@ public class MainActivity extends ReactFragmentActivity { intent.putExtra("newConfig", newConfig); this.sendBroadcast(intent); } -} + /** + * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and + * you can specify the rendered you wish to use (Fabric or the older renderer). + */ + @Override + protected ReactActivityDelegate createReactActivityDelegate() { + return new ReactActivityDelegateWrapper(this, new MainActivityDelegate(this, getMainComponentName())); + } + + public static class MainActivityDelegate extends ReactActivityDelegate { + public MainActivityDelegate(ReactActivity activity, String mainComponentName) { + super(activity, mainComponentName); + } + + @Override + protected ReactRootView createRootView() { + ReactRootView reactRootView = new ReactRootView(getContext()); + // If you opted-in for the New Architecture, we enable the Fabric Renderer. + reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); + return reactRootView; + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java index c0361e78d..871e6e86d 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java @@ -8,24 +8,24 @@ import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.soloader.SoLoader; import com.reactnativecommunity.viewpager.RNCViewPagerPackage; import com.facebook.react.bridge.JSIModulePackage; import com.swmansion.reanimated.ReanimatedJSIModulePackage; -import org.unimodules.adapters.react.ModuleRegistryAdapter; -import org.unimodules.adapters.react.ReactModuleRegistryProvider; +import android.content.res.Configuration; +import expo.modules.ApplicationLifecycleDispatcher; +import expo.modules.ReactNativeHostWrapper; import java.util.Arrays; import java.util.List; -import chat.rocket.reactnative.generated.BasePackageList; +import chat.rocket.reactnative.newarchitecture.MainApplicationReactNativeHost; import chat.rocket.reactnative.networking.SSLPinningPackage; public class MainApplication extends Application implements ReactApplication { - private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(new BasePackageList().getPackageList(), null); - - private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(this, new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; @@ -37,10 +37,6 @@ public class MainApplication extends Application implements ReactApplication { List packages = new PackageList(this).getPackages(); packages.add(new RNCViewPagerPackage()); packages.add(new SSLPinningPackage()); - List unimodules = Arrays.asList( - new ModuleRegistryAdapter(mModuleRegistryProvider) - ); - packages.addAll(unimodules); List additionalModules = new AdditionalModules().getAdditionalModules(MainApplication.this); packages.addAll(additionalModules); return packages; @@ -60,16 +56,32 @@ public class MainApplication extends Application implements ReactApplication { protected @Nullable String getBundleAssetName() { return "app.bundle"; } - }; + }); + + private final ReactNativeHost mNewArchitectureNativeHost = + new ReactNativeHostWrapper(this, new MainApplicationReactNativeHost(this)); @Override public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + return mNewArchitectureNativeHost; + } else { + return mReactNativeHost; + } } @Override public void onCreate() { super.onCreate(); + // If you opted-in for the New Architecture, we enable the TurboModule system + ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; SoLoader.init(this, /* native exopackage */ false); + ApplicationLifecycleDispatcher.onApplicationCreate(this); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig); } } diff --git a/android/app/src/main/java/chat/rocket/reactnative/generated/BasePackageList.java b/android/app/src/main/java/chat/rocket/reactnative/generated/BasePackageList.java deleted file mode 100644 index 8143f5969..000000000 --- a/android/app/src/main/java/chat/rocket/reactnative/generated/BasePackageList.java +++ /dev/null @@ -1,21 +0,0 @@ -package chat.rocket.reactnative.generated; - -import java.util.Arrays; -import java.util.List; -import org.unimodules.core.interfaces.Package; - -public class BasePackageList { - public List getPackageList() { - return Arrays.asList( - new expo.modules.av.AVPackage(), - new expo.modules.constants.ConstantsPackage(), - new expo.modules.filesystem.FileSystemPackage(), - new expo.modules.haptics.HapticsPackage(), - new expo.modules.imageloader.ImageLoaderPackage(), - new expo.modules.keepawake.KeepAwakePackage(), - new expo.modules.localauthentication.LocalAuthenticationPackage(), - new expo.modules.videothumbnails.VideoThumbnailsPackage(), - new expo.modules.webbrowser.WebBrowserPackage() - ); - } -} diff --git a/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/MainApplicationReactNativeHost.java b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/MainApplicationReactNativeHost.java new file mode 100644 index 000000000..cf5e9c1ad --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/MainApplicationReactNativeHost.java @@ -0,0 +1,116 @@ +package chat.rocket.reactnative.newarchitecture; + +import android.app.Application; +import androidx.annotation.NonNull; +import com.facebook.react.PackageList; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.ReactPackageTurboModuleManagerDelegate; +import com.facebook.react.bridge.JSIModulePackage; +import com.facebook.react.bridge.JSIModuleProvider; +import com.facebook.react.bridge.JSIModuleSpec; +import com.facebook.react.bridge.JSIModuleType; +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.UIManager; +import com.facebook.react.fabric.ComponentFactory; +import com.facebook.react.fabric.CoreComponentsRegistry; +import com.facebook.react.fabric.EmptyReactNativeConfig; +import com.facebook.react.fabric.FabricJSIModuleProvider; +import com.facebook.react.uimanager.ViewManagerRegistry; +import chat.rocket.reactnative.BuildConfig; +import chat.rocket.reactnative.newarchitecture.components.MainComponentsRegistry; +import chat.rocket.reactnative.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate; +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both + * TurboModule delegates and the Fabric Renderer. + * + *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the + * `newArchEnabled` property). Is ignored otherwise. + */ +public class MainApplicationReactNativeHost extends ReactNativeHost { + public MainApplicationReactNativeHost(Application application) { + super(application); + } + + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + List packages = new PackageList(this).getPackages(); + // Packages that cannot be autolinked yet can be added manually here, for example: + // packages.add(new MyReactNativePackage()); + // TurboModules must also be loaded here providing a valid TurboReactPackage implementation: + // packages.add(new TurboReactPackage() { ... }); + // If you have custom Fabric Components, their ViewManagers should also be loaded here + // inside a ReactPackage. + return packages; + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + + @NonNull + @Override + protected ReactPackageTurboModuleManagerDelegate.Builder + getReactPackageTurboModuleManagerDelegateBuilder() { + // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary + // for the new architecture and to use TurboModules correctly. + return new MainApplicationTurboModuleManagerDelegate.Builder(); + } + + @Override + protected JSIModulePackage getJSIModulePackage() { + return new JSIModulePackage() { + @Override + public List getJSIModules( + final ReactApplicationContext reactApplicationContext, + final JavaScriptContextHolder jsContext) { + final List specs = new ArrayList<>(); + + // Here we provide a new JSIModuleSpec that will be responsible of providing the + // custom Fabric Components. + specs.add( + new JSIModuleSpec() { + @Override + public JSIModuleType getJSIModuleType() { + return JSIModuleType.UIManager; + } + + @Override + public JSIModuleProvider getJSIModuleProvider() { + final ComponentFactory componentFactory = new ComponentFactory(); + CoreComponentsRegistry.register(componentFactory); + + // Here we register a Components Registry. + // The one that is generated with the template contains no components + // and just provides you the one from React Native core. + MainComponentsRegistry.register(componentFactory); + + final ReactInstanceManager reactInstanceManager = getReactInstanceManager(); + + ViewManagerRegistry viewManagerRegistry = + new ViewManagerRegistry( + reactInstanceManager.getOrCreateViewManagers(reactApplicationContext)); + + return new FabricJSIModuleProvider( + reactApplicationContext, + componentFactory, + new EmptyReactNativeConfig(), + viewManagerRegistry); + } + }); + return specs; + } + }; + } +} \ No newline at end of file diff --git a/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/components/MainComponentsRegistry.java b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/components/MainComponentsRegistry.java new file mode 100644 index 000000000..6519aa669 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/components/MainComponentsRegistry.java @@ -0,0 +1,36 @@ +package chat.rocket.reactnative.newarchitecture.components; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.fabric.ComponentFactory; +import com.facebook.soloader.SoLoader; + +/** + * Class responsible to load the custom Fabric Components. This class has native methods and needs a + * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ + * folder for you). + * + *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the + * `newArchEnabled` property). Is ignored otherwise. + */ +@DoNotStrip +public class MainComponentsRegistry { + static { + SoLoader.loadLibrary("fabricjni"); + } + + @DoNotStrip private final HybridData mHybridData; + + @DoNotStrip + private native HybridData initHybrid(ComponentFactory componentFactory); + + @DoNotStrip + private MainComponentsRegistry(ComponentFactory componentFactory) { + mHybridData = initHybrid(componentFactory); + } + + @DoNotStrip + public static MainComponentsRegistry register(ComponentFactory componentFactory) { + return new MainComponentsRegistry(componentFactory); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java new file mode 100644 index 000000000..74a5728f2 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java @@ -0,0 +1,48 @@ +package chat.rocket.reactnative.newarchitecture.modules; + +import com.facebook.jni.HybridData; +import com.facebook.react.ReactPackage; +import com.facebook.react.ReactPackageTurboModuleManagerDelegate; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.soloader.SoLoader; +import java.util.List; + +/** + * Class responsible to load the TurboModules. This class has native methods and needs a + * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ + * folder for you). + * + *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the + * `newArchEnabled` property). Is ignored otherwise. + */ +public class MainApplicationTurboModuleManagerDelegate + extends ReactPackageTurboModuleManagerDelegate { + + private static volatile boolean sIsSoLibraryLoaded; + + protected MainApplicationTurboModuleManagerDelegate( + ReactApplicationContext reactApplicationContext, List packages) { + super(reactApplicationContext, packages); + } + + protected native HybridData initHybrid(); + + native boolean canCreateTurboModule(String moduleName); + + public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder { + protected MainApplicationTurboModuleManagerDelegate build( + ReactApplicationContext context, List packages) { + return new MainApplicationTurboModuleManagerDelegate(context, packages); + } + } + + @Override + protected synchronized void maybeLoadOtherSoLibraries() { + if (!sIsSoLibraryLoaded) { + // If you change the name of your application .so file in the Android.mk file, + // make sure you update the name here as well. + SoLoader.loadLibrary("rocketchat_appmodules"); + sIsSoLibraryLoaded = true; + } + } +} \ No newline at end of file diff --git a/android/app/src/main/jni/Android.mk b/android/app/src/main/jni/Android.mk new file mode 100644 index 000000000..db632d8b3 --- /dev/null +++ b/android/app/src/main/jni/Android.mk @@ -0,0 +1,49 @@ +THIS_DIR := $(call my-dir) + +include $(REACT_ANDROID_DIR)/Android-prebuilt.mk + +# If you wish to add a custom TurboModule or Fabric component in your app you +# will have to include the following autogenerated makefile. +# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk +include $(CLEAR_VARS) + +LOCAL_PATH := $(THIS_DIR) + +# You can customize the name of your application .so file here. +LOCAL_MODULE := rocketchat_appmodules + +LOCAL_C_INCLUDES := $(LOCAL_PATH) +LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) + +# If you wish to add a custom TurboModule or Fabric component in your app you +# will have to uncomment those lines to include the generated source +# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni) +# +# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni +# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp) +# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni + +# Here you should add any native library you wish to depend on. +LOCAL_SHARED_LIBRARIES := \ + libfabricjni \ + libfbjni \ + libfolly_futures \ + libfolly_json \ + libglog \ + libjsi \ + libreact_codegen_rncore \ + libreact_debug \ + libreact_nativemodule_core \ + libreact_render_componentregistry \ + libreact_render_core \ + libreact_render_debug \ + libreact_render_graphics \ + librrc_view \ + libruntimeexecutor \ + libturbomodulejsijni \ + libyoga + +LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall + +include $(BUILD_SHARED_LIBRARY) \ No newline at end of file diff --git a/android/app/src/main/jni/MainApplicationModuleProvider.cpp b/android/app/src/main/jni/MainApplicationModuleProvider.cpp new file mode 100644 index 000000000..39de093d1 --- /dev/null +++ b/android/app/src/main/jni/MainApplicationModuleProvider.cpp @@ -0,0 +1,24 @@ +#include "MainApplicationModuleProvider.h" + +#include + +namespace facebook { +namespace react { + +std::shared_ptr MainApplicationModuleProvider( + const std::string moduleName, + const JavaTurboModule::InitParams ¶ms) { + // Here you can provide your own module provider for TurboModules coming from + // either your application or from external libraries. The approach to follow + // is similar to the following (for a library called `samplelibrary`: + // + // auto module = samplelibrary_ModuleProvider(moduleName, params); + // if (module != nullptr) { + // return module; + // } + // return rncore_ModuleProvider(moduleName, params); + return rncore_ModuleProvider(moduleName, params); +} + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainApplicationModuleProvider.h b/android/app/src/main/jni/MainApplicationModuleProvider.h new file mode 100644 index 000000000..2f2fb24a3 --- /dev/null +++ b/android/app/src/main/jni/MainApplicationModuleProvider.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include + +namespace facebook { +namespace react { + +std::shared_ptr MainApplicationModuleProvider( + const std::string moduleName, + const JavaTurboModule::InitParams ¶ms); + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp b/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp new file mode 100644 index 000000000..f2e4714dc --- /dev/null +++ b/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp @@ -0,0 +1,45 @@ +#include "MainApplicationTurboModuleManagerDelegate.h" +#include "MainApplicationModuleProvider.h" + +namespace facebook { +namespace react { + +jni::local_ref +MainApplicationTurboModuleManagerDelegate::initHybrid( + jni::alias_ref) { + return makeCxxInstance(); +} + +void MainApplicationTurboModuleManagerDelegate::registerNatives() { + registerHybrid({ + makeNativeMethod( + "initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid), + makeNativeMethod( + "canCreateTurboModule", + MainApplicationTurboModuleManagerDelegate::canCreateTurboModule), + }); +} + +std::shared_ptr +MainApplicationTurboModuleManagerDelegate::getTurboModule( + const std::string name, + const std::shared_ptr jsInvoker) { + // Not implemented yet: provide pure-C++ NativeModules here. + return nullptr; +} + +std::shared_ptr +MainApplicationTurboModuleManagerDelegate::getTurboModule( + const std::string name, + const JavaTurboModule::InitParams ¶ms) { + return MainApplicationModuleProvider(name, params); +} + +bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule( + std::string name) { + return getTurboModule(name, nullptr) != nullptr || + getTurboModule(name, {.moduleName = name}) != nullptr; +} + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h b/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h new file mode 100644 index 000000000..f2401d08f --- /dev/null +++ b/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h @@ -0,0 +1,38 @@ +#include +#include + +#include +#include + +namespace facebook { +namespace react { + +class MainApplicationTurboModuleManagerDelegate + : public jni::HybridClass< + MainApplicationTurboModuleManagerDelegate, + TurboModuleManagerDelegate> { + public: + // Adapt it to the package you used for your Java class. + static constexpr auto kJavaDescriptor = + "Lchat/rocket/reactnative/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;"; + + static jni::local_ref initHybrid(jni::alias_ref); + + static void registerNatives(); + + std::shared_ptr getTurboModule( + const std::string name, + const std::shared_ptr jsInvoker) override; + std::shared_ptr getTurboModule( + const std::string name, + const JavaTurboModule::InitParams ¶ms) override; + + /** + * Test-only method. Allows user to verify whether a TurboModule can be + * created by instances of this class. + */ + bool canCreateTurboModule(std::string name); +}; + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainComponentsRegistry.cpp b/android/app/src/main/jni/MainComponentsRegistry.cpp new file mode 100644 index 000000000..c5188f4dc --- /dev/null +++ b/android/app/src/main/jni/MainComponentsRegistry.cpp @@ -0,0 +1,61 @@ +#include "MainComponentsRegistry.h" + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {} + +std::shared_ptr +MainComponentsRegistry::sharedProviderRegistry() { + auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); + + // Custom Fabric Components go here. You can register custom + // components coming from your App or from 3rd party libraries here. + // + // providerRegistry->add(concreteComponentDescriptorProvider< + // AocViewerComponentDescriptor>()); + return providerRegistry; +} + +jni::local_ref +MainComponentsRegistry::initHybrid( + jni::alias_ref, + ComponentFactory *delegate) { + auto instance = makeCxxInstance(delegate); + + auto buildRegistryFunction = + [](EventDispatcher::Weak const &eventDispatcher, + ContextContainer::Shared const &contextContainer) + -> ComponentDescriptorRegistry::Shared { + auto registry = MainComponentsRegistry::sharedProviderRegistry() + ->createComponentDescriptorRegistry( + {eventDispatcher, contextContainer}); + + auto mutableRegistry = + std::const_pointer_cast(registry); + + mutableRegistry->setFallbackComponentDescriptor( + std::make_shared( + ComponentDescriptorParameters{ + eventDispatcher, contextContainer, nullptr})); + + return registry; + }; + + delegate->buildRegistryFunction = buildRegistryFunction; + return instance; +} + +void MainComponentsRegistry::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid), + }); +} + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/MainComponentsRegistry.h b/android/app/src/main/jni/MainComponentsRegistry.h new file mode 100644 index 000000000..8e5cd1f1f --- /dev/null +++ b/android/app/src/main/jni/MainComponentsRegistry.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +namespace facebook { +namespace react { + +class MainComponentsRegistry + : public facebook::jni::HybridClass { + public: + // Adapt it to the package you used for your Java class. + constexpr static auto kJavaDescriptor = + "Lchat/rocket/reactnative/newarchitecture/components/MainComponentsRegistry;"; + + static void registerNatives(); + + MainComponentsRegistry(ComponentFactory *delegate); + + private: + static std::shared_ptr + sharedProviderRegistry(); + + static jni::local_ref initHybrid( + jni::alias_ref, + ComponentFactory *delegate); +}; + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/android/app/src/main/jni/OnLoad.cpp b/android/app/src/main/jni/OnLoad.cpp new file mode 100644 index 000000000..ae1ef007d --- /dev/null +++ b/android/app/src/main/jni/OnLoad.cpp @@ -0,0 +1,11 @@ +#include +#include "MainApplicationTurboModuleManagerDelegate.h" +#include "MainComponentsRegistry.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return facebook::jni::initialize(vm, [] { + facebook::react::MainApplicationTurboModuleManagerDelegate:: + registerNatives(); + facebook::react::MainComponentsRegistry::registerNatives(); + }); +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable/rn_edit_text_material.xml b/android/app/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 000000000..bb6f578c3 --- /dev/null +++ b/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,29 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 22d14143e..1fd46175e 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -4,6 +4,8 @@ @color/splashBackground @color/splashBackground false + + @drawable/rn_edit_text_material