diff --git a/assets/translations/en.json b/assets/translations/en.json
index 6849ee51..7c866281 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -175,6 +175,8 @@
"navigate": "Navigate",
"newItems": "New item",
"newItems_plural": "New items",
+ "newVersionTitle": "A new version is on the store!",
+ "newVersionBody": "A new version of PoliTO Students ({{newVersion}}) is available.\n\nUpdate it from the <0>store0> to access all the new features and avoid malfunctions.",
"next": "Next",
"noInternet": "You are offline, some features may not be available",
"noValue": "No value",
diff --git a/assets/translations/it.json b/assets/translations/it.json
index 83180785..2183e521 100644
--- a/assets/translations/it.json
+++ b/assets/translations/it.json
@@ -175,6 +175,8 @@
"navigate": "Naviga",
"newItems": "Nuovo elemento",
"newItems_plural": "Nuovi elementi",
+ "newVersionTitle": "Una nuova versione è sullo store!",
+ "newVersionBody": "È disponibile una nuova versione di PoliTO Students ({{newVersion}}).\n\nAggiornala dallo <0>store0> per avere a disposizione tutte le nuove funzionalità e non incorrere in malfunzionamenti.",
"next": "Avanti",
"noInternet": "Sei offline, alcune funzionalità potrebbero non essere disponibili",
"noValue": "Nessun valore",
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 6777661e..cf41e2e3 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -546,6 +546,8 @@ PODS:
- React-Core
- react-native-safe-area-context (4.7.4):
- React-Core
+ - react-native-version-check (3.4.7):
+ - React-Core
- react-native-video (5.2.1):
- React-Core
- react-native-video/Video (= 5.2.1)
@@ -801,6 +803,7 @@ DEPENDENCIES:
- react-native-pdf (from `../node_modules/react-native-pdf`)
- react-native-render-html (from `../node_modules/react-native-render-html`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
+ - react-native-version-check (from `../node_modules/react-native-version-check`)
- react-native-video (from `../node_modules/react-native-video`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
@@ -946,6 +949,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-render-html"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
+ react-native-version-check:
+ :path: "../node_modules/react-native-version-check"
react-native-video:
:path: "../node_modules/react-native-video"
React-NativeModulesApple:
@@ -1083,6 +1088,7 @@ SPEC CHECKSUMS:
react-native-pdf: b4ca3d37a9a86d9165287741c8b2ef4d8940c00e
react-native-render-html: 984dfe2294163d04bf5fe25d7c9f122e60e05ebe
react-native-safe-area-context: 2cd91d532de12acdb0a9cbc8d43ac72a8e4c897c
+ react-native-version-check: 6cd36aad4e30b8e3216747e3b4ddeb09e0647af8
react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253
React-NativeModulesApple: 83cd035f454ac54f4f72ce7b01c34054fa6d60f8
React-perflogger: de3900e86d8a4d896999b08e0b130ad8b83ebf11
diff --git a/package-lock.json b/package-lock.json
index 80dadfa2..09af0dcd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -82,6 +82,7 @@
"react-native-screens": "^3.21.1",
"react-native-svg": "^15.2.0",
"react-native-tab-view": "^3.5.2",
+ "react-native-version-check": "^3.4.7",
"react-native-video": "^5.2.1",
"react-string-replace": "^1.1.0",
"react-usestateref": "^1.0.8",
@@ -111,6 +112,7 @@
"@types/react-native-dotenv": "^0.2.0",
"@types/react-native-html-to-pdf": "^0.8.1",
"@types/react-native-pdf-lib": "^0.2.1",
+ "@types/react-native-version-check": "^3.4.8",
"@types/react-native-video": "^5.0.14",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.54.1",
@@ -6283,6 +6285,16 @@
"version": "16.18.64",
"license": "MIT"
},
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.11",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
+ "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "form-data": "^4.0.0"
+ }
+ },
"node_modules/@types/normalize-package-data": {
"version": "2.4.4",
"dev": true,
@@ -6330,6 +6342,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/react-native-version-check": {
+ "version": "3.4.8",
+ "resolved": "https://registry.npmjs.org/@types/react-native-version-check/-/react-native-version-check-3.4.8.tgz",
+ "integrity": "sha512-SJEIGHRtYFvOusvT9F3xZVu08zaVES8gkmYOtw2f/75mEsucOMALr81zMEMyINCz2DczmIXkNxUZu1H/LWasWw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node-fetch": "*"
+ }
+ },
"node_modules/@types/react-native-video": {
"version": "5.0.18",
"dev": true,
@@ -6972,6 +6993,12 @@
"has-symbols": "^1.0.3"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"dev": true,
@@ -7394,13 +7421,19 @@
}
},
"node_modules/call-bind": {
- "version": "1.0.5",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dev": true,
- "license": "MIT",
"dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.1",
- "set-function-length": "^1.1.1"
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -7803,6 +7836,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/command-exists": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
@@ -8717,16 +8762,20 @@
}
},
"node_modules/define-data-property": {
- "version": "1.1.1",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "get-intrinsic": "^1.2.1",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.0"
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-properties": {
@@ -8745,6 +8794,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/denodeify": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz",
@@ -9110,6 +9168,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-iterator-helpers": {
"version": "1.0.15",
"dev": true,
@@ -10166,6 +10245,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dev": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -10270,15 +10363,20 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.2",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
+ "engines": {
+ "node": ">= 0.4"
+ },
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -10692,11 +10790,12 @@
}
},
"node_modules/has-property-descriptors": {
- "version": "1.0.1",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "get-intrinsic": "^1.2.2"
+ "es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -13676,7 +13775,6 @@
},
"node_modules/lodash.isfunction": {
"version": "3.0.9",
- "dev": true,
"license": "MIT"
},
"node_modules/lodash.ismatch": {
@@ -13684,6 +13782,11 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lodash.isnil": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
+ "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="
+ },
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"dev": true,
@@ -13704,6 +13807,11 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lodash.pick": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
+ "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q=="
+ },
"node_modules/lodash.snakecase": {
"version": "4.1.1",
"dev": true,
@@ -16600,6 +16708,28 @@
"react-native-pager-view": "*"
}
},
+ "node_modules/react-native-version-check": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/react-native-version-check/-/react-native-version-check-3.4.7.tgz",
+ "integrity": "sha512-fkUFaKTiT9DcQSigyTTFcRpClehQlo7MBF1odmX617oFr7zOceUGx6A3bm2GjnfECBsQZoX3WyEBbtdRQlqi2Q==",
+ "dependencies": {
+ "lodash.isfunction": "^3.0.9",
+ "lodash.isnil": "^4.0.0",
+ "lodash.pick": "^4.4.0",
+ "semver": "^6.1.1"
+ },
+ "peerDependencies": {
+ "react-native": ">=0.48.0"
+ }
+ },
+ "node_modules/react-native-version-check/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
"node_modules/react-native-video": {
"version": "5.2.1",
"license": "MIT",
@@ -17410,14 +17540,17 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/set-function-length": {
- "version": "1.1.1",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "define-data-property": "^1.1.1",
- "get-intrinsic": "^1.2.1",
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.0"
+ "has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
diff --git a/package.json b/package.json
index 98a6b8d8..256b9a56 100644
--- a/package.json
+++ b/package.json
@@ -92,6 +92,7 @@
"react-native-screens": "^3.21.1",
"react-native-svg": "^15.2.0",
"react-native-tab-view": "^3.5.2",
+ "react-native-version-check": "^3.4.7",
"react-native-video": "^5.2.1",
"react-string-replace": "^1.1.0",
"react-usestateref": "^1.0.8",
@@ -121,6 +122,7 @@
"@types/react-native-dotenv": "^0.2.0",
"@types/react-native-html-to-pdf": "^0.8.1",
"@types/react-native-pdf-lib": "^0.2.1",
+ "@types/react-native-version-check": "^3.4.8",
"@types/react-native-video": "^5.0.14",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.54.1",
diff --git a/src/core/components/AppContent.tsx b/src/core/components/AppContent.tsx
index 502c0d0e..5fa8b4ce 100644
--- a/src/core/components/AppContent.tsx
+++ b/src/core/components/AppContent.tsx
@@ -1,11 +1,16 @@
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { useApiContext } from '../contexts/ApiContext';
import { usePreferencesContext } from '../contexts/PreferencesContext';
+import { useSplashContext } from '../contexts/SplashContext';
+import { useBottomModal } from '../hooks/useBottomModal';
+import { useCheckForUpdate } from '../hooks/useCheckForUpdate';
import { MigrationService } from '../migrations/MigrationService';
+import { BottomModal } from './BottomModal';
import { GuestNavigator } from './GuestNavigator';
+import { NewVersionModal } from './NewVersionModal';
import { RootNavigator } from './RootNavigator';
export const AppContent = () => {
@@ -13,6 +18,40 @@ export const AppContent = () => {
const preferences = usePreferencesContext();
const queryClient = useQueryClient();
+ const { isSplashLoaded } = useSplashContext();
+
+ const {
+ close: closeModal,
+ open: showModal,
+ modal: bottomModal,
+ } = useBottomModal();
+ const { needsToUpdate, latestAppVersion, storeUrl } = useCheckForUpdate();
+ const [versionModalVisible, setVersionModalVisible] = useState<
+ boolean | undefined
+ >();
+
+ useEffect(() => {
+ if (!isSplashLoaded || needsToUpdate === undefined) return;
+ if (needsToUpdate === false) {
+ setVersionModalVisible(false);
+ return;
+ }
+ setVersionModalVisible(true);
+ showModal(
+ ,
+ );
+ }, [
+ needsToUpdate,
+ isSplashLoaded,
+ closeModal,
+ showModal,
+ latestAppVersion,
+ storeUrl,
+ ]);
useEffect(() => {
MigrationService.migrateIfNeeded(preferences, queryClient);
@@ -20,5 +59,18 @@ export const AppContent = () => {
if (MigrationService.needsMigration(preferences)) return null;
- return isLogged ? : ;
+ return (
+ <>
+ setVersionModalVisible(false)}
+ />
+ {isLogged ? (
+
+ ) : (
+
+ )}
+ >
+ );
};
diff --git a/src/core/components/BottomModal.tsx b/src/core/components/BottomModal.tsx
index d960ca2a..489018b7 100644
--- a/src/core/components/BottomModal.tsx
+++ b/src/core/components/BottomModal.tsx
@@ -6,6 +6,7 @@ export type BottomModalProps = PropsWithChildren<{
visible: boolean;
onClose?: () => void;
dismissable?: boolean;
+ onModalHide?: () => void;
}>;
export const BottomModal = ({
@@ -13,6 +14,7 @@ export const BottomModal = ({
visible,
onClose,
dismissable,
+ onModalHide,
}: BottomModalProps) => {
const handleCloseModal = () => {
dismissable && onClose?.();
@@ -23,6 +25,7 @@ export const BottomModal = ({
return (
void;
+ newVersion: string;
+ storeUrl: string;
+};
+
+export const NewVersionModal = ({ close, newVersion, storeUrl }: Props) => {
+ const styles = useStylesheet(createStyles);
+ const { t } = useTranslation();
+ return (
+
+
+
+ Linking.openURL(storeUrl)}
+ style={styles.version}
+ />,
+ ]}
+ />
+
+
+
+ );
+};
+
+const createStyles = ({ spacing }: Theme) =>
+ StyleSheet.create({
+ content: {
+ padding: spacing[4],
+ },
+ text: {
+ marginHorizontal: spacing[4],
+ marginBottom: spacing[10],
+ },
+ version: {
+ textDecorationLine: 'underline',
+ },
+ });
diff --git a/src/core/components/RootNavigator.tsx b/src/core/components/RootNavigator.tsx
index 5202dded..850dd540 100644
--- a/src/core/components/RootNavigator.tsx
+++ b/src/core/components/RootNavigator.tsx
@@ -16,8 +16,6 @@ import { useTheme } from '@lib/ui/hooks/useTheme';
import { Theme } from '@lib/ui/types/Theme';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { TimingKeyboardAnimationConfig } from '@react-navigation/bottom-tabs/src/types';
-import { useNavigation } from '@react-navigation/native';
-import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AgendaNavigator } from '../../features/agenda/components/AgendaNavigator';
import { PlacesNavigator } from '../../features/places/components/PlacesNavigator';
@@ -28,27 +26,32 @@ import { UserNavigator } from '../../features/user/components/UserNavigator';
import { tabBarStyle } from '../../utils/tab-bar';
import { usePreferencesContext } from '../contexts/PreferencesContext';
import { useInitFirebaseMessaging } from '../hooks/messaging';
+import { useModalManager } from '../hooks/useModalManager';
import { useNotifications } from '../hooks/useNotifications';
import { useGetSites } from '../queries/placesHooks';
-import { useGetModalMessages, useGetStudent } from '../queries/studentHooks';
-import { ONBOARDING_STEPS } from '../screens/OnboardingModal';
+import { useGetStudent } from '../queries/studentHooks';
import { RootParamList } from '../types/navigation';
import { HeaderLogo } from './HeaderLogo';
import { TranslucentView } from './TranslucentView';
const TabNavigator = createBottomTabNavigator();
-export const RootNavigator = () => {
+export const RootNavigator = ({
+ versionModalIsOpen,
+}: {
+ versionModalIsOpen?: boolean;
+}) => {
const { t } = useTranslation();
const { colors } = useTheme();
const styles = useStylesheet(createStyles);
const { data: student } = useGetStudent();
- const { onboardingStep, updatePreference } = usePreferencesContext();
- const navigation = useNavigation>();
+ const { updatePreference } = usePreferencesContext();
const { getUnreadsCount } = useNotifications();
const campus = useGetCurrentCampus();
const { data: sites } = useGetSites();
+ useModalManager(versionModalIsOpen);
+
useEffect(() => {
if (student?.smartCardPicture) {
FastImage.preload([
@@ -67,28 +70,6 @@ export const RootNavigator = () => {
}
}, [campus, sites?.data, student, updatePreference]);
- const { data: messages } = useGetModalMessages();
-
- useEffect(() => {
- if (onboardingStep && onboardingStep >= ONBOARDING_STEPS - 1) return;
- navigation.navigate('TeachingTab', {
- screen: 'OnboardingModal',
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- useEffect(() => {
- if (!onboardingStep || onboardingStep < ONBOARDING_STEPS - 1) {
- return;
- }
-
- if (!messages || messages.length === 0) return;
- navigation.navigate('TeachingTab', {
- screen: 'MessagesModal',
- initial: false,
- });
- }, [messages, navigation, onboardingStep]);
-
const tabBarIconSize = 20;
const instantAnimation = {
diff --git a/src/core/contexts/SplashContext.ts b/src/core/contexts/SplashContext.ts
index cb9bdb69..319cb600 100644
--- a/src/core/contexts/SplashContext.ts
+++ b/src/core/contexts/SplashContext.ts
@@ -3,6 +3,7 @@ import { Dispatch, SetStateAction, createContext, useContext } from 'react';
export interface SplashContextProps {
isAppLoaded: boolean;
setIsAppLoaded: Dispatch>;
+ isSplashLoaded: boolean;
}
export const SplashContext = createContext(
diff --git a/src/core/hooks/useCheckForUpdate.ts b/src/core/hooks/useCheckForUpdate.ts
new file mode 100644
index 00000000..2a2d9a71
--- /dev/null
+++ b/src/core/hooks/useCheckForUpdate.ts
@@ -0,0 +1,52 @@
+import { useEffect, useState } from 'react';
+import VersionCheck from 'react-native-version-check';
+
+import { isNil } from 'lodash';
+import semver from 'semver/preload';
+
+import { usePreferencesContext } from '../contexts/PreferencesContext';
+
+type UpdateInfo = {
+ needsToUpdate?: boolean;
+ latestAppVersion?: string;
+ storeUrl?: string;
+};
+export const useCheckForUpdate = () => {
+ const { lastInstalledVersion } = usePreferencesContext();
+ const [updateInfo, setUpdateInfo] = useState({});
+
+ useEffect(() => {
+ async function getLatestVersion() {
+ try {
+ if (!lastInstalledVersion || !semver.valid(lastInstalledVersion))
+ return;
+ const latestVersion = await VersionCheck.getLatestVersion();
+ if (!latestVersion || !semver.valid(latestVersion)) return;
+ const needsToUpdate = semver.neq(latestVersion, lastInstalledVersion);
+ if (needsToUpdate) {
+ const storeUrl = await VersionCheck.getStoreUrl({
+ appID: '6443913305',
+ });
+ setUpdateInfo({
+ needsToUpdate,
+ latestAppVersion: latestVersion,
+ storeUrl,
+ });
+ }
+ } catch (e) {
+ console.warn('Error while checking for updates', e);
+ }
+ }
+ getLatestVersion();
+ }, [lastInstalledVersion]);
+
+ const { needsToUpdate, latestAppVersion, storeUrl } = updateInfo;
+ const shouldUpdate =
+ needsToUpdate === true && !isNil(latestAppVersion) && !isNil(storeUrl);
+
+ return {
+ needsToUpdate: shouldUpdate,
+ latestAppVersion,
+ storeUrl,
+ };
+};
diff --git a/src/core/hooks/useModalManager.ts b/src/core/hooks/useModalManager.ts
new file mode 100644
index 00000000..e724352e
--- /dev/null
+++ b/src/core/hooks/useModalManager.ts
@@ -0,0 +1,58 @@
+import { useEffect } from 'react';
+
+import { useNavigation } from '@react-navigation/native';
+import { NativeStackNavigationProp } from '@react-navigation/native-stack';
+
+import { usePreferencesContext } from '../contexts/PreferencesContext';
+import { useSplashContext } from '../contexts/SplashContext';
+import { useGetModalMessages } from '../queries/studentHooks';
+import { ONBOARDING_STEPS } from '../screens/OnboardingModal';
+import { RootParamList } from '../types/navigation';
+
+export const useModalManager = (versionModalIsOpen?: boolean) => {
+ const { isSplashLoaded } = useSplashContext();
+ const { onboardingStep } = usePreferencesContext();
+ const navigation = useNavigation>();
+ const routes = navigation.getState()?.routes?.[0]?.state?.routes;
+ const isOnBoardingClosed = routes
+ ? !routes.some(route => route.name === 'OnboardingModal')
+ : undefined;
+
+ const { data: messages } = useGetModalMessages();
+
+ useEffect(() => {
+ if (!isSplashLoaded) return;
+ if (onboardingStep && onboardingStep >= ONBOARDING_STEPS - 1) return;
+ if (versionModalIsOpen === false) {
+ navigation.navigate('TeachingTab', {
+ screen: 'OnboardingModal',
+ initial: false,
+ });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isSplashLoaded, navigation, versionModalIsOpen]);
+
+ useEffect(() => {
+ if (
+ onboardingStep === undefined ||
+ (!isOnBoardingClosed && onboardingStep < ONBOARDING_STEPS - 1) ||
+ !isSplashLoaded
+ ) {
+ return;
+ }
+ if (versionModalIsOpen === false) {
+ if (!messages || messages.length === 0) return;
+ navigation.navigate('TeachingTab', {
+ screen: 'MessagesModal',
+ initial: false,
+ });
+ }
+ }, [
+ messages,
+ versionModalIsOpen,
+ navigation,
+ onboardingStep,
+ isSplashLoaded,
+ isOnBoardingClosed,
+ ]);
+};
diff --git a/src/core/providers/SplashProvider.tsx b/src/core/providers/SplashProvider.tsx
index 491cff28..0e55448a 100644
--- a/src/core/providers/SplashProvider.tsx
+++ b/src/core/providers/SplashProvider.tsx
@@ -1,14 +1,24 @@
-import { PropsWithChildren, useEffect, useRef, useState } from 'react';
+import {
+ Dispatch,
+ PropsWithChildren,
+ SetStateAction,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
import { Animated, StyleSheet, useWindowDimensions } from 'react-native';
import { SplashContext } from '../contexts/SplashContext';
export function SplashProvider({ children }: PropsWithChildren) {
const [isAppLoaded, setIsAppLoaded] = useState(false);
+ const [isSplashLoaded, setIsSplashLoaded] = useState(false);
return (
-
+
{children}
-
+
);
}
@@ -19,7 +29,13 @@ enum SplashPhases {
'HIDDEN',
}
-export const Splash = ({ isAppLoaded }: { isAppLoaded: boolean }) => {
+export const Splash = ({
+ isAppLoaded,
+ setIsSplashLoaded,
+}: {
+ isAppLoaded: boolean;
+ setIsSplashLoaded: Dispatch>;
+}) => {
const containerOpacity = useRef(new Animated.Value(1)).current;
const [splashPhase, setSplashPhase] = useState(
SplashPhases.SHOWN,
@@ -63,6 +79,7 @@ export const Splash = ({ isAppLoaded }: { isAppLoaded: boolean }) => {
useNativeDriver: true,
}).start(() => {
setSplashPhase(SplashPhases.HIDDEN);
+ setIsSplashLoaded(true);
});
}
}, [containerOpacity, splashPhase]);