added OTA support

This commit is contained in:
tapframe 2025-09-06 17:50:04 +05:30
parent 5ddf26af34
commit e4bc0d3896
11 changed files with 1279 additions and 259 deletions

2
.gitignore vendored
View file

@ -49,3 +49,5 @@ local-scrapers-repo
worki.json
VERSION_UPDATE_README.md
hackintosh-emulator-fix.sh
/ota-builds
src/screens/xavio.md

13
App.tsx
View file

@ -29,6 +29,7 @@ import { TrailerProvider } from './src/contexts/TrailerContext';
import SplashScreen from './src/components/SplashScreen';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Sentry from '@sentry/react-native';
import UpdateService from './src/services/updateService';
Sentry.init({
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
@ -60,20 +61,24 @@ const ThemedApp = () => {
const [isAppReady, setIsAppReady] = useState(false);
const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState<boolean | null>(null);
// Check onboarding status
// Check onboarding status and initialize update service
useEffect(() => {
const checkOnboardingStatus = async () => {
const initializeApp = async () => {
try {
// Check onboarding status
const onboardingCompleted = await AsyncStorage.getItem('hasCompletedOnboarding');
setHasCompletedOnboarding(onboardingCompleted === 'true');
// Initialize update service
await UpdateService.initialize();
} catch (error) {
console.error('Error checking onboarding status:', error);
console.error('Error initializing app:', error);
// Default to showing onboarding if we can't check
setHasCompletedOnboarding(false);
}
};
checkOnboardingStatus();
initializeApp();
}, []);
// Create custom themes based on current theme

View file

@ -13,9 +13,11 @@
</intent>
</queries>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true">
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
<meta-data android:name="expo.modules.updates.EXPO_RUNTIME_VERSION" android:value="@string/expo_runtime_version"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"/>
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

View file

@ -3,4 +3,5 @@
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
<string name="expo_system_ui_user_interface_style" translatable="false">dark</string>
<string name="expo_runtime_version">0.6.0-beta.8</string>
</resources>

View file

@ -72,7 +72,20 @@
"organization": "tapframe"
}
],
"expo-localization"
]
"expo-localization",
[
"expo-updates",
{
"username": "nayifleo"
}
]
],
"updates": {
"enabled": true,
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0,
"url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"
},
"runtimeVersion": "0.6.0-beta.8"
}
}

View file

@ -0,0 +1,61 @@
#!/bin/bash
# Check if the correct number of arguments are provided
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <runtimeVersion> <xavia-ota-url>"
exit 1
fi
# Get the current commit hash and message
commitHash=$(git rev-parse HEAD)
commitMessage=$(git log -1 --pretty=%B)
# Assign arguments to variables
runtimeVersion=$1
serverHost=$2
# Generate a timestamp for the output folder
timestamp=$(date -u +%Y%m%d%H%M%S)
outputFolder="ota-builds/$timestamp"
# Ask the user to confirm the hash, commit message, runtime version, and output folder
echo "Output Folder: $outputFolder"
echo "Runtime Version: $runtimeVersion"
echo "Commit Hash: $commitHash"
echo "Commit Message: $commitMessage"
read -p "Do you want to proceed with these values? (y/n): " confirm
if [ "$confirm" != "y" ]; then
echo "Operation cancelled by the user."
exit 1
fi
rm -rf $outputFolder
mkdir -p $outputFolder
# Run expo export with the specified output folder
npx expo export --output-dir $outputFolder
# Extract expo config property from app.json and save to expoconfig.json
jq '.expo' app.json > $outputFolder/expoconfig.json
# Zip the output folder
cd $outputFolder
zip -q -r ${timestamp}.zip .
# Upload the zip file to the server
curl -X POST $serverHost/api/upload -F "file=@${timestamp}.zip" -F "runtimeVersion=$runtimeVersion" -F "commitHash=$commitHash" -F "commitMessage=$commitMessage"
echo ""
echo "Uploaded to $serverHost/api/upload"
cd ..
# Remove the output folder and zip file
rm -rf $outputFolder
echo "Removed $outputFolder"
echo "Done"

535
package-lock.json generated
View file

@ -1,27 +1,28 @@
{
"name": "nuvio",
"version": "1.0.0",
"version": "0.6.0-beta.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nuvio",
"version": "1.0.0",
"version": "0.6.0-beta.6",
"dependencies": {
"@backpackapp-io/react-native-toast": "^0.14.0",
"@expo/metro-runtime": "~4.0.1",
"@expo/vector-icons": "^14.1.0",
"@expo/vector-icons": "~14.0.4",
"@lottiefiles/dotlottie-react": "^0.6.5",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-community/slider": "^4.5.6",
"@react-native-picker/picker": "^2.11.0",
"@react-native-community/slider": "4.5.5",
"@react-native-picker/picker": "2.9.0",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"@react-navigation/stack": "^7.2.10",
"@sentry/react-native": "^6.15.1",
"@shopify/flash-list": "^2.0.2",
"@sentry/react-native": "~6.10.0",
"@shopify/flash-list": "1.7.3",
"@supabase/supabase-js": "^2.54.0",
"@types/lodash": "^4.17.16",
"@types/react-native-video": "^5.0.20",
@ -30,7 +31,7 @@
"cheerio-without-node-native": "^0.20.2",
"date-fns": "^4.1.0",
"eventemitter3": "^5.0.1",
"expo": "~52.0.43",
"expo": "~52.0.47",
"expo-application": "~6.0.2",
"expo-auth-session": "^6.0.3",
"expo-blur": "^14.0.3",
@ -47,9 +48,10 @@
"expo-screen-orientation": "~8.0.4",
"expo-status-bar": "~2.0.1",
"expo-system-ui": "^4.0.9",
"expo-updates": "~0.27.4",
"expo-web-browser": "~14.0.2",
"lodash": "^4.17.21",
"lottie-react-native": "^7.3.1",
"lottie-react-native": "7.1.0",
"posthog-react-native": "^4.4.0",
"react": "18.3.1",
"react-native": "0.76.9",
@ -58,10 +60,10 @@
"react-native-image-colors": "^2.5.0",
"react-native-immersive-mode": "^2.0.2",
"react-native-paper": "^5.13.1",
"react-native-reanimated": "^3.18.0",
"react-native-reanimated": "~3.16.1",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.4.0",
"react-native-svg": "^15.11.2",
"react-native-svg": "15.8.0",
"react-native-url-polyfill": "^2.0.0",
"react-native-video": "^6.12.0",
"react-native-vlc-media-player": "^1.0.87",
@ -77,9 +79,9 @@
}
},
"node_modules/@0no-co/graphql.web": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.1.2.tgz",
"integrity": "sha512-N2NGsU5FLBhT8NZ+3l2YrzZSHITjNXNuDhC4iDiikv0IujaJ0Xc6xIxQZ/Ek3Cb+rgPjnLHYyJm11tInuJn+cw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz",
"integrity": "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==",
"license": "MIT",
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
@ -2340,9 +2342,9 @@
}
},
"node_modules/@expo/cli": {
"version": "0.22.24",
"resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.22.24.tgz",
"integrity": "sha512-lhdenxBC8/x/vL39j79eXE09mOaqNNLmiSDdY/PblnI+UNzGgsQ48hBTYa/MQhd0ioXXVKurZL2941dLKwcxJw==",
"version": "0.22.26",
"resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.22.26.tgz",
"integrity": "sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==",
"license": "MIT",
"dependencies": {
"@0no-co/graphql.web": "^1.0.8",
@ -2358,7 +2360,7 @@
"@expo/osascript": "^2.1.6",
"@expo/package-manager": "^1.7.2",
"@expo/plist": "^0.2.2",
"@expo/prebuild-config": "^8.0.31",
"@expo/prebuild-config": "~8.2.0",
"@expo/rudder-sdk-node": "^1.1.1",
"@expo/spawn-async": "^1.7.2",
"@expo/ws-tunnel": "^1.0.1",
@ -2423,9 +2425,9 @@
}
},
"node_modules/@expo/cli/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -2729,9 +2731,9 @@
}
},
"node_modules/@expo/metro-config/node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
@ -2759,9 +2761,9 @@
}
},
"node_modules/@expo/osascript": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.1.6.tgz",
"integrity": "sha512-SbMp4BUwDAKiFF4zZEJf32rRYMeNnLK9u4FaPo0lQRer60F+SKd20NTSys0wgssiVeQyQz2OhGLRx3cxYowAGw==",
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.2.5.tgz",
"integrity": "sha512-Bpp/n5rZ0UmpBOnl7Li3LtM7la0AR3H9NNesqL+ytW5UiqV/TbonYW3rDZY38u4u/lG7TnYflVIVQPD+iqZJ5w==",
"license": "MIT",
"dependencies": {
"@expo/spawn-async": "^1.7.2",
@ -2772,23 +2774,36 @@
}
},
"node_modules/@expo/package-manager": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.7.2.tgz",
"integrity": "sha512-wT/qh9ebNjl6xr00bYkSh93b6E/78J3JPlT6WzGbxbsnv5FIZKB/nr522oWqVe1E+ML7BpXs8WugErWDN9kOFg==",
"version": "1.8.6",
"resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.8.6.tgz",
"integrity": "sha512-gcdICLuL+nHKZagPIDC5tX8UoDDB8vNA5/+SaQEqz8D+T2C4KrEJc2Vi1gPAlDnKif834QS6YluHWyxjk0yZlQ==",
"license": "MIT",
"dependencies": {
"@expo/json-file": "^9.0.2",
"@expo/json-file": "^9.1.5",
"@expo/spawn-async": "^1.7.2",
"ansi-regex": "^5.0.0",
"chalk": "^4.0.0",
"find-up": "^5.0.0",
"js-yaml": "^3.13.1",
"micromatch": "^4.0.8",
"npm-package-arg": "^11.0.0",
"ora": "^3.4.0",
"resolve-workspace-root": "^2.0.0",
"split": "^1.0.1",
"sudo-prompt": "9.1.1"
"resolve-workspace-root": "^2.0.0"
}
},
"node_modules/@expo/package-manager/node_modules/@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
"license": "MIT",
"dependencies": {
"@babel/highlight": "^7.10.4"
}
},
"node_modules/@expo/package-manager/node_modules/@expo/json-file": {
"version": "9.1.5",
"resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.1.5.tgz",
"integrity": "sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "~7.10.4",
"json5": "^2.2.3"
}
},
"node_modules/@expo/plist": {
@ -2803,9 +2818,9 @@
}
},
"node_modules/@expo/prebuild-config": {
"version": "8.0.31",
"resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.31.tgz",
"integrity": "sha512-YTuS5ic9KolD/WA3GqgLcZytHQU1dpitlZ/cbDq8ZqkY+1ae5YWX+GkYEZf2VyECPaWnHYuDGddaTQVw5miTRg==",
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.2.0.tgz",
"integrity": "sha512-CxiPpd980s0jyxi7eyN3i/7YKu3XL+8qPjBZUCYtc0+axpGweqIkq2CslyLSKHyqVyH/zlPkbVgWdyiYavFS5Q==",
"license": "MIT",
"dependencies": {
"@expo/config": "~10.0.11",
@ -2837,9 +2852,9 @@
}
},
"node_modules/@expo/prebuild-config/node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
@ -2849,9 +2864,9 @@
}
},
"node_modules/@expo/prebuild-config/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -2912,14 +2927,12 @@
"license": "MIT"
},
"node_modules/@expo/vector-icons": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.1.0.tgz",
"integrity": "sha512-7T09UE9h8QDTsUeMGymB4i+iqvtEeaO5VvUjryFB4tugDTG/bkzViWA74hm5pfjjDEhYMXWaX112mcvhccmIwQ==",
"version": "14.0.4",
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.0.4.tgz",
"integrity": "sha512-+yKshcbpDfbV4zoXOgHxCwh7lkE9VVTT5T03OUlBsqfze1PLy6Hi4jp1vSb1GVbY6eskvMIivGVc9SKzIv0oEQ==",
"license": "MIT",
"peerDependencies": {
"expo-font": "*",
"react": "*",
"react-native": "*"
"dependencies": {
"prop-types": "^15.8.1"
}
},
"node_modules/@expo/ws-tunnel": {
@ -3414,6 +3427,26 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lottiefiles/dotlottie-react": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/@lottiefiles/dotlottie-react/-/dotlottie-react-0.6.5.tgz",
"integrity": "sha512-4jbye+HKHaiKwai4+bcAwJfMwW0L55cAGIN0ZKCz+EqXs1R3YhB08VYeRu2LwDUybThgFQnl/XkB4loCfA7t2A==",
"license": "MIT",
"dependencies": {
"@lottiefiles/dotlottie-web": "0.25.0",
"debounce": "^2.0.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@lottiefiles/dotlottie-web": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@lottiefiles/dotlottie-web/-/dotlottie-web-0.25.0.tgz",
"integrity": "sha512-k+4tTckmy7y319qY4AlZiLm4dP95KLXfZd1l1jbss0qX/eyV4KKT+4iLuBfhoFSljfXmM15Oma+e8d663CHPyA==",
"license": "MIT"
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -3462,9 +3495,9 @@
}
},
"node_modules/@npmcli/fs/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -3521,9 +3554,9 @@
}
},
"node_modules/@react-native-community/slider": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.5.6.tgz",
"integrity": "sha512-UhLPFeqx0YfPLrEz8ffT3uqAyXWu6iqFjohNsbp4cOU7hnJwg2RXtDnYHoHMr7MOkZDVdlLMdrSrAuzY6KGqrg==",
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.5.5.tgz",
"integrity": "sha512-x2N415pg4ZxIltArOKczPwn7JEYh+1OxQ4+hTnafomnMsqs65HZuEWcX+Ch8c5r8V83DiunuQUf5hWGWlw8hQQ==",
"license": "MIT"
},
"node_modules/@react-native-masked-view/masked-view": {
@ -3538,13 +3571,10 @@
}
},
"node_modules/@react-native-picker/picker": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.0.tgz",
"integrity": "sha512-QuZU6gbxmOID5zZgd/H90NgBnbJ3VV6qVzp6c7/dDrmWdX8S0X5YFYgDcQFjE3dRen9wB9FWnj2VVdPU64adSg==",
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.9.0.tgz",
"integrity": "sha512-khEhIW/uhfMqq/+tvg4rEAiPGT8GX+Y6QydlP2TSMSmRHoSJK+ShXvXZXSr4Sii4imkj4BwvLunGywwtQDODqg==",
"license": "MIT",
"workspaces": [
"example"
],
"peerDependencies": {
"react": "*",
"react-native": "*"
@ -4156,9 +4186,9 @@
}
},
"node_modules/@sentry/babel-plugin-component-annotate": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.5.0.tgz",
"integrity": "sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw==",
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.2.2.tgz",
"integrity": "sha512-D+SKQ266ra/wo87s9+UI/rKQi3qhGPCR8eSCDe0VJudhjHsqyNU+JJ5lnIGCgmZaWFTXgdBP/gdr1Iz1zqGs4Q==",
"license": "MIT",
"engines": {
"node": ">= 14"
@ -4181,9 +4211,9 @@
}
},
"node_modules/@sentry/cli": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.46.0.tgz",
"integrity": "sha512-nqoPl7UCr446QFkylrsRrUXF51x8Z9dGquyf4jaQU+OzbOJMqclnYEvU6iwbwvaw3tu/2DnoZE/Og+Nq1h63sA==",
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.42.4.tgz",
"integrity": "sha512-BoSZDAWJiz/40tu6LuMDkSgwk4xTsq6zwqYoUqLU3vKBR/VsaaQGvu6EWxZXORthfZU2/5Agz0+t220cge6VQw==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@ -4200,20 +4230,19 @@
"node": ">= 10"
},
"optionalDependencies": {
"@sentry/cli-darwin": "2.46.0",
"@sentry/cli-linux-arm": "2.46.0",
"@sentry/cli-linux-arm64": "2.46.0",
"@sentry/cli-linux-i686": "2.46.0",
"@sentry/cli-linux-x64": "2.46.0",
"@sentry/cli-win32-arm64": "2.46.0",
"@sentry/cli-win32-i686": "2.46.0",
"@sentry/cli-win32-x64": "2.46.0"
"@sentry/cli-darwin": "2.42.4",
"@sentry/cli-linux-arm": "2.42.4",
"@sentry/cli-linux-arm64": "2.42.4",
"@sentry/cli-linux-i686": "2.42.4",
"@sentry/cli-linux-x64": "2.42.4",
"@sentry/cli-win32-i686": "2.42.4",
"@sentry/cli-win32-x64": "2.42.4"
}
},
"node_modules/@sentry/cli-darwin": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.46.0.tgz",
"integrity": "sha512-5Ll+e5KAdIk9OYiZO8aifMBRNWmNyPjSqdjaHlBC1Qfh7pE3b1zyzoHlsUazG0bv0sNrSGea8e7kF5wIO1hvyg==",
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.42.4.tgz",
"integrity": "sha512-PZV4Y97VDWBR4rIt0HkJfXaBXlebIN2s/FDzC3iHINZE5OG62CDFsnC4/lbGlf2/UZLDaGGIK7mYwSHhTvN+HQ==",
"license": "BSD-3-Clause",
"optional": true,
"os": [
@ -4224,9 +4253,9 @@
}
},
"node_modules/@sentry/cli-linux-arm": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.46.0.tgz",
"integrity": "sha512-WRrLNq/TEX/TNJkGqq6Ad0tGyapd5dwlxtsPbVBrIdryuL1mA7VCBoaHBr3kcwJLsgBHFH0lmkMee2ubNZZdkg==",
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.4.tgz",
"integrity": "sha512-lBn0oeeg62h68/4Eo6zbPq99Idz5t0VRV48rEU/WKeM4MtQCvG/iGGQ3lBFW2yNiUBzXZIK9poXLEcgbwmcRVw==",
"cpu": [
"arm"
],
@ -4234,17 +4263,16 @@
"optional": true,
"os": [
"linux",
"freebsd",
"android"
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-arm64": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.46.0.tgz",
"integrity": "sha512-OEJN8yAjI9y5B4telyqzu27Hi3+S4T8VxZCqJz1+z2Mp0Q/MZ622AahVPpcrVq/5bxrnlZR16+lKh8L1QwNFPg==",
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.4.tgz",
"integrity": "sha512-Ex8vRnryyzC/9e43daEmEqPS+9uirY/l6Hw2lAvhBblFaL7PTWNx52H+8GnYGd9Zy2H3rWNyBDYfHwnErg38zA==",
"cpu": [
"arm64"
],
@ -4252,17 +4280,16 @@
"optional": true,
"os": [
"linux",
"freebsd",
"android"
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-i686": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.46.0.tgz",
"integrity": "sha512-xko3/BVa4LX8EmRxVOCipV+PwfcK5Xs8lP6lgF+7NeuAHMNL4DqF6iV9rrN8gkGUHCUI9RXSve37uuZnFy55+Q==",
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.4.tgz",
"integrity": "sha512-IBJg0aHjsLCL4LvcFa3cXIjA+4t5kPqBT9y+PoDu4goIFxYD8zl7mbUdGJutvJafTk8Akf4ss4JJXQBjg019zA==",
"cpu": [
"x86",
"ia32"
@ -4271,17 +4298,16 @@
"optional": true,
"os": [
"linux",
"freebsd",
"android"
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-x64": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.46.0.tgz",
"integrity": "sha512-hJ1g5UEboYcOuRia96LxjJ0jhnmk8EWLDvlGnXLnYHkwy3ree/L7sNgdp/QsY8Z4j2PGO5f22Va+UDhSjhzlfQ==",
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.4.tgz",
"integrity": "sha512-gXI5OEiOSNiAEz7VCE6AZcAgHJ47mlgal3+NmbE8XcHmFOnyDws9FNie6PJAy8KZjXi3nqoBP9JVAbnmOix3uA==",
"cpu": [
"x64"
],
@ -4289,33 +4315,16 @@
"optional": true,
"os": [
"linux",
"freebsd",
"android"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-win32-arm64": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.46.0.tgz",
"integrity": "sha512-mN7cpPoCv2VExFRGHt+IoK11yx4pM4ADZQGEso5BAUZ5duViXB2WrAXCLd8DrwMnP0OE978a7N8OtzsFqjkbNA==",
"cpu": [
"arm64"
],
"license": "BSD-3-Clause",
"optional": true,
"os": [
"win32"
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-win32-i686": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.46.0.tgz",
"integrity": "sha512-6F73AUE3lm71BISUO19OmlnkFD5WVe4/wA1YivtLZTc1RU3eUYJLYxhDfaH3P77+ycDppQ2yCgemLRaA4A8mNQ==",
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.4.tgz",
"integrity": "sha512-vZuR3UPHKqOMniyrijrrsNwn9usaRysXq78F6WV0cL0ZyPLAmY+KBnTDSFk1Oig2pURnzaTm+RtcZu2fc8mlzg==",
"cpu": [
"x86",
"ia32"
@ -4330,9 +4339,9 @@
}
},
"node_modules/@sentry/cli-win32-x64": {
"version": "2.46.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.46.0.tgz",
"integrity": "sha512-yuGVcfepnNL84LGA0GjHzdMIcOzMe0bjPhq/rwPsPN+zu11N+nPR2wV2Bum4U0eQdqYH3iAlMdL5/BEQfuLJww==",
"version": "2.42.4",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.4.tgz",
"integrity": "sha512-OIBj3uaQ6nAERSm5Dcf8UIhyElEEwMNsZEEppQpN4IKl0mrwb/57AznM23Dvpu6GR8WGbVQUSolt879YZR5E9g==",
"cpu": [
"x64"
],
@ -4345,31 +4354,6 @@
"node": ">=10"
}
},
"node_modules/@sentry/cli/node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/@sentry/cli/node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@sentry/core": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.54.0.tgz",
@ -4397,14 +4381,14 @@
}
},
"node_modules/@sentry/react-native": {
"version": "6.15.1",
"resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-6.15.1.tgz",
"integrity": "sha512-uNYjkhi7LUeXe+a3ui3N+sUZ4PbBh/P3Q6Pz5esOQOAEV1N7hxkdnHVic1cVHsirEQvy9rUJPBnja47Va7OpQA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-6.10.0.tgz",
"integrity": "sha512-B56vc+pnFHMiu3cabFb454v4qD0zObW6JVzJ5Gb6fIMdt93AFIJg10ZErzC+ump7xM4BOEROFFRuLiyvadvlPA==",
"license": "MIT",
"dependencies": {
"@sentry/babel-plugin-component-annotate": "3.5.0",
"@sentry/babel-plugin-component-annotate": "3.2.2",
"@sentry/browser": "8.54.0",
"@sentry/cli": "2.46.0",
"@sentry/cli": "2.42.4",
"@sentry/core": "8.54.0",
"@sentry/react": "8.54.0",
"@sentry/types": "8.54.0",
@ -4449,11 +4433,12 @@
}
},
"node_modules/@shopify/flash-list": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-2.0.2.tgz",
"integrity": "sha512-zhlrhA9eiuEzja4wxVvotgXHtqd3qsYbXkQ3rsBfOgbFA9BVeErpDE/yEwtlIviRGEqpuFj/oU5owD6ByaNX+w==",
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.7.3.tgz",
"integrity": "sha512-RLhNptm02aqpqZvjj9pJPcU+EVYxOAJhPRCmDOaUbUP86+636w+plsbjpBPSYGvPZhPj56RtZ9FBlvolPeEmYA==",
"license": "MIT",
"dependencies": {
"recyclerlistview": "4.2.1",
"tslib": "2.8.1"
},
"peerDependencies": {
@ -5214,22 +5199,22 @@
"license": "MIT"
},
"node_modules/@urql/core": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-5.1.1.tgz",
"integrity": "sha512-aGh024z5v2oINGD/In6rAtVKTm4VmQ2TxKQBAtk2ZSME5dunZFcjltw4p5ENQg+5CBhZ3FHMzl0Oa+rwqiWqlg==",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-5.2.0.tgz",
"integrity": "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==",
"license": "MIT",
"dependencies": {
"@0no-co/graphql.web": "^1.0.5",
"@0no-co/graphql.web": "^1.0.13",
"wonka": "^6.3.2"
}
},
"node_modules/@urql/exchange-retry": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.1.tgz",
"integrity": "sha512-EEmtFu8JTuwsInqMakhLq+U3qN8ZMd5V3pX44q0EqD2imqTDsa8ikZqJ1schVrN8HljOdN+C08cwZ1/r5uIgLw==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.2.tgz",
"integrity": "sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg==",
"license": "MIT",
"dependencies": {
"@urql/core": "^5.1.1",
"@urql/core": "^5.1.2",
"wonka": "^6.3.2"
},
"peerDependencies": {
@ -6769,16 +6754,16 @@
}
},
"node_modules/compression": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz",
"integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==",
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"negotiator": "~0.6.4",
"on-headers": "~1.0.2",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
@ -7070,6 +7055,18 @@
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debounce": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz",
"integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@ -7422,9 +7419,9 @@
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
@ -7705,18 +7702,18 @@
"integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
},
"node_modules/expo": {
"version": "52.0.44",
"resolved": "https://registry.npmjs.org/expo/-/expo-52.0.44.tgz",
"integrity": "sha512-qj3+MWxmqLyBaYQ8jDOvVLEgSqNplH3cf+nDhxCo4C1cpTPD1u/HGh1foibtaeuCYLHsE5km1lrcOpRbFJ4luQ==",
"version": "52.0.47",
"resolved": "https://registry.npmjs.org/expo/-/expo-52.0.47.tgz",
"integrity": "sha512-Mkvl7Qi2k+V3FdNRUD+yDj8GqU4IiYulLfl36BmSZs8lh/kCYPhTiyBLiEGPfz7d25QKbPWG727ESozbkbvatw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.0",
"@expo/cli": "0.22.24",
"@expo/cli": "0.22.26",
"@expo/config": "~10.0.11",
"@expo/config-plugins": "~9.0.17",
"@expo/fingerprint": "0.11.11",
"@expo/metro-config": "0.19.12",
"@expo/vector-icons": "^14.0.0",
"@expo/vector-icons": "~14.0.4",
"babel-preset-expo": "~12.0.11",
"expo-asset": "~11.0.5",
"expo-constants": "~17.0.8",
@ -7924,6 +7921,12 @@
"node": "*"
}
},
"node_modules/expo-eas-client": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-0.13.3.tgz",
"integrity": "sha512-t+1F1tiDocSot8iSnrn/CjTUMvVvPV2DpafSVcticpbSzMGybEN7wcamO1t18fK7WxGXpZE9gxtd80qwv/LLqQ==",
"license": "MIT"
},
"node_modules/expo-file-system": {
"version": "18.0.12",
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.0.12.tgz",
@ -8169,6 +8172,12 @@
"react-native": "*"
}
},
"node_modules/expo-structured-headers": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/expo-structured-headers/-/expo-structured-headers-4.0.0.tgz",
"integrity": "sha512-uPiwZjWq3AdFGgY52+I2nGPrNa6izxAglymPXHUZLekZW290GqIUOk7MBNDD4sg4JwUbSi3gdxEurpEvuq+FSg==",
"license": "MIT"
},
"node_modules/expo-system-ui": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-4.0.9.tgz",
@ -8195,6 +8204,35 @@
"integrity": "sha512-FRjRvs7RgsXjkbGSOjYSxhX5V70c0IzA/jy3HXeYpATMwD9fOR1DbveLW497QGsVdCa0vThbJUtR8rIzAfpHQA==",
"license": "MIT"
},
"node_modules/expo-updates": {
"version": "0.27.4",
"resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-0.27.4.tgz",
"integrity": "sha512-0rg4L2fFPEjTR/qnZ9Te4Q4irVC8uvNcTZW1pWnWbadG1SLv2PKjS1MYX5BboKzC3ao0H7m++5TP3hWhNg9org==",
"license": "MIT",
"dependencies": {
"@expo/code-signing-certificates": "0.0.5",
"@expo/config": "~10.0.11",
"@expo/config-plugins": "~9.0.17",
"@expo/spawn-async": "^1.7.2",
"arg": "4.1.0",
"chalk": "^4.1.2",
"expo-eas-client": "~0.13.3",
"expo-manifests": "~0.15.7",
"expo-structured-headers": "~4.0.0",
"expo-updates-interface": "~1.0.0",
"fast-glob": "^3.3.2",
"fbemitter": "^3.0.0",
"ignore": "^5.3.1",
"resolve-from": "^5.0.0"
},
"bin": {
"expo-updates": "bin/cli.js"
},
"peerDependencies": {
"expo": "*",
"react": "*"
}
},
"node_modules/expo-updates-interface": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-1.0.0.tgz",
@ -8204,6 +8242,12 @@
"expo": "*"
}
},
"node_modules/expo-updates/node_modules/arg": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz",
"integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==",
"license": "MIT"
},
"node_modules/expo-web-browser": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-14.0.2.tgz",
@ -8524,14 +8568,15 @@
}
},
"node_modules/form-data": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.3.tgz",
"integrity": "sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz",
"integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.35"
},
"engines": {
@ -9094,6 +9139,31 @@
"npm": ">=1.3.7"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/https-proxy-agent/node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -10497,9 +10567,9 @@
}
},
"node_modules/lottie-react-native": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-7.3.1.tgz",
"integrity": "sha512-VW0gtiP1i3Jv6+PaVaNg6QDIqAXwAqmxVp1UOJFqsB6FeBN0zc8M/XR8DE8YiayZzd149nlqGOgmwoV7FbRGDw==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-7.1.0.tgz",
"integrity": "sha512-73jtQySxRZ8KTTSKf6CtcpCt8tpOCw4NRiCST4HTYgXlycxIihIp89jRcK8rS/QiBKl5bzyixMzpVmd4mYVH5Q==",
"license": "Apache-2.0",
"peerDependencies": {
"@lottiefiles/dotlottie-react": "^0.6.5",
@ -11406,9 +11476,9 @@
}
},
"node_modules/npm-package-arg/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -11558,9 +11628,9 @@
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
@ -12357,9 +12427,9 @@
}
},
"node_modules/pump": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
@ -12502,7 +12572,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
@ -12517,7 +12586,6 @@
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
@ -12659,16 +12727,6 @@
"react-native": ">=0.60.5"
}
},
"node_modules/react-native-is-edge-to-edge": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz",
"integrity": "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-paper": {
"version": "5.13.1",
"resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.13.1.tgz",
@ -12721,9 +12779,9 @@
}
},
"node_modules/react-native-reanimated": {
"version": "3.18.0",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.18.0.tgz",
"integrity": "sha512-eVcNcqeOkMW+BUWAHdtvN3FKgC8J8wiEJkX6bNGGQaLS7m7e4amTfjIcqf/Ta+lerZLurmDaQ0lICI1CKPrb1Q==",
"version": "3.16.7",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.7.tgz",
"integrity": "sha512-qoUUQOwE1pHlmQ9cXTJ2MX9FQ9eHllopCLiWOkDkp6CER95ZWeXhJCP4cSm6AD4jigL5jHcZf/SkWrg8ttZUsw==",
"license": "MIT",
"dependencies": {
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
@ -12736,8 +12794,7 @@
"@babel/plugin-transform-unicode-regex": "^7.0.0-0",
"@babel/preset-typescript": "^7.16.7",
"convert-source-map": "^2.0.0",
"invariant": "^2.2.4",
"react-native-is-edge-to-edge": "1.1.7"
"invariant": "^2.2.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0",
@ -12779,9 +12836,9 @@
}
},
"node_modules/react-native-svg": {
"version": "15.11.2",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.11.2.tgz",
"integrity": "sha512-+YfF72IbWQUKzCIydlijV1fLuBsQNGMT6Da2kFlo1sh+LE3BIm/2Q7AR1zAAR6L0BFLi1WaQPLfFUC9bNZpOmw==",
"version": "15.8.0",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.8.0.tgz",
"integrity": "sha512-KHJzKpgOjwj1qeZzsBjxNdoIgv2zNCO9fVcoq2TEhTRsVV5DGTZ9JzUZwybd7q4giT/H3RdtqC3u44dWdO0Ffw==",
"license": "MIT",
"dependencies": {
"css-select": "^5.1.0",
@ -13266,6 +13323,21 @@
"node": ">=0.10.0"
}
},
"node_modules/recyclerlistview": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.1.tgz",
"integrity": "sha512-NtVYjofwgUCt1rEsTp6jHQg/47TWjnO92TU2kTVgJ9wsc/ely4HnizHHa+f/dI7qaw4+zcSogElrLjhMltN2/g==",
"license": "Apache-2.0",
"dependencies": {
"lodash.debounce": "4.0.8",
"prop-types": "15.8.1",
"ts-object-utils": "0.0.5"
},
"peerDependencies": {
"react": ">= 15.2.1",
"react-native": ">= 0.30.0"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -14062,18 +14134,6 @@
"node": ">=0.10.0"
}
},
"node_modules/split": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
"integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
"license": "MIT",
"dependencies": {
"through": "2"
},
"engines": {
"node": "*"
}
},
"node_modules/split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
@ -14394,13 +14454,6 @@
"node": ">= 6"
}
},
"node_modules/sudo-prompt": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.1.1.tgz",
"integrity": "sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT"
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -14767,12 +14820,6 @@
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
"license": "MIT"
},
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"license": "MIT"
},
"node_modules/timm": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz",
@ -14874,6 +14921,12 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"license": "Apache-2.0"
},
"node_modules/ts-object-utils": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz",
"integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==",
"license": "ISC"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -14972,9 +15025,9 @@
}
},
"node_modules/undici": {
"version": "6.21.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz",
"integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==",
"version": "6.21.3",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
"license": "MIT",
"engines": {
"node": ">=18.17"

View file

@ -11,18 +11,19 @@
"dependencies": {
"@backpackapp-io/react-native-toast": "^0.14.0",
"@expo/metro-runtime": "~4.0.1",
"@expo/vector-icons": "^14.1.0",
"@expo/vector-icons": "~14.0.4",
"@lottiefiles/dotlottie-react": "^0.6.5",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-community/slider": "^4.5.6",
"@react-native-picker/picker": "^2.11.0",
"@react-native-community/slider": "4.5.5",
"@react-native-picker/picker": "2.9.0",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"@react-navigation/stack": "^7.2.10",
"@sentry/react-native": "^6.15.1",
"@shopify/flash-list": "^2.0.2",
"@sentry/react-native": "~6.10.0",
"@shopify/flash-list": "1.7.3",
"@supabase/supabase-js": "^2.54.0",
"@types/lodash": "^4.17.16",
"@types/react-native-video": "^5.0.20",
@ -31,7 +32,7 @@
"cheerio-without-node-native": "^0.20.2",
"date-fns": "^4.1.0",
"eventemitter3": "^5.0.1",
"expo": "~52.0.43",
"expo": "~52.0.47",
"expo-application": "~6.0.2",
"expo-auth-session": "^6.0.3",
"expo-blur": "^14.0.3",
@ -48,9 +49,10 @@
"expo-screen-orientation": "~8.0.4",
"expo-status-bar": "~2.0.1",
"expo-system-ui": "^4.0.9",
"expo-updates": "~0.27.4",
"expo-web-browser": "~14.0.2",
"lodash": "^4.17.21",
"lottie-react-native": "^7.3.1",
"lottie-react-native": "7.1.0",
"posthog-react-native": "^4.4.0",
"react": "18.3.1",
"react-native": "0.76.9",
@ -59,10 +61,10 @@
"react-native-image-colors": "^2.5.0",
"react-native-immersive-mode": "^2.0.2",
"react-native-paper": "^5.13.1",
"react-native-reanimated": "^3.18.0",
"react-native-reanimated": "~3.16.1",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.4.0",
"react-native-svg": "^15.11.2",
"react-native-svg": "15.8.0",
"react-native-url-polyfill": "^2.0.0",
"react-native-video": "^6.12.0",
"react-native-vlc-media-player": "^1.0.87",

View file

@ -13,7 +13,8 @@ import {
Dimensions,
Image,
Button,
Linking
Linking,
Clipboard
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useNavigation } from '@react-navigation/native';
@ -30,6 +31,7 @@ import { useAccount } from '../contexts/AccountContext';
import { catalogService } from '../services/catalogService';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import * as Sentry from '@sentry/react-native';
import UpdateService from '../services/updateService';
const { width, height } = Dimensions.get('window');
const isTablet = width >= 768;
@ -43,6 +45,7 @@ const SETTINGS_CATEGORIES = [
{ id: 'appearance', title: 'Appearance', icon: 'palette' },
{ id: 'integrations', title: 'Integrations', icon: 'extension' },
{ id: 'playback', title: 'Playback', icon: 'play-circle-outline' },
{ id: 'updates', title: 'Updates', icon: 'system-update' },
{ id: 'about', title: 'About', icon: 'info-outline' },
{ id: 'developer', title: 'Developer', icon: 'code' },
{ id: 'cache', title: 'Cache', icon: 'cached' },
@ -220,6 +223,348 @@ const Sidebar: React.FC<SidebarProps> = ({ selectedCategory, onCategorySelect, c
);
};
// Updates Section Component
interface UpdatesSectionProps {
isTablet: boolean;
}
const UpdatesSection: React.FC<UpdatesSectionProps> = ({ isTablet }) => {
const { currentTheme } = useTheme();
const [updateInfo, setUpdateInfo] = useState<any>(null);
const [isChecking, setIsChecking] = useState(false);
const [isInstalling, setIsInstalling] = useState(false);
const [lastChecked, setLastChecked] = useState<Date | null>(null);
const [logs, setLogs] = useState<string[]>([]);
const [showLogs, setShowLogs] = useState(false);
const [lastOperation, setLastOperation] = useState<string>('');
const checkForUpdates = async () => {
try {
setIsChecking(true);
setLastOperation('Checking for updates...');
const info = await UpdateService.checkForUpdates();
setUpdateInfo(info);
setLastChecked(new Date());
// Refresh logs after operation
const logs = UpdateService.getLogs();
setLogs(logs);
if (info.isAvailable) {
setLastOperation(`Update available: ${info.manifest?.id?.substring(0, 8) || 'unknown'}...`);
} else {
setLastOperation('No updates available');
}
} catch (error) {
console.error('Error checking for updates:', error);
setLastOperation(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
Alert.alert('Error', 'Failed to check for updates');
} finally {
setIsChecking(false);
}
};
const installUpdate = async () => {
try {
setIsInstalling(true);
setLastOperation('Installing update...');
const success = await UpdateService.downloadAndInstallUpdate();
// Refresh logs after operation
const logs = UpdateService.getLogs();
setLogs(logs);
if (success) {
setLastOperation('Update installed successfully');
Alert.alert('Success', 'Update will be applied on next app restart');
} else {
setLastOperation('No update available to install');
Alert.alert('No Update', 'No update available to install');
}
} catch (error) {
console.error('Error installing update:', error);
setLastOperation(`Installation error: ${error instanceof Error ? error.message : 'Unknown error'}`);
Alert.alert('Error', 'Failed to install update');
} finally {
setIsInstalling(false);
}
};
const getCurrentUpdateInfo = async () => {
const info = await UpdateService.getCurrentUpdateInfo();
setUpdateInfo(info);
const logs = UpdateService.getLogs();
setLogs(logs);
};
const refreshLogs = () => {
const logs = UpdateService.getLogs();
setLogs(logs);
};
const clearLogs = () => {
UpdateService.clearLogs();
setLogs([]);
setLastOperation('Logs cleared');
};
const copyLog = (logText: string) => {
Clipboard.setString(logText);
Alert.alert('Copied', 'Log entry copied to clipboard');
};
const copyAllLogs = () => {
const allLogsText = logs.join('\n');
Clipboard.setString(allLogsText);
Alert.alert('Copied', 'All logs copied to clipboard');
};
const addTestLog = () => {
UpdateService.addTestLog(`Test log entry at ${new Date().toISOString()}`);
const logs = UpdateService.getLogs();
setLogs(logs);
setLastOperation('Test log added');
};
const testConnectivity = async () => {
try {
setLastOperation('Testing connectivity...');
const isReachable = await UpdateService.testUpdateConnectivity();
const logs = UpdateService.getLogs();
setLogs(logs);
if (isReachable) {
setLastOperation('Update server is reachable');
} else {
setLastOperation('Update server is not reachable');
}
} catch (error) {
console.error('Error testing connectivity:', error);
setLastOperation(`Connectivity test error: ${error instanceof Error ? error.message : 'Unknown error'}`);
const logs = UpdateService.getLogs();
setLogs(logs);
}
};
const testAssetUrls = async () => {
try {
setLastOperation('Testing asset URLs...');
await UpdateService.testAllAssetUrls();
const logs = UpdateService.getLogs();
setLogs(logs);
setLastOperation('Asset URL testing completed');
} catch (error) {
console.error('Error testing asset URLs:', error);
setLastOperation(`Asset URL test error: ${error instanceof Error ? error.message : 'Unknown error'}`);
const logs = UpdateService.getLogs();
setLogs(logs);
}
};
// Load current update info on mount
useEffect(() => {
const loadInitialData = async () => {
await getCurrentUpdateInfo();
// Also refresh logs to ensure we have the latest
refreshLogs();
};
loadInitialData();
}, []);
const formatDate = (date: Date) => {
return date.toLocaleString();
};
return (
<View>
<SettingsCard title="APP UPDATES" isTablet={isTablet}>
<SettingItem
title="Check for Updates"
description={isChecking ? "Checking..." : lastOperation || "Manually check for new updates"}
icon="system-update"
onPress={checkForUpdates}
renderControl={() => (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{isChecking && (
<View style={[styles.loadingSpinner, { borderColor: currentTheme.colors.primary }]} />
)}
<MaterialIcons
name="chevron-right"
size={24}
color={currentTheme.colors.mediumEmphasis}
/>
</View>
)}
isTablet={isTablet}
/>
{updateInfo?.isAvailable && (
<SettingItem
title="Install Update"
description={isInstalling ? "Installing..." : "Download and install the latest update"}
icon="download"
onPress={installUpdate}
renderControl={() => (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{isInstalling && (
<View style={[styles.loadingSpinner, { borderColor: currentTheme.colors.primary }]} />
)}
<MaterialIcons
name="chevron-right"
size={24}
color={currentTheme.colors.mediumEmphasis}
/>
</View>
)}
isTablet={isTablet}
/>
)}
<SettingItem
title="Current Version"
description={updateInfo?.manifest?.id ? `Update ID: ${updateInfo.manifest.id.substring(0, 8)}...` : "App version info"}
icon="info"
isTablet={isTablet}
/>
{lastChecked && (
<SettingItem
title="Last Checked"
description={formatDate(lastChecked)}
icon="schedule"
isTablet={isTablet}
/>
)}
<SettingItem
title="Update Logs"
description={`${logs.length} log entries`}
icon="history"
onPress={() => setShowLogs(!showLogs)}
renderControl={() => (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<MaterialIcons
name={showLogs ? "expand-less" : "expand-more"}
size={24}
color={currentTheme.colors.mediumEmphasis}
/>
</View>
)}
isTablet={isTablet}
/>
</SettingsCard>
{showLogs && (
<SettingsCard title="UPDATE LOGS" isTablet={isTablet}>
<View style={styles.logsContainer}>
<View style={styles.logsHeader}>
<Text style={[styles.logsHeaderText, { color: currentTheme.colors.highEmphasis }]}>
Update Service Logs
</Text>
<View style={styles.logsActions}>
<TouchableOpacity
style={[styles.logActionButton, { backgroundColor: currentTheme.colors.elevation2 }]}
onPress={testConnectivity}
activeOpacity={0.7}
>
<MaterialIcons name="wifi" size={16} color={currentTheme.colors.primary} />
</TouchableOpacity>
<TouchableOpacity
style={[styles.logActionButton, { backgroundColor: currentTheme.colors.elevation2 }]}
onPress={testAssetUrls}
activeOpacity={0.7}
>
<MaterialIcons name="link" size={16} color={currentTheme.colors.primary} />
</TouchableOpacity>
<TouchableOpacity
style={[styles.logActionButton, { backgroundColor: currentTheme.colors.elevation2 }]}
onPress={addTestLog}
activeOpacity={0.7}
>
<MaterialIcons name="add" size={16} color={currentTheme.colors.primary} />
</TouchableOpacity>
<TouchableOpacity
style={[styles.logActionButton, { backgroundColor: currentTheme.colors.elevation2 }]}
onPress={copyAllLogs}
activeOpacity={0.7}
>
<MaterialIcons name="content-copy" size={16} color={currentTheme.colors.primary} />
</TouchableOpacity>
<TouchableOpacity
style={[styles.logActionButton, { backgroundColor: currentTheme.colors.elevation2 }]}
onPress={refreshLogs}
activeOpacity={0.7}
>
<MaterialIcons name="refresh" size={16} color={currentTheme.colors.primary} />
</TouchableOpacity>
<TouchableOpacity
style={[styles.logActionButton, { backgroundColor: currentTheme.colors.elevation2 }]}
onPress={clearLogs}
activeOpacity={0.7}
>
<MaterialIcons name="clear" size={16} color={currentTheme.colors.error || '#ff4444'} />
</TouchableOpacity>
</View>
</View>
<ScrollView
style={[styles.logsScrollView, { backgroundColor: currentTheme.colors.elevation2 }]}
showsVerticalScrollIndicator={true}
nestedScrollEnabled={true}
>
{logs.length === 0 ? (
<Text style={[styles.noLogsText, { color: currentTheme.colors.mediumEmphasis }]}>
No logs available
</Text>
) : (
logs.map((log, index) => {
const isError = log.includes('[ERROR]');
const isWarning = log.includes('[WARN]');
return (
<TouchableOpacity
key={index}
style={[
styles.logEntry,
{ backgroundColor: 'rgba(255,255,255,0.05)' }
]}
onPress={() => copyLog(log)}
activeOpacity={0.7}
>
<View style={styles.logEntryContent}>
<Text style={[
styles.logText,
{
color: isError
? (currentTheme.colors.error || '#ff4444')
: isWarning
? (currentTheme.colors.warning || '#ffaa00')
: currentTheme.colors.mediumEmphasis
}
]}>
{log}
</Text>
<MaterialIcons
name="content-copy"
size={14}
color={currentTheme.colors.mediumEmphasis}
style={styles.logCopyIcon}
/>
</View>
</TouchableOpacity>
);
})
)}
</ScrollView>
</View>
</SettingsCard>
)}
</View>
);
};
const SettingsScreen: React.FC = () => {
const { settings, updateSetting } = useSettings();
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
@ -582,7 +927,7 @@ const SettingsScreen: React.FC = () => {
/>
<SettingItem
title="Version"
description="0.6.0-beta.8"
description="0.6.0-beta.8-test"
icon="info-outline"
isLast={true}
isTablet={isTablet}
@ -657,6 +1002,9 @@ const SettingsScreen: React.FC = () => {
</SettingsCard>
) : null;
case 'updates':
return <UpdatesSection isTablet={isTablet} />;
default:
return null;
}
@ -748,6 +1096,7 @@ const SettingsScreen: React.FC = () => {
{renderCategoryContent('appearance')}
{renderCategoryContent('integrations')}
{renderCategoryContent('playback')}
{renderCategoryContent('updates')}
{renderCategoryContent('about')}
{renderCategoryContent('developer')}
{renderCategoryContent('cache')}
@ -1048,6 +1397,70 @@ const styles = StyleSheet.create({
fontSize: 14,
fontWeight: '500',
},
loadingSpinner: {
width: 16,
height: 16,
borderWidth: 2,
borderRadius: 8,
borderTopColor: 'transparent',
marginRight: 8,
},
// Logs styles
logsContainer: {
padding: 16,
},
logsHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
logsHeaderText: {
fontSize: 16,
fontWeight: '600',
},
logsActions: {
flexDirection: 'row',
gap: 8,
},
logActionButton: {
width: 32,
height: 32,
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
},
logsScrollView: {
maxHeight: 200,
borderRadius: 8,
padding: 12,
},
logEntry: {
marginBottom: 4,
paddingVertical: 4,
paddingHorizontal: 8,
borderRadius: 4,
},
logEntryContent: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
logText: {
fontSize: 12,
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
lineHeight: 16,
flex: 1,
marginRight: 8,
},
logCopyIcon: {
opacity: 0.6,
},
noLogsText: {
fontSize: 14,
textAlign: 'center',
paddingVertical: 20,
},
});
export default SettingsScreen;

View file

@ -0,0 +1,467 @@
import * as Updates from 'expo-updates';
import { Platform } from 'react-native';
export interface UpdateInfo {
isAvailable: boolean;
manifest?: Partial<Updates.Manifest>;
isNew?: boolean;
isEmbeddedLaunch?: boolean;
}
export class UpdateService {
private static instance: UpdateService;
private updateCheckInterval: NodeJS.Timeout | null = null;
private readonly CHECK_INTERVAL = 5 * 60 * 1000; // 5 minutes
private logs: string[] = [];
private readonly MAX_LOGS = 100; // Keep last 100 logs
private constructor() {}
public static getInstance(): UpdateService {
if (!UpdateService.instance) {
UpdateService.instance = new UpdateService();
}
return UpdateService.instance;
}
/**
* Add a log entry with timestamp - always log to console for adb logcat visibility
*/
private addLog(message: string, level: 'INFO' | 'WARN' | 'ERROR' = 'INFO'): void {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] [${level}] ${message}`;
this.logs.unshift(logEntry);
// Keep only the last MAX_LOGS entries
if (this.logs.length > this.MAX_LOGS) {
this.logs = this.logs.slice(0, this.MAX_LOGS);
}
// Always log to console - this will be visible in adb logcat for production builds
// Use different console methods for better filtering in logcat
if (level === 'ERROR') {
console.error(`[UpdateService] ${logEntry}`);
} else if (level === 'WARN') {
console.warn(`[UpdateService] ${logEntry}`);
} else {
console.log(`[UpdateService] ${logEntry}`);
}
// Also log with a consistent prefix for easy filtering
console.log(`UpdateService: ${logEntry}`);
}
/**
* Get all logs
*/
public getLogs(): string[] {
return [...this.logs];
}
/**
* Clear all logs
*/
public clearLogs(): void {
this.logs = [];
this.addLog('Logs cleared', 'INFO');
}
/**
* Add a test log entry (useful for debugging)
*/
public addTestLog(message: string): void {
this.addLog(`TEST: ${message}`, 'INFO');
}
/**
* Test the update URL connectivity
*/
public async testUpdateConnectivity(): Promise<boolean> {
this.addLog('Testing update server connectivity...', 'INFO');
try {
const updateUrl = this.getUpdateUrl();
this.addLog(`Testing URL: ${updateUrl}`, 'INFO');
const response = await fetch(updateUrl, {
method: 'GET',
headers: {
'expo-runtime-version': Updates.runtimeVersion || '0.6.0-beta.8',
'expo-platform': Platform.OS,
'expo-protocol-version': '1',
'expo-api-version': '1',
},
});
this.addLog(`Response status: ${response.status}`, 'INFO');
this.addLog(`Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`, 'INFO');
if (response.ok) {
this.addLog('Update server is reachable', 'INFO');
// Try to get the response body to see what we're getting
try {
const responseText = await response.text();
this.addLog(`Response body preview: ${responseText.substring(0, 500)}...`, 'INFO');
} catch (bodyError) {
this.addLog(`Could not read response body: ${bodyError instanceof Error ? bodyError.message : String(bodyError)}`, 'WARN');
}
return true;
} else {
this.addLog(`Update server returned error: ${response.status} ${response.statusText}`, 'ERROR');
return false;
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.addLog(`Connectivity test failed: ${errorMessage}`, 'ERROR');
return false;
}
}
/**
* Test individual asset URL accessibility
*/
public async testAssetUrl(assetUrl: string): Promise<boolean> {
this.addLog(`Testing asset URL: ${assetUrl}`, 'INFO');
try {
const response = await fetch(assetUrl, {
method: 'HEAD', // Use HEAD to avoid downloading the full asset
});
this.addLog(`Asset response status: ${response.status}`, 'INFO');
this.addLog(`Asset response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`, 'INFO');
if (response.ok) {
this.addLog('Asset URL is accessible', 'INFO');
return true;
} else {
this.addLog(`Asset URL returned error: ${response.status} ${response.statusText}`, 'ERROR');
return false;
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.addLog(`Asset URL test failed: ${errorMessage}`, 'ERROR');
return false;
}
}
/**
* Test all asset URLs from the latest update manifest
*/
public async testAllAssetUrls(): Promise<void> {
this.addLog('Testing all asset URLs from latest update...', 'INFO');
try {
const update = await Updates.checkForUpdateAsync();
if (!update.isAvailable || !update.manifest) {
this.addLog('No update available or no manifest found', 'WARN');
return;
}
this.addLog(`Found update with ${update.manifest.assets?.length || 0} assets`, 'INFO');
if (update.manifest.assets && update.manifest.assets.length > 0) {
for (let i = 0; i < update.manifest.assets.length; i++) {
const asset = update.manifest.assets[i];
if (asset.url) {
this.addLog(`Testing asset ${i + 1}/${update.manifest.assets.length}: ${asset.key || 'unknown'}`, 'INFO');
const isAccessible = await this.testAssetUrl(asset.url);
if (!isAccessible) {
this.addLog(`Asset ${i + 1} is not accessible: ${asset.url}`, 'ERROR');
}
} else {
this.addLog(`Asset ${i + 1} has no URL`, 'ERROR');
}
}
}
// Test launch asset (check if it exists in the manifest)
const manifest = update.manifest as any; // Type assertion to access launchAsset
if (manifest.launchAsset?.url) {
this.addLog('Testing launch asset...', 'INFO');
const isAccessible = await this.testAssetUrl(manifest.launchAsset.url);
if (!isAccessible) {
this.addLog(`Launch asset is not accessible: ${manifest.launchAsset.url}`, 'ERROR');
}
} else {
this.addLog('No launch asset URL found', 'ERROR');
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.addLog(`Failed to test asset URLs: ${errorMessage}`, 'ERROR');
}
}
/**
* Initialize the update service
*/
public async initialize(): Promise<void> {
this.addLog('Initializing UpdateService...', 'INFO');
this.addLog(`Environment: ${__DEV__ ? 'Development' : 'Production'}`, 'INFO');
this.addLog(`Platform: ${Platform.OS}`, 'INFO');
this.addLog(`Updates enabled: ${Updates.isEnabled}`, 'INFO');
this.addLog(`Runtime version: ${Updates.runtimeVersion || 'unknown'}`, 'INFO');
this.addLog(`Update URL: ${this.getUpdateUrl()}`, 'INFO');
try {
// Always perform update check regardless of environment for debugging
this.addLog('Performing initial update check...', 'INFO');
const updateInfo = await this.checkForUpdates();
if (updateInfo.isAvailable) {
this.addLog(`Update available: ${updateInfo.manifest?.id || 'unknown'}`, 'INFO');
} else {
this.addLog('No updates available', 'INFO');
}
// Check if we're running in a development environment
if (__DEV__) {
this.addLog('Running in development mode, but allowing update checks for testing', 'WARN');
this.addLog('UpdateService initialization completed (dev mode)', 'INFO');
// Don't return early - allow update checks in dev mode for testing
}
// Check if updates are enabled
if (!Updates.isEnabled) {
this.addLog('Updates are not enabled in this environment', 'WARN');
this.addLog('UpdateService initialization completed (updates disabled)', 'INFO');
return;
}
this.addLog('Updates are enabled, setting up periodic checks', 'INFO');
// Set up periodic update checks
this.startPeriodicUpdateChecks();
this.addLog('UpdateService initialization completed successfully', 'INFO');
} catch (error) {
this.addLog(`Initialization failed: ${error instanceof Error ? error.message : String(error)}`, 'ERROR');
console.error('Update service initialization failed:', error);
}
}
/**
* Check for available updates
*/
public async checkForUpdates(): Promise<UpdateInfo> {
this.addLog('Starting update check...', 'INFO');
this.addLog(`Update URL: ${this.getUpdateUrl()}`, 'INFO');
this.addLog(`Runtime version: ${Updates.runtimeVersion || 'unknown'}`, 'INFO');
this.addLog(`Platform: ${Platform.OS}`, 'INFO');
this.addLog(`Updates enabled: ${Updates.isEnabled}`, 'INFO');
try {
// Always attempt the check for debugging purposes
this.addLog('Calling Updates.checkForUpdateAsync()...', 'INFO');
const startTime = Date.now();
const update = await Updates.checkForUpdateAsync();
const duration = Date.now() - startTime;
this.addLog(`Update check completed in ${duration}ms`, 'INFO');
this.addLog(`Check result - isAvailable: ${update.isAvailable}`, 'INFO');
if (update.isAvailable) {
this.addLog(`Update available! ID: ${update.manifest?.id || 'unknown'}`, 'INFO');
if (update.manifest) {
this.addLog(`Manifest ID: ${update.manifest.id || 'unknown'}`, 'INFO');
}
// Check if we can actually install updates
if (__DEV__) {
this.addLog('WARNING: Update found but in development mode - installation will be skipped', 'WARN');
} else if (!Updates.isEnabled) {
this.addLog('WARNING: Update found but updates disabled - installation will be skipped', 'WARN');
} else {
this.addLog('Update found and installation is possible', 'INFO');
}
return {
isAvailable: true,
manifest: update.manifest,
isNew: false, // Default value since isNew is not available in the type
isEmbeddedLaunch: false // Default value since isEmbeddedLaunch is not available in the type
};
}
this.addLog('No updates available', 'INFO');
return { isAvailable: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.addLog(`Update check failed: ${errorMessage}`, 'ERROR');
console.error('Failed to check for updates:', error);
return { isAvailable: false };
}
}
/**
* Download and install the latest update
*/
public async downloadAndInstallUpdate(): Promise<boolean> {
this.addLog('Starting update download and installation...', 'INFO');
try {
// Check environment and updates status first
if (__DEV__) {
this.addLog('Running in development mode - update installation may have limitations', 'WARN');
this.addLog('In development mode, Updates.checkForUpdateAsync() may not work properly', 'WARN');
// Don't return false - allow attempting updates in dev mode for testing
}
if (!Updates.isEnabled) {
this.addLog('Update installation skipped (updates disabled)', 'WARN');
this.addLog('Updates.isEnabled is false - this is why installation fails', 'ERROR');
return false;
}
this.addLog('Environment checks passed, proceeding with installation', 'INFO');
this.addLog('Checking for available updates before installation...', 'INFO');
this.addLog(`Update URL: ${this.getUpdateUrl()}`, 'INFO');
this.addLog(`Runtime version: ${Updates.runtimeVersion || 'unknown'}`, 'INFO');
this.addLog(`Platform: ${Platform.OS}`, 'INFO');
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
this.addLog(`Update found, starting download. ID: ${update.manifest?.id || 'unknown'}`, 'INFO');
this.addLog(`Manifest details: ${JSON.stringify(update.manifest, null, 2)}`, 'INFO');
const downloadStartTime = Date.now();
this.addLog('Calling Updates.fetchUpdateAsync()...', 'INFO');
try {
await Updates.fetchUpdateAsync();
const downloadDuration = Date.now() - downloadStartTime;
this.addLog(`Update downloaded successfully in ${downloadDuration}ms`, 'INFO');
this.addLog('Calling Updates.reloadAsync() to apply update...', 'INFO');
await Updates.reloadAsync();
this.addLog('Update installation completed successfully', 'INFO');
return true;
} catch (fetchError) {
const errorMessage = fetchError instanceof Error ? fetchError.message : String(fetchError);
this.addLog(`Update fetch failed: ${errorMessage}`, 'ERROR');
this.addLog(`Fetch error stack: ${fetchError instanceof Error ? fetchError.stack : 'No stack available'}`, 'ERROR');
throw fetchError; // Re-throw to be caught by outer catch block
}
}
this.addLog('No update available for installation', 'WARN');
this.addLog('Updates.checkForUpdateAsync() returned isAvailable: false', 'INFO');
return false;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.addLog(`Update installation failed: ${errorMessage}`, 'ERROR');
this.addLog(`Error stack: ${error instanceof Error ? error.stack : 'No stack available'}`, 'ERROR');
console.error('Failed to download/install update:', error);
return false;
}
}
/**
* Get current update info
*/
public async getCurrentUpdateInfo(): Promise<UpdateInfo> {
try {
this.addLog('Getting current update info...', 'INFO');
this.addLog(`Updates.isEnabled: ${Updates.isEnabled}`, 'INFO');
this.addLog(`Updates.isEmbeddedLaunch: ${Updates.isEmbeddedLaunch}`, 'INFO');
if (__DEV__) {
this.addLog('In development mode - update info may not be accurate', 'WARN');
}
if (!Updates.isEnabled) {
this.addLog('Updates disabled - returning false for isAvailable', 'WARN');
return { isAvailable: false };
}
const info = {
isAvailable: Updates.isEmbeddedLaunch === false,
manifest: Updates.manifest,
isNew: false, // Default value since Updates.isNew is not available
isEmbeddedLaunch: Updates.isEmbeddedLaunch
};
this.addLog(`Current update info - Available: ${info.isAvailable}, Embedded: ${info.isEmbeddedLaunch}`, 'INFO');
if (info.manifest) {
this.addLog(`Current manifest ID: ${info.manifest.id || 'unknown'}`, 'INFO');
} else {
this.addLog('No manifest available', 'INFO');
}
return info;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.addLog(`Failed to get current update info: ${errorMessage}`, 'ERROR');
console.error('Failed to get current update info:', error);
return { isAvailable: false };
}
}
/**
* Start periodic update checks
*/
private startPeriodicUpdateChecks(): void {
if (this.updateCheckInterval) {
this.addLog('Stopping existing periodic update checks', 'INFO');
clearInterval(this.updateCheckInterval);
}
this.addLog(`Starting periodic update checks every ${this.CHECK_INTERVAL / 1000} seconds`, 'INFO');
this.updateCheckInterval = setInterval(async () => {
try {
this.addLog('Performing scheduled update check...', 'INFO');
await this.checkForUpdates();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.addLog(`Scheduled update check failed: ${errorMessage}`, 'ERROR');
console.error('Periodic update check failed:', error);
}
}, this.CHECK_INTERVAL);
}
/**
* Stop periodic update checks
*/
public stopPeriodicUpdateChecks(): void {
if (this.updateCheckInterval) {
this.addLog('Stopping periodic update checks', 'INFO');
clearInterval(this.updateCheckInterval);
this.updateCheckInterval = null;
} else {
this.addLog('No periodic update checks running to stop', 'INFO');
}
}
/**
* Get the update URL for the current platform
*/
public getUpdateUrl(): string {
// Use the URL from app.json configuration
return 'https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest';
}
/**
* Cleanup resources
*/
public cleanup(): void {
this.addLog('Cleaning up UpdateService resources...', 'INFO');
this.stopPeriodicUpdateChecks();
this.addLog('UpdateService cleanup completed', 'INFO');
}
}
export default UpdateService.getInstance();

1
xavia-ota Submodule

@ -0,0 +1 @@
Subproject commit ba2fe779d75a8ed285c5d37f05532c3ff4acce22