diff --git a/native/android/app/src/main/java/app/comm/android/MainActivity.java b/native/android/app/src/main/java/app/comm/android/MainActivity.java deleted file mode 100644 --- a/native/android/app/src/main/java/app/comm/android/MainActivity.java +++ /dev/null @@ -1,95 +0,0 @@ -package app.comm.android; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Bundle; -import androidx.core.app.ActivityCompat; -import app.comm.android.notifications.CommAndroidNotifications; -import com.facebook.react.ReactActivity; -import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; -import expo.modules.ReactActivityDelegateWrapper; - -public class MainActivity extends ReactActivity - implements ActivityCompat.OnRequestPermissionsResultCallback { - - /** - * Returns the name of the main component registered from JavaScript. - * This is used to schedule rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "Comm"; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(null); - } - - @Override - public void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - } - - /** - * Returns the instance of the {@link ReactActivityDelegate}. There the - * RootView is created and you can specify the renderer you wish to use - the - * new renderer (Fabric) or the old renderer (Paper). - */ - @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; - } - @Override - protected boolean isConcurrentRootEnabled() { - // If you opted-in for the New Architecture, we enable Concurrent Root - // (i.e. React 18). More on this on - // https://reactjs.org/blog/2022/03/29/react-v18.html - return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - } - } - - @Override - public void invokeDefaultOnBackPressed() { - moveTaskToBack(true); - } - - @Override - public void onRequestPermissionsResult( - int requestCode, - String[] permissions, - int[] grantResults) { - - for (int permissionIndex = 0; permissionIndex < grantResults.length; - permissionIndex++) { - String permissionName = permissions[permissionIndex]; - if (requestCode == - CommAndroidNotifications - .COMM_ANDROID_NOTIFICATIONS_REQUEST_CODE && - permissionName.equals(Manifest.permission.POST_NOTIFICATIONS)) { - CommAndroidNotifications.resolveNotificationsPermissionRequestPromise( - this, - grantResults[permissionIndex] == PackageManager.PERMISSION_GRANTED); - } - } - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - } -} diff --git a/native/android/app/src/main/java/app/comm/android/MainActivity.kt b/native/android/app/src/main/java/app/comm/android/MainActivity.kt new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/java/app/comm/android/MainActivity.kt @@ -0,0 +1,86 @@ +package app.comm.android + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import app.comm.android.notifications.CommAndroidNotifications + +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate + +import expo.modules.splashscreen.SplashScreenManager +import expo.modules.ReactActivityDelegateWrapper + +class MainActivity : ReactActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + // Set the theme to AppTheme BEFORE onCreate to support + // coloring the background, status bar, and navigation bar. + // This is required for expo-splash-screen. + setTheme(R.style.AppTheme); + super.onCreate(null) + } + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "Comm" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate { + return ReactActivityDelegateWrapper( + this, + BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, + object : DefaultReactActivityDelegate( + this, + mainComponentName, + fabricEnabled + ){}) + } + + /** + * Align the back button behavior with Android S + * where moving root activities to background instead of finishing activities. + * @see onBackPressed + */ + override fun invokeDefaultOnBackPressed() { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + if (!moveTaskToBack(false)) { + // For non-root activities, use the default implementation to finish them. + super.invokeDefaultOnBackPressed() + } + return + } + + // Use the default back button implementation on Android S + // because it's doing more than [Activity.moveTaskToBack] in fact. + super.invokeDefaultOnBackPressed() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + for (permissionIndex in grantResults.indices) { + val permissionName = permissions[permissionIndex] + if (requestCode == + CommAndroidNotifications + .COMM_ANDROID_NOTIFICATIONS_REQUEST_CODE && + permissionName == Manifest.permission.POST_NOTIFICATIONS + ) { + CommAndroidNotifications.resolveNotificationsPermissionRequestPromise( + this, + grantResults[permissionIndex] == PackageManager.PERMISSION_GRANTED + ) + } + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + } +} \ No newline at end of file diff --git a/native/android/app/src/main/java/app/comm/android/MainApplication.java b/native/android/app/src/main/java/app/comm/android/MainApplication.java deleted file mode 100644 --- a/native/android/app/src/main/java/app/comm/android/MainApplication.java +++ /dev/null @@ -1,128 +0,0 @@ -package app.comm.android; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; -import android.database.CursorWindow; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.multidex.MultiDexApplication; -import app.comm.android.commservices.CommServicesPackage; -import app.comm.android.newarchitecture.MainApplicationReactNativeHost; -import app.comm.android.notifications.CommAndroidNotificationsPackage; -import com.facebook.react.PackageList; -import com.facebook.react.ReactApplication; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactNativeHost; -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JSIModulePackage; -import com.facebook.react.config.ReactFeatureFlags; -import com.facebook.soloader.SoLoader; -import com.wix.reactnativekeyboardinput.KeyboardInputPackage; -import expo.modules.ApplicationLifecycleDispatcher; -import expo.modules.ReactNativeHostWrapper; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.security.Security; -import java.util.List; -import org.jetbrains.annotations.Nullable; - -public class MainApplication - extends MultiDexApplication implements ReactApplication { - - static { - System.loadLibrary("fbjni"); - System.loadLibrary("comm_jni_module"); - } - - private static Context context; - - private final ReactNativeHost mReactNativeHost = - new ReactNativeHostWrapper(this, new ReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - packages.add(new KeyboardInputPackage(this.getApplication())); - packages.add(new CommAndroidNotificationsPackage()); - packages.add(new CommServicesPackage()); - return packages; - } - - @Override - protected String getJSMainModuleName() { - return "index"; - } - - @Override - protected JSIModulePackage getJSIModulePackage() { - return new CommCoreJSIModulePackage(); - } - }); - - private final ReactNativeHost mNewArchitectureNativeHost = - new ReactNativeHostWrapper( - this, - new MainApplicationReactNativeHost(this)); - - @Override - public ReactNativeHost getReactNativeHost() { - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - return mNewArchitectureNativeHost; - } else { - return mReactNativeHost; - } - } - - @Override - public Intent - registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) { - if (Build.VERSION.SDK_INT >= 34 && - getApplicationInfo().targetSdkVersion >= 34) { - return super.registerReceiver( - receiver, filter, Context.RECEIVER_EXPORTED); - } else { - return super.registerReceiver(receiver, filter); - } - } - - @Override - public void onCreate() { - super.onCreate(); - // If you opted-in for the New Architecture, we enable the TurboModule - // system - MainApplication.context = this.getApplicationContext(); - ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - - Security.insertProviderAt(new org.conscrypt.OpenSSLProvider(), 1); - - SoLoader.init(this, /* native exopackage */ false); - ApplicationLifecycleDispatcher.onApplicationCreate(this); - try { - Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); - field.setAccessible(true); - field.set(null, 100 * 1024 * 1024); // 100 MiB - } catch (Exception e) { - if (BuildConfig.DEBUG) { - e.printStackTrace(); - } - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig); - } - - public static Context getMainApplicationContext() { - return MainApplication.context; - } -} diff --git a/native/android/app/src/main/java/app/comm/android/MainApplication.kt b/native/android/app/src/main/java/app/comm/android/MainApplication.kt new file mode 100644 --- /dev/null +++ b/native/android/app/src/main/java/app/comm/android/MainApplication.kt @@ -0,0 +1,107 @@ +package app.comm.android + +import android.app.Application +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.res.Configuration +import android.database.CursorWindow +import android.os.Build +import app.comm.android.commservices.CommServicesPackage +import app.comm.android.notifications.CommAndroidNotificationsPackage +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.defaults.DefaultReactNativeHost +import com.facebook.react.soloader.OpenSourceMergedSoMapping +import com.facebook.soloader.SoLoader +import com.wix.reactnativekeyboardinput.KeyboardInputPackage +import expo.modules.ApplicationLifecycleDispatcher +import expo.modules.ReactNativeHostWrapper +import org.conscrypt.OpenSSLProvider +import java.lang.reflect.Field +import java.security.Security + + +class MainApplication : Application(), ReactApplication { + override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( + this, + object : DefaultReactNativeHost(this) { + override fun getPackages(): List { + val packages = PackageList(this).packages + packages.add(KeyboardInputPackage(this.application)) + packages.add(CommAndroidNotificationsPackage()) + packages.add(CommServicesPackage()) + return packages + } + + override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + } + ) + + companion object { + init { + System.loadLibrary("fbjni"); + System.loadLibrary("comm_jni_module"); + } + var context: Context? = null + + fun getMainApplicationContext(): Context? { + return context + } + } + + override val reactHost: ReactHost + get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) + + override fun registerReceiver( + receiver: BroadcastReceiver?, + filter: IntentFilter? + ): Intent? { + return if (Build.VERSION.SDK_INT >= 34 && + applicationInfo.targetSdkVersion >= 34 + ) { + super.registerReceiver( + receiver, filter, RECEIVER_EXPORTED + ) + } else { + super.registerReceiver(receiver, filter) + } + } + + override fun onCreate() { + super.onCreate() + Security.insertProviderAt(OpenSSLProvider(), 1) + + 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() + } + ApplicationLifecycleDispatcher.onApplicationCreate(this) + try { + val field: Field = CursorWindow::class.java.getDeclaredField("sCursorWindowSize") + field.setAccessible(true) + field.set(null, 100 * 1024 * 1024) // 100 MiB + } catch (e: Exception) { + if (BuildConfig.DEBUG) { + e.printStackTrace() + } + } + context = applicationContext + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) + } +} \ No newline at end of file diff --git a/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java --- a/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java +++ b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java @@ -73,7 +73,7 @@ mmkvIdentifier = identifier; } - MMKV.initialize(MainApplication.getMainApplicationContext()); + MMKV.initialize(MainApplication.Companion.getMainApplicationContext()); getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); } } @@ -96,7 +96,7 @@ throw new RuntimeException("Failed to remove MMKV storage."); } assignInitializationData(); - MMKV.initialize(MainApplication.getMainApplicationContext()); + MMKV.initialize(MainApplication.Companion.getMainApplicationContext()); getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); } } diff --git a/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java b/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java --- a/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java +++ b/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java @@ -20,7 +20,7 @@ public static String getNotificationsCryptoAccountPath() { Context mainApplicationContext = - MainApplication.getMainApplicationContext(); + MainApplication.Companion.getMainApplicationContext(); if (mainApplicationContext == null) { throw new RuntimeException( "Failed to resolve notifications crypto account path - main application context not initialized."); @@ -32,7 +32,7 @@ public static String getBackupDirectoryPath() { Context mainApplicationContext = - MainApplication.getMainApplicationContext(); + MainApplication.Companion.getMainApplicationContext(); if (mainApplicationContext == null) { throw new RuntimeException( "Failed to resolve backup path - main application context not initialized.");