Merge branch 'main' into feature/websockets
12
.github/ISSUE_TEMPLATE/01_report_issue.yaml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/build-android.yml
vendored
@@ -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 }}
|
||||
|
||||
2
.github/workflows/build-ios.yml
vendored
@@ -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 }}
|
||||
|
||||
16
.github/workflows/maestro-test.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/publish-beta.yml
vendored
@@ -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/
|
||||
|
||||
5
.github/workflows/publish-ota-update-pr.yml
vendored
@@ -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 }}
|
||||
|
||||
|
||||
2
.github/workflows/run-jest-test-suite.yml
vendored
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)\
|
||||
|
||||
@@ -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
@@ -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=="],
|
||||
|
||||
3
index.js
@@ -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)
|
||||
|
||||
@@ -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)",
|
||||
|
||||
676
ios/Podfile.lock
@@ -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',
|
||||
|
||||
@@ -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
@@ -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',
|
||||
},
|
||||
}))
|
||||
40
package.json
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
BIN
screenshots/add_to_playlist.png
Normal file
|
After Width: | Height: | Size: 488 KiB |
|
Before Width: | Height: | Size: 1005 KiB After Width: | Height: | Size: 755 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 875 KiB |
|
Before Width: | Height: | Size: 999 KiB After Width: | Height: | Size: 696 KiB |
|
Before Width: | Height: | Size: 488 KiB After Width: | Height: | Size: 772 KiB |
|
Before Width: | Height: | Size: 622 KiB After Width: | Height: | Size: 600 KiB |
@@ -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(),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
18
src/api/queries/instant-mix/index.ts
Normal 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
|
||||
9
src/api/queries/instant-mix/keys.ts
Normal 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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum SuggestionQueryKeys {
|
||||
InfiniteArtistSuggestions,
|
||||
SearchSuggestions,
|
||||
SimilarItems,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
100
src/components/Album/footer.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
170
src/components/Album/header.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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%',
|
||||
}}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'} />
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
@@ -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)} />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
})
|
||||
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}: {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
3
src/screens/types.d.ts
vendored
@@ -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
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||