diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml new file mode 100644 index 0000000..8857e82 --- /dev/null +++ b/.github/workflows/build-apk.yml @@ -0,0 +1,125 @@ +name: Build APK + +on: + workflow_dispatch: + push: + branches: + - claude/build-aerostaffpro-apk-R00yT + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write # necessario per creare/aggiornare release + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install SDK components + run: | + echo "y" | sdkmanager \ + "ndk;27.1.12297006" \ + "build-tools;36.0.0" \ + "platforms;android-36" \ + "cmake;3.22.1" + + - name: Install dependencies + run: npm ci --legacy-peer-deps + + - name: Fix expo-modules-core Gradle bugs + run: | + FILE="node_modules/expo-modules-core/android/build.gradle" + + # 1. Re-declare shouldIncludeCompose at project level (buildscript.ext not + # accessible in android {} block with newer Gradle/AGP versions) + sed -i "s|apply plugin: 'com.android.library'|// Fix: project-level re-declaration\ndef _coreFeatures = project.findProperty(\"coreFeatures\") ?: []\next.shouldIncludeCompose = _coreFeatures.contains(\"compose\")\n\napply plugin: 'com.android.library'|" \ + "$FILE" + + # 2. Use property assignment syntax instead of method call + # 'compose shouldIncludeCompose' -> 'compose = shouldIncludeCompose' + sed -i 's/^\s*compose shouldIncludeCompose\s*$/ compose = shouldIncludeCompose/' \ + "$FILE" + + echo "=== Patched lines around the fix ===" + grep -n "shouldIncludeCompose\|apply plugin" "$FILE" | head -20 + + - name: Build debug APK + run: | + cd android + ./gradlew :app:assembleDebug \ + --no-daemon \ + --info \ + -Pandroid.overridePathCheck=true \ + 2>&1 | tail -200 + env: + ANDROID_HOME: ${{ env.ANDROID_SDK_ROOT }} + GRADLE_OPTS: "-Xmx4g -XX:MaxMetaspaceSize=512m" + + - name: Read version from build.gradle + id: version + run: | + VERSION_NAME=$(grep 'versionName' android/app/build.gradle | grep -oP '"\K[^"]+') + VERSION_CODE=$(grep 'versionCode' android/app/build.gradle | grep -oP '\d+') + echo "name=$VERSION_NAME" >> $GITHUB_OUTPUT + echo "code=$VERSION_CODE" >> $GITHUB_OUTPUT + echo "tag=v$VERSION_NAME" >> $GITHUB_OUTPUT + echo "Version: $VERSION_NAME (code $VERSION_CODE)" + + - name: Find and rename APK + run: | + APK=$(find android/app/build/outputs/apk -name "*.apk" -type f | head -1) + echo "Found APK: $APK" + if [ -n "$APK" ]; then + mkdir -p artifacts + cp "$APK" "artifacts/AeroStaffPro-v${{ steps.version.outputs.name }}.apk" + ls -lh artifacts/ + else + echo "ERROR: No APK found" + find android/app/build -type f | head -20 + exit 1 + fi + + - name: Upload APK artifact + uses: actions/upload-artifact@v4 + with: + name: AeroStaffPro-v${{ steps.version.outputs.name }} + path: artifacts/AeroStaffPro-v${{ steps.version.outputs.name }}.apk + + - name: Create or update GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.version.outputs.tag }} + name: AeroStaff Pro ${{ steps.version.outputs.name }} + body: | + ## AeroStaff Pro ${{ steps.version.outputs.name }} + + Build automatico dal branch `claude/build-aerostaffpro-apk-R00yT`. + + ### Novità in questo build + - Carosello ore/minuti nella modifica turni manuali (no tastiera) + - Pull-to-refresh velocizzato (cache 2 min + parallelo) + - Notifica persistente turno in corso (sticky in area notifiche) + - Keystore stabile: aggiornamenti futuri senza disinstallazione + + ### Installazione + 1. Scarica `AeroStaffPro-v${{ steps.version.outputs.name }}.apk` + 2. Se hai già installato una versione con keystore diverso: disinstalla prima + 3. Abilita "Installa da fonti sconosciute" se richiesto + files: artifacts/AeroStaffPro-v${{ steps.version.outputs.name }}.apk + make_latest: true diff --git a/.gitignore b/.gitignore index a198171..49bced3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ expo-env.d.ts *.orig.* *.jks *.keystore +!android/app/debug.keystore *.p8 *.p12 *.key @@ -54,3 +55,10 @@ test_cli.js # generated native folders /ios .dual-graph/ + +# android sdk installer artifacts +*.deb +debian-binary +control.tar.xz +data.tar.xz +org/ diff --git a/android/app/build.gradle b/android/app/build.gradle index 0b49f04..04a028e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -92,8 +92,8 @@ android { applicationId 'com.anonymous.FlightWorkApp' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.1.0" + versionCode 200 + versionName "2.0.0" buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" } diff --git a/android/app/debug.keystore b/android/app/debug.keystore new file mode 100644 index 0000000..521559a Binary files /dev/null and b/android/app/debug.keystore differ diff --git a/android/gradlew b/android/gradlew old mode 100644 new mode 100755 diff --git a/app.json b/app.json index 0473df0..94286f3 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "FlightWorkApp", "slug": "FlightWorkApp", - "version": "1.1.0", + "version": "2.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", diff --git a/package-lock.json b/package-lock.json index a3bbe99..b9e5ea8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@expo/vector-icons": "^15.0.3", "@react-native-async-storage/async-storage": "2.2.0", - "@react-native-picker/picker": "2.11.1", + "@react-native-picker/picker": "2.11.4", "@types/tesseract.js": "^0.0.2", "expo": "~54.0.0", "expo-blur": "~15.0.8", @@ -23,18 +23,19 @@ "expo-linear-gradient": "~15.0.8", "expo-location": "~19.0.8", "expo-notifications": "~0.32.16", + "expo-secure-store": "~15.0.5", "expo-status-bar": "~3.0.9", "react": "19.1.0", "react-native": "0.81.5", "react-native-android-widget": "^0.20.1", "react-native-calendars": "^1.1314.0", - "react-native-webview": "13.15.0", + "react-native-webview": "13.16.1", "tesseract.js": "^7.0.0" }, "devDependencies": { "@react-native-community/cli": "^20.1.3", "@types/react": "~19.1.10", - "pdfjs-dist": "^5.5.207", + "pdfjs-dist": "^5.6.205", "typescript": "~5.9.2" } }, @@ -80,7 +81,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1492,7 +1492,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -1885,14 +1884,14 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@hapi/topo": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" @@ -2379,7 +2378,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -2393,7 +2392,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -2403,7 +2402,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -2429,9 +2428,8 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.1.3.tgz", "integrity": "sha512-sLo8cu9JyFNfuuF1C+8NJ4DHE/PEFaXGd4enkcxi/OJjGG8+sOQrdjNQ4i+cVh/2c+ah1mEMwsYjc3z0+/MqSg==", - "devOptional": true, + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@react-native-community/cli-clean": "20.1.3", "@react-native-community/cli-config": "20.1.3", @@ -2460,7 +2458,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.1.3.tgz", "integrity": "sha512-sFLdLzapfC0scjgzBJJWYDY2RhHPjuuPkA5r6q0gc/UQH/izXpMpLrhh1DW84cMDraNACK0U62tU7ebNaQ1LMQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@react-native-community/cli-tools": "20.1.3", @@ -2473,7 +2471,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.1.3.tgz", "integrity": "sha512-n73nW0cG92oNF0r994pPqm0DjAShOm3F8LSffDYhJqNAno+h/csmv/37iL4NtSpmKIO8xqsG3uVTXz9X/hzNaQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@react-native-community/cli-tools": "20.1.3", @@ -2488,7 +2486,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.1.3.tgz", "integrity": "sha512-DNHDP+OWLyhKShGciBqPcxhxfp1Z/7GQcb4F+TGyCeKQAr+JdnUjRXN3X+YCU/v+g2kbYYyRJKlGabzkVvdrAw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@react-native-community/cli-tools": "20.1.3", @@ -2501,7 +2499,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.1.3.tgz", "integrity": "sha512-QX9B83nAfCPs0KiaYz61kAEHWr9sttooxzRzNdQwvZTwnsIpvWOT9GvMMj/19OeXiQzMJBzZX0Pgt6+spiUsDQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@react-native-community/cli-tools": "20.1.3", @@ -2514,7 +2512,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.1.3.tgz", "integrity": "sha512-EI+mAPWn255/WZ4CQohy1I049yiaxVr41C3BeQ2BCyhxODIDR8XRsLzYb1t9MfqK/C3ZncUN2mPSRXFeKPPI1w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@react-native-community/cli-config": "20.1.3", @@ -2538,7 +2536,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" @@ -2551,7 +2549,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -2568,7 +2566,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2578,7 +2576,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -2594,7 +2592,7 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -2618,7 +2616,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "onetime": "^5.1.0", @@ -2632,7 +2630,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.1.3.tgz", "integrity": "sha512-bzB9ELPOISuqgtDZXFPQlkuxx1YFkNx3cNgslc5ElCrk+5LeCLQLIBh/dmIuK8rwUrPcrramjeBj++Noc+TaAA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@react-native-community/cli-config-android": "20.1.3", @@ -2646,7 +2644,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.1.3.tgz", "integrity": "sha512-XJ+DqAD4hkplWVXK5AMgN7pP9+4yRSe5KfZ/b42+ofkDBI55ALlUmX+9HWE3fMuRjcotTCoNZqX2ov97cFDXpQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@react-native-community/cli-config-apple": "20.1.3", @@ -2660,7 +2658,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.1.3.tgz", "integrity": "sha512-2qL48SINotuHbZO73cgqSwqd/OWNx0xTbFSdujhpogV4p8BNwYYypfjh4vJY5qJEB5PxuoVkMXT+aCADpg9nBg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@react-native-community/cli-platform-apple": "20.1.3" @@ -2670,7 +2668,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.1.3.tgz", "integrity": "sha512-hsNsdUKZDd2T99OuNuiXz4VuvLa1UN0zcxefmPjXQgI0byrBLzzDr+o7p03sKuODSzKi2h+BMnUxiS07HACQLA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@react-native-community/cli-tools": "20.1.3", @@ -2690,7 +2688,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2700,7 +2698,7 @@ "version": "6.4.0", "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "is-wsl": "^1.1.0" @@ -2713,7 +2711,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "async-limiter": "~1.0.0" @@ -2723,7 +2721,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.3.tgz", "integrity": "sha512-EAn0vPCMxtHhfWk2UwLmSUfPfLUnFgC7NjiVJVTKJyVk5qGnkPfoT8te/1IUXFTysUB0F0RIi+NgDB4usFOLeA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@vscode/sudo-prompt": "^9.0.0", @@ -2742,7 +2740,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" @@ -2755,7 +2753,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -2772,7 +2770,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -2788,7 +2786,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -2805,7 +2803,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "mime": "cli.js" @@ -2818,7 +2816,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2828,7 +2826,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -2844,7 +2842,7 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", @@ -2868,7 +2866,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -2884,7 +2882,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -2900,7 +2898,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "onetime": "^5.1.0", @@ -2914,7 +2912,7 @@ "version": "20.1.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.1.3.tgz", "integrity": "sha512-IdAcegf0pH1hVraxWTG1ACLkYC0LDQfqtaEf42ESyLIF3Xap70JzL/9tAlxw7lSCPZPFWhrcgU0TBc4SkC/ecw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "joi": "^17.2.1" @@ -2924,7 +2922,7 @@ "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || >=14" @@ -2934,7 +2932,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -2951,7 +2949,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -2967,7 +2965,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -2983,7 +2981,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -2996,9 +2994,9 @@ } }, "node_modules/@react-native-picker/picker": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.1.tgz", - "integrity": "sha512-ThklnkK4fV3yynnIIRBkxxjxR4IFbdMNJVF6tlLdOJ/zEFUEFUEdXY0KmH0iYzMwY8W4/InWsLiA7AkpAbnexA==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.4.tgz", + "integrity": "sha512-Kf8h1AMnBo54b1fdiVylP2P/iFcZqzpMYcglC28EEFB1DEnOjsNr6Ucqc+3R9e91vHxEDnhZFbYDmAe79P2gjA==", "license": "MIT", "workspaces": [ "example" @@ -3266,7 +3264,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" @@ -3276,14 +3274,14 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { @@ -3397,9 +3395,8 @@ "version": "19.1.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz", "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", - "devOptional": true, + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3467,7 +3464,7 @@ "version": "9.3.2", "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz", "integrity": "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@xmldom/xmldom": { @@ -3562,7 +3559,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "colorette": "^1.0.7", @@ -3574,7 +3571,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3584,7 +3581,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^4.1.0" @@ -3652,7 +3649,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/arg": { @@ -3693,7 +3690,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -3996,7 +3993,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -4014,7 +4011,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -4039,7 +4036,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -4109,7 +4106,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4223,7 +4219,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4393,14 +4389,14 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/commander": { @@ -4506,7 +4502,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4535,7 +4531,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -4562,14 +4558,14 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "devOptional": true, + "dev": true, "license": "Python-2.0" }, "node_modules/cosmiconfig/node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4596,14 +4592,14 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/dayjs": { "version": "1.11.20", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -4627,7 +4623,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4815,7 +4811,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4825,7 +4821,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "envinfo": "dist/cli.js" @@ -4838,7 +4834,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -4857,7 +4853,7 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.2.tgz", "integrity": "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -4963,7 +4959,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -4987,7 +4983,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4997,7 +4993,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -5014,7 +5010,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz", "integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.23", @@ -5130,7 +5125,6 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -5239,6 +5233,15 @@ "react-native": "*" } }, + "node_modules/expo-secure-store": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-15.0.8.tgz", + "integrity": "sha512-lHnzvRajBu4u+P99+0GEMijQMFCOYpWRO4dWsXSuMt77+THPIGjzNvVKrGSl6mMrLsfVaKL8BpwYZLGlgA+zAw==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-server": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-1.0.5.tgz", @@ -5547,7 +5550,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -5570,7 +5573,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -5586,7 +5589,7 @@ "version": "5.5.9", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz", "integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -5607,7 +5610,7 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -5746,7 +5749,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -5863,7 +5866,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -5902,7 +5905,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -6119,7 +6122,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.17.0" @@ -6129,7 +6132,7 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -6196,7 +6199,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -6213,7 +6216,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -6280,7 +6283,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/is-callable": { @@ -6329,7 +6332,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6367,7 +6370,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -6380,7 +6383,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6442,7 +6445,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6470,7 +6473,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -6732,7 +6735,7 @@ "version": "17.13.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.3.0", @@ -6783,7 +6786,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json5": { @@ -6802,7 +6805,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "devOptional": true, + "dev": true, "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" @@ -6830,7 +6833,7 @@ "version": "2.13.2", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "picocolors": "^1.1.1", @@ -7243,7 +7246,7 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ansi-fragments": "^0.2.1", @@ -7258,7 +7261,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -7268,7 +7271,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -7280,7 +7283,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -7295,14 +7298,14 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/logkitty/node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "cliui": "^6.0.0", @@ -7325,7 +7328,7 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "camelcase": "^5.0.0", @@ -7384,7 +7387,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -7418,7 +7421,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -7881,7 +7884,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -7940,7 +7943,7 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -7978,7 +7981,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -8018,7 +8021,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8288,7 +8291,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -8301,7 +8304,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -8350,7 +8353,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -8412,16 +8415,16 @@ } }, "node_modules/pdfjs-dist": { - "version": "5.5.207", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.5.207.tgz", - "integrity": "sha512-WMqqw06w1vUt9ZfT0gOFhMf3wHsWhaCrxGrckGs5Cci6ybDW87IvPaOd2pnBwT6BJuP/CzXDZxjFgmSULLdsdw==", + "version": "5.6.205", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.6.205.tgz", + "integrity": "sha512-tlUj+2IDa7G1SbvBNN74UHRLJybZDWYom+k6p5KIZl7huBvsA4APi6mKL+zCxd3tLjN5hOOEE9Tv7VdzO88pfg==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=20.19.0 || >=22.13.0 || >=24" }, "optionalDependencies": { - "@napi-rs/canvas": "^0.1.95", + "@napi-rs/canvas": "^0.1.96", "node-readable-to-web-readable-stream": "^0.4.2" } }, @@ -8436,7 +8439,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8629,7 +8631,7 @@ "version": "6.15.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -8654,7 +8656,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -8684,7 +8686,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -8716,7 +8718,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8742,7 +8743,6 @@ "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", @@ -8849,11 +8849,10 @@ "license": "MIT" }, "node_modules/react-native-webview": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz", - "integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==", + "version": "13.16.1", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.1.tgz", + "integrity": "sha512-If0eHhoEdOYDcHsX+xBFwHMbWBGK1BvGDQDQdVkwtSIXiq1uiqjkpWVP2uQ1as94J0CzvFE9PUNDuhiX0Z6ubw==", "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" @@ -8952,7 +8951,6 @@ "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" } @@ -8961,7 +8959,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -9068,7 +9066,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/requireg": { @@ -9154,7 +9152,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -9224,7 +9222,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -9285,7 +9283,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/sax": { @@ -9421,7 +9419,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/set-function-length": { @@ -9484,7 +9482,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9504,7 +9502,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9521,7 +9519,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9540,7 +9538,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9604,7 +9602,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.0", @@ -9619,7 +9617,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" @@ -9632,7 +9630,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -9642,14 +9640,14 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9768,14 +9766,14 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/strict-url-sanitise/-/strict-url-sanitise-0.0.1.tgz", "integrity": "sha512-nuFtF539K8jZg3FjaWH/L8eocCR6gegz5RDOsaWxfdbF5Jqr2VXWxZayjTwUzsWJDC91k2EbnJXp6FuWW+Z4hg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -9811,7 +9809,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9830,7 +9828,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -10169,7 +10167,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -10184,7 +10182,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -10194,7 +10192,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -10213,7 +10211,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10281,7 +10278,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 4.0.0" @@ -10343,7 +10340,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -10476,7 +10473,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/which-typed-array": { diff --git a/src/screens/CalendarScreen.tsx b/src/screens/CalendarScreen.tsx index 1266625..d240b33 100644 --- a/src/screens/CalendarScreen.tsx +++ b/src/screens/CalendarScreen.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { View, Text, StyleSheet, ActivityIndicator, ScrollView, TouchableOpacity, - PanResponder, Platform, UIManager, Animated, Dimensions, Modal, Alert, FlatList, TextInput, KeyboardAvoidingView, Keyboard, + PanResponder, Platform, UIManager, Animated, Dimensions, Modal, Alert, FlatList, TextInput, } from 'react-native'; import * as SystemCalendar from 'expo-calendar'; import * as Location from 'expo-location'; @@ -26,6 +26,58 @@ import { const PRIMARY = '#2563EB'; const STORAGE_KEY = '@shift_import_name'; +// ─── Wheel picker arrays ────────────────────────────────────────────────────── +const HOURS = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')); +const MINUTES = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0')); +const WHEEL_ITEM_H = 48; + +function WheelPicker({ items, value, onChange, textColor }: { + items: string[]; + value: string; + onChange: (v: string) => void; + textColor: string; +}) { + const listRef = useRef(null); + const idx = items.indexOf(value); + + useEffect(() => { + if (idx >= 0) { + setTimeout(() => { + listRef.current?.scrollToOffset({ offset: idx * WHEEL_ITEM_H, animated: false }); + }, 80); + } + }, []); + + return ( + + item} + snapToInterval={WHEEL_ITEM_H} + decelerationRate="fast" + showsVerticalScrollIndicator={false} + contentContainerStyle={{ paddingVertical: WHEEL_ITEM_H }} + getItemLayout={(_, i) => ({ length: WHEEL_ITEM_H, offset: WHEEL_ITEM_H * i, index: i })} + onMomentumScrollEnd={e => { + const i = Math.round(e.nativeEvent.contentOffset.y / WHEEL_ITEM_H); + const clamped = Math.max(0, Math.min(items.length - 1, i)); + onChange(items[clamped]); + }} + renderItem={({ item }) => ( + + {item} + + )} + /> + + + ); +} + type ShiftEvent = { id: string; title: string; @@ -84,10 +136,6 @@ export default function CalendarScreen() { const [manualStartM, setManualStartM] = useState('00'); const [manualEndH, setManualEndH] = useState('16'); const [manualEndM, setManualEndM] = useState('00'); - const manualStartMRef = useRef(null); - const manualEndHRef = useRef(null); - const manualEndMRef = useRef(null); - const openManualEntry = () => { setEditMenuOpen(false); setManualDate(selectedDay); @@ -97,8 +145,6 @@ export default function CalendarScreen() { setManualModalOpen(true); }; - const sanitizeTimePart = (value: string) => value.replace(/\D/g, '').slice(0, 2); - const saveManualShift = async () => { const { status } = await SystemCalendar.requestCalendarPermissionsAsync(); if (status !== 'granted') { Alert.alert('Permesso negato'); return; } @@ -493,12 +539,8 @@ export default function CalendarScreen() { {/* ─── Manual Entry Modal ─── */} setManualModalOpen(false)}> - - + + setManualModalOpen(false)} /> @@ -533,68 +575,24 @@ export default function CalendarScreen() { ))} - {/* Orari (solo lavoro) */} + {/* Orari (solo lavoro) — carosello scroll, nessuna tastiera */} {manualType === 'Lavoro' && ( <> ORARIO INIZIO - - setManualStartH(sanitizeTimePart(v))} - selectTextOnFocus - returnKeyType="next" - blurOnSubmit={false} - onSubmitEditing={() => manualStartMRef.current?.focus()} - /> - setManualStartM(sanitizeTimePart(v))} - selectTextOnFocus - returnKeyType="next" - blurOnSubmit={false} - onSubmitEditing={() => manualEndHRef.current?.focus()} - /> + + + + : + + ORARIO FINE - - setManualEndH(sanitizeTimePart(v))} - selectTextOnFocus - returnKeyType="next" - blurOnSubmit={false} - onSubmitEditing={() => manualEndMRef.current?.focus()} - /> - setManualEndM(sanitizeTimePart(v))} - selectTextOnFocus - returnKeyType="done" - onSubmitEditing={Keyboard.dismiss} - /> + + + + : + + )} @@ -604,7 +602,7 @@ export default function CalendarScreen() { - + {/* Hidden WebView for PDF extraction */} diff --git a/src/screens/FlightScreen.tsx b/src/screens/FlightScreen.tsx index 155fb94..a5ecd7d 100644 --- a/src/screens/FlightScreen.tsx +++ b/src/screens/FlightScreen.tsx @@ -11,12 +11,13 @@ import { MaterialIcons } from '@expo/vector-icons'; import { useAppTheme } from '../context/ThemeContext'; import { useAirport } from '../context/AirportContext'; import { getAirlineOps, getAirlineColor } from '../utils/airlineOps'; -import { fetchAirportScheduleRaw } from '../utils/fr24api'; +import { fetchAirportScheduleRaw, type FR24ScheduleRaw } from '../utils/fr24api'; import { formatAirportHeader } from '../utils/airportSettings'; import { requestWidgetUpdate } from 'react-native-android-widget'; import { WIDGET_CACHE_KEY } from '../widgets/widgetTaskHandler'; import type { WidgetData, WidgetFlight } from '../widgets/widgetTaskHandler'; import { ShiftWidget } from '../widgets/ShiftWidget'; +import { fetchStaffMonitor, lookupStaff, type StaffMonitorData } from '../utils/staffMonitor'; const WearDataSender = Platform.OS === 'android' ? NativeModules.WearDataSender : null; @@ -24,6 +25,12 @@ const NOTIF_IDS_KEY = 'aerostaff_notif_ids_v1'; const NOTIF_ENABLED_KEY = 'aerostaff_notif_enabled'; const PINNED_FLIGHT_KEY = 'pinned_flight_v1'; const PINNED_NOTIF_IDS_KEY = 'pinned_notif_ids_v1'; +const SHIFT_NOTIF_ID_KEY = 'aerostaff_shift_notif_id_v1'; +const SHIFT_NOTIF_CHANNEL = 'turno_attivo'; + +// ─── In-memory flight cache ──────────────────────────────────────────────────── +let _flightCache: { data: FR24ScheduleRaw; ts: number } | null = null; +const CACHE_TTL_MS = 2 * 60 * 1000; // 2 minuti // Handler: mostra notifiche anche con app aperta (wrapped for Expo Go compat) try { Notifications.setNotificationHandler({ @@ -229,6 +236,59 @@ async function schedulePinnedNotifications(item: any, tab: 'arrivals' | 'departu } } +// ─── Notifica persistente turno ─────────────────────────────────────────────── +async function setupShiftNotifChannel() { + if (Platform.OS !== 'android') return; + try { + await Notifications.setNotificationChannelAsync(SHIFT_NOTIF_CHANNEL, { + name: 'Turno in corso', + importance: Notifications.AndroidImportance.LOW, // no pop-up, no suono + lockscreenVisibility: Notifications.AndroidNotificationVisibility.PUBLIC, + sound: null, + vibrationPattern: [0], + enableLights: false, + showBadge: false, + }); + } catch {} +} + +async function cancelShiftNotification() { + const id = await AsyncStorage.getItem(SHIFT_NOTIF_ID_KEY); + if (!id) return; + await Promise.all([ + Notifications.dismissNotificationAsync(id).catch(() => {}), + Notifications.cancelScheduledNotificationAsync(id).catch(() => {}), + ]); + await AsyncStorage.removeItem(SHIFT_NOTIF_ID_KEY); +} + +async function showShiftNotification( + shift: { start: number; end: number }, + nextFlightLabel: string | null, +) { + await cancelShiftNotification(); + const fmtT = (ts: number) => + new Date(ts * 1000).toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' }); + + const id = await Notifications.scheduleNotificationAsync({ + content: { + title: `✈️ Turno in corso: ${fmtT(shift.start)} – ${fmtT(shift.end)}`, + body: nextFlightLabel ?? 'Nessun volo imminente nel turno', + sticky: true, + sound: false, + data: { type: 'shift_active' }, + android: { + channelId: SHIFT_NOTIF_CHANNEL, + ongoing: true, + color: '#2563EB', + smallIcon: 'ic_notification', + } as any, + }, + trigger: null, + }); + await AsyncStorage.setItem(SHIFT_NOTIF_ID_KEY, id); +} + // ─── Screen ──────────────────────────────────────────────────────────────────── export default function FlightScreen() { const { colors } = useAppTheme(); @@ -245,23 +305,44 @@ export default function FlightScreen() { const [scheduledCount, setScheduledCount] = useState(0); const [pinnedFlightId, setPinnedFlightId] = useState(null); const [inboundArrivals, setInboundArrivals] = useState>({}); + const [staffData, setStaffData] = useState(null); // Carica preferenza notifiche salvata useEffect(() => { AsyncStorage.getItem(NOTIF_ENABLED_KEY).then(v => setNotifsEnabled(v === 'true')); }, []); + // StaffMonitor: fetch immediato + polling 60s + useEffect(() => { + let cancelled = false; + const poll = async () => { + try { + const data = await fetchStaffMonitor(); + if (!cancelled) setStaffData(data); + } catch { /* rete aeroporto non raggiungibile — silenzioso */ } + }; + poll(); + const interval = setInterval(poll, 60_000); + return () => { cancelled = true; clearInterval(interval); }; + }, []); + const fetchAll = useCallback(async () => { if (airportLoading) return; try { - const { - allArrivals, - departures: fetchedDepartures, - arrivals: fetchedArrivals, - } = await fetchAirportScheduleRaw(airportCode); + // 1. Voli: usa cache in-memory se fresca (< 2 min), altrimenti fetch + const now = Date.now(); + let rawResult: FR24ScheduleRaw; + if (_flightCache && (now - _flightCache.ts) < CACHE_TTL_MS) { + rawResult = _flightCache.data; + } else { + rawResult = await fetchAirportScheduleRaw(airportCode); + _flightCache = { data: rawResult, ts: now }; + } + + const { allArrivals, departures: fetchedDepartures, arrivals: fetchedArrivals } = rawResult; - // Build inbound arrival map: registration → best known arrival timestamp + // Build inbound arrival map const inboundMap: Record = {}; for (const a of allArrivals) { const reg = a.flight?.aircraft?.registration; @@ -272,12 +353,46 @@ export default function FlightScreen() { if (t) inboundMap[reg] = t; } setInboundArrivals(inboundMap); - setArrivals(fetchedArrivals); setDepartures(fetchedDepartures); - // Auto-clear expired pinned flight or stale data from another airport - const pinnedRaw = await AsyncStorage.getItem(PINNED_FLIGHT_KEY); + // 2. Pinned check + calendario IN PARALLELO + const [pinnedRaw, calResult] = await Promise.all([ + AsyncStorage.getItem(PINNED_FLIGHT_KEY), + (async () => { + let shiftToday: { start: number; end: number } | null = null; + let shiftTomorrow: { start: number; end: number } | null = null; + let isRestDay = false; + const { status } = await Calendar.requestCalendarPermissionsAsync(); + if (status === 'granted') { + const cals = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT); + const cal = cals.find(c => c.allowsModifications && c.isPrimary) || cals.find(c => c.allowsModifications); + if (cal) { + const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); + const tomorrowEnd = new Date(todayStart); tomorrowEnd.setDate(tomorrowEnd.getDate() + 1); tomorrowEnd.setHours(23, 59, 59, 999); + const evts = await Calendar.getEventsAsync([cal.id], todayStart, tomorrowEnd); + const todayEnd = new Date(todayStart); todayEnd.setHours(23, 59, 59, 999); + const tomorrowStart = new Date(todayStart); tomorrowStart.setDate(tomorrowStart.getDate() + 1); + for (const e of evts) { + if (e.title.includes('Riposo')) { + const evtDay = new Date(e.startDate); + if (evtDay >= todayStart && evtDay <= todayEnd) isRestDay = true; + continue; + } + if (!e.title.includes('Lavoro')) continue; + const s = new Date(e.startDate).getTime() / 1000; + const en = new Date(e.endDate).getTime() / 1000; + const evtDay = new Date(e.startDate); + if (evtDay >= todayStart && evtDay <= todayEnd) shiftToday = { start: s, end: en }; + else if (evtDay >= tomorrowStart && evtDay <= tomorrowEnd) shiftTomorrow = { start: s, end: en }; + } + } + } + return { shiftToday, shiftTomorrow, isRestDay }; + })(), + ]); + + // 3. Processa pinned if (pinnedRaw) { try { const pinned = JSON.parse(pinnedRaw); @@ -296,106 +411,99 @@ export default function FlightScreen() { } catch {} } - // Shift (today + tomorrow) - let shiftToday: { start: number; end: number } | null = null; - let shiftTomorrow: { start: number; end: number } | null = null; - let isRestDay = false; - const { status } = await Calendar.requestCalendarPermissionsAsync(); - if (status === 'granted') { - const cals = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT); - const cal = cals.find(c => c.allowsModifications && c.isPrimary) || cals.find(c => c.allowsModifications); - if (cal) { - const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); - const tomorrowEnd = new Date(todayStart); tomorrowEnd.setDate(tomorrowEnd.getDate() + 1); tomorrowEnd.setHours(23, 59, 59, 999); - const evts = await Calendar.getEventsAsync([cal.id], todayStart, tomorrowEnd); - const todayEnd = new Date(todayStart); todayEnd.setHours(23, 59, 59, 999); - const tomorrowStart = new Date(todayStart); tomorrowStart.setDate(tomorrowStart.getDate() + 1); - for (const e of evts) { - if (e.title.includes('Riposo')) { - const evtDay = new Date(e.startDate); - if (evtDay >= todayStart && evtDay <= todayEnd) isRestDay = true; - continue; + // 4. Turni + const { shiftToday, shiftTomorrow, isRestDay } = calResult; + setShifts({ today: shiftToday, tomorrow: shiftTomorrow }); + + // 5. Widget cache (fire-and-forget) + ;(async () => { + try { + const fmtT = (ts: number) => new Date(ts * 1000).toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' }); + const fmtOff = (dep: number, off: number) => fmtT(dep - off * 60); + const nowHH = fmtT(Date.now() / 1000); + let widgetData: WidgetData; + if (isRestDay) { + widgetData = { state: 'rest' }; + } else if (!shiftToday) { + widgetData = { state: 'no_shift' }; + } else { + const shiftLabel = `${fmtT(shiftToday.start)} – ${fmtT(shiftToday.end)}`; + const pinnedRawW = await AsyncStorage.getItem(PINNED_FLIGHT_KEY); + let pinnedFn: string | null = null; + if (pinnedRawW) { + try { pinnedFn = JSON.parse(pinnedRawW).flight?.identification?.number?.default || null; } catch {} } - if (!e.title.includes('Lavoro')) continue; - const s = new Date(e.startDate).getTime() / 1000; - const en = new Date(e.endDate).getTime() / 1000; - const evtDay = new Date(e.startDate); - if (evtDay >= todayStart && evtDay <= todayEnd) shiftToday = { start: s, end: en }; - else if (evtDay >= tomorrowStart && evtDay <= tomorrowEnd) shiftTomorrow = { start: s, end: en }; + const wFlights: WidgetFlight[] = fetchedDepartures + .filter(item => { + const ts = item.flight?.time?.scheduled?.departure; + if (ts == null) return false; + const airline = item.flight?.airline?.name || ''; + const ops = getAirlineOps(airline); + const ciO = ts - ops.checkInOpen * 60, ciC = ts - ops.checkInClose * 60; + const gO = ts - ops.gateOpen * 60, gC = ts - ops.gateClose * 60; + return (ciO <= shiftToday!.end && ciC >= shiftToday!.start) || (gO <= shiftToday!.end && gC >= shiftToday!.start); + }) + .map(item => { + const ts = item.flight.time.scheduled.departure; + const airline = item.flight?.airline?.name || 'Sconosciuta'; + const ops = getAirlineOps(airline); + const fn = item.flight?.identification?.number?.default || 'N/A'; + return { + flightNumber: fn, + destinationIata: item.flight?.airport?.destination?.code?.iata || '???', + departureTs: ts, + departureTime: fmtT(ts), + ciOpen: fmtOff(ts, ops.checkInOpen), ciClose: fmtOff(ts, ops.checkInClose), + gateOpen: fmtOff(ts, ops.gateOpen), gateClose: fmtOff(ts, ops.gateClose), + airlineColor: getAirlineColor(airline), + isPinned: fn === pinnedFn, + }; + }) + .sort((a, b) => a.departureTs - b.departureTs); + widgetData = wFlights.length === 0 + ? { state: 'work_empty', shiftLabel, updatedAt: nowHH } + : { state: 'work', shiftLabel, flights: wFlights, updatedAt: nowHH }; } - } - } - setShifts({ today: shiftToday, tomorrow: shiftTomorrow }); + await AsyncStorage.setItem(WIDGET_CACHE_KEY, JSON.stringify(widgetData)); + if (Platform.OS === 'android') { + requestWidgetUpdate({ widgetName: 'ShiftFlights', renderWidget: () => () as any }).catch(() => {}); + } + } catch {} + })(); - // ── Push data to widget cache ── - try { - const fmtT = (ts: number) => new Date(ts * 1000).toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' }); - const fmtOff = (dep: number, off: number) => fmtT(dep - off * 60); - const nowHH = fmtT(Date.now() / 1000); - - let widgetData: WidgetData; - if (isRestDay) { - widgetData = { state: 'rest' }; - } else if (!shiftToday) { - widgetData = { state: 'no_shift' }; - } else { - const shiftLabel = `${fmtT(shiftToday.start)} – ${fmtT(shiftToday.end)}`; - const pinnedRawW = await AsyncStorage.getItem(PINNED_FLIGHT_KEY); - let pinnedFn: string | null = null; - if (pinnedRawW) { - try { pinnedFn = JSON.parse(pinnedRawW).flight?.identification?.number?.default || null; } catch {} + // 6. Notifiche fire-and-forget — non blocca lo spinner + ;(async () => { + try { + const enabled = (await AsyncStorage.getItem(NOTIF_ENABLED_KEY)) === 'true'; + const now = Date.now() / 1000; + if (enabled && shiftToday && now >= shiftToday.start && now <= shiftToday.end) { + // Notifiche temporizzate (arrivi + fine turno) + const shiftFlights = fetchedArrivals.filter(item => { + const ts = item.flight?.time?.scheduled?.arrival; + return ts && ts >= shiftToday!.start && ts <= shiftToday!.end; + }); + const count = await scheduleShiftNotifications(shiftFlights, shiftToday!.end); + setScheduledCount(count); + // Notifica persistente: prossimo volo in arrivo nel turno + const nextArrival = fetchedArrivals + .filter(item => { + const ts = item.flight?.time?.scheduled?.arrival; + return ts && ts > now && ts >= shiftToday!.start && ts <= shiftToday!.end; + }) + .sort((a, b) => (a.flight?.time?.scheduled?.arrival ?? 0) - (b.flight?.time?.scheduled?.arrival ?? 0))[0]; + const fmtT2 = (ts: number) => new Date(ts * 1000).toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' }); + const nextLabel = nextArrival + ? `Prossimo arrivo: ${nextArrival.flight?.identification?.number?.default ?? 'N/A'} alle ${fmtT2(nextArrival.flight.time.scheduled.arrival)}` + : 'Nessun arrivo imminente'; + await showShiftNotification(shiftToday!, nextLabel); + } else { + await cancelPreviousNotifications(); + await cancelShiftNotification(); + setScheduledCount(0); } - const wFlights: WidgetFlight[] = fetchedDepartures - .filter(item => { - const ts = item.flight?.time?.scheduled?.departure; - if (ts == null) return false; - const airline = item.flight?.airline?.name || ''; - const ops = getAirlineOps(airline); - const ciO = ts - ops.checkInOpen * 60, ciC = ts - ops.checkInClose * 60; - const gO = ts - ops.gateOpen * 60, gC = ts - ops.gateClose * 60; - return (ciO <= shiftToday!.end && ciC >= shiftToday!.start) || (gO <= shiftToday!.end && gC >= shiftToday!.start); - }) - .map(item => { - const ts = item.flight.time.scheduled.departure; - const airline = item.flight?.airline?.name || 'Sconosciuta'; - const ops = getAirlineOps(airline); - const fn = item.flight?.identification?.number?.default || 'N/A'; - return { - flightNumber: fn, - destinationIata: item.flight?.airport?.destination?.code?.iata || '???', - departureTs: ts, - departureTime: fmtT(ts), - ciOpen: fmtOff(ts, ops.checkInOpen), ciClose: fmtOff(ts, ops.checkInClose), - gateOpen: fmtOff(ts, ops.gateOpen), gateClose: fmtOff(ts, ops.gateClose), - airlineColor: getAirlineColor(airline), - isPinned: fn === pinnedFn, - }; - }) - .sort((a, b) => a.departureTs - b.departureTs); - - widgetData = wFlights.length === 0 - ? { state: 'work_empty', shiftLabel, updatedAt: nowHH } - : { state: 'work', shiftLabel, flights: wFlights, updatedAt: nowHH }; - } - await AsyncStorage.setItem(WIDGET_CACHE_KEY, JSON.stringify(widgetData)); - if (Platform.OS === 'android') { - requestWidgetUpdate({ widgetName: 'ShiftFlights', renderWidget: () => () as any }).catch(() => {}); - } - } catch {} + } catch {} + })(); - // Schedula notifiche se attive (solo turno di oggi) - const enabled = (await AsyncStorage.getItem(NOTIF_ENABLED_KEY)) === 'true'; - if (enabled && shiftToday) { - const shiftFlights = fetchedArrivals.filter(item => { - const ts = item.flight?.time?.scheduled?.arrival; - return ts && ts >= shiftToday!.start && ts <= shiftToday!.end; - }); - const count = await scheduleShiftNotifications(shiftFlights, shiftToday!.end); - setScheduledCount(count); - } else { - await cancelPreviousNotifications(); - setScheduledCount(0); - } } catch (e) { console.error('[fetchAll]', e); } finally { setLoading(false); setRefreshing(false); } }, [airportCode, airportLoading]); @@ -414,6 +522,8 @@ export default function FlightScreen() { if (id) setPinnedFlightId(id); } catch {} }); + // Crea il canale notifiche persistenti all'avvio + setupShiftNotifChannel(); }, []); // Toggle notifiche @@ -429,24 +539,40 @@ export default function FlightScreen() { if (!next) { await cancelPreviousNotifications(); + await cancelShiftNotification(); setScheduledCount(0); return; } // Schedula subito con i dati già caricati (turno di oggi) - if (shifts.today) { + const now = Date.now() / 1000; + if (shifts.today && now >= shifts.today.start && now <= shifts.today.end) { const shiftFlights = arrivals.filter(item => { const ts = item.flight?.time?.scheduled?.arrival; return ts && ts >= shifts.today!.start && ts <= shifts.today!.end; }); const count = await scheduleShiftNotifications(shiftFlights, shifts.today!.end); setScheduledCount(count); + // Mostra notifica persistente + const nextArrival = arrivals + .filter(item => { + const ts = item.flight?.time?.scheduled?.arrival; + return ts && ts > now && ts >= shifts.today!.start && ts <= shifts.today!.end; + }) + .sort((a, b) => (a.flight?.time?.scheduled?.arrival ?? 0) - (b.flight?.time?.scheduled?.arrival ?? 0))[0]; + const fmtT = (ts: number) => new Date(ts * 1000).toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' }); + const nextLabel = nextArrival + ? `Prossimo arrivo: ${nextArrival.flight?.identification?.number?.default ?? 'N/A'} alle ${fmtT(nextArrival.flight.time.scheduled.arrival)}` + : 'Nessun arrivo imminente'; + await showShiftNotification(shifts.today!, nextLabel); Alert.alert( 'Notifiche attivate', count > 0 - ? `Programmate ${count} notifiche: arrivi voli (15 min prima) + fine turno.` - : 'Nessun volo futuro trovato, ma riceverai la notifica di fine turno.', + ? `Programmate ${count} notifiche + notifica turno in alto.` + : 'Notifica turno attiva. Nessun volo futuro trovato.', ); + } else if (shifts.today) { + Alert.alert('Turno non in corso', 'Le notifiche si attiveranno automaticamente all\'inizio del turno.'); } else { Alert.alert('Nessun turno trovato', 'Non ho trovato un turno "Lavoro" per oggi nel calendario.'); setNotifsEnabled(false); @@ -652,11 +778,39 @@ export default function FlightScreen() { {statusText} )} + {/* ── StaffMonitor live pills ── */} + {(() => { + if (!staffData) return null; + const sf = lookupStaff(staffData, flightNumber, activeTab); + if (!sf) return null; + const pills: { icon: string; label: string; value: string }[] = []; + if (activeTab === 'departures') { + if (sf.checkIn) pills.push({ icon: 'desktop-windows', label: 'CI', value: sf.checkIn }); + if (sf.gate) pills.push({ icon: 'meeting-room', label: 'Gate', value: sf.gate }); + if (sf.stand) pills.push({ icon: 'local-parking', label: 'Stand', value: sf.stand }); + } else { + if (sf.stand) pills.push({ icon: 'local-parking', label: 'Stand', value: sf.stand }); + if (sf.belt) pills.push({ icon: 'luggage', label: 'Nastro', value: sf.belt }); + } + if (pills.length === 0) return null; + return ( + + {pills.map(p => ( + + + + {p.label}: {p.value} + + + ))} + + ); + })()} ); - }, [activeTab, userShift, s, pinnedFlightId, pinFlight, unpinFlight, inboundArrivals, colors]); + }, [activeTab, userShift, s, pinnedFlightId, pinFlight, unpinFlight, inboundArrivals, colors, staffData]); return ( @@ -750,6 +904,9 @@ function makeStyles(c: any) { pinBannerText: { color: '#fff', fontWeight: 'bold', fontSize: 11, letterSpacing: 0.5 }, statusPill: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 20, marginTop: 5 }, statusText: { fontSize: 10, fontWeight: '700' }, + staffRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginTop: 8 }, + staffPill: { flexDirection: 'row', alignItems: 'center', gap: 4, backgroundColor: c.primaryLight, borderRadius: 8, paddingHorizontal: 8, paddingVertical: 4 }, + staffPillText: { fontSize: 11 }, cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 10, paddingHorizontal: 14 }, headerLeft: { flexDirection: 'row', alignItems: 'center', gap: 10 }, headerFlightNum: { color: '#fff', fontWeight: '900', fontSize: 15, lineHeight: 18 }, diff --git a/src/utils/staffMonitor.ts b/src/utils/staffMonitor.ts new file mode 100644 index 0000000..2ad0d38 --- /dev/null +++ b/src/utils/staffMonitor.ts @@ -0,0 +1,195 @@ +/** + * staffMonitor.ts + * Fetches live gate / check-in / stand / belt data from the Pisa airport + * staff monitoring page and exposes a lookup helper for FlightScreen. + */ + +const STAFF_MONITOR_URL = + 'https://servizi.pisa-airport.com/staffMonitor/staffMonitor.html'; +const FETCH_TIMEOUT_MS = 8000; + +// ─── Types ──────────────────────────────────────────────────────────────────── + +export type StaffFlight = { + gate?: string; + checkIn?: string; + stand?: string; + belt?: string; // nastro bagagli +}; + +export type StaffMonitorData = { + departures: Record; // normalized flightNum → data + arrivals: Record; + fetchedAt: number; +}; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +function stripHtml(s: string): string { + return s + .replace(/<[^>]+>/g, '') + .replace(/ /gi, ' ') + .replace(/&/gi, '&') + .replace(/</gi, '<') + .replace(/>/gi, '>') + .replace(/\s+/g, ' ') + .trim(); +} + +/** Normalize flight number: remove spaces, uppercase, strip leading zeros from digits */ +export function normalizeFlightNum(s: string): string { + // e.g. "FR 1234" → "FR1234", "W4 0234" → "W4234", "EJU1234" → "EJU1234" + return s + .replace(/\s+/g, '') + .toUpperCase() + .replace(/([A-Z]{1,3})0+(\d)/, '$1$2'); +} + +function findColIdx(headers: string[], ...keywords: string[]): number { + return headers.findIndex(h => + keywords.some(kw => h.toLowerCase().includes(kw.toLowerCase())), + ); +} + +// ─── Parser ─────────────────────────────────────────────────────────────────── + +function parseTables(html: string): StaffMonitorData { + const result: StaffMonitorData = { + departures: {}, + arrivals: {}, + fetchedAt: Date.now(), + }; + + // Match every …
+ const tableRe = //gi; + for (const tableMatch of html.matchAll(tableRe)) { + const tableHtml = tableMatch[0]; + + // All rows + const rows = [...tableHtml.matchAll(/]*>([\s\S]*?)<\/tr>/gi)].map( + m => m[1], + ); + if (rows.length < 2) continue; + + // Header row: accept both and + const headerCells = [ + ...rows[0].matchAll(/]*>([\s\S]*?)<\/t[hd]>/gi), + ].map(m => stripHtml(m[1]).toLowerCase()); + + // Must contain a "volo / flight / numero" column to be a flight table + const voloIdx = findColIdx( + headerCells, + 'volo', + 'flight', + 'numero', + 'n.volo', + 'n. volo', + ); + if (voloIdx === -1) continue; + + // Identify columns by keyword + const gateIdx = findColIdx(headerCells, 'gate'); + const checkInIdx = findColIdx(headerCells, 'check', 'ci', 'banco', 'accettazione', 'desk'); + const standIdx = findColIdx(headerCells, 'stand', 'piazzola', 'postaz', 'parcheggio'); + const beltIdx = findColIdx(headerCells, 'nastro', 'belt', 'bagagli', 'riconsegna', 'tappeto', 'carousel'); + + // Parse data rows (skip header) + for (const row of rows.slice(1)) { + const cells = [ + ...row.matchAll(/]*>([\s\S]*?)<\/td>/gi), + ].map(m => stripHtml(m[1])); + + if (cells.length <= voloIdx) continue; + const rawFlight = cells[voloIdx]; + if (!rawFlight) continue; + + const flightNum = normalizeFlightNum(rawFlight); + // Skip rows that don't look like a flight number (e.g. empty/header repeated) + if (!/^[A-Z0-9]{2,}\d+$/.test(flightNum)) continue; + + const data: StaffFlight = {}; + if (gateIdx !== -1 && cells[gateIdx]) data.gate = cells[gateIdx]; + if (checkInIdx !== -1 && cells[checkInIdx]) data.checkIn = cells[checkInIdx]; + if (standIdx !== -1 && cells[standIdx]) data.stand = cells[standIdx]; + if (beltIdx !== -1 && cells[beltIdx]) data.belt = cells[beltIdx]; + + // Classify into departures / arrivals based on which fields are populated. + // A row can appear in both if the table is mixed. + if (data.gate || data.checkIn) { + result.departures[flightNum] = { + ...result.departures[flightNum], + ...data, + }; + } + if (data.belt || (data.stand && !data.gate && !data.checkIn)) { + result.arrivals[flightNum] = { + ...result.arrivals[flightNum], + stand: data.stand, + belt: data.belt, + }; + } + } + } + + return result; +} + +// ─── Public API ─────────────────────────────────────────────────────────────── + +export async function fetchStaffMonitor(): Promise { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); + + try { + const res = await fetch(STAFF_MONITOR_URL, { + headers: { + 'User-Agent': + 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Mobile Safari/537.36', + Accept: 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'it-IT,it;q=0.9,en;q=0.5', + }, + signal: controller.signal, + }); + + if (!res.ok) throw new Error(`HTTP ${res.status}`); + + const html = await res.text(); + return parseTables(html); + } finally { + clearTimeout(timer); + } +} + +/** + * Look up a FR24 flight number in the staffMonitor data. + * Tries several normalization strategies to handle codeshares and + * differing airline-code formats between FR24 and staffMonitor. + */ +export function lookupStaff( + data: StaffMonitorData, + flightNumber: string, + tab: 'departures' | 'arrivals', +): StaffFlight | null { + const pool = tab === 'departures' ? data.departures : data.arrivals; + if (!pool || Object.keys(pool).length === 0) return null; + + const normalized = normalizeFlightNum(flightNumber); + + // 1. Direct match + if (pool[normalized]) return pool[normalized]; + + // 2. Try without leading zeros in numeric part + const noZero = normalized.replace(/([A-Z]{1,3})0*(\d+)/, (_, p, n) => p + String(parseInt(n, 10))); + if (pool[noZero]) return pool[noZero]; + + // 3. Match on numeric suffix only (covers codeshare: FR1234 ↔ W41234) + const numSuffix = normalized.replace(/^[A-Z]+/, ''); + if (numSuffix.length >= 3) { + const found = Object.entries(pool).find(([k]) => + k.replace(/^[A-Z]+/, '') === numSuffix, + ); + if (found) return found[1]; + } + + return null; +}