Merge branch 'main' into feature/websockets

This commit is contained in:
Violet Caulfield
2025-12-29 19:12:03 -06:00
committed by GitHub
96 changed files with 2002 additions and 2001 deletions

View File

@@ -10,7 +10,7 @@ body:
attributes:
value: "Please provide the following information to help us investigate and resolve the issue."
- type: input
- type: textarea
id: summary
attributes:
label: Describe the issue.
@@ -58,8 +58,9 @@ body:
multiple: true
options:
- Android
- iOS
- iPadOS
- iOS / iPadOS
- macOS
- ChromeOS
validations:
required: true
@@ -96,9 +97,10 @@ body:
label: Which distribution are you using?
description: As Jellify is distributed on multiple platforms, please select which version you are using.
options:
- Apple App Store
- Apple App Store / Testflight
- Google Play Store
- GitHub Releases
- From source
default: 0
validations:
required: true
@@ -118,4 +120,4 @@ body:
label: If there any relevant links, please provide them here.
description: Links to Discord messages, GitHub issues, or other resources related to this issue.
validations:
required: false
required: false

View File

@@ -43,7 +43,7 @@ jobs:
fi
- uses: actions/cache@v3
- uses: actions/cache@v5
with:
path: |
node_modules
@@ -68,7 +68,7 @@ jobs:
- name: 📦 Upload APK for testing
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
if: always()
with:
name: jellify-android-pr-${{ github.event.number }}-${{ env.VERSION_NUMBER }}

View File

@@ -54,7 +54,7 @@ jobs:
zip -r Jellify-Release-Simulator.zip Jellify.app
- name: 📦 Upload IPA for testing
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
if: always()
with:
name: jellify-ios-pr-${{ github.event.number }}-${{ env.VERSION_NUMBER }}

View File

@@ -24,7 +24,7 @@ jobs:
with:
bun-version: 1.3.4
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
node_modules
@@ -34,13 +34,13 @@ jobs:
${{ runner.os }}-bun-turbo-
- name: ☕ Setup JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '17'
- name: 🐘 Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v5
- name: 🍎 Run bun init-android
run: bun i
@@ -58,7 +58,7 @@ jobs:
run: bun run android-build
- name: 📤 Upload Android Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: android-artifacts
path: ./android/app/build/outputs/apk/release/*.apk
@@ -90,7 +90,7 @@ jobs:
with:
bun-version: 1.3.4
- uses: actions/cache@v3
- uses: actions/cache@v5
with:
path: |
node_modules
@@ -104,13 +104,13 @@ jobs:
run: export MAESTRO_VERSION=1.40.0; curl -Ls "https://get.maestro.mobile.dev" | bash
- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'zulu'
- name: ⬇️ Download Android Artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: android-artifacts
path: artifacts/
@@ -154,7 +154,7 @@ jobs:
DISCORD_WEBHOOK_URL: ${{ secrets.MAESTRO_WEBHOOK_RESULTS }}
- name: Store tests result
uses: actions/upload-artifact@v4.3.4
uses: actions/upload-artifact@v6
if: always()
with:
name: TestResult

View File

@@ -163,7 +163,7 @@ jobs:
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
- name: 📤 Upload Android Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: android-artifacts
path: ./android/app/build/outputs/apk/release/*.apk
@@ -240,7 +240,7 @@ jobs:
MATCH_REPO_PAT: "anultravioletaurora:${{ secrets.SIGNING_REPO_PAT }}"
- name: 📤 Upload iOS Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ios-artifacts
path: ./ios/Jellify.ipa
@@ -251,7 +251,7 @@ jobs:
runs-on: macos-15
steps:
- name: 🛒 Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
token: ${{ secrets.SIGNING_REPO_PAT }}
@@ -290,14 +290,14 @@ jobs:
- name: ⬇️ Download Android Artifacts
if: ${{ github.event.inputs['build-platform'] == 'Android' || github.event.inputs['build-platform'] == 'Both' }}
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: android-artifacts
path: artifacts/
- name: ⬇️ Download iOS Artifacts
if: ${{ github.event.inputs['build-platform'] == 'iOS' || github.event.inputs['build-platform'] == 'Both' }}
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: ios-artifacts
path: artifacts/

View File

@@ -13,6 +13,8 @@ jobs:
steps:
- name: 🛒 Checkout
uses: actions/checkout@v6
with:
token: ${{ secrets.SIGNING_REPO_PAT }}
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
@@ -29,3 +31,6 @@ jobs:
- name: 🤖 Publish Update
run: bun run sendOTA:PR ${{ github.event.pull_request.number }}
env:
SIGNING_REPO_PAT: ${{ secrets.SIGNING_REPO_PAT }}

View File

@@ -22,7 +22,7 @@ jobs:
bun-version: 1.3.4
- name: 📦 Cache dependencies
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: |
~/.bun/install/cache

50
App.tsx
View File

@@ -15,7 +15,7 @@ import TrackPlayer, {
IOSCategory,
IOSCategoryOptions,
} from 'react-native-track-player'
import { CAPABILITIES } from './src/player/constants'
import { CAPABILITIES } from './src/constants/player'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { NavigationContainer } from '@react-navigation/native'
import { JellifyDarkTheme, JellifyLightTheme, JellifyOLEDTheme } from './src/components/theme'
@@ -24,18 +24,60 @@ import ErrorBoundary from './src/components/ErrorBoundary'
import OTAUpdateScreen from './src/components/OtaUpdates'
import { usePerformanceMonitor } from './src/hooks/use-performance-monitor'
import navigationRef from './navigation'
import { BUFFERS, PROGRESS_UPDATE_EVENT_INTERVAL } from './src/player/config'
import { BUFFERS, PROGRESS_UPDATE_EVENT_INTERVAL } from './src/configs/player.config'
import { useThemeSetting } from './src/stores/settings/app'
import { useLoadNewQueue } from './src/providers/Player/hooks/mutations'
import useJellifyStore, { useApi, useJellifyLibrary } from './src/stores'
import useStreamingDeviceProfile from './src/stores/device-profile'
import { useNetworkStatus } from './src/stores/network'
import CarPlayNavigation from './src/components/CarPlay/Navigation'
import { CarPlay } from 'react-native-carplay'
import { useAutoStore } from './src/stores/auto'
import { registerAutoService } from './src/player'
LogBox.ignoreAllLogs()
export default function App(): React.JSX.Element {
// Add performance monitoring to track app-level re-renders
const performanceMetrics = usePerformanceMonitor('App', 3)
usePerformanceMonitor('App', 3)
const [playerIsReady, setPlayerIsReady] = useState<boolean>(false)
const { setIsConnected } = useAutoStore()
const playerInitializedRef = useRef<boolean>(false)
const api = useApi()
const [library] = useJellifyLibrary()
const [networkStatus] = useNetworkStatus()
const deviceProfile = useStreamingDeviceProfile()
const loadNewQueue = useLoadNewQueue()
const onConnect = () => {
if (api && library) {
CarPlay.setRootTemplate(
CarPlayNavigation(
library,
loadNewQueue,
api,
useJellifyStore.getState().user,
networkStatus,
deviceProfile,
),
)
if (Platform.OS === 'ios') {
CarPlay.enableNowPlaying(true)
}
}
setIsConnected(true)
}
const onDisconnect = () => setIsConnected(false)
useEffect(() => {
// Guard against double initialization (React StrictMode, hot reload)
if (playerInitializedRef.current) return
@@ -74,6 +116,8 @@ export default function App(): React.JSX.Element {
setPlayerIsReady(true)
requestStoragePermission()
})
return registerAutoService(onConnect, onDisconnect)
}, []) // Empty deps - only run once on mount
const [reloader, setReloader] = useState(0)

View File

@@ -133,7 +133,7 @@ Install via [Altstore](https://altstore.io) or your favorite sideloading utility
<p align="center">
<img src="screenshots/track_options.png" alt="Track Options" width="275" height="600">
<img src="screenshots/playlist.png" alt="Playlist" width="275" height="600">
<img src="screenshots/add_to_playlist.png" alt="Playlist" width="275" height="600">
</p>
---
@@ -231,7 +231,6 @@ Install via [Altstore](https://altstore.io) or your favorite sideloading utility
[React Native Blurhash](https://github.com/mrousavy/react-native-blurhash)\
[React Native CarPlay](https://github.com/birkir/react-native-carplay)\
[React Native Sortables](https://github.com/MatiPl01/react-native-sortables)\
[React Native Nitro Modules](https://github.com/mrousavy/react-native-nitro-modules)\
[React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)\
[React Native Toast Message](https://github.com/calintamas/react-native-toast-message)\
[React Native Vector Icons](https://github.com/oblador/react-native-vector-icons)
@@ -246,6 +245,8 @@ Install via [Altstore](https://altstore.io) or your favorite sideloading utility
[React Native File Access](https://github.com/alpha0010/react-native-file-access)\
[React Native Google Cast](https://github.com/react-native-google-cast/react-native-google-cast)\
[React Native MMKV](https://github.com/mrousavy/react-native-mmkv)\
[React Native Nitro Fetch](https://github.com/margelo/react-native-nitro-fetch)\
[React Native Nitro Modules](https://github.com/mrousavy/react-native-nitro-modules)\
[React Native Nitro OTA](https://github.com/riteshshukla04/react-native-nitro-ota)\
[React Native Track Player](https://github.com/doublesymmetry/react-native-track-player)\
[React Native URL Polyfill](https://github.com/charpeni/react-native-url-polyfill)\

View File

@@ -91,8 +91,8 @@ android {
applicationId "com.cosmonautical.jellify"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 165
versionName "1.0.6"
versionCode 169
versionName "1.0.10"
resValue "string", "build_config_package", "com.jellify"
}

358
bun.lock
View File

@@ -11,18 +11,18 @@
"@react-native-community/netinfo": "^11.4.1",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-native-vector-icons/material-design-icons": "12.4.0",
"@react-navigation/bottom-tabs": "7.8.12",
"@react-navigation/material-top-tabs": "7.4.10",
"@react-navigation/native": "7.1.25",
"@react-navigation/native-stack": "7.8.6",
"@react-navigation/bottom-tabs": "7.9.0",
"@react-navigation/material-top-tabs": "7.4.11",
"@react-navigation/native": "7.1.26",
"@react-navigation/native-stack": "7.9.0",
"@sentry/react-native": "7.8.0",
"@shopify/flash-list": "2.2.0",
"@tamagui/config": "1.141.4",
"@tamagui/config": "1.141.5",
"@tanstack/query-async-storage-persister": "5.90.12",
"@tanstack/react-query": "5.90.12",
"@tanstack/react-query-persist-client": "5.90.12",
"@testing-library/react-native": "13.3.3",
"@typedigital/telemetrydeck-react": "^0.4.1",
"@typedigital/telemetrydeck-react": "0.4.1",
"axios": "1.13.2",
"bundle": "^2.1.0",
"dlx": "^0.2.1",
@@ -30,14 +30,13 @@
"lodash": "^4.17.21",
"openai": "5.21.0",
"react": "19.2.0",
"react-native": "0.83.0",
"react-native": "0.83.1",
"react-native-background-actions": "^4.0.1",
"react-native-blob-util": "^0.22.2",
"react-native-blurhash": "^2.1.3",
"react-native-carplay": "^2.4.1-beta.0",
"react-native-config": "1.5.6",
"react-native-device-info": "15.0.1",
"react-native-dns-lookup": "^1.0.6",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "2.29.1",
"react-native-google-cast": "^4.9.1",
@@ -45,9 +44,9 @@
"react-native-linear-gradient": "^2.8.3",
"react-native-mmkv": "^4.1.0",
"react-native-nitro-fetch": "^0.1.6",
"react-native-nitro-modules": "0.31.10",
"react-native-nitro-ota": "0.8.2",
"react-native-pager-view": "^7.0.2",
"react-native-nitro-modules": "0.32.0-beta.0",
"react-native-nitro-ota": "0.9.0",
"react-native-pager-view": "8.0.0",
"react-native-reanimated": "4.1.6",
"react-native-safe-area-context": "5.6.2",
"react-native-screens": "4.19.0",
@@ -55,30 +54,31 @@
"react-native-text-ticker": "^1.15.0",
"react-native-toast-message": "^2.3.3",
"react-native-track-player": "5.0.0-alpha0",
"react-native-turbo-image": "^1.23.1",
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-worklets": "^0.7.1",
"react-native-worklets-core": "^1.6.2",
"ruby": "^0.6.1",
"scheduler": "^0.26.0",
"tamagui": "1.141.4",
"tamagui": "1.141.5",
"zustand": "5.0.9",
},
"devDependencies": {
"@babel/core": "7.28.5",
"@babel/preset-env": "7.28.5",
"@babel/runtime": "7.28.4",
"@eslint/eslintrc": "^3.3.1",
"@eslint/eslintrc": "3.3.3",
"@eslint/js": "9.39.2",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.83.0",
"@react-native/eslint-config": "0.83.0",
"@react-native/metro-config": "0.83.0",
"@react-native/typescript-config": "0.83.0",
"@react-native/babel-preset": "0.83.1",
"@react-native/eslint-config": "0.83.1",
"@react-native/metro-config": "0.83.1",
"@react-native/typescript-config": "0.83.1",
"@types/jest": "^30.0.0",
"@types/lodash": "^4.17.21",
"@types/node": "^24.2.1",
"@types/node": "25.0.3",
"@types/react": "19.2.0",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "19.1.0",
@@ -90,7 +90,7 @@
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-native": "^5.0.0",
"globals": "^16.3.0",
"globals": "16.5.0",
"husky": "^9.1.7",
"jest": "30.2.0",
"jscodeshift": "^17.3.0",
@@ -378,7 +378,7 @@
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
"@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="],
@@ -530,55 +530,55 @@
"@react-native-vector-icons/material-design-icons": ["@react-native-vector-icons/material-design-icons@12.4.0", "", { "dependencies": { "@react-native-vector-icons/common": "^12.4.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4ewAiHdOCujqprUJYFnBcUJduNddAc+w3Plnl1NhJksAyOaHzCNBg01JgVtkysxPho6++OOMge3FhwyBT8Wtcg=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.83.0", "", {}, "sha512-EmGSKDvmnEnBrTK75T+0Syt6gy/HACOTfziw5+392Kr1Bb28Rv26GyOIkvptnT+bb2VDHU0hx9G0vSy5/S3rmQ=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.83.1", "", {}, "sha512-AT7/T6UwQqO39bt/4UL5EXvidmrddXrt0yJa7ENXndAv+8yBzMsZn6fyiax6+ERMt9GLzAECikv3lj22cn2wJA=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.83.0", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.83.0" } }, "sha512-H5K0hnv9EhcenojZb9nUMIKPvHZ7ba9vpCyQIeGJmUTDYwZqjmXXyH73+uZo+GHjCIq1n0eF/soC5HJQzalh/Q=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.83.1", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.83.1" } }, "sha512-VPj8O3pG1ESjZho9WVKxqiuryrotAECPHGF5mx46zLUYNTWR5u9OMUXYk7LeLy+JLWdGEZ2Gn3KoXeFZbuqE+g=="],
"@react-native/babel-preset": ["@react-native/babel-preset@0.83.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.83.0", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-v20aTae9+aergUgRQSiy3CLqRSJu5VrHLpPpyYcAkTJ2JWTbtTlKfYuEw0V/WMFpeYZnZ7IVtu/6gTISVV74UQ=="],
"@react-native/babel-preset": ["@react-native/babel-preset@0.83.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.83.1", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-xI+tbsD4fXcI6PVU4sauRCh0a5fuLQC849SINmU2J5wP8kzKu4Ye0YkGjUW3mfGrjaZcjkWmF6s33jpyd3gdTw=="],
"@react-native/codegen": ["@react-native/codegen@0.83.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-3fvMi/pSJHhikjwMZQplU4Ar9ANoR2GSBxotbkKIMI6iNduh+ln1FTvB2me69FA68aHtVZOO+cO+QpGCcvgaMA=="],
"@react-native/codegen": ["@react-native/codegen@0.83.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-FpRxenonwH+c2a5X5DZMKUD7sCudHxB3eSQPgV9R+uxd28QWslyAWrpnJM/Az96AEksHnymDzEmzq2HLX5nb+g=="],
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.83.0", "", { "dependencies": { "@react-native/dev-middleware": "0.83.0", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.3", "metro-config": "^0.83.3", "metro-core": "^0.83.3", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-bJD5pLURgKY2YK0R6gUsFWHiblSAFt1Xyc2fsyCL8XBnB7kJfVhLAKGItk6j1QZbwm1Io41ekZxBmZdyQqIDrg=="],
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.83.1", "", { "dependencies": { "@react-native/dev-middleware": "0.83.1", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.3", "metro-config": "^0.83.3", "metro-core": "^0.83.3", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-FqR1ftydr08PYlRbrDF06eRiiiGOK/hNmz5husv19sK6iN5nHj1SMaCIVjkH/a5vryxEddyFhU6PzO/uf4kOHg=="],
"@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.83.0", "", {}, "sha512-7XVbkH8nCjLKLe8z5DS37LNP62/QNNya/YuLlVoLfsiB54nR/kNZij5UU7rS0npAZ3WN7LR0anqLlYnzDd0JHA=="],
"@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.83.1", "", {}, "sha512-01Rn3goubFvPjHXONooLmsW0FLxJDKIUJNOlOS0cPtmmTIx9YIjxhe/DxwHXGk7OnULd7yl3aYy7WlBsEd5Xmg=="],
"@react-native/debugger-shell": ["@react-native/debugger-shell@0.83.0", "", { "dependencies": { "cross-spawn": "^7.0.6", "fb-dotslash": "0.5.8" } }, "sha512-rJJxRRLLsKW+cqd0ALSBoqwL5SQTmwpd5SGl6rq9sY+fInCUKfkLEIc5HWQ0ppqoPyDteQVWbQ3a5VN84aJaNg=="],
"@react-native/debugger-shell": ["@react-native/debugger-shell@0.83.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "fb-dotslash": "0.5.8" } }, "sha512-d+0w446Hxth5OP/cBHSSxOEpbj13p2zToUy6e5e3tTERNJ8ueGlW7iGwGTrSymNDgXXFjErX+dY4P4/3WokPIQ=="],
"@react-native/dev-middleware": ["@react-native/dev-middleware@0.83.0", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.83.0", "@react-native/debugger-shell": "0.83.0", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^7.5.10" } }, "sha512-HWn42tbp0h8RWttua6d6PjseaSr3IdwkaoqVxhiM9kVDY7Ro00eO7tdlVgSzZzhIibdVS2b2C3x+sFoWhag1fA=="],
"@react-native/dev-middleware": ["@react-native/dev-middleware@0.83.1", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.83.1", "@react-native/debugger-shell": "0.83.1", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^7.5.10" } }, "sha512-QJaSfNRzj3Lp7MmlCRgSBlt1XZ38xaBNXypXAp/3H3OdFifnTZOeYOpFmcpjcXYnDqkxetuwZg8VL65SQhB8dg=="],
"@react-native/eslint-config": ["@react-native/eslint-config@0.83.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", "@react-native/eslint-plugin": "0.83.0", "@typescript-eslint/eslint-plugin": "^8.36.0", "@typescript-eslint/parser": "^8.36.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-ft-flow": "^2.0.1", "eslint-plugin-jest": "^29.0.1", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-native": "^4.0.0" }, "peerDependencies": { "eslint": ">=8", "prettier": ">=2" } }, "sha512-HTJg5XGQSGkVqeTvO7kOm1a1fNZ0VyZqhaLKAdWNwry+cWLkSnk9uohztnEIIP33FbP0Aybc7JuZIQon9OI3+w=="],
"@react-native/eslint-config": ["@react-native/eslint-config@0.83.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", "@react-native/eslint-plugin": "0.83.1", "@typescript-eslint/eslint-plugin": "^8.36.0", "@typescript-eslint/parser": "^8.36.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-ft-flow": "^2.0.1", "eslint-plugin-jest": "^29.0.1", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-native": "^4.0.0" }, "peerDependencies": { "eslint": ">=8", "prettier": ">=2" } }, "sha512-fo3DmFywzkpVZgIji9vR93kN7sSAY122ZIB7VcudgKlmD/YFxJ5Yi+ZNiWYl6aprLexxOWjROgHXNP0B0XaAng=="],
"@react-native/eslint-plugin": ["@react-native/eslint-plugin@0.83.0", "", {}, "sha512-a0lObGV1/1P6mrekSF+1KpRkdH2fefQ/8fm1kLTUNvR5mae8xXz+U+f+1lsgqqEHtoGHey5Ve5MUkjgj4WnqTQ=="],
"@react-native/eslint-plugin": ["@react-native/eslint-plugin@0.83.1", "", {}, "sha512-nKd/FONY8aIIjtjEqI2ScvgJYeblBgdnwseRHlIC+Nm3f3tuOifUrHFtWBJznlrKFJcme31Tl7qiryE2SruLYw=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.83.0", "", {}, "sha512-BXZRmfsbgPhEPkrRPjk2njA2AzhSelBqhuoklnv3DdLTdxaRjKYW+LW0zpKo1k3qPKj7kG1YGI3miol6l1GB5g=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.83.1", "", {}, "sha512-6ESDnwevp1CdvvxHNgXluil5OkqbjkJAkVy7SlpFsMGmVhrSxNAgD09SSRxMNdKsnLtzIvMsFCzyHLsU/S4PtQ=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.83.0", "", {}, "sha512-cVB9BMqlfbQR0v4Wxi5M2yDhZoKiNqWgiEXpp7ChdZIXI0SEnj8WwLwE3bDkyOfF8tCHdytpInXyg/al2O+dLQ=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.83.1", "", {}, "sha512-qgPpdWn/c5laA+3WoJ6Fak8uOm7CG50nBsLlPsF8kbT7rUHIVB9WaP6+GPsoKV/H15koW7jKuLRoNVT7c3Ht3w=="],
"@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.83.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.83.0", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-hB5kpR5Ho9l9xKuh5uHZEIFqnuaW8T7rDYwqf1j0xvTZu88KwaHAXya2IpDZsjlWzVMCl50cAwPkVZOlEOfJvw=="],
"@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.83.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.83.1", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-fqt6DHWX1GBGDKa5WJOjDtPPy2M9lkYVLn59fBeFQ0GXhBRzNbUh8JzWWI/Q2CLDZ2tgKCcwaiXJ1OHWVd2BCQ=="],
"@react-native/metro-config": ["@react-native/metro-config@0.83.0", "", { "dependencies": { "@react-native/js-polyfills": "0.83.0", "@react-native/metro-babel-transformer": "0.83.0", "metro-config": "^0.83.3", "metro-runtime": "^0.83.3" } }, "sha512-7mWZNZOJJLMJ8adBrAgAXcwtyn8PtPjAGavK8k3/mtsWYm79ncf5PD8D9puh6wBHCYwPu2ff/l23nNV8JsqLyg=="],
"@react-native/metro-config": ["@react-native/metro-config@0.83.1", "", { "dependencies": { "@react-native/js-polyfills": "0.83.1", "@react-native/metro-babel-transformer": "0.83.1", "metro-config": "^0.83.3", "metro-runtime": "^0.83.3" } }, "sha512-1rjYZf62fCm6QAinHmRAKnJxIypX0VF/zBPd0qWvWABMZugrS0eACuIbk9Wk0StBod4yL8KnwEJyg77ak8xYzQ=="],
"@react-native/normalize-color": ["@react-native/normalize-color@2.1.0", "", {}, "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA=="],
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.83.0", "", {}, "sha512-DG1ELOqQ6RS82R1zEUGTWa/pfSPOf+vwAnQB7Ao1vRuhW/xdd2OPQJyqx5a5QWMYpGrlkCb7ERxEVX6p2QODCA=="],
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.83.1", "", {}, "sha512-84feABbmeWo1kg81726UOlMKAhcQyFXYz2SjRKYkS78QmfhVDhJ2o/ps1VjhFfBz0i/scDwT1XNv9GwmRIghkg=="],
"@react-native/typescript-config": ["@react-native/typescript-config@0.83.0", "", {}, "sha512-8IcgamT0qoBDL3D8Ho6YHkQrxUMf3fKHkRd6MYDjVKNamz0XtfXNLY/FNnUOolx1HbgMkkwKFcbP3AbIKFxirQ=="],
"@react-native/typescript-config": ["@react-native/typescript-config@0.83.1", "", {}, "sha512-y83qd7fmlZG+EJoOyKEmAXifdjN1csNhcfpyxDvgaIUNO/pw2ws3MV/wp+ERQ8F6JIuAu1zcfyCy1/pEA7tC9g=="],
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.83.0", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-AVnDppwPidQrPrzA4ETr4o9W+40yuijg3EVgFt2hnMldMZkqwPRrgJL2GSreQjCYe1NfM5Yn4Egyy4Kd0yp4Lw=="],
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.83.1", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-MdmoAbQUTOdicCocm5XAFDJWsswxk7hxa6ALnm6Y88p01HFML0W593hAn6qOt9q6IM1KbAcebtH6oOd4gcQy8w=="],
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.8.12", "", { "dependencies": { "@react-navigation/elements": "^2.9.2", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.25", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-efVt5ydHK+b4ZtjmN81iduaO5dPCmzhLBFwjCR8pV4x4VzUfJmtUJizLqTXpT3WatHdeon2gDPwhhoelsvu/JA=="],
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.9.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.3", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.26", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-024FWdHp3ZsE5rP8tmGI4vh+1z3wg8u8E9Frep8eeGoYo1h9rQhvgofQDGxknmrKsb7t8o8Dim+IZSvl57cPFQ=="],
"@react-navigation/core": ["@react-navigation/core@7.13.6", "", { "dependencies": { "@react-navigation/routers": "^7.5.2", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-7QG29HAWOR8wYuPkfTN8L2Po+kE1xn3nsi2sS35sGngq8HYZRHfXvxrhrAZYfFnFq2hUtOhcXnSS6vEWU/5rmA=="],
"@react-navigation/core": ["@react-navigation/core@7.13.7", "", { "dependencies": { "@react-navigation/routers": "^7.5.3", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-k2ABo3250vq1ovOh/iVwXS6Hwr5PVRGXoPh/ewVFOOuEKTvOx9i//OBzt8EF+HokBxS2HBRlR2b+aCOmscRqBw=="],
"@react-navigation/elements": ["@react-navigation/elements@2.9.2", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.25", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-J1GltOAGowNLznEphV/kr4zs0U7mUBO1wVA2CqpkN8ePBsoxrAmsd+T5sEYUCXN9KgTDFvc6IfcDqrGSQngd/g=="],
"@react-navigation/elements": ["@react-navigation/elements@2.9.3", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.26", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-3+eyvWiVPIEf6tN9UdduhOEHcTuNe3R5WovgiVkfH9+jApHMTZDc2loePTpY/i2HDJhObhhChpJzO6BVjrpdYQ=="],
"@react-navigation/material-top-tabs": ["@react-navigation/material-top-tabs@7.4.10", "", { "dependencies": { "@react-navigation/elements": "^2.9.2", "color": "^4.2.3", "react-native-tab-view": "^4.2.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.25", "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0" } }, "sha512-sLR7ctxvqhszJwaBmbZa3lPHL++c0LNFiValMnJ39m3JdfpEsahPUXsbsoT4sqBaU7lpm1uYzRsAyfJEci7DrQ=="],
"@react-navigation/material-top-tabs": ["@react-navigation/material-top-tabs@7.4.11", "", { "dependencies": { "@react-navigation/elements": "^2.9.3", "color": "^4.2.3", "react-native-tab-view": "^4.2.2" }, "peerDependencies": { "@react-navigation/native": "^7.1.26", "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0" } }, "sha512-RSC/f1bSpodnx1oSXw7jNrwe83JddRhb12ehCY8oZZDtrNhm3atSHzlfvHN37i3E8cln7Tmc1ieLxjWrU65n/Q=="],
"@react-navigation/native": ["@react-navigation/native@7.1.25", "", { "dependencies": { "@react-navigation/core": "^7.13.6", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-zQeWK9txDePWbYfqTs0C6jeRdJTm/7VhQtW/1IbJNDi9/rFIRzZule8bdQPAnf8QWUsNujRmi1J9OG/hhfbalg=="],
"@react-navigation/native": ["@react-navigation/native@7.1.26", "", { "dependencies": { "@react-navigation/core": "^7.13.7", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-RhKmeD0E2ejzKS6z8elAfdfwShpcdkYY8zJzvHYLq+wv183BBcElTeyMLcIX6wIn7QutXeI92Yi21t7aUWfqNQ=="],
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.8.6", "", { "dependencies": { "@react-navigation/elements": "^2.9.2", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.25", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-eBY92xb4H53c9jiWriKMOZmQ/Tu9w1qcUrgOA/qjQOvJFbgKF9D6y3e4UuBaDQzjWjLEDZLaiwXe8cwXRb46mg=="],
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.9.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.3", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.26", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-C/mNPhI0Pnerl7C2cB+6fAkdgSmfKECMERrbyfjx3P6JmEuTC54o+GV1c62FUmlRaRUassVHbtw4EeaY2uLh0g=="],
"@react-navigation/routers": ["@react-navigation/routers@7.5.2", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-kymreY5aeTz843E+iPAukrsOtc7nabAH6novtAPREmmGu77dQpfxPB2ZWpKb5nRErIRowp1kYRoN2Ckl+S6JYw=="],
"@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
@@ -634,201 +634,201 @@
"@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="],
"@tamagui/accordion": ["@tamagui/accordion@1.141.4", "", { "dependencies": { "@tamagui/collapsible": "1.141.4", "@tamagui/collection": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/polyfill-dev": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/text": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-direction": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-cVoU6y/XZJzEXYQ7Jc0ROLU4ehhxRZWq6Qrm3uq1rBavY+WGDGzaCGDl69i+uzSJK3aotpt4KxkNyhsxXtfEog=="],
"@tamagui/accordion": ["@tamagui/accordion@1.141.5", "", { "dependencies": { "@tamagui/collapsible": "1.141.5", "@tamagui/collection": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/polyfill-dev": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/text": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-direction": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-IDEWDSRr8HmsYjpxSyu2cH9L12kiksGHo/D7WZXjWhzel81YcluI9t0JOOuAAOrag8bBwlr96QqDwUa1cfY9sg=="],
"@tamagui/adapt": ["@tamagui/adapt@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/portal": "1.141.4", "@tamagui/z-index-stack": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-kQQYMFNyrca7Zj+D24rhr4aWmvbruDh5hqdmzIBT5e71RSzQuH5wCO3wmCzAuTnYw3RDZSFO57sO3B6OOdOeiw=="],
"@tamagui/adapt": ["@tamagui/adapt@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/portal": "1.141.5", "@tamagui/z-index-stack": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-A4z8rpSeM6f87WCXkmhDctfHJD9hkHSfct1b1M0aTnsSjAPWV4zufv2wIJfTGWkIjf4FZn43urEvpoNCtP5yEA=="],
"@tamagui/alert-dialog": ["@tamagui/alert-dialog@1.141.4", "", { "dependencies": { "@tamagui/animate-presence": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/dialog": "1.141.4", "@tamagui/dismissable": "1.141.4", "@tamagui/focus-scope": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/polyfill-dev": "1.141.4", "@tamagui/popper": "1.141.4", "@tamagui/portal": "1.141.4", "@tamagui/remove-scroll": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/text": "1.141.4", "@tamagui/use-controllable-state": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-+K0O99L/GAxnfSJDuExEd4IOA9EoZZk7HBwBCoxarZnCZ4/XKQFH33VoltMDuk3/D0qgCujnNHM6111Aowh6DQ=="],
"@tamagui/alert-dialog": ["@tamagui/alert-dialog@1.141.5", "", { "dependencies": { "@tamagui/animate-presence": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/dialog": "1.141.5", "@tamagui/dismissable": "1.141.5", "@tamagui/focus-scope": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/polyfill-dev": "1.141.5", "@tamagui/popper": "1.141.5", "@tamagui/portal": "1.141.5", "@tamagui/remove-scroll": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/text": "1.141.5", "@tamagui/use-controllable-state": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-eMSMxCpf2IbW2jF0Fl035NboEbDsfCHkW+rNk7uN65ONh2cNm4C8GqH2qkuFA0Hk+MplBApi9aqFtK+wVezKCA=="],
"@tamagui/animate": ["@tamagui/animate@1.141.4", "", { "dependencies": { "@tamagui/animate-presence": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-ysfxPYHAXSVsmktGkIkvUoNQ4xRoo4Z7LyP3sZIrR/8X1o13hl6m7rBXip7Cq/l6YY63f/F7UdbCRDXWwIUpiQ=="],
"@tamagui/animate": ["@tamagui/animate@1.141.5", "", { "dependencies": { "@tamagui/animate-presence": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-JED/YXBEDlFdMlpGv7n5b/tPDDNNy06ZX3NSELbhjXYJxNkHczCgH7V1RVcqTuYnGGKmVi7+MU3OalIm7SvakQ=="],
"@tamagui/animate-presence": ["@tamagui/animate-presence@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/use-constant": "1.141.4", "@tamagui/use-force-update": "1.141.4", "@tamagui/use-presence": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-lNeIeIB9lugp43SMZvTlKjdjKtnb1N8XF4aShaI2FKP125CgiSSZ0vf12jyKBBrPpvmr/hbnxSApH38FxPHIOA=="],
"@tamagui/animate-presence": ["@tamagui/animate-presence@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/use-constant": "1.141.5", "@tamagui/use-force-update": "1.141.5", "@tamagui/use-presence": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-nnDD2Z+ckbGKAbgLDPN4wFRpmFx336BGs0aeKCEGqZwRd+lz3U/qZxzArBDXnESb86s8m6keCWO5ZZxF+0BMUQ=="],
"@tamagui/animations-css": ["@tamagui/animations-css@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/cubic-bezier-animator": "1.141.4", "@tamagui/use-presence": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-fTasO+QAOLsi/fBvuyUdunat8QKxEpOVcEpI2caIzf1/PfAzPkToub8GC8jUDxCOxp1PTMFO61xDgV7ryhLD8g=="],
"@tamagui/animations-css": ["@tamagui/animations-css@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/cubic-bezier-animator": "1.141.5", "@tamagui/use-presence": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-UYwdEJEAFdGGpaB8wzfU9rZb07+alLmuAQvTPKxl+sQVTABWFWfPOsU32wdipmYFvIa3kVDGVQQAHHGalyNnPA=="],
"@tamagui/animations-moti": ["@tamagui/animations-moti@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4", "@tamagui/use-presence": "1.141.4", "moti": "^0.30.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-K2TSnqbhK1Qg6ktnMlQwxvzB639AEgiyEHMI9uMQf+f/Vto5qeLTvD1XscBrQRFTa1qagMXL0rzLX3ijRySEfQ=="],
"@tamagui/animations-moti": ["@tamagui/animations-moti@1.141.5", "", { "dependencies": { "@tamagui/core": "1.141.5", "@tamagui/use-presence": "1.141.5", "moti": "^0.30.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-rdvyY5bGKNDMLyyy8sBUIDCZp9yZBktXKtEqdVFnX0STkytZzGzJo9xTJywppAz4Ykujp43suqxOvdwndJKVRw=="],
"@tamagui/animations-react-native": ["@tamagui/animations-react-native@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/use-presence": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-OV6ZCeeyKrGRlQaoDvYH+K4KuR7hRyG5/HD/yBecYxstD8jpT/w35R6u3jaeO3RhVD5UtRoGR7yOxObFr0KiOQ=="],
"@tamagui/animations-react-native": ["@tamagui/animations-react-native@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/use-presence": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-XyUJegsrVLArjgOFo85zCXHSq19yNA3//aojnjM2DfuXvDGWfgGPhW22vTIhBi/U2e7fBqFaC08ZfuFF7ATCDg=="],
"@tamagui/avatar": ["@tamagui/avatar@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/image": "1.141.4", "@tamagui/shapes": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/text": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-K7cvgUbtriVVa6wSCPwZ58JqePvg0BqsiSUF1qZxNeLC3VozPFu56Y+NebPf5xdkK4lmLwhUOuA1599S1JIfpg=="],
"@tamagui/avatar": ["@tamagui/avatar@1.141.5", "", { "dependencies": { "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/image": "1.141.5", "@tamagui/shapes": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/text": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-YZBdqp/dk4LJVmIrEbAD6K9a+rkFbghdcnzx8wXfhQDOzQI1By/QRuXuHjG3oO7vHfpLCyoH1RC7mWRbHTlLxw=="],
"@tamagui/button": ["@tamagui/button@1.141.4", "", { "dependencies": { "@tamagui/config-default": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/font-size": "1.141.4", "@tamagui/get-button-sized": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/helpers-tamagui": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/text": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-xqlMFabe+Xgobbwe12LH8BhJpYECuihZsWz/RX9/sctfNc/UurmC7n9Avn40DypqR0YMBOXjz1mLRJQpm2WwVw=="],
"@tamagui/button": ["@tamagui/button@1.141.5", "", { "dependencies": { "@tamagui/config-default": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/font-size": "1.141.5", "@tamagui/get-button-sized": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/helpers-tamagui": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/text": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-rg+gR95lICVSjqCD2ISOCfEQgMy1YSJaLGDroAyg8kF/ezsXGPTRQfkjcB2A0of/kOcdoYVAVCAWHPS7Ix7pFg=="],
"@tamagui/card": ["@tamagui/card@1.141.4", "", { "dependencies": { "@tamagui/create-context": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/c/Aehx/mkKPLJWyHINJd02DQLpBWfSUQXR34Dd12bDbmfSmkK/v0GZ+YtggBOZthoR6KFAkG7erINRErAEgHQ=="],
"@tamagui/card": ["@tamagui/card@1.141.5", "", { "dependencies": { "@tamagui/create-context": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-oci2ShZbWakPZOa8+Lep55glKJRfMPe4L/ffrFiuAlB3+dEaSmwxmaSBq+CExSdwSSKAK4W0YBemmD5p4vpinw=="],
"@tamagui/checkbox": ["@tamagui/checkbox@1.141.4", "", { "dependencies": { "@tamagui/checkbox-headless": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/font-size": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/helpers-tamagui": "1.141.4", "@tamagui/label": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-previous": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-7mbQs/Umj+0Jeom7coBJ4CKfwldluNy1bVJuCod/vFWS5MIvv0vActb0oLBhCyeoTLgPIT+HY8dElpr7P5WRaw=="],
"@tamagui/checkbox": ["@tamagui/checkbox@1.141.5", "", { "dependencies": { "@tamagui/checkbox-headless": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/font-size": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/helpers-tamagui": "1.141.5", "@tamagui/label": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-previous": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-3Q10yTUMZgs78Vsfkl+nBeeJAh1yN4vP3CeddCzvHYElXPjHtajooBPjgkTKT4TyYeEpLRjyaPRMTWrVULCEAw=="],
"@tamagui/checkbox-headless": ["@tamagui/checkbox-headless@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/label": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-previous": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-mOZlDshobYTi2T3BIRpr024AIYxC/iQso3zEGAd2vHNKBPpeQ/vKTrdCHx/NLRKIUy0WJNzdg64rWP23wUcCUA=="],
"@tamagui/checkbox-headless": ["@tamagui/checkbox-headless@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/label": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-previous": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-EL7TmbqE9npY0s4yNdz1S2j4uMtO9mfql7U7sy6cYJHkqou439kCA+W45/hTm10gW+u1f4FIDJ0YK5B1PK/JOw=="],
"@tamagui/collapsible": ["@tamagui/collapsible@1.141.4", "", { "dependencies": { "@tamagui/animate-presence": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/polyfill-dev": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-yeaY+nRjpjX0m28bq87oBgD9fTUOveQtvmLvU+CTInMub5tXvrxVXPzx5J7g+QrbknJvGtEtedfX3I3uhzZNEQ=="],
"@tamagui/collapsible": ["@tamagui/collapsible@1.141.5", "", { "dependencies": { "@tamagui/animate-presence": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/polyfill-dev": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-IvC4QdpMwpt3lN8SFsgj4VkD0dWMUALjK/t2zIZbr/00PwIAoaXB/sgi4sZkYBleh92WsBr/86Ff+31QEOJUTw=="],
"@tamagui/collection": ["@tamagui/collection@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/polyfill-dev": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-controllable-state": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-ZmDAlK4FSJQnNxAjIN0ovmyixg8M8jgwuk5EVDtvAqV6jzvTUA40DRh0HYBlPV2IKGzF2DFLg5SC243NrlBAJA=="],
"@tamagui/collection": ["@tamagui/collection@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/polyfill-dev": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-controllable-state": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-PuNfJ6FuI3ROX1LPb5R0U5ibKW4+3tDY5objndzjtH+uGt4TljbbhI825cmiK3TLdqgsHjRGER1nQ32uA5NJTA=="],
"@tamagui/colors": ["@tamagui/colors@1.141.4", "", {}, "sha512-iFDE1DjvU0YmhBYKrjvxQn02mstnwfsEZoJF5MppGhYLp3jfferQVpO+JuWre6heSLiy1R1bM7mO7uHsNdh3QA=="],
"@tamagui/colors": ["@tamagui/colors@1.141.5", "", {}, "sha512-zM8saBOD5VAGjo8JhkBXiuguH6wJMIF5lStvSDVuXMx3+6QTYGaY0cLclqrYSoow82yXHAKM6EUyMztVfdG9bQ=="],
"@tamagui/compose-refs": ["@tamagui/compose-refs@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-Yx15Kp0SxtT0YxT/jLLaKlz518fIM7C18ryJzFzm5kYwe9TzIJBPUzIjlgYyCQhJJ8WwqQbkjN0FEg2J6mmgAw=="],
"@tamagui/compose-refs": ["@tamagui/compose-refs@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-+bmw43BYPYFsHiBV3NBBwB6shexNlnLBfH80PlCxUOWZ92LAvFsjZ5LBXhpIsDPySFK8Oo8IKXNxwznlvt37dQ=="],
"@tamagui/config": ["@tamagui/config@1.141.4", "", { "dependencies": { "@tamagui/animations-css": "1.141.4", "@tamagui/animations-moti": "1.141.4", "@tamagui/animations-react-native": "1.141.4", "@tamagui/colors": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/font-inter": "1.141.4", "@tamagui/font-silkscreen": "1.141.4", "@tamagui/react-native-media-driver": "1.141.4", "@tamagui/shorthands": "1.141.4", "@tamagui/theme-builder": "1.141.4", "@tamagui/themes": "1.141.4", "@tamagui/web": "1.141.4" } }, "sha512-mgVtZImkAtPm3DtiqBir71lKnvbEK4bJz/tjbc9VetTPd+68uLd+uVA3+SkR3IgUEcyjXWgeAl07Li8lWDo/fA=="],
"@tamagui/config": ["@tamagui/config@1.141.5", "", { "dependencies": { "@tamagui/animations-css": "1.141.5", "@tamagui/animations-moti": "1.141.5", "@tamagui/animations-react-native": "1.141.5", "@tamagui/colors": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/font-inter": "1.141.5", "@tamagui/font-silkscreen": "1.141.5", "@tamagui/react-native-media-driver": "1.141.5", "@tamagui/shorthands": "1.141.5", "@tamagui/theme-builder": "1.141.5", "@tamagui/themes": "1.141.5", "@tamagui/web": "1.141.5" } }, "sha512-4VV56MIzfPUxKy1gmGnHOyt1irCFlPA3Wc/4pEhdNvs0TC3XivxEa1K2x1roAifprtwOJ9G/5qBmrap4CeUNeQ=="],
"@tamagui/config-default": ["@tamagui/config-default@1.141.4", "", { "dependencies": { "@tamagui/animations-css": "1.141.4", "@tamagui/animations-react-native": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/shorthands": "1.141.4", "@tamagui/web": "1.141.4" } }, "sha512-v64eTMEhnnsyhcEwgcLsf4/Ab4t5bWp6Xeb0gXCJsuf3sRWk5NGBHh/wq9hK/gKjTxOLUkc20Dtn/C7e4i2Nmg=="],
"@tamagui/config-default": ["@tamagui/config-default@1.141.5", "", { "dependencies": { "@tamagui/animations-css": "1.141.5", "@tamagui/animations-react-native": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/shorthands": "1.141.5", "@tamagui/web": "1.141.5" } }, "sha512-MrhaalpZ5HQzNYi5X9oAY81e06Vx4CaMUlsYJMqTmyJE+KPod6Lj9RfS3tcMPHawroJlgCAlpbNlgZtgm9BixA=="],
"@tamagui/constants": ["@tamagui/constants@1.141.4", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-vh45Cv3NBOzUtIKriAr4lR1Eg947FJZjyBqto1wn/ClHg0tW1AlpZZsP3e5FGVDyBWFqIxbspoMuw2GT6eAY3w=="],
"@tamagui/constants": ["@tamagui/constants@1.141.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-0Vz7FbT6zbGhGvg7pXZo4ifpOUJn4DhufOvlFLtp6VfaTZRN7XPnwV5DKVNJESB5qomXcNBl2O9SSug0DboQLQ=="],
"@tamagui/core": ["@tamagui/core@1.141.4", "", { "dependencies": { "@tamagui/helpers": "1.141.4", "@tamagui/react-native-media-driver": "1.141.4", "@tamagui/react-native-use-pressable": "1.141.4", "@tamagui/react-native-use-responder-events": "1.141.4", "@tamagui/use-element-layout": "1.141.4", "@tamagui/use-event": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-NIKfAjwEqnXTPkeX7ryKYvWFBrUNL5Qno27RoCFyNM++qV3Jc5IOrtOtAysGUYT+umYZqSgSLuc5SXyXcBSx7w=="],
"@tamagui/core": ["@tamagui/core@1.141.5", "", { "dependencies": { "@tamagui/helpers": "1.141.5", "@tamagui/react-native-media-driver": "1.141.5", "@tamagui/react-native-use-pressable": "1.141.5", "@tamagui/react-native-use-responder-events": "1.141.5", "@tamagui/use-element-layout": "1.141.5", "@tamagui/use-event": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-f7Jl3qZIG41CB/669VR5vqm6VnfdED7RQVTgU4LkUB37JQNiDc+SGgdfNSV68pS/uJVjGzhvutaMNHikTOuSQA=="],
"@tamagui/create-context": ["@tamagui/create-context@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-s/zBH54N09F5RuFTD6/mZTD3DyUO9safRSW205xxPYfLoCYDTnNZTMB2V8LxHR2rjBgg6YlPP1fUr1y7aowErg=="],
"@tamagui/create-context": ["@tamagui/create-context@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-wxGqYoVOv1hc9VZeJrlWq1ONAXu0WDRbSaW8fxlFd3w+O0bv7GGzT9jmQuAM0Rnh1CZqseL72363ciUn0vVNvA=="],
"@tamagui/create-theme": ["@tamagui/create-theme@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" } }, "sha512-HrkoOTwbDB8VS/+8aPUVi08yF0lmcJyqjopthRmxH0KXsbANy8+33tynDW9N+CATM8fd6TW9wqLIU+mavVd07A=="],
"@tamagui/create-theme": ["@tamagui/create-theme@1.141.5", "", { "dependencies": { "@tamagui/web": "1.141.5" } }, "sha512-c/9OkDB3kzuUH2a939ODl0YI6Noe87uWZiSegFYRKzTiXn2CNMiYh0TdYrG6f9jxwfyzb5sS7Zfq+oHj69HeRw=="],
"@tamagui/cubic-bezier-animator": ["@tamagui/cubic-bezier-animator@1.141.4", "", {}, "sha512-Wj4Cg7XjPtSE6o8Ss7AmtFeZFYzRkNtbcF2arcktgnSVsgxAjwXn9PvRuXGUy4Z+Pnvh1NLhS2J2iKG23waP0g=="],
"@tamagui/cubic-bezier-animator": ["@tamagui/cubic-bezier-animator@1.141.5", "", {}, "sha512-cavayHr8YwN1oDdI2trFMhWLwopjvvZmSiRxFP8ZsdVqhI6sdMH9qhD+9NkgrtjUIRlJEVUsskKkNQP4sIQx0A=="],
"@tamagui/dialog": ["@tamagui/dialog@1.141.4", "", { "dependencies": { "@tamagui/adapt": "1.141.4", "@tamagui/animate-presence": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/dismissable": "1.141.4", "@tamagui/focus-scope": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/polyfill-dev": "1.141.4", "@tamagui/popper": "1.141.4", "@tamagui/portal": "1.141.4", "@tamagui/remove-scroll": "1.141.4", "@tamagui/sheet": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/text": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/z-index-stack": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Vy51uOB23wl8B+B/ilrzhfFveEa7eWDHkg/CseU++qw3iVw9s+u0fhrOPqsIlOSSEwH2+CrXHBkQZsIDjivfrQ=="],
"@tamagui/dialog": ["@tamagui/dialog@1.141.5", "", { "dependencies": { "@tamagui/adapt": "1.141.5", "@tamagui/animate-presence": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/dismissable": "1.141.5", "@tamagui/focus-scope": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/polyfill-dev": "1.141.5", "@tamagui/popper": "1.141.5", "@tamagui/portal": "1.141.5", "@tamagui/remove-scroll": "1.141.5", "@tamagui/sheet": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/text": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/z-index-stack": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-J1SvM6v+nk9haxgBOi11rcipjC7fAVIsG/18QNd4G/v/mipENb/YqJSXqLX9Y3EqAHSaVycW1vdiJ9isfJqOtw=="],
"@tamagui/dismissable": ["@tamagui/dismissable@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/use-escape-keydown": "1.141.4", "@tamagui/use-event": "1.141.4" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-J4pwz0t8LWbvrpng+FcQU4SpG9ZfMuEipD/giyHBSZ9uNKNau+vDXGPPghxK59Tw0THOmTmpBTl+Oxos/eKXyA=="],
"@tamagui/dismissable": ["@tamagui/dismissable@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/use-escape-keydown": "1.141.5", "@tamagui/use-event": "1.141.5" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-SwTAKflIoY+URVqqCIXU1lAH9OCBIsUkxFVULvVzBZTZyxaYEeHP1vE9UV5gF+haey2L6bgCSmhB6aUJmR0wGA=="],
"@tamagui/elements": ["@tamagui/elements@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-X77AKo/ZCEDOAvT7UphwiDAqEQjmKYYI7RUZxJEBQvWk2tmAkf25WlSl4DU2zop4sVb1uctISfBLFHevOPSQdw=="],
"@tamagui/elements": ["@tamagui/elements@1.141.5", "", { "dependencies": { "@tamagui/core": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-zP73o2/8Y4uUX4qK3K+RPgFHrDJMSQ2jnkpLIFljgS6TWYGfGsjaDSQ/gh60V269/aWylPOgZxyagvNHEK5yMA=="],
"@tamagui/fake-react-native": ["@tamagui/fake-react-native@1.141.4", "", {}, "sha512-0BPBA3Df9mwuSejgz+z66tyUJ0sfe/1vEjSqXTh7LMv/1g30CQ6uI8Z+s1oXeC9dDtuHajeOjxD3O2F+kC7PzQ=="],
"@tamagui/fake-react-native": ["@tamagui/fake-react-native@1.141.5", "", {}, "sha512-YMGwrJJ0bhLDI333+rwZfKhBtgbqayewkNht+3grM5rfFs13VWmPyvEV4oUPacLOZKeMYFnI3bV2ssicUHH4Tg=="],
"@tamagui/floating": ["@tamagui/floating@1.141.4", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.6", "@floating-ui/react-native": "^0.10.7" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-7RJQ+lR3dE1sO2bstXLGwFkLbBt+cRtRm3UHLzLQ8jKr3lWhbepr2mQM/hkNieC9HZS7x7XA2C8pFXAGyigRwA=="],
"@tamagui/floating": ["@tamagui/floating@1.141.5", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.6", "@floating-ui/react-native": "^0.10.7" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-B4dbLaot71tCRISSYwaxZFvap0hSUG2B4dzxRH7wTtKurjWB/ziKzK8DieNffr+x0W32DsaRPjpNuCDjS7bS4A=="],
"@tamagui/focus-scope": ["@tamagui/focus-scope@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/start-transition": "1.141.4", "@tamagui/use-async": "1.141.4", "@tamagui/use-event": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-XQhixzXWItlmisNtRg8uNce93X78wijbBd7rRsgIQjmQPnRXPmVpdTGdQCLBsu1h1OBKLlt/qqyMkt/HvtUt8w=="],
"@tamagui/focus-scope": ["@tamagui/focus-scope@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/start-transition": "1.141.5", "@tamagui/use-async": "1.141.5", "@tamagui/use-event": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-mmm/xpUIr3TPk1sjpE2OtjxO0IBowTXpzYN7Qk7o94lTNsQxbpFjxkjtmrX2Fh9J89Z1c8Js94k+UloJ6v2IQw=="],
"@tamagui/focusable": ["@tamagui/focusable@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-JA8GmtwavvGZeer7yVYQfmWG6F5NVfQGeyhOQkCof4xydkzNpsPUevIXmVNou/1c3CPFnKWBQUvD/Zy5HiVB4g=="],
"@tamagui/focusable": ["@tamagui/focusable@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-fqoM4v95gz+6tlVDPn0cjbHEZZf+O0E2NU8iGCWI/JFX7v/Jn08E8m0OQ+91cgO98AySHt6UPz6qSkQE7KjHbA=="],
"@tamagui/font-inter": ["@tamagui/font-inter@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4" } }, "sha512-jlPErhPX7DTy+XNhnHtpDBLPeH7tB1hvapoLnanOdyg+cBxGZQIE2uOtTISS3A8M9IPq9eH73OoWobyQ302Zsw=="],
"@tamagui/font-inter": ["@tamagui/font-inter@1.141.5", "", { "dependencies": { "@tamagui/core": "1.141.5" } }, "sha512-O0yOOr2mfi3yVUFdWmkngU+exsdOZZgsKv31k7jeQ4fBHRYFhqP6jyRXp2yO0hLD5K2Q4Css7NPzLawhrwejhg=="],
"@tamagui/font-silkscreen": ["@tamagui/font-silkscreen@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4" } }, "sha512-uRRzdFgtrZ/WJp1vDhCc5oXg4lAbw+Ib1HFTBKn1/BhlqgrwCDrAkTNDNnKzUChqpdmDLGeFSQQknmK7xG352w=="],
"@tamagui/font-silkscreen": ["@tamagui/font-silkscreen@1.141.5", "", { "dependencies": { "@tamagui/core": "1.141.5" } }, "sha512-M1fms9r1rtyFvzwvEEpaw8bBvyRy8ggoQVYdCv7cTqA+cSA3aHD+Bg1vw/3+x8T2oKmWz0fIq41ZFVKFUnoPug=="],
"@tamagui/font-size": ["@tamagui/font-size@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-N8bS0Pqcj8UJd3rmuMGjSVOfVY7DW/1zONaE09QvzOStcB9Klfzs6UU0P4zwMkdf9vqJOfXmS7LD7aQaKK2fzQ=="],
"@tamagui/font-size": ["@tamagui/font-size@1.141.5", "", { "dependencies": { "@tamagui/core": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-9TOw7gDq0ut5R1z84iln6LNJ8olNdfixwbhcAQgVsffkzV+YeukoYZKP44OrFZPxG2FlMRJRbNx8vqA7i3jJiQ=="],
"@tamagui/form": ["@tamagui/form@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/get-button-sized": "1.141.4", "@tamagui/get-font-sized": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/text": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-wZbMcNuP/O9qb5L17C1jBzbzagLafFhgyfWkY7VjG94fZ+LtFGtVpl/OgV8nZWNPGl8Fdv7ccAm1Z8j/2NN8Cg=="],
"@tamagui/form": ["@tamagui/form@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/get-button-sized": "1.141.5", "@tamagui/get-font-sized": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/text": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-doRee0kTI1DbIscD5xabh3lF7sSYpBqiXhQisnxVF+6eZjI3SGz6uPjiLDkpsO+frJwMMMqhWc2D7Z9ybFGOpw=="],
"@tamagui/get-button-sized": ["@tamagui/get-button-sized@1.141.4", "", { "dependencies": { "@tamagui/get-token": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-2nT2Jnly6pk1v4GUSmOOhzz3XDj0lULhjDjvMAyfm4xpo3vE3ukbdScB78dHE1NazAlJXl5zluT3LBeyBGgCug=="],
"@tamagui/get-button-sized": ["@tamagui/get-button-sized@1.141.5", "", { "dependencies": { "@tamagui/get-token": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-kdD8Ur6iYW48hIQTMmiE5qmNQ73hWRTeu4m4rlkIJXFM2l2LCwTRAVIoZVEXp2PDL9U2KTFFexZAB7QjmrDfPg=="],
"@tamagui/get-font-sized": ["@tamagui/get-font-sized@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-Ck5mQhUeSQAMpd0ja40Ccpl3hGRIUTxohhI8OabfDhqqEODIGj7E/GYezeYNuw9xCaB0JcYTgnX1sruGmO4aRA=="],
"@tamagui/get-font-sized": ["@tamagui/get-font-sized@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-C34EC+HzwfR6q+3HMVWpv+BteRaDZM1xyEEAe80SIbgwS29SirKhRUIuoV44uUVV9u21AAAsrngxOtcs/y0AMg=="],
"@tamagui/get-token": ["@tamagui/get-token@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-wAcdJVLUXbbKqYF3QOip9sAeLJk19CDJjck5W7MpCma3WoXw58Kmb6VYFA7TV5hUVicv2FCRw4DkU3v/vNoDyA=="],
"@tamagui/get-token": ["@tamagui/get-token@1.141.5", "", { "dependencies": { "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-9GkaM7I/88QtG85v8Z4piw4i80ey4uPE/BMdf7PXE7GU7fd/Gn3a32AnEyskJ8S0qCBoAB8T4QzfYL38nmNSCw=="],
"@tamagui/group": ["@tamagui/group@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-controllable-state": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-8QGhqcds3RBsP/ZTKrNIxOn57jJntQqgclobNayfvoHBR3pN7kR09gTXB0taCzTY+sAPuXiW5KNEbjSozICACQ=="],
"@tamagui/group": ["@tamagui/group@1.141.5", "", { "dependencies": { "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-controllable-state": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-DIqA+HdzD8qW5e8bn2cTUsXKe3VbhnenE2PEmZmInDt+ylnw645XPgnRmHcfS1O8PtKoUfltJ3sYlhAXb4t0pA=="],
"@tamagui/helpers": ["@tamagui/helpers@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/simple-hash": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-kO91N9WZxehm7wAvbdZ92QavOh7jSTdDJ1BzM23MAkSUN3PnnyV9b/3n2vTogNamo6a4aG/htPJKwNXEy4ZEEA=="],
"@tamagui/helpers": ["@tamagui/helpers@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/simple-hash": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-hn/JPucfebc9hLpxPyxC9nvZxUwVpVB+SZG3ULb0Dl1nDxruBwoTRWF+jdhLOmQRmRMP7TelWym1JxNu5ULm2w=="],
"@tamagui/helpers-tamagui": ["@tamagui/helpers-tamagui@1.141.4", "", { "dependencies": { "@tamagui/helpers": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-QSFG/nWeEj8az2zWejp/p1uvCHuCIkv4XW7o6PuU3j3oq/amh31c9qN2DR+PhWN0Xwxooz/SuqUrBuevk//5fw=="],
"@tamagui/helpers-tamagui": ["@tamagui/helpers-tamagui@1.141.5", "", { "dependencies": { "@tamagui/helpers": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/uF5ZHE0fX3Zv3K0B0rEwhfof1I2AkMr7owtS6Uga97VHt3CKWczffKOHlx2lkffA0mkP2fC1jeDxXXnVNi1zw=="],
"@tamagui/image": ["@tamagui/image@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-w/nN/uu39LjQZDPuTbPZbEwhgF/HPyZ2Ug5+n7l8KHaRcFAW32OJUak8aHDDOPqPx/pvHx+Wn2hS3/EnrV0REg=="],
"@tamagui/image": ["@tamagui/image@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-cZW07kv21rUupVC/+/WwA3ZyHSHqAzDgXJ0oilpM+F+gxvAtWGcme0WbW9XYCXcO4potH/krdVcx0jXYpWaHIQ=="],
"@tamagui/is-equal-shallow": ["@tamagui/is-equal-shallow@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-YuzzoST+SGlAJys837RXVX3LOw1ajL76imoAytMXZhBxoJN9rHuLIzRayXGfFy+NLrVLThTlUsswSojKYIsVVQ=="],
"@tamagui/is-equal-shallow": ["@tamagui/is-equal-shallow@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-3Fg2jiuvID7maz/hYo69Ye5fjnFs2DGwk6k7DS8aCCsFw7S05eKYDSZaBEK7VBQ3iMqrXdJIm4S1KuXlWE0V1Q=="],
"@tamagui/label": ["@tamagui/label@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/get-button-sized": "1.141.4", "@tamagui/get-font-sized": "1.141.4", "@tamagui/text": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-efoi95hQCGK1/bS7QJpbn3fOQdXLdAjOs8oyJAgJY1nOkgzZ7TQiccCZb/k4YlDdhBX/LuNzKsllywlHZmw5cQ=="],
"@tamagui/label": ["@tamagui/label@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/get-button-sized": "1.141.5", "@tamagui/get-font-sized": "1.141.5", "@tamagui/text": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Zsk0dVkW/mEmWiJGT2Hy1NUDSjyOYK2hIIJvHog0Ev9pL5yyoAg8gjvcQ7qnqiZceuCyfMjg3aFdEFSiwirQHA=="],
"@tamagui/linear-gradient": ["@tamagui/linear-gradient@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4", "@tamagui/stacks": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-uhIrH1MHWa8JZ8Wi3O6uuyAeZwOvHwEjlI2SNR0yg6QiSnOmXxlQeMBf9CQuPoJH3dqTGUsK1fQ75/OMSBbMjg=="],
"@tamagui/linear-gradient": ["@tamagui/linear-gradient@1.141.5", "", { "dependencies": { "@tamagui/core": "1.141.5", "@tamagui/stacks": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Wwj6JQDE3YINsBjGOJ76wBPfVrtobqGbf72tcf/57Rk6Or6AfSc1xbXofUtl3H3P7jh3BALwO5+8oUPrRVtcmw=="],
"@tamagui/list-item": ["@tamagui/list-item@1.141.4", "", { "dependencies": { "@tamagui/font-size": "1.141.4", "@tamagui/get-font-sized": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/helpers-tamagui": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/text": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-5U/sfi68tZE2ill6PzD/LjZ2JO4kBPQQbPRdyOfwzTvHKJdpAnktk2C/SXQ6KzIbcW1gkU9Q97IwZnKMLbBCtg=="],
"@tamagui/list-item": ["@tamagui/list-item@1.141.5", "", { "dependencies": { "@tamagui/font-size": "1.141.5", "@tamagui/get-font-sized": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/helpers-tamagui": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/text": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-kCMhEMXCJRKJZi3hEFU7TAPYg78+1pq9qHAfOZ3WgYl22fScVma24pdAeLoZFt9Gw8VJvWlZ4UEVxTeajF1cYg=="],
"@tamagui/normalize-css-color": ["@tamagui/normalize-css-color@1.141.4", "", { "dependencies": { "@react-native/normalize-color": "^2.1.0" } }, "sha512-RaNFRxeDX8o0lpBU/PHo4F+dC2eOujs83j087wr1Ik6YFfxrHcay1sA8xLq3QqPbzJ/PX8r0/WGHusEz60xPyg=="],
"@tamagui/normalize-css-color": ["@tamagui/normalize-css-color@1.141.5", "", { "dependencies": { "@react-native/normalize-color": "^2.1.0" } }, "sha512-FV+M+SvPd0H//IfeXBeKv5yD4tQYNOMd18rCpyQ+5s7Zb4q9jnPtoISpn+Si9QHVHVJPKmQn6n6C9QgOgHHezA=="],
"@tamagui/polyfill-dev": ["@tamagui/polyfill-dev@1.141.4", "", {}, "sha512-oaxMP+YoDArZ527RyfWjVH23lr1mlsX6MyRRbJhAqRqATdqUXLzYuBCpEdhwFqwUxWB4ZcH/oe8B5ESbFIW4WA=="],
"@tamagui/polyfill-dev": ["@tamagui/polyfill-dev@1.141.5", "", {}, "sha512-vm7OzilkWAB60+bEuEuFuS/B9i1ShkRcCzxTkuZeErDogm+i4gxId4uY15xh8JEoe0WVPH/RGE5KQqoAaWeG9Q=="],
"@tamagui/popover": ["@tamagui/popover@1.141.4", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@tamagui/adapt": "1.141.4", "@tamagui/animate": "1.141.4", "@tamagui/animate-presence": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/dismissable": "1.141.4", "@tamagui/floating": "1.141.4", "@tamagui/focus-scope": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/polyfill-dev": "1.141.4", "@tamagui/popper": "1.141.4", "@tamagui/portal": "1.141.4", "@tamagui/remove-scroll": "1.141.4", "@tamagui/scroll-view": "1.141.4", "@tamagui/sheet": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/z-index-stack": "1.141.4", "react-freeze": "^1.0.3" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-7YRBnvNURibuUbdpmziVSMQMoxEnRd8b230P3auzCFuk4I+BrEP+7XByfPVvy3NRRQIWXl18e01vH+7wpw6Ftw=="],
"@tamagui/popover": ["@tamagui/popover@1.141.5", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@tamagui/adapt": "1.141.5", "@tamagui/animate": "1.141.5", "@tamagui/animate-presence": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/dismissable": "1.141.5", "@tamagui/floating": "1.141.5", "@tamagui/focus-scope": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/polyfill-dev": "1.141.5", "@tamagui/popper": "1.141.5", "@tamagui/portal": "1.141.5", "@tamagui/remove-scroll": "1.141.5", "@tamagui/scroll-view": "1.141.5", "@tamagui/sheet": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/z-index-stack": "1.141.5", "react-freeze": "^1.0.3" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-phSugO017+JX22BQ+SDITOzLS/Dg9+95SbUfTPCjsAKyN7jvHBu8hvt5g+3VsgSoL/g2Y6y/qLRL7qzBX6tRkQ=="],
"@tamagui/popper": ["@tamagui/popper@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/floating": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/start-transition": "1.141.4", "@tamagui/use-controllable-state": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-NPFeu3+W0Kjy8Tm+bJJWiZFAXUlb8SlUbsvKe34vAaQ6c2xkisdqsXbBg+P9vNbH/sAAN3KDdkJlNM2/6AIXRg=="],
"@tamagui/popper": ["@tamagui/popper@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/floating": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/start-transition": "1.141.5", "@tamagui/use-controllable-state": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-xR/ZE6LErQmFG23gadO3QDq+0269hl/BidVF45gI1qx3vORJ9x4Ghu+cbcKBBLGwn8Pdv0FscHTDwXxdLsjqBQ=="],
"@tamagui/portal": ["@tamagui/portal@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/start-transition": "1.141.4", "@tamagui/use-event": "1.141.4", "@tamagui/web": "1.141.4", "@tamagui/z-index-stack": "1.141.4" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-UfdB98aW/PGvNmUAmw7Uvsy39dXMvC7BBlnP5OMe/5GE5fQavp7Dqi/jlv2ueF7oqhxSdqRyOTmGv6xHsqnNkA=="],
"@tamagui/portal": ["@tamagui/portal@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/start-transition": "1.141.5", "@tamagui/use-event": "1.141.5", "@tamagui/web": "1.141.5", "@tamagui/z-index-stack": "1.141.5" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-H4CCidFgnqW/iZyVDkqtecDwgYw9BbcLFnx3dNHjnkWb4vxAlUjt8Gr9q0KlOPWU/WpFPE1wOaLCG/8xAgOTlQ=="],
"@tamagui/progress": ["@tamagui/progress@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/stacks": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-XzPyDquotznAqSI81TX6hCAgk23m/hUcnrvNOyb/cFAqFRalZnD2iUO08nFyb8m7Ox78b883+FHDm/xg7xxkdQ=="],
"@tamagui/progress": ["@tamagui/progress@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/stacks": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-pGGcRCgZ8Ui9mjrPgQG9Nhl30k/iSh0c1d/4pIV27E6/bmk7tkmYi+lwtGJqThw/5bxu8mF7UnBRZmwuP4G6vQ=="],
"@tamagui/radio-group": ["@tamagui/radio-group@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/label": "1.141.4", "@tamagui/radio-headless": "1.141.4", "@tamagui/roving-focus": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-previous": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-ceN+B7av88vJz6rh9rLCTLmQ3y6ig9/I0ep5tbuO+g6+AVi60h/ZQXA8ykcC4wjDRMkpt8q9Y/2DXHLsEnlKbQ=="],
"@tamagui/radio-group": ["@tamagui/radio-group@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/label": "1.141.5", "@tamagui/radio-headless": "1.141.5", "@tamagui/roving-focus": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-previous": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-mnaOjC9x0oWxxxvndPUveZAUXltF1F+nLxmAM2tpl/fL81aCuqmrKWEOq1FYvayoM61a3hACVOQQLKMgNcWfBw=="],
"@tamagui/radio-headless": ["@tamagui/radio-headless@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/label": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-previous": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-zERVNWoiezti46MQYDne/3vmoCHBC1MjBuz197BWyuYWeVd7GFka+Qye8/DlwWw6IwiOZ3E03D2HGd8gPKdhVQ=="],
"@tamagui/radio-headless": ["@tamagui/radio-headless@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/label": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-previous": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-B8bC+F4cRh0z20E2EDJLA1ek5bSStrY2M79DNKeRiWRIENDj6wHnkoj33FD5hMgCX6dEBcH1DN+VqD0+9EojZA=="],
"@tamagui/react-native-media-driver": ["@tamagui/react-native-media-driver@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" }, "peerDependencies": { "react-native": "*" } }, "sha512-fQttUVWKhks34g9LqPBZGRNUhKWXhDNgplhJ7mKRShVjeA7r1DkQoPWxW6bXBN9hfTW/Z3aW9pSy03cLGp6dUw=="],
"@tamagui/react-native-media-driver": ["@tamagui/react-native-media-driver@1.141.5", "", { "dependencies": { "@tamagui/web": "1.141.5" }, "peerDependencies": { "react-native": "*" } }, "sha512-PWBtEUMVYleEPt01vLCnCw52q9V3aCkTFXDM+X73rK9A37j9gXAcZt/Jqc08xsoZMeyHOpzLTHsQzK0sWtK1Uw=="],
"@tamagui/react-native-use-pressable": ["@tamagui/react-native-use-pressable@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-oZ268oyRlvZeNyqXCKJR3bLqEKYAnGy5D44KSjNr/kAZfCPE4qVf1UWdFDpNUpFLRfJKd7LRJbEt4MF1WhbX7A=="],
"@tamagui/react-native-use-pressable": ["@tamagui/react-native-use-pressable@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-po9gxO30QYQvzFLA2KUv43lV41AJ8zclVpMlaXbu/H7qb+ZbhTVHroaDDWW9w1P6IgGwCJKnwWsUMBcT4Zt4hA=="],
"@tamagui/react-native-use-responder-events": ["@tamagui/react-native-use-responder-events@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-eqSVS/ZHEs+g+4BwnNRdXI7Ef2XZsi3NflSSU/Lovdww64N2efcvqP9xAUpbyK7ES4a2b4U1PTYBQMMCh7qHpg=="],
"@tamagui/react-native-use-responder-events": ["@tamagui/react-native-use-responder-events@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-KVMDWGMdMcZsg7QEH6OqNlQ91Z9hFi1OdCkU5CbCDhZuGUlP2+MsmGBwDteja6r7HX7lw6POehOjDYQsfBczHA=="],
"@tamagui/remove-scroll": ["@tamagui/remove-scroll@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-vbQEhST81IvlaEEQd75UMIIi8FbDoKpyUrvGdeMKjsuM40IlW6gC2Dgtvvw69mGtLf8XCLJXWAY8IxYyNP/7kw=="],
"@tamagui/remove-scroll": ["@tamagui/remove-scroll@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-1mDuBI2+dTZjQ+nmkzsDjccSvIztE+weRBrW540nk+8++4JpuyTZsoCCGW9HKhuyJXbMvPnh4TjDh6+xXsVoQA=="],
"@tamagui/roving-focus": ["@tamagui/roving-focus@1.141.4", "", { "dependencies": { "@tamagui/collection": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-direction": "1.141.4", "@tamagui/use-event": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-ZUpWXvAnjCp95R/iksXErYTttAohW3a30+SOT6+4BFbqAhZBxwrINXkwoTgJUvdMHzooSu1kfulF6dRszWY4OA=="],
"@tamagui/roving-focus": ["@tamagui/roving-focus@1.141.5", "", { "dependencies": { "@tamagui/collection": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-direction": "1.141.5", "@tamagui/use-event": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-8h96x9cOSYDV7e+UMf8w50N4SnmVgl2Cjwj8SrGnrZP8JajdAglKKl+UVUGkOfxksUnqceurA30IGs1+jDgQcg=="],
"@tamagui/scroll-view": ["@tamagui/scroll-view@1.141.4", "", { "dependencies": { "@tamagui/stacks": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/MXrJYIgmqysdFNmAgkyruKjZOWtIZ/OiSLMQW1ofmvOTjgFo8h42Prza8fr2SJCRF7JXE/PVlbFhExqY/0qPw=="],
"@tamagui/scroll-view": ["@tamagui/scroll-view@1.141.5", "", { "dependencies": { "@tamagui/stacks": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-9k0u5MBpsJS40ombThiW9nY8fBBpBabOeGr0lAQuZ/bvy3NYj+YuN3uzHFwZSQ5izt1KuZEP4J+gv42Uu6RIWA=="],
"@tamagui/select": ["@tamagui/select@1.141.4", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/react-native": "^0.10.7", "@tamagui/adapt": "1.141.4", "@tamagui/animate-presence": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/dismissable": "1.141.4", "@tamagui/focus-scope": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/list-item": "1.141.4", "@tamagui/portal": "1.141.4", "@tamagui/remove-scroll": "1.141.4", "@tamagui/separator": "1.141.4", "@tamagui/sheet": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/text": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-debounce": "1.141.4", "@tamagui/use-event": "1.141.4", "@tamagui/use-previous": "1.141.4" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-GHYD7Rk0jTxwXPxAUUfDidG3dC2gTa7akvrfQ2JK1XeYa+t8H3GPN6k5xE7jahHHJesPK2F9wna5sjqbjnFXwA=="],
"@tamagui/select": ["@tamagui/select@1.141.5", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/react-native": "^0.10.7", "@tamagui/adapt": "1.141.5", "@tamagui/animate-presence": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/dismissable": "1.141.5", "@tamagui/focus-scope": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/list-item": "1.141.5", "@tamagui/portal": "1.141.5", "@tamagui/remove-scroll": "1.141.5", "@tamagui/separator": "1.141.5", "@tamagui/sheet": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/text": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-debounce": "1.141.5", "@tamagui/use-event": "1.141.5", "@tamagui/use-previous": "1.141.5" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-J2c8KqyJkk/v8TIpFXzHxw16GjKuLK0JaO+dA8e5psTLxRn6n+eFhDXrhPh8QCcPl1dtVn4D7mvJpzzLOeyV6g=="],
"@tamagui/separator": ["@tamagui/separator@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-0jdocDIJDSx2HKU924G/yai635KVSopBVLOJdSnkPIIPIbZ9tx9eXflxf+tPcEHIZtVdU2v9t0ITfTkjE21oUA=="],
"@tamagui/separator": ["@tamagui/separator@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-Lve3Bz+D+Ml5Rnz8Fo+XOWF4UdNfw/4gVmw1LU9lKc8OMBmQZDuURfT6Bjiv26l2w6HrL9ZYsMzToyu7V//90Q=="],
"@tamagui/shapes": ["@tamagui/shapes@1.141.4", "", { "dependencies": { "@tamagui/stacks": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-t+u0XQn8opOaVfbdi6cYw6aYWVRM3kKT16HQ7zlZ9uwComLlJcwCAgrq1wOUS4/uC8p0thHj/9s55CCMTr7z6w=="],
"@tamagui/shapes": ["@tamagui/shapes@1.141.5", "", { "dependencies": { "@tamagui/stacks": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-6/f3SgGXyZB9JZE5gSk779NkOYRGnzPs1hCOSDQrD5JHBdyXKWz4SggcO37tSbNxFYFFK+hBDkNjlx9HG0k8iw=="],
"@tamagui/sheet": ["@tamagui/sheet@1.141.4", "", { "dependencies": { "@tamagui/adapt": "1.141.4", "@tamagui/animate-presence": "1.141.4", "@tamagui/animations-react-native": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/portal": "1.141.4", "@tamagui/remove-scroll": "1.141.4", "@tamagui/scroll-view": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-constant": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-did-finish-ssr": "1.141.4", "@tamagui/use-keyboard-visible": "1.141.4", "@tamagui/z-index-stack": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-9goKaS98Di5LD/rN9fJaaxswHlUPRA7Y73QqlnY6iPYKzAEeW3J6zMFVo6Bv9a8QScrzvee8kBxX6XXp6CgKGw=="],
"@tamagui/sheet": ["@tamagui/sheet@1.141.5", "", { "dependencies": { "@tamagui/adapt": "1.141.5", "@tamagui/animate-presence": "1.141.5", "@tamagui/animations-react-native": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/portal": "1.141.5", "@tamagui/remove-scroll": "1.141.5", "@tamagui/scroll-view": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-constant": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-did-finish-ssr": "1.141.5", "@tamagui/use-keyboard-visible": "1.141.5", "@tamagui/z-index-stack": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-UMljCRrUlXct2rsbSspzJ77PSg7N8trgOZS1SEE2Cz0Jv4WN20K6Iz5kwai9M2qo9WtsDy47uaf7iq/kOf0mRg=="],
"@tamagui/shorthands": ["@tamagui/shorthands@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" } }, "sha512-sfFFtgRkF32wnFq9cRdXeyP8qCPCWXspUw3TIEU0VVTawZav0OjtpKrthC+ujX5wdm9+oF5EGkKu0m2Xyc4ODQ=="],
"@tamagui/shorthands": ["@tamagui/shorthands@1.141.5", "", { "dependencies": { "@tamagui/web": "1.141.5" } }, "sha512-MsA9UhPToGS6TrY9p8PIAWZZM/Oz3gMalXTviPM7TN1HoculavlsHeBbdMJhtbIwbiRf1O8c1zW8vKIHl4SR4Q=="],
"@tamagui/simple-hash": ["@tamagui/simple-hash@1.141.4", "", {}, "sha512-ZBJ43UVk2jgoSVfUkSXJ1ziQsxXRoGEloQCUJ1+Mf8E8qo2dAXYDRE+maFZ2oO7HatA58z8T2KIxSYRQROeG7w=="],
"@tamagui/simple-hash": ["@tamagui/simple-hash@1.141.5", "", {}, "sha512-o8vGWsCHwXkH8OAu16IbvAFwt5yXZXd2N0vJxGl2aJt5qH33G7FRzMZG8j2JH40sv2dB9EtswBULcoyyy4MELg=="],
"@tamagui/slider": ["@tamagui/slider@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-debounce": "1.141.4", "@tamagui/use-direction": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-NHXnOUy8UD8wJ1qDj2wuwZUiP3TCiBgC9yey3aOV8BmBdCiO/iCZFOz1UJtusAgSkHJ49RIuv1GRWbRJqyclbw=="],
"@tamagui/slider": ["@tamagui/slider@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-debounce": "1.141.5", "@tamagui/use-direction": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-IKhaCnAeGLLvhTWXIMXbzA8WdeIfRRkrc0JgLdZHN5Nt+kfzZ/8ZihbRF/g6viv3DukhspPkZJNWv5CFv/eQwg=="],
"@tamagui/stacks": ["@tamagui/stacks@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4", "@tamagui/get-button-sized": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-C0R7J9kPz5QECQ2EsYsp5WpMTm+B6GOrxI3AFxj5lC0DTnFoimuhkJnnFOMGE12ynu3vN6DZg8YKBBkIbTW74g=="],
"@tamagui/stacks": ["@tamagui/stacks@1.141.5", "", { "dependencies": { "@tamagui/core": "1.141.5", "@tamagui/get-button-sized": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-WEwKwd1jXsEyky4aggdvcMxpXa++V1YoTKYQltsdZ0zcWQZPOKnIXqUrMYdK0N79SYBqd1JRmR6ljB+Lb5NwKw=="],
"@tamagui/start-transition": ["@tamagui/start-transition@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-XsJqp8k2Ejp8MWdWBonYqqoq7314wpP32mtyO5M5k8rYpM9siMxqSB1MzDECOf2rJ9dvL8/9aGQflHyt/TJSEg=="],
"@tamagui/start-transition": ["@tamagui/start-transition@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-JVF+KVhKjE2SSkbc3FfRtcp3kfYvaVf5N2iZTcrxsmANgQX8WAeywifjOEdmsEevXi5UacRKp7HHxU5b9G/YJw=="],
"@tamagui/switch": ["@tamagui/switch@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/label": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/switch-headless": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-previous": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-ilxMA8YSjmgNKXg7W35RqUfg/lJaOnpabY6RFXhXSovz5BtCLPMQ2MrnWXKznSXC3dI+8AblX64gl/k3IOMWMg=="],
"@tamagui/switch": ["@tamagui/switch@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/label": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/switch-headless": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-previous": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-h9NoCXAuIb33nf/nrg4TFXEBUEP4ypwUC+MMtQestyUz5s+GEg1V4DOl2EeQ3anNHdL3MHhP+WiuR63XGSCBaA=="],
"@tamagui/switch-headless": ["@tamagui/switch-headless@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/label": "1.141.4", "@tamagui/use-previous": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-lG/vLMJhhDw15Xkn/rCA96uPpCXHxPV7pXifhk62la0sVozq6R8PYuvB70QGu2uVj2fLDMltkDiMNhFsKiP5HQ=="],
"@tamagui/switch-headless": ["@tamagui/switch-headless@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/label": "1.141.5", "@tamagui/use-previous": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-BrBWXlD9+lK6U2zkyadSCkaepTjyHgxFo9ZwXVb/btC9/OJLDX8vQN/MsqYDLklaVwDL8wCUmQ8ufuy/4lLAFA=="],
"@tamagui/tabs": ["@tamagui/tabs@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/get-button-sized": "1.141.4", "@tamagui/group": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/roving-focus": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-direction": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-2w6hx3rHXx3CDusCGKQqINkAqTMS3BgewvSa3M/XiKPKrR/bzBVdWInD5KLKOOOSJRkUplAPUhfA6/j3BTpaag=="],
"@tamagui/tabs": ["@tamagui/tabs@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/get-button-sized": "1.141.5", "@tamagui/group": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/roving-focus": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-direction": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-3nX5y8HcjL5c68r0kw3kAcWvKyEVFK6vOY1NQ3ekB2CwonqWriVC0bHpVoCFgtyM5hXAEXrfkF3usoRIp9W7/g=="],
"@tamagui/text": ["@tamagui/text@1.141.4", "", { "dependencies": { "@tamagui/get-font-sized": "1.141.4", "@tamagui/helpers-tamagui": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-dij/G+SSZ9rAs93YktN18VLmyl5rS3e+XcUSd4lcawzNL5WKY7iyInDvOZzEJrTnIv5nBwtwU9oaods4Wr9URw=="],
"@tamagui/text": ["@tamagui/text@1.141.5", "", { "dependencies": { "@tamagui/get-font-sized": "1.141.5", "@tamagui/helpers-tamagui": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-yY5Lbg/uxBAfIBsNmF5gpH6SdBauF8LiO8rgUFM1jtXezuEoFs2nkTwmBrx8Gd50JgGAPTbKjQreEHg/hAdYpA=="],
"@tamagui/theme": ["@tamagui/theme@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/start-transition": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-wLN6BescUJEyd037M9ABnzjSRRnuZUQR9RVLtv2Mnl2qHzaG8xRDGQXBlLhm4HvHhkI1J2P0Ef2xNvQVaNuJ+w=="],
"@tamagui/theme": ["@tamagui/theme@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/start-transition": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-thUcFAVBo3xBQQgSxwf0UVkXkh76I0570BkFux85p1OLyZRA/AWg47sAKvo9zXPogLmu1Qzg6o26HYt65VRsWw=="],
"@tamagui/theme-builder": ["@tamagui/theme-builder@1.141.4", "", { "dependencies": { "@tamagui/create-theme": "1.141.4", "@tamagui/web": "1.141.4", "color2k": "^2.0.2" } }, "sha512-mPvPUeJIGRVEBGvu9BG1NuAnUUEqclGqq9Q74UUoHCTPP8lIxedtfKhvf6gtoFQOhVuPeB0HArMIrZNmwzwkbg=="],
"@tamagui/theme-builder": ["@tamagui/theme-builder@1.141.5", "", { "dependencies": { "@tamagui/create-theme": "1.141.5", "@tamagui/web": "1.141.5", "color2k": "^2.0.2" } }, "sha512-qiVDVN0l2WUsz/cHS/iPRv2y9lpTgJi+GGv89XBUs4fR2tu1XtMvD/iOBi60NE72NRZdxomBVv4Tu5ZZ2OJsjw=="],
"@tamagui/themes": ["@tamagui/themes@1.141.4", "", { "dependencies": { "@tamagui/colors": "1.141.4", "@tamagui/create-theme": "1.141.4", "@tamagui/theme-builder": "1.141.4", "@tamagui/web": "1.141.4", "color2k": "^2.0.2" } }, "sha512-PBU5LCRH3XKSDBHGxLuKiahNCSLCrOZo617RaBRCrQEBzWpx9cZtLQcM35H5ytO9VIVjj/g063UP0Du3y/h7WQ=="],
"@tamagui/themes": ["@tamagui/themes@1.141.5", "", { "dependencies": { "@tamagui/colors": "1.141.5", "@tamagui/create-theme": "1.141.5", "@tamagui/theme-builder": "1.141.5", "@tamagui/web": "1.141.5", "color2k": "^2.0.2" } }, "sha512-GWqgQLFnDXm11kFmhwgDisq/nLq9qfz3V8aU5rcBbgeo/Fi4Hw8ippnHFg59lpsbeiAy5fFVREUNfPH9g7uhTQ=="],
"@tamagui/timer": ["@tamagui/timer@1.141.4", "", {}, "sha512-uK81OKhy6u7c0kSf0+028XzwuuTLWN+kAAUSEtpQy/HdM4MU0oGtf1+2mnn3X8wwTBKDP9iP33uN8XHV161uEw=="],
"@tamagui/timer": ["@tamagui/timer@1.141.5", "", {}, "sha512-Mk4c8rwVySUtLGPstD8pYqC8O4cZJPM8C3GkbEOXYxqYV4lmYl5MLziuLT+TWwbMPKrelsqNPbeif/JZRCmsNw=="],
"@tamagui/toggle-group": ["@tamagui/toggle-group@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/font-size": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/group": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/helpers-tamagui": "1.141.4", "@tamagui/roving-focus": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-direction": "1.141.4", "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-Stgvm+XvfbrCDFgo/fsomsrDLaO/ysA1v/TDcCVtbAbJLGjlAJxVe3B7DgagAEHRYFql62KGDOYn4MhgHjaQsw=="],
"@tamagui/toggle-group": ["@tamagui/toggle-group@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/font-size": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/group": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/helpers-tamagui": "1.141.5", "@tamagui/roving-focus": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-direction": "1.141.5", "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-fXGuB7TWYulJW2S7eLeQQUrlW0Ue0HSuVqjNYbn5+ddhmnhIigi7i+wpn7bootTJ7QUHxLP6GLdVL7R/kxZuKw=="],
"@tamagui/tooltip": ["@tamagui/tooltip@1.141.4", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@tamagui/compose-refs": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/floating": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/polyfill-dev": "1.141.4", "@tamagui/popover": "1.141.4", "@tamagui/popper": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/text": "1.141.4", "@tamagui/use-controllable-state": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-InGdJrGeND6Ig4hEWdiIVdfj0hZKSUaLV7bSHvnCci1ba5pxr9t/Y4Z+USy01h4onumSOt7CNJZbJZW/EOMG3w=="],
"@tamagui/tooltip": ["@tamagui/tooltip@1.141.5", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@tamagui/compose-refs": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/floating": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/polyfill-dev": "1.141.5", "@tamagui/popover": "1.141.5", "@tamagui/popper": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/text": "1.141.5", "@tamagui/use-controllable-state": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-b5z8ThGRigQ0enkYI5SyacQLQTHWXIozUt4pK95sO/ueVL2WqXOvZjSdpiy4NN9vkDlJ2ZB+M+7SS6DQQqv8fA=="],
"@tamagui/types": ["@tamagui/types@1.141.4", "", {}, "sha512-PO2YoO+wmIusYNL54I6A02XIoqm3ELnVGwpB5XuQfsoEy16gWU+HDsGKjlBqovSD+XHHdEJf57tMoNdG6VGezQ=="],
"@tamagui/types": ["@tamagui/types@1.141.5", "", {}, "sha512-+bab6iGj9wHGqQr58Ijf6pLtxZXr8Tf+9+im99ZDV4dp9CF8jBpqwFP1KlfTOo1M/hR8QH0SRhvC6PG/2F2lWA=="],
"@tamagui/use-async": ["@tamagui/use-async@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-X/sjWLTXVP4TEAIbC2a6c+SAnoYYrq1HzVXdeh9kN///XIj1koNwFJ1AKQ+e6dsS5d28ox8sRTyPKLryNz/r2w=="],
"@tamagui/use-async": ["@tamagui/use-async@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-tadWaZYu0m0LzvQQGLoVJUwXioA9bJ72PQDArZRGlYKZ82LA/3TCF8bqWsK5HyPv8SbXIVIScwPVc17TuAkQ0A=="],
"@tamagui/use-callback-ref": ["@tamagui/use-callback-ref@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-mI/Jt1teDppdAhLh3DcnX/pmg9wasqXMIzRFqO6Wmgov9/jLVb8+ve/cE7jN8T8YnJHMAHFsfqRalfnUMURpDw=="],
"@tamagui/use-callback-ref": ["@tamagui/use-callback-ref@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-6Ib5zw5XUJUNFjyKjXEZMiJg2G6QiAOlM3d0IiQZ+d9CeCSEClAShSdWMdHAuBjldKHeFyB/FqpgjSiQtLJxTA=="],
"@tamagui/use-constant": ["@tamagui/use-constant@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-iE5rWsl6fjBobd0jH1fMd57yekvwPD3U25DqJ0Wze5C00kvqHiWdiPeXgoZwl6i/mVT4sPmVNkfjueL0mz58Tw=="],
"@tamagui/use-constant": ["@tamagui/use-constant@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-yJzcKbOmZQS+HaqC9SMSjreIBHo5cnQJ1amLjuX9UjOZ1CIo6dy9HdzffLFUbEY/ECzXZXMQZ91wZE1fo7FMrA=="],
"@tamagui/use-controllable-state": ["@tamagui/use-controllable-state@1.141.4", "", { "dependencies": { "@tamagui/start-transition": "1.141.4", "@tamagui/use-event": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-YK/DRJ/YU27ZFlVvPegP/YidkkZ8Kt0+EqFkpLWmNiCN8Nbf2Y6DBEafIs003DPsF0wwYTsN8ANu/SgCBJ9TCA=="],
"@tamagui/use-controllable-state": ["@tamagui/use-controllable-state@1.141.5", "", { "dependencies": { "@tamagui/start-transition": "1.141.5", "@tamagui/use-event": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-q6qaUbj94uArDQU96HxbF6Gtcg9dTQn0Se7sfReb2G93I3PPSwZQKz54UKITksU8xhIfLU6K1Xq9GQ2iv9x6pA=="],
"@tamagui/use-debounce": ["@tamagui/use-debounce@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-Dtk/ee5yTQJ+VD/wsDIrz86gyWBffGEkv2zKVu1OIIjUvJ2+u7GFrwwV5/kQWAdmkvne080AUGtVwHmj+v9uGA=="],
"@tamagui/use-debounce": ["@tamagui/use-debounce@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-XaGZXdSOeHNDbW6osV6E5VWSRfj531lAyVJN1qQ6kMXWWtDAenyfcT3IhW1UZWMUAZoYQhMbaLQy+RKJNwOejQ=="],
"@tamagui/use-did-finish-ssr": ["@tamagui/use-did-finish-ssr@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-QnYEfmdTzkckb8mQaPWrF3c4p64T73rvKGKr9abibWVGRMjRY0cSbxv8FBcnpjgwxnfNDl63veii7RLdxGyU1w=="],
"@tamagui/use-did-finish-ssr": ["@tamagui/use-did-finish-ssr@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-N8ycayHFl9OkzSasqlQIHyNU5zGoRNV5aiwKTAHtviZEOM6mdg1nbEt9/jm0CviWFhvaP7n4/aaBMlkjeMd17w=="],
"@tamagui/use-direction": ["@tamagui/use-direction@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-Fn13pVu+5583gDFcmFvDJSFovsjrXioiQxAJ+2y/7wjZb3uK/fBv1GgVVWJashiF9SRkr7dZhYkWRz+C8k/raQ=="],
"@tamagui/use-direction": ["@tamagui/use-direction@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-U51JEBAHyEzgkFwzkKqf2XxjBxk1jbO8a90tREpW0XrNRWw5kFfagzC1hKyhy0Ah3dm7inHZut+XIGkSXwQ+vA=="],
"@tamagui/use-element-layout": ["@tamagui/use-element-layout@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4", "@tamagui/is-equal-shallow": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-h+G9pg2ZiGAJX2TaEslIcPKQ7Lygml9XlnkVHAk7fvXwLyE+iHuXAl9FxYnEOFOy3xqw3Ej6Y42BAYoG48FgeA=="],
"@tamagui/use-element-layout": ["@tamagui/use-element-layout@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5", "@tamagui/is-equal-shallow": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-YZIvUiRASgBy3naFiUChyHzRXYuTuaCT6NrkEr5G53CFF1harROJCFLiGe8yoPDjLfkcE4MI+bkxuRFwQFF0Nw=="],
"@tamagui/use-escape-keydown": ["@tamagui/use-escape-keydown@1.141.4", "", { "dependencies": { "@tamagui/use-callback-ref": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-fFscuu+Mx7V1Xk3VIcjBLMsmH3wS2PbqMBqaTBeT1lg/hI5tYDppoMLhweXbvi3mqrx135T2uLC7MA7P521bZQ=="],
"@tamagui/use-escape-keydown": ["@tamagui/use-escape-keydown@1.141.5", "", { "dependencies": { "@tamagui/use-callback-ref": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-p8GIscnKeC6H3AW9Kr2OIPksa8Tzxj4BQXescidsjwYsEz9cpZENKFasVSBymJSY8aHD3eec5yVQi/ck5bxIEA=="],
"@tamagui/use-event": ["@tamagui/use-event@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-9AD3gMRA3UQwtA/lFhJyKwAeU8Q4Lcd/TodRF/GQc9tez4sJwcxPOnecNFaMF5R7y1vNrjghbaSw9W5+unVDhQ=="],
"@tamagui/use-event": ["@tamagui/use-event@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-viP+N6/UgyDbQ/HxlTjbJUhxu2y/z5y0ZmwHiTi08b1+mU21JN0XnupuCCMEQKqyi/7jIVInpCb9wCwNn/IuQA=="],
"@tamagui/use-force-update": ["@tamagui/use-force-update@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-+FHa8J24sLb3BWpJrah1cizvBJjhjbtxtZcvTuo3Qovt3b47ktx1o+SeEdoiGwc6OYezeuFdAYiTPVHgXqHrvQ=="],
"@tamagui/use-force-update": ["@tamagui/use-force-update@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-KF58Da/yXmlgwKrs59egN/LidrRa6JYt3fVkU2h71RcILqENuZdp7l5yyGnDS5oy2F9MrF2QOxxtB6QiO7Otrg=="],
"@tamagui/use-keyboard-visible": ["@tamagui/use-keyboard-visible@1.141.4", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-+f3B3TIAfUW1dPkUxtJ8WlGXhakGsvmESkbOfs0Mbk6zVONUaIXfTUB9eWOsVi/hAYL//XSOaEi54bFQD9Ug9Q=="],
"@tamagui/use-keyboard-visible": ["@tamagui/use-keyboard-visible@1.141.5", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-J0BYWgeyg7E0H2veLbLF1f5ZoktsW0ZN6WhlI8Lnj3blZYRYQOjiOEl2qd6LWONrabTWYWd4ahDUmQyyDnHlZQ=="],
"@tamagui/use-presence": ["@tamagui/use-presence@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-KKL7kx3Wkvxhx82E1y/l4DHNC0zxhFa6FQ/76lhA5YLMy04dNXFzWzCNl2YBjH8UhypfWwJsu6teW8ku7+1NkQ=="],
"@tamagui/use-presence": ["@tamagui/use-presence@1.141.5", "", { "dependencies": { "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-4dxNW7jxFTC++hqqqHt24nUYfWPzXTURQtV7lsTbB9sUbTpspBV1dglWcboidO1+xrLj2ERjwXS7wbGG95ZWgA=="],
"@tamagui/use-previous": ["@tamagui/use-previous@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-jfD+x9GpaMjEFcsTpj1ko9HrrXP/quZuwlJ3O8VHCSWCm9zEPCaHV/WLZMpeumKGeLVqUnCowqqmfWITV95cfQ=="],
"@tamagui/use-previous": ["@tamagui/use-previous@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-kBc29hM95p5fxC29dD72X6DQsZozU03mSNrW/wk99XoKy/BR3eAgnc+o9WZItLFr7MXbMNIc0ilZ5mO51ioJ/A=="],
"@tamagui/use-window-dimensions": ["@tamagui/use-window-dimensions@1.141.4", "", { "dependencies": { "@tamagui/constants": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-y2BzfajhEfbWsRIx3ATLI4uu0vEe4Vm/YKyjTCMwJTB+ZfZYoQPTiWOnmCzpsjDOTNlvP2/e1qsSoVePD5woJg=="],
"@tamagui/use-window-dimensions": ["@tamagui/use-window-dimensions@1.141.5", "", { "dependencies": { "@tamagui/constants": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-TXTXKnHZO3Nc1t8lH75/B/o+lMMTJDpSU4XBt/soDf8tU++1YmReZxzHKUAYuLY4YmGCo6hYkOYMjGS7V/l1NA=="],
"@tamagui/visually-hidden": ["@tamagui/visually-hidden@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-B0eTSm8XGNSjUG5xtceAruNh/KHw3Cik5R7LDqg/DhXdVhDSOWPXcMj35C6tGeIes8bz8BAyp9VKKyXeUCkU+w=="],
"@tamagui/visually-hidden": ["@tamagui/visually-hidden@1.141.5", "", { "dependencies": { "@tamagui/web": "1.141.5" }, "peerDependencies": { "react": "*" } }, "sha512-FE+EPtpz24yf4qQT21XwxzgV+ryYrHpDxKETVpO+n0NHuvJAUJsobHGbq7FSRcgX+SEXgKCTDCAG/XaF2VDl2Q=="],
"@tamagui/web": ["@tamagui/web@1.141.4", "", { "dependencies": { "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/helpers": "1.141.4", "@tamagui/is-equal-shallow": "1.141.4", "@tamagui/normalize-css-color": "1.141.4", "@tamagui/timer": "1.141.4", "@tamagui/types": "1.141.4", "@tamagui/use-did-finish-ssr": "1.141.4", "@tamagui/use-event": "1.141.4", "@tamagui/use-force-update": "1.141.4" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-hAqH75IMPcoN/PstAblJF+DbUgjk0cIuzwQiswnpn+60cIn45GkmWlwHzgIwADVrohrIKUEJXPTCu2A3cn3MWQ=="],
"@tamagui/web": ["@tamagui/web@1.141.5", "", { "dependencies": { "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/helpers": "1.141.5", "@tamagui/is-equal-shallow": "1.141.5", "@tamagui/normalize-css-color": "1.141.5", "@tamagui/timer": "1.141.5", "@tamagui/types": "1.141.5", "@tamagui/use-did-finish-ssr": "1.141.5", "@tamagui/use-event": "1.141.5", "@tamagui/use-force-update": "1.141.5" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-GMI4kYYVjy51fKVooq2Wt21aMQoyKLMl89W2lU6KQJ6ipFMirWO3LWA+KFnS7ME3O3Kh2h+IjMfIdpp++db6tQ=="],
"@tamagui/z-index-stack": ["@tamagui/z-index-stack@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-q/rooqEqfKxiY7lgba+7Tiyg8BF2QZ9BRUcBl4EdG4FU5fYTD5fMy7CBDszVkzTcD/Z337VEAggJVyeHxCL+Qw=="],
"@tamagui/z-index-stack": ["@tamagui/z-index-stack@1.141.5", "", { "peerDependencies": { "react": "*" } }, "sha512-h9qqz/HsTiF9hou8FnFYkB1UaZV3bZFU7aWrZdvTIDQTdPU+4jKSvnWhR68K5xGRGkthQ2R9cFnFIrlwIt+efA=="],
"@tanstack/query-async-storage-persister": ["@tanstack/query-async-storage-persister@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.10", "@tanstack/query-persist-client-core": "5.91.9" } }, "sha512-bLOs6ZLTki88if8oDQDdnxk7wgMaKMAVTRxn+WiSI0An7rj3C/7/yDTjOLhwPaoipbTiFLF0/PnbpVXIbWqQYQ=="],
@@ -876,7 +876,7 @@
"@types/lodash": ["@types/lodash@4.17.21", "", {}, "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ=="],
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
"@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="],
@@ -1896,7 +1896,7 @@
"react-is": ["react-is@19.2.0", "", {}, "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA=="],
"react-native": ["react-native@0.83.0", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.83.0", "@react-native/codegen": "0.83.0", "@react-native/community-cli-plugin": "0.83.0", "@react-native/gradle-plugin": "0.83.0", "@react-native/js-polyfills": "0.83.0", "@react-native/normalize-colors": "0.83.0", "@react-native/virtualized-lists": "0.83.0", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.32.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "hermes-compiler": "0.14.0", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.3", "metro-source-map": "^0.83.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.1", "react": "^19.2.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-a8wPjGfkktb1+Mjvzkky3d0u6j6zdWAzftZ2LdQtgRgqkMMfgQxD9S+ri3RNlfAFQpuCAOYUIyrNHiVkUQChxA=="],
"react-native": ["react-native@0.83.1", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.83.1", "@react-native/codegen": "0.83.1", "@react-native/community-cli-plugin": "0.83.1", "@react-native/gradle-plugin": "0.83.1", "@react-native/js-polyfills": "0.83.1", "@react-native/normalize-colors": "0.83.1", "@react-native/virtualized-lists": "0.83.1", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.32.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "hermes-compiler": "0.14.0", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.3", "metro-source-map": "^0.83.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.1", "react": "^19.2.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-mL1q5HPq5cWseVhWRLl+Fwvi5z1UO+3vGOpjr+sHFwcUletPRZ5Kv+d0tUfqHmvi73/53NjlQqX1Pyn4GguUfA=="],
"react-native-background-actions": ["react-native-background-actions@4.0.1", "", { "dependencies": { "eventemitter3": "^4.0.7" }, "peerDependencies": { "react-native": ">=0.47.0" } }, "sha512-LADhnb4ag1oH5Lotq0j8K9e2cFmrafFyg2PCME88VkTjqDUgNcJonkNdMCTHN0N3fh+hwAA7nDR4Cxkj9Q8eCw=="],
@@ -1912,8 +1912,6 @@
"react-native-device-info": ["react-native-device-info@15.0.1", "", { "peerDependencies": { "react-native": "*" } }, "sha512-U5waZRXtT3l1SgZpZMlIvMKPTkFZPH8W7Ks6GrJhdH723aUIPxjVer7cRSij1mvQdOAAYFJV/9BDzlC8apG89A=="],
"react-native-dns-lookup": ["react-native-dns-lookup@1.0.6", "", { "peerDependencies": { "react-native": "*" } }, "sha512-47pdg4h50CEume23HzUs9FwhCqsXGYtXIRRnb0olY62ThifQGVN0eETRenY4YaKv4g9jIt1GVKDTSkH//uXXRg=="],
"react-native-fs": ["react-native-fs@2.20.0", "", { "dependencies": { "base-64": "^0.1.0", "utf8": "^3.0.0" }, "peerDependencies": { "react-native": "*", "react-native-windows": "*" }, "optionalPeers": ["react-native-windows"] }, "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ=="],
"react-native-gesture-handler": ["react-native-gesture-handler@2.29.1", "", { "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-du3qmv0e3Sm7qsd9SfmHps+AggLiylcBBQ8ztz7WUtd8ZjKs5V3kekAbi9R2W9bRLSg47Ntp4GGMYZOhikQdZA=="],
@@ -1930,11 +1928,11 @@
"react-native-nitro-fetch": ["react-native-nitro-fetch@0.1.6", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "^0.29.2", "react-native-worklets-core": "^1.6.0" }, "optionalPeers": ["react-native-worklets-core"] }, "sha512-DbE/vN5B67SJM8Q0myHOwSSc7ASqJPaKYXVsWdNGIPS+csr9gygCKILT4RQ+xZ92iJGKn4bfyq+rRsacRWBV9A=="],
"react-native-nitro-modules": ["react-native-nitro-modules@0.31.10", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-hcvjTu9YJE9fMmnAUvhG8CxvYLpOuMQ/2eyi/S6GyrecezF6Rmk/uRQEL6v09BRFWA/xRVZNQVulQPS+2HS3mQ=="],
"react-native-nitro-modules": ["react-native-nitro-modules@0.32.0-beta.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Yb3PaefFZXVLAj/2meruDX9IH6Q8P0eeKqLiTcwY8NG/bMb/cUqteYA9FZIhc8TUGmn/FiQu18hhCF0QYySOWA=="],
"react-native-nitro-ota": ["react-native-nitro-ota@0.8.2", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "^0.29.8" } }, "sha512-UhL+62PAj5yXQnpTqkMUBN1NX6oerAdA5lifg3XncLT3Nipswy3aZa7rDATYsXPBrWp4w8VvxfEKy4PWk7Ifxw=="],
"react-native-nitro-ota": ["react-native-nitro-ota@0.9.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "^0.31.10" } }, "sha512-hxSGMy612Iz+DEgJu1fLBFV9w2Z9s3hR7glnj/qDmzpF5+Bb2NxPGmr19hhhL6riKZx/+BO3e1mY4OG1cek65w=="],
"react-native-pager-view": ["react-native-pager-view@7.0.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-yj/v6BN/WGuV1VBVWaCjYOjQlhTaqJs4Ocismw0XRSsHGqO2wuQdWF8it5iFnfibQVBBED0/GC7qKOlQ4FBzNg=="],
"react-native-pager-view": ["react-native-pager-view@8.0.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-oAwlWT1lhTkIs9HhODnjNNl/owxzn9DP1MbP+az6OTUdgbmzA16Up83sBH8NRKwrH8rNm7iuWnX1qMqiiWOLhg=="],
"react-native-reanimated": ["react-native-reanimated@4.1.6", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*", "react-native-worklets": ">=0.5.0" } }, "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ=="],
@@ -1944,7 +1942,7 @@
"react-native-sortables": ["react-native-sortables@1.9.4", "", { "optionalDependencies": { "react-native-haptic-feedback": ">=2.0.0" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=3.0.0" } }, "sha512-a6hxT+gl14HA5Sm8UiLXJqF8KMEQVa+mUJd75OnzoVsmrxUDtjAatlMdV0kI9qTQDT/ZSFLPRmdUhOR762IA4g=="],
"react-native-tab-view": ["react-native-tab-view@4.2.1", "", { "dependencies": { "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0" } }, "sha512-3fJXlzTVunVg9V+7a83XVcjwIxUO014qkkkpB5z7mMjXf5XVQFxzxUtC6GrdDK57wBA/OglpsP1qx5HvqMPtfw=="],
"react-native-tab-view": ["react-native-tab-view@4.2.2", "", { "dependencies": { "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0" } }, "sha512-NXtrG6OchvbGjsvbySJGVocXxo4Y2vA17ph4rAaWtA2jh+AasD8OyikKBRg2SmllEfeQ+GEhcKe8kulHv8BhTg=="],
"react-native-text-ticker": ["react-native-text-ticker@1.15.0", "", {}, "sha512-d/uK+PIOhsYMy1r8h825iq/nADiHsabz3WMbRJSnkpQYn+K9aykUAXRRhu8ZbTAzk4CgnUWajJEFxS5ZDygsdg=="],
@@ -1952,6 +1950,8 @@
"react-native-track-player": ["react-native-track-player@5.0.0-alpha0", "", { "peerDependencies": { "react": "*", "react-native": "*", "shaka-player": "^4.7.9" }, "optionalPeers": ["shaka-player"] }, "sha512-dxMI8aX75wS9S5/DM7dWN060uGjEitX3fhvLGtrQ96AgIKvOorKy3HkBU7DgvCF/6YDAMxez2PaZA+4A3DQWAQ=="],
"react-native-turbo-image": ["react-native-turbo-image@1.23.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-PXJO5x2mzrtfcyWaYrSFSMluYjYKKK+u6kPiw9Qk47JTOT/evNyBlMsTjbIWHlHYnb5eGTPhvoFy91csplyUkw=="],
"react-native-url-polyfill": ["react-native-url-polyfill@2.0.0", "", { "dependencies": { "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "react-native": "*" } }, "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA=="],
"react-native-uuid": ["react-native-uuid@2.0.3", "", {}, "sha512-f/YfIS2f5UB+gut7t/9BKGSCYbRA9/74A5R1MDp+FLYsuS+OSWoiM/D8Jko6OJB6Jcu3v6ONuddvZKHdIGpeiw=="],
@@ -2136,7 +2136,7 @@
"tabbable": ["tabbable@6.3.0", "", {}, "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ=="],
"tamagui": ["tamagui@1.141.4", "", { "dependencies": { "@tamagui/accordion": "1.141.4", "@tamagui/adapt": "1.141.4", "@tamagui/alert-dialog": "1.141.4", "@tamagui/animate-presence": "1.141.4", "@tamagui/avatar": "1.141.4", "@tamagui/button": "1.141.4", "@tamagui/card": "1.141.4", "@tamagui/checkbox": "1.141.4", "@tamagui/compose-refs": "1.141.4", "@tamagui/constants": "1.141.4", "@tamagui/core": "1.141.4", "@tamagui/create-context": "1.141.4", "@tamagui/dialog": "1.141.4", "@tamagui/elements": "1.141.4", "@tamagui/fake-react-native": "1.141.4", "@tamagui/focusable": "1.141.4", "@tamagui/font-size": "1.141.4", "@tamagui/form": "1.141.4", "@tamagui/get-button-sized": "1.141.4", "@tamagui/get-font-sized": "1.141.4", "@tamagui/get-token": "1.141.4", "@tamagui/group": "1.141.4", "@tamagui/helpers-tamagui": "1.141.4", "@tamagui/image": "1.141.4", "@tamagui/label": "1.141.4", "@tamagui/linear-gradient": "1.141.4", "@tamagui/list-item": "1.141.4", "@tamagui/polyfill-dev": "1.141.4", "@tamagui/popover": "1.141.4", "@tamagui/popper": "1.141.4", "@tamagui/portal": "1.141.4", "@tamagui/progress": "1.141.4", "@tamagui/radio-group": "1.141.4", "@tamagui/react-native-media-driver": "1.141.4", "@tamagui/scroll-view": "1.141.4", "@tamagui/select": "1.141.4", "@tamagui/separator": "1.141.4", "@tamagui/shapes": "1.141.4", "@tamagui/sheet": "1.141.4", "@tamagui/slider": "1.141.4", "@tamagui/stacks": "1.141.4", "@tamagui/switch": "1.141.4", "@tamagui/tabs": "1.141.4", "@tamagui/text": "1.141.4", "@tamagui/theme": "1.141.4", "@tamagui/toggle-group": "1.141.4", "@tamagui/tooltip": "1.141.4", "@tamagui/use-controllable-state": "1.141.4", "@tamagui/use-debounce": "1.141.4", "@tamagui/use-force-update": "1.141.4", "@tamagui/use-window-dimensions": "1.141.4", "@tamagui/visually-hidden": "1.141.4", "@tamagui/z-index-stack": "1.141.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-lsApekHfFDymY7kqIRVWLRPIiY7lMtkSepuWLcTb0tAGUFKTopWRYZrLRonhKmkyWKNEZY2m7lNHCCqZGg9z0A=="],
"tamagui": ["tamagui@1.141.5", "", { "dependencies": { "@tamagui/accordion": "1.141.5", "@tamagui/adapt": "1.141.5", "@tamagui/alert-dialog": "1.141.5", "@tamagui/animate-presence": "1.141.5", "@tamagui/avatar": "1.141.5", "@tamagui/button": "1.141.5", "@tamagui/card": "1.141.5", "@tamagui/checkbox": "1.141.5", "@tamagui/compose-refs": "1.141.5", "@tamagui/constants": "1.141.5", "@tamagui/core": "1.141.5", "@tamagui/create-context": "1.141.5", "@tamagui/dialog": "1.141.5", "@tamagui/elements": "1.141.5", "@tamagui/fake-react-native": "1.141.5", "@tamagui/focusable": "1.141.5", "@tamagui/font-size": "1.141.5", "@tamagui/form": "1.141.5", "@tamagui/get-button-sized": "1.141.5", "@tamagui/get-font-sized": "1.141.5", "@tamagui/get-token": "1.141.5", "@tamagui/group": "1.141.5", "@tamagui/helpers-tamagui": "1.141.5", "@tamagui/image": "1.141.5", "@tamagui/label": "1.141.5", "@tamagui/linear-gradient": "1.141.5", "@tamagui/list-item": "1.141.5", "@tamagui/polyfill-dev": "1.141.5", "@tamagui/popover": "1.141.5", "@tamagui/popper": "1.141.5", "@tamagui/portal": "1.141.5", "@tamagui/progress": "1.141.5", "@tamagui/radio-group": "1.141.5", "@tamagui/react-native-media-driver": "1.141.5", "@tamagui/scroll-view": "1.141.5", "@tamagui/select": "1.141.5", "@tamagui/separator": "1.141.5", "@tamagui/shapes": "1.141.5", "@tamagui/sheet": "1.141.5", "@tamagui/slider": "1.141.5", "@tamagui/stacks": "1.141.5", "@tamagui/switch": "1.141.5", "@tamagui/tabs": "1.141.5", "@tamagui/text": "1.141.5", "@tamagui/theme": "1.141.5", "@tamagui/toggle-group": "1.141.5", "@tamagui/tooltip": "1.141.5", "@tamagui/use-controllable-state": "1.141.5", "@tamagui/use-debounce": "1.141.5", "@tamagui/use-force-update": "1.141.5", "@tamagui/use-window-dimensions": "1.141.5", "@tamagui/visually-hidden": "1.141.5", "@tamagui/z-index-stack": "1.141.5" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-TMv7yQbeZ9IPCOZj+ts9r70y3DnQ7VhpfFAIBQW3LncdBOtJkmFwY4XyYt6AzuA1etY2IB0nph/EvMptDJ3/bg=="],
"terser": ["terser@5.44.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw=="],
@@ -2300,8 +2300,12 @@
"@istanbuljs/load-nyc-config/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
"@jest/console/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@jest/console/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"@jest/core/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@jest/core/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="],
"@jest/core/jest-validate": ["jest-validate@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.2.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", "pretty-format": "30.2.0" } }, "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw=="],
@@ -2312,10 +2316,14 @@
"@jest/environment/@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
"@jest/environment/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@jest/environment/jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
"@jest/fake-timers/@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
"@jest/fake-timers/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@jest/fake-timers/jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
"@jest/fake-timers/jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
@@ -2324,6 +2332,10 @@
"@jest/globals/@jest/environment": ["@jest/environment@30.2.0", "", { "dependencies": { "@jest/fake-timers": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "jest-mock": "30.2.0" } }, "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g=="],
"@jest/pattern/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@jest/reporters/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@jest/reporters/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"@jest/reporters/jest-worker": ["jest-worker@30.2.0", "", { "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", "jest-util": "30.2.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" } }, "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g=="],
@@ -2336,6 +2348,8 @@
"@jest/transform/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"@jest/types/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@nicolo-ribaudo/eslint-scope-5-internals/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
"@react-native-community/cli/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
@@ -2366,6 +2380,8 @@
"@tanstack/react-query/@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="],
"@types/graceful-fs/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@types/react-native/@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="],
"@types/react-native-vector-icons/@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="],
@@ -2394,6 +2410,10 @@
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"chrome-launcher/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"chromium-edge-launcher/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"chromium-edge-launcher/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"cli-truncate/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
@@ -2408,6 +2428,8 @@
"error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
"eslint/@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
@@ -2444,6 +2466,8 @@
"jest-circus/@jest/environment": ["@jest/environment@30.2.0", "", { "dependencies": { "@jest/fake-timers": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "jest-mock": "30.2.0" } }, "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g=="],
"jest-circus/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-circus/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"jest-cli/jest-validate": ["jest-validate@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.2.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", "pretty-format": "30.2.0" } }, "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw=="],
@@ -2462,20 +2486,28 @@
"jest-environment-node/@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
"jest-environment-node/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-environment-node/jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
"jest-environment-node/jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
"jest-haste-map/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-haste-map/jest-worker": ["jest-worker@30.2.0", "", { "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", "jest-util": "30.2.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" } }, "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g=="],
"jest-message-util/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"jest-mock/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-resolve/jest-validate": ["jest-validate@30.2.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.2.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", "pretty-format": "30.2.0" } }, "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw=="],
"jest-resolve/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"jest-runner/@jest/environment": ["@jest/environment@30.2.0", "", { "dependencies": { "@jest/fake-timers": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "jest-mock": "30.2.0" } }, "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g=="],
"jest-runner/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-runner/jest-environment-node": ["jest-environment-node@30.2.0", "", { "dependencies": { "@jest/environment": "30.2.0", "@jest/fake-timers": "30.2.0", "@jest/types": "30.2.0", "@types/node": "*", "jest-mock": "30.2.0", "jest-util": "30.2.0", "jest-validate": "30.2.0" } }, "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA=="],
"jest-runner/jest-worker": ["jest-worker@30.2.0", "", { "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", "jest-util": "30.2.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" } }, "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g=="],
@@ -2486,6 +2518,8 @@
"jest-runtime/@jest/fake-timers": ["@jest/fake-timers@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw=="],
"jest-runtime/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-runtime/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"jest-runtime/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
@@ -2494,6 +2528,8 @@
"jest-snapshot/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"jest-util/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-util/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="],
"jest-util/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
@@ -2502,6 +2538,10 @@
"jest-validate/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
"jest-watcher/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-worker/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-worker/jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
@@ -2626,6 +2666,8 @@
"@jest/create-cache-key-function/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"@jest/create-cache-key-function/@jest/types/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@jest/environment/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"@jest/environment/jest-mock/jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
@@ -2638,6 +2680,8 @@
"@jest/globals/@jest/environment/@jest/fake-timers": ["@jest/fake-timers@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw=="],
"@jest/globals/@jest/environment/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"@jest/reporters/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@jest/reporters/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
@@ -2692,6 +2736,8 @@
"eslint-plugin-react-hooks/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
"eslint/@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"find-cache-dir/pkg-dir/find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="],
@@ -2714,6 +2760,8 @@
"jest-config/jest-environment-node/@jest/fake-timers": ["@jest/fake-timers@30.2.0", "", { "dependencies": { "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", "jest-message-util": "30.2.0", "jest-mock": "30.2.0", "jest-util": "30.2.0" } }, "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw=="],
"jest-config/jest-environment-node/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-environment-node/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"jest-haste-map/jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
@@ -2734,6 +2782,8 @@
"jest-validate/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"jest-validate/@jest/types/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"jest-validate/pretty-format/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"jest-validate/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
@@ -2808,6 +2858,12 @@
"babel-jest/@jest/transform/@jest/types/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
"babel-jest/@jest/transform/@jest/types/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"babel-jest/@jest/transform/jest-haste-map/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"babel-jest/@jest/transform/jest-util/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
"cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"find-cache-dir/pkg-dir/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],

View File

@@ -4,14 +4,13 @@ import './src/utils/console-override'
import { AppRegistry } from 'react-native'
import App from './App'
import { name as appName } from './app.json'
import { PlaybackService } from './src/player/service'
import { PlaybackService } from './src/player'
import TrackPlayer from 'react-native-track-player'
import { enableScreens } from 'react-native-screens'
enableScreens(true)
AppRegistry.registerComponent(appName, () => App)
AppRegistry.registerComponent('RNCarPlayScene', () => App)
// Register RNTP playback service for remote controls
TrackPlayer.registerPlaybackService(() => PlaybackService)

View File

@@ -543,7 +543,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 273;
CURRENT_PROJECT_VERSION = 277;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_BITCODE = NO;
@@ -554,7 +554,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.6;
MARKETING_VERSION = 1.0.10;
NEW_SETTING = "";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -585,7 +585,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 273;
CURRENT_PROJECT_VERSION = 277;
DEVELOPMENT_TEAM = WAH9CZ8BPG;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -595,7 +595,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.6;
MARKETING_VERSION = 1.0.10;
NEW_SETTING = "";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -823,7 +823,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 273;
CURRENT_PROJECT_VERSION = 277;
DEVELOPMENT_TEAM = WAH9CZ8BPG;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -834,7 +834,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.6;
MARKETING_VERSION = 1.0.10;
NEW_SETTING = "";
OTHER_LDFLAGS = (
"$(inherited)",

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@ module.exports = {
'./jest/setup/blur.ts',
'./jest/setup/carplay.ts',
'./jest/setup/device-info.js', // JS to prevent Typescript implicit any warning
'./jest/setup/google-cast.ts',
'./jest/setup/reanimated.ts',
'./jest/setup/rnfs.ts',
'./jest/setup/rntp.ts',

View File

@@ -27,6 +27,14 @@ jest.mock('react-native-carplay', () => {
this.config = config
}
},
checkForConnection: jest.fn(), // if needed as a named export too
CarPlay: {
checkForConnection: jest.fn(), // if needed as a named export too
setRootTemplate: jest.fn(),
registerOnConnect: jest.fn(),
registerOnDisconnect: jest.fn(),
unregisterOnConnect: jest.fn(),
unregisterOnDisconnect: jest.fn(),
enableNowPlaying: jest.fn(),
},
}
})

18
jest/setup/google-cast.ts Normal file
View File

@@ -0,0 +1,18 @@
jest.mock('react-native-google-cast', () => ({
__esModule: true,
// Commonly accessed API
getCurrentCastSession: jest.fn(() => null),
// Hooks (very important)
useCastSession: jest.fn(() => null),
useRemoteMediaClient: jest.fn(() => null),
// Constants sometimes referenced
CastState: {
NO_DEVICES_AVAILABLE: 'NO_DEVICES_AVAILABLE',
NOT_CONNECTED: 'NOT_CONNECTED',
CONNECTING: 'CONNECTING',
CONNECTED: 'CONNECTED',
},
}))

View File

@@ -1,6 +1,6 @@
{
"name": "jellify",
"version": "1.0.6",
"version": "1.0.10",
"private": true,
"scripts": {
"init-android": "bun i",
@@ -43,18 +43,18 @@
"@react-native-community/netinfo": "^11.4.1",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-native-vector-icons/material-design-icons": "12.4.0",
"@react-navigation/bottom-tabs": "7.8.12",
"@react-navigation/material-top-tabs": "7.4.10",
"@react-navigation/native": "7.1.25",
"@react-navigation/native-stack": "7.8.6",
"@react-navigation/bottom-tabs": "7.9.0",
"@react-navigation/material-top-tabs": "7.4.11",
"@react-navigation/native": "7.1.26",
"@react-navigation/native-stack": "7.9.0",
"@sentry/react-native": "7.8.0",
"@shopify/flash-list": "2.2.0",
"@tamagui/config": "1.141.4",
"@tamagui/config": "1.141.5",
"@tanstack/query-async-storage-persister": "5.90.12",
"@tanstack/react-query": "5.90.12",
"@tanstack/react-query-persist-client": "5.90.12",
"@testing-library/react-native": "13.3.3",
"@typedigital/telemetrydeck-react": "^0.4.1",
"@typedigital/telemetrydeck-react": "0.4.1",
"axios": "1.13.2",
"bundle": "^2.1.0",
"dlx": "^0.2.1",
@@ -62,14 +62,13 @@
"lodash": "^4.17.21",
"openai": "5.21.0",
"react": "19.2.0",
"react-native": "0.83.0",
"react-native": "0.83.1",
"react-native-background-actions": "^4.0.1",
"react-native-blob-util": "^0.22.2",
"react-native-blurhash": "^2.1.3",
"react-native-carplay": "^2.4.1-beta.0",
"react-native-config": "1.5.6",
"react-native-device-info": "15.0.1",
"react-native-dns-lookup": "^1.0.6",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "2.29.1",
"react-native-google-cast": "^4.9.1",
@@ -77,9 +76,9 @@
"react-native-linear-gradient": "^2.8.3",
"react-native-mmkv": "^4.1.0",
"react-native-nitro-fetch": "^0.1.6",
"react-native-nitro-modules": "0.31.10",
"react-native-nitro-ota": "0.8.2",
"react-native-pager-view": "^7.0.2",
"react-native-nitro-modules": "0.32.0-beta.0",
"react-native-nitro-ota": "0.9.0",
"react-native-pager-view": "8.0.0",
"react-native-reanimated": "4.1.6",
"react-native-safe-area-context": "5.6.2",
"react-native-screens": "4.19.0",
@@ -87,30 +86,31 @@
"react-native-text-ticker": "^1.15.0",
"react-native-toast-message": "^2.3.3",
"react-native-track-player": "5.0.0-alpha0",
"react-native-turbo-image": "^1.23.1",
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-worklets": "^0.7.1",
"react-native-worklets-core": "^1.6.2",
"ruby": "^0.6.1",
"scheduler": "^0.26.0",
"tamagui": "1.141.4",
"tamagui": "1.141.5",
"zustand": "5.0.9"
},
"devDependencies": {
"@babel/core": "7.28.5",
"@babel/preset-env": "7.28.5",
"@babel/runtime": "7.28.4",
"@eslint/eslintrc": "^3.3.1",
"@eslint/eslintrc": "3.3.3",
"@eslint/js": "9.39.2",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.83.0",
"@react-native/eslint-config": "0.83.0",
"@react-native/metro-config": "0.83.0",
"@react-native/typescript-config": "0.83.0",
"@react-native/babel-preset": "0.83.1",
"@react-native/eslint-config": "0.83.1",
"@react-native/metro-config": "0.83.1",
"@react-native/typescript-config": "0.83.1",
"@types/jest": "^30.0.0",
"@types/lodash": "^4.17.21",
"@types/node": "^24.2.1",
"@types/node": "25.0.3",
"@types/react": "19.2.0",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "19.1.0",
@@ -122,7 +122,7 @@
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-native": "^5.0.0",
"globals": "^16.3.0",
"globals": "16.5.0",
"husky": "^9.1.7",
"jest": "30.2.0",
"jscodeshift": "^17.3.0",

View File

@@ -1,13 +0,0 @@
diff --git a/node_modules/react-native-dns-lookup/android/build.gradle b/node_modules/react-native-dns-lookup/android/build.gradle
index 013caca..fbb43c9 100644
--- a/node_modules/react-native-dns-lookup/android/build.gradle
+++ b/node_modules/react-native-dns-lookup/android/build.gradle
@@ -6,7 +6,7 @@ def safeExtGet(prop, fallback) {
buildscript {
repositories {
- jcenter()
+ mavenCentral()
}
dependencies {

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1005 KiB

After

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 875 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 KiB

After

Width:  |  Height:  |  Size: 696 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 KiB

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 622 KiB

After

Width:  |  Height:  |  Size: 600 KiB

View File

@@ -1,5 +1,5 @@
import { Jellyfin } from '@jellyfin/sdk'
import { getModel, getUniqueIdSync } from 'react-native-device-info'
import { getDeviceNameSync, getUniqueIdSync } from 'react-native-device-info'
import { name, version } from '../../package.json'
import { capitalize } from 'lodash'
@@ -12,7 +12,7 @@ export const JellyfinInfo: Jellyfin = new Jellyfin({
version: version,
},
deviceInfo: {
name: getModel(),
name: getDeviceNameSync(),
id: getUniqueIdSync(),
},
})

View File

@@ -3,7 +3,6 @@ import { getSystemApi } from '@jellyfin/sdk/lib/utils/api'
import { Jellyfin } from '@jellyfin/sdk/lib/jellyfin'
import { JellyfinInfo } from '../../../info'
import { PublicSystemInfo } from '@jellyfin/sdk/lib/generated-client/models'
import { getIpAddressesForHostname } from 'react-native-dns-lookup'
import { Api } from '@jellyfin/sdk'
import HTTPS, { HTTP } from '../../../../constants/protocols'
@@ -35,26 +34,9 @@ export function connectToServer(
`${serverAddressContainsProtocol ? '' : useHttps ? HTTPS : HTTP}${serverAddress}`,
)
const connectViaIpAddress = () => {
return getIpAddressesForHostname(serverAddress.split(':')[0])
.then((ipAddress) => {
const ipAddressApi = jellyfin.createApi(
`${serverAddressContainsProtocol ? '' : useHttps ? HTTPS : HTTP}${ipAddress[0]}:${serverAddress.split(':')[1]}`,
)
return connect(ipAddressApi, `ipAddress`)
})
.catch(() => {
throw new Error(`Unable to lookup IP Addresses for Hostname`)
})
}
return connect(hostnameApi, 'hostname')
.then((response) => resolve(response))
.catch(() =>
connectViaIpAddress()
.then((response) => resolve(response))
.catch(reject),
)
.catch(reject)
})
}

View File

@@ -0,0 +1,18 @@
import { useApi, useJellifyUser } from '../../../../src/stores'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
import InstantMixQueryKey from './keys'
import { useQuery } from '@tanstack/react-query'
import { fetchInstantMixFromItem } from './utils'
const useInstantMix = (item: BaseItemDto) => {
const api = useApi()
const [user] = useJellifyUser()
return useQuery({
queryKey: InstantMixQueryKey(item),
queryFn: () => fetchInstantMixFromItem(api, user, item),
})
}
export default useInstantMix

View File

@@ -0,0 +1,9 @@
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
enum InstantMixQueryKeys {
InstantMix = 'INSTANT_MIX',
}
const InstantMixQueryKey = ({ Id }: BaseItemDto) => [InstantMixQueryKeys.InstantMix, Id]
export default InstantMixQueryKey

View File

@@ -1,9 +1,9 @@
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { getInstantMixApi } from '@jellyfin/sdk/lib/utils/api'
import { isUndefined } from 'lodash'
import QueryConfig from '../../configs/query.config'
import QueryConfig from '../../../../configs/query.config'
import { Api } from '@jellyfin/sdk'
import { JellifyUser } from '../../types/JellifyUser'
import { JellifyUser } from '../../../../types/JellifyUser'
/**
* Fetches an instant mix for a given item
* @param api The Jellyfin {@link Api} instance

View File

@@ -7,7 +7,7 @@ import { fetchMediaInfo } from './utils'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
import MediaInfoQueryKey from './keys'
import { useApi } from '../../../stores'
import { ONE_DAY } from '../../../constants/query-client'
import { ONE_DAY, ONE_HOUR } from '../../../constants/query-client'
/**
* A React hook that will retrieve the latest media info
@@ -32,8 +32,8 @@ const useStreamedMediaInfo = (itemId: string | null | undefined) => {
queryKey: MediaInfoQueryKey({ api, deviceProfile, itemId }),
queryFn: () => fetchMediaInfo(api, deviceProfile, itemId),
enabled: Boolean(api && deviceProfile && itemId),
staleTime: ONE_DAY, // Only refetch when the user's device profile changes
gcTime: ONE_DAY,
staleTime: Infinity, // Only refetch when the user's device profile changes
gcTime: Infinity,
})
}
@@ -62,7 +62,7 @@ export const useDownloadedMediaInfo = (itemId: string | null | undefined) => {
queryKey: MediaInfoQueryKey({ api, deviceProfile, itemId }),
queryFn: () => fetchMediaInfo(api, deviceProfile, itemId),
enabled: Boolean(api && deviceProfile && itemId),
staleTime: ONE_DAY, // Only refetch when the user's device profile changes
gcTime: ONE_DAY,
staleTime: ONE_HOUR * 6, // Only refetch when the user's device profile changes
gcTime: ONE_HOUR * 6,
})
}

View File

@@ -133,7 +133,12 @@ export async function fetchPlaylistTracks(
Recursive: false,
Limit: ApiLimits.Library,
StartIndex: pageParam * ApiLimits.Library,
Fields: [ItemFields.MediaSources, ItemFields.ParentId, ItemFields.Path],
Fields: [
ItemFields.MediaSources,
ItemFields.ParentId,
ItemFields.Path,
ItemFields.SortName,
],
},
)

View File

@@ -3,6 +3,8 @@ import { SuggestionQueryKeys } from './keys'
import { fetchArtistSuggestions, fetchSearchSuggestions } from './utils/suggestions'
import { useApi, useJellifyLibrary, useJellifyUser } from '../../../stores'
import { isUndefined } from 'lodash'
import fetchSimilarArtists, { fetchSimilarItems } from './utils/similar'
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client'
export const useSearchSuggestions = () => {
const api = useApi()
@@ -40,3 +42,20 @@ export const useDiscoverArtists = () => {
maxPages: 2,
})
}
export const useSimilarItems = (item: BaseItemDto) => {
const api = useApi()
const [library] = useJellifyLibrary()
const [user] = useJellifyUser()
return useQuery({
queryKey: [SuggestionQueryKeys.SimilarItems, library?.musicLibraryId, item.Id],
queryFn: () =>
item.Type === BaseItemKind.MusicArtist
? fetchSimilarArtists(api, user, library?.musicLibraryId, item.Id!)
: fetchSimilarItems(api, user, library?.musicLibraryId, item.Id!),
enabled: !isUndefined(library) && !isUndefined(item.Id),
})
}

View File

@@ -1,4 +1,5 @@
export enum SuggestionQueryKeys {
InfiniteArtistSuggestions,
SearchSuggestions,
SimilarItems,
}

View File

@@ -1,11 +1,11 @@
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api'
import { ApiLimits } from '../../configs/query.config'
import { ApiLimits } from '../../../../configs/query.config'
import { Api } from '@jellyfin/sdk'
import { isUndefined } from 'lodash'
import { JellifyUser } from '../../types/JellifyUser'
import { JellifyUser } from '../../../../types/JellifyUser'
export default function fetchSimilar(
export default function fetchSimilarArtists(
api: Api | undefined,
user: JellifyUser | undefined,
libraryId: string | undefined,
@@ -32,3 +32,31 @@ export default function fetchSimilar(
})
})
}
export function fetchSimilarItems(
api: Api | undefined,
user: JellifyUser | undefined,
libraryId: string | undefined,
itemId: string,
limit: number = ApiLimits.Similar,
startIndex: number = 0,
): Promise<BaseItemDto[]> {
return new Promise((resolve, reject) => {
if (isUndefined(api)) return reject('Client has not been set')
if (isUndefined(user)) return reject('User has not been set')
if (isUndefined(libraryId)) return reject('Library has not been set')
getLibraryApi(api)
.getSimilarItems({
userId: user.id,
itemId: itemId,
limit,
})
.then(({ data }) => {
resolve(data.Items ?? [])
})
.catch((error) => {
reject(error)
})
})
}

View File

@@ -0,0 +1,100 @@
import DiscoverStackParamList from '../../screens/Discover/types'
import HomeStackParamList from '../../screens/Home/types'
import LibraryStackParamList from '../../screens/Library/types'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
import { useNavigation } from '@react-navigation/native'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { FlashList } from '@shopify/flash-list'
import { YStack, Spinner, Text } from 'tamagui'
import ItemCard from '../Global/components/item-card'
import { useSimilarItems } from '../../api/queries/suggestions'
import HorizontalCardList from '../Global/components/horizontal-list'
import navigationRef from '../../../navigation'
import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import ItemRow from '../Global/components/item-row'
export default function AlbumTrackListFooter({ album }: { album: BaseItemDto }): React.JSX.Element {
const navigation =
useNavigation<
NativeStackNavigationProp<
HomeStackParamList | LibraryStackParamList | DiscoverStackParamList
>
>()
const { data: suggestions, isPending: isLoadingSuggestions } = useSimilarItems(album)
return (
<YStack gap={'$3'} marginVertical={'$2'} flex={1}>
{album.ArtistItems && album.ArtistItems.length > 1 && (
<YStack>
<Text marginHorizontal={'$2'} fontWeight='bold'>
Featuring
</Text>
<FlashList
data={album.ArtistItems}
renderItem={({ item: artist }) => (
<ItemRow
circular
item={artist}
onPress={() => {
navigation.navigate('Artist', {
artist,
})
}}
onLongPress={() => {
navigationRef.navigate('Context', { item: artist })
}}
/>
)}
/>
</YStack>
)}
{suggestions && suggestions.length > 0 && (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
style={{ flex: 1 }}
>
<Text marginHorizontal={'$2'} fontWeight='bold'>
Similar Albums
</Text>
<HorizontalCardList
data={suggestions}
renderItem={({ item: album }) => (
<ItemCard
size={'$8'}
item={album}
squared
caption={album.Name ?? 'Unknown Album'}
subCaption={album.Artists?.join(' • ')}
onPress={() => {
navigation.push('Album', {
album,
})
}}
onLongPress={() => {
navigationRef.navigate('Context', { item: album })
}}
captionAlign='left'
/>
)}
ListEmptyComponent={
<YStack alignContent='center'>
{isLoadingSuggestions ? (
<Spinner alignSelf='center' color={'$primary'} />
) : (
<Text justifyContent='center' textAlign='center'>
No similar albums found
</Text>
)}
</YStack>
}
/>
</Animated.View>
)}
</YStack>
)
}

View File

@@ -0,0 +1,170 @@
import { fetchAlbumDiscs } from '../../api/queries/item'
import { QueryKeys } from '../../enums/query-keys'
import { QueuingType } from '../../enums/queuing-type'
import { useLoadNewQueue } from '../../providers/Player/hooks/mutations'
import { BaseStackParamList } from '../../screens/types'
import { useApi } from '../../stores'
import useStreamingDeviceProfile from '../../stores/device-profile'
import { useNetworkStatus } from '../../stores/network'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
import { useNavigation } from '@react-navigation/native'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { useQuery } from '@tanstack/react-query'
import Animated, { FadeInUp, FadeOutDown, LinearTransition } from 'react-native-reanimated'
import { YStack, H5, XStack, Separator } from 'tamagui'
import Icon from '../Global/components/icon'
import ItemImage from '../Global/components/image'
import { RunTimeTicks } from '../Global/helpers/time-codes'
import Button from '../Global/helpers/button'
import { Text } from '../Global/helpers/text'
import { InstantMixButton } from '../Global/components/instant-mix-button'
/**
* Renders a header for an Album's track list
* @param album The {@link BaseItemDto} of the album to render the header for
* @param navigation The navigation object from the parent {@link Album}
* @param playAlbum The function to call to play the album
* @returns A React component
*/
export default function AlbumTrackListHeader({ album }: { album: BaseItemDto }): React.JSX.Element {
const api = useApi()
const [networkStatus] = useNetworkStatus()
const streamingDeviceProfile = useStreamingDeviceProfile()
const loadNewQueue = useLoadNewQueue()
const { data: discs, isPending } = useQuery({
queryKey: [QueryKeys.ItemTracks, album.Id],
queryFn: () => fetchAlbumDiscs(api, album),
})
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
const playAlbum = (shuffled: boolean = false) => {
if (!discs || discs.length === 0) return
const allTracks = discs.flatMap((disc) => disc.data) ?? []
if (allTracks.length === 0) return
loadNewQueue({
api,
networkStatus,
deviceProfile: streamingDeviceProfile,
track: allTracks[0],
index: 0,
tracklist: allTracks,
queue: album,
queuingType: QueuingType.FromSelection,
shuffled,
startPlayback: true,
})
}
return (
<YStack alignContent='center' flex={1} marginTop={'$4'}>
<ItemImage
item={album}
width={200}
height={200}
imageOptions={{
maxHeight: 750,
maxWidth: 750,
}}
/>
<YStack marginTop={'$2'} alignContent='center' justifyContent='center' gap={'$2'}>
<H5 lineBreakStrategyIOS='standard' textAlign='center' numberOfLines={5}>
{album.Name ?? 'Untitled Album'}
</H5>
{album.AlbumArtists && album.AlbumArtists.length > 0 && (
<Text
bold
color={'$primary'}
onPress={() =>
navigation.navigate('Artist', {
artist: album.AlbumArtists![0],
})
}
textAlign='center'
fontSize={'$5'}
paddingBottom={'$2'}
>
{album.AlbumArtists[0].Name ?? 'Untitled Artist'}
</Text>
)}
<XStack justify='center' gap={'$3'} marginBottom={'$2'}>
<YStack flex={1}>
{album.ProductionYear ? (
<Text fontVariant={['tabular-nums']} textAlign='right'>
{album.ProductionYear?.toString() ?? 'Unknown Year'}
</Text>
) : null}
</YStack>
<Separator vertical />
<RunTimeTicks props={{ flex: 1, textAlign: 'left' }}>
{album.RunTimeTicks}
</RunTimeTicks>
</XStack>
{discs && (
<XStack alignContent='center' gap={'$2'} marginHorizontal={'$2'}>
<Animated.View
style={{
flex: 2,
}}
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Button
icon={() => <Icon small name='play' color='$primary' />}
borderWidth={'$1'}
borderColor={'$primary'}
flex={1}
onPress={() => playAlbum(false)}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
animation={'bouncy'}
>
<Text bold color={'$primary'}>
Play
</Text>
</Button>
</Animated.View>
<InstantMixButton item={album} navigation={navigation} />
<Animated.View
style={{
flex: 2,
}}
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Button
icon={() => <Icon small name='shuffle' color='$primary' />}
borderWidth={'$1'}
borderColor={'$primary'}
flex={1}
onPress={() => playAlbum(true)}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
animation={'bouncy'}
>
<Text bold color={'$primary'}>
Shuffle
</Text>
</Button>
</Animated.View>
</XStack>
)}
</YStack>
</YStack>
)
}

View File

@@ -1,34 +1,25 @@
import { YStack, XStack, Separator, Spinner } from 'tamagui'
import { H5, Text } from '../Global/helpers/text'
import { Text } from '../Global/helpers/text'
import { SectionList } from 'react-native'
import { RunTimeTicks } from '../Global/helpers/time-codes'
import Track from '../Global/components/track'
import FavoriteButton from '../Global/components/favorite-button'
import { ItemCard } from '../Global/components/item-card'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import InstantMixButton from '../Global/components/instant-mix-button'
import ItemImage from '../Global/components/image'
import React, { useLayoutEffect } from 'react'
import Icon from '../Global/components/icon'
import { useNetworkStatus } from '../../stores/network'
import { useLoadNewQueue } from '../../providers/Player/hooks/mutations'
import { QueuingType } from '../../enums/queuing-type'
import { useNavigation } from '@react-navigation/native'
import HomeStackParamList from '../../screens/Home/types'
import LibraryStackParamList from '../../screens/Library/types'
import DiscoverStackParamList from '../../screens/Discover/types'
import { BaseStackParamList } from '../../screens/types'
import useStreamingDeviceProfile from '../../stores/device-profile'
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
import { useApi } from '../../stores'
import { QueryKeys } from '../../enums/query-keys'
import { fetchAlbumDiscs } from '../../api/queries/item'
import { useQuery } from '@tanstack/react-query'
import useAddToPendingDownloads, { usePendingDownloads } from '../../stores/network/downloads'
import Button from '../Global/helpers/button'
import Animated, { FadeInUp, FadeOutDown, LinearTransition } from 'react-native-reanimated'
import { FlashList } from '@shopify/flash-list'
import useAddToPendingDownloads, { useIsDownloading } from '../../stores/network/downloads'
import { useIsDownloaded } from '../../api/queries/download'
import AlbumTrackListFooter from './footer'
import AlbumTrackListHeader from './header'
import Animated, { FadeIn, FadeOutDown, LinearTransition } from 'react-native-reanimated'
import { useStorageContext } from '../../providers/Storage'
/**
* The screen for an Album's track list
@@ -41,18 +32,6 @@ import { FlashList } from '@shopify/flash-list'
export function Album({ album }: { album: BaseItemDto }): React.JSX.Element {
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<XStack gap={'$2'} justifyContent='center' alignContent='center'>
<FavoriteButton item={album} />
<InstantMixButton item={album} navigation={navigation} />
</XStack>
),
})
})
const api = useApi()
const { data: discs, isPending } = useQuery({
@@ -60,12 +39,12 @@ export function Album({ album }: { album: BaseItemDto }): React.JSX.Element {
queryFn: () => fetchAlbumDiscs(api, album),
})
const isDownloaded = useIsDownloaded(
discs?.flatMap(({ data }) => data).map(({ Id }) => Id) ?? [],
)
const addToDownloadQueue = useAddToPendingDownloads()
const pendingDownloads = usePendingDownloads()
const downloadAlbum = (item: BaseItemDto[]) => addToDownloadQueue(item)
const sections = (Array.isArray(discs) ? discs : []).map(({ title, data }) => ({
title,
data: Array.isArray(data) ? data : [],
@@ -75,11 +54,66 @@ export function Album({ album }: { album: BaseItemDto }): React.JSX.Element {
const albumTrackList = discs?.flatMap((disc) => disc.data)
const albumDownloadPending = useIsDownloading(albumTrackList ?? [])
const { deleteDownloads } = useStorageContext()
const handleDeleteDownload = () => deleteDownloads(albumTrackList?.map(({ Id }) => Id!) ?? [])
const handleDownload = () => addToDownloadQueue(albumTrackList ?? [])
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<XStack gap={'$2'} justifyContent='center' alignContent='center'>
{albumTrackList &&
(isDownloaded ? (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Icon
color='$warning'
name='broom'
onPress={handleDeleteDownload}
/>
</Animated.View>
) : albumDownloadPending ? (
<Spinner justifyContent='center' color={'$neutral'} />
) : (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Icon
color='$success'
name='download-circle-outline'
onPress={handleDownload}
/>
</Animated.View>
))}
<FavoriteButton item={album} />
</XStack>
),
})
}, [
album,
navigation,
isDownloaded,
handleDeleteDownload,
handleDownload,
albumDownloadPending,
])
return (
<SectionList
contentInsetAdjustmentBehavior='automatic'
contentContainerStyle={{ flexGrow: 1 }}
sections={sections}
keyExtractor={(item, index) => item.Id! + index}
maxToRenderPerBatch={50}
ItemSeparatorComponent={Separator}
renderSectionHeader={({ section }) => {
return !isPending && hasMultipleSections ? (
@@ -91,16 +125,6 @@ export function Album({ album }: { album: BaseItemDto }): React.JSX.Element {
paddingHorizontal={'$2'}
>
<Text padding={'$2'} bold>{`Disc ${section.title}`}</Text>
<Icon
name={pendingDownloads.length ? 'progress-download' : 'download'}
small
onPress={() => {
if (pendingDownloads.length) {
return
}
downloadAlbum(section.data)
}}
/>
</XStack>
) : null
}}
@@ -128,175 +152,3 @@ export function Album({ album }: { album: BaseItemDto }): React.JSX.Element {
/>
)
}
/**
* Renders a header for an Album's track list
* @param album The {@link BaseItemDto} of the album to render the header for
* @param navigation The navigation object from the parent {@link Album}
* @param playAlbum The function to call to play the album
* @returns A React component
*/
function AlbumTrackListHeader({ album }: { album: BaseItemDto }): React.JSX.Element {
const api = useApi()
const [networkStatus] = useNetworkStatus()
const streamingDeviceProfile = useStreamingDeviceProfile()
const loadNewQueue = useLoadNewQueue()
const { data: discs, isPending } = useQuery({
queryKey: [QueryKeys.ItemTracks, album.Id],
queryFn: () => fetchAlbumDiscs(api, album),
})
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
const playAlbum = (shuffled: boolean = false) => {
if (!discs || discs.length === 0) return
const allTracks = discs.flatMap((disc) => disc.data) ?? []
if (allTracks.length === 0) return
loadNewQueue({
api,
networkStatus,
deviceProfile: streamingDeviceProfile,
track: allTracks[0],
index: 0,
tracklist: allTracks,
queue: album,
queuingType: QueuingType.FromSelection,
shuffled,
startPlayback: true,
})
}
return (
<YStack alignContent='center' flex={1} marginTop={'$4'}>
<ItemImage
item={album}
width={'$20'}
height={'$20'}
imageOptions={{
maxHeight: 500,
maxWidth: 500,
}}
/>
<YStack marginTop={'$2'} alignContent='center' justifyContent='center' gap={'$2'}>
<H5 lineBreakStrategyIOS='standard' textAlign='center' numberOfLines={5}>
{album.Name ?? 'Untitled Album'}
</H5>
{album.AlbumArtists && (
<Text
bold
color={'$primary'}
onPress={() =>
navigation.navigate('Artist', {
artist: album.AlbumArtists![0],
})
}
textAlign='center'
fontSize={'$5'}
paddingBottom={'$2'}
>
{album.AlbumArtists![0].Name ?? 'Untitled Artist'}
</Text>
)}
<XStack justify='center' gap={'$3'} marginBottom={'$2'}>
<YStack flex={1}>
{album.ProductionYear ? (
<Text fontVariant={['tabular-nums']} textAlign='right'>
{album.ProductionYear?.toString() ?? 'Unknown Year'}
</Text>
) : null}
</YStack>
<Separator vertical />
<RunTimeTicks props={{ flex: 1, textAlign: 'left' }}>
{album.RunTimeTicks}
</RunTimeTicks>
</XStack>
{discs && (
<Animated.View
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<XStack alignContent='center' gap={'$2'} marginHorizontal={'$2'}>
<Button
icon={() => <Icon small name='play' color='$primary' />}
borderWidth={'$1'}
borderColor={'$primary'}
flex={1}
onPress={() => playAlbum(false)}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
animation={'bouncy'}
>
<Text bold color={'$primary'}>
Play
</Text>
</Button>
<Button
icon={() => <Icon small name='shuffle' color='$primary' />}
borderWidth={'$1'}
borderColor={'$primary'}
flex={1}
onPress={() => playAlbum(true)}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
animation={'bouncy'}
>
<Text bold color={'$primary'}>
Shuffle
</Text>
</Button>
</XStack>
</Animated.View>
)}
</YStack>
</YStack>
)
}
function AlbumTrackListFooter({ album }: { album: BaseItemDto }): React.JSX.Element {
const navigation =
useNavigation<
NativeStackNavigationProp<
HomeStackParamList | LibraryStackParamList | DiscoverStackParamList
>
>()
return (
<YStack marginLeft={'$2'}>
{album.ArtistItems && album.ArtistItems.length > 1 && (
<>
<H5>Featuring</H5>
<FlashList
data={album.ArtistItems}
horizontal
renderItem={({ item: artist }) => (
<ItemCard
size={'$8'}
item={artist}
caption={artist.Name ?? 'Unknown Artist'}
onPress={() => {
navigation.navigate('Artist', {
artist,
})
}}
/>
)}
/>
</>
)}
</YStack>
)
}

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo } from 'react'
import React from 'react'
import { useArtistContext } from '../../providers/Artist'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { BaseStackParamList } from '@/src/screens/types'
@@ -16,43 +16,42 @@ export default function ArtistOverviewTab({
}): React.JSX.Element {
const { featuredOn, artist, albums } = useArtistContext()
const sections: SectionListData<BaseItemDto>[] = useMemo(() => {
return [
{
title: 'Albums',
data: albums?.filter(({ ChildCount }) => (ChildCount ?? 0) > 6) ?? [],
},
{
title: 'EPs',
data:
albums?.filter(
({ ChildCount }) => (ChildCount ?? 0) <= 6 && (ChildCount ?? 0) >= 3,
) ?? [],
},
{
title: 'Singles',
data: albums?.filter(({ ChildCount }) => (ChildCount ?? 0) === 1) ?? [],
},
{
title: '',
data: albums?.filter(({ ChildCount }) => typeof ChildCount !== 'number') ?? [],
},
{
title: 'Featured On',
data: featuredOn ?? [],
},
]
}, [artist, albums?.map(({ Id }) => Id)])
const sections: SectionListData<BaseItemDto>[] = [
{
title: 'Albums',
data: albums?.filter(({ ChildCount }) => (ChildCount ?? 0) > 6) ?? [],
},
{
title: 'EPs',
data:
albums?.filter(
({ ChildCount }) => (ChildCount ?? 0) <= 6 && (ChildCount ?? 0) >= 3,
) ?? [],
},
{
title: 'Singles',
data: albums?.filter(({ ChildCount }) => (ChildCount ?? 0) === 1) ?? [],
},
{
title: '',
data: albums?.filter(({ ChildCount }) => typeof ChildCount !== 'number') ?? [],
},
{
title: 'Featured On',
data: featuredOn ?? [],
},
]
const renderSectionHeader = useCallback(
({ section }: { section: SectionListData<BaseItemDto, DefaultSectionT> }) =>
section.data.length > 0 ? (
<Text padding={'$3'} fontSize={'$6'} bold backgroundColor={'$background'}>
{section.title}
</Text>
) : null,
[],
)
const renderSectionHeader = ({
section,
}: {
section: SectionListData<BaseItemDto, DefaultSectionT>
}) =>
section.data.length > 0 ? (
<Text padding={'$2'} fontSize={'$6'} bold backgroundColor={'$background'}>
{section.title}
</Text>
) : null
return (
<SectionList

View File

@@ -1,94 +0,0 @@
import { MaterialTopTabBarProps } from '@react-navigation/material-top-tabs'
import React from 'react'
import { Square, XStack, YStack } from 'tamagui'
import Icon from '../Global/components/icon'
import { Text } from '../Global/helpers/text'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import useHapticFeedback from '../../hooks/use-haptic-feedback'
import { ItemSortBy, SortOrder } from '@jellyfin/sdk/lib/generated-client'
import { MaterialTopTabBar } from '@react-navigation/material-top-tabs'
interface ArtistTabBarProps extends MaterialTopTabBarProps {
isFavorites: boolean
setIsFavorites: (isFavorites: boolean) => void
sortBy: ItemSortBy
setSortBy: (sortBy: ItemSortBy) => void
sortOrder: SortOrder
setSortOrder: (sortOrder: SortOrder) => void
}
export default function ArtistTabBar({
isFavorites,
setIsFavorites,
sortBy,
setSortBy,
sortOrder,
setSortOrder,
...props
}: ArtistTabBarProps) {
const trigger = useHapticFeedback()
const insets = useSafeAreaInsets()
return (
<YStack>
<MaterialTopTabBar {...props} />
{props.state.routes[props.state.index].name === 'Tracks' && (
<XStack
borderColor={'$borderColor'}
alignContent={'flex-start'}
justifyContent='flex-start'
paddingHorizontal={'$1'}
paddingVertical={'$2'}
gap={'$2'}
maxWidth={'80%'}
>
<XStack
onPress={() => {
trigger('impactLight')
setIsFavorites(!isFavorites)
}}
alignItems={'center'}
justifyContent={'center'}
>
<Icon
name={isFavorites ? 'heart' : 'heart-outline'}
color={isFavorites ? '$primary' : '$borderColor'}
/>
<Text color={isFavorites ? '$primary' : '$borderColor'}>
{isFavorites ? 'Favorites' : 'All'}
</Text>
</XStack>
<XStack
onPress={() => {
trigger('impactLight')
if (sortBy === ItemSortBy.DateCreated) {
setSortBy(ItemSortBy.SortName)
setSortOrder(SortOrder.Ascending)
} else {
setSortBy(ItemSortBy.DateCreated)
setSortOrder(SortOrder.Descending)
}
}}
alignItems={'center'}
justifyContent={'center'}
>
<Icon
name={
sortBy === ItemSortBy.DateCreated
? 'calendar'
: 'sort-alphabetical-ascending'
}
color={'$borderColor'}
/>{' '}
<Text color={'$borderColor'}>
{sortBy === ItemSortBy.DateCreated ? 'Date Added' : 'A-Z'}
</Text>
</XStack>
</XStack>
)}
</YStack>
)
}

View File

@@ -1,11 +1,11 @@
import { ImageType } from '@jellyfin/sdk/lib/generated-client'
import { XStack, YStack } from 'tamagui'
import { Text, XStack, YStack } from 'tamagui'
import ItemImage from '../Global/components/image'
import { useSafeAreaFrame } from 'react-native-safe-area-context'
import { H5 } from '../Global/helpers/text'
import { useArtistContext } from '../../providers/Artist'
import FavoriteButton from '../Global/components/favorite-button'
import InstantMixButton from '../Global/components/instant-mix-button'
import { InstantMixIconButton } from '../Global/components/instant-mix-button'
import { useNavigation } from '@react-navigation/native'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { BaseStackParamList } from '@/src/screens/types'
@@ -16,6 +16,8 @@ import { QueuingType } from '../../enums/queuing-type'
import useStreamingDeviceProfile from '../../stores/device-profile'
import { useNetworkStatus } from '../../stores/network'
import { useApi } from '../../stores'
import Icon from '../Global/components/icon'
import useTracks from '../../api/queries/track'
export default function ArtistHeader(): React.JSX.Element {
const { width } = useSafeAreaFrame()
@@ -62,6 +64,8 @@ export default function ArtistHeader(): React.JSX.Element {
}
}
const [trackPageParams, tracksInfiniteQuery] = useTracks(artist.Id)
return (
<YStack flex={1}>
<ItemImage
@@ -73,7 +77,7 @@ export default function ArtistHeader(): React.JSX.Element {
imageOptions={{ maxWidth: width * 2, maxHeight: 640 }}
/>
<YStack alignItems='center' paddingHorizontal={'$3'}>
<YStack paddingHorizontal={'$2'}>
<XStack alignItems='flex-end' justifyContent='flex-start' flex={1}>
<XStack alignItems='center' flex={1} justifyContent='space-between'>
<H5 flexGrow={1} fontWeight={'bold'}>
@@ -86,14 +90,35 @@ export default function ArtistHeader(): React.JSX.Element {
<XStack alignItems='center' gap={'$3'} flex={1}>
<FavoriteButton item={artist} />
<InstantMixButton item={artist} navigation={navigation} />
<InstantMixIconButton item={artist} navigation={navigation} />
</XStack>
<XStack alignItems='center' justifyContent='flex-end' gap={'$3'} flex={1}>
{/* <Icon name='shuffle' onPress={() => playArtist(true)} /> */}
<IconButton circular name='play' onPress={playArtist} />
<Icon
small
color='$primary'
name='shuffle'
onPress={() => playArtist(true)}
/>
<IconButton circular name='play' onPress={() => playArtist(false)} />
</XStack>
</XStack>
<XStack
alignItems='center'
flex={1}
justifyContent='flex-start'
marginVertical={'$2'}
onPress={() =>
navigation.push('Tracks', {
tracksInfiniteQuery,
})
}
>
<Text fontWeight={'bold'} fontSize={'$4'}>{`View Tracks`}</Text>
<Icon name='chevron-right' small />
</XStack>
</YStack>
</YStack>
)

View File

@@ -1,71 +1,12 @@
import React, { useState } from 'react'
import React from 'react'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { BaseStackParamList } from '@/src/screens/types'
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'
import ArtistOverviewTab from './OverviewTab'
import ArtistTracksTab from './TracksTab'
import ArtistTabBar from './TabBar'
import { ItemSortBy, SortOrder } from '@jellyfin/sdk/lib/generated-client'
import { getTokenValue, useTheme } from 'tamagui'
const Tab = createMaterialTopTabNavigator()
export default function ArtistNavigation({
navigation,
}: {
navigation: NativeStackNavigationProp<BaseStackParamList>
}): React.JSX.Element {
const [isFavorites, setIsFavorites] = useState(false)
const [sortBy, setSortBy] = useState<ItemSortBy>(ItemSortBy.SortName)
const [sortOrder, setSortOrder] = useState<SortOrder>(SortOrder.Ascending)
const theme = useTheme()
return (
<Tab.Navigator
tabBar={(props) => (
<ArtistTabBar
{...props}
isFavorites={isFavorites}
setIsFavorites={setIsFavorites}
sortBy={sortBy}
setSortBy={setSortBy}
sortOrder={sortOrder}
setSortOrder={setSortOrder}
/>
)}
screenOptions={{
swipeEnabled: false,
tabBarIndicatorStyle: {
borderColor: theme.background.val,
borderBottomWidth: getTokenValue('$2'),
},
tabBarActiveTintColor: theme.background.val,
tabBarInactiveTintColor: theme.background50.val,
tabBarStyle: {
backgroundColor: theme.primary.val,
},
tabBarLabelStyle: {
fontSize: 16,
fontFamily: 'Figtree-Bold',
},
tabBarPressOpacity: 0.5,
lazy: true, // Enable lazy loading to prevent all tabs from mounting simultaneously
}}
>
<Tab.Screen name='Overview'>
{() => <ArtistOverviewTab navigation={navigation} />}
</Tab.Screen>
<Tab.Screen name='Tracks'>
{() => (
<ArtistTracksTab
navigation={navigation}
isFavorites={isFavorites}
sortBy={sortBy}
sortOrder={sortOrder}
/>
)}
</Tab.Screen>
</Tab.Navigator>
)
return <ArtistOverviewTab navigation={navigation} />
}

View File

@@ -15,7 +15,7 @@ export default function SimilarArtists(): React.JSX.Element {
return (
<YStack flex={1}>
<Text
margin={'$3'}
margin={'$2'}
fontSize={'$6'}
bold
>{`Similar to ${artist.Name ?? 'Unknown Artist'}`}</Text>

View File

@@ -6,7 +6,7 @@ import SuggestedArtists from './helpers/suggested-artists'
import useDiscoverQueries from '../../api/mutations/discover'
import { useIsRestoring } from '@tanstack/react-query'
import { useRecentlyAddedAlbums } from '../../api/queries/album'
import { RefreshControl } from 'react-native'
import { Platform, RefreshControl } from 'react-native'
export default function Index(): React.JSX.Element {
const { mutateAsync: refreshAsync, isPending: refreshing } = useDiscoverQueries()
@@ -20,9 +20,7 @@ export default function Index(): React.JSX.Element {
return (
<ScrollView
contentContainerStyle={{
flexGrow: 1,
marginTop: getToken('$4'),
marginHorizontal: getToken('$2'),
marginVertical: getToken('$4'),
}}
contentInsetAdjustmentBehavior='automatic'
removeClippedSubviews
@@ -41,7 +39,11 @@ export default function Index(): React.JSX.Element {
function DiscoverContent() {
return (
<YStack gap={'$3'}>
<YStack
alignContent='flex-start'
gap={'$3'}
marginBottom={Platform.OS === 'android' ? '$4' : undefined}
>
<RecentlyAdded />
<PublicPlaylists />

View File

@@ -1,6 +1,6 @@
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
import { ItemCard } from '../../../components/Global/components/item-card'
import ItemCard from '../../../components/Global/components/item-card'
import { H5, View, XStack } from 'tamagui'
import Icon from '../../Global/components/icon'
import { useNavigation } from '@react-navigation/native'

View File

@@ -2,7 +2,7 @@ import { H5, XStack } from 'tamagui'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import Icon from '../../Global/components/icon'
import HorizontalCardList from '../../Global/components/horizontal-list'
import { ItemCard } from '../../Global/components/item-card'
import ItemCard from '../../Global/components/item-card'
import { useSafeAreaFrame } from 'react-native-safe-area-context'
import { useNavigation } from '@react-navigation/native'
import DiscoverStackParamList from '../../../screens/Discover/types'

View File

@@ -1,7 +1,7 @@
import { H5, View, XStack } from 'tamagui'
import Icon from '../../Global/components/icon'
import HorizontalCardList from '../../Global/components/horizontal-list'
import { ItemCard } from '../../Global/components/item-card'
import ItemCard from '../../Global/components/item-card'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { useNavigation } from '@react-navigation/native'
import DiscoverStackParamList from '../../../screens/Discover/types'

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { XStack, YStack, getToken } from 'tamagui'
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import Animated, {
@@ -101,23 +101,23 @@ export default function SwipeableRow({
idRef.current = `swipeable-row-${Math.random().toString(36).slice(2)}`
}
const syncClosedState = useCallback(() => {
const syncClosedState = () => {
setIsMenuOpen(false)
menuOpenSV.value = false
notifySwipeableRowClosed(idRef.current!)
}, [menuOpenSV])
}
const close = useCallback(() => {
const close = () => {
syncClosedState()
cancelAnimation(tx)
tx.value = withTiming(0, { duration: 160, easing: Easing.out(Easing.cubic) })
}, [syncClosedState, tx])
}
const openMenu = useCallback(() => {
const openMenu = () => {
setIsMenuOpen(true)
menuOpenSV.value = true
notifySwipeableRowOpened(idRef.current!)
}, [menuOpenSV])
}
useEffect(() => {
registerSwipeableRow(idRef.current!, close)
@@ -130,215 +130,200 @@ export default function SwipeableRow({
const fgOpacity = useSharedValue(1.0)
const tapGesture = useMemo(() => {
// Reserve the right edge for per-row controls (e.g. three dots) by shrinking the tap area there
// so those controls can receive presses without being swallowed by the row tap gesture.
return Gesture.Tap()
.runOnJS(true)
.hitSlop({ right: -64 })
.maxDistance(2)
.onBegin(() => {
fgOpacity.set(0.5)
})
.onEnd((e, success) => {
// If a quick-action menu is open, row-level tap should NOT trigger onPress.
if (!isMenuOpen && onPress && success) {
/**
* Reserve the right edge for per-row controls (e.g. three dots) by shrinking the tap area there
* so those controls can receive presses without being swallowed by the row tap gesture.
*/
const tapGesture = Gesture.Tap()
.runOnJS(true)
.hitSlop({ right: -64 })
.maxDistance(2)
.onBegin(() => {
fgOpacity.set(0.5)
})
.onEnd((e, success) => {
// If a quick-action menu is open, row-level tap should NOT trigger onPress.
if (!isMenuOpen && onPress && success) {
triggerHaptic('impactLight')
onPress()
}
})
.onFinalize(() => {
fgOpacity.set(1.0)
})
const longPressGesture = Gesture.LongPress()
.runOnJS(true)
.onBegin(() => {
fgOpacity.set(0.5)
})
.onStart(() => {
if (onLongPress) {
triggerHaptic('effectDoubleClick')
onLongPress()
}
fgOpacity.set(1.0)
})
.onTouchesCancelled(() => {
fgOpacity.set(1.0)
})
const panGesture = Gesture.Pan()
.runOnJS(true)
.hitSlop({
/**
* Preserve Swipe to go back system gestures
*
* This was a value I saw ComputerJazz recommend in an issue on
* `react-native-draggable-flatlist`, figured it could serve as a good
* basis to start from and tune from there ~Vi
*
* {@link https://github.com/computerjazz}
* {@link https://github.com/computerjazz/react-native-draggable-flatlist/issues/336#issuecomment-970573916}
*/
left: -50,
})
.activeOffsetX([-15, 15])
.failOffsetY([-8, 8])
.onBegin(() => {
if (disabled) return
dragging.set(true)
fgOpacity.set(1.0)
})
.onUpdate((e) => {
if (disabled) return
const next = Math.max(Math.min(e.translationX, maxLeft), maxRight)
tx.value = next
})
.onEnd((e) => {
if (disabled) return
// Velocity-based assistance: fast flicks open even if displacement below threshold
const v = e.velocityX
const velocityTrigger = 800
if (tx.value > threshold) {
// Right swipe: show left quick actions if provided; otherwise trigger leftAction
if (leftActions && leftActions.length > 0) {
triggerHaptic('impactLight')
onPress()
// Snap open to expose quick actions, do not auto-trigger
cancelAnimation(tx)
tx.value = withTiming(maxLeft, {
duration: 140,
easing: Easing.out(Easing.cubic),
})
openMenu()
return
} else if (leftAction) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(
maxLeft,
{ duration: 140, easing: Easing.out(Easing.cubic) },
() => {
scheduleOnRN(leftAction.onTrigger)
cancelAnimation(tx)
tx.value = withTiming(0, {
duration: 160,
easing: Easing.out(Easing.cubic),
})
},
)
return
}
})
.onFinalize(() => {
fgOpacity.set(1.0)
})
}, [onPress, isMenuOpen])
const longPressGesture = useMemo(() => {
return Gesture.LongPress()
.runOnJS(true)
.onBegin(() => {
fgOpacity.set(0.5)
})
.onStart(() => {
if (onLongPress) {
triggerHaptic('effectDoubleClick')
onLongPress()
}
// Left swipe (quick actions)
if (tx.value < -Math.min(threshold, Math.abs(maxRight) / 2)) {
if (rightActions && rightActions.length > 0) {
triggerHaptic('impactLight')
// Snap open to expose quick actions, do not auto-trigger
cancelAnimation(tx)
tx.value = withTiming(maxRight, {
duration: 140,
easing: Easing.out(Easing.cubic),
})
openMenu()
return
} else if (rightAction) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(
maxRight,
{ duration: 140, easing: Easing.out(Easing.cubic) },
() => {
scheduleOnRN(rightAction.onTrigger)
cancelAnimation(tx)
tx.value = withTiming(0, {
duration: 160,
easing: Easing.out(Easing.cubic),
})
},
)
return
}
fgOpacity.set(1.0)
})
.onTouchesCancelled(() => {
fgOpacity.set(1.0)
})
}, [onLongPress])
const panGesture = useMemo(() => {
return Gesture.Pan()
.runOnJS(true)
.hitSlop({
/**
* Preserve Swipe to go back system gestures
*
* This was a value I saw ComputerJazz recommend in an issue on
* `react-native-draggable-flatlist`, figured it could serve as a good
* basis to start from and tune from there ~Vi
*
* {@link https://github.com/computerjazz}
* {@link https://github.com/computerjazz/react-native-draggable-flatlist/issues/336#issuecomment-970573916}
*/
left: -50,
})
.activeOffsetX([-15, 15])
.failOffsetY([-8, 8])
.onBegin(() => {
if (disabled) return
dragging.set(true)
fgOpacity.set(1.0)
})
.onUpdate((e) => {
if (disabled) return
const next = Math.max(Math.min(e.translationX, maxLeft), maxRight)
tx.value = next
})
.onEnd((e) => {
if (disabled) return
// Velocity-based assistance: fast flicks open even if displacement below threshold
const v = e.velocityX
const velocityTrigger = 800
if (tx.value > threshold) {
// Right swipe: show left quick actions if provided; otherwise trigger leftAction
if (leftActions && leftActions.length > 0) {
triggerHaptic('impactLight')
// Snap open to expose quick actions, do not auto-trigger
cancelAnimation(tx)
tx.value = withTiming(maxLeft, {
duration: 140,
easing: Easing.out(Easing.cubic),
})
openMenu()
return
} else if (leftAction) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(
maxLeft,
{ duration: 140, easing: Easing.out(Easing.cubic) },
() => {
scheduleOnRN(leftAction.onTrigger)
cancelAnimation(tx)
tx.value = withTiming(0, {
duration: 160,
easing: Easing.out(Easing.cubic),
})
},
)
return
}
}
// Velocity fallback (open quick actions if fast flick even without full displacement)
if (v > velocityTrigger && hasLeftSide) {
if (leftActions && leftActions.length > 0) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(maxLeft, {
duration: 140,
easing: Easing.out(Easing.cubic),
})
openMenu()
return
} else if (leftAction) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(
maxLeft,
{ duration: 140, easing: Easing.out(Easing.cubic) },
() => {
scheduleOnRN(leftAction.onTrigger)
cancelAnimation(tx)
tx.value = withTiming(0, {
duration: 160,
easing: Easing.out(Easing.cubic),
})
},
)
return
}
// Left swipe (quick actions)
if (tx.value < -Math.min(threshold, Math.abs(maxRight) / 2)) {
if (rightActions && rightActions.length > 0) {
triggerHaptic('impactLight')
// Snap open to expose quick actions, do not auto-trigger
cancelAnimation(tx)
tx.value = withTiming(maxRight, {
duration: 140,
easing: Easing.out(Easing.cubic),
})
openMenu()
return
} else if (rightAction) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(
maxRight,
{ duration: 140, easing: Easing.out(Easing.cubic) },
() => {
scheduleOnRN(rightAction.onTrigger)
cancelAnimation(tx)
tx.value = withTiming(0, {
duration: 160,
easing: Easing.out(Easing.cubic),
})
},
)
return
}
}
if (v < -velocityTrigger && hasRightSide) {
if (rightActions && rightActions.length > 0) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(maxRight, {
duration: 140,
easing: Easing.out(Easing.cubic),
})
openMenu()
return
} else if (rightAction) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(
maxRight,
{ duration: 140, easing: Easing.out(Easing.cubic) },
() => {
scheduleOnRN(rightAction.onTrigger)
cancelAnimation(tx)
tx.value = withTiming(0, {
duration: 160,
easing: Easing.out(Easing.cubic),
})
},
)
return
}
// Velocity fallback (open quick actions if fast flick even without full displacement)
if (v > velocityTrigger && hasLeftSide) {
if (leftActions && leftActions.length > 0) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(maxLeft, {
duration: 140,
easing: Easing.out(Easing.cubic),
})
openMenu()
return
} else if (leftAction) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(
maxLeft,
{ duration: 140, easing: Easing.out(Easing.cubic) },
() => {
scheduleOnRN(leftAction.onTrigger)
cancelAnimation(tx)
tx.value = withTiming(0, {
duration: 160,
easing: Easing.out(Easing.cubic),
})
},
)
return
}
}
if (v < -velocityTrigger && hasRightSide) {
if (rightActions && rightActions.length > 0) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(maxRight, {
duration: 140,
easing: Easing.out(Easing.cubic),
})
openMenu()
return
} else if (rightAction) {
triggerHaptic('impactLight')
cancelAnimation(tx)
tx.value = withTiming(
maxRight,
{ duration: 140, easing: Easing.out(Easing.cubic) },
() => {
scheduleOnRN(rightAction.onTrigger)
cancelAnimation(tx)
tx.value = withTiming(0, {
duration: 160,
easing: Easing.out(Easing.cubic),
})
},
)
return
}
}
tx.value = withTiming(0, { duration: 160, easing: Easing.out(Easing.cubic) })
syncClosedState()
})
.onFinalize(() => {
if (disabled) return
dragging.set(false)
})
}, [
disabled,
leftAction,
leftActions,
rightAction,
rightActions,
maxRight,
maxLeft,
openMenu,
syncClosedState,
triggerHaptic,
])
}
tx.value = withTiming(0, { duration: 160, easing: Easing.out(Easing.cubic) })
syncClosedState()
})
.onFinalize(() => {
if (disabled) return
dragging.set(false)
})
const fgStyle = useAnimatedStyle(() => ({
transform: [
@@ -457,8 +442,8 @@ export default function SwipeableRow({
backgroundColor={action.color}
borderRadius={0}
pressStyle={{ opacity: 0.8 }}
accessibilityRole='button'
accessibilityLabel={`Left quick action ${action.icon}`}
role='button'
aria-label={`Left quick action ${action.icon}`}
onPress={() => {
action.onPress()
close()
@@ -540,8 +525,8 @@ export default function SwipeableRow({
backgroundColor={action.color}
borderRadius={0}
pressStyle={{ opacity: 0.8 }}
accessibilityRole='button'
accessibilityLabel={`Right quick action ${action.icon}`}
role='button'
aria-label={`Right quick action ${action.icon}`}
onPress={() => {
action.onPress()
close()

View File

@@ -57,8 +57,6 @@ export default function Icon({
onPress={onPress}
onPressIn={onPressIn}
hitSlop={getTokenValue('$2.5')}
width={size + getToken('$0.5')}
height={size + getToken('$0.5')}
flex={flex}
>
<MaterialDesignIcon

View File

@@ -1,14 +1,12 @@
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { isUndefined } from 'lodash'
import { getTokenValue, Token, Image as TamaguiImage, ZStack } from 'tamagui'
import { getTokenValue, Square, Token } from 'tamagui'
import { StyleSheet } from 'react-native'
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models'
import { Blurhash } from 'react-native-blurhash'
import { getBlurhashFromDto } from '../../../utils/blurhash'
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
import { getItemImageUrl, ImageUrlOptions } from '../../../api/queries/image/utils'
import { useCallback, useState } from 'react'
import { useApi } from '../../../stores'
import TurboImage from 'react-native-turbo-image'
interface ItemImageProps {
item: BaseItemDto
@@ -36,95 +34,23 @@ function ItemImage({
const imageUrl = getItemImageUrl(api, item, type, imageOptions)
return imageUrl ? (
<Image
item={item}
type={type}
imageUrl={imageUrl!}
testID={testID}
height={height}
width={width}
circular={circular}
cornered={cornered}
/>
) : (
<></>
)
}
interface ItemBlurhashProps {
item: BaseItemDto
type: ImageType
cornered?: boolean | undefined
circular?: boolean | undefined
width?: Token | string | number | string | undefined
height?: Token | string | number | string | undefined
testID?: string | undefined
}
const Styles = StyleSheet.create({
blurhash: {
width: '100%',
height: '100%',
},
blurhashInner: {
...StyleSheet.absoluteFillObject,
},
})
function ItemBlurhash({ item, type }: ItemBlurhashProps): React.JSX.Element {
const blurhash = getBlurhashFromDto(item, type)
return (
<Animated.View style={Styles.blurhash} entering={FadeIn} exiting={FadeOut}>
<Blurhash resizeMode={'cover'} style={Styles.blurhashInner} blurhash={blurhash} />
</Animated.View>
)
}
const style = getImageStyleSheet(width, height, cornered, circular)
interface ImageProps {
imageUrl: string
type: ImageType
item: BaseItemDto
cornered?: boolean | undefined
circular?: boolean | undefined
width?: Token | string | number | string | undefined
height?: Token | string | number | string | undefined
testID?: string | undefined
}
function Image({
item,
type = ImageType.Primary,
imageUrl,
width,
height,
circular,
cornered,
testID,
}: ImageProps): React.JSX.Element {
const [isLoaded, setIsLoaded] = useState<boolean>(false)
const handleImageLoad = useCallback(() => setIsLoaded(true), [setIsLoaded])
const imageViewStyle = getImageStyleSheet(width, height, cornered, circular)
const imageSource = { uri: imageUrl }
const blurhash = !isLoaded ? <ItemBlurhash item={item} type={type} /> : null
return (
<ZStack style={imageViewStyle.view} justifyContent='center' alignContent='center'>
<TamaguiImage
objectFit='cover'
source={imageSource}
testID={testID}
onLoad={handleImageLoad}
style={Styles.blurhash}
animation={'quick'}
/>
{blurhash}
</ZStack>
return imageUrl ? (
<TurboImage
cachePolicy='dataCache'
resizeMode='cover'
source={{ uri: imageUrl }}
testID={testID}
style={style.view}
placeholder={{
blurhash,
}}
/>
) : (
<Square backgroundColor={'$neutral'} style={style.view} />
)
}

View File

@@ -1,43 +1,68 @@
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import React from 'react'
import { QueryKeys } from '../../../enums/query-keys'
import { useQuery } from '@tanstack/react-query'
import { fetchInstantMixFromItem } from '../../../api/queries/instant-mixes'
import Icon from './icon'
import { Spacer, Spinner } from 'tamagui'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { BaseStackParamList } from '../../../screens/types'
import { useApi, useJellifyUser } from '../../../stores'
import Button from '../helpers/button'
import { CommonActions } from '@react-navigation/native'
import { Text } from '../helpers/text'
import Animated, { FadeInUp, FadeOutDown, LinearTransition } from 'react-native-reanimated'
export default function InstantMixButton({
export function InstantMixIconButton({
item,
navigation,
}: {
item: BaseItemDto
navigation: Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
}): React.JSX.Element {
const api = useApi()
const [user] = useJellifyUser()
const { data, isFetching, refetch } = useQuery({
queryKey: [QueryKeys.InstantMix, item.Id!],
queryFn: () => fetchInstantMixFromItem(api, user, item),
})
return data ? (
return (
<Icon
name='radio'
color={'$success'}
onPress={() =>
navigation.navigate('InstantMix', {
item,
mix: data,
})
}
/>
) : isFetching ? (
<Spinner alignSelf='center' />
) : (
<Spacer />
)
}
export function InstantMixButton({
item,
navigation,
}: {
item: BaseItemDto
navigation: Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
}): React.JSX.Element {
return (
<Animated.View
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
style={{
flex: 2,
}}
>
<Button
borderColor={'$success'}
borderWidth={'$1'}
icon={<Icon name='radio' color='$success' small />}
onPress={() =>
navigation.dispatch(
CommonActions.navigate('InstantMix', {
item,
}),
)
}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
animation={'bouncy'}
>
<Text bold color={'$success'}>
Mix
</Text>
</Button>
</Animated.View>
)
}

View File

@@ -1,4 +1,4 @@
import React, { memo, useCallback, useEffect, useMemo } from 'react'
import React, { useEffect } from 'react'
import { CardProps as TamaguiCardProps } from 'tamagui'
import { Card as TamaguiCard, View, YStack } from 'tamagui'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
@@ -23,7 +23,7 @@ interface CardProps extends TamaguiCardProps {
*
* @param props
*/
function ItemCardComponent({
export default function ItemCard({
caption,
subCaption,
item,
@@ -41,22 +41,16 @@ function ItemCardComponent({
if (item.Type === 'Audio') warmContext(item)
}, [item.Id, item.Type, warmContext])
const hoverStyle = useMemo(() => (onPress ? { scale: 0.925 } : undefined), [onPress])
const hoverStyle = onPress ? { scale: 0.925 } : undefined
const pressStyle = useMemo(() => (onPress ? { scale: 0.875 } : undefined), [onPress])
const pressStyle = onPress ? { scale: 0.875 } : undefined
const handlePressIn = useCallback(
() => (item.Type !== 'Audio' ? warmContext(item) : undefined),
[item.Id, warmContext],
)
const handlePressIn = () => (item.Type !== 'Audio' ? warmContext(item) : undefined)
const background = useMemo(
() => (
<TamaguiCard.Background>
<ItemImage item={item} circular={!squared} />
</TamaguiCard.Background>
),
[item.Id, squared],
const background = (
<TamaguiCard.Background>
<ItemImage item={item} circular={!squared} />
</TamaguiCard.Background>
)
return (
@@ -88,62 +82,41 @@ function ItemCardComponent({
)
}
const ItemCardComponentCaption = memo(
function ItemCardComponentCaption({
size,
captionAlign = 'center',
caption,
subCaption,
}: {
size: string | number
captionAlign: 'center' | 'left' | 'right'
caption?: string | null | undefined
subCaption?: string | null | undefined
}): React.JSX.Element | null {
if (!caption) return null
function ItemCardComponentCaption({
size,
captionAlign = 'center',
caption,
subCaption,
}: {
size: string | number
captionAlign: 'center' | 'left' | 'right'
caption?: string | null | undefined
subCaption?: string | null | undefined
}): React.JSX.Element | null {
if (!caption) return null
return (
<YStack maxWidth={size}>
return (
<YStack maxWidth={size}>
<Text
bold
lineBreakStrategyIOS='standard'
width={size}
numberOfLines={1}
textAlign={captionAlign}
>
{caption}
</Text>
{subCaption && (
<Text
bold
lineBreakStrategyIOS='standard'
width={size}
numberOfLines={1}
textAlign={captionAlign}
>
{caption}
{subCaption}
</Text>
{subCaption && (
<Text
lineBreakStrategyIOS='standard'
width={size}
numberOfLines={1}
textAlign={captionAlign}
>
{subCaption}
</Text>
)}
</YStack>
)
},
(prevProps, nextProps) =>
prevProps.size === nextProps.size &&
prevProps.captionAlign === nextProps.captionAlign &&
prevProps.caption === nextProps.caption &&
prevProps.subCaption === nextProps.subCaption,
)
export const ItemCard = React.memo(
ItemCardComponent,
(a, b) =>
a.item.Id === b.item.Id &&
a.item.Type === b.item.Type &&
a.caption === b.caption &&
a.subCaption === b.subCaption &&
a.squared === b.squared &&
a.size === b.size &&
a.testId === b.testId &&
!!a.onPress === !!b.onPress &&
a.captionAlign === b.captionAlign,
)
)}
</YStack>
)
}

View File

@@ -14,7 +14,7 @@ import { useAddToQueue, useLoadNewQueue } from '../../../providers/Player/hooks/
import useStreamingDeviceProfile from '../../../stores/device-profile'
import useItemContext from '../../../hooks/use-item-context'
import { RouteProp, useRoute } from '@react-navigation/native'
import React, { useEffect } from 'react'
import React from 'react'
import { LayoutChangeEvent } from 'react-native'
import Animated, {
SharedValue,
@@ -36,6 +36,7 @@ interface ItemRowProps {
item: BaseItemDto
circular?: boolean
onPress?: () => void
onLongPress?: () => void
navigation?: Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
queueName?: Queue
}
@@ -56,6 +57,7 @@ function ItemRow({
circular,
navigation,
onPress,
onLongPress,
queueName,
}: ItemRowProps): React.JSX.Element {
const artworkAreaWidth = useSharedValue(0)
@@ -77,11 +79,14 @@ function ItemRow({
const onPressIn = () => warmContext(item)
const onLongPress = () =>
navigationRef.navigate('Context', {
item,
navigation,
})
const handleLongPress = () => {
if (onLongPress) onLongPress()
else
navigationRef.navigate('Context', {
item,
navigation,
})
}
const onPressCallback = async () => {
if (onPress) await onPress()
@@ -165,7 +170,7 @@ function ItemRow({
<SwipeableRow
disabled={!isAudio}
{...swipeConfig}
onLongPress={onLongPress}
onLongPress={handleLongPress}
onPress={onPressCallback}
>
<XStack
@@ -174,7 +179,7 @@ function ItemRow({
testID={item.Id ? `item-row-${item.Id}` : undefined}
onPressIn={onPressIn}
onPress={onPressCallback}
onLongPress={onLongPress}
onLongPress={handleLongPress}
animation={'quick'}
pressStyle={pressStyle}
paddingVertical={'$2'}
@@ -211,9 +216,9 @@ function ItemRowDetails({ item }: { item: BaseItemDto }): React.JSX.Element {
const route = useRoute<RouteProp<BaseStackParamList>>()
const shouldRenderArtistName =
item.Type === 'Audio' || (item.Type === 'MusicAlbum' && !route.name.includes('Overview'))
item.Type === 'Audio' || (item.Type === 'MusicAlbum' && !route.name.includes('Artist'))
const shouldRenderProductionYear = item.Type === 'MusicAlbum' && route.name.includes('Overview')
const shouldRenderProductionYear = item.Type === 'MusicAlbum' && route.name.includes('Artist')
const shouldRenderGenres = item.Type === 'Playlist' || item.Type === BaseItemKind.MusicArtist

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'
import { getToken, Theme, useTheme, XStack, YStack } from 'tamagui'
import { getToken, getTokenValue, Theme, useTheme, XStack, YStack } from 'tamagui'
import { Text } from '../helpers/text'
import { RunTimeTicks } from '../helpers/time-codes'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
@@ -14,7 +14,7 @@ import navigationRef from '../../../../navigation'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { BaseStackParamList } from '../../../screens/types'
import ItemImage from './image'
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
import { useAddToQueue, useLoadNewQueue } from '../../../providers/Player/hooks/mutations'
import useStreamingDeviceProfile from '../../../stores/device-profile'
import { useDownloadedTrack } from '../../../api/queries/download'
@@ -294,7 +294,10 @@ export default function Track({
function HideableArtwork({ children }: { children: React.ReactNode }) {
const { tx } = useSwipeableRowContext()
// Hide artwork as soon as swiping starts (any non-zero tx)
const style = useAnimatedStyle(() => ({ opacity: tx.value === 0 ? 1 : 0 }))
const style = useAnimatedStyle(() => ({
marginHorizontal: 6,
opacity: withTiming(tx.value === 0 ? 1 : 0),
}))
return <Animated.View style={style}>{children}</Animated.View>
}

View File

@@ -1,7 +1,7 @@
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import React, { useCallback } from 'react'
import { ItemCard } from '../../../components/Global/components/item-card'
import ItemCard from '../../../components/Global/components/item-card'
import { H5, XStack } from 'tamagui'
import Icon from '../../Global/components/icon'
import { useDisplayContext } from '../../../providers/Display/display-provider'

View File

@@ -1,7 +1,7 @@
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { H5, XStack } from 'tamagui'
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
import { ItemCard } from '../../../components/Global/components/item-card'
import ItemCard from '../../../components/Global/components/item-card'
import { QueuingType } from '../../../enums/queuing-type'
import Icon from '../../Global/components/icon'
import { useLoadNewQueue } from '../../../providers/Player/hooks/mutations'

View File

@@ -1,7 +1,7 @@
import React, { useCallback } from 'react'
import { H5, View, XStack } from 'tamagui'
import { RootStackParamList } from '../../../screens/types'
import { ItemCard } from '../../Global/components/item-card'
import ItemCard from '../../Global/components/item-card'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
import Icon from '../../Global/components/icon'

View File

@@ -1,6 +1,6 @@
import React from 'react'
import { H5, XStack } from 'tamagui'
import { ItemCard } from '../../Global/components/item-card'
import ItemCard from '../../Global/components/item-card'
import { RootStackParamList } from '../../../screens/types'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { QueuingType } from '../../../enums/queuing-type'

View File

@@ -1,22 +1,23 @@
import { useCallback } from 'react'
import { InstantMixProps } from '../../screens/types'
import Track from '../Global/components/track'
import { Separator } from 'tamagui'
import { Separator, useTheme } from 'tamagui'
import { FlashList } from '@shopify/flash-list'
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
import useInstantMix from '../../api/queries/instant-mix'
import { Text } from '../Global/helpers/text'
import { RefreshControl } from 'react-native'
export default function InstantMix({ route, navigation }: InstantMixProps): React.JSX.Element {
const { mix } = route.params
const handleScrollBeginDrag = useCallback(() => {
closeAllSwipeableRows()
}, [])
const { data: mix, isFetching, refetch } = useInstantMix(route.params.item)
const theme = useTheme()
return (
<FlashList
contentInsetAdjustmentBehavior='automatic'
data={mix}
ItemSeparatorComponent={() => <Separator />}
onScrollBeginDrag={handleScrollBeginDrag}
onScrollBeginDrag={closeAllSwipeableRows}
renderItem={({ item, index }) => (
<Track
showArtwork
@@ -26,6 +27,16 @@ export default function InstantMix({ route, navigation }: InstantMixProps): Reac
tracklist={mix}
/>
)}
ListEmptyComponent={
!isFetching ? <Text color={'$neutral'}>No mix tracks</Text> : undefined // Refresh Control will handle the spinner, which is actually called a "throbber" ;)
}
refreshControl={
<RefreshControl
refreshing={isFetching}
onRefresh={refetch}
tintColor={theme.success.val}
/>
}
/>
)
}

View File

@@ -6,7 +6,7 @@ import BlurredBackground from './blurred-background'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useProgress } from '../../../providers/Player/hooks/queries'
import { useSeekTo } from '../../../providers/Player/hooks/mutations'
import { UPDATE_INTERVAL } from '../../../player/config'
import { UPDATE_INTERVAL } from '../../../configs/player.config'
import React, { useEffect, useMemo, useRef, useCallback } from 'react'
import Animated, {
useSharedValue,

View File

@@ -5,7 +5,7 @@ import { Spacer, XStack, YStack } from 'tamagui'
import { useSafeAreaFrame } from 'react-native-safe-area-context'
import { useSeekTo } from '../../../providers/Player/hooks/mutations'
import { RunTimeSeconds } from '../../../components/Global/helpers/time-codes'
import { UPDATE_INTERVAL } from '../../../player/config'
import { UPDATE_INTERVAL } from '../../../configs/player.config'
import { ProgressMultiplier } from '../component.config'
import { useProgress } from '../../../providers/Player/hooks/queries'
import QualityBadge from './quality-badge'

View File

@@ -106,13 +106,21 @@ export default function SongInfo({ swipeX }: SongInfoProps = {}): React.JSX.Elem
return (
<XStack>
<YStack justifyContent='flex-start' flex={1} gap={'$0.25'}>
<TextTicker {...TextTickerConfig} style={{ height: getToken('$9') }}>
<TextTicker
{...TextTickerConfig}
style={{ height: getToken('$9') }}
key={`${nowPlaying!.item.Id}-title`}
>
<Text bold fontSize={'$6'}>
{trackTitle}
</Text>
</TextTicker>
<TextTicker {...TextTickerConfig} style={{ height: getToken('$8') }}>
<TextTicker
{...TextTickerConfig}
style={{ height: getToken('$8') }}
key={`${nowPlaying!.item.Id}-artist`}
>
<Text fontSize={'$6'} color={'$color'} onPress={handleArtistPress}>
{nowPlaying?.artist ?? 'Unknown Artist'}
</Text>

View File

@@ -5,7 +5,7 @@ import { Text } from '../Global/helpers/text'
import TextTicker from 'react-native-text-ticker'
import { PlayPauseIcon } from './components/buttons'
import { TextTickerConfig } from './component.config'
import { UPDATE_INTERVAL } from '../../player/config'
import { UPDATE_INTERVAL } from '../../configs/player.config'
import { Progress as TrackPlayerProgress } from 'react-native-track-player'
import { useProgress } from '../../providers/Player/hooks/queries'
@@ -118,7 +118,7 @@ export default function Miniplayer(): React.JSX.Element {
<Animated.View
entering={FadeIn}
exiting={FadeOut}
key={`${nowPlaying!.item.AlbumId}-mini-player-song-info`}
key={`${nowPlaying!.item.Id}-mini-player-song-info`}
style={{
width: '100%',
}}

View File

@@ -2,8 +2,8 @@ import Icon from '../Global/components/icon'
import Track from '../Global/components/track'
import { RootStackParamList } from '../../screens/types'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { ScrollView, XStack } from 'tamagui'
import { useLayoutEffect, useCallback, useState } from 'react'
import { ScrollView, Text, XStack } from 'tamagui'
import { useLayoutEffect, useState } from 'react'
import JellifyTrack from '../../types/JellifyTrack'
import {
useRemoveFromQueue,
@@ -13,8 +13,9 @@ import {
} from '../../providers/Player/hooks/mutations'
import { usePlayerQueueStore, useQueueRef } from '../../stores/player/queue'
import Sortable from 'react-native-sortables'
import { RenderItemInfo } from 'react-native-sortables/dist/typescript/types'
import { OrderChangeParams, RenderItemInfo } from 'react-native-sortables/dist/typescript/types'
import { useReducedHapticsSetting } from '../../stores/settings/app'
import uuid from 'react-native-uuid'
export default function Queue({
navigation,
@@ -35,51 +36,62 @@ export default function Queue({
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => {
return <Icon name='notification-clear-all' onPress={removeUpcomingTracks} />
return (
<XStack>
<Text>Clear Upcoming</Text>
<Icon
name='notification-clear-all'
onPress={async () => {
await removeUpcomingTracks()
setQueue(usePlayerQueueStore.getState().queue)
}}
/>
</XStack>
)
},
})
}, [navigation, removeUpcomingTracks])
const keyExtractor = useCallback((item: JellifyTrack) => `${item.item.Id}`, [])
const keyExtractor = (item: JellifyTrack) => item.item.Id ?? uuid.v4()
// Memoize renderItem function for better performance
const renderItem = useCallback(
({ item: queueItem, index }: RenderItemInfo<JellifyTrack>) => (
<XStack alignItems='center' key={`${index}-${queueItem.item.Id}`}>
<Sortable.Handle style={{ display: 'flex', flexShrink: 1 }}>
<Icon name='drag' />
</Sortable.Handle>
const renderItem = ({ item: queueItem, index }: RenderItemInfo<JellifyTrack>) => (
<XStack alignItems='center'>
<Sortable.Handle style={{ display: 'flex', flexShrink: 1 }}>
<Icon name='drag' />
</Sortable.Handle>
<Sortable.Touchable
onTap={() => skip(index)}
style={{
flexGrow: 1,
}}
>
<Track
queue={queueRef ?? 'Recently Played'}
track={queueItem.item}
index={index}
showArtwork
testID={`queue-item-${index}`}
isNested
editing
/>
</Sortable.Touchable>
<Sortable.Touchable
onTap={() => skip(index)}
style={{
flexGrow: 1,
}}
>
<Track
queue={queueRef ?? 'Recently Played'}
track={queueItem.item}
index={index}
showArtwork
testID={`queue-item-${index}`}
isNested
editing
/>
</Sortable.Touchable>
<Sortable.Touchable
onTap={async () => {
setQueue(queue.filter(({ item }) => item.Id !== queueItem.item.Id))
await removeFromQueue(index)
}}
>
<Icon name='close' color='$warning' />
</Sortable.Touchable>
</XStack>
),
[queueRef, skip, removeFromQueue],
<Sortable.Touchable
onTap={async () => {
setQueue(queue.filter(({ item }) => item.Id !== queueItem.item.Id))
await removeFromQueue(index)
}}
>
<Icon name='close' color='$warning' />
</Sortable.Touchable>
</XStack>
)
const handleReorder = async ({ fromIndex, toIndex }: OrderChangeParams) =>
await reorderQueue({ fromIndex, toIndex })
return (
<ScrollView flex={1} contentInsetAdjustmentBehavior='automatic'>
<Sortable.Grid
@@ -87,10 +99,8 @@ export default function Queue({
columns={1}
keyExtractor={keyExtractor}
renderItem={renderItem}
onOrderChange={reorderQueue}
onDragEnd={({ data }) => {
setQueue(data)
}}
onOrderChange={handleReorder}
onDragEnd={({ data }) => setQueue(data)}
overDrag='vertical'
customHandle
hapticsEnabled={!reducedHaptics}

View File

@@ -1,10 +1,9 @@
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { H5, Spacer, XStack, YStack } from 'tamagui'
import InstantMixButton from '../../Global/components/instant-mix-button'
import { InstantMixButton } from '../../Global/components/instant-mix-button'
import Icon from '../../Global/components/icon'
import { useNetworkStatus } from '../../../stores/network'
import { ActivityIndicator } from 'react-native'
import { QueuingType } from '../../../enums/queuing-type'
import { useNavigation } from '@react-navigation/native'
import LibraryStackParamList from '@/src/screens/Library/types'
@@ -13,9 +12,16 @@ import useStreamingDeviceProfile from '../../../stores/device-profile'
import ItemImage from '../../Global/components/image'
import { useApi } from '../../../stores'
import Input from '../../Global/helpers/input'
import Animated, { FadeInDown, FadeOutDown } from 'react-native-reanimated'
import Animated, {
FadeInDown,
FadeInUp,
FadeOutDown,
LinearTransition,
} from 'react-native-reanimated'
import { Dispatch, SetStateAction } from 'react'
import useAddToPendingDownloads, { useIsDownloading } from '../../../stores/network/downloads'
import Button from '../../Global/helpers/button'
import { Text } from '../../Global/helpers/text'
import { RunTimeTicks } from '../../Global/helpers/time-codes'
export default function PlaylistTracklistHeader({
playlist,
@@ -31,7 +37,7 @@ export default function PlaylistTracklistHeader({
setNewName: Dispatch<SetStateAction<string>>
}): React.JSX.Element {
return (
<YStack justifyContent='center' alignItems='center' paddingTop={'$1'} marginBottom={'$2'}>
<YStack paddingTop={'$1'} marginBottom={'$2'} gap={'$2'}>
<YStack justifyContent='center' alignContent='center' padding={'$2'}>
<ItemImage item={playlist} width={'$20'} height={'$20'} />
</YStack>
@@ -47,7 +53,7 @@ export default function PlaylistTracklistHeader({
onChangeText={setNewName}
placeholder='Playlist Name'
textAlign='center'
fontSize={'$9'}
fontSize={'$8'}
fontWeight='bold'
clearButtonMode='while-editing'
marginHorizontal={'$4'}
@@ -55,19 +61,18 @@ export default function PlaylistTracklistHeader({
</Animated.View>
) : (
<Animated.View entering={FadeInDown} exiting={FadeOutDown}>
<H5
lineBreakStrategyIOS='standard'
textAlign='center'
numberOfLines={5}
marginBottom={'$2'}
>
{newName ?? 'Untitled Playlist'}
</H5>
<YStack alignItems='center' gap={'$2'}>
<H5 lineBreakStrategyIOS='standard' textAlign='center' numberOfLines={5}>
{newName ?? 'Untitled Playlist'}
</H5>
<RunTimeTicks>{playlist.RunTimeTicks}</RunTimeTicks>
</YStack>
</Animated.View>
)}
{!editing ? (
<Animated.View entering={FadeInDown} exiting={FadeOutDown}>
<Animated.View entering={FadeInDown} exiting={FadeOutDown} style={{ flex: 1 }}>
<PlaylistHeaderControls
editing={editing}
playlist={playlist}
@@ -89,18 +94,14 @@ function PlaylistHeaderControls({
playlist: BaseItemDto
playlistTracks: BaseItemDto[]
}): React.JSX.Element {
const addToDownloadQueue = useAddToPendingDownloads()
const streamingDeviceProfile = useStreamingDeviceProfile()
const loadNewQueue = useLoadNewQueue()
const isDownloading = useIsDownloading(playlistTracks)
const api = useApi()
const [networkStatus] = useNetworkStatus()
const navigation = useNavigation<NativeStackNavigationProp<LibraryStackParamList>>()
const downloadPlaylist = () => addToDownloadQueue(playlistTracks)
const playPlaylist = (shuffled: boolean = false) => {
if (!playlistTracks || playlistTracks.length === 0) return
@@ -119,31 +120,54 @@ function PlaylistHeaderControls({
}
return (
<XStack justifyContent='center' marginVertical={'$1'} gap={'$2'} flexWrap='wrap'>
<YStack justifyContent='center' alignContent='center'>
<InstantMixButton item={playlist} navigation={navigation} />
</YStack>
<XStack justifyContent='center' marginHorizontal={'$2'} gap={'$2'}>
<Animated.View
style={{
flex: 2,
}}
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Button
animation={'bouncy'}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
borderColor={'$primary'}
borderWidth={'$1'}
onPress={() => playPlaylist(false)}
icon={<Icon name='play' color='$primary' small />}
>
<Text bold color={'$primary'}>
Play
</Text>
</Button>
</Animated.View>
<YStack justifyContent='center' alignContent='center'>
<Icon name='play' onPress={() => playPlaylist(false)} small />
</YStack>
<InstantMixButton item={playlist} navigation={navigation} />
<YStack justifyContent='center' alignContent='center'>
<Icon name='shuffle' onPress={() => playPlaylist(true)} small />
</YStack>
<YStack justifyContent='center' alignContent='center'>
{!isDownloading ? (
<Icon
color={'$borderColor'}
name={'download'}
onPress={downloadPlaylist}
small
/>
) : (
<ActivityIndicator />
)}
</YStack>
<Animated.View
style={{
flex: 2,
}}
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Button
animation={'bouncy'}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
borderColor={'$primary'}
borderWidth={'$1'}
onPress={() => playPlaylist(true)}
icon={<Icon name='shuffle' color='$primary' small />}
>
<Text bold color={'$primary'}>
Shuffle
</Text>
</Button>
</Animated.View>
</XStack>
)
}

View File

@@ -21,10 +21,21 @@ import { updatePlaylist } from '../../../src/api/mutations/playlists'
import { usePlaylistTracks } from '../../../src/api/queries/playlist'
import useHapticFeedback from '../../hooks/use-haptic-feedback'
import { useMutation } from '@tanstack/react-query'
import Animated, { SlideInLeft, SlideOutRight } from 'react-native-reanimated'
import Animated, {
FadeIn,
FadeInUp,
FadeOut,
FadeOutDown,
LinearTransition,
SlideInLeft,
SlideOutRight,
} from 'react-native-reanimated'
import { FlashList, ListRenderItem } from '@shopify/flash-list'
import { Text } from '../Global/helpers/text'
import { RefreshControl } from 'react-native'
import { useIsDownloaded } from '../../api/queries/download'
import useAddToPendingDownloads, { useIsDownloading } from '../../stores/network/downloads'
import { useStorageContext } from '../../providers/Storage'
export default function Playlist({
playlist,
@@ -128,53 +139,107 @@ export default function Playlist({
const [networkStatus] = useNetworkStatus()
const isDownloaded = useIsDownloaded(playlistTracks?.map(({ Id }) => Id) ?? [])
const playlistDownloadPending = useIsDownloading(playlistTracks ?? [])
const { deleteDownloads } = useStorageContext()
const addToDownloadQueue = useAddToPendingDownloads()
const handleDeleteDownload = () => deleteDownloads(playlistTracks?.map(({ Id }) => Id!) ?? [])
const handleDownload = () => addToDownloadQueue(playlistTracks ?? [])
const editModeActions = (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
>
<XStack gap={'$2'}>
<Icon
color={'$warning'}
name='delete-sweep-outline' // otherwise use "delete-circle"
onPress={() => {
navigationRef.dispatch(
StackActions.push('DeletePlaylist', {
playlist,
onDelete: navigation.goBack,
}),
)
}}
/>
<Icon color='$neutral' name='close-circle-outline' onPress={handleCancel} />
</XStack>
</Animated.View>
)
const downloadActions = (
<XStack gap={'$2'}>
{playlistTracks &&
(isDownloaded ? (
<Animated.View
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Icon color='$warning' name='broom' onPress={handleDeleteDownload} />
</Animated.View>
) : playlistDownloadPending ? (
<Spinner justifyContent='center' color={'$neutral'} />
) : (
<Animated.View
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Icon
color='$success'
name='download-circle-outline'
onPress={handleDownload}
/>
</Animated.View>
))}
</XStack>
)
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () =>
canEdit && (
<XStack gap={'$3'}>
{editing && (
<>
headerRight: () => (
<XStack gap={'$2'}>
{playlistTracks && !editing && downloadActions}
{canEdit && (
<XStack gap={'$2'}>
{editing ? (
editModeActions
) : isUpdating || isPreparingEditMode ? (
<Spinner color={isPreparingEditMode ? '$primary' : '$success'} />
) : null}
<Animated.View
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
>
<Icon
color={'$warning'}
name='delete-sweep-outline' // otherwise use "delete-circle"
onPress={() => {
navigationRef.dispatch(
StackActions.push('DeletePlaylist', {
playlist,
onDelete: navigation.goBack,
}),
)
}}
name={editing ? 'floppy' : 'pencil'}
color={editing ? '$success' : '$color'}
onPress={() =>
!editing
? handleEnterEditMode()
: useUpdatePlaylist({
playlist,
tracks: playlistTracks ?? [],
newName,
})
}
/>
<Icon
color='$neutral'
name='close-circle-outline'
onPress={handleCancel}
/>
</>
)}
{isUpdating || isPreparingEditMode ? (
<Spinner color={isPreparingEditMode ? '$primary' : '$success'} />
) : (
<Icon
name={editing ? 'floppy' : 'pencil'}
color={editing ? '$success' : '$color'}
onPress={() =>
!editing
? handleEnterEditMode()
: useUpdatePlaylist({
playlist,
tracks: playlistTracks ?? [],
newName,
})
}
/>
)}
</XStack>
),
</Animated.View>
</XStack>
)}
</XStack>
),
})
}, [
editing,

View File

@@ -9,6 +9,7 @@ import { BaseStackParamList } from '@/src/screens/types'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
import { RefreshControl } from 'react-native'
import { Text } from '../Global/helpers/text'
// Extracted as stable component to prevent recreation on each render
function ListSeparatorComponent(): React.JSX.Element {
@@ -73,6 +74,7 @@ export default function Playlists({
onEndReached={handleEndReached}
removeClippedSubviews
onScrollBeginDrag={closeAllSwipeableRows}
ListEmptyComponent={<Text color={'$neutral'}>No playlists</Text>}
/>
)
}

View File

@@ -7,12 +7,11 @@ import { QueryKeys } from '../../enums/query-keys'
import { fetchSearchResults } from '../../api/queries/search'
import { useQuery } from '@tanstack/react-query'
import { FlatList } from 'react-native'
import { fetchSearchSuggestions } from '../../api/queries/suggestions/utils/suggestions'
import { getToken, H3, Separator, Spinner, YStack } from 'tamagui'
import Suggestions from './suggestions'
import { isEmpty } from 'lodash'
import HorizontalCardList from '../Global/components/horizontal-list'
import { ItemCard } from '../Global/components/item-card'
import ItemCard from '../Global/components/item-card'
import SearchParamList from '../../screens/Search/types'
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
import { useApi, useJellifyLibrary, useJellifyUser } from '../../stores'

View File

@@ -1,7 +1,7 @@
import ItemRow from '../Global/components/item-row'
import { Text } from '../Global/helpers/text'
import { H5, Separator, Spinner, YStack } from 'tamagui'
import { ItemCard } from '../Global/components/item-card'
import ItemCard from '../Global/components/item-card'
import HorizontalCardList from '../Global/components/horizontal-list'
import { FlashList } from '@shopify/flash-list'
import SearchParamList from '../../screens/Search/types'

View File

@@ -98,9 +98,9 @@ function ThemeOptionCard({
padding='$3'
gap='$2'
hitSlop={8}
accessibilityRole='button'
accessibilityLabel={`${option.label} theme option`}
accessibilityState={{ selected: isSelected }}
role='button'
aria-label={`${option.label} theme option`}
aria-selected={isSelected}
>
<XStack alignItems='center' gap='$2'>
<Icon small name={option.icon} color={isSelected ? '$primary' : '$borderColor'} />

View File

@@ -1,61 +0,0 @@
// DownloadProgressBar.tsx
import React from 'react'
import { View, Text, StyleSheet } from 'react-native'
import { useQueryClient, useQuery } from '@tanstack/react-query'
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'
export const DownloadProgressBar = () => {
const { data: downloads } = useQuery({
queryKey: ['downloads'],
initialData: {},
})
return (
<View style={styles.container}>
{/* eslint-disable @typescript-eslint/no-explicit-any */}
{Object.entries(downloads || {}).map(([url, item]: any) => {
const animatedWidth = useSharedValue(item.progress)
animatedWidth.value = withTiming(item.progress, { duration: 200 })
const animatedStyle = useAnimatedStyle(() => ({
width: `${animatedWidth.value * 100}%`,
}))
return (
<View key={url} style={styles.item}>
<Text style={styles.label}>{item.name}</Text>
<View style={styles.bar}>
<Animated.View style={[styles.fill, animatedStyle]} />
</View>
</View>
)
})}
</View>
)
}
const styles = StyleSheet.create({
container: {
padding: 12,
backgroundColor: '#111',
},
item: {
marginBottom: 12,
},
label: {
color: '#fff',
marginBottom: 4,
fontSize: 14,
},
bar: {
height: 8,
backgroundColor: '#333',
borderRadius: 4,
overflow: 'hidden',
},
fill: {
height: 8,
backgroundColor: '#00bcd4',
borderRadius: 4,
},
})

View File

@@ -1,197 +0,0 @@
import React, { useEffect, useState } from 'react'
import { StyleSheet, Pressable, Alert, FlatList } from 'react-native'
import RNFS from 'react-native-fs'
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'
import { deleteAudioCache } from '../../api/mutations/download/offlineModeUtils'
import Icon from '../Global/components/icon'
import { View } from 'tamagui'
import { Text } from '../Global/helpers/text'
import { useDownloadProgress } from '@/src/stores/network/downloads'
// 🔹 Single Download Item with animated progress bar
function DownloadItem({
name,
progress,
fileName,
}: {
name: string
progress: number
fileName: string
}): React.JSX.Element {
const progressValue = useSharedValue(progress)
useEffect(() => {
progressValue.value = withTiming(progress, { duration: 300 })
}, [progress])
const animatedStyle = useAnimatedStyle(() => ({
width: `${progressValue.value * 100}%`,
}))
return (
<View style={styles.item}>
<Text style={styles.label}>{fileName}</Text>
<View style={styles.downloadBar}>
<Animated.View style={[styles.downloadFill, animatedStyle]} />
</View>
</View>
)
}
// 🔹 Main UI Component
export default function StorageBar(): React.JSX.Element {
const [used, setUsed] = useState(0)
const [total, setTotal] = useState(1)
const activeDownloadsArray = useDownloadProgress()
const usageShared = useSharedValue(0)
const percentUsed = used / total
const storageBarStyle = useAnimatedStyle(() => ({
width: `${usageShared.value * 100}%`,
}))
useEffect(() => {
usageShared.value = withTiming(percentUsed, { duration: 500 })
}, [percentUsed])
// Refresh storage info
const refreshStats = async () => {
const files = await RNFS.readDir(RNFS.DocumentDirectoryPath)
let usedBytes = 0
for (const file of files) {
const stat = await RNFS.stat(file.path)
usedBytes += Number(stat.size)
}
const info = await RNFS.getFSInfo()
setUsed(usedBytes)
setTotal(info.totalSpace)
}
const deleteAllDownloads = async () => {
const result = await deleteAudioCache()
Alert.alert(
'Downloads removed',
`Deleted ${result.deletedCount} ${result.deletedCount === 1 ? 'item' : 'items'} and freed ${(
result.freedBytes /
1024 /
1024
).toFixed(2)} MB`,
)
refreshStats()
}
useEffect(() => {
refreshStats()
}, [])
const activeDownloads = Object.values(activeDownloadsArray ?? {}).map((item) => ({
name: item.name,
progress: item.progress,
songName: item.songName,
}))
return (
<View style={styles.container}>
{/* Storage Usage */}
<Text style={styles.title}>📦 Storage Usage</Text>
<Text style={styles.usage}>
{`${(used / 1024 / 1024).toFixed(2)} MB / ${(total / 1024 / 1024 / 1024).toFixed(
2,
)} GB`}
</Text>
<View style={styles.progressBackground}>
<Animated.View style={[styles.progressFill, storageBarStyle]} />
</View>
{/* Active Downloads */}
{(activeDownloads ?? []).length > 0 && (
<>
<Text style={[styles.title, { marginTop: 24 }]}> Active Downloads</Text>
<FlatList
data={activeDownloads}
keyExtractor={(download) => download.name}
renderItem={({ item }) => {
return (
<DownloadItem
name={item.name}
progress={item.progress}
fileName={item.songName}
/>
)
}}
contentContainerStyle={{ paddingBottom: 40 }}
/>
</>
)}
{/* Delete All Downloads */}
<Pressable style={styles.deleteButton} onPress={deleteAllDownloads}>
<Icon name='delete-outline' small color={'$danger'} />
<Text style={styles.deleteText}> Delete Downloads</Text>
</Pressable>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#1c1c2e',
},
title: {
color: 'white',
fontSize: 18,
fontWeight: '600',
marginBottom: 4,
},
usage: {
color: '#aaa',
fontSize: 14,
marginBottom: 12,
},
progressBackground: {
height: 10,
backgroundColor: '#333',
borderRadius: 5,
overflow: 'hidden',
},
progressFill: {
height: 10,
backgroundColor: '#ff2d75',
borderRadius: 5,
},
item: {
marginTop: 16,
},
label: {
color: '#ccc',
fontSize: 14,
marginBottom: 4,
},
downloadBar: {
height: 8,
backgroundColor: '#2e2e3f',
borderRadius: 4,
overflow: 'hidden',
},
downloadFill: {
height: 8,
backgroundColor: '#00bcd4',
},
deleteButton: {
marginTop: 30,
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'center',
padding: 12,
backgroundColor: '#2a0f13',
borderRadius: 8,
},
deleteText: {
color: '#ff4d4f',
fontSize: 15,
fontWeight: '600',
},
})

View File

@@ -14,7 +14,6 @@ import { getToken, Theme, useTheme } from 'tamagui'
import Toast from 'react-native-toast-message'
import JellifyToastConfig from '../configs/toast.config'
import { useColorScheme } from 'react-native'
import { CarPlayProvider } from '../providers/CarPlay'
import { StorageProvider } from '../providers/Storage'
import { useSelectPlayerEngine } from '../stores/player/engine'
import { useSendMetricsSetting, useThemeSetting } from '../stores/settings/app'
@@ -80,7 +79,6 @@ function App(): React.JSX.Element {
return (
<StorageProvider>
<CarPlayProvider />
<PlayerProvider />
<Root />
<Toast topOffset={getToken('$12')} config={JellifyToastConfig(theme)} />

View File

@@ -1,12 +1,51 @@
import axios from 'axios'
import axios, { AxiosAdapter } from 'axios'
import { fetch } from 'react-native-nitro-fetch'
/**
* Custom Axios adapter using {@link fetch} from `react-native-nitro-fetch`.
*
* This will handle HTTP requests made through Axios by leveraging the Nitro Fetch API.
*
* @param config the Axios request config
* @returns
*/
const nitroAxiosAdapter: AxiosAdapter = async (config) => {
const response = await fetch(config.url!, {
method: config.method?.toUpperCase(),
headers: config.headers,
body: config.data,
cache: 'no-store',
})
const responseText = await response.text()
const data = responseText.length > 0 ? JSON.parse(responseText) : null
const headers: Record<string, string> = {}
response.headers.forEach((value, key) => {
headers[key] = value
})
return {
data,
status: response.status,
statusText: response.statusText,
headers,
config,
request: null,
}
}
/**
* The Axios instance for making HTTP requests.
*
* Leverages the {@link nitroAxiosAdapter} for handling requests.
*
* Default timeout is set to 60 seconds.
*/
const AXIOS_INSTANCE = axios.create({
timeout: 60000,
adapter: nitroAxiosAdapter,
})
export default AXIOS_INSTANCE

View File

@@ -1,5 +1,5 @@
import TrackPlayer, { RatingType } from 'react-native-track-player'
import { CAPABILITIES } from '../constants'
import { CAPABILITIES } from '../constants/player'
export const useUpdateOptions = async (isFavorite: boolean) => {
return await TrackPlayer.updateOptions({

View File

@@ -4,7 +4,7 @@ import {
PREFETCH_TRACK_COUNT,
MAX_QUEUE_LOOKAHEAD,
QUEUE_PREPARATION_THRESHOLD_SECONDS,
} from '../gapless-config'
} from '../../configs/gapless.config'
/**
* Enhanced gapless playback helper functions

View File

@@ -1,5 +1,6 @@
import TrackPlayer, { Event } from 'react-native-track-player'
import { SKIP_TO_PREVIOUS_THRESHOLD } from './config'
import { SKIP_TO_PREVIOUS_THRESHOLD } from '../configs/player.config'
import { CarPlay } from 'react-native-carplay'
/**
* Jellify Playback Service.
@@ -30,3 +31,13 @@ export async function PlaybackService() {
await TrackPlayer.seekTo(event.position)
})
}
export function registerAutoService(onConnect: () => void, onDisconnect: () => void) {
CarPlay.registerOnConnect(onConnect)
CarPlay.registerOnDisconnect(onDisconnect)
return () => {
CarPlay.unregisterOnConnect(onConnect)
CarPlay.unregisterOnDisconnect(onDisconnect)
}
}

View File

@@ -1,4 +1,4 @@
import fetchSimilar from '../../api/queries/similar'
import fetchSimilarArtists from '../../api/queries/suggestions/utils/similar'
import { QueryKeys } from '../../enums/query-keys'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { useQuery } from '@tanstack/react-query'
@@ -61,7 +61,7 @@ export const ArtistProvider = ({
isPending: fetchingSimilarArtists,
} = useQuery({
queryKey: [QueryKeys.SimilarItems, library?.musicLibraryId, artist.Id],
queryFn: () => fetchSimilar(api, user, library?.musicLibraryId, artist.Id!),
queryFn: () => fetchSimilarArtists(api, user, library?.musicLibraryId, artist.Id!),
enabled: !isUndefined(artist.Id),
})

View File

@@ -1,73 +0,0 @@
import CarPlayNavigation from '../../components/CarPlay/Navigation'
import { createContext, useEffect, useState } from 'react'
import { Platform } from 'react-native'
import { CarPlay } from 'react-native-carplay'
import { useLoadNewQueue } from '../Player/hooks/mutations'
import { useNetworkStatus } from '../../stores/network'
import useStreamingDeviceProfile from '../../stores/device-profile'
import useJellifyStore, { useApi, useJellifyLibrary } from '../../stores'
interface CarPlayContext {
carplayConnected: boolean
}
const CarPlayContextInitializer = () => {
const api = useApi()
const [library] = useJellifyLibrary()
const [carplayConnected, setCarPlayConnected] = useState(CarPlay ? CarPlay.connected : false)
const [networkStatus] = useNetworkStatus()
const deviceProfile = useStreamingDeviceProfile()
const loadNewQueue = useLoadNewQueue()
useEffect(() => {
function onConnect() {
setCarPlayConnected(true)
if (library) {
CarPlay.setRootTemplate(
CarPlayNavigation(
library,
loadNewQueue,
api,
useJellifyStore.getState().user,
networkStatus,
deviceProfile,
),
)
if (Platform.OS === 'ios') {
CarPlay.enableNowPlaying(true) // https://github.com/birkir/react-native-carplay/issues/185
}
}
}
function onDisconnect() {
setCarPlayConnected(false)
}
if (CarPlay) {
CarPlay.registerOnConnect(onConnect)
CarPlay.registerOnDisconnect(onDisconnect)
return () => {
CarPlay.unregisterOnConnect(onConnect)
CarPlay.unregisterOnDisconnect(onDisconnect)
}
}
})
return {
carplayConnected,
}
}
const CarPlayContext = createContext<CarPlayContext>({
carplayConnected: false,
})
export const CarPlayProvider = () => {
const value = CarPlayContextInitializer()
return <CarPlayContext.Provider value={value} />
}

View File

@@ -1,5 +1,5 @@
import { isUndefined } from 'lodash'
import { SKIP_TO_PREVIOUS_THRESHOLD } from '../../../player/config'
import { SKIP_TO_PREVIOUS_THRESHOLD } from '../../../configs/player.config'
import TrackPlayer, { State } from 'react-native-track-player'
export async function previous(): Promise<void> {

View File

@@ -1,7 +1,6 @@
import { useMutation } from '@tanstack/react-query'
import TrackPlayer, { RepeatMode, State } from 'react-native-track-player'
import { loadQueue, playLaterInQueue, playNextInQueue } from '../functions/queue'
import { isUndefined } from 'lodash'
import { previous, skip } from '../functions/controls'
import { AddToQueueMutation, QueueMutation, QueueOrderMutation } from '../interfaces'
import { QueuingType } from '../../../enums/queuing-type'
@@ -11,12 +10,8 @@ import JellifyTrack from '@/src/types/JellifyTrack'
import calculateTrackVolume from '../utils/normalization'
import usePlayerEngineStore, { PlayerEngine } from '../../../stores/player/engine'
import { useRemoteMediaClient } from 'react-native-google-cast'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { RootStackParamList } from '../../../screens/types'
import { useNavigation } from '@react-navigation/native'
import useHapticFeedback from '../../../hooks/use-haptic-feedback'
import { usePlayerQueueStore } from '../../../stores/player/queue'
import { useCallback } from 'react'
/**
* A mutation to handle starting playback
@@ -40,7 +35,7 @@ export const useTogglePlayback = () => {
const trigger = useHapticFeedback()
return useCallback(async () => {
return async () => {
trigger('impactMedium')
const { state } = await TrackPlayer.getPlaybackState()
@@ -75,13 +70,13 @@ export const useTogglePlayback = () => {
// handlePlaybackStateChanged(State.Playing)
return TrackPlayer.play()
}, [isCasting, remoteClient, trigger])
}
}
export const useToggleRepeatMode = () => {
const trigger = useHapticFeedback()
return useCallback(async () => {
return async () => {
trigger('impactLight')
const currentMode = await TrackPlayer.getRepeatMode()
let nextMode: RepeatMode
@@ -99,7 +94,7 @@ export const useToggleRepeatMode = () => {
await TrackPlayer.setRepeatMode(nextMode)
usePlayerQueueStore.getState().setRepeatMode(nextMode)
}, [trigger])
}
}
/**
@@ -112,21 +107,18 @@ export const useSeekTo = () => {
const trigger = useHapticFeedback()
return useCallback(
async (position: number) => {
trigger('impactLight')
return async (position: number) => {
trigger('impactLight')
if (isCasting && remoteClient) {
await remoteClient.seek({
position: position,
resumeState: 'play',
})
return
}
await TrackPlayer.seekTo(position)
},
[isCasting, remoteClient, trigger],
)
if (isCasting && remoteClient) {
await remoteClient.seek({
position: position,
resumeState: 'play',
})
return
}
await TrackPlayer.seekTo(position)
}
}
/**
@@ -135,20 +127,17 @@ export const useSeekTo = () => {
const useSeekBy = () => {
const trigger = useHapticFeedback()
return useCallback(
async (seekSeconds: number) => {
trigger('clockTick')
return async (seekSeconds: number) => {
trigger('clockTick')
await TrackPlayer.seekBy(seekSeconds)
},
[trigger],
)
await TrackPlayer.seekBy(seekSeconds)
}
}
export const useAddToQueue = () => {
const trigger = useHapticFeedback()
return useCallback(async (variables: AddToQueueMutation) => {
return async (variables: AddToQueueMutation) => {
try {
if (variables.queuingType === QueuingType.PlayingNext) playNextInQueue({ ...variables })
else playLaterInQueue({ ...variables })
@@ -179,97 +168,108 @@ export const useAddToQueue = () => {
usePlayerQueueStore.getState().setQueue(newQueue as JellifyTrack[])
}
}, [])
}
}
export const useLoadNewQueue = () => {
const isCasting =
usePlayerEngineStore((state) => state.playerEngineData) === PlayerEngine.GOOGLE_CAST
const remoteClient = useRemoteMediaClient()
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
const trigger = useHapticFeedback()
return useCallback(
async (variables: QueueMutation) => {
trigger('impactLight')
await TrackPlayer.pause()
const { finalStartIndex, tracks } = await loadQueue({ ...variables })
return async (variables: QueueMutation) => {
trigger('impactLight')
await TrackPlayer.pause()
const { finalStartIndex, tracks } = await loadQueue({ ...variables })
usePlayerQueueStore.getState().setCurrentIndex(finalStartIndex)
if (isCasting && remoteClient) {
await TrackPlayer.skip(finalStartIndex)
navigation.navigate('PlayerRoot', { screen: 'PlayerScreen' })
return
}
usePlayerQueueStore.getState().setCurrentIndex(finalStartIndex)
if (isCasting && remoteClient) {
await TrackPlayer.skip(finalStartIndex)
return
}
if (variables.startPlayback) await TrackPlayer.play()
await TrackPlayer.skip(finalStartIndex)
usePlayerQueueStore.getState().setQueueRef(variables.queue)
usePlayerQueueStore.getState().setQueue(tracks)
usePlayerQueueStore.getState().setCurrentTrack(tracks[finalStartIndex])
},
[isCasting, remoteClient, navigation, trigger, usePlayerQueueStore],
)
if (variables.startPlayback) await TrackPlayer.play()
usePlayerQueueStore.getState().setQueueRef(variables.queue)
usePlayerQueueStore.getState().setQueue(tracks)
usePlayerQueueStore.getState().setCurrentTrack(tracks[finalStartIndex])
}
}
export const usePrevious = () => {
const trigger = useHapticFeedback()
return useCallback(async () => {
return async () => {
trigger('impactMedium')
await previous()
}, [trigger])
}
}
export const useSkip = () => {
const trigger = useHapticFeedback()
return useCallback(
async (index?: number | undefined) => {
trigger('impactMedium')
return async (index?: number | undefined) => {
trigger('impactMedium')
await skip(index)
},
[trigger],
)
await skip(index)
}
}
export const useRemoveFromQueue = () => {
const trigger = useHapticFeedback()
return useCallback(
async (index: number) => {
trigger('impactMedium')
TrackPlayer.remove([index])
const newQueue = await TrackPlayer.getQueue()
return async (index: number) => {
trigger('impactMedium')
await TrackPlayer.remove([index])
usePlayerQueueStore.getState().setQueue(newQueue as JellifyTrack[])
},
[trigger],
)
const prevQueue = usePlayerQueueStore.getState().queue
const newQueue = prevQueue.filter((_, i) => i !== index)
usePlayerQueueStore.getState().setQueue(newQueue)
// If queue is now empty, reset player state to hide miniplayer
if (newQueue.length === 0) {
usePlayerQueueStore.getState().setCurrentTrack(undefined)
usePlayerQueueStore.getState().setCurrentIndex(undefined)
await TrackPlayer.reset()
}
}
}
export const useRemoveUpcomingTracks = () => {
return useCallback(async () => {
return async () => {
await TrackPlayer.removeUpcomingTracks()
const newQueue = await TrackPlayer.getQueue()
usePlayerQueueStore.getState().setQueue(newQueue as JellifyTrack[])
}, [])
// If queue is now empty, reset player state to hide miniplayer
if (newQueue.length === 0) {
usePlayerQueueStore.getState().setCurrentTrack(undefined)
usePlayerQueueStore.getState().setCurrentIndex(undefined)
await TrackPlayer.reset()
}
}
}
export const useReorderQueue = () => {
return useCallback(async ({ fromIndex, toIndex }: QueueOrderMutation) => {
return async ({ fromIndex, toIndex }: QueueOrderMutation) => {
await TrackPlayer.move(fromIndex, toIndex)
const newQueue = await TrackPlayer.getQueue()
usePlayerQueueStore.getState().setQueue(newQueue as JellifyTrack[])
}, [])
const queue = usePlayerQueueStore.getState().queue
const itemToMove = queue[fromIndex]
const newQueue = [...queue]
newQueue.splice(fromIndex, 1)
newQueue.splice(toIndex, 0, itemToMove)
usePlayerQueueStore.getState().setQueue(newQueue)
}
}
export const useResetQueue = () =>
@@ -308,9 +308,8 @@ export const useToggleShuffle = () => {
})
}
export const useAudioNormalization = () =>
useCallback(async (track: JellifyTrack) => {
const volume = calculateTrackVolume(track)
await TrackPlayer.setVolume(volume)
return volume
}, [])
export const useAudioNormalization = () => async (track: JellifyTrack) => {
const volume = calculateTrackVolume(track)
await TrackPlayer.setVolume(volume)
return volume
}

View File

@@ -1,10 +1,10 @@
import ArtistNavigation from '../../components/Artist'
import { ArtistProvider } from '../../providers/Artist'
import { RouteProp } from '@react-navigation/native'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { BaseStackParamList } from '../types'
import { ArtistProvider } from '../../providers/Artist'
import ArtistNavigation from '../../components/Artist'
export function ArtistScreen({
export default function ArtistScreen({
route,
navigation,
}: {

View File

@@ -1,7 +1,7 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import Index from '../../components/Discover/component'
import AlbumScreen from '../Album'
import { ArtistScreen } from '../Artist'
import ArtistScreen from '../Artist'
import { getTokenValue, useTheme } from 'tamagui'
import RecentlyAdded from './albums'
import PublicPlaylists from './playlists'
@@ -10,6 +10,7 @@ import SuggestedArtists from './artists'
import DiscoverStackParamList from './types'
import InstantMix from '../../components/InstantMix/component'
import { getItemName } from '../../utils/text'
import TracksScreen from '../Tracks'
export const DiscoverStack = createNativeStackNavigator<DiscoverStackParamList>()
@@ -57,6 +58,9 @@ export function Discover(): React.JSX.Element {
component={PlaylistScreen}
options={({ route }) => ({
title: route.params.playlist.Name ?? 'Untitled Playlist',
headerTitleStyle: {
color: theme.background.val,
},
})}
/>
@@ -98,6 +102,8 @@ export function Discover(): React.JSX.Element {
headerTitle: `${getItemName(route.params.item)} Mix`,
})}
/>
<DiscoverStack.Screen name='Tracks' component={TracksScreen} />
</DiscoverStack.Navigator>
)
}

View File

@@ -2,7 +2,7 @@ import _ from 'lodash'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { PlaylistScreen } from '../Playlist'
import { Home as HomeComponent } from '../../components/Home'
import { ArtistScreen } from '../Artist'
import ArtistScreen from '../Artist'
import { getTokenValue, useTheme } from 'tamagui'
import HomeArtistsScreen from './artists'
import HomeTracksScreen from './tracks'
@@ -10,6 +10,7 @@ import AlbumScreen from '../Album'
import HomeStackParamList from './types'
import InstantMix from '../../components/InstantMix/component'
import { getItemName } from '../../utils/text'
import TracksScreen from '../Tracks'
const HomeStack = createNativeStackNavigator<HomeStackParamList>()
@@ -99,6 +100,8 @@ export default function Home(): React.JSX.Element {
headerTitle: `${getItemName(route.params.item)} Mix`,
})}
/>
<HomeStack.Screen name='Tracks' component={TracksScreen} />
</HomeStack.Group>
</HomeStack.Navigator>
)

View File

@@ -2,7 +2,7 @@ import React from 'react'
import Library from '../../components/Library/component'
import { PlaylistScreen } from '../Playlist'
import AddPlaylist from './add-playlist'
import { ArtistScreen } from '../Artist'
import ArtistScreen from '../Artist'
import { useTheme } from 'tamagui'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import AlbumScreen from '../Album'
@@ -10,6 +10,7 @@ import LibraryStackParamList from './types'
import InstantMix from '../../components/InstantMix/component'
import { getItemName } from '../../utils/text'
import { Platform } from 'react-native'
import TracksScreen from '../Tracks'
const LibraryStack = createNativeStackNavigator<LibraryStackParamList>()
@@ -81,6 +82,8 @@ export default function LibraryScreen(): React.JSX.Element {
sheetAllowedDetents: Platform.OS === 'ios' ? 'fitToContents' : [0.5],
}}
/>
<LibraryStack.Screen name='Tracks' component={TracksScreen} />
</LibraryStack.Navigator>
)
}

View File

@@ -1,5 +1,5 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { ArtistScreen } from '../Artist'
import ArtistScreen from '../Artist'
import AlbumScreen from '../Album'
import { PlaylistScreen } from '../Playlist'
import { getTokenValue, useTheme } from 'tamagui'
@@ -7,6 +7,7 @@ import Search from '../../components/Search'
import SearchParamList from './types'
import InstantMix from '../../components/InstantMix/component'
import { getItemName } from '../../utils/text'
import TracksScreen from '../Tracks'
const Stack = createNativeStackNavigator<SearchParamList>()
@@ -68,6 +69,8 @@ export default function SearchStack(): React.JSX.Element {
headerTitle: `${getItemName(route.params.item)} Mix`,
})}
/>
<Stack.Screen name='Tracks' component={TracksScreen} />
</Stack.Navigator>
)
}

View File

@@ -1,18 +1,16 @@
import React, { useState } from 'react'
import { FlashList, ListRenderItem } from '@shopify/flash-list'
import { useFocusEffect, useNavigation } from '@react-navigation/native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { Pressable, Alert } from 'react-native'
import { Card, Paragraph, Separator, SizableText, Spinner, XStack, YStack, Image } from 'tamagui'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { useStorageContext, CleanupSuggestion } from '../../../providers/Storage'
import Icon from '../../../components/Global/components/icon'
import Button from '../../../components/Global/helpers/button'
import { formatBytes } from '../../../utils/format-bytes'
import { JellifyDownload, JellifyDownloadProgress } from '../../../types/JellifyDownload'
import { SettingsStackParamList } from '../types'
import { useDeletionToast } from './useDeletionToast'
import { Text } from '../../../components/Global/helpers/text'
const getDownloadSize = (download: JellifyDownload) =>
(download.fileSizeBytes ?? 0) + (download.artworkSizeBytes ?? 0)
@@ -44,7 +42,7 @@ export default function StorageManagementScreen(): React.JSX.Element {
const [applyingSuggestionId, setApplyingSuggestionId] = useState<string | null>(null)
const insets = useSafeAreaInsets()
const navigation = useNavigation<NativeStackNavigationProp<SettingsStackParamList>>()
const showDeletionToast = useDeletionToast()
const sortedDownloads = !downloads
@@ -86,12 +84,12 @@ export default function StorageManagementScreen(): React.JSX.Element {
const handleDeleteAll = () =>
Alert.alert(
'Delete all downloads?',
'Clear all downloads?',
'This will remove all downloaded music from your device. This action cannot be undone.',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Delete All',
text: 'Clear All',
style: 'destructive',
onPress: async () => {
if (!downloads) return
@@ -109,12 +107,12 @@ export default function StorageManagementScreen(): React.JSX.Element {
const handleDeleteSelection = () =>
Alert.alert(
'Delete selected items?',
`Are you sure you want to delete ${selectedIds.length} items?`,
'Clear selected downloads?',
`Are you sure you want to clear ${selectedIds.length} downloads?`,
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Delete',
text: 'Clear',
style: 'destructive',
onPress: async () => {
const result = await deleteDownloads(selectedIds)
@@ -255,18 +253,20 @@ const StorageSummaryCard = ({
)
}
onPress={onRefresh}
accessibilityLabel='Refresh storage overview'
aria-label='Refresh storage overview'
/>
<Button
size='$2'
backgroundColor='$danger'
borderColor='$danger'
backgroundColor='$warning'
borderColor='$warning'
borderWidth={1}
color='white'
onPress={onDeleteAll}
icon={() => <Icon name='delete-outline' color='$background' small />}
icon={() => <Icon name='broom' color='$background' small />}
>
Delete All
<Text bold color={'$background'}>
Clear All
</Text>
</Button>
</XStack>
</XStack>
@@ -388,7 +388,7 @@ const DownloadRow = ({
<XStack padding='$3' alignItems='center' gap='$3' borderRadius='$4'>
<Icon
name={isSelected ? 'check-circle-outline' : 'circle-outline'}
color={isSelected ? '$primary' : '$borderColor'}
color={isSelected ? '$color' : '$borderColor'}
/>
{download.artwork ? (
@@ -428,12 +428,12 @@ const DownloadRow = ({
circular
backgroundColor='transparent'
hitSlop={10}
icon={() => <Icon name='delete-outline' color='$danger' />}
icon={() => <Icon name='broom' color='$warning' />}
onPress={(event) => {
event.stopPropagation()
onDelete()
}}
accessibilityLabel='Delete download'
aria-label='Clear download'
/>
</XStack>
</Pressable>
@@ -506,14 +506,13 @@ const SelectionReviewBanner = ({
</XStack>
<Button
size='$3'
backgroundColor='$warning'
borderColor='$warning'
borderWidth={1}
color='white'
icon={() => <Icon name='delete-outline' color='$color' />}
icon={() => <Icon small name='broom' color='$warning' />}
onPress={onDelete}
>
Delete ({formatBytes(selectedBytes)})
<Text bold color={'$warning'}>{`Clear ${formatBytes(selectedBytes)}`}</Text>
</Button>
</YStack>
</Card>

View File

@@ -33,11 +33,10 @@ export type BaseStackParamList = {
InstantMix: {
item: BaseItemDto
mix: BaseItemDto[]
}
Tracks: {
tracksInfiniteQuery: UseInfiniteQueryResult<BaseItemDto[], Error>
tracksInfiniteQuery: UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error>
}
}

18
src/stores/auto/index.ts Normal file
View File

@@ -0,0 +1,18 @@
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
type AutoStore = {
isConnected: boolean
setIsConnected: (connected: boolean) => void
}
export const useAutoStore = create<AutoStore>()(
devtools((set) => ({
isConnected: false,
setIsConnected: (connected: boolean) => set({ isConnected: connected }),
})),
)
const useAutoIsConnected = () => useAutoStore((state) => state.isConnected)
export default useAutoIsConnected

View File

@@ -70,7 +70,7 @@ export const useIsDownloading = (items: BaseItemDto[]) => {
return (
items.length !== 0 &&
(pendingDownloads.length !== 0 || currentDownloads.length !== 0) &&
items.filter((item) => downloadQueue.has(item.Id)).length === items.length
items.filter((item) => downloadQueue.has(item.Id)).length > 0
)
}