about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorAnsh <anshnanda10@gmail.com>2023-06-02 13:27:59 -0700
committerGitHub <noreply@github.com>2023-06-02 13:27:59 -0700
commitba4bb46c3fc3d670e565c69bdf71dbb4510b51f0 (patch)
treecb48529f6fcf6553d0b8697aeb66d405d4124166 /src
parentad4eaf5ed2e35233ecc7b29ddcafc52c2001dcd1 (diff)
downloadvoidsky-ba4bb46c3fc3d670e565c69bdf71dbb4510b51f0.tar.zst
[APP-107] OTA updates (#587)
* add 1000ms fallbackToCacheTimeout

* add listener via useOTAUpdate hook and show modal if update is available

* finish expo-updates setup

* setup useOTAUpdate hook

* add 1000ms fallbackToCacheTimeout

* add listener via useOTAUpdate hook and show modal if update is available

* finish expo-updates setup

* setup useOTAUpdate hook

* add OTA updates

* Update build.md

* temporarily disable ota updates

* refactor useOTAUpdate code
Diffstat (limited to 'src')
-rw-r--r--src/lib/app-info.ts3
-rw-r--r--src/lib/hooks/useOTAUpdate.ts74
-rw-r--r--src/view/screens/Settings.tsx2
-rw-r--r--src/view/shell/index.tsx2
4 files changed, 80 insertions, 1 deletions
diff --git a/src/lib/app-info.ts b/src/lib/app-info.ts
index a365e7e9f..3f026d3fe 100644
--- a/src/lib/app-info.ts
+++ b/src/lib/app-info.ts
@@ -1,2 +1,5 @@
 import VersionNumber from 'react-native-version-number'
+import * as Updates from 'expo-updates'
+export const updateChannel = Updates.channel
+
 export const appVersion = `${VersionNumber.appVersion} (${VersionNumber.buildVersion})`
diff --git a/src/lib/hooks/useOTAUpdate.ts b/src/lib/hooks/useOTAUpdate.ts
new file mode 100644
index 000000000..ae6035223
--- /dev/null
+++ b/src/lib/hooks/useOTAUpdate.ts
@@ -0,0 +1,74 @@
+import * as Updates from 'expo-updates'
+import {useCallback, useEffect} from 'react'
+import {AppState} from 'react-native'
+import {useStores} from 'state/index'
+
+export function useOTAUpdate() {
+  const store = useStores()
+
+  // HELPER FUNCTIONS
+  const showUpdatePopup = useCallback(() => {
+    store.shell.openModal({
+      name: 'confirm',
+      title: 'Update Available',
+      message:
+        'A new version of the app is available. Please update to continue using the app.',
+      onPressConfirm: async () => {
+        Updates.reloadAsync().catch(err => {
+          throw err
+        })
+      },
+    })
+  }, [store.shell])
+  const checkForUpdate = useCallback(async () => {
+    store.log.debug('useOTAUpdate: Checking for update...')
+    try {
+      // Check if new OTA update is available
+      const update = await Updates.checkForUpdateAsync()
+      // If updates aren't available stop the function execution
+      if (!update.isAvailable) {
+        return
+      }
+      // Otherwise fetch the update in the background, so even if the user rejects switching to latest version it will be done automatically on next relaunch.
+      await Updates.fetchUpdateAsync()
+      // show a popup modal
+      showUpdatePopup()
+    } catch (e) {
+      console.error('useOTAUpdate: Error while checking for update', e)
+      store.log.error('useOTAUpdate: Error while checking for update', e)
+    }
+  }, [showUpdatePopup, store.log])
+  const updateEventListener = useCallback(
+    (event: Updates.UpdateEvent) => {
+      store.log.debug('useOTAUpdate: Listening for update...')
+      if (event.type === Updates.UpdateEventType.ERROR) {
+        throw new Error(event.message)
+      } else if (event.type === Updates.UpdateEventType.NO_UPDATE_AVAILABLE) {
+        // Handle no update available
+        // do nothing
+      } else if (event.type === Updates.UpdateEventType.UPDATE_AVAILABLE) {
+        // Handle update available
+        // open modal, ask for user confirmation, and reload the app
+        showUpdatePopup()
+      }
+    },
+    [showUpdatePopup, store.log],
+  )
+
+  useEffect(() => {
+    // ADD EVENT LISTENERS
+    const updateEventSubscription = Updates.addListener(updateEventListener)
+    const appStateSubscription = AppState.addEventListener('change', state => {
+      if (state === 'active' && !__DEV__) {
+        checkForUpdate()
+      }
+    })
+
+    // REMOVE EVENT LISTENERS (CLEANUP)
+    return () => {
+      updateEventSubscription.remove()
+      appStateSubscription.remove()
+    }
+  }, []) // eslint-disable-line react-hooks/exhaustive-deps
+  // disable exhaustive deps because we don't want to run this effect again
+}
diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx
index 798893855..52be35a48 100644
--- a/src/view/screens/Settings.tsx
+++ b/src/view/screens/Settings.tsx
@@ -445,7 +445,7 @@ export const SettingsScreen = withAuthRequired(
             </Link>
           ) : null}
           <Text type="sm" style={[styles.buildInfo, pal.textLight]}>
-            Build version {AppInfo.appVersion}
+            Build version {AppInfo.appVersion} {AppInfo.updateChannel}
           </Text>
           <View style={s.footerSpacer} />
         </ScrollView>
diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx
index a6066b25f..36f7442dc 100644
--- a/src/view/shell/index.tsx
+++ b/src/view/shell/index.tsx
@@ -18,9 +18,11 @@ import {RoutesContainer, TabsNavigator} from '../../Navigation'
 import {isStateAtTabRoot} from 'lib/routes/helpers'
 import {isAndroid} from 'platform/detection'
 import {SafeAreaProvider} from 'react-native-safe-area-context'
+import {useOTAUpdate} from 'lib/hooks/useOTAUpdate'
 
 const ShellInner = observer(() => {
   const store = useStores()
+  useOTAUpdate() // this hook polls for OTA updates every few seconds
   const winDim = useWindowDimensions()
   const safeAreaInsets = useSafeAreaInsets()
   const containerPadding = React.useMemo(