mirror of
https://github.com/anultravioletaurora/Jellify.git
synced 2026-01-06 05:40:26 -06:00
Merge branch 'main' of github.com:Jellify-Music/App
This commit is contained in:
33
.github/workflows/build-android.yml
vendored
33
.github/workflows/build-android.yml
vendored
@@ -1,12 +1,12 @@
|
||||
name: Build Android APK
|
||||
on:
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/build-android.yml'
|
||||
- 'android/**'
|
||||
- 'package.json'
|
||||
|
||||
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -18,12 +18,11 @@ jobs:
|
||||
- name: 🛒 Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'yarn'
|
||||
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: 💎 Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
@@ -31,8 +30,8 @@ jobs:
|
||||
bundler-cache: true
|
||||
|
||||
- name: 💬 Echo package.json version to Github ENV
|
||||
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
run: echo VERSION_NUMBER=$(bun -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: 🔑 Decode and setup release keystore
|
||||
continue-on-error: true
|
||||
run: |
|
||||
@@ -42,31 +41,31 @@ jobs:
|
||||
else
|
||||
echo "No keystore secret found, will use debug keystore"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
path: |
|
||||
node_modules
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
~/.cache/turbo
|
||||
android/.gradle
|
||||
android/app/build
|
||||
key: ${{ runner.os }}-gradle-turbo-${{ hashFiles('**/yarn.lock', '**/build.gradle') }}
|
||||
key: ${{ runner.os }}-gradle-turbo-${{ hashFiles('**/build.gradle') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-turbo-
|
||||
|
||||
- name: 🤖 Run yarn init-android
|
||||
run: yarn install --network-concurrency 1
|
||||
|
||||
- name: 🤖 Run bun init-android
|
||||
run: bun i
|
||||
|
||||
- name: 🚀 Run turbo build
|
||||
run: yarn android-build
|
||||
run: bun android-build
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
|
||||
|
||||
|
||||
- name: 📦 Upload APK for testing
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
16
.github/workflows/build-bundle.yml
vendored
16
.github/workflows/build-bundle.yml
vendored
@@ -16,20 +16,19 @@ jobs:
|
||||
- name: 🧾 Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: ⚙️ Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'yarn'
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: yarn install --network-concurrency 1
|
||||
run: bun i
|
||||
|
||||
|
||||
- name: 🧩 Build JS bundle for iOS
|
||||
run: |
|
||||
mkdir -p ios/build
|
||||
npx react-native bundle \
|
||||
bun x react-native bundle \
|
||||
--platform ios \
|
||||
--dev false \
|
||||
--entry-file index.js \
|
||||
@@ -40,12 +39,9 @@ jobs:
|
||||
run: |
|
||||
mkdir -p android/app/src/main/assets
|
||||
mkdir -p android/app/src/main/res
|
||||
npx react-native bundle \
|
||||
bun x react-native bundle \
|
||||
--platform android \
|
||||
--dev false \
|
||||
--entry-file index.js \
|
||||
--bundle-output android/app/src/main/assets/index.android.bundle \
|
||||
--assets-dest android/app/src/main/res
|
||||
|
||||
|
||||
|
||||
20
.github/workflows/build-ios.yml
vendored
20
.github/workflows/build-ios.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Build iOS IPA
|
||||
on:
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -19,21 +19,21 @@ jobs:
|
||||
- name: 🛒 Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: 20
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: 💬 Echo package.json version to Github ENV
|
||||
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
|
||||
run: echo VERSION_NUMBER=$(bun -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: 🍎 Setup Xcode
|
||||
uses: ./.github/actions/setup-xcode
|
||||
|
||||
- name: 🍎 Run yarn init-ios:new-arch
|
||||
run: yarn init-android && cd ios && bundle install && bundle exec pod install
|
||||
|
||||
|
||||
- name: 🍎 Run bun init-ios:new-arch
|
||||
run: bun run init-android && cd ios && bundle install && bundle exec pod install
|
||||
|
||||
|
||||
- name: 🚀 Run fastlane build
|
||||
run: |
|
||||
cd ios
|
||||
@@ -65,4 +65,4 @@ jobs:
|
||||
*.zip
|
||||
ios/build/Build/Products/Release-iphonesimulator/Jellify-Release-Simulator.zip
|
||||
retention-days: 7
|
||||
if-no-files-found: warn
|
||||
if-no-files-found: warn
|
||||
|
||||
55
.github/workflows/maestro-test.yml
vendored
55
.github/workflows/maestro-test.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
jobs:
|
||||
build-android:
|
||||
runs-on: macos-15
|
||||
@@ -17,19 +17,18 @@ jobs:
|
||||
steps:
|
||||
- name: 🛒 Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'yarn'
|
||||
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: 💎 Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.0'
|
||||
ruby-version: '3.0'
|
||||
bundler-cache: true
|
||||
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
@@ -39,25 +38,25 @@ jobs:
|
||||
~/.cache/turbo
|
||||
android/.gradle
|
||||
android/app/build
|
||||
key: ${{ runner.os }}-gradle-turbo-${{ hashFiles('**/yarn.lock', '**/build.gradle') }}
|
||||
key: ${{ runner.os }}-gradle-turbo-${{ hashFiles('**/bun.lock', '**/build.gradle') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-turbo-
|
||||
|
||||
- name: 🍎 Run yarn init-android
|
||||
run: yarn install --network-concurrency 1
|
||||
- name: 🍎 Run bun init-android
|
||||
run: bun i
|
||||
|
||||
- name: 💬 Disable OTA Updates and Enable Maestro Build
|
||||
run: node scripts/updateEnv.js OTA_UPDATE_ENABLED=false IS_MAESTRO_BUILD=true
|
||||
|
||||
|
||||
run: bun scripts/updateEnv.js OTA_UPDATE_ENABLED=false IS_MAESTRO_BUILD=true
|
||||
|
||||
|
||||
- name: ✅ Validate Config Files
|
||||
run: |
|
||||
node -e "JSON.parse(require('fs').readFileSync('telemetrydeck.json'))"
|
||||
node -e "JSON.parse(require('fs').readFileSync('glitchtip.json'))"
|
||||
|
||||
bun -p "JSON.parse(require('fs').readFileSync('telemetrydeck.json'))"
|
||||
bun -p "JSON.parse(require('fs').readFileSync('glitchtip.json'))"
|
||||
|
||||
- name: 🚀 Run Android fastlane build
|
||||
run: yarn android-build
|
||||
|
||||
run: bun run android-build
|
||||
|
||||
- name: 📤 Upload Android Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -74,10 +73,10 @@ jobs:
|
||||
- name: 🛒 Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: 20
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: Installing Maestro
|
||||
shell: bash
|
||||
@@ -88,7 +87,7 @@ jobs:
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'zulu'
|
||||
|
||||
|
||||
- name: ⬇️ Download Android Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -121,19 +120,19 @@ jobs:
|
||||
disable-animations: false
|
||||
avd-name: e2e_emulator
|
||||
script: bash scripts/maestro-android-retry.sh "https://jellyfin.jellify.app" "jerry"
|
||||
|
||||
|
||||
|
||||
- name: 🗣️ Notify Success on Discord
|
||||
if: success()
|
||||
run: |
|
||||
node scripts/sendDiscordMessage.js "__**## ✅ Maestro Test Passed**__All checks completed successfully!"
|
||||
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()
|
||||
run: |
|
||||
node scripts/sendDiscordMessage.js "__**## ❌ Maestro Test Failed**__Some tests did not pass."
|
||||
bun scripts/sendDiscordMessage.js "__**## ❌ Maestro Test Failed**__Some tests did not pass."
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.MAESTRO_WEBHOOK_RESULTS }}
|
||||
- name: Store tests result
|
||||
|
||||
106
.github/workflows/publish-beta.yml
vendored
106
.github/workflows/publish-beta.yml
vendored
@@ -22,7 +22,7 @@ on:
|
||||
- minor
|
||||
- patch
|
||||
- major
|
||||
|
||||
|
||||
jobs:
|
||||
generate-release-notes:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -64,10 +64,10 @@ jobs:
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
- name: 📜 Generate release notes using Node.js
|
||||
- name: 📜 Generate release notes using Bun
|
||||
run: |
|
||||
yarn install --network-concurrency 1
|
||||
node scripts/generate-release-notes.js "${{ steps.commits.outputs.messages }}"
|
||||
bun i
|
||||
bun scripts/generate-release-notes.js "${{ steps.commits.outputs.messages }}"
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
@@ -89,32 +89,32 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: 💎 Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.0'
|
||||
ruby-version: '3.0'
|
||||
bundler-cache: true
|
||||
|
||||
- name: 🍎 Run yarn init-android
|
||||
run: yarn install --network-concurrency 1
|
||||
- name: 🍎 Run bun init-android # you never actually run yarn init-android, so I kept the same
|
||||
run: bun i
|
||||
|
||||
- name: ➕ Version Up
|
||||
if: ${{ github.event.inputs['version-bump'] != 'No Bump' }}
|
||||
run: yarn react-native bump-version --type ${{ github.event.inputs['version-bump'] }}
|
||||
|
||||
- id: setver
|
||||
run: echo "version=$(node -p -e "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
run: bun x react-native bump-version --type ${{ github.event.inputs['version-bump'] }} # Is this supposed to be like npx, or some special yarn thing?
|
||||
|
||||
- id: setver
|
||||
run: echo "version=$(bun -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
|
||||
- name: 💬 Echo package.json version to Github ENV
|
||||
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
run: echo VERSION_NUMBER=$(bun -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: 🔑 Setup release keystore
|
||||
run: |
|
||||
if [ -n "${{ secrets.ANDROID_SIGNING_BASE64 }}" ]; then
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
echo "ERROR: No keystore secret found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
- name: 🔑 Setup Play Store credentials
|
||||
run: |
|
||||
if [ -n "$PLAY_STORE_CREDENTIALS" ]; then
|
||||
@@ -136,8 +136,8 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
PLAY_STORE_CREDENTIALS: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT }}
|
||||
|
||||
- name: 🤫 Output TelemetryDeck Secrets to TelemetryDeck.json
|
||||
|
||||
- name: 🤫 Output TelemetryDeck Secrets to TelemetryDeck.json
|
||||
run: |
|
||||
echo "{" > telemetrydeck.json
|
||||
echo "\"appID\": \"${{ secrets.TELEMETRYDECK_APPID }}\"," >> telemetrydeck.json
|
||||
@@ -145,29 +145,29 @@ jobs:
|
||||
echo "\"app\": \"Jellify\"" >> telemetrydeck.json
|
||||
echo "}" >> telemetrydeck.json
|
||||
|
||||
|
||||
|
||||
- name: 🤫 Output Glitchtip Secrets to Glitchtip.json
|
||||
run: |
|
||||
echo "{" > glitchtip.json
|
||||
echo "\"dsn\": \"${{ secrets.GLITCHTIP_DSN }}\"" >> glitchtip.json
|
||||
echo "}" >> glitchtip.json
|
||||
|
||||
|
||||
- name: 📝 Output Glitchip secrets to .env
|
||||
run: |
|
||||
echo "GLITCHTIP_DSN=${{ secrets.GLITCHTIP_DSN }}" >> .env
|
||||
|
||||
- name: ✅ Validate Config Files
|
||||
run: |
|
||||
node -e "JSON.parse(require('fs').readFileSync('telemetrydeck.json'))"
|
||||
node -e "JSON.parse(require('fs').readFileSync('glitchtip.json'))"
|
||||
|
||||
bun -p "JSON.parse(require('fs').readFileSync('telemetrydeck.json'))"
|
||||
bun -p "JSON.parse(require('fs').readFileSync('glitchtip.json'))"
|
||||
|
||||
- name: 🚀 Run Android fastlane deploy
|
||||
run: yarn fastlane:android:deploy
|
||||
run: bun run fastlane:android:deploy
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
|
||||
|
||||
- name: 📤 Upload Android Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -182,38 +182,38 @@ jobs:
|
||||
version: ${{ steps.setver.outputs.version }}
|
||||
needs: [generate-release-notes]
|
||||
steps:
|
||||
|
||||
|
||||
- name: 🛒 Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: 20
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: 🍎 Setup Xcode
|
||||
uses: ./.github/actions/setup-xcode
|
||||
|
||||
- name: 🍎 Run yarn init-ios:new-arch
|
||||
run: yarn init-ios:new-arch
|
||||
|
||||
- name: 🍎 Run run init-ios:new-arch
|
||||
run: bun run init-ios:new-arch
|
||||
|
||||
- name: ➕ Version Up
|
||||
if: ${{ github.event.inputs['version-bump'] != 'No Bump' }}
|
||||
run: yarn react-native bump-version --type ${{ github.event.inputs['version-bump'] }}
|
||||
run: bun x react-native bump-version --type ${{ github.event.inputs['version-bump'] }} # Is this supposed to be like npx, or some special yarn thing?
|
||||
|
||||
- id: setver
|
||||
run: echo "version=$(node -p -e "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
|
||||
run: echo "version=$(bun -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 💬 Echo package.json version to Github ENV
|
||||
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
run: echo VERSION_NUMBER=$(bun -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: 🤫 Output App Store Connect API Key JSON to Fastlane
|
||||
run: echo -e '${{ secrets.APPSTORE_CONNECT_API_KEY_JSON }}' > appstore_connect_api_key.json
|
||||
working-directory: ./ios/fastlane
|
||||
|
||||
- name: 🤫 Output TelemetryDeck Secrets to TelemetryDeck.json
|
||||
- name: 🤫 Output TelemetryDeck Secrets to TelemetryDeck.json
|
||||
run: |
|
||||
echo "{" > telemetrydeck.json
|
||||
echo "\"appID\": \"${{ secrets.TELEMETRYDECK_APPID }}\"," >> telemetrydeck.json
|
||||
@@ -221,24 +221,24 @@ jobs:
|
||||
echo "\"app\": \"Jellify\"" >> telemetrydeck.json
|
||||
echo "}" >> telemetrydeck.json
|
||||
|
||||
|
||||
|
||||
- name: 🤫 Output Glitchtip Secrets to Glitchtip.json
|
||||
run: |
|
||||
echo "{" > glitchtip.json
|
||||
echo "\"dsn\": \"${{ secrets.GLITCHTIP_DSN }}\"" >> glitchtip.json
|
||||
echo "}" >> glitchtip.json
|
||||
|
||||
|
||||
- name: 📝 Output Glitchip secrets to .env
|
||||
run: |
|
||||
echo "GLITCHTIP_DSN=${{ secrets.GLITCHTIP_DSN }}" >> .env
|
||||
|
||||
- name: ✅ Validate Config Files
|
||||
run: |
|
||||
node -e "JSON.parse(require('fs').readFileSync('telemetrydeck.json'))"
|
||||
node -e "JSON.parse(require('fs').readFileSync('glitchtip.json'))"
|
||||
|
||||
bun -p "JSON.parse(require('fs').readFileSync('telemetrydeck.json'))"
|
||||
bun -p "JSON.parse(require('fs').readFileSync('glitchtip.json'))"
|
||||
|
||||
- name: 🚀 Run iOS fastlane build and publish to TestFlight
|
||||
run: yarn fastlane:ios:beta
|
||||
run: bun run fastlane:ios:beta
|
||||
env:
|
||||
APPSTORE_CONNECT_API_KEY_JSON: ${{ secrets.APPSTORE_CONNECT_API_KEY_JSON }}
|
||||
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
|
||||
@@ -259,7 +259,7 @@ jobs:
|
||||
- name: 🛒 Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
token: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
- name: ❌ Fail if selected job failed
|
||||
run: |
|
||||
@@ -287,7 +287,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: yarn install --network-concurrency 1
|
||||
run: bun i
|
||||
|
||||
- name: ⬇️ Download Android Artifacts
|
||||
if: ${{ github.event.inputs['build-platform'] == 'Android' || github.event.inputs['build-platform'] == 'Both' }}
|
||||
@@ -305,8 +305,8 @@ jobs:
|
||||
|
||||
- name: ➕ Version Up
|
||||
if: ${{ github.event.inputs['version-bump'] != 'No Bump' }}
|
||||
run: yarn react-native bump-version --type ${{ github.event.inputs['version-bump'] }}
|
||||
|
||||
run: bun x react-native bump-version --type ${{ github.event.inputs['version-bump'] }} # Is this supposed to be like npx, or some special yarn thing?
|
||||
|
||||
- name: 🔢 Set artifact version numbers
|
||||
run: |
|
||||
VERSION=${{ needs.publish-ios.outputs.version || needs.publish-android.outputs.version }}
|
||||
@@ -355,4 +355,4 @@ jobs:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
APP_VERSION: ${{ needs.publish-ios.outputs.version || needs.publish-android.outputs.version }}
|
||||
release_url: ${{ steps.githubRelease.outputs.html_url }}
|
||||
RELEASE_NOTES: ${{ needs.generate-release-notes.outputs.release_notes }}
|
||||
RELEASE_NOTES: ${{ needs.generate-release-notes.outputs.release_notes }}
|
||||
|
||||
14
.github/workflows/publish-ota-update-pr.yml
vendored
14
.github/workflows/publish-ota-update-pr.yml
vendored
@@ -16,20 +16,20 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: 20
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: 🥟 Run bun
|
||||
run: bun i
|
||||
|
||||
- name: 🧵 Run yarn
|
||||
run: yarn install --network-concurrency 1
|
||||
|
||||
- name: 👩💻 Configure Git
|
||||
run: |
|
||||
git config --global user.email "violet@cosmonautical.cloud"
|
||||
git config --global user.name "anultravioletaurora"
|
||||
|
||||
- name: 🤖 Publish Android Update
|
||||
run: yarn sendOTA:PR ${{ github.event.pull_request.number }}
|
||||
run: bun run sendOTA:PR ${{ github.event.pull_request.number }}
|
||||
env:
|
||||
SIGNING_REPO_PAT: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
18
.github/workflows/publish-ota-update.yml
vendored
18
.github/workflows/publish-ota-update.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Publish Over-the-Air Update
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
jobs:
|
||||
publish-ota-update:
|
||||
runs-on: macos-15
|
||||
@@ -11,25 +11,25 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: 20
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: 🥟 Run bun install
|
||||
run: bun i
|
||||
|
||||
- name: 🧵 Run yarn
|
||||
run: yarn install --network-concurrency 1
|
||||
|
||||
- name: 👩💻 Configure Git
|
||||
run: |
|
||||
git config --global user.email "violet@cosmonautical.cloud"
|
||||
git config --global user.name "anultravioletaurora"
|
||||
|
||||
- name: 🤖 Publish Android Update
|
||||
run: yarn sendOTA:android
|
||||
run: bun run sendOTA:android
|
||||
env:
|
||||
SIGNING_REPO_PAT: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
- name: 🍎 Publish iOS Update
|
||||
run: yarn sendOTA:iOS
|
||||
run: bun run sendOTA:iOS
|
||||
env:
|
||||
SIGNING_REPO_PAT: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
20
.github/workflows/run-jest-test-suite.yml
vendored
20
.github/workflows/run-jest-test-suite.yml
vendored
@@ -16,22 +16,22 @@ jobs:
|
||||
- name: 🛒 Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
- name: 🖥 Setup Bun 1.3.2
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
node-version: 20
|
||||
bun-version: 1.3.2
|
||||
|
||||
- name: 💬 Echo package.json version to Github ENV
|
||||
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
|
||||
run: echo VERSION_NUMBER=$(bun -p "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: 🤖 Run yarn init-android
|
||||
run: yarn install --network-concurrency 1
|
||||
|
||||
run: bun i
|
||||
|
||||
- name: 🔍 Run yarn tsc
|
||||
run: yarn tsc
|
||||
run: bun tsc
|
||||
|
||||
- name: 🧪 Run yarn test
|
||||
run: yarn test
|
||||
run: bun test
|
||||
|
||||
- name: 🦋 Check Styling
|
||||
run: yarn format:check
|
||||
- name: 🦋 Check Styling
|
||||
run: bun run format:check
|
||||
|
||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -37,8 +37,10 @@ local.properties
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Don't think these will exist anymore!
|
||||
# npm-debug.log
|
||||
# yarn-error.log
|
||||
|
||||
# fastlane
|
||||
#
|
||||
@@ -66,12 +68,12 @@ yarn-error.log
|
||||
/coverage
|
||||
|
||||
# Yarn
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
#.yarn/*
|
||||
#!.yarn/patches
|
||||
#!.yarn/plugins
|
||||
#!.yarn/releases
|
||||
#!.yarn/sdks
|
||||
#!.yarn/versions
|
||||
|
||||
# Expo
|
||||
.expo
|
||||
@@ -80,4 +82,4 @@ web-build/
|
||||
|
||||
# Maestro Output
|
||||
video.mp4
|
||||
.github/copilot-instructions.md
|
||||
.github/copilot-instructions.md
|
||||
|
||||
@@ -1 +1 @@
|
||||
yarn lint-staged
|
||||
bun lint-staged
|
||||
|
||||
@@ -18,7 +18,7 @@ Here's the best way to get started:
|
||||
|
||||
- [Ruby](https://www.ruby-lang.org/en/documentation/installation/) for Fastlane
|
||||
- [NodeJS v22](https://nodejs.org/en/download) for React Native
|
||||
- [Yarn](https://yarnpkg.com/) for managing dependencies
|
||||
- [Bun](https://bun.sh/) for managing dependencies
|
||||
|
||||
### 🍎 iOS
|
||||
|
||||
@@ -31,21 +31,21 @@ Here's the best way to get started:
|
||||
##### Setup
|
||||
|
||||
- Clone this repository
|
||||
- Run `yarn init-ios:new-arch` to initialize the project
|
||||
- Run `bun init-ios:new-arch` to initialize the project
|
||||
- This will install `npm` packages, install `bundler` and required gems, and install required CocoaPods with [React Native's New Architecture](https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here#what-is-the-new-architecture)
|
||||
- In the `ios` directory, run `fastlane match development --readonly` to fetch the development signing certificates
|
||||
- _You will need access to the "Jellify Signing" private repository_
|
||||
|
||||
##### Running
|
||||
|
||||
- Run `yarn start` to start the dev server
|
||||
- Run `bun start` to start the dev server
|
||||
- Open the `Jellify.xcodeworkspace` with Xcode, _not_ the `Jellify.xcodeproject`
|
||||
- Run either on a device or in the simulator
|
||||
- _You will need to wait for Xcode to finish it's "Indexing" step_
|
||||
|
||||
##### Building
|
||||
|
||||
- To create a build, run `yarn fastlane:ios:build` to use fastlane to compile an `.ipa`
|
||||
- To create a build, run `bun fastlane:ios:build` to use fastlane to compile an `.ipa`
|
||||
|
||||
### 🤖 Android
|
||||
|
||||
@@ -59,18 +59,18 @@ Here's the best way to get started:
|
||||
##### Setup
|
||||
|
||||
- Clone this repository
|
||||
- Run `yarn install` to install `npm` packages
|
||||
- Run `bun install` to install `npm` packages
|
||||
|
||||
##### Running
|
||||
|
||||
- Run `yarn start` to start the dev server
|
||||
- Run `bun start` to start the dev server
|
||||
- Open the `android` folder with Android Studio
|
||||
- _Android Studio should automatically grab the "Run Configurations" and initialize Gradle_
|
||||
- Run either on a device or in the simulator
|
||||
|
||||
##### Building
|
||||
|
||||
- To create a build, run `yarn fastlane:android:build` to use fastlane to compile an `.apk` for all architectures
|
||||
- To create a build, run `bun fastlane:android:build` to use fastlane to compile an `.apk` for all architectures
|
||||
- Alternatively, run `cd android; ./gradlew assembleRelease` to use Gradle to compile an `.apk`
|
||||
|
||||
#### References
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||
<module name="Jellify.app" />
|
||||
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
||||
<option name="DEPLOY" value="true" />
|
||||
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||
|
||||
@@ -91,8 +91,8 @@ android {
|
||||
applicationId "com.cosmonautical.jellify"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 150
|
||||
versionName "0.20.13"
|
||||
versionCode 151
|
||||
versionName "0.20.14"
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
|
||||
@@ -543,7 +543,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -554,7 +554,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.20.13;
|
||||
MARKETING_VERSION = 0.20.14;
|
||||
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 = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEVELOPMENT_TEAM = WAH9CZ8BPG;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
@@ -595,7 +595,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.20.13;
|
||||
MARKETING_VERSION = 0.20.14;
|
||||
NEW_SETTING = "";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -821,7 +821,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 260;
|
||||
CURRENT_PROJECT_VERSION = 261;
|
||||
DEVELOPMENT_TEAM = WAH9CZ8BPG;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
@@ -832,7 +832,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.20.13;
|
||||
MARKETING_VERSION = 0.20.14;
|
||||
NEW_SETTING = "";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
|
||||
@@ -2720,7 +2720,7 @@ PODS:
|
||||
- React
|
||||
- RNFS (2.20.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.28.0):
|
||||
- RNGestureHandler (2.29.1):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -3471,7 +3471,7 @@ SPEC CHECKSUMS:
|
||||
RNDeviceInfo: 36d7f232bfe7c9b5c494cb7793230424ed32c388
|
||||
RNDnsLookup: db4a89381b80ec1a5153088518d2c4f8e51f2521
|
||||
RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8
|
||||
RNGestureHandler: f1dd7f92a0faa2868a919ab53bb9d66eb4ebfcf5
|
||||
RNGestureHandler: e1cf8ef3f11045536eed6bd4f132b003ef5f9a5f
|
||||
RNReactNativeHapticFeedback: be4f1b4bf0398c30b59b76ed92ecb0a2ff3a69c6
|
||||
RNReanimated: ac06da53579693ab451941ef89f5a55afeab0dd9
|
||||
RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479
|
||||
|
||||
28
package.json
28
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jellify",
|
||||
"version": "0.20.13",
|
||||
"version": "0.20.14",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"init-android": "yarn install --network-concurrency 1",
|
||||
"init-ios": "yarn init-ios:new-arch",
|
||||
"init-ios:new-arch": "yarn install --network-concurrency 1 && yarn pod:install:new-arch",
|
||||
"reinstall": "rm -rf ./node_modules && yarn install",
|
||||
"init-android": "bun i",
|
||||
"init-ios": "bun run init-ios:new-arch",
|
||||
"init-ios:new-arch": "bun i && bun run pod:install:new-arch",
|
||||
"reinstall": "rm -rf ./node_modules && bun i",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"lint": "eslint .",
|
||||
@@ -16,7 +16,7 @@
|
||||
"codegen": "env DEBUG=metro:* react-native codegen",
|
||||
"clean:ios": "cd ios && pod deintegrate",
|
||||
"clean:android": "cd android && rm -rf app/ build/",
|
||||
"pod:install": "echo 'Please run `yarn pod:install:new-arch` to enable the new architecture'",
|
||||
"pod:install": "echo 'Please run `bun run pod:install:new-arch` to enable the new architecture'",
|
||||
"pod:install:new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
|
||||
"pod:clean": "cd ios && pod deintegrate",
|
||||
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
|
||||
@@ -46,7 +46,7 @@
|
||||
"@react-navigation/bottom-tabs": "7.8.6",
|
||||
"@react-navigation/material-top-tabs": "7.4.4",
|
||||
"@react-navigation/native": "7.1.21",
|
||||
"@react-navigation/native-stack": "7.6.4",
|
||||
"@react-navigation/native-stack": "7.7.0",
|
||||
"@sentry/react-native": "7.6.0",
|
||||
"@shopify/flash-list": "2.2.0",
|
||||
"@tamagui/config": "1.137.1",
|
||||
@@ -58,7 +58,6 @@
|
||||
"axios": "1.12.2",
|
||||
"bundle": "^2.1.0",
|
||||
"dlx": "^0.2.1",
|
||||
"gem": "^2.4.3",
|
||||
"invert-color": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"openai": "5.21.0",
|
||||
@@ -71,10 +70,8 @@
|
||||
"react-native-config": "1.5.6",
|
||||
"react-native-device-info": "15.0.1",
|
||||
"react-native-dns-lookup": "^1.0.6",
|
||||
"react-native-draggable-flatlist": "^4.0.3",
|
||||
"react-native-flashdrag-list": "^0.2.5",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "^2.28.0",
|
||||
"react-native-gesture-handler": "2.29.1",
|
||||
"react-native-google-cast": "^4.9.1",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-linear-gradient": "^2.8.3",
|
||||
@@ -86,7 +83,6 @@
|
||||
"react-native-safe-area-context": "5.6.2",
|
||||
"react-native-screens": "4.18.0",
|
||||
"react-native-sortables": "^1.9.3",
|
||||
"react-native-swipeable-item": "^2.0.9",
|
||||
"react-native-text-ticker": "^1.15.0",
|
||||
"react-native-toast-message": "^2.3.3",
|
||||
"react-native-track-player": "5.0.0-alpha0",
|
||||
@@ -142,7 +138,13 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"bun": ">=1.3.2",
|
||||
"node": ">=18"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
"packageManager": "bun@1.3.2",
|
||||
"trustedDependencies": [
|
||||
"@sentry/cli",
|
||||
"react-native-nitro-modules",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ else
|
||||
git checkout -b "$target_branch"
|
||||
fi
|
||||
cd ../..
|
||||
yarn createBundle:android
|
||||
bun createBundle:android
|
||||
cd android/App-Bundles
|
||||
bash ../../scripts/getRandomVersion.sh --PR
|
||||
git add .
|
||||
@@ -42,7 +42,7 @@ else
|
||||
fi
|
||||
rm -rf Readme.md
|
||||
cd ../..
|
||||
yarn createBundle:ios
|
||||
bun createBundle:ios
|
||||
cd ios/App-Bundles
|
||||
bash ../../scripts/getRandomVersion.sh --PR
|
||||
git add .
|
||||
|
||||
@@ -11,7 +11,7 @@ else
|
||||
git checkout -b "$target_branch"
|
||||
fi
|
||||
cd ../..
|
||||
yarn createBundle:android
|
||||
bun createBundle:android
|
||||
cd android/App-Bundles
|
||||
bash ../../scripts/getRandomVersion.sh
|
||||
git add .
|
||||
|
||||
@@ -14,7 +14,7 @@ else
|
||||
fi
|
||||
rm -rf Readme.md
|
||||
cd ../..
|
||||
yarn createBundle:ios
|
||||
bun createBundle:ios
|
||||
cd ios/App-Bundles
|
||||
bash ../../scripts/getRandomVersion.sh
|
||||
git add .
|
||||
|
||||
@@ -232,6 +232,7 @@ export async function updatePlaylist(
|
||||
name: string,
|
||||
trackIds: string[],
|
||||
) {
|
||||
console.info('Updating playlist with name:', name, 'and track IDs:', trackIds)
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (isUndefined(api)) return reject(new Error('No API client available'))
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export type QuickAction = {
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
onPress?: () => void | null
|
||||
onPress?: () => Promise<void> | null
|
||||
onLongPress?: () => void | null
|
||||
leftAction?: SwipeAction | null // immediate action on right swipe
|
||||
leftActions?: QuickAction[] | null // quick action menu on right swipe
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import DraggableFlatList from 'react-native-draggable-flatlist'
|
||||
|
||||
const AnimatedDraggableFlatList = Animated.createAnimatedComponent(DraggableFlatList<BaseItemDto>)
|
||||
|
||||
export default AnimatedDraggableFlatList
|
||||
@@ -80,8 +80,8 @@ export default function ItemRow({
|
||||
[navigationRef, navigation, item],
|
||||
)
|
||||
|
||||
const onPressCallback = useCallback(() => {
|
||||
if (onPress) onPress()
|
||||
const onPressCallback = useCallback(async () => {
|
||||
if (onPress) await onPress()
|
||||
else
|
||||
switch (item.Type) {
|
||||
case 'Audio': {
|
||||
|
||||
@@ -42,9 +42,8 @@ export interface TrackProps {
|
||||
isNested?: boolean | undefined
|
||||
invertedColors?: boolean | undefined
|
||||
prependElement?: React.JSX.Element | undefined
|
||||
showRemove?: boolean | undefined
|
||||
onRemove?: () => void | undefined
|
||||
testID?: string | undefined
|
||||
editing?: boolean | undefined
|
||||
}
|
||||
|
||||
export default function Track({
|
||||
@@ -60,8 +59,7 @@ export default function Track({
|
||||
isNested,
|
||||
invertedColors,
|
||||
prependElement,
|
||||
showRemove,
|
||||
onRemove,
|
||||
editing,
|
||||
}: TrackProps): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
const [artworkAreaWidth, setArtworkAreaWidth] = useState(0)
|
||||
@@ -106,9 +104,9 @@ export default function Track({
|
||||
)
|
||||
|
||||
// Memoize handlers to prevent recreation
|
||||
const handlePress = useCallback(() => {
|
||||
const handlePress = useCallback(async () => {
|
||||
if (onPress) {
|
||||
onPress()
|
||||
await onPress()
|
||||
} else {
|
||||
loadNewQueue({
|
||||
api,
|
||||
@@ -140,19 +138,15 @@ export default function Track({
|
||||
}, [onLongPress, track, isNested, mediaInfo?.MediaSources, offlineAudio])
|
||||
|
||||
const handleIconPress = useCallback(() => {
|
||||
if (showRemove) {
|
||||
if (onRemove) onRemove()
|
||||
} else {
|
||||
navigationRef.navigate('Context', {
|
||||
item: track,
|
||||
navigation,
|
||||
streamingMediaSourceInfo: mediaInfo?.MediaSources
|
||||
? mediaInfo!.MediaSources![0]
|
||||
: undefined,
|
||||
downloadedMediaSourceInfo: offlineAudio?.mediaSourceInfo,
|
||||
})
|
||||
}
|
||||
}, [showRemove, onRemove, track, isNested, mediaInfo?.MediaSources, offlineAudio])
|
||||
navigationRef.navigate('Context', {
|
||||
item: track,
|
||||
navigation,
|
||||
streamingMediaSourceInfo: mediaInfo?.MediaSources
|
||||
? mediaInfo!.MediaSources![0]
|
||||
: undefined,
|
||||
downloadedMediaSourceInfo: offlineAudio?.mediaSourceInfo,
|
||||
})
|
||||
}, [track, isNested, mediaInfo?.MediaSources, offlineAudio])
|
||||
|
||||
// Memoize text color to prevent recalculation
|
||||
const textColor = useMemo(() => {
|
||||
@@ -317,10 +311,7 @@ export default function Track({
|
||||
<DownloadedIcon item={track} />
|
||||
<FavoriteIcon item={track} />
|
||||
{runtimeComponent}
|
||||
<Icon
|
||||
name={showRemove ? 'close' : 'dots-horizontal'}
|
||||
onPress={handleIconPress}
|
||||
/>
|
||||
{!editing && <Icon name={'dots-horizontal'} onPress={handleIconPress} />}
|
||||
</XStack>
|
||||
</XStack>
|
||||
</SwipeableRow>
|
||||
|
||||
@@ -63,20 +63,23 @@ export default function Queue({
|
||||
<Icon name='drag' />
|
||||
</Sortable.Handle>
|
||||
|
||||
<Track
|
||||
queue={queueRef ?? 'Recently Played'}
|
||||
track={queueItem.item}
|
||||
index={index ?? 0}
|
||||
showArtwork
|
||||
testID={`queue-item-${index}`}
|
||||
onPress={() => skip(index)}
|
||||
isNested
|
||||
showRemove
|
||||
onRemove={() => removeFromQueue(index)}
|
||||
/>
|
||||
<Sortable.Touchable onTap={() => skip(index)}>
|
||||
<Track
|
||||
queue={queueRef ?? 'Recently Played'}
|
||||
track={queueItem.item}
|
||||
index={index}
|
||||
showArtwork
|
||||
testID={`queue-item-${index}`}
|
||||
isNested
|
||||
/>
|
||||
</Sortable.Touchable>
|
||||
|
||||
<Sortable.Touchable onTap={() => removeFromQueue(index)}>
|
||||
<Icon name='close' />
|
||||
</Sortable.Touchable>
|
||||
</XStack>
|
||||
),
|
||||
[queueRef, navigation, useSkip, useRemoveFromQueue],
|
||||
[queueRef, skip, removeFromQueue],
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { getTokens, Separator, View, XStack, YStack } from 'tamagui'
|
||||
import { AnimatedH5 } from '../../Global/helpers/text'
|
||||
import { H5, XStack, YStack } from 'tamagui'
|
||||
import InstantMixButton from '../../Global/components/instant-mix-button'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { usePlaylistContext } from '../../../providers/Playlist'
|
||||
import Animated, { useAnimatedStyle, withSpring } from 'react-native-reanimated'
|
||||
import { useNetworkStatus } from '../../../../src/stores/network'
|
||||
import { useNetworkContext } from '../../../../src/providers/Network'
|
||||
import { ActivityIndicator } from 'react-native'
|
||||
@@ -20,98 +17,54 @@ import useStreamingDeviceProfile, {
|
||||
} from '../../../stores/device-profile'
|
||||
import ItemImage from '../../Global/components/image'
|
||||
import { useApi } from '../../../stores'
|
||||
import Input from '../../Global/helpers/input'
|
||||
|
||||
export default function PlayliistTracklistHeader(
|
||||
playlist: BaseItemDto,
|
||||
editing: boolean,
|
||||
playlistTracks: BaseItemDto[],
|
||||
canEdit: boolean | undefined,
|
||||
): React.JSX.Element {
|
||||
const { width } = useSafeAreaFrame()
|
||||
|
||||
const { setEditing, scroll } = usePlaylistContext()
|
||||
|
||||
const artworkSize = 200
|
||||
|
||||
const textSize = getTokens().size['$12'].val
|
||||
|
||||
const animatedArtworkStyle = useAnimatedStyle(() => {
|
||||
'worklet'
|
||||
return {
|
||||
height: withSpring(Math.max(0, Math.min(artworkSize, artworkSize - scroll.value * 2)), {
|
||||
stiffness: 100,
|
||||
damping: 25,
|
||||
}),
|
||||
width: withSpring(Math.max(0, Math.min(artworkSize, artworkSize - scroll.value * 2)), {
|
||||
stiffness: 100,
|
||||
damping: 25,
|
||||
}),
|
||||
display: scroll.value * 3 > artworkSize ? 'none' : 'flex',
|
||||
}
|
||||
})
|
||||
|
||||
const animatedNameStyle = useAnimatedStyle(() => {
|
||||
'worklet'
|
||||
|
||||
const clampedWidth = Math.max(
|
||||
// Prevent the name from getting too small
|
||||
width / 2.5,
|
||||
Math.min(
|
||||
// Prevent the name from getting too large
|
||||
width / 1.1,
|
||||
width / 2.25 + scroll.value * 2,
|
||||
),
|
||||
)
|
||||
|
||||
return {
|
||||
width: withSpring(clampedWidth, {
|
||||
stiffness: 100,
|
||||
damping: 25,
|
||||
}),
|
||||
height: withSpring(Math.max(textSize, artworkSize - scroll.value), {
|
||||
stiffness: 100,
|
||||
damping: 25,
|
||||
}),
|
||||
alignContent: 'center',
|
||||
justifyContent: 'center',
|
||||
}
|
||||
})
|
||||
export default function PlaylistTracklistHeader({
|
||||
canEdit,
|
||||
}: {
|
||||
canEdit?: boolean
|
||||
}): React.JSX.Element {
|
||||
const { playlist, playlistTracks, editing, setEditing, newName, setNewName } =
|
||||
usePlaylistContext()
|
||||
|
||||
return (
|
||||
<View backgroundColor={'$background'} borderRadius={'$2'}>
|
||||
<XStack
|
||||
justifyContent='flex-start'
|
||||
alignItems='flex-start'
|
||||
paddingTop={'$1'}
|
||||
marginBottom={'$2'}
|
||||
>
|
||||
<YStack justifyContent='center' alignContent='center' padding={'$2'}>
|
||||
<Animated.View style={[animatedArtworkStyle]}>
|
||||
<ItemImage item={playlist} />
|
||||
</Animated.View>
|
||||
</YStack>
|
||||
<YStack justifyContent='center' alignItems='center' paddingTop={'$1'} marginBottom={'$2'}>
|
||||
<YStack justifyContent='center' alignContent='center' padding={'$2'}>
|
||||
<ItemImage item={playlist} width={'$20'} height={'$20'} />
|
||||
</YStack>
|
||||
|
||||
<Animated.View style={[animatedNameStyle, { flex: 1 }]}>
|
||||
<AnimatedH5
|
||||
lineBreakStrategyIOS='standard'
|
||||
textAlign='center'
|
||||
numberOfLines={5}
|
||||
marginBottom={'$2'}
|
||||
>
|
||||
{playlist.Name ?? 'Untitled Playlist'}
|
||||
</AnimatedH5>
|
||||
{editing ? (
|
||||
<Input
|
||||
value={newName}
|
||||
onChangeText={setNewName}
|
||||
placeholder='Playlist Name'
|
||||
textAlign='center'
|
||||
fontSize={18}
|
||||
fontWeight='bold'
|
||||
clearButtonMode='while-editing'
|
||||
marginHorizontal={'$4'}
|
||||
/>
|
||||
) : (
|
||||
<H5
|
||||
lineBreakStrategyIOS='standard'
|
||||
textAlign='center'
|
||||
numberOfLines={5}
|
||||
marginBottom={'$2'}
|
||||
>
|
||||
{newName ?? 'Untitled Playlist'}
|
||||
</H5>
|
||||
)}
|
||||
|
||||
<PlaylistHeaderControls
|
||||
editing={editing}
|
||||
setEditing={setEditing}
|
||||
playlist={playlist}
|
||||
playlistTracks={playlistTracks}
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
</Animated.View>
|
||||
</XStack>
|
||||
<Separator />
|
||||
</View>
|
||||
{!editing && (
|
||||
<PlaylistHeaderControls
|
||||
editing={editing}
|
||||
setEditing={setEditing}
|
||||
playlist={playlist}
|
||||
playlistTracks={playlistTracks ?? []}
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
)}
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,192 @@
|
||||
import { Separator, useTheme, XStack } from 'tamagui'
|
||||
import { ScrollView, Spinner, useTheme, XStack, YStack } from 'tamagui'
|
||||
import Track from '../Global/components/track'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { RefreshControl } from 'react-native'
|
||||
import { PlaylistProps } from './interfaces'
|
||||
import PlayliistTracklistHeader from './components/header'
|
||||
import { usePlaylistContext } from '../../providers/Playlist'
|
||||
import { runOnJS, useAnimatedScrollHandler } from 'react-native-reanimated'
|
||||
import AnimatedDraggableFlatList from '../Global/components/animated-draggable-flat-list'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackActions, useNavigation } from '@react-navigation/native'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import useHapticFeedback from '../../hooks/use-haptic-feedback'
|
||||
import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry'
|
||||
import Sortable from 'react-native-sortables'
|
||||
import { useCallback, useLayoutEffect } from 'react'
|
||||
import { useReducedHapticsSetting } from '../../stores/settings/app'
|
||||
import { RenderItemInfo } from 'react-native-sortables/dist/typescript/types'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'
|
||||
import PlaylistTracklistHeader from './components/header'
|
||||
import navigationRef from '../../../navigation'
|
||||
import { useLoadNewQueue } from '../../providers/Player/hooks/mutations'
|
||||
import { useNetworkStatus } from '../../stores/network'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
import { useApi } from '../../stores'
|
||||
import useStreamingDeviceProfile from '../../stores/device-profile'
|
||||
import { RefreshControl } from 'react-native-gesture-handler'
|
||||
|
||||
export default function Playlist({
|
||||
playlist,
|
||||
navigation,
|
||||
canEdit,
|
||||
}: PlaylistProps): React.JSX.Element {
|
||||
const {
|
||||
scroll,
|
||||
playlistTracks,
|
||||
isPending,
|
||||
editing,
|
||||
refetch,
|
||||
setPlaylistTracks,
|
||||
useUpdatePlaylist,
|
||||
useRemoveFromPlaylist,
|
||||
} = usePlaylistContext()
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
const api = useApi()
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
const {
|
||||
playlistTracks,
|
||||
isPending,
|
||||
refetch,
|
||||
editing,
|
||||
setEditing,
|
||||
isUpdating,
|
||||
newName,
|
||||
setPlaylistTracks,
|
||||
useUpdatePlaylist,
|
||||
handleCancel,
|
||||
} = usePlaylistContext()
|
||||
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerRight: () =>
|
||||
canEdit && (
|
||||
<XStack gap={'$3'}>
|
||||
{editing && (
|
||||
<>
|
||||
<Icon
|
||||
color={'$danger'}
|
||||
name='delete-sweep-outline' // otherwise use "delete-circle"
|
||||
onPress={() => {
|
||||
navigationRef.dispatch(
|
||||
StackActions.push('DeletePlaylist', { playlist }),
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Icon
|
||||
color='$neutral'
|
||||
name='close-circle-outline'
|
||||
onPress={handleCancel}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isUpdating ? (
|
||||
<Icon
|
||||
name={editing ? 'floppy' : 'pencil'}
|
||||
color={editing ? '$success' : '$color'}
|
||||
onPress={() =>
|
||||
!editing
|
||||
? setEditing(true)
|
||||
: useUpdatePlaylist({
|
||||
playlist,
|
||||
tracks: playlistTracks ?? [],
|
||||
newName,
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Spinner color={'$success'} />
|
||||
)}
|
||||
</XStack>
|
||||
),
|
||||
})
|
||||
}, [
|
||||
editing,
|
||||
navigation,
|
||||
canEdit,
|
||||
playlist,
|
||||
handleCancel,
|
||||
isUpdating,
|
||||
useUpdatePlaylist,
|
||||
playlistTracks,
|
||||
newName,
|
||||
setEditing,
|
||||
])
|
||||
|
||||
const [reducedHaptics] = useReducedHapticsSetting()
|
||||
|
||||
const streamingDeviceProfile = useStreamingDeviceProfile()
|
||||
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const scrollOffsetHandler = useAnimatedScrollHandler({
|
||||
onBeginDrag: () => {
|
||||
'worklet'
|
||||
runOnJS(closeAllSwipeableRows)()
|
||||
const renderItem = useCallback(
|
||||
({ item: track, index }: RenderItemInfo<BaseItemDto>) => {
|
||||
const handlePress = async () => {
|
||||
await loadNewQueue({
|
||||
track,
|
||||
tracklist: playlistTracks ?? [],
|
||||
api,
|
||||
networkStatus,
|
||||
deviceProfile: streamingDeviceProfile,
|
||||
index,
|
||||
queue: playlist,
|
||||
queuingType: QueuingType.FromSelection,
|
||||
startPlayback: true,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<XStack alignItems='center' key={`${index}-${track.Id}`} flex={1}>
|
||||
{editing && (
|
||||
<Sortable.Handle>
|
||||
<Icon name='drag' />
|
||||
</Sortable.Handle>
|
||||
)}
|
||||
|
||||
<Sortable.Touchable
|
||||
style={{ flexGrow: 1 }}
|
||||
onTap={handlePress}
|
||||
onLongPress={() => {
|
||||
if (!editing)
|
||||
rootNavigation.navigate('Context', {
|
||||
item: track,
|
||||
navigation,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Track
|
||||
navigation={navigation}
|
||||
track={track}
|
||||
tracklist={playlistTracks ?? []}
|
||||
index={index}
|
||||
queue={playlist}
|
||||
showArtwork
|
||||
editing={editing}
|
||||
/>
|
||||
</Sortable.Touchable>
|
||||
|
||||
{editing && (
|
||||
<Sortable.Touchable
|
||||
onTap={() => {
|
||||
setPlaylistTracks(
|
||||
(playlistTracks ?? []).filter(({ Id }) => Id !== track.Id),
|
||||
)
|
||||
}}
|
||||
>
|
||||
<Icon name='close' color={'$danger'} />
|
||||
</Sortable.Touchable>
|
||||
)}
|
||||
</XStack>
|
||||
)
|
||||
},
|
||||
onScroll: (event) => {
|
||||
'worklet'
|
||||
scroll.value = event.contentOffset.y
|
||||
},
|
||||
})
|
||||
[
|
||||
navigation,
|
||||
playlist,
|
||||
playlistTracks,
|
||||
editing,
|
||||
setPlaylistTracks,
|
||||
loadNewQueue,
|
||||
api,
|
||||
networkStatus,
|
||||
streamingDeviceProfile,
|
||||
rootNavigation,
|
||||
],
|
||||
)
|
||||
|
||||
return (
|
||||
<AnimatedDraggableFlatList
|
||||
<ScrollView
|
||||
flex={1}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isPending}
|
||||
@@ -55,67 +194,23 @@ export default function Playlist({
|
||||
tintColor={theme.primary.val}
|
||||
/>
|
||||
}
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
data={playlistTracks ?? []}
|
||||
dragHitSlop={{ left: -50 }} // https://github.com/computerjazz/react-native-draggable-flatlist/issues/336
|
||||
keyExtractor={(item, index) => {
|
||||
return `${index}-${item.Id}`
|
||||
}}
|
||||
ItemSeparatorComponent={() => <Separator />}
|
||||
ListHeaderComponent={() =>
|
||||
PlayliistTracklistHeader(playlist, editing, playlistTracks ?? [], canEdit)
|
||||
}
|
||||
stickyHeaderIndices={[0]}
|
||||
numColumns={1}
|
||||
onDragBegin={() => {
|
||||
trigger('impactMedium')
|
||||
}}
|
||||
onDragEnd={({ data, from, to }) => {
|
||||
useUpdatePlaylist.mutate(
|
||||
{
|
||||
playlist,
|
||||
tracks: data,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setPlaylistTracks(data)
|
||||
},
|
||||
},
|
||||
)
|
||||
}}
|
||||
refreshing={isPending}
|
||||
renderItem={({ item: track, getIndex, drag }) => (
|
||||
<Track
|
||||
navigation={navigation}
|
||||
track={track}
|
||||
tracklist={playlistTracks ?? []}
|
||||
index={getIndex() ?? 0}
|
||||
queue={playlist}
|
||||
showArtwork
|
||||
onLongPress={() => {
|
||||
if (editing) {
|
||||
drag()
|
||||
} else {
|
||||
rootNavigation.navigate('Context', {
|
||||
item: track,
|
||||
navigation,
|
||||
})
|
||||
}
|
||||
}}
|
||||
showRemove={editing}
|
||||
onRemove={() =>
|
||||
useRemoveFromPlaylist.mutate({ playlist, track, index: getIndex()! })
|
||||
}
|
||||
prependElement={
|
||||
editing && canEdit ? <Icon name='drag' onPress={drag} /> : undefined
|
||||
}
|
||||
isNested={editing}
|
||||
/>
|
||||
)}
|
||||
style={{
|
||||
marginHorizontal: 2,
|
||||
}}
|
||||
onScroll={scrollOffsetHandler}
|
||||
/>
|
||||
>
|
||||
<PlaylistTracklistHeader />
|
||||
|
||||
<Sortable.Grid
|
||||
data={playlistTracks ?? []}
|
||||
keyExtractor={(item) => {
|
||||
return `${item.Id}`
|
||||
}}
|
||||
autoScrollEnabled
|
||||
columns={1}
|
||||
customHandle
|
||||
overDrag='vertical'
|
||||
sortEnabled={canEdit && editing}
|
||||
onDragEnd={({ data }) => setPlaylistTracks(data)}
|
||||
renderItem={renderItem}
|
||||
hapticsEnabled={!reducedHaptics}
|
||||
/>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ export default function Search({
|
||||
value={searchString}
|
||||
marginHorizontal={'$2'}
|
||||
testID='search-input'
|
||||
clearButtonMode='while-editing'
|
||||
/>
|
||||
|
||||
{!isEmpty(items) && (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useMutation, UseMutationResult } from '@tanstack/react-query'
|
||||
import { UseMutateFunction, useMutation } from '@tanstack/react-query'
|
||||
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
||||
import { removeFromPlaylist, updatePlaylist } from '../../api/mutations/playlists'
|
||||
import { RemoveFromPlaylistMutation } from '../../components/Playlist/interfaces'
|
||||
import { updatePlaylist } from '../../api/mutations/playlists'
|
||||
import { SharedValue, useSharedValue } from 'react-native-reanimated'
|
||||
import useHapticFeedback from '../../hooks/use-haptic-feedback'
|
||||
import { useApi } from '../../stores'
|
||||
@@ -15,18 +14,21 @@ interface PlaylistContext {
|
||||
isPending: boolean
|
||||
editing: boolean
|
||||
setEditing: (editing: boolean) => void
|
||||
newName: string
|
||||
setNewName: (name: string) => void
|
||||
setPlaylistTracks: (tracks: BaseItemDto[]) => void
|
||||
useUpdatePlaylist: UseMutationResult<
|
||||
useUpdatePlaylist: UseMutateFunction<
|
||||
void,
|
||||
Error,
|
||||
{ playlist: BaseItemDto; tracks: BaseItemDto[] }
|
||||
{
|
||||
playlist: BaseItemDto
|
||||
tracks: BaseItemDto[]
|
||||
newName: string
|
||||
},
|
||||
unknown
|
||||
>
|
||||
useRemoveFromPlaylist: UseMutationResult<
|
||||
void,
|
||||
Error,
|
||||
{ playlist: BaseItemDto; track: BaseItemDto; index: number }
|
||||
>
|
||||
scroll: SharedValue<number>
|
||||
isUpdating?: boolean
|
||||
handleCancel: () => void
|
||||
}
|
||||
|
||||
const PlaylistContextInitializer = (playlist: BaseItemDto) => {
|
||||
@@ -35,20 +37,28 @@ const PlaylistContextInitializer = (playlist: BaseItemDto) => {
|
||||
const canEdit = playlist.CanDelete
|
||||
const [editing, setEditing] = useState<boolean>(false)
|
||||
|
||||
const [playlistTracks, setPlaylistTracks] = useState<BaseItemDto[] | undefined>(undefined)
|
||||
const [newName, setNewName] = useState<string>(playlist.Name ?? '')
|
||||
|
||||
const scroll = useSharedValue(0)
|
||||
const [playlistTracks, setPlaylistTracks] = useState<BaseItemDto[] | undefined>(undefined)
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
const { data: tracks, isPending, refetch, isSuccess } = usePlaylistTracks(playlist)
|
||||
|
||||
const useUpdatePlaylist = useMutation({
|
||||
mutationFn: ({ playlist, tracks }: { playlist: BaseItemDto; tracks: BaseItemDto[] }) => {
|
||||
const { mutate: useUpdatePlaylist, isPending: isUpdating } = useMutation({
|
||||
mutationFn: ({
|
||||
playlist,
|
||||
tracks,
|
||||
newName,
|
||||
}: {
|
||||
playlist: BaseItemDto
|
||||
tracks: BaseItemDto[]
|
||||
newName: string
|
||||
}) => {
|
||||
return updatePlaylist(
|
||||
api,
|
||||
playlist.Id!,
|
||||
playlist.Name!,
|
||||
newName,
|
||||
tracks.map((track) => track.Id!),
|
||||
)
|
||||
},
|
||||
@@ -60,31 +70,20 @@ const PlaylistContextInitializer = (playlist: BaseItemDto) => {
|
||||
},
|
||||
onError: () => {
|
||||
trigger('notificationError')
|
||||
|
||||
setNewName(playlist.Name ?? '')
|
||||
setPlaylistTracks(tracks ?? [])
|
||||
},
|
||||
})
|
||||
|
||||
const useRemoveFromPlaylist = useMutation({
|
||||
mutationFn: ({ playlist, track, index }: RemoveFromPlaylistMutation) => {
|
||||
return removeFromPlaylist(api, track, playlist)
|
||||
},
|
||||
onSuccess: (data, { index }) => {
|
||||
trigger('notificationSuccess')
|
||||
|
||||
if (playlistTracks) {
|
||||
setPlaylistTracks(
|
||||
playlistTracks
|
||||
.slice(0, index)
|
||||
.concat(playlistTracks.slice(index + 1, playlistTracks.length - 1)),
|
||||
)
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
trigger('notificationError')
|
||||
onSettled: () => {
|
||||
setEditing(false)
|
||||
},
|
||||
})
|
||||
|
||||
const handleCancel = () => {
|
||||
setEditing(false)
|
||||
setNewName(playlist.Name ?? '')
|
||||
setPlaylistTracks(tracks)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPending && isSuccess) setPlaylistTracks(tracks)
|
||||
}, [tracks, isPending, isSuccess])
|
||||
@@ -100,10 +99,12 @@ const PlaylistContextInitializer = (playlist: BaseItemDto) => {
|
||||
isPending,
|
||||
editing,
|
||||
setEditing,
|
||||
newName,
|
||||
setNewName,
|
||||
setPlaylistTracks,
|
||||
useUpdatePlaylist,
|
||||
useRemoveFromPlaylist,
|
||||
scroll,
|
||||
handleCancel,
|
||||
isUpdating,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,44 +115,12 @@ const PlaylistContext = createContext<PlaylistContext>({
|
||||
isPending: false,
|
||||
editing: false,
|
||||
setEditing: () => {},
|
||||
newName: '',
|
||||
setNewName: () => {},
|
||||
setPlaylistTracks: () => {},
|
||||
useUpdatePlaylist: {
|
||||
mutate: () => {},
|
||||
mutateAsync: async (variables) => {},
|
||||
data: undefined,
|
||||
error: null,
|
||||
variables: undefined,
|
||||
isError: false,
|
||||
isIdle: true,
|
||||
isPaused: false,
|
||||
isPending: false,
|
||||
isSuccess: false,
|
||||
status: 'idle',
|
||||
reset: () => {},
|
||||
context: {},
|
||||
failureCount: 0,
|
||||
failureReason: null,
|
||||
submittedAt: 0,
|
||||
},
|
||||
useRemoveFromPlaylist: {
|
||||
mutate: () => {},
|
||||
mutateAsync: async (variables) => {},
|
||||
data: undefined,
|
||||
error: null,
|
||||
variables: undefined,
|
||||
isError: false,
|
||||
isIdle: true,
|
||||
isPaused: false,
|
||||
isPending: false,
|
||||
isSuccess: false,
|
||||
status: 'idle',
|
||||
reset: () => {},
|
||||
context: {},
|
||||
failureCount: 0,
|
||||
failureReason: null,
|
||||
submittedAt: 0,
|
||||
},
|
||||
scroll: { value: 0 } as SharedValue<number>,
|
||||
useUpdatePlaylist: () => {},
|
||||
handleCancel: () => {},
|
||||
isUpdating: false,
|
||||
})
|
||||
|
||||
export const PlaylistProvider = ({
|
||||
|
||||
Reference in New Issue
Block a user