Merge branch 'main' into fixing-the-artist-player-link

This commit is contained in:
skalthoff
2025-12-20 23:41:21 -08:00
committed by GitHub
99 changed files with 2250 additions and 2057 deletions

View File

@@ -16,12 +16,12 @@ jobs:
runs-on: macos-15
steps:
- name: 🛒 Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 💎 Set up Ruby
uses: ruby/setup-ruby@v1

View File

@@ -14,12 +14,12 @@ jobs:
steps:
- name: 🧾 Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 📦 Install dependencies
run: bun i

View File

@@ -17,12 +17,12 @@ jobs:
runs-on: macos-15
steps:
- name: 🛒 Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 💬 Echo package.json version to Github ENV
run: echo VERSION_NUMBER=$(bun -p "require('./package.json').version") >> $GITHUB_ENV

View File

@@ -11,36 +11,36 @@ concurrency:
jobs:
build-android:
runs-on: macos-15
if: github.repository == 'Jellify-Music/App'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.setver.outputs.version }}
steps:
- name: 🛒 Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 💎 Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: |
node_modules
~/.gradle/caches
~/.gradle/wrapper
~/.cache/turbo
android/.gradle
android/app/build
key: ${{ runner.os }}-gradle-turbo-${{ hashFiles('**/bun.lock', '**/build.gradle') }}
key: ${{ runner.os }}-bun-turbo-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-gradle-turbo-
${{ runner.os }}-bun-turbo-
- name: ☕ Setup JDK 17
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: 🐘 Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: 🍎 Run bun init-android
run: bun i
@@ -65,18 +65,39 @@ jobs:
run-maestro-tests:
if: github.repository == 'Jellify-Music/App'
runs-on: ubuntu-latest
needs: build-android
steps:
- name: 🛒 Checkout
uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
tool-cache: true
android: false
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: 🖥 Setup Bun 1.3.2
- name: 🛒 Checkout
uses: actions/checkout@v6
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- uses: actions/cache@v3
with:
path: |
node_modules
~/.cache/turbo
key: ${{ runner.os }}-bun-turbo-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-turbo-
- name: Installing Maestro
shell: bash
@@ -93,10 +114,6 @@ jobs:
with:
name: android-artifacts
path: artifacts/
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4' # Not needed with a .ruby-version, .tool-versions or mise.toml
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Enable KVM group perms
shell: bash
@@ -113,28 +130,29 @@ jobs:
target: google_apis
arch: x86_64
profile: Nexus 6
ram-size: '8192M'
heap-size: '4096M'
disk-size: '10G'
cores: '4'
disable-animations: false
ram-size: '3072M'
heap-size: '1024M'
disk-size: '4G'
cores: '3'
disable-animations: true
avd-name: e2e_emulator
script: bash scripts/maestro-android-retry.sh "https://jellyfin.jellify.app" "jerry"
- name: 🗣️ Notify Success on Discord
if: success()
if: success() && github.repository == 'Jellify-Music/App'
run: |
bun scripts/sendDiscordMessage.js "__**## ✅ Maestro Test Passed**__All checks completed successfully!"
env:
DISCORD_WEBHOOK_URL: ${{ secrets.MAESTRO_WEBHOOK_RESULTS }}
- name: 🗣️ Notify Failure on Discord
if: failure()
if: failure() && github.repository == 'Jellify-Music/App'
run: |
bun scripts/sendDiscordMessage.js "__**## ❌ Maestro Test Failed**__Some tests did not pass."
env:
DISCORD_WEBHOOK_URL: ${{ secrets.MAESTRO_WEBHOOK_RESULTS }}
- name: Store tests result
uses: actions/upload-artifact@v4.3.4
if: always()

View File

@@ -30,15 +30,15 @@ jobs:
release_notes: ${{ steps.set-output.outputs.release_notes }}
steps:
- name: 📦 Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.SIGNING_REPO_PAT }}
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 🧠 Collect commit messages
id: commits
run: |
@@ -80,14 +80,14 @@ jobs:
version: ${{ steps.setver.outputs.version }}
steps:
- name: 🛒 Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
token: ${{ secrets.SIGNING_REPO_PAT }}
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 💎 Set up Ruby
uses: ruby/setup-ruby@v1
@@ -178,14 +178,14 @@ jobs:
steps:
- name: 🛒 Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
token: ${{ secrets.SIGNING_REPO_PAT }}
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 🍎 Setup Xcode
uses: ./.github/actions/setup-xcode
@@ -280,10 +280,10 @@ jobs:
exit 1
fi
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 📦 Install dependencies
run: bun i

View File

@@ -12,14 +12,12 @@ jobs:
runs-on: macos-15
steps:
- name: 🛒 Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.SIGNING_REPO_PAT }}
uses: actions/checkout@v6
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 🥟 Run bun
run: bun i
@@ -29,7 +27,5 @@ jobs:
git config --global user.email "violet@cosmonautical.cloud"
git config --global user.name "anultravioletaurora"
- name: 🤖 Publish Android Update
- name: 🤖 Publish Update
run: bun run sendOTA:PR ${{ github.event.pull_request.number }}
env:
SIGNING_REPO_PAT: ${{ secrets.SIGNING_REPO_PAT }}

View File

@@ -7,14 +7,14 @@ jobs:
runs-on: macos-15
steps:
- name: 🛒 Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
token: ${{ secrets.SIGNING_REPO_PAT }}
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 🥟 Run bun install
run: bun i

View File

@@ -14,12 +14,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 🛒 Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: 🖥 Setup Bun 1.3.2
- name: 🖥 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2
bun-version: 1.3.4
- name: 📦 Cache dependencies
uses: actions/cache@v4

View File

@@ -4,15 +4,10 @@
[![Latest Version](https://img.shields.io/github/package-json/version/anultravioletaurora/jellify?label=Latest%20Version&color=indigo)](https://github.com/anultravioletaurora/Jellify/releases) [![iTunes App Store](https://img.shields.io/itunes/v/6736884612?logo=app-store&logoColor=white&label=Apple%20App%20Store&labelColor=%60&color=blue)](https://apps.apple.com/us/app/jellify/id6736884612) [![Google Play](https://img.shields.io/badge/Google%20Play-Download-red?logo=googleplay&logoColor=white)](https://play.google.com/store/apps/details?id=com.cosmonautical.jellify)
[![Sponsors](https://img.shields.io/github/sponsors/anultravioletaurora?label=Project%20Sponsors&color=magenta)](https://github.com/sponsors/anultravioletaurora) [![Patreon](https://img.shields.io/badge/Patreon-F96854?logo=patreon&logoColor=white)](https://patreon.com/anultravioletaurora?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink)
[![Sponsors](https://img.shields.io/github/sponsors/anultravioletaurora?label=Project%20Sponsors&color=magenta)](https://github.com/sponsors/anultravioletaurora) [![Patreon](https://img.shields.io/badge/Patreon-F96854?logo=patreon&logoColor=white)](https://patreon.com/anultravioletaurora) [![Ko-Fi](https://img.shields.io/badge/ko--fi-rgb(94%2C94%2C94)?logo=ko-fi&logoColor=white&color=rgb(243%2C110%2C60))](https://ko-fi.com/jellify)
## Quick Links
[TestFlight](https://testflight.apple.com/join/etVSc7ZQ)
[![Discord Server](https://dcbadge.limes.pink/api/server/https://discord.gg/yf8fBatktn)](https://discord.gg/yf8fBatktn)
[![Discord](https://img.shields.io/discord/1351285328400351344?logo=discord&logoColor=white&label=Discord&labelColor=rgb(94%2C%2094%2C%2094)&color=rgb(89%2C100%2C238))](https://discord.gg/jellify)
## Contents
@@ -138,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>
---
@@ -173,7 +168,7 @@ Install via [Altstore](https://altstore.io) or your favorite sideloading utility
### Current
- Available via [Play Store](https://play.google.com/store/apps/details?id=com.cosmonautical.jellify&pcampaignid=web_share), [App Store](https://apps.apple.com/us/app/jellify/id6736884612), [Testflight](https://testflight.apple.com/join/etVSc7ZQ), and Android APKs
- Available via [Play Store](https://play.google.com/store/apps/details?id=com.cosmonautical.jellify), [App Store](https://apps.apple.com/us/app/jellify/id6736884612), [Testflight](https://testflight.apple.com/join/etVSc7ZQ), and Android APKs
- APKs are associated with each [release](https://github.com/anultravioletaurora/Jellify/releases)
- Light and Dark modes
- Home screen access to previously played tracks, artists, and your playlists
@@ -235,8 +230,8 @@ Install via [Altstore](https://altstore.io) or your favorite sideloading utility
[React Navigation](https://reactnavigation.org/)\
[React Native Blurhash](https://github.com/mrousavy/react-native-blurhash)\
[React Native CarPlay](https://github.com/birkir/react-native-carplay)\
[React Native Draggable Flatlist](https://github.com/computerjazz/react-native-draggable-flatlist)\
[React Native Nitro Image](https://github.com/mrousavy/react-native-nitro-image)\
[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)
@@ -274,7 +269,7 @@ This is undoubtedly a passion project of [mine](https://github.com/anultraviolet
## Support the Project
You can support _Jellify_ development via [Patreon](https://patreon.com/anultravioletaurora?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink) or [GitHub Sponsors](https://github.com/sponsors/anultravioletaurora) starting at $1.
You can support _Jellify_ development via [Patreon](https://patreon.com/anultravioletaurora) or [GitHub Sponsors](https://github.com/sponsors/anultravioletaurora) starting at $1.
Paid supporters will be recognized by having their name displayed within the Settings.
@@ -308,7 +303,7 @@ Paid supporters will be recognized by having their name displayed within the Set
- Trevor (Android)
- [Laine](https://github.com/lainie-ftw) (Android)
- [Jordan](https://github.com/jordanbleu) (iOS)
- My best(est) friend [Alyssa](https://www.instagram.com/uhh.lyssarae?igsh=MTRmczExempnbjBwZw==), for your design knowledge and for making various artwork for _Jellify_.
- My best(est) friend [Alyssa](https://www.instagram.com/uhh.lyssarae), for your design knowledge and for making various artwork for _Jellify_.
- Youve been instrumental in shaping its user experience, my rock during development, and an overall inspiration in my life

View File

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

View File

@@ -8,6 +8,7 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate
import com.reactnative.googlecast.api.RNGCCastContext
import android.os.Bundle
import androidx.annotation.Nullable
import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory
@@ -27,7 +28,9 @@ class MainActivity : ReactActivity() {
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
override fun onCreate(@Nullable savedInstanceState: Bundle?) {
override fun onCreate(savedInstanceState: Bundle?) {
// react-native-screens fragment factory for proper fragment restoration
supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()
super.onCreate(savedInstanceState)
// lazy load Google Cast context (if supported on this device)
RNGCCastContext.getSharedInstance(this)

View File

@@ -4,7 +4,6 @@ import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost

434
bun.lock
View File

@@ -11,29 +11,29 @@
"@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.10",
"@react-navigation/material-top-tabs": "7.4.7",
"@react-navigation/native": "7.1.23",
"@react-navigation/native-stack": "7.8.4",
"@sentry/react-native": "7.6.0",
"@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.139.1",
"@tanstack/query-async-storage-persister": "5.89.0",
"@tanstack/react-query": "5.89.0",
"@tanstack/react-query-persist-client": "5.89.0",
"@tamagui/config": "1.141.4",
"@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",
"invert-color": "^2.0.0",
"lodash": "^4.17.21",
"openai": "5.21.0",
"react": "19.1.1",
"react-native": "0.82.1",
"react": "19.2.0",
"react-native": "0.83.0",
"react-native-background-actions": "^4.0.1",
"react-native-blob-util": "^0.22.2",
"react-native-blurhash": "2.1.1",
"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",
@@ -43,48 +43,48 @@
"react-native-google-cast": "^4.9.1",
"react-native-haptic-feedback": "^2.3.3",
"react-native-linear-gradient": "^2.8.3",
"react-native-mmkv": "3.3.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.7.2",
"react-native-nitro-ota": "0.8.2",
"react-native-pager-view": "^7.0.2",
"react-native-reanimated": "4.1.5",
"react-native-reanimated": "4.1.6",
"react-native-safe-area-context": "5.6.2",
"react-native-screens": "4.18.0",
"react-native-screens": "4.19.0",
"react-native-sortables": "1.9.4",
"react-native-text-ticker": "^1.15.0",
"react-native-toast-message": "^2.3.3",
"react-native-track-player": "5.0.0-alpha0",
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-worklets": "0.6.1",
"react-native-worklets": "^0.7.1",
"react-native-worklets-core": "^1.6.2",
"ruby": "^0.6.1",
"scheduler": "^0.26.0",
"tamagui": "1.139.1",
"zustand": "^5.0.8",
"tamagui": "1.141.4",
"zustand": "5.0.9",
},
"devDependencies": {
"@babel/core": "^7.28.0",
"@babel/preset-env": "^7.28.0",
"@babel/runtime": "^7.28.0",
"@babel/core": "7.28.5",
"@babel/preset-env": "7.28.5",
"@babel/runtime": "7.28.4",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.32.0",
"@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.82.1",
"@react-native/eslint-config": "0.82.1",
"@react-native/metro-config": "0.82.1",
"@react-native/typescript-config": "0.82.1",
"@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",
"@types/jest": "^30.0.0",
"@types/lodash": "^4.17.20",
"@types/lodash": "^4.17.21",
"@types/node": "^24.2.1",
"@types/react": "^19.1.1",
"@types/react": "19.2.0",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "19.1.0",
"babel-plugin-module-resolver": "^5.0.2",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.33.0",
"eslint": "9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.4",
@@ -92,15 +92,15 @@
"eslint-plugin-react-native": "^5.0.0",
"globals": "^16.3.0",
"husky": "^9.1.7",
"jest": "^30.0.5",
"jest": "30.2.0",
"jscodeshift": "^17.3.0",
"lint-staged": "^16.1.5",
"patch-package": "8.0.0",
"prettier": "^3.6.2",
"react-dom": "^19.1.0",
"prettier": "3.7.4",
"react-dom": "19.2.0",
"react-native-cli-bump-version": "^1.5.1",
"react-test-renderer": "19.1.1",
"typescript": "5.9.2",
"react-test-renderer": "19.2.0",
"typescript": "5.9.3",
},
},
},
@@ -380,7 +380,7 @@
"@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/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
"@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
@@ -530,95 +530,95 @@
"@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.82.1", "", {}, "sha512-B1SRwpntaAcckiatxbjzylvNK562Ayza05gdJCjDQHTiDafa1OABmyB5LHt7qWDOpNkaluD+w11vHF7pBmTpzQ=="],
"@react-native/assets-registry": ["@react-native/assets-registry@0.83.0", "", {}, "sha512-EmGSKDvmnEnBrTK75T+0Syt6gy/HACOTfziw5+392Kr1Bb28Rv26GyOIkvptnT+bb2VDHU0hx9G0vSy5/S3rmQ=="],
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.82.1", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.82.1" } }, "sha512-wzmEz/RlR4SekqmaqeQjdMVh4LsnL9e62mrOikOOkHDQ3QN0nrKLuUDzXyYptVbxQ0IRua4pTm3efJLymDBoEg=="],
"@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-preset": ["@react-native/babel-preset@0.82.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.82.1", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-Olj7p4XIsUWLKjlW46CqijaXt45PZT9Lbvv/Hz698FXTenPKk4k7sy6RGRGZPWO2TCBBfcb73dus1iNHRFSq7g=="],
"@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/codegen": ["@react-native/codegen@0.82.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-ezXTN70ygVm9l2m0i+pAlct0RntoV4afftWMGUIeAWLgaca9qItQ54uOt32I/9dBJvzBibT33luIR/pBG0dQvg=="],
"@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/community-cli-plugin": ["@react-native/community-cli-plugin@0.82.1", "", { "dependencies": { "@react-native/dev-middleware": "0.82.1", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.1", "metro-config": "^0.83.1", "metro-core": "^0.83.1", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-H/eMdtOy9nEeX7YVeEG1N2vyCoifw3dr9OV8++xfUElNYV7LtSmJ6AqxZUUfxGJRDFPQvaU/8enmJlM/l11VxQ=="],
"@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/debugger-frontend": ["@react-native/debugger-frontend@0.82.1", "", {}, "sha512-a2O6M7/OZ2V9rdavOHyCQ+10z54JX8+B+apYKCQ6a9zoEChGTxUMG2YzzJ8zZJVvYf1ByWSNxv9Se0dca1hO9A=="],
"@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.83.0", "", {}, "sha512-7XVbkH8nCjLKLe8z5DS37LNP62/QNNya/YuLlVoLfsiB54nR/kNZij5UU7rS0npAZ3WN7LR0anqLlYnzDd0JHA=="],
"@react-native/debugger-shell": ["@react-native/debugger-shell@0.82.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "fb-dotslash": "0.5.8" } }, "sha512-fdRHAeqqPT93bSrxfX+JHPpCXHApfDUdrXMXhoxlPgSzgXQXJDykIViKhtpu0M6slX6xU/+duq+AtP/qWJRpBw=="],
"@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/dev-middleware": ["@react-native/dev-middleware@0.82.1", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.82.1", "@react-native/debugger-shell": "0.82.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": "^6.2.3" } }, "sha512-wuOIzms/Qg5raBV6Ctf2LmgzEOCqdP3p1AYN4zdhMT110c39TVMbunpBaJxm0Kbt2HQ762MQViF9naxk7SBo4w=="],
"@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/eslint-config": ["@react-native/eslint-config@0.82.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", "@react-native/eslint-plugin": "0.82.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": "^5.2.0", "eslint-plugin-react-native": "^4.0.0" }, "peerDependencies": { "eslint": ">=8", "prettier": ">=2" } }, "sha512-K3xCTEAg8WDd7WpDhQ1hsKbuY3OXaQtqpokeOdgyJag100ZvUX84YIaqDqsVaAZqjA53zCA5PbxerWs6mPA+PQ=="],
"@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-plugin": ["@react-native/eslint-plugin@0.82.1", "", {}, "sha512-PU0ho8pNp24pdegIpYRAwppfO8z7werpoTts2CJ/wXYQ+ryZKa2M31DHW+kl+K3wwwqVqFKAzLh4t3sP/mOqMQ=="],
"@react-native/eslint-plugin": ["@react-native/eslint-plugin@0.83.0", "", {}, "sha512-a0lObGV1/1P6mrekSF+1KpRkdH2fefQ/8fm1kLTUNvR5mae8xXz+U+f+1lsgqqEHtoGHey5Ve5MUkjgj4WnqTQ=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.82.1", "", {}, "sha512-KkF/2T1NSn6EJ5ALNT/gx0MHlrntFHv8YdooH9OOGl9HQn5NM0ZmQSr86o5utJsGc7ME3R6p3SaQuzlsFDrn8Q=="],
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.83.0", "", {}, "sha512-BXZRmfsbgPhEPkrRPjk2njA2AzhSelBqhuoklnv3DdLTdxaRjKYW+LW0zpKo1k3qPKj7kG1YGI3miol6l1GB5g=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.82.1", "", {}, "sha512-tf70X7pUodslOBdLN37J57JmDPB/yiZcNDzS2m+4bbQzo8fhx3eG9QEBv5n4fmzqfGAgSB4BWRHgDMXmmlDSVA=="],
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.83.0", "", {}, "sha512-cVB9BMqlfbQR0v4Wxi5M2yDhZoKiNqWgiEXpp7ChdZIXI0SEnj8WwLwE3bDkyOfF8tCHdytpInXyg/al2O+dLQ=="],
"@react-native/metro-babel-transformer": ["@react-native/metro-babel-transformer@0.82.1", "", { "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.82.1", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-kVQyYxYe1Da7cr7uGK9c44O6vTzM8YY3KW9CSLhhV1CGw7jmohU1HfLaUxDEmYfFZMc4Kj3JsIEbdUlaHMtprQ=="],
"@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-config": ["@react-native/metro-config@0.82.1", "", { "dependencies": { "@react-native/js-polyfills": "0.82.1", "@react-native/metro-babel-transformer": "0.82.1", "metro-config": "^0.83.1", "metro-runtime": "^0.83.1" } }, "sha512-mAY6R3xnDMlmDOrUCAtLTjIkli26DZt4LNVuAjDEdnlv5sHANOr5x4qpMn7ea1p9Q/tpfHLalPQUQeJ8CZH4gA=="],
"@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/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.82.1", "", {}, "sha512-CCfTR1uX+Z7zJTdt3DNX9LUXr2zWXsNOyLbwupW2wmRzrxlHRYfmLgTABzRL/cKhh0Ubuwn15o72MQChvCRaHw=="],
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.83.0", "", {}, "sha512-DG1ELOqQ6RS82R1zEUGTWa/pfSPOf+vwAnQB7Ao1vRuhW/xdd2OPQJyqx5a5QWMYpGrlkCb7ERxEVX6p2QODCA=="],
"@react-native/typescript-config": ["@react-native/typescript-config@0.82.1", "", {}, "sha512-kCTjmBg44p0kqU4xEMg7l6SNJyHWTHuTqiT9MpHasEYcnVpBWyEQsSQAiVKONHwcUWcAktrGVLE1dYGfBmPJ3Q=="],
"@react-native/typescript-config": ["@react-native/typescript-config@0.83.0", "", {}, "sha512-8IcgamT0qoBDL3D8Ho6YHkQrxUMf3fKHkRd6MYDjVKNamz0XtfXNLY/FNnUOolx1HbgMkkwKFcbP3AbIKFxirQ=="],
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.82.1", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.1.1", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-f5zpJg9gzh7JtCbsIwV+4kP3eI0QBuA93JGmwFRd4onQ3DnCjV2J5pYqdWtM95sjSKK1dyik59Gj01lLeKqs1Q=="],
"@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-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.8.10", "", { "dependencies": { "@react-navigation/elements": "^2.9.0", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.23", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-NxKjtlRwkGU3O3hPxpS+Aq7mVNfgtLzBe4xpGjQNphLzklRbxa6Me//m2eKzogpitZhLR2xZb1A49HrLuWe2ww=="],
"@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.4", "", { "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-JM9bkb7fr4P5YUOVEwoAZq3xPeSL9V6Nd1KKTyAwCgGUVhESbSRSy3Ri/PGu6ZcLb/t7/tM1NqP5tV1e1bAwUg=="],
"@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.0", "", { "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.23", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-423uE+q/esaiMbXVLckFOd9MbWG06/vCYOP2hwzEUj9ZwzUgSpsIPovcu78qa8UMuvKD8wkyouM01Wvav1y/KQ=="],
"@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.7", "", { "dependencies": { "@react-navigation/elements": "^2.9.0", "color": "^4.2.3", "react-native-tab-view": "^4.2.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.23", "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0" } }, "sha512-0fv+Ym9kOO7DLf8GRmkt9zNKPTbnYU62ATacv0zirNA+vBDT/fhlE67orUXsQa/nORXlUMvllCaKPf/oyD7UcQ=="],
"@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.23", "", { "dependencies": { "@react-navigation/core": "^7.13.4", "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-V+drzVkoVA8VO83cJ59UYe7dfdnMFpGDAybp7d5O1ufxt321Z5tOtNDOzhMGzHUENqo9QWc4P/HuCUmz7KMy+A=="],
"@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.4", "", { "dependencies": { "@react-navigation/elements": "^2.9.0", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.23", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-7kpYHoZZ81SPtDDG9ttZtI4nXR8GbVsLL1KnT/7RiLkFdqHXlriGpVhG5BKJRS1CYXrGEn40NogYW2+OBplglg=="],
"@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=="],
"@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.24.0", "", { "dependencies": { "@sentry/core": "10.24.0" } }, "sha512-2nLj5TgPc/KkGy7LCW9sBGJT0CT+9U+Vlqa8yl7APd5agzxrpRyTcm4hPBBOw5tw7D4NWWUMulFxtZKZzT/Rcw=="],
"@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.30.0", "", { "dependencies": { "@sentry/core": "10.30.0" } }, "sha512-dVsHTUbvgaLNetWAQC6yJFnmgD0xUbVgCkmzNB7S28wIP570GcZ4cxFGPOkXbPx6dEBUfoOREeXzLqjJLtJPfg=="],
"@sentry-internal/feedback": ["@sentry-internal/feedback@10.24.0", "", { "dependencies": { "@sentry/core": "10.24.0" } }, "sha512-leYFQfgax50sYTEgkcEzPP8lTvtE12nryJSsdtPNym6gmQgA2SN20oSRNlxo1AitNpwNnTkj+ow/Y9ytrJlXUQ=="],
"@sentry-internal/feedback": ["@sentry-internal/feedback@10.30.0", "", { "dependencies": { "@sentry/core": "10.30.0" } }, "sha512-+bnQZ6SNF265nTXrRlXTmq5Ila1fRfraDOAahlOT/VM4j6zqCvNZzmeDD9J6IbxiAdhlp/YOkrG3zbr5vgYo0A=="],
"@sentry-internal/replay": ["@sentry-internal/replay@10.24.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.24.0", "@sentry/core": "10.24.0" } }, "sha512-xqSw3sCu5yxDQFpo/42t1zzxe+6kn542DRwHNBqIBd0CWN7un/j5YIW1Sq/+TdHYGbeG8LzD5UOuvZsT4zF2nQ=="],
"@sentry-internal/replay": ["@sentry-internal/replay@10.30.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.30.0", "@sentry/core": "10.30.0" } }, "sha512-Pj/fMIZQkXzIw6YWpxKWUE5+GXffKq6CgXwHszVB39al1wYz1gTIrTqJqt31IBLIihfCy8XxYddglR2EW0BVIQ=="],
"@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.24.0", "", { "dependencies": { "@sentry-internal/replay": "10.24.0", "@sentry/core": "10.24.0" } }, "sha512-pjNZ+/L/ct0huutkTQrcR+V/v3ICf5wKE8OOB2Dt3DcjNsbLKtUsy9Um6bCbSz/fRI8K/ZFlVLjiIQkMW+WX0Q=="],
"@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.30.0", "", { "dependencies": { "@sentry-internal/replay": "10.30.0", "@sentry/core": "10.30.0" } }, "sha512-RIlIz+XQ4DUWaN60CjfmicJq2O2JRtDKM5lw0wB++M5ha0TBh6rv+Ojf6BDgiV3LOQ7lZvCM57xhmNUtrGmelg=="],
"@sentry/babel-plugin-component-annotate": ["@sentry/babel-plugin-component-annotate@4.6.0", "", {}, "sha512-3soTX50JPQQ51FSbb4qvNBf4z/yP7jTdn43vMTp9E4IxvJ9HKJR7OEuKkCMszrZmWsVABXl02msqO7QisePdiQ=="],
"@sentry/babel-plugin-component-annotate": ["@sentry/babel-plugin-component-annotate@4.6.1", "", {}, "sha512-aSIk0vgBqv7PhX6/Eov+vlI4puCE0bRXzUG5HdCsHBpAfeMkI8Hva6kSOusnzKqs8bf04hU7s3Sf0XxGTj/1AA=="],
"@sentry/browser": ["@sentry/browser@10.24.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.24.0", "@sentry-internal/feedback": "10.24.0", "@sentry-internal/replay": "10.24.0", "@sentry-internal/replay-canvas": "10.24.0", "@sentry/core": "10.24.0" } }, "sha512-kKSNYupPIIk02+4OVR13qpJ8/uzsf6SrCzgxr6EvdK8qEuGYLJyM6lLJze/C5BZTSsam6UGAfahrSI1K5il8oQ=="],
"@sentry/browser": ["@sentry/browser@10.30.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.30.0", "@sentry-internal/feedback": "10.30.0", "@sentry-internal/replay": "10.30.0", "@sentry-internal/replay-canvas": "10.30.0", "@sentry/core": "10.30.0" } }, "sha512-7M/IJUMLo0iCMLNxDV/OHTPI0WKyluxhCcxXJn7nrCcolu8A1aq9R8XjKxm0oTCO8ht5pz8bhGXUnYJj4eoEBA=="],
"@sentry/cli": ["@sentry/cli@2.58.0", "", { "dependencies": { "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", "progress": "^2.0.3", "proxy-from-env": "^1.1.0", "which": "^2.0.2" }, "optionalDependencies": { "@sentry/cli-darwin": "2.58.0", "@sentry/cli-linux-arm": "2.58.0", "@sentry/cli-linux-arm64": "2.58.0", "@sentry/cli-linux-i686": "2.58.0", "@sentry/cli-linux-x64": "2.58.0", "@sentry/cli-win32-arm64": "2.58.0", "@sentry/cli-win32-i686": "2.58.0", "@sentry/cli-win32-x64": "2.58.0" }, "bin": { "sentry-cli": "bin/sentry-cli" } }, "sha512-ywfV2uYkNaW5BGFBgIEX+urkxWtY03GYKN08OLYJpfJeOWl5tzxAKKg+AkMZqnqsDqjCf8gLjZh7sF4jY+ZE1Q=="],
"@sentry/cli": ["@sentry/cli@2.58.4", "", { "dependencies": { "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", "progress": "^2.0.3", "proxy-from-env": "^1.1.0", "which": "^2.0.2" }, "optionalDependencies": { "@sentry/cli-darwin": "2.58.4", "@sentry/cli-linux-arm": "2.58.4", "@sentry/cli-linux-arm64": "2.58.4", "@sentry/cli-linux-i686": "2.58.4", "@sentry/cli-linux-x64": "2.58.4", "@sentry/cli-win32-arm64": "2.58.4", "@sentry/cli-win32-i686": "2.58.4", "@sentry/cli-win32-x64": "2.58.4" }, "bin": { "sentry-cli": "bin/sentry-cli" } }, "sha512-ArDrpuS8JtDYEvwGleVE+FgR+qHaOp77IgdGSacz6SZy6Lv90uX0Nu4UrHCQJz8/xwIcNxSqnN22lq0dH4IqTg=="],
"@sentry/cli-darwin": ["@sentry/cli-darwin@2.58.0", "", { "os": "darwin" }, "sha512-dI8+85N2xNsQeJZBbfGkjFScYH0xP/8+TDgoA5YiWWxsD/qSlWv1pf2VCR83smMyfcjIkDiPYIxBDticD67skQ=="],
"@sentry/cli-darwin": ["@sentry/cli-darwin@2.58.4", "", { "os": "darwin" }, "sha512-kbTD+P4X8O+nsNwPxCywtj3q22ecyRHWff98rdcmtRrvwz8CKi/T4Jxn/fnn2i4VEchy08OWBuZAqaA5Kh2hRQ=="],
"@sentry/cli-linux-arm": ["@sentry/cli-linux-arm@2.58.0", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm" }, "sha512-QxBWSQkm2OL8d0XXTUOcX5RYZzZGkMw48ubU4g/c4rlT06PuJV56Z03jsMQdJWUDzKmVYoJdvFV/whxYIkwmWw=="],
"@sentry/cli-linux-arm": ["@sentry/cli-linux-arm@2.58.4", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm" }, "sha512-rdQ8beTwnN48hv7iV7e7ZKucPec5NJkRdrrycMJMZlzGBPi56LqnclgsHySJ6Kfq506A2MNuQnKGaf/sBC9REA=="],
"@sentry/cli-linux-arm64": ["@sentry/cli-linux-arm64@2.58.0", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm64" }, "sha512-Fso5GImxQOigZqLHAHhz85w71zxS1bvL52PI/tcjadmKrIaJdD3ANukC0UcKyKuj9xhr/k1ufNR7V+2BD16kmg=="],
"@sentry/cli-linux-arm64": ["@sentry/cli-linux-arm64@2.58.4", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "arm64" }, "sha512-0g0KwsOozkLtzN8/0+oMZoOuQ0o7W6O+hx+ydVU1bktaMGKEJLMAWxOQNjsh1TcBbNIXVOKM/I8l0ROhaAb8Ig=="],
"@sentry/cli-linux-i686": ["@sentry/cli-linux-i686@2.58.0", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "ia32" }, "sha512-Av+T5YwuTtbOpe/Fyr/lsbl5XIZTFspHCiAt4Kgtllme6T1ASIDhQDXDh/OVJ8So4pHkToTn3iH8mm8vLqBqOA=="],
"@sentry/cli-linux-i686": ["@sentry/cli-linux-i686@2.58.4", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "ia32" }, "sha512-NseoIQAFtkziHyjZNPTu1Gm1opeQHt7Wm1LbLrGWVIRvUOzlslO9/8i6wETUZ6TjlQxBVRgd3Q0lRBG2A8rFYA=="],
"@sentry/cli-linux-x64": ["@sentry/cli-linux-x64@2.58.0", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "x64" }, "sha512-AxK0eqZbHn0NGWsAE8bzt/iRMMUlqsx77kru/TIBQy9cMMJaq+rLb63W7HWXln4ER32nPZYx+JuhHD9UNiAFHA=="],
"@sentry/cli-linux-x64": ["@sentry/cli-linux-x64@2.58.4", "", { "os": [ "linux", "android", "freebsd", ], "cpu": "x64" }, "sha512-d3Arz+OO/wJYTqCYlSN3Ktm+W8rynQ/IMtSZLK8nu0ryh5mJOh+9XlXY6oDXw4YlsM8qCRrNquR8iEI1Y/IH+Q=="],
"@sentry/cli-win32-arm64": ["@sentry/cli-win32-arm64@2.58.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-lIRTfGjD1TQIOuFh4rJGWt3zXyeXAlfoYYQbzG/rP6gXstiGENQtfEXZyKT+wlIGSqtbBGVfL8xp65ryjbXSgQ=="],
"@sentry/cli-win32-arm64": ["@sentry/cli-win32-arm64@2.58.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-bqYrF43+jXdDBh0f8HIJU3tbvlOFtGyRjHB8AoRuMQv9TEDUfENZyCelhdjA+KwDKYl48R1Yasb4EHNzsoO83w=="],
"@sentry/cli-win32-i686": ["@sentry/cli-win32-i686@2.58.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-7VdB3QZ/3t2FABgIwRP2SoJcDmZaPPPZofVmJem+FgeONeLOUvHQw9WSLG4y5Dfc9yi5wO31H1ClW4uxv8EtuA=="],
"@sentry/cli-win32-i686": ["@sentry/cli-win32-i686@2.58.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-3triFD6jyvhVcXOmGyttf+deKZcC1tURdhnmDUIBkiDPJKGT/N5xa4qAtHJlAB/h8L9jgYih9bvJnvvFVM7yug=="],
"@sentry/cli-win32-x64": ["@sentry/cli-win32-x64@2.58.0", "", { "os": "win32", "cpu": "x64" }, "sha512-uItx4P4v9cKbgVbOpuShvIV8g42qLmZorPHwg3pYUu78c85xAWrmiXL+0JKNUf5JVBEHeHB+rIu08AZfDMhxig=="],
"@sentry/cli-win32-x64": ["@sentry/cli-win32-x64@2.58.4", "", { "os": "win32", "cpu": "x64" }, "sha512-cSzN4PjM1RsCZ4pxMjI0VI7yNCkxiJ5jmWncyiwHXGiXrV1eXYdQ3n1LhUYLZ91CafyprR0OhDcE+RVZ26Qb5w=="],
"@sentry/core": ["@sentry/core@10.24.0", "", {}, "sha512-apJ1NtCK/Kt5uTytee+4rhhcTm4u3+z0bESH8GNMXMcuJ/A3Rvy3HUh+gqCx4BTOR0Sa44dbMvJcm/ewO+mzVg=="],
"@sentry/core": ["@sentry/core@10.30.0", "", {}, "sha512-IfNuqIoGVO9pwphwbOptAEJJI1SCAfewS5LBU1iL7hjPBHYAnE8tCVzyZN+pooEkQQ47Q4rGanaG1xY8mjTT1A=="],
"@sentry/react": ["@sentry/react@10.24.0", "", { "dependencies": { "@sentry/browser": "10.24.0", "@sentry/core": "10.24.0", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "sha512-HW83v7LC5E06H/cYtU4fnlOV5fykNl5QkrOoZzKrYfAUCh4T11gjd4RvlvI+WaXb6nhD+gW2YLu95iIRHid/TA=="],
"@sentry/react": ["@sentry/react@10.30.0", "", { "dependencies": { "@sentry/browser": "10.30.0", "@sentry/core": "10.30.0", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "sha512-3co0QwAU9VrCVBWgpRf/4G19MwzR+DM0sDe9tgN7P3pv/tMlEHhnPFv88nPfuSa2W8uVCpHehvV+GnUPF4V7Ag=="],
"@sentry/react-native": ["@sentry/react-native@7.6.0", "", { "dependencies": { "@sentry/babel-plugin-component-annotate": "4.6.0", "@sentry/browser": "10.24.0", "@sentry/cli": "2.58.0", "@sentry/core": "10.24.0", "@sentry/react": "10.24.0", "@sentry/types": "10.24.0" }, "peerDependencies": { "expo": ">=49.0.0", "react": ">=17.0.0", "react-native": ">=0.65.0" }, "optionalPeers": ["expo"], "bin": { "sentry-expo-upload-sourcemaps": "scripts/expo-upload-sourcemaps.js" } }, "sha512-oL6Tl6B+vHP/OtHt9LkhZMg+mntjn2mFTzqnPCggXDIPxn5cqZ41154wA7d33i6JLKiXiK02EHJlnImdb4s06w=="],
"@sentry/react-native": ["@sentry/react-native@7.8.0", "", { "dependencies": { "@sentry/babel-plugin-component-annotate": "4.6.1", "@sentry/browser": "10.30.0", "@sentry/cli": "2.58.4", "@sentry/core": "10.30.0", "@sentry/react": "10.30.0", "@sentry/types": "10.30.0" }, "peerDependencies": { "expo": ">=49.0.0", "react": ">=17.0.0", "react-native": ">=0.65.0" }, "optionalPeers": ["expo"], "bin": { "sentry-expo-upload-sourcemaps": "scripts/expo-upload-sourcemaps.js" } }, "sha512-0YMD0ObuGPbJVfHCBaYTfmRtS7tUd64W2GMNPA3b6rGlVlMQlL7bfdkCUouVBZzFDJYLV8ik1PzJKPWunKHCvw=="],
"@sentry/types": ["@sentry/types@10.24.0", "", { "dependencies": { "@sentry/core": "10.24.0" } }, "sha512-hLcLS9mFVqZGbkVgkvnkFvwbqkxSv2vKI6zYNJ+3ZW6PWyS82KBEHgedwxtg2F6lCGWQHQxINKjp0GZYKxtRjg=="],
"@sentry/types": ["@sentry/types@10.30.0", "", { "dependencies": { "@sentry/core": "10.30.0" } }, "sha512-tSyzG/JunWjbuQDDwP3DKgt8KP23ZSuNUEudMSv2jCF/956o8ksamPeidCTSVMXoEyTt5tvimWNeNvUFIFq3EA=="],
"@shopify/flash-list": ["@shopify/flash-list@2.2.0", "", { "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-mL61IofcfBNRZ/qazIf+pghGULkcZUQ7EZNldH1JBbIjtDb25ADSiQrt62ZTnRz0H5+bPFEZUmN9+WChHzX8pw=="],
@@ -634,211 +634,211 @@
"@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.139.1", "", { "dependencies": { "@tamagui/collapsible": "1.139.1", "@tamagui/collection": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/polyfill-dev": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/text": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-direction": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-hH3/ZZwDhY6sPIhSzG3PjmH9B2rkV8GPDDOwwSBSCck2B2J/Z3fT8XLH99OtPIq+VeexQrAHiL/yaZz17lUFsQ=="],
"@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/adapt": ["@tamagui/adapt@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/portal": "1.139.1", "@tamagui/z-index-stack": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-w8od60YREZ1kKbLZ5sncN2/7AonnBS4VU2yW9XAFxipV2jPtihpCETjA1fKi6IYe/m6SbLLe8XQuchMyxX9/Lw=="],
"@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/alert-dialog": ["@tamagui/alert-dialog@1.139.1", "", { "dependencies": { "@tamagui/animate-presence": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/dialog": "1.139.1", "@tamagui/dismissable": "1.139.1", "@tamagui/focus-scope": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/polyfill-dev": "1.139.1", "@tamagui/popper": "1.139.1", "@tamagui/portal": "1.139.1", "@tamagui/remove-scroll": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/text": "1.139.1", "@tamagui/use-controllable-state": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-oWepuzhaSYHWH1yofaByyWk4+znAwIc1cAQY25+gBEEvy1lGfiz6utkGvh6eOzX7DlC8IXJYVnVGq2Jv+TRiGw=="],
"@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/animate": ["@tamagui/animate@1.139.1", "", { "dependencies": { "@tamagui/animate-presence": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-r+XQVbQWQUIs4FmubIsxECvfE7tShrZK5Hs2d03HzA4+8LNiEJcmhAFf6nB+0ILKmT0qBXm61+0Ozrrhfqx/DQ=="],
"@tamagui/animate": ["@tamagui/animate@1.141.4", "", { "dependencies": { "@tamagui/animate-presence": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-ysfxPYHAXSVsmktGkIkvUoNQ4xRoo4Z7LyP3sZIrR/8X1o13hl6m7rBXip7Cq/l6YY63f/F7UdbCRDXWwIUpiQ=="],
"@tamagui/animate-presence": ["@tamagui/animate-presence@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/use-constant": "1.139.1", "@tamagui/use-force-update": "1.139.1", "@tamagui/use-presence": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-PmBPQ2x1eKhHsjXJ7CbNbprN4KZjEFpvXKrlnRtQoykEtc0Ya2fZV0icVSgAitlGMUJWROLXKbiFPbiV6IUxUg=="],
"@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/animations-css": ["@tamagui/animations-css@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/cubic-bezier-animator": "1.139.1", "@tamagui/use-presence": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-Dr7E7gwZSVkhXQcn2PTY7jAHLBly8VfPRFfBRBda2nohA/VlQIYCODlxhbVCJvNKqG3UzmSF29wxGM2S2MlKeA=="],
"@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-moti": ["@tamagui/animations-moti@1.139.1", "", { "dependencies": { "@tamagui/core": "1.139.1", "@tamagui/use-presence": "1.139.1", "moti": "^0.30.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-k0jEKnzZQjyZU64wDPzKSXoA+j7vArGTtlKUO4SsU3p6mB5urNbFdLkNolOzI1uZH8QFhoOLE2fgRPauf6ToXQ=="],
"@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-react-native": ["@tamagui/animations-react-native@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/use-presence": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-SX2I1wiAhFxpp2oRPaQjEhu/lF/JTwsmaDU2k6HerzL/mB6VaLj16HXFy+ywMSTwgqIDPYTWTD4Mvl2HX/Wegw=="],
"@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/avatar": ["@tamagui/avatar@1.139.1", "", { "dependencies": { "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/image": "1.139.1", "@tamagui/shapes": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/text": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Kvgm4f8XVrnFwuAd3t1FDZ69nhJb2JC8Ty2ACHAGrIpWRp2eO4U24dATEdGftifMpK+8ukBdYQDBK0iMSsfHuw=="],
"@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/button": ["@tamagui/button@1.139.1", "", { "dependencies": { "@tamagui/config-default": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/font-size": "1.139.1", "@tamagui/get-button-sized": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/helpers-tamagui": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/text": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-gOlRoxKuLvpPotEIkAyHbncYx6hdPYYhAt/tqgJuWFEm2MQje8FJjhvnVGVwAXehDTx+6wc5LkxgNGpR7VSr4g=="],
"@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/card": ["@tamagui/card@1.139.1", "", { "dependencies": { "@tamagui/create-context": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-7w6dVESuD5FKGxYlOxAu1ednNBD0LQKvFHMRKYL/C1FdfJrCGGrMoGAWyjTO1sS4IelhCmftg+QhHDZsZAvdqA=="],
"@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/checkbox": ["@tamagui/checkbox@1.139.1", "", { "dependencies": { "@tamagui/checkbox-headless": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/font-size": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/helpers-tamagui": "1.139.1", "@tamagui/label": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-previous": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-z6BL5Kpn3T1QfSKXFsQevSt1AR8mRfh9A/dzdPmL8d0RzhMCtpJ/rgWAt+xRXbr9UNzzeHT/EF2/sC+hfM11Cw=="],
"@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-headless": ["@tamagui/checkbox-headless@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/label": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-previous": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-qU6hDYWN0Axiq1kZ4A+y237UhPGc9WJtrRKunhRvII2MCUeFJZ/IHcsId2hqvLZvbmS9bo/2P/yOCePt/IA/Vg=="],
"@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/collapsible": ["@tamagui/collapsible@1.139.1", "", { "dependencies": { "@tamagui/animate-presence": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/polyfill-dev": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-1HNsDBsEzEoefHNzQJXwtOjoojzDZi3aKredu5DxWRCx6W/GiOYH8EqT2SVgXhXrYwh0EjYnrELlw6D1UNTDJg=="],
"@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/collection": ["@tamagui/collection@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/polyfill-dev": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-controllable-state": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-5S4Hgh2vKcd+YXw7A8zGwipqDdOi6wfiz61Picy+KOcn48YNLcCMFjGUCUFN5i27yflNgev0TC1iUuc+wPCBVA=="],
"@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/colors": ["@tamagui/colors@1.139.1", "", {}, "sha512-uRflr8TkunrVNdnBgEJ6Zlk/4kWYy1dlxYgT0bkG5MLfDlu4OlA4rVGwirCxfwIwrXov/E2/2qjAIUbBnxr1Zg=="],
"@tamagui/colors": ["@tamagui/colors@1.141.4", "", {}, "sha512-iFDE1DjvU0YmhBYKrjvxQn02mstnwfsEZoJF5MppGhYLp3jfferQVpO+JuWre6heSLiy1R1bM7mO7uHsNdh3QA=="],
"@tamagui/compose-refs": ["@tamagui/compose-refs@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-mapsTVe30yl8+iHRxK05L5A762W8oQQjXnnLvan3EEV684YoGNGBO09aZOXp/ELGvbyjRsFHkMrbB8YbjSPdLQ=="],
"@tamagui/compose-refs": ["@tamagui/compose-refs@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-Yx15Kp0SxtT0YxT/jLLaKlz518fIM7C18ryJzFzm5kYwe9TzIJBPUzIjlgYyCQhJJ8WwqQbkjN0FEg2J6mmgAw=="],
"@tamagui/config": ["@tamagui/config@1.139.1", "", { "dependencies": { "@tamagui/animations-css": "1.139.1", "@tamagui/animations-moti": "1.139.1", "@tamagui/animations-react-native": "1.139.1", "@tamagui/colors": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/font-inter": "1.139.1", "@tamagui/font-silkscreen": "1.139.1", "@tamagui/react-native-media-driver": "1.139.1", "@tamagui/shorthands": "1.139.1", "@tamagui/theme-builder": "1.139.1", "@tamagui/themes": "1.139.1", "@tamagui/web": "1.139.1" } }, "sha512-XH3SbBYVXkrnM9Q6LYQPJBTEQ6WvKNqECJILM0GmxUKUaNaF3S3o+bhvja7IIINEbyyYIJQfwjvj/H4kAPsRvg=="],
"@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-default": ["@tamagui/config-default@1.139.1", "", { "dependencies": { "@tamagui/animations-css": "1.139.1", "@tamagui/animations-react-native": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/shorthands": "1.139.1", "@tamagui/web": "1.139.1" } }, "sha512-4D0oYWFY0DMUaaTvaAtHXj+/3NTm0CFLwcOW5MtcIFN8HvR28Q/gyOpfzNe000z9OUArK5r5nH5jB1mnCMgXOw=="],
"@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/constants": ["@tamagui/constants@1.139.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-jwhbmR6R2gA1zcPxWvHjL0+gQdMuVmEs5XI5fgNb6kAqJ5VVfCwqq7mDtjDNDF8bW3QmZpVCZRpudyDkAFqmjA=="],
"@tamagui/constants": ["@tamagui/constants@1.141.4", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-vh45Cv3NBOzUtIKriAr4lR1Eg947FJZjyBqto1wn/ClHg0tW1AlpZZsP3e5FGVDyBWFqIxbspoMuw2GT6eAY3w=="],
"@tamagui/core": ["@tamagui/core@1.139.1", "", { "dependencies": { "@tamagui/helpers": "1.139.1", "@tamagui/react-native-media-driver": "1.139.1", "@tamagui/react-native-use-pressable": "1.139.1", "@tamagui/react-native-use-responder-events": "1.139.1", "@tamagui/use-element-layout": "1.139.1", "@tamagui/use-event": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-kfRKsT6MIEtoWHywMvV+zYurLsZZVplDgpaXl7UwfXJlmFqT3enqGpMqv0TsEzTeuQpboPYBhbNgGJgO0Eihgg=="],
"@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/create-context": ["@tamagui/create-context@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-6Vfg8Ix8aypIWd2ocRMbUtoSw70sv9uDDlSU/LNxHfSNabKNg9D1LZwamQA57h6/DUYQSSp2Ucl7venk47wQJQ=="],
"@tamagui/create-context": ["@tamagui/create-context@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-s/zBH54N09F5RuFTD6/mZTD3DyUO9safRSW205xxPYfLoCYDTnNZTMB2V8LxHR2rjBgg6YlPP1fUr1y7aowErg=="],
"@tamagui/create-theme": ["@tamagui/create-theme@1.139.1", "", { "dependencies": { "@tamagui/web": "1.139.1" } }, "sha512-cZIFA8WCFa5L43WJY0NbKrxh+TwgG6jdZG6id0p1lXGBjqk9Xqp3pwnjlY7GOBsvm4uwvNWK07ZSzVh3VEy+fA=="],
"@tamagui/create-theme": ["@tamagui/create-theme@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" } }, "sha512-HrkoOTwbDB8VS/+8aPUVi08yF0lmcJyqjopthRmxH0KXsbANy8+33tynDW9N+CATM8fd6TW9wqLIU+mavVd07A=="],
"@tamagui/cubic-bezier-animator": ["@tamagui/cubic-bezier-animator@1.139.1", "", {}, "sha512-x4z+LirjGmqgwWlpwHczHIMA6jyui8ShL5Q+agqumSePG1kHf2a/TcMuCa1n/vXaHSMX5lDljfxGt2S+C8BXfA=="],
"@tamagui/cubic-bezier-animator": ["@tamagui/cubic-bezier-animator@1.141.4", "", {}, "sha512-Wj4Cg7XjPtSE6o8Ss7AmtFeZFYzRkNtbcF2arcktgnSVsgxAjwXn9PvRuXGUy4Z+Pnvh1NLhS2J2iKG23waP0g=="],
"@tamagui/dialog": ["@tamagui/dialog@1.139.1", "", { "dependencies": { "@tamagui/adapt": "1.139.1", "@tamagui/animate-presence": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/dismissable": "1.139.1", "@tamagui/focus-scope": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/polyfill-dev": "1.139.1", "@tamagui/popper": "1.139.1", "@tamagui/portal": "1.139.1", "@tamagui/remove-scroll": "1.139.1", "@tamagui/sheet": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/text": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/z-index-stack": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-XOC1eUlBAFUhhHS/XtdfUVAhDb2i7EQofOmoBp8geDxHmmWXfUca+r1i7V3th270KEei7dFhbHxwW4Uwi7AphQ=="],
"@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/dismissable": ["@tamagui/dismissable@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/use-escape-keydown": "1.139.1", "@tamagui/use-event": "1.139.1" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-MeG90z3rP9bCDOAEQUiPoaJhpa0q+NC7MpC4TRQ7goE36tI5DSKpufpr73wSzNxu6JLv5eevKuiLgGpZT8XaFg=="],
"@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/elements": ["@tamagui/elements@1.139.1", "", { "dependencies": { "@tamagui/core": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-v8eCmqRygD9jr/D6ZOnid/3r0IvdBzszTTKRSo5uG2OJAwFT4RC8VUqW/7nNb0p9rtjpeb6/upDdWUc1vSEXTQ=="],
"@tamagui/elements": ["@tamagui/elements@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-X77AKo/ZCEDOAvT7UphwiDAqEQjmKYYI7RUZxJEBQvWk2tmAkf25WlSl4DU2zop4sVb1uctISfBLFHevOPSQdw=="],
"@tamagui/fake-react-native": ["@tamagui/fake-react-native@1.139.1", "", {}, "sha512-Fufym6jy4VVWEDVPWbynL+CD9J+T+7HzpVpbkukhaNVCLUrlAsj3bZ8uUSq6pPkJXRg3AEW50Tu06/npReFkoA=="],
"@tamagui/fake-react-native": ["@tamagui/fake-react-native@1.141.4", "", {}, "sha512-0BPBA3Df9mwuSejgz+z66tyUJ0sfe/1vEjSqXTh7LMv/1g30CQ6uI8Z+s1oXeC9dDtuHajeOjxD3O2F+kC7PzQ=="],
"@tamagui/floating": ["@tamagui/floating@1.139.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.6", "@floating-ui/react-native": "^0.10.7" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-8A4zSv2VjNTEB0zWeSpkoDvZU+GMoCDK/Pk4CQ9s72OCZLAtZSRVYOZuhT7RavFVmhFkgRFmnrTu2uDFaYAvJA=="],
"@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/focus-scope": ["@tamagui/focus-scope@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/start-transition": "1.139.1", "@tamagui/use-async": "1.139.1", "@tamagui/use-event": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-RwGQuph3IoPNiQWAvQ5RaaBUw/J2FCKiT4RfoeEfS9ZStGcqYE0jVeiN7KRZ7rTb4WMQf6ybutvyfecelAwCQw=="],
"@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/focusable": ["@tamagui/focusable@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-914lwTz0WFuRqmxq51myR+Lni28QxqHcEoS9oF7j8HZR+TunNho09wLsbdK8G8Ar7cBrXbl03DEnIbpL/QKGgw=="],
"@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/font-inter": ["@tamagui/font-inter@1.139.1", "", { "dependencies": { "@tamagui/core": "1.139.1" } }, "sha512-UY4bb7VxFkd3FMi8dbgoh98+BppgI1CsO9h/9z/avgFAuKUDzIQhsAeUJal+GY19mvETYYyJR3EghoVzv2FmxQ=="],
"@tamagui/font-inter": ["@tamagui/font-inter@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4" } }, "sha512-jlPErhPX7DTy+XNhnHtpDBLPeH7tB1hvapoLnanOdyg+cBxGZQIE2uOtTISS3A8M9IPq9eH73OoWobyQ302Zsw=="],
"@tamagui/font-silkscreen": ["@tamagui/font-silkscreen@1.139.1", "", { "dependencies": { "@tamagui/core": "1.139.1" } }, "sha512-W4j718sNhAN2Fwaw+OK8ApqR6pBOlzkziFEMWTABFpL1seP4+w5wvZnRP9Vposy9wOlJzuzuJ+XnI8Ny2ywfIQ=="],
"@tamagui/font-silkscreen": ["@tamagui/font-silkscreen@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4" } }, "sha512-uRRzdFgtrZ/WJp1vDhCc5oXg4lAbw+Ib1HFTBKn1/BhlqgrwCDrAkTNDNnKzUChqpdmDLGeFSQQknmK7xG352w=="],
"@tamagui/font-size": ["@tamagui/font-size@1.139.1", "", { "dependencies": { "@tamagui/core": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-4H/AQwCW32+Cupxi3vYueHQUiMJ5hlKQL4VogsfTIXNkMS8vvrwYKUKz0DD0H8x3nUd0LWwzhUas5gwdB6WTjA=="],
"@tamagui/font-size": ["@tamagui/font-size@1.141.4", "", { "dependencies": { "@tamagui/core": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-N8bS0Pqcj8UJd3rmuMGjSVOfVY7DW/1zONaE09QvzOStcB9Klfzs6UU0P4zwMkdf9vqJOfXmS7LD7aQaKK2fzQ=="],
"@tamagui/form": ["@tamagui/form@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/get-button-sized": "1.139.1", "@tamagui/get-font-sized": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/text": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-R7j57ld4dXLosnyCYxOyX8Yfs14elt8mMYBL5nNEwrIB2huRfMdWDfop+ruc711OIp/fy4fOaL5rp8v/dUb9EA=="],
"@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/get-button-sized": ["@tamagui/get-button-sized@1.139.1", "", { "dependencies": { "@tamagui/get-token": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-ven6PhEey1NhBi5KJgCD52+dnIJ78r3w9KWjDYdndIjSkWcGu6vvI36OfDokgTTKXnYlny38PmCtVNh7XM2VMA=="],
"@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-font-sized": ["@tamagui/get-font-sized@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-eHdBGcPX1bVdEiZIDc+ChAUps3mAr5ki8nivFJHmNCWH8jpT984zPF+zR0BdQoWywswwcU1eeAuwsdScxadHEA=="],
"@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-token": ["@tamagui/get-token@1.139.1", "", { "dependencies": { "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-bZijQVpQLbYGs5OnL16JkRybwYMuULgKfiuTPUAFpzOlJIgAo9m5b+6hSN5CDRquTH6poxWaNHj75V2efh3saw=="],
"@tamagui/get-token": ["@tamagui/get-token@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-wAcdJVLUXbbKqYF3QOip9sAeLJk19CDJjck5W7MpCma3WoXw58Kmb6VYFA7TV5hUVicv2FCRw4DkU3v/vNoDyA=="],
"@tamagui/group": ["@tamagui/group@1.139.1", "", { "dependencies": { "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-controllable-state": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-NhFnDabL4B0rDmjuEyS3J0Wf2Dq4KUoi4ph9Z6qLwHqQm9gvgssNmOp8aAgo5HAsTTB9bTOvD+2Zoqd06s68VA=="],
"@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/helpers": ["@tamagui/helpers@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/simple-hash": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-E6QSJJxxUoLHDOD8nHlIZo0dU9MX0H5eJHnQH7EI5aKoyVVLxRaBVU3Ca6/BHziXefxCZuv+RJjPur3D2nk3Yg=="],
"@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": ["@tamagui/helpers-tamagui@1.139.1", "", { "dependencies": { "@tamagui/helpers": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-N5/t+ZKOpS95dUR5mVEcfjyEVUeSLFFVedzDNIQLAazW7Bn2MD9vrSU5Sik5SgWqxoFIa600t5pPpt3abI1j3w=="],
"@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/image": ["@tamagui/image@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-NuvdhO5AJwKIJ8EOtWlBDBkqApz3cvb3fgYdunJVcNQi7dW0+sVlX7sunbG0JbnSbie5L2p0wlaP/GaYii5D8A=="],
"@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/is-equal-shallow": ["@tamagui/is-equal-shallow@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-ZAGI7BwXEQomWd6KI5yMngxLHmNpSchHQN+iki++Zx8Bl+m894ZwCG3WBK23CgmBiLk3WTzHgUWmNhVCm0I0ng=="],
"@tamagui/is-equal-shallow": ["@tamagui/is-equal-shallow@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-YuzzoST+SGlAJys837RXVX3LOw1ajL76imoAytMXZhBxoJN9rHuLIzRayXGfFy+NLrVLThTlUsswSojKYIsVVQ=="],
"@tamagui/label": ["@tamagui/label@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/get-button-sized": "1.139.1", "@tamagui/get-font-sized": "1.139.1", "@tamagui/text": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-zIapwcPAIVExn//2Ftt0P7PLCDfAki4YpVd0aVqiUNGfI5bXRbMyv1M/G3grIfm29XENg7UCNeEuf0j3SnC6uw=="],
"@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/linear-gradient": ["@tamagui/linear-gradient@1.139.1", "", { "dependencies": { "@tamagui/core": "1.139.1", "@tamagui/stacks": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-tfJuuTxJpBWTSx7X5Z7h1R4HyXg2VcwhmWyyzRJa90zPMUzsJWArssH/OBcRQeqzXlSD1A39C5kq6bdIg2tXIQ=="],
"@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/list-item": ["@tamagui/list-item@1.139.1", "", { "dependencies": { "@tamagui/font-size": "1.139.1", "@tamagui/get-font-sized": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/helpers-tamagui": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/text": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-czFsX7qGmtdA303fRzAsAW7GKq52B4tMky5IbNDKB9hdXtstwHVxN5nfz7m9WHM4uUbmKVuCK+SnbfTuB4QmEA=="],
"@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/normalize-css-color": ["@tamagui/normalize-css-color@1.139.1", "", { "dependencies": { "@react-native/normalize-color": "^2.1.0" } }, "sha512-mrQuU3tpDlx856vwunuav6JY93knNHZTEEBIH0E1AmeM68PjMbKF5rcgdc4BrP7/ZTVdjSqqoNUzRdA6uexLbA=="],
"@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/polyfill-dev": ["@tamagui/polyfill-dev@1.139.1", "", {}, "sha512-aP6LGaaD15jqOsV5qn29lriIG0PM8XDeQJXkp5pcjfRC5nf1Apj2C/UtqFquFQ4CWnYgKPJi+WMouV0C7y07Mw=="],
"@tamagui/polyfill-dev": ["@tamagui/polyfill-dev@1.141.4", "", {}, "sha512-oaxMP+YoDArZ527RyfWjVH23lr1mlsX6MyRRbJhAqRqATdqUXLzYuBCpEdhwFqwUxWB4ZcH/oe8B5ESbFIW4WA=="],
"@tamagui/popover": ["@tamagui/popover@1.139.1", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@tamagui/adapt": "1.139.1", "@tamagui/animate": "1.139.1", "@tamagui/animate-presence": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/dismissable": "1.139.1", "@tamagui/floating": "1.139.1", "@tamagui/focus-scope": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/polyfill-dev": "1.139.1", "@tamagui/popper": "1.139.1", "@tamagui/portal": "1.139.1", "@tamagui/remove-scroll": "1.139.1", "@tamagui/scroll-view": "1.139.1", "@tamagui/sheet": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/z-index-stack": "1.139.1", "react-freeze": "^1.0.3" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-GIk76O6NkbNTFVYwEark5hhByzve8ZLRT4DPwqo9GB8gvazbkHWYW7kLOO4R8scoWS84L3nhvSizxCtxJqj61w=="],
"@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/popper": ["@tamagui/popper@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/floating": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/start-transition": "1.139.1", "@tamagui/use-controllable-state": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Iq/hYBkCmzF2/iJcle5Ydqr6H0tblcQp4SY6ONmnRcjkrfT8SPfbZv5nhAYAFFgCr+ftHhLa6Sh26uHJuqdOWQ=="],
"@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/portal": ["@tamagui/portal@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/start-transition": "1.139.1", "@tamagui/use-event": "1.139.1", "@tamagui/web": "1.139.1", "@tamagui/z-index-stack": "1.139.1" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-fR98C1PhMjb2xyHxXSh2tXaAIwrd8wyvjs8xzjYn+xCWs9mkU5kKiOxSLkX76u3lFO+yySk83BnVyXkUR/884g=="],
"@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/progress": ["@tamagui/progress@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/stacks": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-r0Y6qCh8PVyWvaWqXrRj5zeYfD8FxPzrD75oWUFENY6inkgUKAV+qe2qcdwJ/hMXRlsAy4e4I2GVnMrAdLNKHw=="],
"@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/radio-group": ["@tamagui/radio-group@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/label": "1.139.1", "@tamagui/radio-headless": "1.139.1", "@tamagui/roving-focus": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-previous": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-okAa0kc4zpZ3YKxgp7bZjTBLike/0Kk1ixJzZpTdshVzhDSyT+Rn0Ok59b+3chW9+ZP5/00DF5+Ke+OkW3U2IA=="],
"@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-headless": ["@tamagui/radio-headless@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/label": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-previous": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-o4avZwfdsLrCnLhmqYsuW/H4TBtozHYvA+3323VxDk7D61mDwuPX/X73KUZcGz2CnAqjVS4D8LBmpo/kcYYQow=="],
"@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/react-native-media-driver": ["@tamagui/react-native-media-driver@1.139.1", "", { "dependencies": { "@tamagui/web": "1.139.1" }, "peerDependencies": { "react-native": "*" } }, "sha512-rzVK0//YAddUU8KYPyvmD66x3Fbmklxnxkm4Fjz6uwM4L0M2vkpF7AwCwpub36e3DaCJc7dQeIb/5uQ+vuObqw=="],
"@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-use-pressable": ["@tamagui/react-native-use-pressable@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-dRDD6GIjkb8Fq0DAjnK2DvU5VPkeX4elIFWeAyGJLG9vRFVgKBznYDzawYnyJEiNQ/gqWOTm0ylgxNYbjGMt8Q=="],
"@tamagui/react-native-use-pressable": ["@tamagui/react-native-use-pressable@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-oZ268oyRlvZeNyqXCKJR3bLqEKYAnGy5D44KSjNr/kAZfCPE4qVf1UWdFDpNUpFLRfJKd7LRJbEt4MF1WhbX7A=="],
"@tamagui/react-native-use-responder-events": ["@tamagui/react-native-use-responder-events@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-y2uq6CVhymYte6PSPopWBNzxbOwaSMp/PMJ5Pg0b1Fh9yC3qT9Edk1D3gDCVRF+kQtuYBws7uLDfo053VjeD5w=="],
"@tamagui/react-native-use-responder-events": ["@tamagui/react-native-use-responder-events@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-eqSVS/ZHEs+g+4BwnNRdXI7Ef2XZsi3NflSSU/Lovdww64N2efcvqP9xAUpbyK7ES4a2b4U1PTYBQMMCh7qHpg=="],
"@tamagui/remove-scroll": ["@tamagui/remove-scroll@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-ZSjwDjSKxe7Ow3Ua2fqXFrQCqPJRs0GTeX3FCgtfzydaG8TnQSpECnobf0sdgWttQHEpolAdX8jd1STfw1MEIw=="],
"@tamagui/remove-scroll": ["@tamagui/remove-scroll@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-vbQEhST81IvlaEEQd75UMIIi8FbDoKpyUrvGdeMKjsuM40IlW6gC2Dgtvvw69mGtLf8XCLJXWAY8IxYyNP/7kw=="],
"@tamagui/roving-focus": ["@tamagui/roving-focus@1.139.1", "", { "dependencies": { "@tamagui/collection": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-direction": "1.139.1", "@tamagui/use-event": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-v2TmKEfJqaKu6SXSNC32MjEEifCc64ZsmyuT29hJpf0cGxNZBJ8qSB8a866Cp5ZIg10w5ibIWCHs9rIyEGGpyg=="],
"@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/scroll-view": ["@tamagui/scroll-view@1.139.1", "", { "dependencies": { "@tamagui/stacks": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-vgzmf5E74qZwF17uabKvAjX8PWBPQW6RHemKslsc8Zt9ZZzal2BH54voQCIs9yJreydpOTNv3q8jf58Gf5O3qQ=="],
"@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/select": ["@tamagui/select@1.139.1", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/react-native": "^0.10.7", "@tamagui/adapt": "1.139.1", "@tamagui/animate-presence": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/dismissable": "1.139.1", "@tamagui/focus-scope": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/list-item": "1.139.1", "@tamagui/portal": "1.139.1", "@tamagui/remove-scroll": "1.139.1", "@tamagui/separator": "1.139.1", "@tamagui/sheet": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/text": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-debounce": "1.139.1", "@tamagui/use-event": "1.139.1", "@tamagui/use-previous": "1.139.1" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-5lwJIcf9ofFedpjkVJlblWQ5beHJd3F6Wg9YU/seCyGv4JetSaroCDJp4l9x78F60du6DP54AB5PTGFesvWMZA=="],
"@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/separator": ["@tamagui/separator@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-p8BjZEtVoxSA8T5kwwj6VMFeLNcbpUYG4DEzRi2bS8LYZG3CN6FAnGB8XBbvqQnkVfmw2KpYfjBfZaYUjh4tQg=="],
"@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/shapes": ["@tamagui/shapes@1.139.1", "", { "dependencies": { "@tamagui/stacks": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-wEx0v28q0deXPVv9tKCiQMPh50dAHWsRBUsjPts16hnKkSl8H68/6/vvUEDH5Ir/F0dLi21d36X0m4k+Kh1owQ=="],
"@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/sheet": ["@tamagui/sheet@1.139.1", "", { "dependencies": { "@tamagui/adapt": "1.139.1", "@tamagui/animate-presence": "1.139.1", "@tamagui/animations-react-native": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/portal": "1.139.1", "@tamagui/remove-scroll": "1.139.1", "@tamagui/scroll-view": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-constant": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-did-finish-ssr": "1.139.1", "@tamagui/use-keyboard-visible": "1.139.1", "@tamagui/z-index-stack": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-1P6yg1DMHqCHmI21mOR3LsG9xbGCBmdYt9/h0GgPGooE0DNXsmsThdoQJHULjckkvtfoROICmGx+ColsxqI4MA=="],
"@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/shorthands": ["@tamagui/shorthands@1.139.1", "", { "dependencies": { "@tamagui/web": "1.139.1" } }, "sha512-lpevqf1ERAZNUKysTmNddHi5bFJmpXioRqBiPBdiktUkRgHUaCTRkSYORaOpVopzH0xQ+LuZvRmonARM3XFLFg=="],
"@tamagui/shorthands": ["@tamagui/shorthands@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" } }, "sha512-sfFFtgRkF32wnFq9cRdXeyP8qCPCWXspUw3TIEU0VVTawZav0OjtpKrthC+ujX5wdm9+oF5EGkKu0m2Xyc4ODQ=="],
"@tamagui/simple-hash": ["@tamagui/simple-hash@1.139.1", "", {}, "sha512-nnaFGje6PmIT35pXL6Yv/r0dbjzsJl4n5pJg9wniW9UXGNtW12fqr8m0wgI0IK1Ej4ehEoY/pd97owhg1uEHOw=="],
"@tamagui/simple-hash": ["@tamagui/simple-hash@1.141.4", "", {}, "sha512-ZBJ43UVk2jgoSVfUkSXJ1ziQsxXRoGEloQCUJ1+Mf8E8qo2dAXYDRE+maFZ2oO7HatA58z8T2KIxSYRQROeG7w=="],
"@tamagui/slider": ["@tamagui/slider@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-debounce": "1.139.1", "@tamagui/use-direction": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-zBtfq6toMA65+CbSfqQUEOZlZwoeMdYJnoC8/KA5cE4tgi4XUcD+JYNtvujFwBXL/2skcLURuAZaZs1slOfOAw=="],
"@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/stacks": ["@tamagui/stacks@1.139.1", "", { "dependencies": { "@tamagui/core": "1.139.1", "@tamagui/get-button-sized": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-4tNPtkuLIlKbpMk8Z+W7vVfoIvz16hO/haRtAf/PVx3I4Zqjldiz7QwNX34ADu/0J3Pfnfg/R55W5k9UPdbp+A=="],
"@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/start-transition": ["@tamagui/start-transition@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-BgG+XT/30p3BIjYWxLU7Tfge/ZNXEQhyhzfFy6buJLwUWKfgAZyKVRdPcp7fgXoY+Ox633bZJI7tXUjXDEy+og=="],
"@tamagui/start-transition": ["@tamagui/start-transition@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-XsJqp8k2Ejp8MWdWBonYqqoq7314wpP32mtyO5M5k8rYpM9siMxqSB1MzDECOf2rJ9dvL8/9aGQflHyt/TJSEg=="],
"@tamagui/switch": ["@tamagui/switch@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/label": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/switch-headless": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-previous": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Zba9BK5DMJnq7/yUNVadtPqK2gKBo3JpWVgQMAz0wXv2sXa7dR117FoQ9lBTUzJa+sNljlZ5e4yhlucAdFmvFA=="],
"@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-headless": ["@tamagui/switch-headless@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/label": "1.139.1", "@tamagui/use-previous": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-pDT4f6N5t9k5aDU0eHMgP1HBaUdcfij76qvJHMfsZLC9uWt8u0mtNJuQUVPVdMetXNS+oscM34Q4/VXg5mlGwA=="],
"@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/tabs": ["@tamagui/tabs@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/get-button-sized": "1.139.1", "@tamagui/group": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/roving-focus": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-direction": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Mbl2WYL+E4iwSNvVWKfFNPRx/CzWTWYWDiSd4Nh3aAx2vyp23F/ATOinr9gfJryDOx8V6BEyYOIZ3mbs00jMdg=="],
"@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/text": ["@tamagui/text@1.139.1", "", { "dependencies": { "@tamagui/get-font-sized": "1.139.1", "@tamagui/helpers-tamagui": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-YegV383opV6dt7h5mIuAT/LKsshm5WypPV/6znDNDkrjGvodMDbFv3h9SiSTlpAa/1jqLIpdXwGhCMwRbDHvUw=="],
"@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/theme": ["@tamagui/theme@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/start-transition": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-HIlHoZBje1YrhStYSleduVNmdPjgVJGB5Q2uOx8uutXEWaO9d814xL6+uBKoY7DZmrvd9FR/PqvgAAv/WF/PZg=="],
"@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-builder": ["@tamagui/theme-builder@1.139.1", "", { "dependencies": { "@tamagui/create-theme": "1.139.1", "@tamagui/web": "1.139.1", "color2k": "^2.0.2" } }, "sha512-vNnV59SD0FvZrTunKp39JC8Vf6gUBpjUuYV+JSxI93hO+mnGg/oGCRR6j1QwsxpOV6R06irWmdUMl+O/GWhHag=="],
"@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/themes": ["@tamagui/themes@1.139.1", "", { "dependencies": { "@tamagui/colors": "1.139.1", "@tamagui/create-theme": "1.139.1", "@tamagui/theme-builder": "1.139.1", "@tamagui/web": "1.139.1", "color2k": "^2.0.2" } }, "sha512-p4Hvwtigf7dUjoMXDHX1pfNV4+GVzBdE2H57d+R6L6mLTBuXjvqRyHqqTLjUrhMf3rtL5UJleWtqCRteXKytMw=="],
"@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/timer": ["@tamagui/timer@1.139.1", "", {}, "sha512-YKahedLQEUWSNkUw/oQWHFD2AWRMONfsU3BlPe3cV1hGTICL0duqMk6XaPX9HzMVwP0kKo7IhipTZ1Q2wpGMtA=="],
"@tamagui/timer": ["@tamagui/timer@1.141.4", "", {}, "sha512-uK81OKhy6u7c0kSf0+028XzwuuTLWN+kAAUSEtpQy/HdM4MU0oGtf1+2mnn3X8wwTBKDP9iP33uN8XHV161uEw=="],
"@tamagui/toggle-group": ["@tamagui/toggle-group@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/font-size": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/group": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/helpers-tamagui": "1.139.1", "@tamagui/roving-focus": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-direction": "1.139.1", "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-Jgq+tLzHEhBk5L0bOED8qL1xfcYTbOzVV9uDRdU+GAM6nzPidU0/iqNfjZAsS3JDHJIy0YvLVOgJThnMeFeu8g=="],
"@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/tooltip": ["@tamagui/tooltip@1.139.1", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@tamagui/compose-refs": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/floating": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/polyfill-dev": "1.139.1", "@tamagui/popover": "1.139.1", "@tamagui/popper": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/text": "1.139.1", "@tamagui/use-controllable-state": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-RSNitKeIGvql7IyBkjIYklUUYMUUqewB+1jnZMz4mWDkA/dcM0uTYJRGYHt6d5s0M3UcazIGeRf+qweXjGMDuA=="],
"@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/types": ["@tamagui/types@1.139.1", "", {}, "sha512-BWzJkC35l6Ue09k/fUCjJ1u5CNhIsOidTba6oYHT/2GpS48RQD2PwQxNuZn6kgWjJ5yESUHpsojAhf2mhUcXZg=="],
"@tamagui/types": ["@tamagui/types@1.141.4", "", {}, "sha512-PO2YoO+wmIusYNL54I6A02XIoqm3ELnVGwpB5XuQfsoEy16gWU+HDsGKjlBqovSD+XHHdEJf57tMoNdG6VGezQ=="],
"@tamagui/use-async": ["@tamagui/use-async@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-sUssjjaUXORaKpSMFYpKdqTKILrLTJE4G5bkyNQdOnzB4vGCSRZsT99slrOxscP8mFG3XPVV3BupJ1FFGPLDCw=="],
"@tamagui/use-async": ["@tamagui/use-async@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-X/sjWLTXVP4TEAIbC2a6c+SAnoYYrq1HzVXdeh9kN///XIj1koNwFJ1AKQ+e6dsS5d28ox8sRTyPKLryNz/r2w=="],
"@tamagui/use-callback-ref": ["@tamagui/use-callback-ref@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-/mL8kkfIJ9murERiTBsI6qwlOZxikjYeHqTRZvnSJaHZgyCMNo+qrJtn7nzkDuo1lzYVPYi8j1U9mcgWI6KUrQ=="],
"@tamagui/use-callback-ref": ["@tamagui/use-callback-ref@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-mI/Jt1teDppdAhLh3DcnX/pmg9wasqXMIzRFqO6Wmgov9/jLVb8+ve/cE7jN8T8YnJHMAHFsfqRalfnUMURpDw=="],
"@tamagui/use-constant": ["@tamagui/use-constant@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-8ayHg6nsGaSNnICzGh31tNiThRGEq18U+KWKa71Hx7EUAfF+dVDyZ3Iy9Oy2A6NTy4rrBmE2eVJz1UYA9chthA=="],
"@tamagui/use-constant": ["@tamagui/use-constant@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-iE5rWsl6fjBobd0jH1fMd57yekvwPD3U25DqJ0Wze5C00kvqHiWdiPeXgoZwl6i/mVT4sPmVNkfjueL0mz58Tw=="],
"@tamagui/use-controllable-state": ["@tamagui/use-controllable-state@1.139.1", "", { "dependencies": { "@tamagui/start-transition": "1.139.1", "@tamagui/use-event": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-Py7oFPL4P5tpSQdUJixW/ahi3ZwyAxoCf7kR8y/esCcOtjZnRyFjbDGM4rXwYqN+Y6Eat3T6gxoKJ0t1+x+LNg=="],
"@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-debounce": ["@tamagui/use-debounce@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-V61EGTGk4om+L+ahQD62qjKwHPfP+9Y60w1wgyxpdh1VJ925sPU080A8gamo/DlKZIGyf/hw+tOtPsYApdKdRw=="],
"@tamagui/use-debounce": ["@tamagui/use-debounce@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-Dtk/ee5yTQJ+VD/wsDIrz86gyWBffGEkv2zKVu1OIIjUvJ2+u7GFrwwV5/kQWAdmkvne080AUGtVwHmj+v9uGA=="],
"@tamagui/use-did-finish-ssr": ["@tamagui/use-did-finish-ssr@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-b0nmisx8yMxRu2YzZ/Gyys6cDsiOrVNHo1cJYV7BB/UuQ5MVTFx3t5Nwa1qeU7y36Et/jUCA4x91ipiXoulqhw=="],
"@tamagui/use-did-finish-ssr": ["@tamagui/use-did-finish-ssr@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-QnYEfmdTzkckb8mQaPWrF3c4p64T73rvKGKr9abibWVGRMjRY0cSbxv8FBcnpjgwxnfNDl63veii7RLdxGyU1w=="],
"@tamagui/use-direction": ["@tamagui/use-direction@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-bunQLUlB7tORvu04FB/ffxmdRt1jf+rlE6iUrVzTbXqa1gslQIjpjdY8IJyjGZVYognX3M6nOI3C8E75iDOBIg=="],
"@tamagui/use-direction": ["@tamagui/use-direction@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-Fn13pVu+5583gDFcmFvDJSFovsjrXioiQxAJ+2y/7wjZb3uK/fBv1GgVVWJashiF9SRkr7dZhYkWRz+C8k/raQ=="],
"@tamagui/use-element-layout": ["@tamagui/use-element-layout@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1", "@tamagui/is-equal-shallow": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-z2dTvuYYFyzVzIalfgBg78rqKzBQSvBWS5RFy5WT0QSWZoyANeWmePVLesV0BRliLObVxAIGKvWHa+LPox9Anw=="],
"@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-escape-keydown": ["@tamagui/use-escape-keydown@1.139.1", "", { "dependencies": { "@tamagui/use-callback-ref": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-19gc6iwDMbY0Q/UOyplTHhbaCC5FDfYyzXDXmrjhT9UbIPbI/j0Z+upenHAbAITfVfLCFqjO4SZ3kgA0Mha7OA=="],
"@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-event": ["@tamagui/use-event@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-xlRYNfEt8URIs2h5m4XZ/7RPEjMjQbLPgaxCuUcCjvS2vmWURBhZsiYU0eLtfPivvtAuBoQI2hclAkVyqaubPg=="],
"@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-force-update": ["@tamagui/use-force-update@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-fgU7dFFZ0Fr6d+yRzrgZHI9noBTUiTkx95sgIS/g6ogmVMBvuwHgLURhMloFdYzEgB6c5nA8+N2MZ9r4dQWnjQ=="],
"@tamagui/use-force-update": ["@tamagui/use-force-update@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-+FHa8J24sLb3BWpJrah1cizvBJjhjbtxtZcvTuo3Qovt3b47ktx1o+SeEdoiGwc6OYezeuFdAYiTPVHgXqHrvQ=="],
"@tamagui/use-keyboard-visible": ["@tamagui/use-keyboard-visible@1.139.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-bAlQzv4JEHFSQZcjTLqIIuJwM7GdBxOz6PVggvzjX34Ng2U2VZEShu2Vo5InOxoX3UBZoE8zsVYumgsIwNjdVQ=="],
"@tamagui/use-keyboard-visible": ["@tamagui/use-keyboard-visible@1.141.4", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-+f3B3TIAfUW1dPkUxtJ8WlGXhakGsvmESkbOfs0Mbk6zVONUaIXfTUB9eWOsVi/hAYL//XSOaEi54bFQD9Ug9Q=="],
"@tamagui/use-presence": ["@tamagui/use-presence@1.139.1", "", { "dependencies": { "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-78rVxmYbFuzCRAgkXrOwfKoC5lkZHdB5ozVEpri3qD/imlwtE8uAetlMHXMmrikr7ugYiREcpEpnRkfk5LqFVQ=="],
"@tamagui/use-presence": ["@tamagui/use-presence@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-KKL7kx3Wkvxhx82E1y/l4DHNC0zxhFa6FQ/76lhA5YLMy04dNXFzWzCNl2YBjH8UhypfWwJsu6teW8ku7+1NkQ=="],
"@tamagui/use-previous": ["@tamagui/use-previous@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-XhaflDHoddeuYvGlZF7CVSau+MYWBdYb8o2xSb3a8Uvr9wIxUymvBcE0HCGwQa7pxn5CBiRS+YlYT5hs9qwY0Q=="],
"@tamagui/use-previous": ["@tamagui/use-previous@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-jfD+x9GpaMjEFcsTpj1ko9HrrXP/quZuwlJ3O8VHCSWCm9zEPCaHV/WLZMpeumKGeLVqUnCowqqmfWITV95cfQ=="],
"@tamagui/use-window-dimensions": ["@tamagui/use-window-dimensions@1.139.1", "", { "dependencies": { "@tamagui/constants": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-bT6mn3fCDbIs5p+3zAQeEQxCRSkkf60Q+jvo+qg6iULxSA7sMt6m96mVRq6atUmokvRKZorwuyys98jIERmYSA=="],
"@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/visually-hidden": ["@tamagui/visually-hidden@1.139.1", "", { "dependencies": { "@tamagui/web": "1.139.1" }, "peerDependencies": { "react": "*" } }, "sha512-MYIwr8m/vh4ClYQbVb8V+935XYq/34FOLk1+nHbmny1G5eqR4wdjuJ9k/iKGH7fUDMcARszKDU4Uz5aextAlIA=="],
"@tamagui/visually-hidden": ["@tamagui/visually-hidden@1.141.4", "", { "dependencies": { "@tamagui/web": "1.141.4" }, "peerDependencies": { "react": "*" } }, "sha512-B0eTSm8XGNSjUG5xtceAruNh/KHw3Cik5R7LDqg/DhXdVhDSOWPXcMj35C6tGeIes8bz8BAyp9VKKyXeUCkU+w=="],
"@tamagui/web": ["@tamagui/web@1.139.1", "", { "dependencies": { "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/helpers": "1.139.1", "@tamagui/is-equal-shallow": "1.139.1", "@tamagui/normalize-css-color": "1.139.1", "@tamagui/timer": "1.139.1", "@tamagui/types": "1.139.1", "@tamagui/use-did-finish-ssr": "1.139.1", "@tamagui/use-event": "1.139.1", "@tamagui/use-force-update": "1.139.1" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-9eUOAHvNBndr7byhl80WT57wPDrfmU8Z8P561J9V9Bt47v2FLAGMdF3f656oSsePFOrWoRYg0QCebMldB2HE/A=="],
"@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/z-index-stack": ["@tamagui/z-index-stack@1.139.1", "", { "peerDependencies": { "react": "*" } }, "sha512-tKp6m0wcAOq7Ydt57k/+AiKdGKdZh9BzOic0tU7Viao/TrPA2qA+IC42Hluq+Gxiazl+qLKvKG7xgQ0q8FICjw=="],
"@tamagui/z-index-stack": ["@tamagui/z-index-stack@1.141.4", "", { "peerDependencies": { "react": "*" } }, "sha512-q/rooqEqfKxiY7lgba+7Tiyg8BF2QZ9BRUcBl4EdG4FU5fYTD5fMy7CBDszVkzTcD/Z337VEAggJVyeHxCL+Qw=="],
"@tanstack/query-async-storage-persister": ["@tanstack/query-async-storage-persister@5.89.0", "", { "dependencies": { "@tanstack/query-core": "5.89.0", "@tanstack/query-persist-client-core": "5.89.0" } }, "sha512-/l7JVKvQ6/oad0+ncvBcJOqRL4lIJ5E3UjfB+xgTfVeqga8wMnH1cgOlJEpvBFEDCw88hA/jpGb6Cp9my9Vc+w=="],
"@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=="],
"@tanstack/query-core": ["@tanstack/query-core@5.89.0", "", {}, "sha512-joFV1MuPhSLsKfTzwjmPDrp8ENfZ9N23ymFu07nLfn3JCkSHy0CFgsyhHTJOmWaumC/WiNIKM0EJyduCF/Ih/Q=="],
"@tanstack/query-core": ["@tanstack/query-core@5.90.10", "", {}, "sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ=="],
"@tanstack/query-persist-client-core": ["@tanstack/query-persist-client-core@5.89.0", "", { "dependencies": { "@tanstack/query-core": "5.89.0" } }, "sha512-kxZgQGgD7VqSFTDA/JyajywixHGGhzjMTtkENeVcS6BoTW6CGOkvoZH3L4/ROsaCZ4ibDfrmPzfUCpghID5ENg=="],
"@tanstack/query-persist-client-core": ["@tanstack/query-persist-client-core@5.91.9", "", { "dependencies": { "@tanstack/query-core": "5.90.10" } }, "sha512-LliMZl/pkO/6vRf5//fO8nl4UCfM1LQsnT+N0aRYkK7bqoM3QdqHxD65EApmJRypKkqaWmiyulPG3Mi1NYuyIA=="],
"@tanstack/react-query": ["@tanstack/react-query@5.89.0", "", { "dependencies": { "@tanstack/query-core": "5.89.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-SXbtWSTSRXyBOe80mszPxpEbaN4XPRUp/i0EfQK1uyj3KCk/c8FuPJNIRwzOVe/OU3rzxrYtiNabsAmk1l714A=="],
"@tanstack/react-query": ["@tanstack/react-query@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg=="],
"@tanstack/react-query-persist-client": ["@tanstack/react-query-persist-client@5.89.0", "", { "dependencies": { "@tanstack/query-persist-client-core": "5.89.0" }, "peerDependencies": { "@tanstack/react-query": "^5.89.0", "react": "^18 || ^19" } }, "sha512-c1RaSID8DPzr7HnO2kfah5ON/lEtN/g0gN4nRsxWPi8gjWQRMfOh9av/KJWxxqWnBMPZ+tMV5Lb1OS38GAIRrw=="],
"@tanstack/react-query-persist-client": ["@tanstack/react-query-persist-client@5.90.12", "", { "dependencies": { "@tanstack/query-persist-client-core": "5.91.9" }, "peerDependencies": { "@tanstack/react-query": "^5.90.10", "react": "^18 || ^19" } }, "sha512-o51hwImpKgb85FnFljtCXcUzuLXpKONF9N6bhKfifPL3SNSj8neh1a2aHQd7sN9mbeIeNfGMGJuDpSt/Fc3GwQ=="],
"@telemetrydeck/sdk": ["@telemetrydeck/sdk@2.0.4", "", {}, "sha512-x4S83AqSo6wvLJ6nRYdyJEqd9qmblUdBgsTRrjH5z++b9pnf2NMc8NpVAa48KIB1pRuP/GTGzXxVYdNoie/DVg=="],
@@ -874,11 +874,11 @@
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
"@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="],
"@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/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="],
"@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="],
"@types/react-native": ["@types/react-native@0.70.19", "", { "dependencies": { "@types/react": "*" } }, "sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg=="],
@@ -1238,7 +1238,7 @@
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
"eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="],
"eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="],
@@ -1258,7 +1258,7 @@
"eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
"eslint-plugin-react-native": ["eslint-plugin-react-native@5.0.0", "", { "dependencies": { "eslint-plugin-react-native-globals": "^0.1.1" }, "peerDependencies": { "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-VyWlyCC/7FC/aONibOwLkzmyKg4j9oI8fzrk9WYNs4I8/m436JuOTAFwLvEn1CVvc7La4cPfbCyspP4OYpP52Q=="],
@@ -1408,7 +1408,7 @@
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hermes-compiler": ["hermes-compiler@0.0.0", "", {}, "sha512-boVFutx6ME/Km2mB6vvsQcdnazEYYI/jV1pomx1wcFUG/EVqTkr5CU0CW9bKipOA/8Hyu3NYwW3THg2Q1kNCfA=="],
"hermes-compiler": ["hermes-compiler@0.14.0", "", {}, "sha512-clxa193o+GYYwykWVFfpHduCATz8fR5jvU7ngXpfKHj+E9hr9vjLNtdLSEe8MUbObvVexV3wcyxQ00xTPIrB1Q=="],
"hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
@@ -1854,7 +1854,7 @@
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
"prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="],
@@ -1886,7 +1886,7 @@
"raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="],
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="],
@@ -1896,13 +1896,13 @@
"react-is": ["react-is@19.2.0", "", {}, "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA=="],
"react-native": ["react-native@0.82.1", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.82.1", "@react-native/codegen": "0.82.1", "@react-native/community-cli-plugin": "0.82.1", "@react-native/gradle-plugin": "0.82.1", "@react-native/js-polyfills": "0.82.1", "@react-native/normalize-colors": "0.82.1", "@react-native/virtualized-lists": "0.82.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.0.0", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.1", "metro-source-map": "^0.83.1", "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.26.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.1", "react": "^19.1.1" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-tFAqcU7Z4g49xf/KnyCEzI4nRTu1Opcx05Ov2helr8ZTg1z7AJR/3sr2rZ+AAVlAs2IXk+B0WOxXGmdD3+4czA=="],
"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-background-actions": ["react-native-background-actions@4.0.1", "", { "dependencies": { "eventemitter3": "^4.0.7" }, "peerDependencies": { "react-native": ">=0.47.0" } }, "sha512-LADhnb4ag1oH5Lotq0j8K9e2cFmrafFyg2PCME88VkTjqDUgNcJonkNdMCTHN0N3fh+hwAA7nDR4Cxkj9Q8eCw=="],
"react-native-blob-util": ["react-native-blob-util@0.22.2", "", { "dependencies": { "base-64": "0.1.0", "glob": "^10.3.10" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-Czx01QMg7aLsm/4F/7+eqoRAi1q/qjLY2Kao16g+n2SRnTH1+qkD8Qhx2q9okB+VNQvZKB1LbiXhktzYQV52xQ=="],
"react-native-blurhash": ["react-native-blurhash@2.1.1", "", { "peerDependencies": { "react": ">=16.8.1", "react-native": ">=0.60.0-rc.0 <1.0.x" } }, "sha512-b1aA5Tn31pPbqmaWnhJv7zSuN6o9M1t4yHciPunfP89LDkH2dvDIynvkE00Hen4Vmt6SnyXViSYH34MyvTvRiA=="],
"react-native-blurhash": ["react-native-blurhash@2.1.3", "", { "peerDependencies": { "react": ">=16.8.1", "react-native": ">=0.60.0-rc.0 <1.0.x" } }, "sha512-tYyFketZkrrEIABJ5Z1Pt6N2dqPqtSp7w4Lce17sx1bvspdppgVu/ehtZoPRZu02fYyX92RkyCeGXtCbtUNntg=="],
"react-native-carplay": ["react-native-carplay@2.4.1-beta.0", "", { "peerDependencies": { "react": "^17.0.2 || ^18.0.0", "react-native": "^0.60.0" }, "optionalPeers": ["react", "react-native"] }, "sha512-tYJymLgJi+0516niv4ApGVC+VgENX/uCYqCX81tewSILWnS6KR7M0A9+bHyNk8xoheFyYGruX7onYxU2U8ykPA=="],
@@ -1926,25 +1926,25 @@
"react-native-linear-gradient": ["react-native-linear-gradient@2.8.3", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA=="],
"react-native-mmkv": ["react-native-mmkv@3.3.3", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-GMsfOmNzx0p5+CtrCFRVtpOOMYNJXuksBVARSQrCFaZwjUyHJdQzcN900GGaFFNTxw2fs8s5Xje//RDKj9+PZA=="],
"react-native-mmkv": ["react-native-mmkv@4.1.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }, "sha512-ia76WnU6dkLZxFkSSflxqFgHT2pIaML763aucEu7nMglF41oEWTdTtBu0o8a1cxbhZOaONk6KF8RQp5fLvPitA=="],
"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-ota": ["react-native-nitro-ota@0.7.2", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "^0.29.8" } }, "sha512-DUa2/QhFJBhSbzrTHGrc+qm1pSuJctccUcHlHZXjPV4fCEpi+4Y17QqI9U4D9MUnnP77afKEZJKFy+0NQeSAdA=="],
"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-pager-view": ["react-native-pager-view@7.0.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-yj/v6BN/WGuV1VBVWaCjYOjQlhTaqJs4Ocismw0XRSsHGqO2wuQdWF8it5iFnfibQVBBED0/GC7qKOlQ4FBzNg=="],
"react-native-reanimated": ["react-native-reanimated@4.1.5", "", { "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-UA6VUbxwhRjEw2gSNrvhkusUq3upfD3Cv+AnB07V+kC8kpvwRVI+ivwY95ePbWNFkFpP+Y2Sdw1WHpHWEV+P2Q=="],
"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=="],
"react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="],
"react-native-screens": ["react-native-screens@4.18.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-mRTLWL7Uc1p/RFNveEIIrhP22oxHduC2ZnLr/2iHwBeYpGXR0rJZ7Bgc0ktxQSHRjWTPT70qc/7yd4r9960PBQ=="],
"react-native-screens": ["react-native-screens@4.19.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-qSDAO3AL5bti0Ri7KZRSVmWlhDr8MV86N5GruiKVQfEL7Zx2nUi3Dl62lqHUAD/LnDvOPuDDsMHCfIpYSv3hPQ=="],
"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.0", "", { "dependencies": { "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0" } }, "sha512-TUbh7Yr0tE/99t1pJQLbQ+4/Px67xkT7/r3AhfV+93Q3WoUira0Lx7yuKUP2C118doqxub8NCLERwcqsHr29nQ=="],
"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=="],
@@ -1956,13 +1956,13 @@
"react-native-uuid": ["react-native-uuid@2.0.3", "", {}, "sha512-f/YfIS2f5UB+gut7t/9BKGSCYbRA9/74A5R1MDp+FLYsuS+OSWoiM/D8Jko6OJB6Jcu3v6ONuddvZKHdIGpeiw=="],
"react-native-worklets": ["react-native-worklets@0.6.1", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "semver": "7.7.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", "react-native": "*" } }, "sha512-URca8l7c7Uog7gv4mcg9KILdJlnbvwdS5yfXQYf5TDkD2W1VY1sduEKrD+sA3lUPXH/TG1vmXAvNxCNwPMYgGg=="],
"react-native-worklets": ["react-native-worklets@0.7.1", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "7.27.1", "@babel/plugin-transform-class-properties": "7.27.1", "@babel/plugin-transform-classes": "7.28.4", "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", "@babel/plugin-transform-optional-chaining": "7.27.1", "@babel/plugin-transform-shorthand-properties": "7.27.1", "@babel/plugin-transform-template-literals": "7.27.1", "@babel/plugin-transform-unicode-regex": "7.27.1", "@babel/preset-typescript": "7.27.1", "convert-source-map": "2.0.0", "semver": "7.7.3" }, "peerDependencies": { "@babel/core": "*", "react": "*", "react-native": "*" } }, "sha512-KNsvR48ULg73QhTlmwPbdJLPsWcyBotrGPsrDRDswb5FYpQaJEThUKc2ncXE4UM5dn/ewLoQHjSjLaKUVPxPhA=="],
"react-native-worklets-core": ["react-native-worklets-core@1.6.2", "", { "dependencies": { "string-hash-64": "^1.0.3" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-zw73JfL40ZL/OD2TOil1El4D9ZwS3l6AFPeFfUWXh+V2/dHN8i28jHX8QXlz5DYtAkR+Ju3U1h4yiaODi/igZw=="],
"react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
"react-test-renderer": ["react-test-renderer@19.1.1", "", { "dependencies": { "react-is": "^19.1.1", "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-aGRXI+zcBTtg0diHofc7+Vy97nomBs9WHHFY1Csl3iV0x6xucjNYZZAkiVKGiNYUv23ecOex5jE67t8ZzqYObA=="],
"react-test-renderer": ["react-test-renderer@19.2.0", "", { "dependencies": { "react-is": "^19.2.0", "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-zLCFMHFE9vy/w3AxO0zNxy6aAupnCuLSVOJYDe/Tp+ayGI1f2PLQsFVPANSD42gdSbmYx5oN+1VWDhcXtq7hAQ=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
@@ -2136,7 +2136,7 @@
"tabbable": ["tabbable@6.3.0", "", {}, "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ=="],
"tamagui": ["tamagui@1.139.1", "", { "dependencies": { "@tamagui/accordion": "1.139.1", "@tamagui/adapt": "1.139.1", "@tamagui/alert-dialog": "1.139.1", "@tamagui/animate-presence": "1.139.1", "@tamagui/avatar": "1.139.1", "@tamagui/button": "1.139.1", "@tamagui/card": "1.139.1", "@tamagui/checkbox": "1.139.1", "@tamagui/compose-refs": "1.139.1", "@tamagui/constants": "1.139.1", "@tamagui/core": "1.139.1", "@tamagui/create-context": "1.139.1", "@tamagui/dialog": "1.139.1", "@tamagui/elements": "1.139.1", "@tamagui/fake-react-native": "1.139.1", "@tamagui/focusable": "1.139.1", "@tamagui/font-size": "1.139.1", "@tamagui/form": "1.139.1", "@tamagui/get-button-sized": "1.139.1", "@tamagui/get-font-sized": "1.139.1", "@tamagui/get-token": "1.139.1", "@tamagui/group": "1.139.1", "@tamagui/helpers-tamagui": "1.139.1", "@tamagui/image": "1.139.1", "@tamagui/label": "1.139.1", "@tamagui/linear-gradient": "1.139.1", "@tamagui/list-item": "1.139.1", "@tamagui/polyfill-dev": "1.139.1", "@tamagui/popover": "1.139.1", "@tamagui/popper": "1.139.1", "@tamagui/portal": "1.139.1", "@tamagui/progress": "1.139.1", "@tamagui/radio-group": "1.139.1", "@tamagui/react-native-media-driver": "1.139.1", "@tamagui/scroll-view": "1.139.1", "@tamagui/select": "1.139.1", "@tamagui/separator": "1.139.1", "@tamagui/shapes": "1.139.1", "@tamagui/sheet": "1.139.1", "@tamagui/slider": "1.139.1", "@tamagui/stacks": "1.139.1", "@tamagui/switch": "1.139.1", "@tamagui/tabs": "1.139.1", "@tamagui/text": "1.139.1", "@tamagui/theme": "1.139.1", "@tamagui/toggle-group": "1.139.1", "@tamagui/tooltip": "1.139.1", "@tamagui/use-controllable-state": "1.139.1", "@tamagui/use-debounce": "1.139.1", "@tamagui/use-force-update": "1.139.1", "@tamagui/use-window-dimensions": "1.139.1", "@tamagui/visually-hidden": "1.139.1", "@tamagui/z-index-stack": "1.139.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-z0eT+uC+hI5JT0AIv1JwxN2s9wZDGeUJc1fJ2vLrkIYgPME0f/OYTbjuO+idhcXllL/RvSeFC5MA/ZxPM4ZfxQ=="],
"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=="],
"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=="],
@@ -2178,7 +2178,7 @@
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
@@ -2274,7 +2274,11 @@
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="],
"zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="],
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
"zustand": ["zustand@5.0.9", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg=="],
"@babel/eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@2.1.0", "", {}, "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw=="],
@@ -2354,10 +2358,20 @@
"@react-native/community-cli-plugin/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"@react-native/dev-middleware/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"@react-native/eslint-config/eslint-config-prettier": ["eslint-config-prettier@8.10.2", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A=="],
"@react-native/eslint-config/eslint-plugin-react-native": ["eslint-plugin-react-native@4.1.0", "", { "dependencies": { "eslint-plugin-react-native-globals": "^0.1.1" }, "peerDependencies": { "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "sha512-QLo7rzTBOl43FvVqDdq5Ql9IoElIuTdjrz9SKAXCvULvBoRZ44JGSkx9z4999ZusCsb4rK3gjS8gOGyeYqZv2Q=="],
"@tanstack/react-query/@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="],
"@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=="],
"@types/react-test-renderer/@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -2404,6 +2418,8 @@
"eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
"eslint-plugin-react-hooks/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
@@ -2546,13 +2562,23 @@
"react-native/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=="],
"react-native/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"react-native/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"react-native/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"react-native-blob-util/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=="],
"react-native-reanimated/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"react-native-worklets/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"react-native-worklets/@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg=="],
"react-native-worklets/@babel/preset-typescript": ["@babel/preset-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="],
"react-native-worklets/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"react-test-renderer/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"resolve-cwd/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
@@ -2664,6 +2690,8 @@
"connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"eslint-plugin-react-hooks/hermes-parser/hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
"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=="],

View File

@@ -543,7 +543,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 267;
CURRENT_PROJECT_VERSION = 273;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_BITCODE = NO;
@@ -554,7 +554,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 1.0.6;
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 = 267;
CURRENT_PROJECT_VERSION = 273;
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.0;
MARKETING_VERSION = 1.0.6;
NEW_SETTING = "";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -698,6 +698,7 @@
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
@@ -788,6 +789,7 @@
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
@@ -821,7 +823,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 267;
CURRENT_PROJECT_VERSION = 273;
DEVELOPMENT_TEAM = WAH9CZ8BPG;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -832,7 +834,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.0;
MARKETING_VERSION = 1.0.6;
NEW_SETTING = "";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -920,6 +922,7 @@
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",

View File

@@ -23,7 +23,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>12</string>
<string>13</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
@@ -32,40 +32,6 @@
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsForMedia</key>
<true/>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSAllowsLocalNetworkingUsageDescription</key>
<dict>
<key>NSATSExceptionThirdPartyServiceConnectionUsage</key>
<dict>
<key>NSATSExceptionThirdPartyServiceConnectionPurpose</key>
<array>
<dict>
<key>NSThirdPartyMediaStreaming</key>
<true/>
</dict>
</array>
</dict>
</dict>
<key>NSExceptionDomains</key>
<dict>
<key>nip.io</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>100.64.0.0/10</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>NSBonjourServices</key>
<array>
@@ -73,7 +39,7 @@
<string>_CC1AD845._googlecast._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>${PRODUCT_NAME} uses the local network to connect to one&apos;s Jellyfin server for streaming music</string>
<string>${PRODUCT_NAME} uses the local network to connect to one's Jellyfin server for streaming music</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>RCTNewArchEnabled</key>

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,7 @@ module.exports = {
'./jest/setup/nitro-ota.ts',
'./tamagui.config.ts',
'./jest/setup/native-modules.ts',
'./jest/setup/worklets.ts',
],
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transformIgnorePatterns: [

1
jest/setup/worklets.ts Normal file
View File

@@ -0,0 +1 @@
jest.mock('react-native-worklets', () => require('react-native-worklets/src/mock'))

View File

@@ -36,7 +36,7 @@ appId: com.cosmonautical.jellify
- assertVisible:
text: "Downloaded Tracks"
- assertVisible:
text: "Automatically Cache Tracks"
text: "Auto-Download Tracks"
- assertVisible:
text: "Download Quality"

View File

@@ -1,6 +1,6 @@
{
"name": "jellify",
"version": "1.0.0",
"version": "1.0.6",
"private": true,
"scripts": {
"init-android": "bun i",
@@ -43,29 +43,29 @@
"@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.10",
"@react-navigation/material-top-tabs": "7.4.7",
"@react-navigation/native": "7.1.23",
"@react-navigation/native-stack": "7.8.4",
"@sentry/react-native": "7.6.0",
"@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.139.1",
"@tanstack/query-async-storage-persister": "5.89.0",
"@tanstack/react-query": "5.89.0",
"@tanstack/react-query-persist-client": "5.89.0",
"@tamagui/config": "1.141.4",
"@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",
"invert-color": "^2.0.0",
"lodash": "^4.17.21",
"openai": "5.21.0",
"react": "19.1.1",
"react-native": "0.82.1",
"react": "19.2.0",
"react-native": "0.83.0",
"react-native-background-actions": "^4.0.1",
"react-native-blob-util": "^0.22.2",
"react-native-blurhash": "2.1.1",
"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",
@@ -75,48 +75,48 @@
"react-native-google-cast": "^4.9.1",
"react-native-haptic-feedback": "^2.3.3",
"react-native-linear-gradient": "^2.8.3",
"react-native-mmkv": "3.3.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.7.2",
"react-native-nitro-ota": "0.8.2",
"react-native-pager-view": "^7.0.2",
"react-native-reanimated": "4.1.5",
"react-native-reanimated": "4.1.6",
"react-native-safe-area-context": "5.6.2",
"react-native-screens": "4.18.0",
"react-native-screens": "4.19.0",
"react-native-sortables": "1.9.4",
"react-native-text-ticker": "^1.15.0",
"react-native-toast-message": "^2.3.3",
"react-native-track-player": "5.0.0-alpha0",
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-worklets": "0.6.1",
"react-native-worklets": "^0.7.1",
"react-native-worklets-core": "^1.6.2",
"ruby": "^0.6.1",
"scheduler": "^0.26.0",
"tamagui": "1.139.1",
"zustand": "^5.0.8"
"tamagui": "1.141.4",
"zustand": "5.0.9"
},
"devDependencies": {
"@babel/core": "^7.28.0",
"@babel/preset-env": "^7.28.0",
"@babel/runtime": "^7.28.0",
"@babel/core": "7.28.5",
"@babel/preset-env": "7.28.5",
"@babel/runtime": "7.28.4",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.32.0",
"@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.82.1",
"@react-native/eslint-config": "0.82.1",
"@react-native/metro-config": "0.82.1",
"@react-native/typescript-config": "0.82.1",
"@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",
"@types/jest": "^30.0.0",
"@types/lodash": "^4.17.20",
"@types/lodash": "^4.17.21",
"@types/node": "^24.2.1",
"@types/react": "^19.1.1",
"@types/react": "19.2.0",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "19.1.0",
"babel-plugin-module-resolver": "^5.0.2",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.33.0",
"eslint": "9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.4",
@@ -124,15 +124,15 @@
"eslint-plugin-react-native": "^5.0.0",
"globals": "^16.3.0",
"husky": "^9.1.7",
"jest": "^30.0.5",
"jest": "30.2.0",
"jscodeshift": "^17.3.0",
"lint-staged": "^16.1.5",
"patch-package": "8.0.0",
"prettier": "^3.6.2",
"react-dom": "^19.1.0",
"prettier": "3.7.4",
"react-dom": "19.2.0",
"react-native-cli-bump-version": "^1.5.1",
"react-test-renderer": "19.1.1",
"typescript": "5.9.2"
"react-test-renderer": "19.2.0",
"typescript": "5.9.3"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [

View File

@@ -1,29 +0,0 @@
diff --git a/node_modules/react-native-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashViewManager.kt b/node_modules/react-native-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashViewManager.kt
index 9224e9a..193540b 100644
--- a/node_modules/react-native-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashViewManager.kt
+++ b/node_modules/react-native-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashViewManager.kt
@@ -108,7 +108,7 @@ class BlurhashViewManager :
return image
}
- override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? {
+ override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
return MapBuilder.builder<String, Any>()
.put(LoadErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadError"))
.put(LoadStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadStart"))
diff --git a/node_modules/react-native-blurhash/ios/BlurhashViewManager.swift b/node_modules/react-native-blurhash/ios/BlurhashViewManager.swift
index a22972f..8415cdc 100644
--- a/node_modules/react-native-blurhash/ios/BlurhashViewManager.swift
+++ b/node_modules/react-native-blurhash/ios/BlurhashViewManager.swift
@@ -88,6 +88,11 @@ final class BlurhashViewWrapper: UIView, BlurhashViewDelegate {
blurhashView.frame = frame
}
+ override func layoutSubviews() {
+ super.layoutSubviews()
+ blurhashView.frame = bounds
+ }
+
override func didSetProps(_: [String]!) {
blurhashView.finalizeUpdates()
}

View File

@@ -0,0 +1,13 @@
diff --git a/node_modules/react-native-reanimated/compatibility.json b/node_modules/react-native-reanimated/compatibility.json
index f1ba9cb..234363b 100644
--- a/node_modules/react-native-reanimated/compatibility.json
+++ b/node_modules/react-native-reanimated/compatibility.json
@@ -4,7 +4,7 @@
"react-native-worklets": ["nightly"]
},
"4.1.x": {
- "react-native": ["0.78", "0.79", "0.80", "0.81", "0.82"],
+ "react-native": ["0.78", "0.79", "0.80", "0.81", "0.82", "0.83"],
"react-native-worklets": ["0.5.x", "0.6.x", "0.7.x"]
},
"4.0.x": {

View File

@@ -1,17 +0,0 @@
diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm
index 65c18f1..e11b806 100644
--- a/node_modules/react-native-screens/ios/RNSScreen.mm
+++ b/node_modules/react-native-screens/ios/RNSScreen.mm
@@ -928,7 +928,11 @@ - (BOOL)isTransparentModal
- (void)invalidate
{
_controller = nil;
- [_sheetsScrollView removeObserver:self forKeyPath:@"bounds" context:nil];
+ @try {
+ [_sheetsScrollView removeObserver:self forKeyPath:@"bounds" context:nil];
+ } @catch (NSException *exception) {
+ RCTLogWarn(@"[RNScreens] Tried to remove observer in invalidate, but it was not registered: %@.", exception);
+ }
}
#if !TARGET_OS_TV && !TARGET_OS_VISION

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1005 KiB

After

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 875 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 KiB

After

Width:  |  Height:  |  Size: 696 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 KiB

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 622 KiB

After

Width:  |  Height:  |  Size: 600 KiB

View File

@@ -17,6 +17,7 @@ const useDiscoverQueries = () => {
refetchPublicPlaylists(),
refetchArtistSuggestions(),
]),
networkMode: 'online',
})
}

View File

@@ -1,4 +1,4 @@
import { MMKV } from 'react-native-mmkv'
import { createMMKV } from 'react-native-mmkv'
import RNFS from 'react-native-fs'
import JellifyTrack from '../../../types/JellifyTrack'
@@ -27,8 +27,16 @@ const getExtensionFromUrl = (url: string): string | null => {
const normalizeExtension = (ext: string | undefined | null) => {
if (!ext) return null
let extension
const clean = ext.toLowerCase()
return clean === 'mpeg' ? 'mp3' : clean
if (clean.includes('mpeg')) extension = 'mp3'
else if (clean.includes('m4a')) extension = 'm4a'
else extension = clean
return extension
}
const extensionFromContentType = (contentType: string | undefined): string | null => {
@@ -52,6 +60,11 @@ export async function downloadJellyfinFile(
setDownloadProgress: JellifyDownloadProgressState,
preferredExtension?: string | null,
): Promise<DownloadedFileInfo> {
// Validate URL before attempting download to prevent NPE in native code
if (!url || url.trim() === '') {
throw new Error('Invalid download URL: URL is empty or undefined')
}
try {
const urlExtension = normalizeExtension(getExtensionFromUrl(url))
const hintedExtension = normalizeExtension(preferredExtension)
@@ -117,7 +130,7 @@ export async function downloadJellyfinFile(
}
}
const mmkv = new MMKV({
const mmkv = createMMKV({
id: 'offlineMode',
encryptionKey: 'offlineMode',
})
@@ -160,6 +173,12 @@ export const saveAudio = async (
//Ignore
}
// Validate track URL before attempting download
if (!track.url || track.url.trim() === '') {
console.error('Cannot download track: URL is missing', track.item.Id)
return false
}
try {
const downloadedTrackFile = await downloadJellyfinFile(
track.url,
@@ -169,10 +188,11 @@ export const saveAudio = async (
track.mediaSourceInfo?.Container,
)
let downloadedArtworkFile: DownloadedFileInfo | undefined
if (track.artwork) {
// Check for non-empty artwork URL (empty string passes truthy check but fails download)
if (track.artwork && typeof track.artwork === 'string' && track.artwork.trim() !== '') {
downloadedArtworkFile = await downloadJellyfinFile(
track.artwork as string,
track.item.Id as string,
track.artwork,
`${track.item.Id}-artwork`,
track.title as string,
setDownloadProgress,
undefined,
@@ -309,7 +329,7 @@ export const deleteDownloadsByIds = async (
export const deleteAudioCache = async (): Promise<DeleteDownloadsResult> => {
const downloads = getAudioCache()
const result = await deleteDownloadsByIds(downloads.map((download) => download.item.Id))
mmkv.delete(MMKV_OFFLINE_MODE_KEYS.AUDIO_CACHE)
mmkv.remove(MMKV_OFFLINE_MODE_KEYS.AUDIO_CACHE)
return result
}

View File

@@ -24,6 +24,7 @@ const useHomeQueries = () => {
await Promise.allSettled([refetchFrequentArtists(), refetchRecentArtists()])
return true
},
networkMode: 'online',
})
}

View File

@@ -20,4 +20,4 @@ export const useDownloadedTrack = (itemId: string | null | undefined) =>
useDownloadedTracks([itemId])?.at(0)
export const useIsDownloaded = (itemIds: (string | null | undefined)[]) =>
useDownloadedTracks(itemIds)?.length === itemIds.length
useDownloadedTracks(itemIds)?.length === itemIds.length && itemIds.length > 0

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,45 @@
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, H5 } from 'tamagui'
import { ItemCard } from '../Global/components/item-card'
export default function AlbumTrackListFooter({ album }: { album: BaseItemDto }): React.JSX.Element {
const navigation =
useNavigation<
NativeStackNavigationProp<
HomeStackParamList | LibraryStackParamList | DiscoverStackParamList
>
>()
return (
<YStack marginLeft={'$2'}>
{album.ArtistItems && album.ArtistItems.length > 1 && (
<>
<H5>Featuring</H5>
<FlashList
data={album.ArtistItems}
horizontal
renderItem={({ item: artist }) => (
<ItemCard
size={'$8'}
item={artist}
caption={artist.Name ?? 'Unknown Artist'}
onPress={() => {
navigation.navigate('Artist', {
artist,
})
}}
/>
)}
/>
</>
)}
</YStack>
)
}

View File

@@ -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 && (
<Text
bold
color={'$primary'}
onPress={() =>
navigation.navigate('Artist', {
artist: album.AlbumArtists![0],
})
}
textAlign='center'
fontSize={'$5'}
paddingBottom={'$2'}
>
{album.AlbumArtists![0].Name ?? 'Untitled Artist'}
</Text>
)}
<XStack justify='center' gap={'$3'} marginBottom={'$2'}>
<YStack flex={1}>
{album.ProductionYear ? (
<Text fontVariant={['tabular-nums']} textAlign='right'>
{album.ProductionYear?.toString() ?? 'Unknown Year'}
</Text>
) : null}
</YStack>
<Separator vertical />
<RunTimeTicks props={{ flex: 1, textAlign: 'left' }}>
{album.RunTimeTicks}
</RunTimeTicks>
</XStack>
{discs && (
<XStack alignContent='center' gap={'$2'} marginHorizontal={'$2'}>
<Animated.View
style={{
flex: 2,
}}
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Button
icon={() => <Icon small name='play' color='$primary' />}
borderWidth={'$1'}
borderColor={'$primary'}
flex={1}
onPress={() => playAlbum(false)}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
animation={'bouncy'}
>
<Text bold color={'$primary'}>
Play
</Text>
</Button>
</Animated.View>
<InstantMixButton item={album} navigation={navigation} />
<Animated.View
style={{
flex: 2,
}}
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Button
icon={() => <Icon small name='shuffle' color='$primary' />}
borderWidth={'$1'}
borderColor={'$primary'}
flex={1}
onPress={() => playAlbum(true)}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
animation={'bouncy'}
>
<Text bold color={'$primary'}>
Shuffle
</Text>
</Button>
</Animated.View>
</XStack>
)}
</YStack>
</YStack>
)
}

View File

@@ -1,32 +1,25 @@
import { YStack, XStack, Separator, getToken, Spacer, Spinner } from 'tamagui'
import { H5, Text } from '../Global/helpers/text'
import { FlatList, SectionList } from 'react-native'
import { RunTimeTicks } from '../Global/helpers/time-codes'
import { YStack, XStack, Separator, Spinner } from 'tamagui'
import { Text } from '../Global/helpers/text'
import { SectionList } from 'react-native'
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, { useCallback } from 'react'
import { useSafeAreaFrame } from 'react-native-safe-area-context'
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 useAddToPendingDownloads, { useIsDownloading } from '../../stores/network/downloads'
import { useIsDownloaded } from '../../api/queries/download'
import AlbumTrackListFooter from './footer'
import AlbumTrackListHeader from './header'
import Animated, { FadeInUp, FadeOutDown, LinearTransition } from 'react-native-reanimated'
import { useStorageContext } from '../../providers/Storage'
/**
* The screen for an Album's track list
@@ -46,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 : [],
@@ -61,6 +54,59 @@ 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={FadeInUp.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={FadeInUp.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'
@@ -77,16 +123,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
}}
@@ -102,172 +138,15 @@ export function Album({ album }: { album: BaseItemDto }): React.JSX.Element {
)}
ListFooterComponent={() => <AlbumTrackListFooter album={album} />}
ListEmptyComponent={() => (
<YStack flex={1} alignContent='center'>
{isPending ? <Spinner color={'$primary'} /> : <Text>No tracks found</Text>}
<YStack flex={1} alignContent='center' margin={'$4'}>
{isPending ? (
<Spinner color={'$primary'} />
) : (
<Text color={'$borderColor'}>No album tracks</Text>
)}
</YStack>
)}
onScrollBeginDrag={closeAllSwipeableRows}
/>
)
}
/**
* 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 { width } = useSafeAreaFrame()
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 = useCallback(
(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,
})
},
[discs, loadNewQueue],
)
return (
<YStack marginTop={'$4'} alignItems='center'>
<XStack justifyContent='center'>
<ItemImage item={album} width={'$20'} height={'$20'} />
<Spacer />
<YStack alignContent='center' justifyContent='center'>
<H5
lineBreakStrategyIOS='standard'
textAlign='center'
numberOfLines={5}
minWidth={width / 2.25}
maxWidth={width / 2.15}
>
{album.Name ?? 'Untitled Album'}
</H5>
<XStack justify='center' marginVertical={'$2'}>
<YStack flex={1}>
{album.ProductionYear ? (
<Text textAlign='right'>
{album.ProductionYear?.toString() ?? 'Unknown Year'}
</Text>
) : null}
</YStack>
<Separator vertical marginHorizontal={'$3'} />
<YStack flex={1}>
<RunTimeTicks>{album.RunTimeTicks}</RunTimeTicks>
</YStack>
</XStack>
<XStack
justifyContent='center'
marginVertical={'$2'}
gap={'$4'}
flexWrap='wrap'
>
<FavoriteButton item={album} />
<InstantMixButton item={album} navigation={navigation} />
<Icon name='play' onPress={() => playAlbum(false)} small />
<Icon name='shuffle' onPress={() => playAlbum(true)} small />
</XStack>
</YStack>
</XStack>
<FlatList
contentContainerStyle={{
marginTop: getToken('$4'),
}}
style={{
alignSelf: 'center',
}}
horizontal
keyExtractor={(item) => item.Id!}
data={album.AlbumArtists}
renderItem={({ item: artist }) => (
<ItemCard
size={'$10'}
item={artist}
caption={artist.Name ?? 'Unknown Artist'}
onPress={() => {
navigation.navigate('Artist', {
artist,
})
}}
/>
)}
/>
</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>
<FlatList
data={album.ArtistItems}
horizontal
renderItem={({ item: artist }) => (
<ItemCard
size={'$8'}
item={artist}
caption={artist.Name ?? 'Unknown Artist'}
onPress={() => {
navigation.navigate('Artist', {
artist,
})
}}
/>
)}
/>
</>
)}
</YStack>
)
}

View File

@@ -1,4 +1,3 @@
import { RefreshControl } from 'react-native'
import { Separator, useTheme, XStack, YStack } from 'tamagui'
import React, { RefObject, useEffect, useRef } from 'react'
import { Text } from '../Global/helpers/text'
@@ -14,6 +13,7 @@ import { isString } from 'lodash'
import FlashListStickyHeader from '../Global/helpers/flashlist-sticky-header'
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
import useLibraryStore from '../../stores/library'
import { RefreshControl } from 'react-native'
interface AlbumsProps {
albumsInfiniteQuery: UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error>

View File

@@ -1,12 +1,11 @@
import { ImageType } from '@jellyfin/sdk/lib/generated-client'
import LinearGradient from 'react-native-linear-gradient'
import { getTokenValue, useTheme, XStack, YStack, ZStack } from 'tamagui'
import { 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'
@@ -17,7 +16,6 @@ import { QueuingType } from '../../enums/queuing-type'
import { useNetworkStatus } from '../../stores/network'
import useStreamingDeviceProfile from '../../stores/device-profile'
import { useApi } from '../../stores'
import useIsLightMode from '../../hooks/use-is-light-mode'
export default function ArtistHeader(): React.JSX.Element {
const { width } = useSafeAreaFrame()
@@ -32,10 +30,6 @@ export default function ArtistHeader(): React.JSX.Element {
const loadNewQueue = useLoadNewQueue()
const theme = useTheme()
const isLightMode = useIsLightMode()
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
const playArtist = async (shuffled: boolean = false) => {
@@ -92,7 +86,7 @@ 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}>

View File

@@ -1,7 +1,6 @@
import React, { RefObject, useEffect, useRef } from 'react'
import { Separator, useTheme, XStack, YStack } from 'tamagui'
import { Text } from '../Global/helpers/text'
import { RefreshControl } from 'react-native'
import ItemRow from '../Global/components/item-row'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'
import { FlashList, FlashListRef } from '@shopify/flash-list'
@@ -14,6 +13,7 @@ import LibraryStackParamList from '../../screens/Library/types'
import FlashListStickyHeader from '../Global/helpers/flashlist-sticky-header'
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
import useLibraryStore from '../../stores/library'
import { RefreshControl } from 'react-native'
export interface ArtistsProps {
artistsInfiniteQuery: UseInfiniteQueryResult<
@@ -142,7 +142,7 @@ export default function Artists({
refreshControl={
<RefreshControl
refreshing={artistsInfiniteQuery.isPending && !isAlphabetSelectorPending}
onRefresh={() => artistsInfiniteQuery.refetch()}
onRefresh={artistsInfiniteQuery.refetch}
tintColor={theme.primary.val}
/>
}

View File

@@ -0,0 +1,35 @@
import navigationRef from '../../../../navigation'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
import { StackActions, TabActions, useNavigation } from '@react-navigation/native'
import { ListItem } from 'tamagui'
import Icon from '../../Global/components/icon'
import { Text } from '../../Global/helpers/text'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import LibraryStackParamList from '@/src/screens/Library/types'
export default function DeletePlaylistRow({
playlist,
}: {
playlist: BaseItemDto
}): React.JSX.Element {
return (
<ListItem
backgroundColor={'transparent'}
gap={'$2.5'}
justifyContent='flex-start'
onPress={() => {
navigationRef.dispatch(
StackActions.push('DeletePlaylist', {
playlist,
onDelete: navigationRef.goBack,
}),
)
}}
pressStyle={{ opacity: 0.5 }}
>
<Icon small name='delete' color='$warning' />
<Text bold>Delete Playlist</Text>
</ListItem>
)
}

View File

@@ -31,11 +31,8 @@ import { useDeleteDownloads } from '../../api/mutations/download'
import useHapticFeedback from '../../hooks/use-haptic-feedback'
import { Platform } from 'react-native'
import { useApi } from '../../stores'
import useAddToPendingDownloads, {
useIsDownloading,
usePendingDownloads,
} from '../../stores/network/downloads'
import { networkStatusTypes } from '../Network/internetConnectionWatcher'
import useAddToPendingDownloads, { useIsDownloading } from '../../stores/network/downloads'
import DeletePlaylistRow from './components/delete-playlist-row'
type StackNavigation = Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
@@ -58,8 +55,6 @@ export default function ItemContext({
const trigger = useHapticFeedback()
const [networkStatus] = useNetworkStatus()
const isArtist = item.Type === BaseItemKind.MusicArtist
const isAlbum = item.Type === BaseItemKind.MusicAlbum
const isTrack = item.Type === BaseItemKind.Audio
@@ -95,6 +90,8 @@ export default function ItemContext({
const renderViewAlbumRow = isAlbum || (isTrack && album)
const renderDeletePlaylistRow = isPlaylist && item.CanDelete
const artistIds = !isPlaylist
? isArtist
? [item.Id]
@@ -116,6 +113,8 @@ export default function ItemContext({
<YGroup scrollable={Platform.OS === 'android'} marginBottom={'$8'}>
<FavoriteContextMenuRow item={item} />
{renderDeletePlaylistRow && <DeletePlaylistRow playlist={item} />}
{renderAddToQueueRow && <AddToQueueMenuRow tracks={itemTracks} />}
{renderAddToQueueRow && <DownloadMenuRow items={itemTracks} />}

View File

@@ -1,22 +1,22 @@
import React from 'react'
import { getToken, ScrollView, useTheme, YStack } from 'tamagui'
import RecentlyAdded from './helpers/just-added'
import { RefreshControl } from 'react-native'
import PublicPlaylists from './helpers/public-playlists'
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'
export default function Index(): React.JSX.Element {
const theme = useTheme()
const { mutateAsync: refreshAsync, isPending: refreshing } = useDiscoverQueries()
const isRestoring = useIsRestoring()
const { isPending: loadingInitialData } = useRecentlyAddedAlbums()
const theme = useTheme()
return (
<ScrollView
contentContainerStyle={{
@@ -28,8 +28,8 @@ export default function Index(): React.JSX.Element {
removeClippedSubviews
refreshControl={
<RefreshControl
refreshing={refreshing || isRestoring || loadingInitialData}
onRefresh={refreshAsync}
refreshing={refreshing || isRestoring || loadingInitialData}
tintColor={theme.primary.val}
/>
}

View File

@@ -9,57 +9,61 @@ import navigationRef from '../../../../navigation'
import { useRecentlyAddedAlbums } from '../../../api/queries/album'
import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
export default function RecentlyAdded(): React.JSX.Element | undefined {
export default function RecentlyAdded(): React.JSX.Element | null {
const recentlyAddedAlbumsInfinityQuery = useRecentlyAddedAlbums()
const navigation = useNavigation<NativeStackNavigationProp<DiscoverStackParamList>>()
return (
recentlyAddedAlbumsInfinityQuery.data && (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
testID='discover-recently-added'
>
<XStack
alignItems='center'
onPress={() => {
navigation.navigate('RecentlyAdded', {
albumsInfiniteQuery: recentlyAddedAlbumsInfinityQuery,
})
}}
>
<H5 marginLeft={'$2'}>Recently Added</H5>
<Icon name='arrow-right' />
</XStack>
const recentlyAddedExists =
recentlyAddedAlbumsInfinityQuery.data && recentlyAddedAlbumsInfinityQuery.data.length > 0
<HorizontalCardList
data={recentlyAddedAlbumsInfinityQuery.data?.slice(0, 10) ?? []}
renderItem={({ item }) => (
<ItemCard
caption={item.Name}
subCaption={`${item.Artists?.join(', ')}`}
squared
size={'$11'}
item={item}
onPress={() => {
navigation.navigate('Album', {
album: item,
})
}}
onLongPress={() => {
navigationRef.navigate('Context', {
item,
navigation,
})
}}
gap={'$1'}
captionAlign='left'
/>
)}
/>
</Animated.View>
)
)
return recentlyAddedExists ? (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
testID='discover-recently-added'
style={{
flex: 1,
}}
>
<XStack
alignItems='center'
onPress={() => {
navigation.navigate('RecentlyAdded', {
albumsInfiniteQuery: recentlyAddedAlbumsInfinityQuery,
})
}}
>
<H5 marginLeft={'$2'}>Recently Added</H5>
<Icon name='arrow-right' />
</XStack>
<HorizontalCardList
data={recentlyAddedAlbumsInfinityQuery.data?.slice(0, 10) ?? []}
renderItem={({ item }) => (
<ItemCard
caption={item.Name}
subCaption={`${item.Artists?.join(', ')}`}
squared
size={'$11'}
item={item}
onPress={() => {
navigation.navigate('Album', {
album: item,
})
}}
onLongPress={() => {
navigationRef.navigate('Context', {
item,
navigation,
})
}}
gap={'$1'}
captionAlign='left'
/>
)}
/>
</Animated.View>
) : null
}

View File

@@ -11,7 +11,7 @@ import { useJellifyServer } from '../../../stores'
import { usePublicPlaylists } from '../../../api/queries/playlist'
import Animated, { FadeIn, LinearTransition } from 'react-native-reanimated'
export default function PublicPlaylists() {
export default function PublicPlaylists(): React.JSX.Element | null {
const {
data: playlists,
fetchNextPage,
@@ -25,57 +25,61 @@ export default function PublicPlaylists() {
const [server] = useJellifyServer()
const { width } = useSafeAreaFrame()
return (
playlists && (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeIn.springify()}
layout={LinearTransition.springify()}
testID='discover-public-playlists'
const publicPlaylistsExist = playlists && playlists.length > 0
return publicPlaylistsExist ? (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeIn.springify()}
layout={LinearTransition.springify()}
testID='discover-public-playlists'
style={{
flex: 1,
}}
>
<XStack
alignItems='center'
onPress={() => {
navigation.navigate('PublicPlaylists', {
playlists,
navigation: navigation,
fetchNextPage,
hasNextPage,
isPending,
isFetchingNextPage,
refetch,
})
}}
>
<XStack
alignItems='center'
onPress={() => {
navigation.navigate('PublicPlaylists', {
playlists,
navigation: navigation,
fetchNextPage,
hasNextPage,
isPending,
isFetchingNextPage,
refetch,
})
}}
>
<H5 marginLeft={'$2'} lineBreakStrategyIOS='standard' maxWidth={width * 0.8}>
Playlists on {server?.name ?? 'Jellyfin'}
</H5>
<Icon name='arrow-right' />
</XStack>
<HorizontalCardList
data={playlists?.slice(0, 10) ?? []}
renderItem={({ item }) => (
<ItemCard
caption={item.Name}
subCaption={`${item.Genres?.join(', ')}`}
squared
size={'$10'}
item={item}
onPress={() => {
navigation.navigate('Playlist', { playlist: item, canEdit: false })
}}
onLongPress={() =>
navigationRef.navigate('Context', {
item,
navigation,
})
}
marginHorizontal={'$1'}
captionAlign='left'
/>
)}
/>
</Animated.View>
)
)
<H5 marginLeft={'$2'} lineBreakStrategyIOS='standard' maxWidth={width * 0.8}>
Playlists on {server?.name ?? 'Jellyfin'}
</H5>
<Icon name='arrow-right' />
</XStack>
<HorizontalCardList
data={playlists?.slice(0, 10) ?? []}
renderItem={({ item }) => (
<ItemCard
caption={item.Name}
subCaption={`${item.Genres?.join(', ')}`}
squared
size={'$10'}
item={item}
onPress={() => {
navigation.navigate('Playlist', { playlist: item, canEdit: false })
}}
onLongPress={() =>
navigationRef.navigate('Context', {
item,
navigation,
})
}
marginHorizontal={'$1'}
captionAlign='left'
/>
)}
/>
</Animated.View>
) : null
}

View File

@@ -10,55 +10,59 @@ import { pickFirstGenre } from '../../../utils/genre-formatting'
import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'
import { useDiscoverArtists } from '../../../api/queries/suggestions'
export default function SuggestedArtists(): React.JSX.Element | undefined {
export default function SuggestedArtists(): React.JSX.Element | null {
const suggestedArtistsInfiniteQuery = useDiscoverArtists()
const navigation = useNavigation<NativeStackNavigationProp<DiscoverStackParamList>>()
return (
suggestedArtistsInfiniteQuery.data && (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
testID='discover-suggested-artists'
const suggestedArtistsExist =
suggestedArtistsInfiniteQuery.data && suggestedArtistsInfiniteQuery.data.length > 0
return suggestedArtistsExist ? (
<Animated.View
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
testID='discover-suggested-artists'
style={{
flex: 1,
}}
>
<XStack
alignItems='center'
onPress={() => {
navigation.navigate('SuggestedArtists', {
artistsInfiniteQuery: suggestedArtistsInfiniteQuery,
navigation: navigation,
})
}}
marginLeft={'$2'}
>
<XStack
alignItems='center'
onPress={() => {
navigation.navigate('SuggestedArtists', {
artistsInfiniteQuery: suggestedArtistsInfiniteQuery,
navigation: navigation,
})
}}
marginLeft={'$2'}
>
<H5>Suggested Artists</H5>
<Icon name='arrow-right' />
</XStack>
<HorizontalCardList
data={suggestedArtistsInfiniteQuery.data?.slice(0, 10) ?? []}
renderItem={({ item }) => (
<ItemCard
caption={item.Name}
subCaption={pickFirstGenre(item.Genres)}
size={'$10'}
item={item}
onPress={() => {
navigation.navigate('Artist', {
artist: item,
})
}}
onLongPress={() =>
navigationRef.navigate('Context', {
item,
navigation,
})
}
/>
)}
/>
</Animated.View>
)
)
<H5>Suggested Artists</H5>
<Icon name='arrow-right' />
</XStack>
<HorizontalCardList
data={suggestedArtistsInfiniteQuery.data?.slice(0, 10) ?? []}
renderItem={({ item }) => (
<ItemCard
caption={item.Name}
subCaption={pickFirstGenre(item.Genres)}
size={'$10'}
item={item}
onPress={() => {
navigation.navigate('Artist', {
artist: item,
})
}}
onLongPress={() =>
navigationRef.navigate('Context', {
item,
navigation,
})
}
/>
)}
/>
</Animated.View>
) : null
}

View File

@@ -457,8 +457,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 +540,8 @@ export default function SwipeableRow({
backgroundColor={action.color}
borderRadius={0}
pressStyle={{ opacity: 0.8 }}
accessibilityRole='button'
accessibilityLabel={`Right quick action ${action.icon}`}
role='button'
aria-label={`Right quick action ${action.icon}`}
onPress={() => {
action.onPress()
close()

View File

@@ -7,7 +7,7 @@ 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 { memo, useCallback, useMemo, useState } from 'react'
import { useCallback, useState } from 'react'
import { useApi } from '../../../stores'
interface ItemImageProps {
@@ -22,51 +22,35 @@ interface ItemImageProps {
imageOptions?: ImageUrlOptions
}
const ItemImage = memo(
function ItemImage({
item,
type = ImageType.Primary,
cornered,
circular,
width,
height,
testID,
imageOptions,
}: ItemImageProps): React.JSX.Element {
const api = useApi()
function ItemImage({
item,
type = ImageType.Primary,
cornered,
circular,
width,
height,
testID,
imageOptions,
}: ItemImageProps): React.JSX.Element {
const api = useApi()
const imageUrl = useMemo(
() => getItemImageUrl(api, item, type, imageOptions),
[api, item.Id, type, imageOptions],
)
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}
/>
) : (
<></>
)
},
(prevProps, nextProps) =>
prevProps.item.Id === nextProps.item.Id &&
prevProps.type === nextProps.type &&
prevProps.cornered === nextProps.cornered &&
prevProps.circular === nextProps.circular &&
prevProps.width === nextProps.width &&
prevProps.height === nextProps.height &&
prevProps.testID === nextProps.testID &&
prevProps.imageOptions?.maxWidth === nextProps.imageOptions?.maxWidth &&
prevProps.imageOptions?.maxHeight === nextProps.imageOptions?.maxHeight &&
prevProps.imageOptions?.quality === nextProps.imageOptions?.quality,
)
return imageUrl ? (
<Image
item={item}
type={type}
imageUrl={imageUrl!}
testID={testID}
height={height}
width={width}
circular={circular}
cornered={cornered}
/>
) : (
<></>
)
}
interface ItemBlurhashProps {
item: BaseItemDto
@@ -88,19 +72,20 @@ const Styles = StyleSheet.create({
},
})
const ItemBlurhash = memo(
function ItemBlurhash({ item, type }: ItemBlurhashProps): React.JSX.Element {
const blurhash = getBlurhashFromDto(item, type)
function ItemBlurhash({ item, type, testID }: 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>
)
},
(prevProps: ItemBlurhashProps, nextProps: ItemBlurhashProps) =>
prevProps.item.Id === nextProps.item.Id && prevProps.type === nextProps.type,
)
return (
<Animated.View style={Styles.blurhash} entering={FadeIn} exiting={FadeOut}>
<Blurhash
resizeMode={'cover'}
style={Styles.blurhashInner}
blurhash={blurhash}
testID={testID}
/>
</Animated.View>
)
}
interface ImageProps {
imageUrl: string
@@ -113,60 +98,40 @@ interface ImageProps {
testID?: string | undefined
}
const Image = memo(
function Image({
item,
type = ImageType.Primary,
imageUrl,
width,
height,
circular,
cornered,
testID,
}: ImageProps): React.JSX.Element {
const [isLoaded, setIsLoaded] = useState<boolean>(false)
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 handleImageLoad = useCallback(() => setIsLoaded(true), [setIsLoaded])
const imageViewStyle = useMemo(
() => getImageStyleSheet(width, height, cornered, circular),
[cornered, circular, width, height],
)
const imageViewStyle = getImageStyleSheet(width, height, cornered, circular)
const imageSource = useMemo(() => ({ uri: imageUrl }), [imageUrl])
const imageSource = { uri: imageUrl }
const blurhash = useMemo(
() => (!isLoaded ? <ItemBlurhash item={item} type={type} /> : null),
[isLoaded],
)
const blurhash = !isLoaded ? <ItemBlurhash item={item} type={type} testID={testID} /> : 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>
)
},
(prevProps, nextProps) => {
return (
prevProps.imageUrl === nextProps.imageUrl &&
prevProps.type === nextProps.type &&
prevProps.item.Id === nextProps.item.Id &&
prevProps.cornered === nextProps.cornered &&
prevProps.circular === nextProps.circular &&
prevProps.width === nextProps.width &&
prevProps.height === nextProps.height &&
prevProps.testID === nextProps.testID
)
},
)
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>
)
}
function getImageStyleSheet(
width: Token | string | number | string | undefined,

View File

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

View File

@@ -14,7 +14,7 @@ import { useNetworkStatus } from '../../../stores/network'
import useStreamingDeviceProfile from '../../../stores/device-profile'
import useItemContext from '../../../hooks/use-item-context'
import { RouteProp, useRoute } from '@react-navigation/native'
import React from 'react'
import React, { useEffect } from 'react'
import { LayoutChangeEvent } from 'react-native'
import Animated, {
SharedValue,
@@ -211,9 +211,9 @@ function ItemRowDetails({ item }: { item: BaseItemDto }): React.JSX.Element {
const route = useRoute<RouteProp<BaseStackParamList>>()
const shouldRenderArtistName =
item.Type === 'Audio' || (item.Type === 'MusicAlbum' && route.name !== 'Artist')
item.Type === 'Audio' || (item.Type === 'MusicAlbum' && !route.name.includes('Overview'))
const shouldRenderProductionYear = item.Type === 'MusicAlbum' && route.name === 'Artist'
const shouldRenderProductionYear = item.Type === 'MusicAlbum' && route.name.includes('Overview')
const shouldRenderGenres = item.Type === 'Playlist' || item.Type === BaseItemKind.MusicArtist

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { Spinner, ToggleGroup, XStack, YStack } from 'tamagui'
import React, { useEffect, useRef, useState } from 'react'
import { H3, Spinner, ToggleGroup, XStack, YStack } from 'tamagui'
import { H2, Text } from '../helpers/text'
import Button from '../helpers/button'
import { SafeAreaView } from 'react-native-safe-area-context'
@@ -9,6 +9,13 @@ import { fetchUserViews } from '../../../api/queries/libraries'
import { useQuery } from '@tanstack/react-query'
import Icon from './icon'
import { useApi, useJellifyLibrary, useJellifyUser } from '../../../stores'
import Animated, {
FadeIn,
FadeInUp,
FadeOut,
FadeOutUp,
LinearTransition,
} from 'react-native-reanimated'
interface LibrarySelectorProps {
onLibrarySelected: (
@@ -73,9 +80,15 @@ export default function LibrarySelector({
useEffect(() => {
if (!isPending && isSuccess && libraries) {
setMusicLibraries(
libraries.filter((library) => library.CollectionType === CollectionType.Music),
const filteredMusicLibraries = libraries.filter(
(library) => library.CollectionType === CollectionType.Music,
)
setMusicLibraries(filteredMusicLibraries)
// Auto-select if there's only one music library
if (filteredMusicLibraries.length === 1 && !selectedLibraryId) {
setSelectedLibraryId(filteredMusicLibraries[0].Id)
}
// Find the playlist library
const foundPlaylistLibrary = libraries.find(
@@ -86,100 +99,135 @@ export default function LibrarySelector({
}
}, [isPending, isSuccess, libraries])
const libraryToggleItems = useMemo(
() =>
musicLibraries.map((library) => {
const isSelected: boolean = selectedLibraryId === library.Id!
const libraryToggleItems = musicLibraries.map((library) => {
const isSelected: boolean = selectedLibraryId === library.Id!
return (
<ToggleGroup.Item
key={library.Id}
value={library.Id!}
aria-label={library.Name!}
pressStyle={{
scale: 0.9,
}}
backgroundColor={isSelected ? '$primary' : '$background'}
>
<Text
fontWeight={isSelected ? 'bold' : '600'}
color={isSelected ? '$background' : '$neutral'}
>
{library.Name ?? 'Unnamed Library'}
</Text>
</ToggleGroup.Item>
)
}),
[selectedLibraryId, musicLibraries],
)
return (
<ToggleGroup.Item
key={library.Id}
value={library.Id!}
aria-label={library.Name!}
pressStyle={{
scale: 0.9,
}}
backgroundColor={isSelected ? '$primary' : '$background'}
borderWidth={hasMultipleLibraries ? 1 : 0}
borderColor={isSelected ? '$primary' : '$borderColor'}
>
<Text
fontWeight={isSelected ? 'bold' : '600'}
color={isSelected ? '$background' : '$neutral'}
>
{library.Name ?? 'Unnamed Library'}
</Text>
</ToggleGroup.Item>
)
})
return (
<SafeAreaView style={{ flex: 1 }}>
<YStack
flex={1}
justifyContent='center'
paddingHorizontal={'$4'}
marginBottom={isOnboarding ? '$20' : 'unset'}
<YStack
flex={1}
justifyContent='center'
paddingHorizontal={'$4'}
marginBottom={isOnboarding ? '$20' : '$4'}
>
<Animated.View
entering={FadeInUp.springify()}
exiting={FadeOutUp.springify()}
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'flex-end',
}}
>
<YStack flex={1} alignItems='center' justifyContent='flex-end'>
<H2 textAlign='center' marginBottom={'$2'}>
{title}
</H2>
{!hasMultipleLibraries && !isOnboarding && (
<Text color='$borderColor' textAlign='center'>
Only one music library is available
</Text>
)}
</YStack>
<H3 textAlign='center' marginBottom={'$2'}>
{title}
</H3>
</Animated.View>
{!hasMultipleLibraries && !isOnboarding && (
<Animated.View entering={FadeIn.springify()} exiting={FadeOut.springify()}>
<Text color='$borderColor' textAlign='center'>
Only one music library is available
</Text>
</Animated.View>
)}
<YStack justifyContent='center' flexGrow={1} minHeight={'$12'} gap={'$4'}>
{isPending ? (
<Spinner size='large' />
) : isError ? (
<Text color='$danger' textAlign='center'>
Unable to load libraries
</Text>
) : (
<ToggleGroup
orientation='vertical'
type='single'
animation={'quick'}
disableDeactivation={true}
value={selectedLibraryId}
onValueChange={setSelectedLibraryId}
disabled={!hasMultipleLibraries && !isOnboarding}
>
{libraryToggleItems}
</ToggleGroup>
)}
</YStack>
<XStack alignItems='flex-end' gap={'$3'} marginTop={'$4'}>
{showCancelButton && (
<Button
variant='outlined'
icon={() => <Icon name={cancelButtonIcon} small />}
onPress={onCancel}
flex={1}
>
{cancelButtonText}
</Button>
)}
<Animated.View
style={{
justifyContent: 'center',
flexGrow: 1,
}}
>
{isPending ? (
<Spinner size='large' enterStyle={{ opacity: 1 }} exitStyle={{ opacity: 0 }} />
) : isError ? (
<LoadErrorMessage />
) : musicLibraries.length === 0 ? (
<NoLibrariesMessage />
) : (
<ToggleGroup
enterStyle={{ opacity: 1 }}
exitStyle={{ opacity: 0 }}
orientation='vertical'
type='single'
animation={'quick'}
disableDeactivation={true}
value={selectedLibraryId}
onValueChange={setSelectedLibraryId}
disabled={!hasMultipleLibraries && !isOnboarding}
>
{libraryToggleItems}
</ToggleGroup>
)}
</Animated.View>
<XStack alignItems='flex-end' gap={'$3'} marginTop={'$4'}>
{showCancelButton && (
<Button
variant='outlined'
borderColor={'$primary'}
color={'$primary'}
disabled={!selectedLibraryId}
icon={() => <Icon name={primaryButtonIcon} small color='$primary' />}
onPress={handleLibrarySelection}
testID='let_s_go_button'
icon={() => <Icon name={cancelButtonIcon} small />}
onPress={onCancel}
flex={1}
>
{primaryButtonText}
{cancelButtonText}
</Button>
</XStack>
</YStack>
</SafeAreaView>
)}
<Button
variant='outlined'
borderColor={'$primary'}
color={'$primary'}
disabled={!selectedLibraryId}
icon={() => <Icon name={primaryButtonIcon} small color='$primary' />}
onPress={handleLibrarySelection}
testID='let_s_go_button'
flex={1}
>
{primaryButtonText}
</Button>
</XStack>
</YStack>
)
}
function LoadErrorMessage(): React.JSX.Element {
return (
<Text color='$warning' textAlign='center'>
Unable to load libraries
</Text>
)
}
function NoLibrariesMessage(): React.JSX.Element {
return (
<YStack alignItems='center' gap={'$2'}>
<Icon name='alert' color='$warning' />
<Text color='$warning' textAlign='center'>
No music libraries found
</Text>
<Text color='$borderColor' textAlign='center' fontSize={'$3'}>
Please create a music library in Jellyfin to continue
</Text>
</YStack>
)
}

View File

@@ -45,8 +45,8 @@ export default function FrequentArtists(): React.JSX.Element {
return frequentArtistsInfiniteQuery.data ? (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
style={{
flex: 1,

View File

@@ -51,8 +51,8 @@ export default function RecentArtists(): React.JSX.Element {
return recentArtistsInfiniteQuery.data ? (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
style={{
flex: 1,

View File

@@ -34,8 +34,8 @@ export default function RecentlyPlayed(): React.JSX.Element {
return tracksInfiniteQuery.data ? (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
style={{
flex: 1,

View File

@@ -1,4 +1,4 @@
import { ScrollView, RefreshControl, Platform } from 'react-native'
import { ScrollView, Platform, RefreshControl } from 'react-native'
import { YStack, getToken, useTheme } from 'tamagui'
import RecentArtists from './helpers/recent-artists'
import RecentlyPlayed from './helpers/recently-played'
@@ -13,10 +13,10 @@ import { useRecentlyPlayedTracks } from '../../api/queries/recents'
const COMPONENT_NAME = 'Home'
export function Home(): React.JSX.Element {
usePreventRemove(true, () => {})
const theme = useTheme()
usePreventRemove(true, () => {})
usePerformanceMonitor(COMPONENT_NAME, 5)
const { isPending: refreshing, mutateAsync: refresh } = useHomeQueries()
@@ -33,7 +33,7 @@ export function Home(): React.JSX.Element {
}}
refreshControl={
<RefreshControl
refreshing={refreshing || isRestoring || loadingInitialData}
refreshing={refreshing || loadingInitialData || isRestoring}
onRefresh={refresh}
tintColor={theme.primary.val}
/>

View File

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

View File

@@ -112,7 +112,7 @@ const InternetConnectionWatcher = () => {
justifyContent='center'
alignContent='center'
backgroundColor={
networkStatus === networkStatusTypes.ONLINE ? '$success' : '$danger'
networkStatus === networkStatusTypes.ONLINE ? '$success' : '$warning'
}
>
<Text textAlign='center' color='$purpleDark'>

View File

@@ -67,7 +67,6 @@ function PlayerArtwork(): React.JSX.Element {
const animatedStyle = useAnimatedStyle(() => ({
width: withSpring(artworkMaxWidth.get()),
height: withSpring(artworkMaxWidth.get()),
opacity: withTiming(nowPlaying ? 1 : 0),
}))
const handleLayout = (event: LayoutChangeEvent) => {
@@ -87,12 +86,7 @@ function PlayerArtwork(): React.JSX.Element {
onLayout={handleLayout}
>
{nowPlaying && (
<Animated.View
key={`${nowPlaying!.item.AlbumId}-item-image`}
style={{
...animatedStyle,
}}
>
<Animated.View key={`${nowPlaying!.item.AlbumId}-item-image`} style={animatedStyle}>
<ItemImage
item={nowPlaying!.item}
testID='player-image-test-id'

View File

@@ -1,11 +1,8 @@
import React, { useCallback, useMemo, useState } from 'react'
import React from 'react'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { YStack, useTheme, ZStack, useWindowDimensions, View, getTokenValue } from 'tamagui'
import { YStack, ZStack, useWindowDimensions, View, getTokenValue } from 'tamagui'
import Scrubber from './components/scrubber'
import Controls from './components/controls'
import Toast from 'react-native-toast-message'
import JellifyToastConfig from '../../configs/toast.config'
import { useFocusEffect } from '@react-navigation/native'
import Footer from './components/footer'
import BlurredBackground from './components/blurred-background'
import PlayerHeader from './components/header'
@@ -29,22 +26,11 @@ import { useCurrentTrack } from '../../stores/player/queue'
export default function PlayerScreen(): React.JSX.Element {
usePerformanceMonitor('PlayerScreen', 5)
const [showToast, setShowToast] = useState(true)
const skip = useSkip()
const previous = usePrevious()
const trigger = useHapticFeedback()
const nowPlaying = useCurrentTrack()
const theme = useTheme()
useFocusEffect(
useCallback(() => {
setShowToast(true)
return () => setShowToast(false)
}, []),
)
const isAndroid = Platform.OS === 'android'
const { width, height } = useWindowDimensions()
@@ -63,49 +49,43 @@ export default function PlayerScreen(): React.JSX.Element {
}))
// Let the native sheet gesture handle vertical dismissals; we only own horizontal swipes
const sheetDismissGesture = useMemo(() => Gesture.Native(), [])
const sheetDismissGesture = Gesture.Native()
// Gesture logic for central big swipe area
const swipeGesture = useMemo(
() =>
Gesture.Pan()
.activeOffsetX([-12, 12])
// Bail on vertical intent so native sheet dismiss keeps working
.failOffsetY([-8, 8])
.simultaneousWithExternalGesture(sheetDismissGesture)
.onUpdate((e) => {
if (Math.abs(e.translationY) < 40) {
translateX.value = Math.max(-160, Math.min(160, e.translationX))
}
})
.onEnd((e) => {
const threshold = 120
const minVelocity = 600
const isHorizontal = Math.abs(e.translationY) < 40
if (
isHorizontal &&
(Math.abs(e.translationX) > threshold ||
Math.abs(e.velocityX) > minVelocity)
) {
if (e.translationX > 0) {
// Inverted: swipe right = previous
translateX.value = withSpring(220)
runOnJS(trigger)('notificationSuccess')
runOnJS(previous)()
} else {
// Inverted: swipe left = next
translateX.value = withSpring(-220)
runOnJS(trigger)('notificationSuccess')
runOnJS(skip)(undefined)
}
translateX.value = withDelay(160, withSpring(0))
} else {
translateX.value = withSpring(0)
}
}),
[previous, skip, trigger, translateX, sheetDismissGesture],
)
const swipeGesture = Gesture.Pan()
.activeOffsetX([-12, 12])
// Bail on vertical intent so native sheet dismiss keeps working
.failOffsetY([-8, 8])
.simultaneousWithExternalGesture(sheetDismissGesture)
.onUpdate((e) => {
if (Math.abs(e.translationY) < 40) {
translateX.value = Math.max(-160, Math.min(160, e.translationX))
}
})
.onEnd((e) => {
const threshold = 120
const minVelocity = 600
const isHorizontal = Math.abs(e.translationY) < 40
if (
isHorizontal &&
(Math.abs(e.translationX) > threshold || Math.abs(e.velocityX) > minVelocity)
) {
if (e.translationX > 0) {
// Inverted: swipe right = previous
translateX.value = withSpring(220)
runOnJS(trigger)('notificationSuccess')
runOnJS(previous)()
} else {
// Inverted: swipe left = next
translateX.value = withSpring(-220)
runOnJS(trigger)('notificationSuccess')
runOnJS(skip)(undefined)
}
translateX.value = withDelay(160, withSpring(0))
} else {
translateX.value = withSpring(0)
}
})
/**
* Styling for the top layer of Player ZStack
*
@@ -114,81 +94,69 @@ export default function PlayerScreen(): React.JSX.Element {
*
* Apple devices get a small amount of margin
*/
const mainContainerStyle = useMemo(
() => ({
marginTop: isAndroid ? top : getTokenValue('$4'),
marginBottom: bottom + getTokenValue('$10'),
}),
[top, bottom, isAndroid],
)
const mainContainerStyle = {
marginTop: isAndroid ? top : getTokenValue('$4'),
marginBottom: bottom + getTokenValue(isAndroid ? '$10' : '$12', 'space'),
}
return (
<View flex={1}>
{nowPlaying && (
<ZStack fullscreen>
<BlurredBackground width={width} height={height} />
return nowPlaying ? (
<ZStack width={width} height={height}>
<BlurredBackground width={width} height={height} />
{/* Swipe feedback icons (topmost overlay) */}
<Animated.View
pointerEvents='none'
style={{
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
zIndex: 9999,
}}
>
<YStack flex={1} justifyContent='center'>
<Animated.View
style={[{ position: 'absolute', left: 12 }, leftIconStyle]}
>
<Icon name='skip-next' color='$primary' large />
</Animated.View>
<Animated.View
style={[{ position: 'absolute', right: 12 }, rightIconStyle]}
>
<Icon name='skip-previous' color='$primary' large />
</Animated.View>
</YStack>
{/* Swipe feedback icons (topmost overlay) */}
<Animated.View
pointerEvents='none'
style={{
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
zIndex: 9999,
}}
>
<YStack flex={1} justifyContent='center'>
<Animated.View style={[{ position: 'absolute', left: 12 }, leftIconStyle]}>
<Icon name='skip-next' color='$primary' large />
</Animated.View>
<Animated.View style={[{ position: 'absolute', right: 12 }, rightIconStyle]}>
<Icon name='skip-previous' color='$primary' large />
</Animated.View>
</YStack>
</Animated.View>
{/* Central large swipe area overlay (captures swipe like big album art) */}
<GestureDetector
gesture={Gesture.Simultaneous(sheetDismissGesture, swipeGesture)}
>
<View
style={{
position: 'absolute',
top: height * 0.18,
left: width * 0.06,
right: width * 0.06,
height: height * 0.36,
zIndex: 9998,
}}
/>
</GestureDetector>
{/* Central large swipe area overlay (captures swipe like big album art) */}
<GestureDetector gesture={Gesture.Simultaneous(sheetDismissGesture, swipeGesture)}>
<View
style={{
position: 'absolute',
top: height * 0.18,
left: width * 0.06,
right: width * 0.06,
height: height * 0.36,
zIndex: 9998,
}}
/>
</GestureDetector>
<YStack
justifyContent='center'
flex={1}
marginHorizontal={'$5'}
{...mainContainerStyle}
>
{/* flexGrow 1 */}
<PlayerHeader />
<YStack
justifyContent='center'
flex={1}
marginHorizontal={'$5'}
{...mainContainerStyle}
>
{/* flexGrow 1 */}
<PlayerHeader />
<YStack justifyContent='flex-start' gap={'$4'} flexShrink={1}>
<SongInfo />
<Scrubber />
<Controls />
<Footer />
</YStack>
</YStack>
</ZStack>
)}
{showToast && <Toast config={JellifyToastConfig(theme)} />}
</View>
<YStack justifyContent='flex-start' gap={'$4'} flexShrink={1}>
<SongInfo />
<Scrubber />
<Controls />
<Footer />
</YStack>
</YStack>
</ZStack>
) : (
<></>
)
}

View File

@@ -22,10 +22,10 @@ import { runOnJS } from 'react-native-worklets'
import { RootStackParamList } from '../../screens/types'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import ItemImage from '../Global/components/image'
import { usePrevious, useSkip, useTogglePlayback } from '../../providers/Player/hooks/mutations'
import { usePrevious, useSkip } from '../../providers/Player/hooks/mutations'
import { useCurrentTrack } from '../../stores/player/queue'
export const Miniplayer = React.memo(function Miniplayer(): React.JSX.Element {
export default function Miniplayer(): React.JSX.Element {
const nowPlaying = useCurrentTrack()
const skip = useSkip()
const previous = usePrevious()
@@ -150,7 +150,7 @@ export const Miniplayer = React.memo(function Miniplayer(): React.JSX.Element {
</Animated.View>
</GestureDetector>
)
})
}
function MiniPlayerProgress(): React.JSX.Element {
const progress = useProgress(UPDATE_INTERVAL)

View File

@@ -73,7 +73,7 @@ export default function Queue({
await removeFromQueue(index)
}}
>
<Icon name='close' color='$danger' />
<Icon name='close' color='$warning' />
</Sortable.Touchable>
</XStack>
),

View File

@@ -1,10 +1,9 @@
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { H5, Spacer, XStack, YStack } from 'tamagui'
import InstantMixButton from '../../Global/components/instant-mix-button'
import { InstantMixButton } from '../../Global/components/instant-mix-button'
import Icon from '../../Global/components/icon'
import { useNetworkStatus } from '../../../stores/network'
import { ActivityIndicator } from 'react-native'
import { QueuingType } from '../../../enums/queuing-type'
import { useNavigation } from '@react-navigation/native'
import LibraryStackParamList from '@/src/screens/Library/types'
@@ -13,9 +12,16 @@ import useStreamingDeviceProfile from '../../../stores/device-profile'
import ItemImage from '../../Global/components/image'
import { useApi } from '../../../stores'
import Input from '../../Global/helpers/input'
import Animated, { FadeInDown, FadeOutDown } from 'react-native-reanimated'
import Animated, {
FadeInDown,
FadeInUp,
FadeOutDown,
LinearTransition,
} from 'react-native-reanimated'
import { Dispatch, SetStateAction } from 'react'
import useAddToPendingDownloads, { usePendingDownloads } 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,19 +94,14 @@ function PlaylistHeaderControls({
playlist: BaseItemDto
playlistTracks: BaseItemDto[]
}): React.JSX.Element {
const addToDownloadQueue = useAddToPendingDownloads()
const pendingDownloads = usePendingDownloads()
const streamingDeviceProfile = useStreamingDeviceProfile()
const loadNewQueue = useLoadNewQueue()
const isDownloading = pendingDownloads.length != 0
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
@@ -120,31 +120,54 @@ function PlaylistHeaderControls({
}
return (
<XStack justifyContent='center' marginVertical={'$1'} gap={'$2'} flexWrap='wrap'>
<YStack justifyContent='center' alignContent='center'>
<InstantMixButton item={playlist} navigation={navigation} />
</YStack>
<XStack justifyContent='center' marginHorizontal={'$2'} gap={'$2'}>
<Animated.View
style={{
flex: 2,
}}
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Button
animation={'bouncy'}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
borderColor={'$primary'}
borderWidth={'$1'}
onPress={() => playPlaylist(false)}
icon={<Icon name='play' color='$primary' small />}
>
<Text bold color={'$primary'}>
Play
</Text>
</Button>
</Animated.View>
<YStack justifyContent='center' alignContent='center'>
<Icon name='play' onPress={() => playPlaylist(false)} small />
</YStack>
<InstantMixButton item={playlist} navigation={navigation} />
<YStack justifyContent='center' alignContent='center'>
<Icon name='shuffle' onPress={() => playPlaylist(true)} small />
</YStack>
<YStack justifyContent='center' alignContent='center'>
{!isDownloading ? (
<Icon
color={'$borderColor'}
name={'download'}
onPress={downloadPlaylist}
small
/>
) : (
<ActivityIndicator />
)}
</YStack>
<Animated.View
style={{
flex: 2,
}}
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Button
animation={'bouncy'}
pressStyle={{ scale: 0.875 }}
hoverStyle={{ scale: 0.925 }}
borderColor={'$primary'}
borderWidth={'$1'}
onPress={() => playPlaylist(true)}
icon={<Icon name='shuffle' color='$primary' small />}
>
<Text bold color={'$primary'}>
Shuffle
</Text>
</Button>
</Animated.View>
</XStack>
)
}

View File

@@ -17,14 +17,25 @@ import { QueuingType } from '../../enums/queuing-type'
import { useApi } from '../../stores'
import useStreamingDeviceProfile from '../../stores/device-profile'
import { useCallback, useEffect, useLayoutEffect, useState } from 'react'
import { RefreshControl } from 'react-native'
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,50 +139,104 @@ 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 ?? [])
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () =>
canEdit && (
<XStack gap={'$3'}>
{editing && (
<>
headerRight: () => (
<XStack gap={'$2'}>
{playlistTracks &&
(isDownloaded ? (
<Animated.View
entering={FadeInUp.springify()}
exiting={FadeOutDown.springify()}
layout={LinearTransition.springify()}
>
<Icon
color={'$danger'}
name='delete-sweep-outline' // otherwise use "delete-circle"
onPress={() => {
navigationRef.dispatch(
StackActions.push('DeletePlaylist', { playlist }),
)
}}
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='$neutral'
name='close-circle-outline'
onPress={handleCancel}
color='$success'
name='download-circle-outline'
onPress={handleDownload}
/>
</>
)}
</Animated.View>
))}
{canEdit &&
(editing ? (
<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,
}),
)
}}
/>
{isUpdating || isPreparingEditMode ? (
<Icon
color='$neutral'
name='close-circle-outline'
onPress={handleCancel}
/>
</XStack>
</Animated.View>
) : 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
entering={FadeIn.springify()}
exiting={FadeOut.springify()}
layout={LinearTransition.springify()}
>
<Icon
name={editing ? 'floppy' : 'pencil'}
color={editing ? '$success' : '$color'}
onPress={() =>
!editing
? handleEnterEditMode()
: useUpdatePlaylist({
playlist,
tracks: playlistTracks ?? [],
newName,
})
}
/>
</Animated.View>
))}
)
</XStack>
),
})
}, [
editing,
@@ -246,7 +311,7 @@ export default function Playlist({
)
}}
>
<Icon name='close' color={'$danger'} />
<Icon name='close' color={'$warning'} />
</Sortable.Touchable>
</XStack>
)

View File

@@ -1,5 +1,4 @@
import React, { useCallback } from 'react'
import { RefreshControl } from 'react-native'
import { Separator, useTheme } from 'tamagui'
import { FlashList } from '@shopify/flash-list'
import ItemRow from '../Global/components/item-row'
@@ -9,6 +8,8 @@ import { useNavigation } from '@react-navigation/native'
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 {
@@ -72,6 +73,8 @@ export default function Playlists({
renderItem={renderItem}
onEndReached={handleEndReached}
removeClippedSubviews
onScrollBeginDrag={closeAllSwipeableRows}
ListEmptyComponent={<Text color={'$neutral'}>No playlists</Text>}
/>
)
}

View File

@@ -75,7 +75,6 @@ export default function Search({
placeholder='Seek and ye shall find'
onChangeText={(value) => handleSearchStringUpdate(value)}
value={searchString}
marginHorizontal={'$2'}
testID='search-input'
clearButtonMode='while-editing'
/>
@@ -149,7 +148,7 @@ export default function Search({
renderItem={({ item }) => <ItemRow item={item} navigation={navigation} />}
onScrollBeginDrag={handleScrollBeginDrag}
style={{
marginHorizontal: getToken('$2'),
marginHorizontal: getToken('$4'),
marginTop: getToken('$4'),
}}
/>

View File

@@ -1,6 +1,6 @@
import ItemRow from '../Global/components/item-row'
import { Text } from '../Global/helpers/text'
import { H3, Separator, YStack } from 'tamagui'
import { H5, Separator, Spinner, YStack } from 'tamagui'
import { ItemCard } from '../Global/components/item-card'
import HorizontalCardList from '../Global/components/horizontal-list'
import { FlashList } from '@shopify/flash-list'
@@ -26,7 +26,7 @@ export default function Suggestions({
data={suggestions?.filter((suggestion) => suggestion.Type !== 'MusicArtist')}
ListHeaderComponent={
<YStack>
<H3>Suggestions</H3>
<H5>Suggestions</H5>
<HorizontalCardList
data={suggestions?.filter(
@@ -51,17 +51,17 @@ export default function Suggestions({
}
ItemSeparatorComponent={() => <Separator />}
ListEmptyComponent={
<Text textAlign='center'>
Wake now, discover that you are the eyes of the world...
</Text>
<YStack justifyContent='center' alignContent='center'>
<Text textAlign='center'>
Wake now, discover that you are the eyes of the world...
</Text>
<Spinner color={'$primary'} />
</YStack>
}
onScrollBeginDrag={handleScrollBeginDrag}
renderItem={({ item }) => {
return <ItemRow item={item} navigation={navigation} />
}}
style={{
marginHorizontal: 2,
}}
/>
)
}

View File

@@ -5,12 +5,9 @@ import { Linking } from 'react-native'
import { ScrollView, XStack, YStack } from 'tamagui'
import Icon from '../../Global/components/icon'
import usePatrons from '../../../api/queries/patrons'
import { useQuery } from '@tanstack/react-query'
import { INFO_CAPTIONS } from '../../../configs/info.config'
import { ONE_HOUR } from '../../../constants/query-client'
import { pickRandomItemFromArray } from '../../../utils/random'
import { getStoredOtaVersion } from 'react-native-nitro-ota'
import { downloadUpdate } from '../../OtaUpdates'
import { useInfoCaption } from '../../../hooks/use-caption'
function PatronsList({ patrons }: { patrons: { fullName: string }[] | undefined }) {
if (!patrons?.length) return null
@@ -31,14 +28,7 @@ function PatronsList({ patrons }: { patrons: { fullName: string }[] | undefined
export default function InfoTab() {
const patrons = usePatrons()
const { data: caption } = useQuery({
queryKey: ['Info_Caption'],
queryFn: () => `${pickRandomItemFromArray(INFO_CAPTIONS)}`,
staleTime: ONE_HOUR,
initialData: 'Live and in stereo',
refetchOnMount: 'always',
refetchOnWindowFocus: 'always',
})
const { data: caption } = useInfoCaption()
const otaVersion = getStoredOtaVersion()
const otaVersionText = otaVersion ? `OTA Version: ${otaVersion}` : ''
return (
@@ -115,7 +105,7 @@ export default function InfoTab() {
title: 'Wall of Fame',
subTitle: 'Sponsor on GitHub, Patreon, or Ko-fi',
iconName: 'hand-heart',
iconColor: '$secondary',
iconColor: '$success',
children: (
<YStack>
<XStack

View File

@@ -9,17 +9,17 @@ export default function LabsTab(): React.JSX.Element {
return (
<SettingsListGroup
borderColor={'$danger'}
borderColor={'$warning'}
settingsList={[
{
title: 'Clear Artists Cache',
subTitle: 'Invalidates the artists in the library',
iconName: 'test-tube-off',
iconColor: '$danger',
iconColor: '$warning',
children: (
<Button
onPress={() => {
storage.delete(QueryKeys.InfiniteArtists)
storage.remove(QueryKeys.InfiniteArtists)
queryClient.invalidateQueries({
queryKey: [QueryKeys.InfiniteArtists],
})

View File

@@ -24,7 +24,7 @@ export default function PlaybackTab(): React.JSX.Element {
subTitle: `Changes apply to new tracks`,
iconName: 'radio-tower',
iconColor:
streamingQuality === StreamingQuality.Original ? '$primary' : '$danger',
streamingQuality === StreamingQuality.Original ? '$primary' : '$warning',
children: (
<RadioGroup
value={streamingQuality}
@@ -87,18 +87,3 @@ export default function PlaybackTab(): React.JSX.Element {
/>
)
}
function getStreamingQualityIconColor(streamingQuality: StreamingQuality): string {
switch (streamingQuality) {
case 'original':
return '$success'
case 'high':
return '$success'
case 'medium':
return '$secondary'
case 'low':
return '$danger'
default:
return '$borderColor'
}
}

View File

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

View File

@@ -11,15 +11,15 @@ export default function SignOut({
}): React.JSX.Element {
return (
<Button
color={'$danger'}
icon={() => <Icon name='hand-peace' small color={'$danger'} />}
borderColor={'$danger'}
color={'$warning'}
icon={() => <Icon name='hand-peace' small color={'$warning'} />}
borderColor={'$warning'}
marginHorizontal={'$6'}
onPress={() => {
navigation.navigate('SignOut')
}}
>
<Text bold color={'$danger'}>
<Text bold color={'$warning'}>
Sign Out
</Text>
</Button>

View File

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

View File

@@ -1,197 +0,0 @@
import React, { useEffect, useState } from 'react'
import { StyleSheet, Pressable, Alert, FlatList } from 'react-native'
import RNFS from 'react-native-fs'
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'
import { deleteAudioCache } from '../../api/mutations/download/offlineModeUtils'
import Icon from '../Global/components/icon'
import { getToken, 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={getToken('$danger')} />
<Text style={styles.deleteText}> Delete Downloads</Text>
</Pressable>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#1c1c2e',
},
title: {
color: 'white',
fontSize: 18,
fontWeight: '600',
marginBottom: 4,
},
usage: {
color: '#aaa',
fontSize: 14,
marginBottom: 12,
},
progressBackground: {
height: 10,
backgroundColor: '#333',
borderRadius: 5,
overflow: 'hidden',
},
progressFill: {
height: 10,
backgroundColor: '#ff2d75',
borderRadius: 5,
},
item: {
marginTop: 16,
},
label: {
color: '#ccc',
fontSize: 14,
marginBottom: 4,
},
downloadBar: {
height: 8,
backgroundColor: '#2e2e3f',
borderRadius: 4,
overflow: 'hidden',
},
downloadFill: {
height: 8,
backgroundColor: '#00bcd4',
},
deleteButton: {
marginTop: 30,
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'center',
padding: 12,
backgroundColor: '#2a0f13',
borderRadius: 8,
},
deleteText: {
color: '#ff4d4f',
fontSize: 15,
fontWeight: '600',
},
})

View File

@@ -10,9 +10,9 @@ import { Text } from '../Global/helpers/text'
import AZScroller, { useAlphabetSelector } from '../Global/components/alphabetical-selector'
import { UseInfiniteQueryResult } from '@tanstack/react-query'
import { isString } from 'lodash'
import { RefreshControl } from 'react-native'
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
import FlashListStickyHeader from '../Global/helpers/flashlist-sticky-header'
import { RefreshControl } from 'react-native'
interface TracksProps {
tracksInfiniteQuery: UseInfiniteQueryResult<(string | number | BaseItemDto)[], Error>

View File

@@ -1,10 +1,10 @@
import { MMKV } from 'react-native-mmkv'
import { createMMKV } from 'react-native-mmkv'
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
import { AsyncStorage as TanstackAsyncStorage } from '@tanstack/react-query-persist-client'
import { StateStorage } from 'zustand/middleware'
import AsyncStorage from '@react-native-async-storage/async-storage'
export const storage = new MMKV()
export const storage = createMMKV()
const storageFunctions = {
setItem: async (key: string, value: string) => {
@@ -28,7 +28,7 @@ const mmkvStorageFunctions = {
return value === undefined ? null : value
},
removeItem: (key: string) => {
storage.delete(key)
storage.remove(key)
},
}

View File

@@ -1,4 +1,4 @@
import { MMKV } from 'react-native-mmkv'
import { createMMKV } from 'react-native-mmkv'
import { StateStorage } from 'zustand/middleware'
import { storage } from './storage'
@@ -19,14 +19,17 @@ export const STORAGE_SCHEMA_VERSIONS: Record<string, number> = {
* Checks if a specific store needs to be cleared due to version bump
* and clears it if necessary
*/
export function migrateStorageIfNeeded(storeName: string, storage: MMKV): void {
export function migrateStorageIfNeeded(
storeName: string,
storage: ReturnType<typeof createMMKV>,
): void {
const versionKey = `${STORAGE_VERSION_KEY}:${storeName}`
const storedVersion = storage.getNumber(versionKey)
const currentVersion = STORAGE_SCHEMA_VERSIONS[storeName] ?? 1
if (storedVersion !== currentVersion) {
// Clear the stale storage for this specific store
storage.delete(storeName)
storage.remove(storeName)
// Update the version
storage.set(versionKey, currentVersion)
console.log(
@@ -56,7 +59,7 @@ export function createVersionedMmkvStorage(storeName: string): StateStorage {
storage.set(key, value)
},
removeItem: (key: string) => {
storage.delete(key)
storage.remove(key)
},
}
}
@@ -67,8 +70,8 @@ export function createVersionedMmkvStorage(storeName: string): StateStorage {
*/
export function clearAllVersionedStorage(): void {
Object.keys(STORAGE_SCHEMA_VERSIONS).forEach((storeName) => {
storage.delete(storeName)
storage.delete(`${STORAGE_VERSION_KEY}:${storeName}`)
storage.remove(storeName)
storage.remove(`${STORAGE_VERSION_KEY}:${storeName}`)
})
console.log('[Storage] Cleared all versioned storage')
}

30
src/hooks/use-caption.ts Normal file
View File

@@ -0,0 +1,30 @@
import { useQuery } from '@tanstack/react-query'
import { INFO_CAPTIONS } from '../configs/info.config'
import { ONE_HOUR } from '../constants/query-client'
import { pickRandomItemFromArray } from '../utils/random'
import { LOADING_CAPTIONS } from '../configs/loading.config'
enum CaptionQueryKeys {
InfoCaption,
LoadingCaption,
}
export const useInfoCaption = () =>
useQuery({
queryKey: [CaptionQueryKeys.InfoCaption],
queryFn: () => `${pickRandomItemFromArray(INFO_CAPTIONS)}`,
staleTime: ONE_HOUR,
initialData: 'Live and in stereo',
refetchOnMount: 'always',
refetchOnWindowFocus: 'always',
})
export const useLoadingCaption = () =>
useQuery({
queryKey: [CaptionQueryKeys.LoadingCaption],
queryFn: () => `${pickRandomItemFromArray(LOADING_CAPTIONS)}`,
staleTime: 0,
initialData: 'Reticulating splines',
refetchOnMount: 'always',
refetchOnWindowFocus: 'always',
})

View File

@@ -2,7 +2,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'
import Index from '../../components/Discover/component'
import AlbumScreen from '../Album'
import { ArtistScreen } from '../Artist'
import { useTheme } from 'tamagui'
import { getTokenValue, useTheme } from 'tamagui'
import RecentlyAdded from './albums'
import PublicPlaylists from './playlists'
import { PlaylistScreen } from '../Playlist'
@@ -22,8 +22,10 @@ export function Discover(): React.JSX.Element {
name='Discover'
component={Index}
options={{
headerTitleAlign: 'center',
headerTitleStyle: {
fontFamily: 'Figtree-Bold',
fontSize: getTokenValue('$6'),
},
}}
/>
@@ -55,6 +57,9 @@ export function Discover(): React.JSX.Element {
component={PlaylistScreen}
options={({ route }) => ({
title: route.params.playlist.Name ?? 'Untitled Playlist',
headerTitleStyle: {
color: theme.background.val,
},
})}
/>

View File

@@ -3,7 +3,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { PlaylistScreen } from '../Playlist'
import { Home as HomeComponent } from '../../components/Home'
import { ArtistScreen } from '../Artist'
import { useTheme } from 'tamagui'
import { getTokenValue, useTheme } from 'tamagui'
import HomeArtistsScreen from './artists'
import HomeTracksScreen from './tracks'
import AlbumScreen from '../Album'
@@ -28,8 +28,10 @@ export default function Home(): React.JSX.Element {
component={HomeComponent}
options={{
title: 'Home',
headerTitleAlign: 'center',
headerTitleStyle: {
fontFamily: 'Figtree-Bold',
fontSize: getTokenValue('$6'),
},
}}
/>

View File

@@ -11,7 +11,8 @@ import Icon from '../../components/Global/components/icon'
import LibraryStackParamList from './types'
import useHapticFeedback from '../../hooks/use-haptic-feedback'
import { useUserPlaylists } from '../../api/queries/playlist'
import { useApi, useJellifyUser, useJellifyLibrary } from '../../stores'
import { useApi, useJellifyUser } from '../../stores'
import { isEmpty } from 'lodash'
export default function AddPlaylist({
navigation,
@@ -20,7 +21,6 @@ export default function AddPlaylist({
}): React.JSX.Element {
const api = useApi()
const [user] = useJellifyUser()
const [library] = useJellifyLibrary()
const [name, setName] = useState<string>('')
const { refetch } = useUserPlaylists()
@@ -32,12 +32,6 @@ export default function AddPlaylist({
onSuccess: (data: void, { name }: { name: string }) => {
trigger('notificationSuccess')
// Burnt.alert({
// title: `Playlist created`,
// message: `Created playlist ${name}`,
// duration: 1,
// preset: 'done',
// })
Toast.show({
text1: 'Playlist created',
text2: `Created playlist ${name}`,
@@ -55,7 +49,7 @@ export default function AddPlaylist({
})
return (
<View margin={'$2'}>
<View margin={'$2'} flex={1}>
<Label size='$2' htmlFor='name'>
Name
</Label>
@@ -79,6 +73,7 @@ export default function AddPlaylist({
borderWidth={'$1'}
borderColor={'$primary'}
icon={() => <Icon name='content-save' small color={'$primary'} />}
disabled={isEmpty(name)}
>
<Text bold color={'$primary'}>
Save

View File

@@ -1,4 +1,4 @@
import { Spinner, View, XStack } from 'tamagui'
import { Spinner, XStack, YStack } from 'tamagui'
import Button from '../../components/Global/helpers/button'
import { Text } from '../../components/Global/helpers/text'
import { useMutation } from '@tanstack/react-query'
@@ -6,15 +6,16 @@ import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { deletePlaylist } from '../../api/mutations/playlists'
import { queryClient } from '../../constants/query-client'
import Icon from '../../components/Global/components/icon'
import { LibraryDeletePlaylistProps } from './types'
import { DeletePlaylistProps } from '../types'
import useHapticFeedback from '../../hooks/use-haptic-feedback'
import { useApi, useJellifyLibrary } from '../../stores'
import { UserPlaylistsQueryKey } from '../../api/queries/playlist/keys'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
export default function DeletePlaylist({
navigation,
route,
}: LibraryDeletePlaylistProps): React.JSX.Element {
}: DeletePlaylistProps): React.JSX.Element {
const api = useApi()
const [library] = useJellifyLibrary()
@@ -26,8 +27,9 @@ export default function DeletePlaylist({
onSuccess: (data: void, playlist: BaseItemDto) => {
trigger('notificationSuccess')
navigation.goBack()
navigation.goBack()
navigation.goBack() // Dismiss modal
route.params.onDelete()
// Refresh favorite playlists component in library
queryClient.invalidateQueries({
@@ -39,11 +41,13 @@ export default function DeletePlaylist({
},
})
const { bottom } = useSafeAreaInsets()
return (
<View margin={'$4'}>
<Text bold textAlign='center'>{`Delete playlist ${
route.params.playlist.Name ?? 'Untitled Playlist'
}?`}</Text>
<YStack margin={'$4'} gap={'$4'} justifyContent='space-between' marginBottom={bottom}>
<Text bold textAlign='center'>
{`Delete playlist ${route.params.playlist.Name ?? 'Untitled Playlist'}?`}
</Text>
<XStack justifyContent='space-evenly' gap={'$2'}>
<Button
onPress={() => navigation.goBack()}
@@ -60,23 +64,23 @@ export default function DeletePlaylist({
danger
flex={1}
borderWidth={'$1'}
borderColor={'$danger'}
borderColor={'$warning'}
onPress={() => useDeletePlaylist.mutate(route.params.playlist)}
icon={() =>
useDeletePlaylist.isPending && (
<Icon name='trash-can-outline' small color={'$danger'} />
<Icon name='trash-can-outline' small color={'$warning'} />
)
}
>
{useDeletePlaylist.isPending ? (
<Spinner color={'$danger'} />
<Spinner color={'$warning'} />
) : (
<Text bold color={'$danger'}>
<Text bold color={'$warning'}>
Delete
</Text>
)}
</Button>
</XStack>
</View>
</YStack>
)
}

View File

@@ -2,19 +2,18 @@ import React from 'react'
import Library from '../../components/Library/component'
import { PlaylistScreen } from '../Playlist'
import AddPlaylist from './add-playlist'
import DeletePlaylist from './delete-playlist'
import { ArtistScreen } from '../Artist'
import { useTheme } from 'tamagui'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import AlbumScreen from '../Album'
import LibraryStackParamList from './types'
import { LibraryTabProps } from '../Tabs/types'
import InstantMix from '../../components/InstantMix/component'
import { getItemName } from '../../utils/text'
import { Platform } from 'react-native'
const LibraryStack = createNativeStackNavigator<LibraryStackParamList>()
export default function LibraryScreen({ route, navigation }: LibraryTabProps): React.JSX.Element {
export default function LibraryScreen(): React.JSX.Element {
const theme = useTheme()
return (
@@ -73,30 +72,15 @@ export default function LibraryScreen({ route, navigation }: LibraryTabProps): R
})}
/>
<LibraryStack.Group
screenOptions={{
<LibraryStack.Screen
name='AddPlaylist'
component={AddPlaylist}
options={{
title: 'Add Playlist',
presentation: 'formSheet',
sheetAllowedDetents: 'fitToContents',
sheetAllowedDetents: Platform.OS === 'ios' ? 'fitToContents' : [0.5],
}}
>
<LibraryStack.Screen
name='AddPlaylist'
component={AddPlaylist}
options={{
title: 'Add Playlist',
}}
/>
<LibraryStack.Screen
name='DeletePlaylist'
component={DeletePlaylist}
options={{
title: 'Delete Playlist',
headerShown: false,
sheetGrabberVisible: true,
}}
/>
</LibraryStack.Group>
/>
</LibraryStack.Navigator>
)
}

View File

@@ -4,7 +4,7 @@ import { BaseStackParamList } from '../types'
import { NavigatorScreenParams } from '@react-navigation/native'
type LibraryStackParamList = BaseStackParamList & {
LibraryScreen: NavigatorScreenParams<BaseStackParamList>
LibraryScreen: NavigatorScreenParams<BaseStackParamList> | undefined
AddPlaylist: undefined
DeletePlaylist: {
@@ -14,7 +14,7 @@ type LibraryStackParamList = BaseStackParamList & {
export default LibraryStackParamList
export type LibraryScreenProps = NativeStackScreenProps<LibraryScreenParamList, 'LibraryScreen'>
export type LibraryScreenProps = NativeStackScreenProps<LibraryStackParamList, 'LibraryScreen'>
export type LibraryArtistProps = NativeStackScreenProps<LibraryStackParamList, 'Artist'>
export type LibraryAlbumProps = NativeStackScreenProps<LibraryStackParamList, 'Album'>
@@ -23,7 +23,3 @@ export type LibraryDeletePlaylistProps = NativeStackScreenProps<
LibraryStackParamList,
'DeletePlaylist'
>
type LibraryScreenParamList = {
LibraryScreen: NavigatorScreenParams<LibraryStackParamList>
}

View File

@@ -6,7 +6,7 @@ import MultipleArtistsSheet from '../Context/multiple-artists'
import { PlayerParamList } from './types'
import Lyrics from '../../components/Player/components/lyrics'
export const PlayerStack = createNativeStackNavigator<PlayerParamList>()
const PlayerStack = createNativeStackNavigator<PlayerParamList>()
export default function Player(): React.JSX.Element {
return (

View File

@@ -2,7 +2,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { ArtistScreen } from '../Artist'
import AlbumScreen from '../Album'
import { PlaylistScreen } from '../Playlist'
import { useTheme } from 'tamagui'
import { getTokenValue, useTheme } from 'tamagui'
import Search from '../../components/Search'
import SearchParamList from './types'
import InstantMix from '../../components/InstantMix/component'
@@ -20,8 +20,10 @@ export default function SearchStack(): React.JSX.Element {
component={Search}
options={{
title: 'Search',
headerTitleAlign: 'center',
headerTitleStyle: {
fontFamily: 'Figtree-Bold',
fontSize: getTokenValue('$6'),
},
}}
/>

View File

@@ -4,10 +4,9 @@ import { H5, Text } from '../../components/Global/helpers/text'
import Button from '../../components/Global/helpers/button'
import Icon from '../../components/Global/components/icon'
import { useResetQueue } from '../../providers/Player/hooks/mutations'
import navigationRef from '../../../navigation'
import { useClearAllDownloads } from '../../api/mutations/download'
import { useJellifyServer } from '../../stores'
import { StackActions, useNavigation } from '@react-navigation/native'
import { useNavigation } from '@react-navigation/native'
import { RootStackParamList } from '../types'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'

View File

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

View File

@@ -1,17 +1,14 @@
import { Miniplayer } from '../../components/Player/mini-player'
import Miniplayer from '../../components/Player/mini-player'
import InternetConnectionWatcher from '../../components/Network/internetConnectionWatcher'
import { BottomTabBar, BottomTabBarProps } from '@react-navigation/bottom-tabs'
import useIsMiniPlayerActive from '../../hooks/use-mini-player'
import { useIsFocused } from '@react-navigation/native'
export default function TabBar({ ...props }: BottomTabBarProps): React.JSX.Element {
const isFocused = useIsFocused()
const isMiniPlayerActive = useIsMiniPlayerActive()
return (
<>
{isMiniPlayerActive && isFocused && <Miniplayer />}
{isMiniPlayerActive && <Miniplayer />}
<InternetConnectionWatcher />
<BottomTabBar {...props} />

View File

@@ -4,7 +4,7 @@ import LibraryStackParamList from '../Library/types'
type TabParamList = {
HomeTab: undefined
LibraryTab: NavigatorScreenParams<LibraryStackParamList>
LibraryTab: undefined | NavigatorScreenParams<LibraryStackParamList>
SearchTab: undefined
DiscoverTab: undefined
SettingsTab: undefined

View File

@@ -13,6 +13,8 @@ import { Text } from '../components/Global/helpers/text'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import AudioSpecsSheet from './Stats'
import { useApi, useJellifyLibrary } from '../stores'
import DeletePlaylist from './Library/delete-playlist'
import { Platform } from 'react-native'
const RootStack = createNativeStackNavigator<RootStackParamList>()
@@ -37,8 +39,9 @@ export default function Root(): React.JSX.Element {
name='PlayerRoot'
component={Player}
options={{
presentation: 'formSheet',
sheetAllowedDetents: [1.0],
// Form Sheet gives swipe to dismiss for Android, but royally fucks up the display on iOS
presentation: Platform.OS === 'android' ? 'formSheet' : 'modal',
sheetAllowedDetents: Platform.OS === 'android' ? [1.0] : undefined,
headerShown: false,
}}
/>
@@ -82,6 +85,18 @@ export default function Root(): React.JSX.Element {
sheetGrabberVisible: true,
})}
/>
<RootStack.Screen
name='DeletePlaylist'
component={DeletePlaylist}
options={{
title: 'Delete Playlist',
presentation: 'formSheet',
headerShown: false,
sheetGrabberVisible: true,
sheetAllowedDetents: 'fitToContents',
}}
/>
</RootStack.Navigator>
)
}

View File

@@ -33,7 +33,6 @@ export type BaseStackParamList = {
InstantMix: {
item: BaseItemDto
mix: BaseItemDto[]
}
Tracks: {
@@ -72,6 +71,11 @@ export type RootStackParamList = {
streamingMediaSourceInfo?: MediaSourceInfo
downloadedMediaSourceInfo?: MediaSourceInfo
}
DeletePlaylist: {
playlist: BaseItemDto
onDelete: () => void
}
}
export type LoginProps = NativeStackNavigationProp<RootStackParamList, 'Login'>
@@ -81,6 +85,8 @@ export type ContextProps = NativeStackScreenProps<RootStackParamList, 'Context'>
export type AddToPlaylistProps = NativeStackScreenProps<RootStackParamList, 'AddToPlaylist'>
export type AudioSpecsProps = NativeStackScreenProps<RootStackParamList, 'AudioSpecs'>
export type DeletePlaylistProps = NativeStackScreenProps<RootStackParamList, 'DeletePlaylist'>
export type GenresProps = {
genres: InfiniteData<BaseItemDto[], unknown> | undefined
fetchNextPage: (options?: FetchNextPageOptions | undefined) => void

View File

@@ -67,9 +67,11 @@ export const useIsDownloading = (items: BaseItemDto[]) => {
...currentDownloads.map((download) => download.item.Id),
])
const itemIds = items.map((item) => item.Id)
return itemIds.filter((id) => downloadQueue.has(id)).length === items.length
return (
items.length !== 0 &&
(pendingDownloads.length !== 0 || currentDownloads.length !== 0) &&
items.filter((item) => downloadQueue.has(item.Id)).length > 0
)
}
export const useAddToCompletedDownloads = () => {

View File

@@ -23,6 +23,18 @@ import StreamingQuality from '../enums/audio-quality'
import { getAudioCache } from '../api/mutations/download/offlineModeUtils'
import RNFS from 'react-native-fs'
/**
* Ensures a valid session ID is returned.
* The ?? operator doesn't catch empty strings, so we need this helper.
* Empty session IDs cause MusicService to crash with "Session ID must be unique. ID="
*/
function getValidSessionId(sessionId: string | null | undefined): string {
if (sessionId && sessionId.trim() !== '') {
return sessionId
}
return uuid.v4().toString()
}
/**
* Gets the artwork URL for a track, prioritizing the track's own artwork over the album's artwork.
* Falls back to artist image if no album artwork is available.
@@ -169,16 +181,21 @@ function ensureFileUri(path?: string): string | undefined {
}
function buildDownloadedTrack(downloadedTrack: JellifyDownload): TrackMediaInfo {
// Safely build the image path - artwork is optional and may be undefined
const imagePath = downloadedTrack.artwork
? `file://${RNFS.DocumentDirectoryPath}/${downloadedTrack.artwork.split('/').pop()}`
: undefined
return {
type: TrackType.Default,
url: `file://${RNFS.DocumentDirectoryPath}/${downloadedTrack.path!.split('/').pop()}`,
image: `file://${RNFS.DocumentDirectoryPath}/${downloadedTrack.artwork!.split('/').pop()}`,
image: imagePath,
duration: convertRunTimeTicksToSeconds(
downloadedTrack.mediaSourceInfo?.RunTimeTicks || downloadedTrack.item.RunTimeTicks || 0,
),
item: downloadedTrack.item,
mediaSourceInfo: downloadedTrack.mediaSourceInfo,
sessionId: downloadedTrack.sessionId,
sessionId: getValidSessionId(downloadedTrack.sessionId),
sourceType: 'download',
}
}
@@ -198,7 +215,7 @@ function buildTranscodedTrack(
duration: convertRunTimeTicksToSeconds(RunTimeTicks ?? 0),
mediaSourceInfo,
item,
sessionId,
sessionId: getValidSessionId(sessionId),
sourceType: 'stream',
}
}
@@ -228,7 +245,7 @@ function buildAudioApiUrl(
const mediaSource = mediaInfo!.MediaSources![0]
urlParams = {
playSessionId: mediaInfo?.PlaySessionId ?? uuid.v4(),
playSessionId: getValidSessionId(mediaInfo?.PlaySessionId),
startTimeTicks: '0',
static: 'true',
}

View File

@@ -5,7 +5,10 @@ import { headingFont, bodyFont } from './fonts.config'
const tokens = createTokens({
...TamaguiTokens,
color: {
danger: '#ff9966',
dangerDark: '#FF066F',
dangerLight: '#B30077',
warningDark: '#FF6625',
warningLight: '#a93300ff',
purpleDark: '#0C0622',
tealLight: 'rgba(16, 175, 141, 1)',
tealDark: 'rgba(87, 233, 201, 1)',
@@ -17,9 +20,10 @@ const tokens = createTokens({
amethyst50: 'rgba(126, 114, 175, 0.5)',
amethyst75: 'rgba(126, 114, 175, 0.75)',
secondary: '#cc2f71',
secondaryDark: 'rgba(75, 125, 215, 1)',
secondaryLight: 'rgba(0, 58, 159, 1)',
primaryLight: '#6D2FFF',
primaryLight: '#4b0fd6ff',
primaryDark: '#887BFF',
white: '#ffffff',
neutral: '#77748E',
@@ -65,9 +69,10 @@ const jellifyConfig = createTamagui({
borderColor: tokens.color.neutral,
color: tokens.color.white,
success: tokens.color.tealDark,
secondary: tokens.color.secondary,
secondary: tokens.color.secondaryDark,
primary: tokens.color.primaryDark,
danger: tokens.color.danger,
danger: tokens.color.dangerDark,
warning: tokens.color.warningDark,
neutral: tokens.color.neutral,
translucent: tokens.color.darkTranslucent,
@@ -81,9 +86,10 @@ const jellifyConfig = createTamagui({
borderColor: tokens.color.neutral,
color: tokens.color.white,
success: tokens.color.tealDark,
secondary: tokens.color.secondary,
secondary: tokens.color.secondaryDark,
primary: tokens.color.primaryDark,
danger: tokens.color.danger,
danger: tokens.color.dangerDark,
warning: tokens.color.warningDark,
neutral: tokens.color.neutral,
translucent: tokens.color.darkTranslucent,
@@ -96,9 +102,10 @@ const jellifyConfig = createTamagui({
background50: tokens.color.amethyst50,
background75: tokens.color.amethyst75,
success: tokens.color.tealDark,
secondary: tokens.color.secondary,
secondary: tokens.color.secondaryDark,
primary: tokens.color.primaryDark,
danger: tokens.color.danger,
danger: tokens.color.dangerDark,
warning: tokens.color.warningDark,
neutral: tokens.color.neutral,
translucent: tokens.color.darkTranslucent,
@@ -111,9 +118,10 @@ const jellifyConfig = createTamagui({
borderColor: tokens.color.neutral,
color: tokens.color.purpleDark,
success: tokens.color.tealLight,
secondary: tokens.color.secondary,
secondary: tokens.color.secondaryLight,
primary: tokens.color.primaryLight,
danger: tokens.color.danger,
danger: tokens.color.dangerLight,
warning: tokens.color.warningLight,
neutral: tokens.color.neutral,
translucent: tokens.color.lightTranslucent,
@@ -126,9 +134,10 @@ const jellifyConfig = createTamagui({
background50: tokens.color.amethyst50,
background75: tokens.color.amethyst75,
success: tokens.color.tealLight,
secondary: tokens.color.secondary,
secondary: tokens.color.secondaryLight,
primary: tokens.color.primaryLight,
danger: tokens.color.danger,
danger: tokens.color.dangerLight,
warning: tokens.color.warningLight,
neutral: tokens.color.neutral,
translucent: tokens.color.lightTranslucent,