diff --git a/docs/app-store-assets/README.md b/docs/app-store-assets/README.md new file mode 100644 index 0000000..c3736fc --- /dev/null +++ b/docs/app-store-assets/README.md @@ -0,0 +1,17 @@ +# App Store screenshots + +These are the sanitized English (United Kingdom) screenshots for the Switchify Remote App Store listing. + +- `iphone/`: five opaque 1242 by 2688 PNGs captured from iPhone 11 Pro Max. +- `ipad/`: five opaque 2048 by 2732 PNGs captured from iPad Pro 12.9-inch. +- Files are uploaded in filename order: pairing, Mouse, Typing, Window, and accessible Settings. + +The source captures must come from a local iOS build made with `EXPO_PUBLIC_STORE_CAPTURE=1`. This exact build-time flag replaces the production root with a sanitized fixture using production UI components and `Demo PC`. It does not mount the connection provider, initialize Bluetooth, access pairing storage, or send commands. Normal, CI, and release builds must leave the flag unset. + +The fixture cycles through pairing, Mouse, Typing, Window, and Settings every five seconds for deterministic simulator capture. A deep link to `switchify-remote://capture/` can also select a screen during manual local capture. Then generate the branded outputs: + +```text +npm run app-store:assets -- --iphone-source-dir --ipad-source-dir +``` + +Never include pairing codes, typed text, personal device names, credentials, diagnostics, notifications, or developer controls. diff --git a/docs/app-store-assets/ipad/01-pair.png b/docs/app-store-assets/ipad/01-pair.png new file mode 100644 index 0000000..eca7600 Binary files /dev/null and b/docs/app-store-assets/ipad/01-pair.png differ diff --git a/docs/app-store-assets/ipad/02-mouse.png b/docs/app-store-assets/ipad/02-mouse.png new file mode 100644 index 0000000..3ce76de Binary files /dev/null and b/docs/app-store-assets/ipad/02-mouse.png differ diff --git a/docs/app-store-assets/ipad/03-typing.png b/docs/app-store-assets/ipad/03-typing.png new file mode 100644 index 0000000..ccbc525 Binary files /dev/null and b/docs/app-store-assets/ipad/03-typing.png differ diff --git a/docs/app-store-assets/ipad/04-window.png b/docs/app-store-assets/ipad/04-window.png new file mode 100644 index 0000000..094e7f8 Binary files /dev/null and b/docs/app-store-assets/ipad/04-window.png differ diff --git a/docs/app-store-assets/ipad/05-access.png b/docs/app-store-assets/ipad/05-access.png new file mode 100644 index 0000000..c754f2d Binary files /dev/null and b/docs/app-store-assets/ipad/05-access.png differ diff --git a/docs/app-store-assets/iphone/01-pair.png b/docs/app-store-assets/iphone/01-pair.png new file mode 100644 index 0000000..a284b43 Binary files /dev/null and b/docs/app-store-assets/iphone/01-pair.png differ diff --git a/docs/app-store-assets/iphone/02-mouse.png b/docs/app-store-assets/iphone/02-mouse.png new file mode 100644 index 0000000..bfba482 Binary files /dev/null and b/docs/app-store-assets/iphone/02-mouse.png differ diff --git a/docs/app-store-assets/iphone/03-typing.png b/docs/app-store-assets/iphone/03-typing.png new file mode 100644 index 0000000..198e2ee Binary files /dev/null and b/docs/app-store-assets/iphone/03-typing.png differ diff --git a/docs/app-store-assets/iphone/04-window.png b/docs/app-store-assets/iphone/04-window.png new file mode 100644 index 0000000..c3dbba3 Binary files /dev/null and b/docs/app-store-assets/iphone/04-window.png differ diff --git a/docs/app-store-assets/iphone/05-access.png b/docs/app-store-assets/iphone/05-access.png new file mode 100644 index 0000000..d9109fc Binary files /dev/null and b/docs/app-store-assets/iphone/05-access.png differ diff --git a/docs/app-store-connect.md b/docs/app-store-connect.md index 45a7a18..50bb265 100644 --- a/docs/app-store-connect.md +++ b/docs/app-store-connect.md @@ -1,6 +1,6 @@ # App Store Connect and internal TestFlight -Switchify Remote uses App Store Connect only for internal TestFlight distribution in this milestone. External testers, beta review, public App Store metadata completion, pricing, screenshots, and App Review are separate work. +Switchify Remote uses App Store Connect for internal TestFlight distribution and a saved public-listing draft. The canonical listing values and draft-only guardrails are in [App Store listing](app-store-listing.md). External testers and App Review remain separate work. ## Apple records diff --git a/docs/app-store-listing.md b/docs/app-store-listing.md new file mode 100644 index 0000000..0ba69d7 --- /dev/null +++ b/docs/app-store-listing.md @@ -0,0 +1,82 @@ +# App Store listing + +This document is the source of truth for the English (United Kingdom) App Store listing. It authorizes saving a draft only. Do not select **Add for Review**, submit for App Review, or release the app. + +## Product page + +- Name: `Switchify Remote` +- Subtitle: `Accessible PC remote control` +- Promotional text: `Control your Windows PC or Mac from your iPhone or iPad with accessible mouse, typing and window controls over Bluetooth.` +- Keywords: `accessibility,remote control,mouse,keyboard,typing,Windows,Mac,Bluetooth,switch control` +- Primary category: Utilities +- Secondary category: Productivity +- Support URL: https://github.com/switchifyapp/switchify-remote/issues +- Marketing URL: https://switchifyapp.com +- Privacy policy: https://switchifyapp.com/privacy/remote +- Copyright: `2026 Enabo Apps` +- Price: Free +- Distribution: Public, worldwide, iPhone and iPad +- Apple silicon Mac availability: Off +- Apple Vision Pro availability: Off +- Release: Manual + +### Description + +```text +Switchify Remote lets you control a Windows PC or Mac from your iPhone or iPad over Bluetooth Low Energy. It is the companion app for Switchify PC. + +Pair securely with your computer, then choose the controls you need: + +• Mouse controls for pointer movement, clicks, dragging, scrolling, pointer speed and multiple monitors +• Typing controls for live text, drafts and common computer keys +• Window controls for switching apps, task view, desktop, minimize, maximize, close and shortcuts + +Built for access: + +• Works with VoiceOver, Voice Control and Switch Control +• Large touch targets and labels that scale without being cut off +• Light and dark themes, reduced-motion support, and phone and tablet layouts +• Clear selected, disabled, busy and connection states + +Privacy and security: + +• No account, ads, analytics or cloud sync +• Authenticated Bluetooth pairing with approval on the computer +• Pairing credentials are stored securely on your device +• Typed text and control commands go only to the paired computer +• Diagnostics stay on the device and exclude typed text, commands and pairing secrets + +Requires Switchify PC on Windows 10 or later, or an Apple-silicon Mac running macOS 13 or later. Download Switchify PC from github.com/switchifyapp/switchify-pc/releases. +``` + +## Review information + +- Sign-in required: No +- Contact: Owen McGirr, `owen@switchifyapp.com` +- Phone: entered by Owen directly at action time +- Attachment: None + +Review notes: + +```text +Switchify Remote has no account or login. It requires a Bluetooth Low Energy computer running Switchify PC: Windows 10 or later, or macOS 13 or later on Apple silicon. Download Switchify PC from https://github.com/switchifyapp/switchify-pc/releases and leave it open. On first run, continue to Connect with Bluetooth and choose Allow Bluetooth. Then select PCs > Find nearby PCs, choose the computer, compare the six-digit code, and approve the request in Switchify PC. Mouse, Typing and Window controls then become available. +``` + +Select the newest valid `1.0.0` build whose source is contained in `main` (build 19 for Beta 17 at the time this listing was prepared). + +## Declarations + +- Content rights: no third-party content requiring rights. +- Age rating: answer No to every content and capability question. Stop if Apple calculates a materially higher rating than its lowest general-audience result. +- App Privacy: the app collects no data. Publish that declaration only after action-time confirmation. +- Accessibility: VoiceOver, Voice Control, Larger Text, Dark Interface, Differentiate Without Color Alone, Sufficient Contrast, and Reduced Motion. Do not select Captions or Audio Descriptions because the app contains no media. Publish only after action-time confirmation. +- App Clip, Game Center, in-app purchases, subscriptions, and routing coverage: None. +- DSA: declare the whole developer account non-trader only after action-time confirmation. This affects every app on the account and may later be overridden per app. +- Leave the expired Paid Apps agreement untouched because this app is free. + +## Chrome completion checklist + +1. Upload the five iPhone and five iPad images from `docs/app-store-assets`, in filename order. +2. Complete and save product-page metadata, categories, build, review information, manual release, pricing, availability, content rights, age rating, privacy, accessibility, and DSA sections. +3. Before saving declarations, obtain confirmation for the account-wide non-trader status, public privacy/accessibility declarations, and transmission of Owen's review email and phone to Apple. Owen enters the phone directly. +4. Confirm the version remains **Prepare for Submission**. Never select **Add for Review**. diff --git a/package-lock.json b/package-lock.json index c5fb53b..28884a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,36 +8,36 @@ "name": "switchify-remote", "version": "1.0.0-beta.17", "dependencies": { - "@expo/ui": "~57.0.13", + "@expo/ui": "~57.0.14", "@expo/vector-icons": "^15.0.2", "@noble/hashes": "^2.3.0", "@react-native-async-storage/async-storage": "2.2.0", "@react-navigation/native": "^7.3.16", "base64-js": "^1.5.1", - "expo": "~57.0.16", + "expo": "~57.0.18", "expo-blur": "~57.0.2", - "expo-build-properties": "~57.0.14", + "expo-build-properties": "~57.0.15", "expo-clipboard": "~57.0.1", - "expo-constants": "~57.0.12", + "expo-constants": "~57.0.16", "expo-crypto": "~57.0.2", - "expo-dev-client": "~57.0.15", + "expo-dev-client": "~57.0.16", "expo-device": "~57.0.1", - "expo-file-system": "~57.0.4", - "expo-font": "~57.0.1", + "expo-file-system": "~57.0.6", + "expo-font": "~57.0.2", "expo-glass-effect": "~57.0.1", "expo-image": "~57.0.3", - "expo-linking": "~57.0.7", - "expo-router": "~57.0.16", - "expo-secure-store": "~57.0.1", - "expo-sharing": "~57.0.15", + "expo-linking": "~57.0.8", + "expo-router": "~57.0.17", + "expo-secure-store": "~57.0.2", + "expo-sharing": "~57.0.16", "expo-splash-screen": "~57.0.8", "expo-status-bar": "~57.0.1", "expo-symbols": "~57.0.2", - "expo-system-ui": "~57.0.2", + "expo-system-ui": "~57.0.3", "expo-web-browser": "~57.0.2", "react": "19.2.3", "react-dom": "19.2.3", - "react-native": "0.86.2", + "react-native": "0.86.3", "react-native-ble-plx": "^3.5.1", "react-native-gesture-handler": "~2.32.0", "react-native-reanimated": "4.5.1", @@ -49,13 +49,14 @@ "devDependencies": { "@emnapi/core": "^1.11.3", "@emnapi/runtime": "^1.11.3", + "@react-native/jest-preset": "0.86.3", "@testing-library/react-native": "^14.0.1", "@types/jest": "^29.5.14", "@types/react": "~19.2.2", "eslint": "^9.39.5", - "eslint-config-expo": "^57.0.1", + "eslint-config-expo": "~57.0.2", "jest": "^29.7.0", - "jest-expo": "^57.0.4", + "jest-expo": "~57.0.5", "prettier": "^3.9.6", "typescript": "~6.0.3" } @@ -88,7 +89,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -1154,6 +1154,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, @@ -1169,6 +1170,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, @@ -1200,6 +1202,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, @@ -1402,7 +1405,6 @@ "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" @@ -1414,7 +1416,6 @@ "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -1760,9 +1761,9 @@ } }, "node_modules/@expo/env": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.4.2.tgz", - "integrity": "sha512-28pqaEqwnmLduZ00Pq9HkSzE5wbj1MTwp5/n8nm8rD8MCjR9eUnVOwmNksPI3Be2ReAPO/DbPn1puy0mvoocsQ==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.4.3.tgz", + "integrity": "sha512-M1NXeZCA1mkMkYOyIe7PlyRX0/jqFtMoJgyblnlq/vpCRfmueFT7RnGSQG8uEFDF5WHOFGijAQ3fogPh3/n5Ng==", "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -1780,12 +1781,12 @@ "license": "MIT" }, "node_modules/@expo/fingerprint": { - "version": "0.20.10", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.20.10.tgz", - "integrity": "sha512-Ox/smKlpPpyTUwZAXS927xuJ8qICwNxHOtZElv0anp6xJ80FzwkfvCdC/NjCxlALS2Hp3IsmwyKd975gYgW12Q==", + "version": "0.20.11", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.20.11.tgz", + "integrity": "sha512-GC43EcjQwzpzNZISqmhvjKdi+6FTfxnC9ZxOeg6osCkYXJz94j107l0QpPLipg62khD5HAA5G5cUhevffbRC4w==", "license": "MIT", "dependencies": { - "@expo/env": "^2.4.2", + "@expo/env": "^2.4.3", "@expo/spawn-async": "^1.8.0", "arg": "^5.0.2", "chalk": "^4.1.2", @@ -1817,12 +1818,12 @@ } }, "node_modules/@expo/inline-modules": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@expo/inline-modules/-/inline-modules-0.1.6.tgz", - "integrity": "sha512-5f6EiOIKsFj9zlrCBet4ZIQRPEa9dBUdQgTzpjYwdZ8Z/M8W5lqMVL9dEs4HYKvEmDnqv0dQuDkFLyRSwu/DAQ==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@expo/inline-modules/-/inline-modules-0.1.7.tgz", + "integrity": "sha512-Bz/khd1gIJqDkje7t5ejD5e9jFbm4xEJzWSwRKadRo6gruepbw1xJ3Eb+e58OS8fY1ZNQYjzZbspnuph67sN0g==", "license": "MIT", "dependencies": { - "@expo/config-plugins": "~57.0.8" + "@expo/config-plugins": "~57.0.9" } }, "node_modules/@expo/json-file": { @@ -1836,19 +1837,19 @@ } }, "node_modules/@expo/local-build-cache-provider": { - "version": "57.0.7", - "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-57.0.7.tgz", - "integrity": "sha512-Hq5xWhXJWuyH3CWh3uqDWoHtxM0XYqLs9h2OzRWImBJahCPfePOo4kmw2uTEB6qHT5T47eqO4jtDG3MRGGZCpw==", + "version": "57.0.8", + "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-57.0.8.tgz", + "integrity": "sha512-SEdE0pAQrr90bRh3MNR0ZuwoIBw390cYdFgbn7Vk0Mtm9EHaBfP7kYj+2hXnXZJgOEm3/JMtfoD3rV7rWA9FGg==", "license": "MIT", "dependencies": { - "@expo/config": "~57.0.8", + "@expo/config": "~57.0.9", "chalk": "^4.1.2" } }, "node_modules/@expo/log-box": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-57.0.3.tgz", - "integrity": "sha512-qv/cMliNax6es07Un/4IGJIcs4PUuhTKKTrJZX/0X2YRcOT5cqm/QicibZwTIbiZWi1i8P4YBRQTfFT2B17giw==", + "version": "57.0.4", + "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-57.0.4.tgz", + "integrity": "sha512-IxwS9s1L2muj8mj8AQSuiy7u8OFJdc02NRFo2me/Tj6DiaeG5SREqmpBE4rQpR2cadqSg5jl8Qab8Cjie616dg==", "license": "MIT", "dependencies": { "@expo/dom-webview": "^57.0.1", @@ -1885,16 +1886,16 @@ } }, "node_modules/@expo/metro-config": { - "version": "57.0.10", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-57.0.10.tgz", - "integrity": "sha512-YHcocILXfcBrwPlX5CtMKy0MitssjyHQg7ndFq+P26GzMnk8uXMpw9PFo/aNaAwh7d27/cOhbZON/TJdoHfVdQ==", + "version": "57.0.12", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-57.0.12.tgz", + "integrity": "sha512-S62Lrq35HZqBFD55423pmWb8PjaiR/W02zQC1uECBmw1vTN8WZaFz4TJ0i21EeJzwfebMb9MLxL8JZ102Z6VbA==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@expo/config": "~57.0.9", - "@expo/env": "~2.4.2", + "@expo/env": "~2.4.3", "@expo/json-file": "~11.0.1", "@expo/metro": "~56.0.2", "@expo/require-utils": "^57.0.5", @@ -1938,19 +1939,19 @@ } }, "node_modules/@expo/metro-runtime": { - "version": "57.0.13", - "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-57.0.13.tgz", - "integrity": "sha512-o5VKd/vGti4z8uEJRAvKgzUZf+b5UE6eXx9R9rjT5UFmcnjCGXjv5LNbzvcb8wiSSakDDXE/z4wpOhJEPqV5JA==", + "version": "57.0.14", + "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-57.0.14.tgz", + "integrity": "sha512-LVpsfF0QOzlLSKlHYpxgW1cP1NKcYeaoalLO18jYHgqseSveEkb9VOffFpicTQ7AN03odZ4fgkUF+p5YpoY4yw==", "license": "MIT", "dependencies": { - "@expo/log-box": "^57.0.3", + "@expo/log-box": "^57.0.4", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { - "@expo/log-box": "^57.0.3", + "@expo/log-box": "^57.0.4", "expo": "*", "react": "*", "react-dom": "*", @@ -2294,9 +2295,9 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "57.0.14", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-57.0.14.tgz", - "integrity": "sha512-Lopsj0O/gqjVf94ZL8OH8Q7OV7YsREzxC+ScUQjgmYP5DTLlB193C6WO4hq/Mtia52NBLVAjRVMbW6TUJQtiWA==", + "version": "57.0.15", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-57.0.15.tgz", + "integrity": "sha512-xTbWHroj0PDmlbqvmU+zF9ZZxveJkiuyiPoeRJYRGruFHebRAWnoTdw5S7d/UCzDBI8ropGGu9g2eb2nMxtvAw==", "license": "MIT", "dependencies": { "@expo/config": "~57.0.9", @@ -2304,9 +2305,9 @@ "@expo/config-types": "^57.0.2", "@expo/image-utils": "^0.11.5", "@expo/json-file": "^11.0.1", - "@react-native/normalize-colors": "0.86.2", + "@react-native/normalize-colors": "0.86.3", "debug": "^4.3.1", - "expo-modules-autolinking": "~57.0.11", + "expo-modules-autolinking": "~57.0.12", "resolve-from": "^5.0.0", "semver": "^7.6.0" } @@ -2361,9 +2362,9 @@ "license": "MIT" }, "node_modules/@expo/ui": { - "version": "57.0.13", - "resolved": "https://registry.npmjs.org/@expo/ui/-/ui-57.0.13.tgz", - "integrity": "sha512-vGM2iVFKFg4P+XESrPPfCkHws9HuE5bzzw3BuxdJjIcHiVqq3lQlIyDTMTMg66sjYVRtqR6wAeadj1uDFYcRcg==", + "version": "57.0.14", + "resolved": "https://registry.npmjs.org/@expo/ui/-/ui-57.0.14.tgz", + "integrity": "sha512-m8Z5dCplkOOQ70lu/xfB67qhk00Muw4U1T9frHBIW3LxxJeJZQ+jOmV6vCk6O6zVgoG/ib1et7PeX6etxI4Rsw==", "license": "MIT", "dependencies": { "sf-symbols-typescript": "^2.1.0", @@ -3559,32 +3560,33 @@ } }, "node_modules/@react-native/assets-registry": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.2.tgz", - "integrity": "sha512-vcX/mBjWAVnWofu7KecotquI2unZ/tITwA7OGdq/mdY/zmGXIEvYhfEYyOQij/LRqi9WAL+iizInTBWnxDhK/Q==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.3.tgz", + "integrity": "sha512-TDhgCZA4wjJg84d5A9swiOQYPIWSKEEVdg9IwMFZDupQzW/F3QoLUrfAJOcalgqTDA9/buTB8awhE3Whwg6u9Q==", "license": "MIT", "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.86.2.tgz", - "integrity": "sha512-NNDZqOlNbH5SzgPks1jFDYH3234Rpa5e/nhZymxhIiBH3NcE3uD+rGj/HWXhH7nHF2ToGK6XbUpqy7nmJPeh+g==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.86.3.tgz", + "integrity": "sha512-O6Xza4JBGPIU8J7YbKTyBoYL4thpy8jMW/oaLDWdAyOwYHKIjK47pAL5HUEbOe2bWz2PEKjbYRF2ApkJv1ottQ==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.0", - "@react-native/codegen": "0.86.2" + "@react-native/codegen": "0.86.3" }, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/babel-preset": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.86.2.tgz", - "integrity": "sha512-4XKEJ6jKW9lXMB1O5o47gBoGgolde1fbX13gLW/erlcn+1ky+MHHo5UjuM3RWGdPJHIvIzekDDumSUHhB9x5iQ==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.86.3.tgz", + "integrity": "sha512-/eqs/Hy9RZRcjdcs4wj3Cqmxvtb3NM5g+Uuh1RIvsjynMO8PRsrVWWLWgBcZL/jYUo+ogXd2NFB0W8L5Bg89xw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", @@ -3615,7 +3617,7 @@ "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@react-native/babel-plugin-codegen": "0.86.2", + "@react-native/babel-plugin-codegen": "0.86.3", "babel-plugin-syntax-hermes-parser": "0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" @@ -3632,6 +3634,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.0.tgz", "integrity": "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==", "license": "MIT", + "peer": true, "dependencies": { "hermes-parser": "0.36.0" } @@ -3640,21 +3643,23 @@ "version": "0.36.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@react-native/babel-preset/node_modules/hermes-parser": { "version": "0.36.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", "license": "MIT", + "peer": true, "dependencies": { "hermes-estree": "0.36.0" } }, "node_modules/@react-native/codegen": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.2.tgz", - "integrity": "sha512-xKkudsahUJ1n//55g4fXk5BStVqqmZlz8HQveL45ZxcfDnwvhuYe2GymksQANFsSN+slvrarjrfq8kIxJzbceA==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.3.tgz", + "integrity": "sha512-Ux4jHi0fh+bdtVEcL0gaPLbY56V+SvFUDl/8sRAE1jdb4k+o7fT/4Nc29yz4X+qfjstkSqObQTMBGhdzxH9JvA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -3688,12 +3693,12 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.86.2.tgz", - "integrity": "sha512-YHXNKoM6Y/HjREySZ5arET2xgiHgg67r1MdwJB//MPJAJ0Xc5g0u6UHxY9VzsHO3Y07dre6s0BinYwjt1SEWvQ==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.86.3.tgz", + "integrity": "sha512-qSDL9LQc5mZSZPNczT95WU9YQuPzxBklgON9vLhhqfI0yWIwKInqFx88dQ/uiEXBtf0yossthaQIqA4Ml6bF6g==", "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.86.2", + "@react-native/dev-middleware": "0.86.3", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.84.3", @@ -3706,7 +3711,7 @@ }, "peerDependencies": { "@react-native-community/cli": "*", - "@react-native/metro-config": "0.86.2" + "@react-native/metro-config": "0.86.3" }, "peerDependenciesMeta": { "@react-native-community/cli": { @@ -3718,18 +3723,18 @@ } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.2.tgz", - "integrity": "sha512-KGS1aV5F6cIqpnoIUhLBXyVzy1oAj8jBFGau6vX4Vy0HXRJN7p+68RU7x6NuyraHvQcR14ccMGT5TkFuNjQ4gA==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.3.tgz", + "integrity": "sha512-TQmeofQ0PcuylhhlleOeuzHYZfbrgm3gayXzowqUEzgRisTm1D40/J3ggqs7XkQi5HP5ZA3n8dHmKL9vIzPcsw==", "license": "BSD-3-Clause", "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/debugger-shell": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.2.tgz", - "integrity": "sha512-/TaVJ2+gGajZPJGrFaObUQmHmlaxAlfmOPZicl6pNKDUjzSgFMpcLkdTOExvb+USYTVdGX1XwxXyvjQdUO2bvg==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.3.tgz", + "integrity": "sha512-O4ds+J7xZfxkbih9T+cAGegBdvKSPKYJm/lDgC9CpEjFMkmzWTpVLU3Qsv9sqZuo58z+sGhIcJfPsCFFWHpqbQ==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6", @@ -3741,14 +3746,14 @@ } }, "node_modules/@react-native/dev-middleware": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.2.tgz", - "integrity": "sha512-B7L0vKvg+IcEElT7Vpqh1xj5yJAqWUegjbP+bQRaorJMAYnv11GkliTnZV2AdTDfZQJWgOEx8i8LGkHkUg7bnA==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.3.tgz", + "integrity": "sha512-LiEPTqTg/63bYUnrPyHLfjTDCNhA/+CUqI1+DsA9tYyewtSbULd5awsva6SgE10I+2iMhgKXS3ymkhU/kSCrGA==", "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.86.2", - "@react-native/debugger-shell": "0.86.2", + "@react-native/debugger-frontend": "0.86.3", + "@react-native/debugger-shell": "0.86.3", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.3.0", "connect": "^3.6.5", @@ -3764,24 +3769,23 @@ } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.86.2.tgz", - "integrity": "sha512-2F6x14NcHMpVmfTTFKfMkpV5dZedZrLiv6PE+c3vgnesV2bjleUBydr4U+NI8VkI7OwW71L0A5qQ76I9LCrfoQ==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.86.3.tgz", + "integrity": "sha512-lxmx0GqLEWRIpZfpYFXlYVIs3ENQwaW6Vmp6oi29l2GoQJ1wZfFZRdMimDWlGEk8LKfHar3QH3iaPMkTcK9lEQ==", "license": "MIT", "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/jest-preset": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/jest-preset/-/jest-preset-0.86.2.tgz", - "integrity": "sha512-wneoqwKWdv6wIcWotVgp6NMlMWcJDjxDnp7fVYym2Pn6JLgAKgkbmuHGmxW0cLCGoa9wtcO680uciv+Kzx0G7w==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/jest-preset/-/jest-preset-0.86.3.tgz", + "integrity": "sha512-oMmBwXUpTgJvGlcXQQPuiQLykoM8jsWYp6RwdJ2NWyIWzJcGp1rmSr3XV7istoIcUt0wdOVLtQs0A0nDm9ND+A==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", - "@react-native/js-polyfills": "0.86.2", + "@react-native/js-polyfills": "0.86.3", "babel-jest": "^29.7.0", "jest-environment-node": "^29.7.0", "regenerator-runtime": "^0.13.2" @@ -3794,22 +3798,23 @@ } }, "node_modules/@react-native/js-polyfills": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.86.2.tgz", - "integrity": "sha512-bIwNcGBaQ74shB5z1mRkxOpjikimuwsnOCEkZSzL67Z1FTyK1ObpENfyd2QvcvVW9Cjl+tHuw9ynpBnb2jPoJQ==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.86.3.tgz", + "integrity": "sha512-eYIJ0es967+tePBFQDnl/gidVFxLns3fnbiK6rxscQrGodvuUO6hwxpQnfNynJ8MjbbndImXihXjcnJdc7SzJg==", "license": "MIT", "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/metro-babel-transformer": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.86.2.tgz", - "integrity": "sha512-mX1wgLErdb2hDgXJr9zM9SWLe+ZteZTFTwRWGOQ53yEJwc9DVSnxdTlAtVdMeOj2ntycKh0R8jYdat2Am43cwQ==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.86.3.tgz", + "integrity": "sha512-0nlwVkG0uT9o72nrGTKUz/IqDnCkM4zE0ihe4r+OJcxH93yBdDm58AXJ3EXZeSjv3osHEFXU7ceajdZiS34mTw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.86.2", + "@react-native/babel-preset": "0.86.3", "hermes-parser": "0.36.0", "nullthrows": "^1.1.1" }, @@ -3824,25 +3829,28 @@ "version": "0.36.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { "version": "0.36.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", "license": "MIT", + "peer": true, "dependencies": { "hermes-estree": "0.36.0" } }, "node_modules/@react-native/metro-config": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.86.2.tgz", - "integrity": "sha512-hJno256j+MS0b3JD1aD3ouTGZVacKNVBuXL2atMQQ8BZ060vl1ptnZ83y569aDW+/rgFSOcqn6ydKeSz4uUKQQ==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.86.3.tgz", + "integrity": "sha512-qdzDMepV2xdUhsO4XC7idnnbt8K6+Hd59AjeK9y4KS86fxSQq5oL0TAbqUuajDGR7beNQoYTuS6AowN/zP0oaQ==", "license": "MIT", + "peer": true, "dependencies": { - "@react-native/js-polyfills": "0.86.2", - "@react-native/metro-babel-transformer": "0.86.2", + "@react-native/js-polyfills": "0.86.3", + "@react-native/metro-babel-transformer": "0.86.3", "metro-config": "^0.84.3", "metro-runtime": "^0.84.3" }, @@ -3851,15 +3859,15 @@ } }, "node_modules/@react-native/normalize-colors": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.2.tgz", - "integrity": "sha512-EzPFc9Y6lzYOWeso2almwXI7f8+qReHxWvT+algsOczb2UhWXIWXDoSvkdwoSfiwwmGt/ijJgKJoeHlzPkLwRg==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.3.tgz", + "integrity": "sha512-Cv3CDkprb67GrzuaS9BGbBJC/6G4lIw3nyKOHRKTqTTum4bn37y5+R0Z04L8mcbQN85eEohNrRwb7IOM4j6uvg==", "license": "MIT" }, "node_modules/@react-native/virtualized-lists": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.86.2.tgz", - "integrity": "sha512-uO0J72gh3EvE+1/GHRk18QRyBDTRHRB0AraAfojsRjbT7VMuJwKrZYaKGshavoaEud6aw00ZB9/8mTMIKjjcAw==", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.86.3.tgz", + "integrity": "sha512-1j44NEyNn05Ut40vHAmoSWbsIcybFkMAOBTwQt1PrESyfSS+qBoyU1LGIogNva0VIa0rQyEC5PzbA7R4/7Nhyw==", "license": "MIT", "dependencies": { "invariant": "^2.2.4", @@ -3871,7 +3879,7 @@ "peerDependencies": { "@types/react": "^19.2.0", "react": "*", - "react-native": "0.86.2" + "react-native": "0.86.3" }, "peerDependenciesMeta": { "@types/react": { @@ -4207,7 +4215,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4218,6 +4225,7 @@ "integrity": "sha512-HZOXsKT0tGI9LlUw2LuedXsVeB88wFa536vVL0M6vE8zN63nI+sSr1ByxmPToP5K5bukaVscyeCJcF9guVNJ1g==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "*" } @@ -4305,7 +4313,6 @@ "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", @@ -4891,7 +4898,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5442,9 +5448,9 @@ } }, "node_modules/babel-preset-expo": { - "version": "57.0.8", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-57.0.8.tgz", - "integrity": "sha512-i4XOFUayGudQ845hbzKYYDAr2CL9jF1CQh8ehZTlNu3SKsWEIM+1KyY91P/3aqiEDsQOu5Pa/dVs937HvQTXig==", + "version": "57.0.9", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-57.0.9.tgz", + "integrity": "sha512-T19biGOBTnMp161PyBqWOoSIEeZug8/EjtuUdVob8JPQKZMTalHUQaQt+qtQQE7XsEkZJ6erb3k0CohneTFzQg==", "license": "MIT", "dependencies": { "@babel/generator": "^7.20.5", @@ -5483,7 +5489,7 @@ "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-plugin-codegen": "0.86.2", + "@react-native/babel-plugin-codegen": "0.86.3", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.36.0", @@ -5493,7 +5499,7 @@ "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", - "expo-widgets": "^57.0.12", + "expo-widgets": "^57.0.13", "react-refresh": ">=0.14.0 <1.0.0" }, "peerDependenciesMeta": { @@ -5639,7 +5645,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", @@ -6857,7 +6862,6 @@ "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6913,9 +6917,9 @@ } }, "node_modules/eslint-config-expo": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-57.0.1.tgz", - "integrity": "sha512-OsLAReXOF3uXBwyxxw4f55sORYC+lOH4pSaGdU9MnYhRpjqPziK2uCnRxjLTGKeAKER7GSX5Wz9kNMGJT1MZEw==", + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-57.0.2.tgz", + "integrity": "sha512-dOWkx+MWclLVnYpPPBzas4sfnPaAjk+fe/dbvJSzbWtHUR4fGGaTT/lTcOpxqhwRIlTKKBUYGKude0tcTBsZHg==", "dev": true, "license": "MIT", "dependencies": { @@ -7065,7 +7069,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7621,32 +7624,31 @@ } }, "node_modules/expo": { - "version": "57.0.16", - "resolved": "https://registry.npmjs.org/expo/-/expo-57.0.16.tgz", - "integrity": "sha512-+RyhqrVXDJDJGlhV24DM8pLmofrR63J5q9xNcbWJlRefR7ggVgHmrl2DNs7qDYSlbFOAbIM0ijN7wanMi2PAMw==", + "version": "57.0.18", + "resolved": "https://registry.npmjs.org/expo/-/expo-57.0.18.tgz", + "integrity": "sha512-6nax9hJPhf9dWrstliXUABTJXDNIJrfJKcCtjSxuYVs2Yf127GIhKf3wsEYSFUl+LFdRbPPTNaweJePH159wZA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "^57.0.18", + "@expo/cli": "^57.0.20", "@expo/config": "~57.0.9", "@expo/config-plugins": "~57.0.9", "@expo/devtools": "~57.0.1", "@expo/dom-webview": "~57.0.1", - "@expo/fingerprint": "^0.20.10", - "@expo/local-build-cache-provider": "^57.0.7", - "@expo/log-box": "^57.0.3", + "@expo/fingerprint": "^0.20.11", + "@expo/local-build-cache-provider": "^57.0.8", + "@expo/log-box": "^57.0.4", "@expo/metro": "~56.0.2", - "@expo/metro-config": "~57.0.10", + "@expo/metro-config": "~57.0.12", "@ungap/structured-clone": "^1.3.0", - "babel-preset-expo": "~57.0.8", - "expo-asset": "~57.0.14", - "expo-constants": "~57.0.14", - "expo-file-system": "~57.0.5", - "expo-font": "~57.0.1", + "babel-preset-expo": "~57.0.9", + "expo-asset": "~57.0.15", + "expo-constants": "~57.0.16", + "expo-file-system": "~57.0.6", + "expo-font": "~57.0.2", "expo-keep-awake": "~57.0.1", - "expo-modules-autolinking": "~57.0.11", - "expo-modules-core": "~57.0.13", + "expo-modules-autolinking": "~57.0.12", + "expo-modules-core": "~57.0.14", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.2" @@ -7684,13 +7686,13 @@ } }, "node_modules/expo-asset": { - "version": "57.0.14", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-57.0.14.tgz", - "integrity": "sha512-OR1wVSRcF6g8clB0tk3rY/uv3k8MUHf5PN6cHulnNpjkl5IYWpCvkcTZT3RFiJA9ThkumvWaWIJWtLAIeesTJw==", + "version": "57.0.15", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-57.0.15.tgz", + "integrity": "sha512-ZoiUftQb1Nn1jvL6l1kZoUXvgRCx57tMQYxSsp+g4ZvY+jgVWp/THruXOtPtZVdC/WWZxPUTct53bfAm2EPDAQ==", "license": "MIT", "dependencies": { "@expo/image-utils": "^0.11.5", - "expo-constants": "~57.0.14" + "expo-constants": "~57.0.15" }, "peerDependencies": { "expo": "*", @@ -7710,9 +7712,9 @@ } }, "node_modules/expo-build-properties": { - "version": "57.0.14", - "resolved": "https://registry.npmjs.org/expo-build-properties/-/expo-build-properties-57.0.14.tgz", - "integrity": "sha512-gIU7e37gHWDcozQPelPzGlIa+lg8s8/O/gSuU/M7isy46NMdrBZIu/1TCPuL6LwRUCErNvuzNs3t6ljtiGSOkw==", + "version": "57.0.15", + "resolved": "https://registry.npmjs.org/expo-build-properties/-/expo-build-properties-57.0.15.tgz", + "integrity": "sha512-qZe9pnxlBpHT7SSNDR2a3C+P34xv67Ep9I0Erh+3xCczGlokyRd4pu9h0PrBdsVlr1d0QREQ1Do/+IgrIWkQQA==", "license": "MIT", "dependencies": { "@expo/schema-utils": "^57.0.2", @@ -7735,13 +7737,12 @@ } }, "node_modules/expo-constants": { - "version": "57.0.14", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.14.tgz", - "integrity": "sha512-c9WlP18GqXRm/Kaa7rALRbpFvWiuijrXu3C/38iYdHt9lscA8deJhfK7TURRApWLacdzre5mnNmHdEthNeCEjg==", + "version": "57.0.16", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.16.tgz", + "integrity": "sha512-HG3yJjGZo2BPTo2F1sBoxdJZRwX7d9C554EhKn8m12K62pxGolCsvPSoh/lQf95OEP9vO34wwXB2C3QFs0KG0g==", "license": "MIT", - "peer": true, "dependencies": { - "@expo/env": "~2.4.2" + "@expo/env": "~2.4.3" }, "peerDependencies": { "expo": "*", @@ -7758,13 +7759,13 @@ } }, "node_modules/expo-dev-client": { - "version": "57.0.15", - "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-57.0.15.tgz", - "integrity": "sha512-3ryaF/bVBmq7v0jzg07zkBvPmfW6B34xZbEU0j18p6nedtJjAMfa2wNmyB7Eq1I/vhhxs5uI21nh08eRaZgF2g==", + "version": "57.0.16", + "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-57.0.16.tgz", + "integrity": "sha512-Txss9sgzEFI0E+4I34E76vPiukeVDHnqCj0ZXy8SDijVM31/P6MCNniVbEZk7MBPQclD0aopfuaCFtkV8+r1sw==", "license": "MIT", "dependencies": { - "expo-dev-launcher": "~57.0.15", - "expo-dev-menu": "~57.0.15", + "expo-dev-launcher": "~57.0.16", + "expo-dev-menu": "~57.0.16", "expo-dev-menu-interface": "~57.0.0", "expo-manifests": "~57.0.1", "expo-updates-interface": "~57.0.1" @@ -7774,13 +7775,13 @@ } }, "node_modules/expo-dev-launcher": { - "version": "57.0.15", - "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-57.0.15.tgz", - "integrity": "sha512-lTRHfPcDKog/lK4RgQdN/252lplGm3MZ5DrVjVsUcmgjx6YfIohMRtkW3DTXXdRfTRcfpGUIFKg9JZvc1RO5kA==", + "version": "57.0.16", + "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-57.0.16.tgz", + "integrity": "sha512-jaBr6q4A5js/r465m2mRxN8A87YOi8I6PiIt8XIt4qfQWDgw6hqGovaF1AVFGT1Z5jxkjtgK8WNz/rAeypjqPQ==", "license": "MIT", "dependencies": { "@expo/schema-utils": "^57.0.2", - "expo-dev-menu": "~57.0.15", + "expo-dev-menu": "~57.0.16", "expo-manifests": "~57.0.1" }, "peerDependencies": { @@ -7789,9 +7790,9 @@ } }, "node_modules/expo-dev-menu": { - "version": "57.0.15", - "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-57.0.15.tgz", - "integrity": "sha512-T6kJ/G9P+Ef5MS3TpH/JBtZIt+v4kQKUED1gzUV9aqKfCKhI6jC3rVV3vTGqBMOA2/RaP2VlkmTrg9/aSXtlRA==", + "version": "57.0.16", + "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-57.0.16.tgz", + "integrity": "sha512-KzMHtAmnr0GqyU6MCrKLzIo/AfOjcToGhzK+pFeXlcqSPOrPJbTGlZqQ3E1o2G2jZMs+M1XvXh/VgnRyJUaRGw==", "license": "MIT", "dependencies": { "expo-dev-menu-interface": "~57.0.0" @@ -7823,9 +7824,9 @@ } }, "node_modules/expo-file-system": { - "version": "57.0.5", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.5.tgz", - "integrity": "sha512-XjGrCClF0935y5wLB9qNQQUVptNEy+0OloBstXlCd+IeNXLo3uNOod6cX4N0hofIhNIrN1Al8fwfay7R0Z1a2A==", + "version": "57.0.6", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.6.tgz", + "integrity": "sha512-pm8PMYEW6BnVOCBJ7df9FcDmQtE1tqImuYphlfYe1ipQRLYtdCayRczJcbKIsnB3mqEyp4gC2gWmMbsAsAOjVg==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -7833,11 +7834,10 @@ } }, "node_modules/expo-font": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.1.tgz", - "integrity": "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==", + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.2.tgz", + "integrity": "sha512-eWR0CAdysgVqg8VewJ8b5wKh1pa6C8YShIEU3SP0+hlSqOk4djUwuJjn3P04KHVzL3hws9FrUuPxCNmd4TGung==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -7895,13 +7895,12 @@ } }, "node_modules/expo-linking": { - "version": "57.0.7", - "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-57.0.7.tgz", - "integrity": "sha512-Ujn4kY0bd1MoJFLYuvIUWamlZH61pkahrUpug3hBtOeS89LDbPyKNjjmWef6+CPTGQfed2PlfuMZuPO1BCrg5w==", + "version": "57.0.8", + "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-57.0.8.tgz", + "integrity": "sha512-gv0ZNaF7tILbcvX8xinNbQr6GIGQStxeUAJseweP+LjC9ped7Wl6EeyIMBlDR83U6Da+8drVeqBQSMvwAXgytA==", "license": "MIT", - "peer": true, "dependencies": { - "expo-constants": "~57.0.13", + "expo-constants": "~57.0.15", "invariant": "^2.2.4" }, "peerDependencies": { @@ -7922,9 +7921,9 @@ } }, "node_modules/expo-modules-autolinking": { - "version": "57.0.11", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.11.tgz", - "integrity": "sha512-cvbEwYDKGhvMWoStZVceCdFRvrizm82uNMIDKIeo6NO4X/QC7PN4Eqm7Vn57DmgBdZNBJIk/647/NpNowPqAxQ==", + "version": "57.0.12", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.12.tgz", + "integrity": "sha512-Q8KAlq37nLKsQ+HsS9NpQVpd5jCgqtu694TDUNHBUBpV9ViD82mRBh8Uug/h68RG9xnLS+kuL4nYaCuFRghHjg==", "license": "MIT", "dependencies": { "@expo/require-utils": "^57.0.5", @@ -7937,13 +7936,13 @@ } }, "node_modules/expo-modules-core": { - "version": "57.0.13", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.13.tgz", - "integrity": "sha512-RQRlhb5ETd5zgS4RV02ktZRLGXFAe8EgK6gywQTq1lBZkthUrVsmg9j5xQ9nijQmTZqdsJtHNifaQdNoKf7ugw==", + "version": "57.0.14", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.14.tgz", + "integrity": "sha512-Cg3aQnVsQhZcMOF/RFgPGzMuhIak5W9eR9ecwmFSeQ813r4/49ut6RMA6PqUuUKuPCUIRqnBAHfSyTm1W9nfzg==", "license": "MIT", "dependencies": { "@expo/expo-modules-macros-plugin": "0.6.1", - "expo-modules-jsi": "~57.0.5", + "expo-modules-jsi": "~57.0.6", "invariant": "^2.2.4" }, "peerDependencies": { @@ -7958,25 +7957,24 @@ } }, "node_modules/expo-modules-jsi": { - "version": "57.0.5", - "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-57.0.5.tgz", - "integrity": "sha512-NQF7MUF0j3rQHV8KJqETYA1SrJr3USvQ/AWEhODo+unMRFBDP9BKpn8+aNGGmx6fa2XLnqC2qulVw4DT6Gp13Q==", + "version": "57.0.6", + "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-57.0.6.tgz", + "integrity": "sha512-WK0xFEe0FdZTCLCdfXK+HVNYfAmEbA6goecRxqjr0gh3XYRKAr9tikxqcM0ItLQoEPKntscPZhWcPLGvr97Tfw==", "license": "MIT", "peerDependencies": { "react-native": "*" } }, "node_modules/expo-router": { - "version": "57.0.16", - "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-57.0.16.tgz", - "integrity": "sha512-k2ykpt91f2tVuaK+NTlyZPpy7ZL+VigGdgcrnTg+3KMDLBItXFAZezubeOe93CiirjJpXt5ktgQz8QMSse22HA==", + "version": "57.0.17", + "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-57.0.17.tgz", + "integrity": "sha512-zKAfGLxrbdkUFzzW1xHezLXPSa0V4DlYKEWweDUUFGgnPWPNy3OY0EKYAqOhlBGSDebAvtnJXn+2zyyJjYKwyg==", "license": "MIT", - "peer": true, "dependencies": { - "@expo/log-box": "^57.0.3", - "@expo/metro-runtime": "^57.0.13", + "@expo/log-box": "^57.0.4", + "@expo/metro-runtime": "^57.0.14", "@expo/schema-utils": "^57.0.2", - "@expo/ui": "^57.0.13", + "@expo/ui": "^57.0.14", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-native-masked-view/masked-view": "^0.3.2", @@ -8002,12 +8000,12 @@ "vaul": "^1.1.2" }, "peerDependencies": { - "@expo/log-box": "^57.0.3", - "@expo/metro-runtime": "^57.0.13", + "@expo/log-box": "^57.0.4", + "@expo/metro-runtime": "^57.0.14", "@testing-library/react-native": ">= 13.2.0", "expo": "*", - "expo-constants": "^57.0.14", - "expo-linking": "^57.0.7", + "expo-constants": "^57.0.15", + "expo-linking": "^57.0.8", "react": "*", "react-dom": "*", "react-native": "*", @@ -8040,9 +8038,9 @@ } }, "node_modules/expo-secure-store": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-57.0.1.tgz", - "integrity": "sha512-tLa1VmSadOq19mA/dwkl99RbHyjLE0T1qqBYMY3/OsguZTI+rlrDy/DDJjupqlVtmr95hD7o1pYqx5aL+B4YMA==", + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-57.0.2.tgz", + "integrity": "sha512-PhrPMKnI7YSObLEQZOoP9evB2ZOCv9kFuG2L7cZfNlOwWjGbir711PlppkXVoM3JE8vamDrTqzMLGv266FhJJg==", "license": "MIT", "peerDependencies": { "expo": "*" @@ -8053,15 +8051,14 @@ "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.3.tgz", "integrity": "sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.16.0" } }, "node_modules/expo-sharing": { - "version": "57.0.15", - "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-57.0.15.tgz", - "integrity": "sha512-2OT7aCk0Qh/nW3A5AuNeAHNd1P+RWA8f5k4Vc5LdaaYFX/8xdNPIzCOC07s7qaoIE0iuaf8dXQCAxCGXJWZrBw==", + "version": "57.0.16", + "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-57.0.16.tgz", + "integrity": "sha512-Z4ZFYLP8+EqIdUAiERYRI4/r7rtZDCRQ8Lj6jDG0xPF19B8nsFhYMgh7uyic+WulFEr+Xn7FeWgSDAuufkOyzw==", "license": "MIT", "dependencies": { "@expo/config-plugins": "^57.0.9", @@ -8116,12 +8113,12 @@ } }, "node_modules/expo-system-ui": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-57.0.2.tgz", - "integrity": "sha512-zABCRqFSioDBAo/RtmS0dQiGgDtDPZUVk01Y3Ti4ducobNM4HTM2sNAtq+YPpEUhaVW3hccPVsEUH4LK/ADVhA==", + "version": "57.0.3", + "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-57.0.3.tgz", + "integrity": "sha512-3N3BoagGojEvQw0h7kL0Lvv2LrVjqEvODSNS+Nc9lj/kzaTaVx4K1HI8qqxeALMf5jui3rLP2b3Ai2pXotoNuA==", "license": "MIT", "dependencies": { - "@react-native/normalize-colors": "0.86.2", + "@react-native/normalize-colors": "0.86.3", "debug": "^4.3.2" }, "peerDependencies": { @@ -8155,34 +8152,34 @@ } }, "node_modules/expo/node_modules/@expo/cli": { - "version": "57.0.18", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-57.0.18.tgz", - "integrity": "sha512-ZLHe7/a76nno3ZvC8czmUhbv/tmdzKa27fqW2J/Ybhx78u71C7qpeRk8UgWGYWeIhonT8XILVMxECjlYsj6hfQ==", + "version": "57.0.20", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-57.0.20.tgz", + "integrity": "sha512-ZjWz7SA5TBTyKq4+aFRUPgMFxmqHtKkDMv6d85J2UAAjdRhkNRf+B80t+rr5IFo2ywuTvyrPRBth4ei4w08olA==", "license": "MIT", "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~57.0.9", "@expo/config-plugins": "~57.0.9", "@expo/devcert": "^1.2.1", - "@expo/env": "~2.4.2", + "@expo/env": "~2.4.3", "@expo/image-utils": "^0.11.5", - "@expo/inline-modules": "^0.1.6", + "@expo/inline-modules": "^0.1.7", "@expo/json-file": "^11.0.1", - "@expo/log-box": "^57.0.3", + "@expo/log-box": "^57.0.4", "@expo/metro": "~56.0.2", - "@expo/metro-config": "~57.0.10", + "@expo/metro-config": "~57.0.12", "@expo/metro-file-map": "^57.0.2", "@expo/osascript": "^2.7.1", "@expo/package-manager": "^1.13.1", "@expo/plist": "^0.8.1", - "@expo/prebuild-config": "^57.0.14", + "@expo/prebuild-config": "^57.0.15", "@expo/require-utils": "^57.0.5", - "@expo/router-server": "^57.0.7", + "@expo/router-server": "^57.0.8", "@expo/schema-utils": "^57.0.2", "@expo/spawn-async": "^1.8.0", "@expo/ws-tunnel": "^2.0.0", "@expo/xcpretty": "^4.4.4", - "@react-native/dev-middleware": "0.86.2", + "@react-native/dev-middleware": "0.86.3", "accepts": "^1.3.8", "agent-cli-detector": "0.1.6", "arg": "^5.0.2", @@ -8238,17 +8235,17 @@ } }, "node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": { - "version": "57.0.7", - "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-57.0.7.tgz", - "integrity": "sha512-QFHwt6V7UovmYA7+8BoMeKj3fh6WtkM9zksKjNAlZdcEI/hUhcW4uged6So5yc4tq+eA60kA7gIVZPKgBIZdrQ==", + "version": "57.0.8", + "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-57.0.8.tgz", + "integrity": "sha512-lEPGYoDmh97yyEcY/MFWQsfE7yIgMRnGDcnsBN7B6AiWBFSJIMEcGnO0aYPgY0ZjNWyJDi7Ee8QZRGqB+LhI6w==", "license": "MIT", "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { - "@expo/metro-runtime": "^57.0.12", + "@expo/metro-runtime": "^57.0.14", "expo": "*", - "expo-constants": "^57.0.13", + "expo-constants": "^57.0.15", "expo-font": "^57.0.1", "expo-router": "*", "expo-server": "^57.0.3", @@ -8343,7 +8340,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -8676,7 +8672,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -9041,9 +9036,9 @@ } }, "node_modules/hermes-compiler": { - "version": "250829098.0.16", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.16.tgz", - "integrity": "sha512-xsgzk+mUyvt9t1nUbF8USBlYxajTUtPJhVZ86q85s/SEoMKCF+52YZcudb0ENSnV3T3lV9mgB3s6R7+pH90zgw==", + "version": "250829098.0.17", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.17.tgz", + "integrity": "sha512-qG1PXzTEtriF6oQLZF3vyHhSMxOdW5h2TqqLri0rdpstPustd2fSvRZQMVAPdlhgFwBfYnj3OUZtiO6LjYsEFw==", "license": "MIT" }, "node_modules/hermes-estree": { @@ -9980,7 +9975,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -10402,9 +10396,9 @@ } }, "node_modules/jest-expo": { - "version": "57.0.4", - "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-57.0.4.tgz", - "integrity": "sha512-EpTc8zizYWepKXHdHYuYBdEP0mZLjZfB1ldb2dyLKHug0HYFGuBnQ3Qp3gmBWCj3rSRaJYP90Lq/qPozzx2Khg==", + "version": "57.0.5", + "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-57.0.5.tgz", + "integrity": "sha512-U08fwjaDBwnnbKnZcOA0WQbHfKqOWTCGku+IdCaekrLZmfbbZr3P1WqEsXHo6ntidQ3DmW+TT6G1aOp+ChYfKA==", "dev": true, "license": "MIT", "dependencies": { @@ -10425,7 +10419,7 @@ "jest": "bin/jest.js" }, "peerDependencies": { - "@react-native/jest-preset": "^0.86.2", + "@react-native/jest-preset": "^0.86.3", "expo": "*", "react-native": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" @@ -11550,6 +11544,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11570,6 +11567,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11590,6 +11590,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11610,6 +11613,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -13331,7 +13337,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -13351,7 +13356,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13400,19 +13404,18 @@ "license": "MIT" }, "node_modules/react-native": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.2.tgz", - "integrity": "sha512-zbJXGZpwfZGA79Z9ob6Atvfx4nAQL8yJBa35s58E4Oo+khPykfQP2sTeumkKbjwajFYfVayg8pj7Il9nIfTk7A==", - "license": "MIT", - "peer": true, - "dependencies": { - "@react-native/assets-registry": "0.86.2", - "@react-native/codegen": "0.86.2", - "@react-native/community-cli-plugin": "0.86.2", - "@react-native/gradle-plugin": "0.86.2", - "@react-native/js-polyfills": "0.86.2", - "@react-native/normalize-colors": "0.86.2", - "@react-native/virtualized-lists": "0.86.2", + "version": "0.86.3", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.3.tgz", + "integrity": "sha512-JR5s3bM9ezud+Mw24GlNXNfthqPIKwrQgPPJcam+L97t2sKjjEavhCzBn+fyqZZRcM5+XlhYxpTxVkK7e1n38Q==", + "license": "MIT", + "dependencies": { + "@react-native/assets-registry": "0.86.3", + "@react-native/codegen": "0.86.3", + "@react-native/community-cli-plugin": "0.86.3", + "@react-native/gradle-plugin": "0.86.3", + "@react-native/js-polyfills": "0.86.3", + "@react-native/normalize-colors": "0.86.3", + "@react-native/virtualized-lists": "0.86.3", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -13420,7 +13423,7 @@ "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", - "hermes-compiler": "250829098.0.16", + "hermes-compiler": "250829098.0.17", "invariant": "^2.2.4", "memoize-one": "^5.0.0", "metro-runtime": "^0.84.3", @@ -13446,7 +13449,7 @@ "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { - "@react-native/jest-preset": "0.86.2", + "@react-native/jest-preset": "0.86.3", "@types/react": "^19.1.1", "react": "^19.2.3" }, @@ -13493,7 +13496,6 @@ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.32.0.tgz", "integrity": "sha512-uYIMOKlKENORq2SABE+jIjbPU+h5I/sQKcq2v16zRq848nwEp1fWRVwML4QWqijc8UcXJC25o54S8GQd4Mf2OA==", "license": "MIT", - "peer": true, "dependencies": { "@egjs/hammerjs": "^2.0.17", "@types/react-test-renderer": "^19.1.0", @@ -13520,7 +13522,6 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.5.1.tgz", "integrity": "sha512-RnMvtDuR+68ig864gAvZCOdZehqhC5rFmMo0kn+ARfgVSTvFeF6IFLBVgMPUu0KwihaapEyW24WRi6nEyy1kSA==", "license": "MIT", - "peer": true, "dependencies": { "react-native-is-edge-to-edge": "^1.3.1", "semver": "^7.7.3" @@ -13536,7 +13537,6 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz", "integrity": "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -13561,7 +13561,6 @@ "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", @@ -13594,7 +13593,6 @@ "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.10.1.tgz", "integrity": "sha512-62mRM19bDpfpdI8HLkEErcdOsrAPDtE9lA/sw+5lLRpzBHNhxaoj9QyY2KjXqUmirelxkX4zuPGTC3VdA0feJA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-class-properties": "^7.28.6", @@ -13655,6 +13653,7 @@ "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13670,7 +13669,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" } @@ -15254,7 +15252,6 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15992,7 +15989,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 9c8580d..09a240a 100644 --- a/package.json +++ b/package.json @@ -3,36 +3,36 @@ "main": "expo-router/entry", "version": "1.0.0-beta.17", "dependencies": { - "@expo/ui": "~57.0.13", + "@expo/ui": "~57.0.14", "@expo/vector-icons": "^15.0.2", "@noble/hashes": "^2.3.0", "@react-native-async-storage/async-storage": "2.2.0", "@react-navigation/native": "^7.3.16", "base64-js": "^1.5.1", - "expo": "~57.0.16", + "expo": "~57.0.18", "expo-blur": "~57.0.2", - "expo-build-properties": "~57.0.14", + "expo-build-properties": "~57.0.15", "expo-clipboard": "~57.0.1", - "expo-constants": "~57.0.12", + "expo-constants": "~57.0.16", "expo-crypto": "~57.0.2", - "expo-dev-client": "~57.0.15", + "expo-dev-client": "~57.0.16", "expo-device": "~57.0.1", - "expo-file-system": "~57.0.4", - "expo-font": "~57.0.1", + "expo-file-system": "~57.0.6", + "expo-font": "~57.0.2", "expo-glass-effect": "~57.0.1", "expo-image": "~57.0.3", - "expo-linking": "~57.0.7", - "expo-router": "~57.0.16", - "expo-secure-store": "~57.0.1", - "expo-sharing": "~57.0.15", + "expo-linking": "~57.0.8", + "expo-router": "~57.0.17", + "expo-secure-store": "~57.0.2", + "expo-sharing": "~57.0.16", "expo-splash-screen": "~57.0.8", "expo-status-bar": "~57.0.1", "expo-symbols": "~57.0.2", - "expo-system-ui": "~57.0.2", + "expo-system-ui": "~57.0.3", "expo-web-browser": "~57.0.2", "react": "19.2.3", "react-dom": "19.2.3", - "react-native": "0.86.2", + "react-native": "0.86.3", "react-native-ble-plx": "^3.5.1", "react-native-gesture-handler": "~2.32.0", "react-native-reanimated": "4.5.1", @@ -44,13 +44,14 @@ "devDependencies": { "@emnapi/core": "^1.11.3", "@emnapi/runtime": "^1.11.3", + "@react-native/jest-preset": "0.86.3", "@testing-library/react-native": "^14.0.1", "@types/jest": "^29.5.14", "@types/react": "~19.2.2", "eslint": "^9.39.5", - "eslint-config-expo": "^57.0.1", + "eslint-config-expo": "~57.0.2", "jest": "^29.7.0", - "jest-expo": "^57.0.4", + "jest-expo": "~57.0.5", "prettier": "^3.9.6", "typescript": "~6.0.3" }, @@ -63,6 +64,7 @@ "typecheck": "tsc --noEmit", "test": "jest", "test:ci": "jest --ci --runInBand", + "app-store:assets": "node scripts/generate-app-store-assets.cjs", "version:check": "node scripts/versioning.cjs check", "version:bump": "node scripts/versioning.cjs bump", "validate": "npm run version:check && npm run lint && npm run typecheck && npm run test:ci && npx expo-doctor" diff --git a/scripts/app-store-assets.test.js b/scripts/app-store-assets.test.js new file mode 100644 index 0000000..8ac4b02 --- /dev/null +++ b/scripts/app-store-assets.test.js @@ -0,0 +1,116 @@ +const { Buffer } = require("node:buffer"); +const fs = require("node:fs"); +const path = require("node:path"); +const zlib = require("node:zlib"); +const { describe, expect, test } = require("@jest/globals"); + +const root = path.resolve("."); +const names = ["01-pair.png", "02-mouse.png", "03-typing.png", "04-window.png", "05-access.png"]; + +function pngHeader(relativePath) { + const bytes = fs.readFileSync(path.join(root, relativePath)); + expect(bytes.subarray(0, 8)).toEqual(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])); + return { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20), colorType: bytes[25], size: bytes.length }; +} + +function decodeRgba(relativePath) { + const bytes = fs.readFileSync(path.join(root, relativePath)); + const { width, height, colorType } = pngHeader(relativePath); + expect(colorType).toBe(6); + const idatChunks = []; + let chunkOffset = 8; + while (chunkOffset < bytes.length) { + const length = bytes.readUInt32BE(chunkOffset); + if (bytes.toString("ascii", chunkOffset + 4, chunkOffset + 8) === "IDAT") { + idatChunks.push(bytes.subarray(chunkOffset + 8, chunkOffset + 8 + length)); + } + chunkOffset += length + 12; + } + + const inflated = zlib.inflateSync(Buffer.concat(idatChunks)); + const bytesPerPixel = 4; + const rowLength = width * bytesPerPixel; + const pixels = Buffer.alloc(rowLength * height); + let sourceOffset = 0; + let previous = Buffer.alloc(rowLength); + for (let y = 0; y < height; y += 1) { + const filter = inflated[sourceOffset]; + expect(filter).toBeGreaterThanOrEqual(0); + expect(filter).toBeLessThanOrEqual(4); + sourceOffset += 1; + const row = pixels.subarray(y * rowLength, (y + 1) * rowLength); + for (let x = 0; x < rowLength; x += 1) { + const raw = inflated[sourceOffset]; + sourceOffset += 1; + const left = x >= bytesPerPixel ? row[x - bytesPerPixel] : 0; + const above = previous[x]; + const upperLeft = x >= bytesPerPixel ? previous[x - bytesPerPixel] : 0; + let predictor = 0; + if (filter === 1) predictor = left; + else if (filter === 2) predictor = above; + else if (filter === 3) predictor = Math.floor((left + above) / 2); + else if (filter === 4) { + const estimate = left + above - upperLeft; + const leftDistance = Math.abs(estimate - left); + const aboveDistance = Math.abs(estimate - above); + const upperLeftDistance = Math.abs(estimate - upperLeft); + predictor = leftDistance <= aboveDistance && leftDistance <= upperLeftDistance + ? left + : aboveDistance <= upperLeftDistance ? above : upperLeft; + } + row[x] = (raw + predictor) & 0xff; + } + previous = row; + } + return { height, pixels, width }; +} + +function rgbaAt(image, x, y) { + const offset = (y * image.width + x) * 4; + return [...image.pixels.subarray(offset, offset + 4)]; +} + +describe("App Store screenshots", () => { + test.each([ + ...names.map((name) => [`iphone/${name}`, 1242, 2688]), + ...names.map((name) => [`ipad/${name}`, 2048, 2732]), + ])("has a correctly sized opaque image: %s", (file, width, height) => { + const relativePath = path.join("docs", "app-store-assets", file); + expect(pngHeader(relativePath)).toEqual(expect.objectContaining({ width, height, colorType: 6 })); + expect(pngHeader(relativePath).size).toBeLessThanOrEqual(10 * 1024 * 1024); + + const image = decodeRgba(relativePath); + let firstTranslucentPixel = -1; + for (let index = 3; index < image.pixels.length; index += 4) { + if (image.pixels[index] !== 255) { + firstTranslucentPixel = Math.floor(index / 4); + break; + } + } + expect(firstTranslucentPixel).toBe(-1); + + const frameX = Math.round(width * 0.105) + 2; + const frameY = Math.round(height * 0.115) + Math.round(width * 0.08); + const [frameRed, frameGreen, frameBlue] = rgbaAt(image, frameX, frameY); + expect(frameRed).toBeGreaterThan(170); + expect(frameGreen).toBeLessThan(80); + expect(frameBlue).toBeLessThan(100); + + let brandPixels = 0; + for (let y = Math.round(height * 0.015); y < Math.round(height * 0.07); y += 1) { + for (let x = Math.round(width * 0.25); x < Math.round(width * 0.75); x += 1) { + const [red, green, blue] = rgbaAt(image, x, y); + if (red > 170 && green < 80 && blue < 100) brandPixels += 1; + } + } + expect(brandPixels).toBeGreaterThan(100); + }); + + test("keeps the metadata within App Store limits", () => { + const listing = fs.readFileSync(path.join(root, "docs", "app-store-listing.md"), "utf8"); + expect(listing).toContain("Subtitle: `Accessible PC remote control`"); + expect("Accessible PC remote control").toHaveLength(28); + expect("Control your Windows PC or Mac from your iPhone or iPad with accessible mouse, typing and window controls over Bluetooth.".length).toBeLessThanOrEqual(170); + expect("accessibility,remote control,mouse,keyboard,typing,Windows,Mac,Bluetooth,switch control".length).toBeLessThanOrEqual(100); + }); +}); diff --git a/scripts/generate-app-store-asset.swift b/scripts/generate-app-store-asset.swift new file mode 100644 index 0000000..28a1b84 --- /dev/null +++ b/scripts/generate-app-store-asset.swift @@ -0,0 +1,62 @@ +import AppKit +import Foundation + +guard CommandLine.arguments.count == 6, + let width = Int(CommandLine.arguments[3]), + let height = Int(CommandLine.arguments[4]), + let source = NSImage(contentsOfFile: CommandLine.arguments[1]) else { + fputs("Usage: generate-app-store-asset.swift \n", stderr) + exit(1) +} + +let output = CommandLine.arguments[2] +let caption = CommandLine.arguments[5] +guard let bitmap = NSBitmapImageRep( + bitmapDataPlanes: nil, + pixelsWide: width, + pixelsHigh: height, + bitsPerSample: 8, + samplesPerPixel: 4, + hasAlpha: true, + isPlanar: false, + colorSpaceName: .deviceRGB, + bytesPerRow: width * 4, + bitsPerPixel: 32 +), let context = NSGraphicsContext(bitmapImageRep: bitmap) else { exit(1) } + +NSGraphicsContext.saveGraphicsState() +NSGraphicsContext.current = context +NSColor(calibratedWhite: 0.02, alpha: 1).setFill() +NSRect(x: 0, y: 0, width: width, height: height).fill() + +let margin = CGFloat(width) * 0.105 +let top = CGFloat(height) * 0.115 +let bottom = CGFloat(height) * 0.055 +let border = max(8, CGFloat(width) * 0.008) +let frame = NSRect(x: margin, y: bottom, width: CGFloat(width) - margin * 2, height: CGFloat(height) - top - bottom) +NSColor(calibratedRed: 217 / 255, green: 4 / 255, blue: 41 / 255, alpha: 1).setFill() +NSBezierPath(roundedRect: frame, xRadius: CGFloat(width) * 0.035, yRadius: CGFloat(width) * 0.035).fill() + +let inner = frame.insetBy(dx: border, dy: border) +NSColor(calibratedRed: 23 / 255, green: 23 / 255, blue: 26 / 255, alpha: 1).setFill() +NSBezierPath(roundedRect: inner, xRadius: CGFloat(width) * 0.027, yRadius: CGFloat(width) * 0.027).fill() +let scale = min(inner.width / source.size.width, inner.height / source.size.height) +let imageRect = NSRect(x: inner.midX - source.size.width * scale / 2, y: inner.midY - source.size.height * scale / 2, width: source.size.width * scale, height: source.size.height * scale) +source.draw(in: imageRect, from: .zero, operation: .sourceOver, fraction: 1) + +func drawCentered(_ text: String, y: CGFloat, size: CGFloat, color: NSColor, weight: NSFont.Weight) { + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .center + let attributes: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: size, weight: weight), .foregroundColor: color, .paragraphStyle: paragraph] + text.draw(in: NSRect(x: 0, y: y, width: CGFloat(width), height: size * 1.35), withAttributes: attributes) +} + +let captionSize = CGFloat(width) * (width < 1600 ? 0.057 : 0.038) +let brandSize = CGFloat(width) * (width < 1600 ? 0.026 : 0.018) +drawCentered(caption, y: CGFloat(height) - top * 0.72, size: captionSize, color: .white, weight: .heavy) +drawCentered("Switchify Remote", y: CGFloat(height) - top * 0.27, size: brandSize, color: NSColor(calibratedRed: 217 / 255, green: 4 / 255, blue: 41 / 255, alpha: 1), weight: .bold) + +context.flushGraphics() +NSGraphicsContext.restoreGraphicsState() +guard let data = bitmap.representation(using: .png, properties: [:]) else { exit(1) } +try data.write(to: URL(fileURLWithPath: output), options: .atomic) diff --git a/scripts/generate-app-store-assets.cjs b/scripts/generate-app-store-assets.cjs new file mode 100644 index 0000000..d2304a3 --- /dev/null +++ b/scripts/generate-app-store-assets.cjs @@ -0,0 +1,51 @@ +const { spawnSync } = require("node:child_process"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); + +const root = path.resolve("."); +const outputRoot = path.join(root, "docs", "app-store-assets"); +const captures = [ + ["01-pair.png", "Pair securely over Bluetooth"], + ["02-mouse.png", "Move, click and scroll"], + ["03-typing.png", "Type from iPhone or iPad"], + ["04-window.png", "Manage windows quickly"], + ["05-access.png", "Made for accessible control"], +]; + +function argument(name) { + const index = process.argv.indexOf(name); + if (index === -1 || !process.argv[index + 1]) return null; + return path.resolve(process.argv[index + 1]); +} + +const sets = [ + { name: "iphone", source: argument("--iphone-source-dir"), width: 1242, height: 2688 }, + { name: "ipad", source: argument("--ipad-source-dir"), width: 2048, height: 2732 }, +]; +if (sets.some(({ source }) => !source)) { + throw new Error("Usage: node scripts/generate-app-store-assets.cjs --iphone-source-dir --ipad-source-dir "); +} + +function run(command, args) { + const result = spawnSync(command, args, { encoding: "utf8" }); + if (result.status !== 0) throw new Error(result.stderr || `${command} exited with ${result.status}`); +} + +for (const set of sets) { + const output = path.join(outputRoot, set.name); + fs.mkdirSync(output, { recursive: true }); + const temp = fs.mkdtempSync(path.join(os.tmpdir(), `switchify-app-store-${set.name}-`)); + try { + for (const [fileName, caption] of captures) { + const source = path.join(set.source, fileName); + if (!fs.existsSync(source)) throw new Error(`Missing capture: ${source}`); + if (process.platform !== "darwin") throw new Error("App Store screenshot rendering requires macOS and Xcode."); + const rendered = path.join(temp, fileName); + run("xcrun", ["swift", path.join(root, "scripts", "generate-app-store-asset.swift"), source, rendered, String(set.width), String(set.height), caption]); + fs.copyFileSync(rendered, path.join(output, fileName)); + } + } finally { + fs.rmSync(temp, { recursive: true, force: true }); + } +} diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index 268eedd..5f3e5c1 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -6,12 +6,13 @@ import { BridgeProvider } from '@/bridge/BridgeContext'; import { ConnectionProvider } from '@/connection/ConnectionContext'; import { diagnosticsScreenOptions } from '@/navigation/diagnosticsScreenOptions'; import { FirstRunSetupGate } from '@/onboarding/FirstRunSetup'; +import { StoreCaptureRoot, storeCaptureEnabled } from '@/store-capture/StoreCaptureRoot'; import { ThemeProvider, useTheme } from '@/theme/ThemeContext'; export default function RootLayout() { return ( - + {storeCaptureEnabled() ? : } ); } diff --git a/src/store-capture/StoreCaptureRoot.test.tsx b/src/store-capture/StoreCaptureRoot.test.tsx new file mode 100644 index 0000000..238b385 --- /dev/null +++ b/src/store-capture/StoreCaptureRoot.test.tsx @@ -0,0 +1,23 @@ +import { render } from '@testing-library/react-native'; + +import { StoreCaptureRoot, storeCaptureEnabled, storeCaptureShot } from './StoreCaptureRoot'; + +describe('store capture mode', () => { + it.each([[undefined, false], ['', false], ['0', false], ['true', false], ['1', true]] as const)('requires the exact local build flag %p', (value, expected) => { + expect(storeCaptureEnabled(value)).toBe(expected); + }); + + it('accepts only sanitized capture deep links', () => { + expect(storeCaptureShot('switchify-remote://capture/mouse')).toBe('mouse'); + expect(storeCaptureShot('switchify-remote://capture/access?fixture=1')).toBe('access'); + expect(storeCaptureShot('https://example.com/capture/mouse')).toBeNull(); + expect(storeCaptureShot('switchify-remote://capture/diagnostics')).toBeNull(); + }); + + it('renders a sanitized fixture without a Bluetooth provider', async () => { + const view = await render(); + expect(view.getByTestId('store-capture-pair')).toBeTruthy(); + expect(view.getByText('Demo PC')).toBeTruthy(); + expect(view.queryByText(/verification code/i)).toBeNull(); + }); +}); diff --git a/src/store-capture/StoreCaptureRoot.tsx b/src/store-capture/StoreCaptureRoot.tsx new file mode 100644 index 0000000..214d62a --- /dev/null +++ b/src/store-capture/StoreCaptureRoot.tsx @@ -0,0 +1,144 @@ +import MaterialIcons from '@expo/vector-icons/MaterialIcons'; +import { StatusBar } from 'expo-status-bar'; +import { useEffect, useMemo, useState, type ReactNode } from 'react'; +import { Linking, Pressable, View } from 'react-native'; + +import { AppText } from '@/components/AppText'; +import { Card } from '@/components/Card'; +import { ControlButton } from '@/components/ControlButton'; +import { ListRow } from '@/components/ListRow'; +import { ResponsiveGrid } from '@/components/ResponsiveGrid'; +import { Screen } from '@/components/Screen'; +import { StatusBadge } from '@/components/StatusBadge'; +import type { ConnectionManager } from '@/connection/ConnectionManager'; +import type { PointerProfile } from '@/domain/protocol/types'; +import { MouseSurface } from '@/remote/MouseSurface'; +import { RemoteSession } from '@/remote/RemoteSession'; +import { TypingSurface } from '@/remote/TypingSurface'; +import { WindowSurface } from '@/remote/WindowSurface'; +import type { SwitchifyBridge } from '@/bridge/types'; +import { useTheme } from '@/theme/ThemeContext'; + +export type StoreCaptureShot = 'pair' | 'mouse' | 'typing' | 'window' | 'access'; + +const shots = new Set(['pair', 'mouse', 'typing', 'window', 'access']); +const captureSequence: StoreCaptureShot[] = ['pair', 'mouse', 'typing', 'window', 'access']; +const commands = [ + 'mouse.move', 'mouse.click', 'mouse.doubleClick', 'mouse.rightClick', 'mouse.dragStart', 'mouse.dragEnd', 'mouse.scroll', + 'mouse.repeat.start', 'mouse.repeat.stop', 'pointer.speed.set', 'pointer.display.move', + 'keyboard.typeText', 'keyboard.key', 'keyboard.textStream.open', 'keyboard.textStream.chunk', 'keyboard.textStream.key', + 'keyboard.textStream.close', 'keyboard.modifierDown', 'keyboard.modifierUp', 'keyboard.shortcut', 'window.control', +]; + +const profile: PointerProfile = { + displayId: 'capture-display', + scaleFactor: 2, + bounds: { x: 0, y: 0, width: 1920, height: 1080 }, + maxDelta: 256, + recommendedDeltas: { small: 32, medium: 64, large: 128 }, + capabilities: { + noAckMouseMove: true, + noAckCommands: ['mouse.move'], + supportedCommands: commands, + mouseRepeat: { supported: true, enabled: true, intervalMs: 250, minIntervalMs: 100, maxIntervalMs: 2_000 }, + pointerSpeed: { supported: true, setSupported: true, scalePercent: 75, minScalePercent: 5, maxScalePercent: 225, stepPercent: 5, baseMoveDelta: 64, effectiveMoveDelta: 48 }, + displayNavigation: { supported: true, displayCount: 2 }, + }, +}; + +const captureBridge: SwitchifyBridge = { + snapshot: () => ({ version: 0, captureAvailable: false, externalSwitches: [] }), + subscribe: () => () => undefined, + connect: async () => false, + disconnect: async () => undefined, + nextGeneration: () => 0, + setRepeatActive: async () => false, + setForwardingActive: async () => false, +}; + +const captureManager = { + send: async () => false, +} as unknown as ConnectionManager; + +export function storeCaptureEnabled(value = process.env.EXPO_PUBLIC_STORE_CAPTURE): boolean { + return value === '1'; +} + +export function storeCaptureShot(url: string | null | undefined): StoreCaptureShot | null { + if (!url) return null; + const match = url.match(/^switchify-remote:\/\/capture\/(pair|mouse|typing|window|access)(?:[/?#]|$)/); + return match && shots.has(match[1] as StoreCaptureShot) ? match[1] as StoreCaptureShot : null; +} + +export function StoreCaptureRoot() { + const [shot, setShot] = useState('pair'); + useEffect(() => { + const apply = (url: string | null | undefined) => { + const next = storeCaptureShot(url); + if (next) setShot(next); + }; + void Linking.getInitialURL().then(apply); + const subscription = Linking.addEventListener('url', ({ url }) => apply(url)); + const sequence = setInterval(() => setShot((current) => captureSequence[(captureSequence.indexOf(current) + 1) % captureSequence.length] ?? 'pair'), 5_000); + return () => { + clearInterval(sequence); + subscription.remove(); + }; + }, []); + return <>; +} + +function CaptureScreen({ shot }: { shot: StoreCaptureShot }) { + const session = useMemo(() => new RemoteSession(captureManager, profile, () => 'capture', 'capture-desktop', captureBridge), []); + useEffect(() => () => session.dispose(), [session]); + const tab = shot === 'pair' ? 'pcs' : shot === 'access' ? 'settings' : 'remote'; + return + {shot === 'pair' ? : null} + {shot === 'mouse' ? : null} + {shot === 'typing' ? : null} + {shot === 'window' ? : null} + {shot === 'access' ? : null} + + ; +} + +function PairCapture() { + return + + + Demo PCWindows · Saved + + + Pairing approval keeps control limited to computers you trust. + undefined} /> + + + + + ; +} + +function RemoteCapture({ children, title }: { children: ReactNode; title: string }) { + return }> + {children} + ; +} + +function AccessCapture() { + return + Opening surface undefined} /> undefined} /> undefined} /> + Pointer controls + + ; +} + +function CaptureTabBar({ selected }: { selected: 'pcs' | 'remote' | 'settings' }) { + const { colors, spacing, typography } = useTheme(); + const items = [['pcs', 'computer', 'PCs'], ['remote', 'settings-remote', 'Remote'], ['settings', 'settings', 'Settings']] as const; + return + {items.map(([key, icon, label]) => + + {label} + )} + ; +}