From f5d5139f09c118b6bf9dcd9adb50ca0d098f1a88 Mon Sep 17 00:00:00 2001 From: Sultan Date: Fri, 18 Oct 2024 17:19:28 +0200 Subject: [PATCH 01/12] feat: Add android in app update --- android/app/build.gradle | 5 +++ .../main/java/net/artsy/app/MainActivity.kt | 44 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/android/app/build.gradle b/android/app/build.gradle index a7027fad0b7..a82e235d36b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -217,10 +217,15 @@ dependencies { implementation 'com.facebook.fresco:animated-webp:3.6.0' implementation 'com.facebook.fresco:webpsupport:3.6.0' + // for Android In App Update + implementation 'com.google.android.play:app-update:2.1.0' + implementation 'com.google.android.play:app-update-ktx:2.1.0' + // Test dependencies testImplementation 'junit:junit:4.13.2' testImplementation 'io.mockk:mockk:1.13.12' + if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { diff --git a/android/app/src/main/java/net/artsy/app/MainActivity.kt b/android/app/src/main/java/net/artsy/app/MainActivity.kt index 2fd3ffb9554..94f0a62e17d 100644 --- a/android/app/src/main/java/net/artsy/app/MainActivity.kt +++ b/android/app/src/main/java/net/artsy/app/MainActivity.kt @@ -10,6 +10,7 @@ import android.view.View import android.view.WindowInsets import android.view.WindowManager import android.content.Intent +import android.content.IntentSender import android.net.Uri import androidx.annotation.Nullable import com.facebook.react.ReactActivity @@ -19,9 +20,20 @@ import com.zoontek.rnbootsplash.RNBootSplash import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate +import com.google.android.play.core.appupdate.AppUpdateManagerFactory; +import androidx.activity.result.contract.ActivityResultContracts +import com.google.android.gms.tasks.Task +import com.google.android.play.core.appupdate.AppUpdateInfo +import com.google.android.play.core.appupdate.AppUpdateManager +import com.google.android.play.core.appupdate.AppUpdateOptions +import com.google.android.play.core.install.model.AppUpdateType +import com.google.android.play.core.install.model.UpdateAvailability + import android.util.Log class MainActivity : ReactActivity() { + private val DAYS_FOR_FLEXIBLE_UPDATE = 7 + private lateinit var appUpdateManager: AppUpdateManager /** * Returns the name of the main component registered from JavaScript. This is @@ -51,6 +63,38 @@ class MainActivity : ReactActivity() { // prevent screen rotation on phones requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } + + appUpdateManager = AppUpdateManagerFactory.create(this) + + checkForAppUpdate() + } + + private fun checkForAppUpdate() { + val appUpdateInfoTask: Task = appUpdateManager.appUpdateInfo + + appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> + if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && + (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE && + appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) { + // Start a flexible update + try { + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, + registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> + // handle callback + if (result.resultCode != RESULT_OK) { + Log.d("ARTSY", "Update flow failed! Result code: ${result.resultCode}") + // If the update is canceled or fails, + // you can request to start the update again. + } + }, + AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build() + ) + } catch (e: IntentSender.SendIntentException) { + e.printStackTrace() + } + } + } } // Basic overriding this class required for braze integration: From b8eefa4a5e06cc9308275831ce3bfb5abcc2a474 Mon Sep 17 00:00:00 2001 From: Sultan Date: Mon, 21 Oct 2024 16:01:41 +0200 Subject: [PATCH 02/12] add logs to test --- .../src/main/java/net/artsy/app/MainActivity.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/net/artsy/app/MainActivity.kt b/android/app/src/main/java/net/artsy/app/MainActivity.kt index 94f0a62e17d..ec6d3d94d2c 100644 --- a/android/app/src/main/java/net/artsy/app/MainActivity.kt +++ b/android/app/src/main/java/net/artsy/app/MainActivity.kt @@ -22,7 +22,6 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate import com.google.android.play.core.appupdate.AppUpdateManagerFactory; import androidx.activity.result.contract.ActivityResultContracts -import com.google.android.gms.tasks.Task import com.google.android.play.core.appupdate.AppUpdateInfo import com.google.android.play.core.appupdate.AppUpdateManager import com.google.android.play.core.appupdate.AppUpdateOptions @@ -33,6 +32,7 @@ import android.util.Log class MainActivity : ReactActivity() { private val DAYS_FOR_FLEXIBLE_UPDATE = 7 + private val TAG = "ArtsyApp" private lateinit var appUpdateManager: AppUpdateManager /** @@ -70,20 +70,29 @@ class MainActivity : ReactActivity() { } private fun checkForAppUpdate() { - val appUpdateInfoTask: Task = appUpdateManager.appUpdateInfo + Log.d(TAG, "checkForAppUpdate: started checking for update!") + val appUpdateInfoTask = appUpdateManager.appUpdateInfo + + appUpdateInfoTask.addOnFailureListener { appUpdateInfo -> + Log.d(TAG, "checkForAppUpdate: task failed: ${appUpdateInfo.toString()}") + } appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> + Log.d(TAG, "checkForAppUpdate: adding listener, appUpdateInfo: ${appUpdateInfo.toString()}") if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) { + Log.d(TAG, "appUpdateInfoTask.addOnSuccessListener: passed appUpdateInfo if statements") // Start a flexible update try { + Log.d(TAG, "appUpdateInfoTask.addOnSuccessListener: trying to start an update flow") appUpdateManager.startUpdateFlowForResult( appUpdateInfo, registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> + Log.d(TAG, "startUpdateFlowForResult: getting result: ${result.toString()}") // handle callback if (result.resultCode != RESULT_OK) { - Log.d("ARTSY", "Update flow failed! Result code: ${result.resultCode}") + Log.d(TAG, "Update flow failed! Result code: ${result.resultCode}") // If the update is canceled or fails, // you can request to start the update again. } @@ -91,6 +100,7 @@ class MainActivity : ReactActivity() { AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build() ) } catch (e: IntentSender.SendIntentException) { + Log.d(TAG, "startUpdateFlowForResult: errored out with ${e.toString()}") e.printStackTrace() } } From c49144613a3ca4ab2e4e6bb4b2affea6165eb6c8 Mon Sep 17 00:00:00 2001 From: Sultan Date: Mon, 21 Oct 2024 16:40:43 +0200 Subject: [PATCH 03/12] add log to build a new version --- android/app/src/main/java/net/artsy/app/MainActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/android/app/src/main/java/net/artsy/app/MainActivity.kt b/android/app/src/main/java/net/artsy/app/MainActivity.kt index ec6d3d94d2c..2b893b6107a 100644 --- a/android/app/src/main/java/net/artsy/app/MainActivity.kt +++ b/android/app/src/main/java/net/artsy/app/MainActivity.kt @@ -70,6 +70,7 @@ class MainActivity : ReactActivity() { } private fun checkForAppUpdate() { + Log.d(TAG, "here we go!") Log.d(TAG, "checkForAppUpdate: started checking for update!") val appUpdateInfoTask = appUpdateManager.appUpdateInfo From b0c892660e6389b6ce3cdd88baa9ece22833e3bf Mon Sep 17 00:00:00 2001 From: Sultan Date: Mon, 21 Oct 2024 17:53:08 +0200 Subject: [PATCH 04/12] add checks for conditions --- android/app/src/main/java/net/artsy/app/MainActivity.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android/app/src/main/java/net/artsy/app/MainActivity.kt b/android/app/src/main/java/net/artsy/app/MainActivity.kt index 2b893b6107a..575074e7852 100644 --- a/android/app/src/main/java/net/artsy/app/MainActivity.kt +++ b/android/app/src/main/java/net/artsy/app/MainActivity.kt @@ -80,6 +80,12 @@ class MainActivity : ReactActivity() { appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> Log.d(TAG, "checkForAppUpdate: adding listener, appUpdateInfo: ${appUpdateInfo.toString()}") + + Log.d(TAG, "checkForAppUpdate: conditions: \n" + + "appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE: ${appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE}\n" + + "(appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE: ${(appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE}" + + "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)}" + ) if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) { From 36e97e7621adec9a06ec97138a3b7a3ec7644c48 Mon Sep 17 00:00:00 2001 From: Sultan Date: Mon, 21 Oct 2024 21:27:56 +0200 Subject: [PATCH 05/12] remove staleness check --- android/app/src/main/java/net/artsy/app/MainActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/android/app/src/main/java/net/artsy/app/MainActivity.kt b/android/app/src/main/java/net/artsy/app/MainActivity.kt index 575074e7852..bc056175b9c 100644 --- a/android/app/src/main/java/net/artsy/app/MainActivity.kt +++ b/android/app/src/main/java/net/artsy/app/MainActivity.kt @@ -87,7 +87,6 @@ class MainActivity : ReactActivity() { "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)}" ) if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && - (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) { Log.d(TAG, "appUpdateInfoTask.addOnSuccessListener: passed appUpdateInfo if statements") // Start a flexible update From 7dfabb2c958d187efa0958ac604ed130906c6a63 Mon Sep 17 00:00:00 2001 From: Sultan Date: Mon, 21 Oct 2024 22:07:39 +0200 Subject: [PATCH 06/12] add new line --- android/app/src/main/java/net/artsy/app/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/net/artsy/app/MainActivity.kt b/android/app/src/main/java/net/artsy/app/MainActivity.kt index bc056175b9c..07876c44ec9 100644 --- a/android/app/src/main/java/net/artsy/app/MainActivity.kt +++ b/android/app/src/main/java/net/artsy/app/MainActivity.kt @@ -83,7 +83,7 @@ class MainActivity : ReactActivity() { Log.d(TAG, "checkForAppUpdate: conditions: \n" + "appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE: ${appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE}\n" + - "(appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE: ${(appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE}" + + "(appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE: ${(appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE}\n" + "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)}" ) if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && From 0c09eebd5247c860d41723f5f762402ae70c86fa Mon Sep 17 00:00:00 2001 From: Sultan Date: Fri, 22 Aug 2025 18:32:11 +0200 Subject: [PATCH 07/12] show in app update --- android/app/build.gradle | 7 +- .../java/net/artsy/app/ArtsyNativeModule.java | 82 +++++++++++ .../main/java/net/artsy/app/MainActivity.kt | 131 ++++++++++++++---- babel.config.js | 2 +- src/app/NativeModules/ArtsyNativeModule.tsx | 14 ++ src/app/Scenes/HomeView/HomeView.tsx | 2 + src/app/utils/promptForUpdate.ts | 22 +++ src/app/utils/usePromptForUpdate.tsx | 57 ++++++++ src/setupJest.tsx | 3 + 9 files changed, 290 insertions(+), 30 deletions(-) create mode 100644 src/app/utils/promptForUpdate.ts create mode 100644 src/app/utils/usePromptForUpdate.tsx diff --git a/android/app/build.gradle b/android/app/build.gradle index a82e235d36b..fd0979b7b17 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -108,15 +108,14 @@ android { applicationId "net.artsy.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 7 - versionName "1.0.0" - - buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" + versionCode 1674646332 + versionName "8.82.0" vectorDrawables.useSupportLibrary = true multiDexEnabled true + buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" buildConfigField "String", "GITCommitShortHash", '"c77772ee1"' buildConfigField "String", "GITCommitHash", '"c77772ee1f32093109049504f7865f9737ad8474"' buildConfigField "String", "GITCommitDate", '"2021-01-21T15:08:22+04:00"' diff --git a/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java b/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java index 8f9371188d7..48232e101b1 100644 --- a/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java +++ b/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java @@ -3,6 +3,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Build; +import android.util.Log; import androidx.annotation.NonNull; @@ -10,6 +11,10 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.Arguments; + import java.io.File; import java.util.HashMap; @@ -19,17 +24,44 @@ public class ArtsyNativeModule extends ReactContextBaseJavaModule { // this is called on application launch by MainApplication#onCreate private static final String LAUNCH_COUNT = "launchCount"; + private static final String TAG = "ArtsyApp"; public static void didLaunch(SharedPreferences prefs) { launchCount = prefs.getInt(LAUNCH_COUNT, 0) + 1; prefs.edit().putInt(LAUNCH_COUNT, launchCount).commit(); } private static Integer launchCount = 0; + // Reference to MainActivity for update functionality + private static MainActivity mainActivity; + public static void setMainActivity(MainActivity activity) { + mainActivity = activity; + } + + // Update state management + private static boolean updateDownloaded = false; + private static ArtsyNativeModule instance = null; + + public static void setUpdateDownloadedState(boolean state) { + updateDownloaded = state; + } + + public static void triggerUpdateDownloadedEvent() { + if (instance != null) { + WritableMap params = Arguments.createMap(); + params.putString("message", "Update downloaded successfully"); + instance.sendEvent("onAppUpdateDownloaded", params); + Log.d(TAG, "Update downloaded event sent to React Native"); + } else { + Log.e(TAG, "ArtsyNativeModule instance is null, cannot send event"); + } + } + ReactApplicationContext context; ArtsyNativeModule(ReactApplicationContext reactApplicationContext) { super(reactApplicationContext); context = reactApplicationContext; + instance = this; } @NonNull @@ -44,6 +76,56 @@ public Map getConstants() { constants.put("launchCount", ArtsyNativeModule.launchCount); constants.put("gitCommitShortHash", BuildConfig.GITCommitShortHash); constants.put("isBeta", BuildConfig.IS_BETA); + constants.put("updateDownloaded", ArtsyNativeModule.updateDownloaded); return constants; } + + @ReactMethod + public void checkForAppUpdate(Promise promise) { + Log.d(TAG, "checkForAppUpdate called from React Native"); + if (mainActivity != null) { + Log.d(TAG, "MainActivity reference found, calling update check"); + mainActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + mainActivity.checkForAppUpdateFromRN(); + promise.resolve(true); + } catch (Exception e) { + Log.e(TAG, "Error checking for update", e); + promise.reject("UPDATE_CHECK_FAILED", "Failed to check for app update", e); + } + } + }); + } else { + Log.e(TAG, "MainActivity reference is null"); + promise.reject("NO_MAIN_ACTIVITY", "MainActivity reference not available"); + } + } + + private void sendEvent(String eventName, WritableMap params) { + getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, params); + } + + @ReactMethod + public void completeAppUpdate(Promise promise) { + if (mainActivity != null) { + mainActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + mainActivity.completeAppUpdate(); + promise.resolve(true); + } catch (Exception e) { + promise.reject("UPDATE_COMPLETE_FAILED", "Failed to complete app update", e); + } + } + }); + } else { + promise.reject("NO_MAIN_ACTIVITY", "MainActivity reference not available"); + } + } + } diff --git a/android/app/src/main/java/net/artsy/app/MainActivity.kt b/android/app/src/main/java/net/artsy/app/MainActivity.kt index 07876c44ec9..b910fb7256e 100644 --- a/android/app/src/main/java/net/artsy/app/MainActivity.kt +++ b/android/app/src/main/java/net/artsy/app/MainActivity.kt @@ -27,14 +27,28 @@ import com.google.android.play.core.appupdate.AppUpdateManager import com.google.android.play.core.appupdate.AppUpdateOptions import com.google.android.play.core.install.model.AppUpdateType import com.google.android.play.core.install.model.UpdateAvailability +import com.google.android.play.core.install.model.InstallStatus +import com.google.android.play.core.install.InstallStateUpdatedListener import android.util.Log class MainActivity : ReactActivity() { - private val DAYS_FOR_FLEXIBLE_UPDATE = 7 + private val DAYS_FOR_FLEXIBLE_UPDATE = -1 + private val DAYS_FOR_IMMEDIATE_UPDATE = 14 private val TAG = "ArtsyApp" private lateinit var appUpdateManager: AppUpdateManager + private val updateResultLauncher = registerForActivityResult( + ActivityResultContracts.StartIntentSenderForResult() + ) { result -> + Log.d(TAG, "Update flow result: ${result.resultCode}") + if (result.resultCode != RESULT_OK) { + Log.d(TAG, "Update flow failed! Result code: ${result.resultCode}") + } + } + + private lateinit var installStateUpdatedListener: InstallStateUpdatedListener + /** * Returns the name of the main component registered from JavaScript. This is * used to schedule @@ -66,6 +80,49 @@ class MainActivity : ReactActivity() { appUpdateManager = AppUpdateManagerFactory.create(this) + installStateUpdatedListener = InstallStateUpdatedListener { state -> + Log.d(TAG, "Install state updated: ${state.installStatus()}") + when (state.installStatus()) { + InstallStatus.DOWNLOADED -> { + Log.d(TAG, "Update downloaded, notifying React Native") + notifyReactNativeUpdateDownloaded() + } + InstallStatus.INSTALLED -> { + Log.d(TAG, "Update installed successfully") + appUpdateManager.unregisterListener(installStateUpdatedListener) + } + InstallStatus.FAILED -> { + Log.d(TAG, "Update installation failed") + appUpdateManager.unregisterListener(installStateUpdatedListener) + } + } + } + + appUpdateManager.registerListener(installStateUpdatedListener) + + // Register this activity with the native module for RN bridge + ArtsyNativeModule.setMainActivity(this) + } + + override fun onResume() { + super.onResume() + + appUpdateManager + .appUpdateInfo + .addOnSuccessListener { appUpdateInfo -> + if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) { + Log.d(TAG, "onResume: Update downloaded, notifying React Native") + notifyReactNativeUpdateDownloaded() + } + } + } + + override fun onDestroy() { + super.onDestroy() + appUpdateManager.unregisterListener(installStateUpdatedListener) + } + + fun checkForAppUpdateFromRN() { checkForAppUpdate() } @@ -81,38 +138,62 @@ class MainActivity : ReactActivity() { appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> Log.d(TAG, "checkForAppUpdate: adding listener, appUpdateInfo: ${appUpdateInfo.toString()}") + val staleDays = appUpdateInfo.clientVersionStalenessDays() ?: -1 + Log.d(TAG, "checkForAppUpdate: conditions: \n" + "appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE: ${appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE}\n" + - "(appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE: ${(appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE}\n" + - "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)}" + "staleDays ($staleDays) >= DAYS_FOR_IMMEDIATE_UPDATE ($DAYS_FOR_IMMEDIATE_UPDATE): ${staleDays >= DAYS_FOR_IMMEDIATE_UPDATE}\n" + + "staleDays ($staleDays) >= DAYS_FOR_FLEXIBLE_UPDATE ($DAYS_FOR_FLEXIBLE_UPDATE): ${staleDays >= DAYS_FOR_FLEXIBLE_UPDATE}\n" + + "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)}\n" + + "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)}" ) - if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && - appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) { - Log.d(TAG, "appUpdateInfoTask.addOnSuccessListener: passed appUpdateInfo if statements") - // Start a flexible update - try { - Log.d(TAG, "appUpdateInfoTask.addOnSuccessListener: trying to start an update flow") - appUpdateManager.startUpdateFlowForResult( - appUpdateInfo, - registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> - Log.d(TAG, "startUpdateFlowForResult: getting result: ${result.toString()}") - // handle callback - if (result.resultCode != RESULT_OK) { - Log.d(TAG, "Update flow failed! Result code: ${result.resultCode}") - // If the update is canceled or fails, - // you can request to start the update again. - } - }, - AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build() - ) - } catch (e: IntentSender.SendIntentException) { - Log.d(TAG, "startUpdateFlowForResult: errored out with ${e.toString()}") - e.printStackTrace() + + if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) { + when { + staleDays >= DAYS_FOR_IMMEDIATE_UPDATE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) -> { + Log.d(TAG, "Starting immediate update (app is $staleDays days stale)") + startUpdateFlow(appUpdateInfo, AppUpdateType.IMMEDIATE) + } + staleDays >= DAYS_FOR_FLEXIBLE_UPDATE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) -> { + Log.d(TAG, "Starting flexible update (app is $staleDays days stale)") + startUpdateFlow(appUpdateInfo, AppUpdateType.FLEXIBLE) + } + else -> { + Log.d(TAG, "Update available but conditions not met for update prompt") + } } } } } + private fun startUpdateFlow(appUpdateInfo: AppUpdateInfo, updateType: Int) { + try { + val updateTypeString = if (updateType == AppUpdateType.IMMEDIATE) "immediate" else "flexible" + Log.d(TAG, "startUpdateFlow: Starting $updateTypeString update flow") + + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, + updateResultLauncher, + AppUpdateOptions.newBuilder(updateType).build() + ) + Log.d(TAG, "startUpdateFlow: $updateTypeString update flow started successfully") + } catch (e: IntentSender.SendIntentException) { + Log.e(TAG, "startUpdateFlow: Failed to start update flow", e) + e.printStackTrace() + } + } + + private fun notifyReactNativeUpdateDownloaded() { + // Store the update state and trigger event + ArtsyNativeModule.setUpdateDownloadedState(true) + ArtsyNativeModule.triggerUpdateDownloadedEvent() + Log.d(TAG, "Update downloaded state set and event triggered for React Native") + } + + fun completeAppUpdate() { + appUpdateManager.completeUpdate() + } + // Basic overriding this class required for braze integration: // https://www.braze.com/docs/developer_guide/platform_integration_guides/react_native/react_sdk_setup/#step-2-complete-native-setup override fun onNewIntent(intent: Intent) { diff --git a/babel.config.js b/babel.config.js index 31ecee15e1a..84ba35e345c 100644 --- a/babel.config.js +++ b/babel.config.js @@ -27,7 +27,7 @@ module.exports = (api) => { "react-native-reanimated/plugin", // has to be listed last according to the documentation. https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation/#babel-plugin ] - const prodPlugins = [["transform-remove-console", { exclude: ["error"] }], ...plugins] + const prodPlugins = [["transform-remove-console", { exclude: ["error", "warn"] }], ...plugins] const babelPlugins = isProd ? prodPlugins : plugins diff --git a/src/app/NativeModules/ArtsyNativeModule.tsx b/src/app/NativeModules/ArtsyNativeModule.tsx index bd6cbe4167a..68074933a0c 100644 --- a/src/app/NativeModules/ArtsyNativeModule.tsx +++ b/src/app/NativeModules/ArtsyNativeModule.tsx @@ -17,4 +17,18 @@ export const ArtsyNativeModule = { Platform.OS === "ios" ? NativeModules.ArtsyNativeModule.isBetaOrDev : (NativeModules.ArtsyNativeModule.getConstants().isBeta as boolean) || __DEV__, + checkForAppUpdate: + Platform.OS === "ios" + ? () => { + console.error("checkForAppUpdate is not supported on iOS") + } + : NativeModules.ArtsyNativeModule.checkForAppUpdate, + updateDownloaded: + Platform.OS === "ios" + ? false + : (NativeModules.ArtsyNativeModule.getConstants().updateDownloaded as boolean), + completeAppUpdate: + Platform.OS === "ios" + ? console.error("lockActivityScreenOrientation is unsupported on iOS") + : NativeModules.ArtsyNativeModule.completeAppUpdate, } diff --git a/src/app/Scenes/HomeView/HomeView.tsx b/src/app/Scenes/HomeView/HomeView.tsx index 3f78170c013..f66c7c01296 100644 --- a/src/app/Scenes/HomeView/HomeView.tsx +++ b/src/app/Scenes/HomeView/HomeView.tsx @@ -37,6 +37,7 @@ import { usePrefetch } from "app/utils/queryPrefetching" import { ExtractNodeType } from "app/utils/relayHelpers" import { requestPushNotificationsPermission } from "app/utils/requestPushNotificationsPermission" import { useMaybePromptForReview } from "app/utils/useMaybePromptForReview" +import { usePromptForUpdate } from "app/utils/usePromptForUpdate" import { memo, RefObject, Suspense, useCallback, useEffect, useRef, useState } from "react" import { FlatList, @@ -123,6 +124,7 @@ export const HomeView: React.FC = memo(() => { const tracking = useHomeViewTracking() useMaybePromptForReview({ contextModule: ContextModule.tabBar, contextOwnerType: OwnerType.home }) + usePromptForUpdate() const sections = extractNodes(data?.homeView.sectionsConnection) diff --git a/src/app/utils/promptForUpdate.ts b/src/app/utils/promptForUpdate.ts new file mode 100644 index 00000000000..e5a428d4570 --- /dev/null +++ b/src/app/utils/promptForUpdate.ts @@ -0,0 +1,22 @@ +import { ArtsyNativeModule } from "app/NativeModules/ArtsyNativeModule" + +export const promptForUpdate = () => { + console.warn("promptForUpdate called") + return ArtsyNativeModule.checkForAppUpdate() + ?.then((wasTriggered: boolean) => { + console.warn("App update check result:", wasTriggered) + if (!wasTriggered) { + return + } + }) + .catch((error: any) => { + console.warn("App update check failed: ", error) + }) +} + +// Function to complete app update +export const completeAppUpdate = () => { + return ArtsyNativeModule.completeAppUpdate()?.catch((error: any) => { + console.warn("Failed to complete app update:", error) + }) +} diff --git a/src/app/utils/usePromptForUpdate.tsx b/src/app/utils/usePromptForUpdate.tsx new file mode 100644 index 00000000000..2b2a579db5a --- /dev/null +++ b/src/app/utils/usePromptForUpdate.tsx @@ -0,0 +1,57 @@ +import { useToast } from "app/Components/Toast/toastHook" +import { ArtsyNativeModule } from "app/NativeModules/ArtsyNativeModule" +import { useEffect } from "react" +import { NativeEventEmitter, NativeModules, Platform } from "react-native" +import { completeAppUpdate, promptForUpdate } from "./promptForUpdate" + +/** + * This is used to check for app updates on every app launch. + * Updates are triggered based on app staleness (7+ days for flexible, 14+ days for immediate). + * Also monitors for downloaded updates and shows restart toast. + */ +export const usePromptForUpdate = () => { + const toast = useToast() + + useEffect(() => { + const eventEmitter = new NativeEventEmitter(NativeModules.ArtsyNativeModule) + console.warn("usePromptForUpdate effect running, Platform.OS:", Platform.OS) + // Only run on Android + if (Platform.OS !== "android") { + console.warn("Skipping update check - not Android") + return + } + + console.warn("Calling promptForUpdate") + setTimeout(() => { + // delay prompt until homescreen loads + promptForUpdate() + }, 10000) + + // Check if an update was already downloaded and show toast + if (ArtsyNativeModule.updateDownloaded) { + console.warn("Update already downloaded, showing toast") + showUpdateDownloadedToast() + } else { + console.warn("No update downloaded yet") + } + + // Listen for update download events + const eventListener = eventEmitter.addListener("onAppUpdateDownloaded", (event) => { + console.warn("Received update downloaded event:", event) + showUpdateDownloadedToast() + }) + + return () => { + eventListener.remove() + } + }, [ArtsyNativeModule.updateDownloaded]) + + const showUpdateDownloadedToast = () => { + toast.show("Update downloaded. Reload and apply the update.", "bottom", { + backgroundColor: "green100", + cta: "Reload the App", + onPress: completeAppUpdate, + duration: "superLong", + }) + } +} diff --git a/src/setupJest.tsx b/src/setupJest.tsx index c41d04444e7..89247a1341b 100644 --- a/src/setupJest.tsx +++ b/src/setupJest.tsx @@ -506,6 +506,9 @@ function getNativeModules(): OurNativeModules { getPushToken: jest.fn(), getRecentPushPayloads: jest.fn(), clearUserData: jest.fn(), + checkForAppUpdate: jest.fn(), + completeAppUpdate: jest.fn(), + updateDownloaded: false, }, } } From 6baca6a6c0475d82371bf02dda861046a4e810a6 Mon Sep 17 00:00:00 2001 From: Sultan Date: Mon, 25 Aug 2025 15:08:03 +0200 Subject: [PATCH 08/12] fix toast not showing on update download + delay check update prompt --- android/app/build.gradle | 5 ++--- .../java/net/artsy/app/ArtsyNativeModule.java | 6 +++--- .../src/main/java/net/artsy/app/MainActivity.kt | 15 +-------------- src/app/utils/promptForUpdate.ts | 1 - src/app/utils/usePromptForUpdate.tsx | 9 ++++----- 5 files changed, 10 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index fd0979b7b17..711af735b89 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -108,8 +108,8 @@ android { applicationId "net.artsy.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1674646332 - versionName "8.82.0" + versionCode 7 + versionName "1.0.0" vectorDrawables.useSupportLibrary = true @@ -224,7 +224,6 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'io.mockk:mockk:1.13.12' - if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { diff --git a/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java b/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java index 48232e101b1..b76f727766b 100644 --- a/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java +++ b/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java @@ -49,7 +49,7 @@ public static void triggerUpdateDownloadedEvent() { if (instance != null) { WritableMap params = Arguments.createMap(); params.putString("message", "Update downloaded successfully"); - instance.sendEvent("onAppUpdateDownloaded", params); + instance.sendEvent(params); Log.d(TAG, "Update downloaded event sent to React Native"); } else { Log.e(TAG, "ArtsyNativeModule instance is null, cannot send event"); @@ -103,10 +103,10 @@ public void run() { } } - private void sendEvent(String eventName, WritableMap params) { + private void sendEvent(WritableMap params) { getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(eventName, params); + .emit("onAppUpdateDownloaded", params); } @ReactMethod diff --git a/android/app/src/main/java/net/artsy/app/MainActivity.kt b/android/app/src/main/java/net/artsy/app/MainActivity.kt index b910fb7256e..c572124a53b 100644 --- a/android/app/src/main/java/net/artsy/app/MainActivity.kt +++ b/android/app/src/main/java/net/artsy/app/MainActivity.kt @@ -1,18 +1,11 @@ package net.artsy.app import expo.modules.ReactActivityDelegateWrapper -import android.graphics.Color -import android.os.Build import android.os.Bundle import android.content.pm.ActivityInfo import android.content.res.Configuration -import android.view.View -import android.view.WindowInsets -import android.view.WindowManager import android.content.Intent import android.content.IntentSender -import android.net.Uri -import androidx.annotation.Nullable import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.zoontek.rnbootsplash.RNBootSplash @@ -20,8 +13,8 @@ import com.zoontek.rnbootsplash.RNBootSplash import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate -import com.google.android.play.core.appupdate.AppUpdateManagerFactory; import androidx.activity.result.contract.ActivityResultContracts +import com.google.android.play.core.appupdate.AppUpdateManagerFactory import com.google.android.play.core.appupdate.AppUpdateInfo import com.google.android.play.core.appupdate.AppUpdateManager import com.google.android.play.core.appupdate.AppUpdateOptions @@ -34,7 +27,6 @@ import android.util.Log class MainActivity : ReactActivity() { private val DAYS_FOR_FLEXIBLE_UPDATE = -1 - private val DAYS_FOR_IMMEDIATE_UPDATE = 14 private val TAG = "ArtsyApp" private lateinit var appUpdateManager: AppUpdateManager @@ -142,7 +134,6 @@ class MainActivity : ReactActivity() { Log.d(TAG, "checkForAppUpdate: conditions: \n" + "appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE: ${appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE}\n" + - "staleDays ($staleDays) >= DAYS_FOR_IMMEDIATE_UPDATE ($DAYS_FOR_IMMEDIATE_UPDATE): ${staleDays >= DAYS_FOR_IMMEDIATE_UPDATE}\n" + "staleDays ($staleDays) >= DAYS_FOR_FLEXIBLE_UPDATE ($DAYS_FOR_FLEXIBLE_UPDATE): ${staleDays >= DAYS_FOR_FLEXIBLE_UPDATE}\n" + "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)}\n" + "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)}" @@ -150,10 +141,6 @@ class MainActivity : ReactActivity() { if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) { when { - staleDays >= DAYS_FOR_IMMEDIATE_UPDATE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) -> { - Log.d(TAG, "Starting immediate update (app is $staleDays days stale)") - startUpdateFlow(appUpdateInfo, AppUpdateType.IMMEDIATE) - } staleDays >= DAYS_FOR_FLEXIBLE_UPDATE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) -> { Log.d(TAG, "Starting flexible update (app is $staleDays days stale)") startUpdateFlow(appUpdateInfo, AppUpdateType.FLEXIBLE) diff --git a/src/app/utils/promptForUpdate.ts b/src/app/utils/promptForUpdate.ts index e5a428d4570..909433745bd 100644 --- a/src/app/utils/promptForUpdate.ts +++ b/src/app/utils/promptForUpdate.ts @@ -14,7 +14,6 @@ export const promptForUpdate = () => { }) } -// Function to complete app update export const completeAppUpdate = () => { return ArtsyNativeModule.completeAppUpdate()?.catch((error: any) => { console.warn("Failed to complete app update:", error) diff --git a/src/app/utils/usePromptForUpdate.tsx b/src/app/utils/usePromptForUpdate.tsx index 2b2a579db5a..611c01c94e4 100644 --- a/src/app/utils/usePromptForUpdate.tsx +++ b/src/app/utils/usePromptForUpdate.tsx @@ -6,7 +6,6 @@ import { completeAppUpdate, promptForUpdate } from "./promptForUpdate" /** * This is used to check for app updates on every app launch. - * Updates are triggered based on app staleness (7+ days for flexible, 14+ days for immediate). * Also monitors for downloaded updates and shows restart toast. */ export const usePromptForUpdate = () => { @@ -25,7 +24,7 @@ export const usePromptForUpdate = () => { setTimeout(() => { // delay prompt until homescreen loads promptForUpdate() - }, 10000) + }, 12000) // Check if an update was already downloaded and show toast if (ArtsyNativeModule.updateDownloaded) { @@ -44,12 +43,12 @@ export const usePromptForUpdate = () => { return () => { eventListener.remove() } - }, [ArtsyNativeModule.updateDownloaded]) + }, []) const showUpdateDownloadedToast = () => { - toast.show("Update downloaded. Reload and apply the update.", "bottom", { + toast.show("Update downloaded. Reload to apply the update.", "bottom", { backgroundColor: "green100", - cta: "Reload the App", + cta: "Reload", onPress: completeAppUpdate, duration: "superLong", }) From a2a015b6d8e719c14b0ce1f6b95636f8e4a356c6 Mon Sep 17 00:00:00 2001 From: Sultan Date: Mon, 25 Aug 2025 15:09:19 +0200 Subject: [PATCH 09/12] revert babel changes --- babel.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/babel.config.js b/babel.config.js index 84ba35e345c..31ecee15e1a 100644 --- a/babel.config.js +++ b/babel.config.js @@ -27,7 +27,7 @@ module.exports = (api) => { "react-native-reanimated/plugin", // has to be listed last according to the documentation. https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation/#babel-plugin ] - const prodPlugins = [["transform-remove-console", { exclude: ["error", "warn"] }], ...plugins] + const prodPlugins = [["transform-remove-console", { exclude: ["error"] }], ...plugins] const babelPlugins = isProd ? prodPlugins : plugins From d48442c3850379e7366b62ed40e6556aeea6ed27 Mon Sep 17 00:00:00 2001 From: Sultan Date: Mon, 25 Aug 2025 15:21:39 +0200 Subject: [PATCH 10/12] clean up logs + refactor --- .../java/net/artsy/app/ArtsyNativeModule.java | 9 ------ .../main/java/net/artsy/app/MainActivity.kt | 30 ++----------------- src/app/utils/promptForUpdate.ts | 21 ------------- src/app/utils/usePromptForUpdate.tsx | 11 +++++-- 4 files changed, 12 insertions(+), 59 deletions(-) delete mode 100644 src/app/utils/promptForUpdate.ts diff --git a/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java b/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java index b76f727766b..21047afa927 100644 --- a/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java +++ b/android/app/src/main/java/net/artsy/app/ArtsyNativeModule.java @@ -3,7 +3,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Build; -import android.util.Log; import androidx.annotation.NonNull; @@ -15,7 +14,6 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.Arguments; - import java.io.File; import java.util.HashMap; import java.util.Map; @@ -50,9 +48,6 @@ public static void triggerUpdateDownloadedEvent() { WritableMap params = Arguments.createMap(); params.putString("message", "Update downloaded successfully"); instance.sendEvent(params); - Log.d(TAG, "Update downloaded event sent to React Native"); - } else { - Log.e(TAG, "ArtsyNativeModule instance is null, cannot send event"); } } @@ -82,9 +77,7 @@ public Map getConstants() { @ReactMethod public void checkForAppUpdate(Promise promise) { - Log.d(TAG, "checkForAppUpdate called from React Native"); if (mainActivity != null) { - Log.d(TAG, "MainActivity reference found, calling update check"); mainActivity.runOnUiThread(new Runnable() { @Override public void run() { @@ -92,13 +85,11 @@ public void run() { mainActivity.checkForAppUpdateFromRN(); promise.resolve(true); } catch (Exception e) { - Log.e(TAG, "Error checking for update", e); promise.reject("UPDATE_CHECK_FAILED", "Failed to check for app update", e); } } }); } else { - Log.e(TAG, "MainActivity reference is null"); promise.reject("NO_MAIN_ACTIVITY", "MainActivity reference not available"); } } diff --git a/android/app/src/main/java/net/artsy/app/MainActivity.kt b/android/app/src/main/java/net/artsy/app/MainActivity.kt index c572124a53b..5d03354b8e0 100644 --- a/android/app/src/main/java/net/artsy/app/MainActivity.kt +++ b/android/app/src/main/java/net/artsy/app/MainActivity.kt @@ -33,7 +33,6 @@ class MainActivity : ReactActivity() { private val updateResultLauncher = registerForActivityResult( ActivityResultContracts.StartIntentSenderForResult() ) { result -> - Log.d(TAG, "Update flow result: ${result.resultCode}") if (result.resultCode != RESULT_OK) { Log.d(TAG, "Update flow failed! Result code: ${result.resultCode}") } @@ -73,18 +72,14 @@ class MainActivity : ReactActivity() { appUpdateManager = AppUpdateManagerFactory.create(this) installStateUpdatedListener = InstallStateUpdatedListener { state -> - Log.d(TAG, "Install state updated: ${state.installStatus()}") when (state.installStatus()) { InstallStatus.DOWNLOADED -> { - Log.d(TAG, "Update downloaded, notifying React Native") notifyReactNativeUpdateDownloaded() } InstallStatus.INSTALLED -> { - Log.d(TAG, "Update installed successfully") appUpdateManager.unregisterListener(installStateUpdatedListener) } InstallStatus.FAILED -> { - Log.d(TAG, "Update installation failed") appUpdateManager.unregisterListener(installStateUpdatedListener) } } @@ -103,7 +98,6 @@ class MainActivity : ReactActivity() { .appUpdateInfo .addOnSuccessListener { appUpdateInfo -> if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) { - Log.d(TAG, "onResume: Update downloaded, notifying React Native") notifyReactNativeUpdateDownloaded() } } @@ -119,8 +113,6 @@ class MainActivity : ReactActivity() { } private fun checkForAppUpdate() { - Log.d(TAG, "here we go!") - Log.d(TAG, "checkForAppUpdate: started checking for update!") val appUpdateInfoTask = appUpdateManager.appUpdateInfo appUpdateInfoTask.addOnFailureListener { appUpdateInfo -> @@ -128,22 +120,12 @@ class MainActivity : ReactActivity() { } appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> - Log.d(TAG, "checkForAppUpdate: adding listener, appUpdateInfo: ${appUpdateInfo.toString()}") - val staleDays = appUpdateInfo.clientVersionStalenessDays() ?: -1 - Log.d(TAG, "checkForAppUpdate: conditions: \n" + - "appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE: ${appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE}\n" + - "staleDays ($staleDays) >= DAYS_FOR_FLEXIBLE_UPDATE ($DAYS_FOR_FLEXIBLE_UPDATE): ${staleDays >= DAYS_FOR_FLEXIBLE_UPDATE}\n" + - "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)}\n" + - "appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE): ${appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)}" - ) - if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) { when { staleDays >= DAYS_FOR_FLEXIBLE_UPDATE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) -> { - Log.d(TAG, "Starting flexible update (app is $staleDays days stale)") - startUpdateFlow(appUpdateInfo, AppUpdateType.FLEXIBLE) + startUpdateFlow(appUpdateInfo) } else -> { Log.d(TAG, "Update available but conditions not met for update prompt") @@ -153,19 +135,14 @@ class MainActivity : ReactActivity() { } } - private fun startUpdateFlow(appUpdateInfo: AppUpdateInfo, updateType: Int) { + private fun startUpdateFlow(appUpdateInfo: AppUpdateInfo,) { try { - val updateTypeString = if (updateType == AppUpdateType.IMMEDIATE) "immediate" else "flexible" - Log.d(TAG, "startUpdateFlow: Starting $updateTypeString update flow") - appUpdateManager.startUpdateFlowForResult( appUpdateInfo, updateResultLauncher, - AppUpdateOptions.newBuilder(updateType).build() + AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build() ) - Log.d(TAG, "startUpdateFlow: $updateTypeString update flow started successfully") } catch (e: IntentSender.SendIntentException) { - Log.e(TAG, "startUpdateFlow: Failed to start update flow", e) e.printStackTrace() } } @@ -174,7 +151,6 @@ class MainActivity : ReactActivity() { // Store the update state and trigger event ArtsyNativeModule.setUpdateDownloadedState(true) ArtsyNativeModule.triggerUpdateDownloadedEvent() - Log.d(TAG, "Update downloaded state set and event triggered for React Native") } fun completeAppUpdate() { diff --git a/src/app/utils/promptForUpdate.ts b/src/app/utils/promptForUpdate.ts deleted file mode 100644 index 909433745bd..00000000000 --- a/src/app/utils/promptForUpdate.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ArtsyNativeModule } from "app/NativeModules/ArtsyNativeModule" - -export const promptForUpdate = () => { - console.warn("promptForUpdate called") - return ArtsyNativeModule.checkForAppUpdate() - ?.then((wasTriggered: boolean) => { - console.warn("App update check result:", wasTriggered) - if (!wasTriggered) { - return - } - }) - .catch((error: any) => { - console.warn("App update check failed: ", error) - }) -} - -export const completeAppUpdate = () => { - return ArtsyNativeModule.completeAppUpdate()?.catch((error: any) => { - console.warn("Failed to complete app update:", error) - }) -} diff --git a/src/app/utils/usePromptForUpdate.tsx b/src/app/utils/usePromptForUpdate.tsx index 611c01c94e4..7d2db1a6c9b 100644 --- a/src/app/utils/usePromptForUpdate.tsx +++ b/src/app/utils/usePromptForUpdate.tsx @@ -2,7 +2,6 @@ import { useToast } from "app/Components/Toast/toastHook" import { ArtsyNativeModule } from "app/NativeModules/ArtsyNativeModule" import { useEffect } from "react" import { NativeEventEmitter, NativeModules, Platform } from "react-native" -import { completeAppUpdate, promptForUpdate } from "./promptForUpdate" /** * This is used to check for app updates on every app launch. @@ -23,7 +22,7 @@ export const usePromptForUpdate = () => { console.warn("Calling promptForUpdate") setTimeout(() => { // delay prompt until homescreen loads - promptForUpdate() + ArtsyNativeModule.checkForAppUpdate() }, 12000) // Check if an update was already downloaded and show toast @@ -45,6 +44,14 @@ export const usePromptForUpdate = () => { } }, []) + const completeAppUpdate = async () => { + try { + await ArtsyNativeModule.completeAppUpdate() + } catch (error: any) { + console.warn("Failed to complete app update:", error) + } + } + const showUpdateDownloadedToast = () => { toast.show("Update downloaded. Reload to apply the update.", "bottom", { backgroundColor: "green100", From 03052cd992018b9ac9bb95f75e2eec859f3958b1 Mon Sep 17 00:00:00 2001 From: Sultan Date: Mon, 25 Aug 2025 16:12:36 +0200 Subject: [PATCH 11/12] remove and update logs --- src/app/utils/usePromptForUpdate.tsx | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/app/utils/usePromptForUpdate.tsx b/src/app/utils/usePromptForUpdate.tsx index 7d2db1a6c9b..f7ed4326962 100644 --- a/src/app/utils/usePromptForUpdate.tsx +++ b/src/app/utils/usePromptForUpdate.tsx @@ -11,15 +11,13 @@ export const usePromptForUpdate = () => { const toast = useToast() useEffect(() => { - const eventEmitter = new NativeEventEmitter(NativeModules.ArtsyNativeModule) - console.warn("usePromptForUpdate effect running, Platform.OS:", Platform.OS) // Only run on Android if (Platform.OS !== "android") { - console.warn("Skipping update check - not Android") return } - console.warn("Calling promptForUpdate") + const eventEmitter = new NativeEventEmitter(NativeModules.ArtsyNativeModule) + setTimeout(() => { // delay prompt until homescreen loads ArtsyNativeModule.checkForAppUpdate() @@ -27,15 +25,11 @@ export const usePromptForUpdate = () => { // Check if an update was already downloaded and show toast if (ArtsyNativeModule.updateDownloaded) { - console.warn("Update already downloaded, showing toast") showUpdateDownloadedToast() - } else { - console.warn("No update downloaded yet") } // Listen for update download events - const eventListener = eventEmitter.addListener("onAppUpdateDownloaded", (event) => { - console.warn("Received update downloaded event:", event) + const eventListener = eventEmitter.addListener("onAppUpdateDownloaded", () => { showUpdateDownloadedToast() }) @@ -48,7 +42,7 @@ export const usePromptForUpdate = () => { try { await ArtsyNativeModule.completeAppUpdate() } catch (error: any) { - console.warn("Failed to complete app update:", error) + console.log("Failed to complete app update:", error) } } From a0650d93ce957cd12397a0f515483dbfeb50bf44 Mon Sep 17 00:00:00 2001 From: Sultan Date: Tue, 26 Aug 2025 16:12:14 +0200 Subject: [PATCH 12/12] update completeAppUpdate log message on iOS --- src/app/NativeModules/ArtsyNativeModule.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/NativeModules/ArtsyNativeModule.tsx b/src/app/NativeModules/ArtsyNativeModule.tsx index 68074933a0c..048d57dadef 100644 --- a/src/app/NativeModules/ArtsyNativeModule.tsx +++ b/src/app/NativeModules/ArtsyNativeModule.tsx @@ -29,6 +29,6 @@ export const ArtsyNativeModule = { : (NativeModules.ArtsyNativeModule.getConstants().updateDownloaded as boolean), completeAppUpdate: Platform.OS === "ios" - ? console.error("lockActivityScreenOrientation is unsupported on iOS") + ? console.error("completeAppUpdate is unsupported on iOS") : NativeModules.ArtsyNativeModule.completeAppUpdate, }