From e8e76cdf8bed442fc88270d5014dec15ba22c3d6 Mon Sep 17 00:00:00 2001 From: leonardo Date: Mon, 2 Mar 2026 12:13:01 +0100 Subject: [PATCH] feat: enhance configuration management, add update checks, and improve UI components --- .env.example | 12 +++- app/(protected)/_layout.tsx | 8 +-- app/(protected)/permits/index.tsx | 27 +++++---- app/(protected)/profile/_layout.tsx | 2 +- app/_layout.tsx | 21 ++++--- components/RequestPermitModal.tsx | 4 +- components/UpdateScreen.tsx | 43 ++++++++++++++ package-lock.json | 44 ++++++++++----- utils/api.ts | 8 +++ utils/authContext.tsx | 2 +- utils/configProvider.tsx | 87 +++++++++++++++++++++++++++++ utils/version.ts | 27 +++++++++ 12 files changed, 239 insertions(+), 46 deletions(-) create mode 100644 components/UpdateScreen.tsx create mode 100644 utils/configProvider.tsx create mode 100644 utils/version.ts diff --git a/.env.example b/.env.example index 15ec0f2..2738591 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,10 @@ -EXPO_PUBLIC_API_URL=[YOUR_API_URL] -EXPO_PUBLIC_HA_API_URL=[YOUR_HOME_ASSISTANT_API_URL] -EXPO_PUBLIC_HA_TOKEN=[YOUR_HOME_ASSISTANT_TOKEN] +EXPO_PUBLIC_API_URL=[YOUR_API_URL] # backend API URL (used for development, it overrides the one provided by the gateway) + +EXPO_PUBLIC_HA_API_URL=[YOUR_HOME_ASSISTANT_API_URL] # Home Assistant API URL +EXPO_PUBLIC_HA_TOKEN=[YOUR_HOME_ASSISTANT_TOKEN] # Home Assistant Long-Lived Access Token + EXPO_PUBLIC_ENABLE_NFC=[true|false] + +EXPO_PUBLIC_GW_API_URL=[YOUR_GW_API_URL] # Gateway API URL +EXPO_PUBLIC_GW_UUID=[YOUR_GW_UUID] # Gateway UUID +EXPO_PUBLIC_GW_API_TOKEN=[YOUR_GW_API_TOKEN] # Gateway API Token \ No newline at end of file diff --git a/app/(protected)/_layout.tsx b/app/(protected)/_layout.tsx index 766bedb..e06e97b 100644 --- a/app/(protected)/_layout.tsx +++ b/app/(protected)/_layout.tsx @@ -2,7 +2,7 @@ import { Redirect, Tabs } from 'expo-router'; import { Home, Clock, Zap, CalendarIcon, Building } from 'lucide-react-native'; import { useContext } from 'react'; import { AuthContext } from '@/utils/authContext'; -import {useSafeAreaInsets} from "react-native-safe-area-context"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; export default function ProtectedLayout() { const authState = useContext(AuthContext); @@ -32,9 +32,9 @@ export default function ProtectedLayout() { tabBarActiveTintColor: '#099499', tabBarInactiveTintColor: '#9ca3af', tabBarLabelStyle: { - fontSize: 12, - fontWeight: '600', - marginTop: 4 + fontSize: 12, + fontWeight: '600', + marginTop: 4 } }} backBehavior='history' diff --git a/app/(protected)/permits/index.tsx b/app/(protected)/permits/index.tsx index 29ec6b4..16d7882 100644 --- a/app/(protected)/permits/index.tsx +++ b/app/(protected)/permits/index.tsx @@ -83,7 +83,7 @@ export default function PermitsScreen() { const deletePermitRequest = async (id: number, itemRef?: React.ElementRef | null) => { try { itemRef?.close(); - await api.post(`/time-off-request/delete-request`, {id: id}); + await api.post(`/time-off-request/delete-request`, { id: id }); // Optimistic update setPermits(prevPermits => prevPermits.filter(p => p.id !== id)); alert.showAlert('success', 'Richiesta eliminata', 'La richiesta è stata eliminata con successo.'); @@ -136,7 +136,7 @@ export default function PermitsScreen() { onPress={() => confirmDelete(item, swipeableRef.current)} className="bg-red-500 justify-center items-center px-6 rounded-3xl ml-3" activeOpacity={0.7} - style={{margin: 2}} + style={{ margin: 2 }} > @@ -196,23 +196,28 @@ export default function PermitsScreen() { {typeIcons[item.timeOffRequestType.name]?.(item.timeOffRequestType.color)} - - {item.timeOffRequestType.name} + + + {item.timeOffRequestType.name} + + + {item.status === 1 ? 'Approvata' : item.status === 0 ? 'Rifiutata' : 'In Attesa'} + + + + + {item.message} {formatDate(item.start_date?.toLocaleString())} {item.end_date ? `- ${formatDate(item.end_date.toLocaleString())}` : ''} {item.timeOffRequestType.name === 'Permesso' && ( - + {formatTime(item.start_time)} - {formatTime(item.end_time)} )} - - - {item.status===1 ? 'Approvata' : item.status===0 ? 'Rifiutata' : 'In Attesa'} - - + ); @@ -228,7 +233,7 @@ export default function PermitsScreen() { rightThreshold={40} friction={2} overshootFriction={8} - containerStyle={{padding: 2}} + containerStyle={{ padding: 2 }} > {cardContent} diff --git a/app/(protected)/profile/_layout.tsx b/app/(protected)/profile/_layout.tsx index c4da687..b16564f 100644 --- a/app/(protected)/profile/_layout.tsx +++ b/app/(protected)/profile/_layout.tsx @@ -4,7 +4,7 @@ export default function ProfileLayout() { return ( - + ); } \ No newline at end of file diff --git a/app/_layout.tsx b/app/_layout.tsx index 8162644..75c57c5 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -5,7 +5,8 @@ import { AlertProvider } from '@/components/AlertComponent'; import { NetworkProvider } from '@/utils/networkProvider'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { KeyboardProvider } from "react-native-keyboard-controller"; -import {GestureHandlerRootView} from "react-native-gesture-handler"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { ConfigProvider } from '@/utils/configProvider'; export default function AppLayout() { return ( @@ -13,14 +14,16 @@ export default function AppLayout() { - - - - - - - - + + + + + + + + + + diff --git a/components/RequestPermitModal.tsx b/components/RequestPermitModal.tsx index 7109807..39faf29 100644 --- a/components/RequestPermitModal.tsx +++ b/components/RequestPermitModal.tsx @@ -64,6 +64,8 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } if (response.data.status === 'success') { alert.showAlert('success', 'Successo', response.data.message || 'La tua richiesta è stata inviata con successo.'); + onSubmit(requestData); + onClose(); } else { alert.showAlert('error', 'Errore', response.data.message || 'Impossibile inviare la richiesta.'); } @@ -92,8 +94,6 @@ export default function RequestPermitModal({ visible, types, onClose, onSubmit } try { await saveRequest(requestData); - onSubmit(requestData); - onClose(); } catch (e) { alert.showAlert("error", "Errore", "Impossibile inviare la richiesta."); } diff --git a/components/UpdateScreen.tsx b/components/UpdateScreen.tsx new file mode 100644 index 0000000..0fb8313 --- /dev/null +++ b/components/UpdateScreen.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { CloudDownload } from 'lucide-react-native'; + +interface UpdateScreenProps { + onUpdate: () => void; + isOpeningStore?: boolean; +} + +export default function UpdateScreen({ onUpdate, isOpeningStore = false }: UpdateScreenProps) { + return ( + + + {/* Icon */} + + + + + + Aggiornamento Richiesto + + + + È disponibile una nuova versione dell'applicazione.{'\n'}Per continuare a utilizzarla è necessario effettuare l'aggiornamento. + + + {/* Update Button */} + + + {isOpeningStore ? 'Apertura store...' : 'Aggiorna Ora'} + + + + + ); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b453554..6af81e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,6 +111,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1492,6 +1493,7 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -3164,6 +3166,7 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.25.tgz", "integrity": "sha512-zQeWK9txDePWbYfqTs0C6jeRdJTm/7VhQtW/1IbJNDi9/rFIRzZule8bdQPAnf8QWUsNujRmi1J9OG/hhfbalg==", "license": "MIT", + "peer": true, "dependencies": { "@react-navigation/core": "^7.13.6", "escape-string-regexp": "^4.0.0", @@ -3362,6 +3365,7 @@ "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3432,6 +3436,7 @@ "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", @@ -3993,6 +3998,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4655,8 +4661,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/bplist-creator": { "version": "0.1.0", @@ -4720,6 +4725,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5280,7 +5286,6 @@ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -5297,7 +5302,6 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", "license": "MIT", - "peer": true, "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" @@ -5311,7 +5315,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5321,7 +5324,6 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">= 6" }, @@ -5589,7 +5591,6 @@ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", - "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -5609,15 +5610,13 @@ "url": "https://github.com/sponsors/fb55" } ], - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -5633,7 +5632,6 @@ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -5716,7 +5714,6 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.12" }, @@ -5948,6 +5945,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6144,6 +6142,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6382,6 +6381,7 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.27.tgz", "integrity": "sha512-50BcJs8eqGwRiMUoWwphkRGYtKFS2bBnemxLzy0lrGVA1E6F4Q7L5h3WT6w1ehEZybtOVkfJu4Z6GWo2IJcpEA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.18", @@ -6469,6 +6469,7 @@ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.11.tgz", "integrity": "sha512-xnfrfZ7lHjb+03skhmDSYeFF7OU2K3Xn/lAeP+7RhkV2xp2f5RCKtOUYajCnYeZesvMrsUxOsbGOP2JXSOH3NA==", "license": "MIT", + "peer": true, "dependencies": { "@expo/config": "~12.0.11", "@expo/env": "~2.0.8" @@ -6575,6 +6576,7 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.10.tgz", "integrity": "sha512-UqyNaaLKRpj4pKAP4HZSLnuDQqueaO5tB1c/NWu5vh1/LF9ulItyyg2kF/IpeOp0DeOLk0GY0HrIXaKUMrwB+Q==", "license": "MIT", + "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -8852,6 +8854,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -9467,8 +9470,7 @@ "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "license": "CC0-1.0", - "peer": true + "license": "CC0-1.0" }, "node_modules/memoize-one": { "version": "5.2.1", @@ -10083,7 +10085,6 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -10662,6 +10663,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", @@ -11110,6 +11112,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11129,6 +11132,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -11165,6 +11169,7 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", @@ -11505,6 +11510,7 @@ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", "integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==", "license": "MIT", + "peer": true, "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", @@ -11558,6 +11564,7 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.6.tgz", "integrity": "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==", "license": "MIT", + "peer": true, "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" @@ -11586,6 +11593,7 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -11596,6 +11604,7 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", "license": "MIT", + "peer": true, "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.2.1", @@ -11647,6 +11656,7 @@ "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", @@ -11789,6 +11799,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12986,6 +12997,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -13192,6 +13204,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -13398,6 +13411,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/utils/api.ts b/utils/api.ts index f4ec75a..d281b4e 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -14,6 +14,14 @@ const api = axios.create({ timeout: 10000, // 10 seconds timeout }); +// Export function to update base URL +export const setApiBaseUrl = (url: string) => { + if (url) { + api.defaults.baseURL = url; + console.log(`[API] Base URL updated to: ${url}`); + } +}; + // Interceptor: Adds the token to EVERY request if it exists api.interceptors.request.use( async (config) => { diff --git a/utils/authContext.tsx b/utils/authContext.tsx index 16d732c..bf077f0 100644 --- a/utils/authContext.tsx +++ b/utils/authContext.tsx @@ -62,7 +62,7 @@ export function AuthProvider({ children }: PropsWithChildren) { const savedToken = await SecureStore.getItemAsync(KEY_TOKEN); if (savedToken) { - console.log("Token trovato: ", savedToken); + console.log("Token trovato:", savedToken); // Call backend to verify token and fetch user data // Note: api.ts already adds the Authorization header thanks to the interceptor (if configured to read from SecureStore) diff --git a/utils/configProvider.tsx b/utils/configProvider.tsx new file mode 100644 index 0000000..adb1bca --- /dev/null +++ b/utils/configProvider.tsx @@ -0,0 +1,87 @@ +import React, { createContext, useState, useEffect, ReactNode } from 'react'; +import { Linking, Platform } from 'react-native'; +import Constants from 'expo-constants'; +import axios from 'axios'; +import LoadingScreen from '@/components/LoadingScreen'; +import UpdateScreen from '@/components/UpdateScreen'; +import { isUpdateAvailable } from '@/utils/version'; +import { setApiBaseUrl } from './api'; + +interface ConfigContextProps { + children: ReactNode +}; + +// Context (useful if you want to trigger manual checks from inside the app in the future) +export const ConfigContext = createContext({}); + +const GW_API = process.env.EXPO_PUBLIC_GW_API_URL; +const GW_UUID = process.env.EXPO_PUBLIC_GW_UUID; +const GW_TOKEN = process.env.EXPO_PUBLIC_GW_API_TOKEN; + +export const ConfigProvider = ({ children }: ConfigContextProps) => { + const [isChecking, setIsChecking] = useState(true); + const [needsUpdate, setNeedsUpdate] = useState(false); + const [updateUrl, setUpdateUrl] = useState(''); + + useEffect(() => { + const checkAppVersion = async () => { + try { + + const apiUrl = `${GW_API}${GW_UUID}`; + const response = await axios.get(apiUrl, { + headers: { "x-access-tokens": GW_TOKEN } + }); + + // Update API URL: prioritize environment variable (override) over gateway response + setApiBaseUrl(process.env.EXPO_PUBLIC_API_URL || response.data.url); + + const currentVersion = Constants.expoConfig?.version; + console.log("Versione attuale dell'app:", currentVersion); + + const latestVersion = response.data.version; + console.log("Versione più recente disponibile:", latestVersion); + + // Check if an update is needed + if (isUpdateAvailable(currentVersion, latestVersion)) { + setNeedsUpdate(true); + setUpdateUrl(Platform.OS === 'ios' ? response.data.app_url_ios : response.data.app_url_android); + } + + } catch (error) { + console.error("Errore durante il controllo della versione:", error); + setNeedsUpdate(false); + } finally { + setIsChecking(false); + } + }; + + checkAppVersion(); + }, []); + + const handleUpdate = async () => { + if (updateUrl) { + Linking.openURL(updateUrl); + } + }; + + // Loading state + if (isChecking) { + return ( + + ); + } + + // Update state: blocks children rendering + if (needsUpdate) { + return ( + + ); + } + + // Version is up to date + return ( + + {children} + + ); +}; diff --git a/utils/version.ts b/utils/version.ts new file mode 100644 index 0000000..c33fddf --- /dev/null +++ b/utils/version.ts @@ -0,0 +1,27 @@ +/** + * Compare two version strings. + * Returns true if 'latest' is greater than 'current' (an update is available). + */ +export const isUpdateAvailable = (currentVersion: string | undefined, latestVersion: string) => { + if (!currentVersion || !latestVersion) return false; + + // Split strings into an array of numbers: "1.2.10" -> [1, 2, 10] + const currentParts = currentVersion.split('.').map(Number); + const latestParts = latestVersion.split('.').map(Number); + const maxLength = Math.max(currentParts.length, latestParts.length); + + for (let i = 0; i < maxLength; i++) { + // If a part is missing, we consider it as 0 (e.g. "1.0" -> [1, 0, 0]) + const current = currentParts[i] || 0; + const latest = latestParts[i] || 0; + + if (current < latest) { + return true; // It needs an update + } + if (current > latest) { + return false; + } + } + + return false; +}; \ No newline at end of file