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>store 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>store 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]);