diff --git a/android/app/build.gradle b/android/app/build.gradle index 3829c9457..105d050df 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -14,6 +14,7 @@ react { hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() + enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean() // Use Expo CLI to bundle the app, this ensures the Metro config // works correctly with Expo projects. cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) @@ -31,6 +32,7 @@ react { // The list of variants to that are debuggable. For those we're going to // skip the bundling of the JS bundle and the assets. By default is just 'debug'. // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + // debuggableVariants = ["liteDebug", "prodDebug"] debuggableVariants = ["callbackTiramisuDebug"] /* Bundling */ @@ -63,9 +65,9 @@ react { } /** - * Set this to true to Run Proguard on Release builds to minify the Java bytecode. + * Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization). */ -def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() +def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean() /** * The preferred build flavor of JavaScriptCore (JSC) @@ -78,7 +80,7 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ -def jscFlavor = 'org.webkit:android-jsc:+' +def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' android { ndkVersion rootProject.ext.ndkVersion @@ -93,15 +95,15 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode 119 versionName "1.8.7" - } + buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" + } flavorDimensions "react-native-capture-protection" productFlavors { callbackTiramisu { dimension "react-native-capture-protection" } } - signingConfigs { debug { storeFile file('debug.keystore') @@ -124,15 +126,18 @@ android { // Caution! In production, you need to generate your own keystore file. // see https://reactnative.dev/docs/signed-apk-android. signingConfig signingConfigs.debug - shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) - minifyEnabled enableProguardInReleaseBuilds + def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false' + shrinkResources enableShrinkResources.toBoolean() + minifyEnabled enableMinifyInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true) + def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true' + crunchPngs enablePngCrunchInRelease.toBoolean() } } packagingOptions { jniLibs { - useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false) + def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false' + useLegacyPackaging enableLegacyPackaging.toBoolean() } } androidResources { @@ -170,15 +175,15 @@ dependencies { if (isGifEnabled) { // For animated gif support - implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}") + implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}") } if (isWebpEnabled) { // For webp support - implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}") + implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}") if (isWebpAnimatedEnabled) { // Animated webp support - implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}") + implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}") } } diff --git a/android/app/src/debugOptimized/AndroidManifest.xml b/android/app/src/debugOptimized/AndroidManifest.xml new file mode 100644 index 000000000..3ec2507ba --- /dev/null +++ b/android/app/src/debugOptimized/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 79628b06a..d77c6dd4b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,9 @@ - + + + + @@ -16,13 +18,12 @@ - + - @@ -33,12 +34,21 @@ - + + + + + + + + + + @@ -47,6 +57,5 @@ - \ No newline at end of file diff --git a/android/app/src/main/java/com/internxt/cloud/MainActivity.kt b/android/app/src/main/java/com/internxt/cloud/MainActivity.kt index d4bfd46d3..e3dd93bc1 100644 --- a/android/app/src/main/java/com/internxt/cloud/MainActivity.kt +++ b/android/app/src/main/java/com/internxt/cloud/MainActivity.kt @@ -1,61 +1,114 @@ package com.internxt.cloud -import expo.modules.splashscreen.SplashScreenManager +import android.content.Intent +import android.net.Uri import android.os.Build import android.os.Bundle - +import android.provider.OpenableColumns import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableArray import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate - import expo.modules.ReactActivityDelegateWrapper +import expo.modules.splashscreen.SplashScreenManager class MainActivity : ReactActivity() { override fun onCreate(savedInstanceState: Bundle?) { - // @generated begin expo-splashscreen - expo prebuild (DO NOT MODIFY) sync-f3ff59a738c56c9a6119210cb55f0b613eb8b6af + // @generated begin expo-splashscreen - expo prebuild (DO NOT MODIFY) + // sync-f3ff59a738c56c9a6119210cb55f0b613eb8b6af SplashScreenManager.registerOnActivity(this) // @generated end expo-splashscreen super.onCreate(null) + parseShareIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + val files = parseShareIntent(intent) ?: return + ShareIntentModule.emitFilesIfReady(files) } - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ override fun getMainComponentName(): String = "main" - /** - * 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 - ){}) + 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 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + if (!moveTaskToBack(false)) { + super.invokeDefaultOnBackPressed() } + return + } + super.invokeDefaultOnBackPressed() + } + + private fun parseShareIntent(intent: Intent): WritableArray? { + val action = intent.action ?: return null + if (action != Intent.ACTION_SEND && action != Intent.ACTION_SEND_MULTIPLE) return null + + val uris = mutableListOf() + val mimeType = intent.type - // Use the default back button implementation on Android S - // because it's doing more than [Activity.moveTaskToBack] in fact. - super.invokeDefaultOnBackPressed() + if (action == Intent.ACTION_SEND) { + val uri = getParcelableUri(intent) ?: return null + uris.add(uri) + } else { + val list = getParcelableUriList(intent) ?: return null + uris.addAll(list) + } + + val files = mutableListOf>() + for (uri in uris) { + files.add( + mapOf( + "uri" to uri.toString(), + "mimeType" to mimeType, + "fileName" to resolveFileName(uri) + ) + ) + } + + ShareIntentModule.pendingFiles = files + + val array = Arguments.createArray() + for (file in files) { + val map = Arguments.createMap() + file.forEach { (k, v) -> if (v != null) map.putString(k, v) else map.putNull(k) } + array.pushMap(map) + } + return array + } + + private fun resolveFileName(uri: Uri): String? { + return try { + contentResolver.query(uri, null, null, null, null)?.use { cursor -> + if (!cursor.moveToFirst()) return null + val idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (idx < 0) null else cursor.getString(idx) + } + } catch (e: Exception) { + null + } } + + @Suppress("DEPRECATION") + private fun getParcelableUri(intent: Intent): Uri? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java) + else intent.getParcelableExtra(Intent.EXTRA_STREAM) + + @Suppress("DEPRECATION") + private fun getParcelableUriList(intent: Intent): ArrayList? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java) + else intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) } diff --git a/android/app/src/main/java/com/internxt/cloud/MainApplication.kt b/android/app/src/main/java/com/internxt/cloud/MainApplication.kt index 2cd759315..522fcce5a 100644 --- a/android/app/src/main/java/com/internxt/cloud/MainApplication.kt +++ b/android/app/src/main/java/com/internxt/cloud/MainApplication.kt @@ -5,13 +5,13 @@ import android.content.res.Configuration import com.facebook.react.PackageList import com.facebook.react.ReactApplication +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative import com.facebook.react.ReactNativeHost import com.facebook.react.ReactPackage import com.facebook.react.ReactHost -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load +import com.facebook.react.common.ReleaseLevel +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint import com.facebook.react.defaults.DefaultReactNativeHost -import com.facebook.react.soloader.OpenSourceMergedSoMapping -import com.facebook.soloader.SoLoader import expo.modules.ApplicationLifecycleDispatcher import expo.modules.ReactNativeHostWrapper @@ -19,21 +19,18 @@ import expo.modules.ReactNativeHostWrapper class MainApplication : Application(), ReactApplication { override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( - this, - object : DefaultReactNativeHost(this) { - override fun getPackages(): List { - val packages = PackageList(this).packages - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new MyReactNativePackage()); - return packages - } + this, + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + add(ShareIntentPackage()) + } 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 } ) @@ -42,11 +39,12 @@ class MainApplication : Application(), ReactApplication { 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() + DefaultNewArchitectureEntryPoint.releaseLevel = try { + ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase()) + } catch (e: IllegalArgumentException) { + ReleaseLevel.STABLE } + loadReactNative(this) ApplicationLifecycleDispatcher.onApplicationCreate(this) } diff --git a/android/app/src/main/java/com/internxt/cloud/ShareIntentModule.kt b/android/app/src/main/java/com/internxt/cloud/ShareIntentModule.kt new file mode 100644 index 000000000..83d646720 --- /dev/null +++ b/android/app/src/main/java/com/internxt/cloud/ShareIntentModule.kt @@ -0,0 +1,50 @@ +package com.internxt.cloud + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.WritableArray +import com.facebook.react.modules.core.DeviceEventManagerModule + +class ShareIntentModule(private val ctx: ReactApplicationContext) : + ReactContextBaseJavaModule(ctx) { + + companion object { + var pendingFiles: List>? = null + private var instance: ShareIntentModule? = null + + fun emitFilesIfReady(files: WritableArray) { + instance?.emit(files) + } + } + + init { + instance = this + } + + override fun getName() = "ShareIntentModule" + + @ReactMethod + fun getSharedFiles(promise: Promise) { + val files = pendingFiles + pendingFiles = null + if (files == null) { + promise.resolve(null) + return + } + val array = Arguments.createArray() + for (file in files) { + val map = Arguments.createMap() + file.forEach { (k, v) -> if (v != null) map.putString(k, v) else map.putNull(k) } + array.pushMap(map) + } + promise.resolve(array) + } + + private fun emit(files: WritableArray) { + ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("ShareIntentReceived", files) + } +} diff --git a/android/app/src/main/java/com/internxt/cloud/ShareIntentPackage.kt b/android/app/src/main/java/com/internxt/cloud/ShareIntentPackage.kt new file mode 100644 index 000000000..a9d336eb8 --- /dev/null +++ b/android/app/src/main/java/com/internxt/cloud/ShareIntentPackage.kt @@ -0,0 +1,14 @@ +package com.internxt.cloud + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class ShareIntentPackage : ReactPackage { + override fun createNativeModules(context: ReactApplicationContext): List = + listOf(ShareIntentModule(context)) + + override fun createViewManagers(context: ReactApplicationContext): List> = + emptyList() +} diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 7b4d7131c..3ff205caf 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ Internxt automatic - 1.8.6 + 1.8.7 contain false \ 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 2fa96d2eb..117325ad4 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,10 +1,9 @@