mirror of
https://github.com/Jellify-Music/App.git
synced 2026-04-20 16:50:33 -05:00
Merge branch 'main' into dependabot/npm_and_yarn/openai-6.15.0
This commit is contained in:
@@ -43,7 +43,7 @@ jobs:
|
||||
fi
|
||||
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
|
||||
- name: 📦 Upload APK for testing
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: jellify-android-pr-${{ github.event.number }}-${{ env.VERSION_NUMBER }}
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
zip -r Jellify-Release-Simulator.zip Jellify.app
|
||||
|
||||
- name: 📦 Upload IPA for testing
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: jellify-ios-pr-${{ github.event.number }}-${{ env.VERSION_NUMBER }}
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
bun-version: 1.3.4
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
java-version: '17'
|
||||
|
||||
- name: 🐘 Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
|
||||
- name: 🍎 Run bun init-android
|
||||
run: bun i
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
run: bun run android-build
|
||||
|
||||
- name: 📤 Upload Android Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: android-artifacts
|
||||
path: ./android/app/build/outputs/apk/release/*.apk
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
with:
|
||||
bun-version: 1.3.4
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
@@ -104,13 +104,13 @@ jobs:
|
||||
run: export MAESTRO_VERSION=1.40.0; curl -Ls "https://get.maestro.mobile.dev" | bash
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'zulu'
|
||||
|
||||
- name: ⬇️ Download Android Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: android-artifacts
|
||||
path: artifacts/
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.MAESTRO_WEBHOOK_RESULTS }}
|
||||
|
||||
- name: Store tests result
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: TestResult
|
||||
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
|
||||
- name: 📤 Upload Android Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: android-artifacts
|
||||
path: ./android/app/build/outputs/apk/release/*.apk
|
||||
@@ -240,7 +240,7 @@ jobs:
|
||||
MATCH_REPO_PAT: "anultravioletaurora:${{ secrets.SIGNING_REPO_PAT }}"
|
||||
|
||||
- name: 📤 Upload iOS Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ios-artifacts
|
||||
path: ./ios/Jellify.ipa
|
||||
@@ -251,7 +251,7 @@ jobs:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- name: 🛒 Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
token: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
@@ -290,14 +290,14 @@ jobs:
|
||||
|
||||
- name: ⬇️ Download Android Artifacts
|
||||
if: ${{ github.event.inputs['build-platform'] == 'Android' || github.event.inputs['build-platform'] == 'Both' }}
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: android-artifacts
|
||||
path: artifacts/
|
||||
|
||||
- name: ⬇️ Download iOS Artifacts
|
||||
if: ${{ github.event.inputs['build-platform'] == 'iOS' || github.event.inputs['build-platform'] == 'Both' }}
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: ios-artifacts
|
||||
path: artifacts/
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
bun-version: 1.3.4
|
||||
|
||||
- name: 📦 Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
|
||||
@@ -133,7 +133,12 @@ export async function fetchPlaylistTracks(
|
||||
Recursive: false,
|
||||
Limit: ApiLimits.Library,
|
||||
StartIndex: pageParam * ApiLimits.Library,
|
||||
Fields: [ItemFields.MediaSources, ItemFields.ParentId, ItemFields.Path],
|
||||
Fields: [
|
||||
ItemFields.MediaSources,
|
||||
ItemFields.ParentId,
|
||||
ItemFields.Path,
|
||||
ItemFields.SortName,
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { XStack, YStack, getToken } from 'tamagui'
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
||||
import Animated, {
|
||||
@@ -101,23 +101,23 @@ export default function SwipeableRow({
|
||||
idRef.current = `swipeable-row-${Math.random().toString(36).slice(2)}`
|
||||
}
|
||||
|
||||
const syncClosedState = useCallback(() => {
|
||||
const syncClosedState = () => {
|
||||
setIsMenuOpen(false)
|
||||
menuOpenSV.value = false
|
||||
notifySwipeableRowClosed(idRef.current!)
|
||||
}, [menuOpenSV])
|
||||
}
|
||||
|
||||
const close = useCallback(() => {
|
||||
const close = () => {
|
||||
syncClosedState()
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(0, { duration: 160, easing: Easing.out(Easing.cubic) })
|
||||
}, [syncClosedState, tx])
|
||||
}
|
||||
|
||||
const openMenu = useCallback(() => {
|
||||
const openMenu = () => {
|
||||
setIsMenuOpen(true)
|
||||
menuOpenSV.value = true
|
||||
notifySwipeableRowOpened(idRef.current!)
|
||||
}, [menuOpenSV])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
registerSwipeableRow(idRef.current!, close)
|
||||
@@ -130,215 +130,200 @@ export default function SwipeableRow({
|
||||
|
||||
const fgOpacity = useSharedValue(1.0)
|
||||
|
||||
const tapGesture = useMemo(() => {
|
||||
// Reserve the right edge for per-row controls (e.g. three dots) by shrinking the tap area there
|
||||
// so those controls can receive presses without being swallowed by the row tap gesture.
|
||||
return Gesture.Tap()
|
||||
.runOnJS(true)
|
||||
.hitSlop({ right: -64 })
|
||||
.maxDistance(2)
|
||||
.onBegin(() => {
|
||||
fgOpacity.set(0.5)
|
||||
})
|
||||
.onEnd((e, success) => {
|
||||
// If a quick-action menu is open, row-level tap should NOT trigger onPress.
|
||||
if (!isMenuOpen && onPress && success) {
|
||||
/**
|
||||
* Reserve the right edge for per-row controls (e.g. three dots) by shrinking the tap area there
|
||||
* so those controls can receive presses without being swallowed by the row tap gesture.
|
||||
*/
|
||||
const tapGesture = Gesture.Tap()
|
||||
.runOnJS(true)
|
||||
.hitSlop({ right: -64 })
|
||||
.maxDistance(2)
|
||||
.onBegin(() => {
|
||||
fgOpacity.set(0.5)
|
||||
})
|
||||
.onEnd((e, success) => {
|
||||
// If a quick-action menu is open, row-level tap should NOT trigger onPress.
|
||||
if (!isMenuOpen && onPress && success) {
|
||||
triggerHaptic('impactLight')
|
||||
onPress()
|
||||
}
|
||||
})
|
||||
.onFinalize(() => {
|
||||
fgOpacity.set(1.0)
|
||||
})
|
||||
|
||||
const longPressGesture = Gesture.LongPress()
|
||||
.runOnJS(true)
|
||||
.onBegin(() => {
|
||||
fgOpacity.set(0.5)
|
||||
})
|
||||
.onStart(() => {
|
||||
if (onLongPress) {
|
||||
triggerHaptic('effectDoubleClick')
|
||||
onLongPress()
|
||||
}
|
||||
fgOpacity.set(1.0)
|
||||
})
|
||||
.onTouchesCancelled(() => {
|
||||
fgOpacity.set(1.0)
|
||||
})
|
||||
|
||||
const panGesture = Gesture.Pan()
|
||||
.runOnJS(true)
|
||||
.hitSlop({
|
||||
/**
|
||||
* Preserve Swipe to go back system gestures
|
||||
*
|
||||
* This was a value I saw ComputerJazz recommend in an issue on
|
||||
* `react-native-draggable-flatlist`, figured it could serve as a good
|
||||
* basis to start from and tune from there ~Vi
|
||||
*
|
||||
* {@link https://github.com/computerjazz}
|
||||
* {@link https://github.com/computerjazz/react-native-draggable-flatlist/issues/336#issuecomment-970573916}
|
||||
*/
|
||||
left: -50,
|
||||
})
|
||||
.activeOffsetX([-15, 15])
|
||||
.failOffsetY([-8, 8])
|
||||
.onBegin(() => {
|
||||
if (disabled) return
|
||||
dragging.set(true)
|
||||
fgOpacity.set(1.0)
|
||||
})
|
||||
.onUpdate((e) => {
|
||||
if (disabled) return
|
||||
const next = Math.max(Math.min(e.translationX, maxLeft), maxRight)
|
||||
tx.value = next
|
||||
})
|
||||
.onEnd((e) => {
|
||||
if (disabled) return
|
||||
// Velocity-based assistance: fast flicks open even if displacement below threshold
|
||||
const v = e.velocityX
|
||||
const velocityTrigger = 800
|
||||
if (tx.value > threshold) {
|
||||
// Right swipe: show left quick actions if provided; otherwise trigger leftAction
|
||||
if (leftActions && leftActions.length > 0) {
|
||||
triggerHaptic('impactLight')
|
||||
onPress()
|
||||
// Snap open to expose quick actions, do not auto-trigger
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(maxLeft, {
|
||||
duration: 140,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
openMenu()
|
||||
return
|
||||
} else if (leftAction) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(
|
||||
maxLeft,
|
||||
{ duration: 140, easing: Easing.out(Easing.cubic) },
|
||||
() => {
|
||||
scheduleOnRN(leftAction.onTrigger)
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(0, {
|
||||
duration: 160,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
})
|
||||
.onFinalize(() => {
|
||||
fgOpacity.set(1.0)
|
||||
})
|
||||
}, [onPress, isMenuOpen])
|
||||
|
||||
const longPressGesture = useMemo(() => {
|
||||
return Gesture.LongPress()
|
||||
.runOnJS(true)
|
||||
.onBegin(() => {
|
||||
fgOpacity.set(0.5)
|
||||
})
|
||||
.onStart(() => {
|
||||
if (onLongPress) {
|
||||
triggerHaptic('effectDoubleClick')
|
||||
onLongPress()
|
||||
}
|
||||
// Left swipe (quick actions)
|
||||
if (tx.value < -Math.min(threshold, Math.abs(maxRight) / 2)) {
|
||||
if (rightActions && rightActions.length > 0) {
|
||||
triggerHaptic('impactLight')
|
||||
// Snap open to expose quick actions, do not auto-trigger
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(maxRight, {
|
||||
duration: 140,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
openMenu()
|
||||
return
|
||||
} else if (rightAction) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(
|
||||
maxRight,
|
||||
{ duration: 140, easing: Easing.out(Easing.cubic) },
|
||||
() => {
|
||||
scheduleOnRN(rightAction.onTrigger)
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(0, {
|
||||
duration: 160,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
fgOpacity.set(1.0)
|
||||
})
|
||||
.onTouchesCancelled(() => {
|
||||
fgOpacity.set(1.0)
|
||||
})
|
||||
}, [onLongPress])
|
||||
|
||||
const panGesture = useMemo(() => {
|
||||
return Gesture.Pan()
|
||||
.runOnJS(true)
|
||||
.hitSlop({
|
||||
/**
|
||||
* Preserve Swipe to go back system gestures
|
||||
*
|
||||
* This was a value I saw ComputerJazz recommend in an issue on
|
||||
* `react-native-draggable-flatlist`, figured it could serve as a good
|
||||
* basis to start from and tune from there ~Vi
|
||||
*
|
||||
* {@link https://github.com/computerjazz}
|
||||
* {@link https://github.com/computerjazz/react-native-draggable-flatlist/issues/336#issuecomment-970573916}
|
||||
*/
|
||||
left: -50,
|
||||
})
|
||||
.activeOffsetX([-15, 15])
|
||||
.failOffsetY([-8, 8])
|
||||
.onBegin(() => {
|
||||
if (disabled) return
|
||||
dragging.set(true)
|
||||
fgOpacity.set(1.0)
|
||||
})
|
||||
.onUpdate((e) => {
|
||||
if (disabled) return
|
||||
const next = Math.max(Math.min(e.translationX, maxLeft), maxRight)
|
||||
tx.value = next
|
||||
})
|
||||
.onEnd((e) => {
|
||||
if (disabled) return
|
||||
// Velocity-based assistance: fast flicks open even if displacement below threshold
|
||||
const v = e.velocityX
|
||||
const velocityTrigger = 800
|
||||
if (tx.value > threshold) {
|
||||
// Right swipe: show left quick actions if provided; otherwise trigger leftAction
|
||||
if (leftActions && leftActions.length > 0) {
|
||||
triggerHaptic('impactLight')
|
||||
// Snap open to expose quick actions, do not auto-trigger
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(maxLeft, {
|
||||
duration: 140,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
openMenu()
|
||||
return
|
||||
} else if (leftAction) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(
|
||||
maxLeft,
|
||||
{ duration: 140, easing: Easing.out(Easing.cubic) },
|
||||
() => {
|
||||
scheduleOnRN(leftAction.onTrigger)
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(0, {
|
||||
duration: 160,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Velocity fallback (open quick actions if fast flick even without full displacement)
|
||||
if (v > velocityTrigger && hasLeftSide) {
|
||||
if (leftActions && leftActions.length > 0) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(maxLeft, {
|
||||
duration: 140,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
openMenu()
|
||||
return
|
||||
} else if (leftAction) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(
|
||||
maxLeft,
|
||||
{ duration: 140, easing: Easing.out(Easing.cubic) },
|
||||
() => {
|
||||
scheduleOnRN(leftAction.onTrigger)
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(0, {
|
||||
duration: 160,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
// Left swipe (quick actions)
|
||||
if (tx.value < -Math.min(threshold, Math.abs(maxRight) / 2)) {
|
||||
if (rightActions && rightActions.length > 0) {
|
||||
triggerHaptic('impactLight')
|
||||
// Snap open to expose quick actions, do not auto-trigger
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(maxRight, {
|
||||
duration: 140,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
openMenu()
|
||||
return
|
||||
} else if (rightAction) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(
|
||||
maxRight,
|
||||
{ duration: 140, easing: Easing.out(Easing.cubic) },
|
||||
() => {
|
||||
scheduleOnRN(rightAction.onTrigger)
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(0, {
|
||||
duration: 160,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (v < -velocityTrigger && hasRightSide) {
|
||||
if (rightActions && rightActions.length > 0) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(maxRight, {
|
||||
duration: 140,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
openMenu()
|
||||
return
|
||||
} else if (rightAction) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(
|
||||
maxRight,
|
||||
{ duration: 140, easing: Easing.out(Easing.cubic) },
|
||||
() => {
|
||||
scheduleOnRN(rightAction.onTrigger)
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(0, {
|
||||
duration: 160,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
// Velocity fallback (open quick actions if fast flick even without full displacement)
|
||||
if (v > velocityTrigger && hasLeftSide) {
|
||||
if (leftActions && leftActions.length > 0) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(maxLeft, {
|
||||
duration: 140,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
openMenu()
|
||||
return
|
||||
} else if (leftAction) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(
|
||||
maxLeft,
|
||||
{ duration: 140, easing: Easing.out(Easing.cubic) },
|
||||
() => {
|
||||
scheduleOnRN(leftAction.onTrigger)
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(0, {
|
||||
duration: 160,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (v < -velocityTrigger && hasRightSide) {
|
||||
if (rightActions && rightActions.length > 0) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(maxRight, {
|
||||
duration: 140,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
openMenu()
|
||||
return
|
||||
} else if (rightAction) {
|
||||
triggerHaptic('impactLight')
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(
|
||||
maxRight,
|
||||
{ duration: 140, easing: Easing.out(Easing.cubic) },
|
||||
() => {
|
||||
scheduleOnRN(rightAction.onTrigger)
|
||||
cancelAnimation(tx)
|
||||
tx.value = withTiming(0, {
|
||||
duration: 160,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
tx.value = withTiming(0, { duration: 160, easing: Easing.out(Easing.cubic) })
|
||||
syncClosedState()
|
||||
})
|
||||
.onFinalize(() => {
|
||||
if (disabled) return
|
||||
dragging.set(false)
|
||||
})
|
||||
}, [
|
||||
disabled,
|
||||
leftAction,
|
||||
leftActions,
|
||||
rightAction,
|
||||
rightActions,
|
||||
maxRight,
|
||||
maxLeft,
|
||||
openMenu,
|
||||
syncClosedState,
|
||||
triggerHaptic,
|
||||
])
|
||||
}
|
||||
tx.value = withTiming(0, { duration: 160, easing: Easing.out(Easing.cubic) })
|
||||
syncClosedState()
|
||||
})
|
||||
.onFinalize(() => {
|
||||
if (disabled) return
|
||||
dragging.set(false)
|
||||
})
|
||||
|
||||
const fgStyle = useAnimatedStyle(() => ({
|
||||
transform: [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react'
|
||||
import { getToken, Theme, useTheme, XStack, YStack } from 'tamagui'
|
||||
import { getToken, getTokenValue, Theme, useTheme, XStack, YStack } from 'tamagui'
|
||||
import { Text } from '../helpers/text'
|
||||
import { RunTimeTicks } from '../helpers/time-codes'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
@@ -14,7 +14,7 @@ import navigationRef from '../../../../navigation'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '../../../screens/types'
|
||||
import ItemImage from './image'
|
||||
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
|
||||
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
||||
import { useAddToQueue, useLoadNewQueue } from '../../../providers/Player/hooks/mutations'
|
||||
import useStreamingDeviceProfile from '../../../stores/device-profile'
|
||||
import { useDownloadedTrack } from '../../../api/queries/download'
|
||||
@@ -292,7 +292,10 @@ export default function Track({
|
||||
function HideableArtwork({ children }: { children: React.ReactNode }) {
|
||||
const { tx } = useSwipeableRowContext()
|
||||
// Hide artwork as soon as swiping starts (any non-zero tx)
|
||||
const style = useAnimatedStyle(() => ({ opacity: tx.value === 0 ? 1 : 0 }))
|
||||
const style = useAnimatedStyle(() => ({
|
||||
marginHorizontal: 6,
|
||||
opacity: withTiming(tx.value === 0 ? 1 : 0),
|
||||
}))
|
||||
return <Animated.View style={style}>{children}</Animated.View>
|
||||
}
|
||||
|
||||
|
||||
@@ -106,13 +106,21 @@ export default function SongInfo({ swipeX }: SongInfoProps = {}): React.JSX.Elem
|
||||
return (
|
||||
<XStack>
|
||||
<YStack justifyContent='flex-start' flex={1} gap={'$0.25'}>
|
||||
<TextTicker {...TextTickerConfig} style={{ height: getToken('$9') }}>
|
||||
<TextTicker
|
||||
{...TextTickerConfig}
|
||||
style={{ height: getToken('$9') }}
|
||||
key={`${nowPlaying!.item.Id}-title`}
|
||||
>
|
||||
<Text bold fontSize={'$6'}>
|
||||
{trackTitle}
|
||||
</Text>
|
||||
</TextTicker>
|
||||
|
||||
<TextTicker {...TextTickerConfig} style={{ height: getToken('$8') }}>
|
||||
<TextTicker
|
||||
{...TextTickerConfig}
|
||||
style={{ height: getToken('$8') }}
|
||||
key={`${nowPlaying!.item.Id}-artist`}
|
||||
>
|
||||
<Text fontSize={'$6'} color={'$color'} onPress={handleArtistPress}>
|
||||
{nowPlaying?.artist ?? 'Unknown Artist'}
|
||||
</Text>
|
||||
|
||||
@@ -118,7 +118,7 @@ export default function Miniplayer(): React.JSX.Element {
|
||||
<Animated.View
|
||||
entering={FadeIn}
|
||||
exiting={FadeOut}
|
||||
key={`${nowPlaying!.item.AlbumId}-mini-player-song-info`}
|
||||
key={`${nowPlaying!.item.Id}-mini-player-song-info`}
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
|
||||
@@ -225,12 +225,19 @@ export const useRemoveFromQueue = () => {
|
||||
|
||||
return async (index: number) => {
|
||||
trigger('impactMedium')
|
||||
TrackPlayer.remove([index])
|
||||
await TrackPlayer.remove([index])
|
||||
|
||||
const prevQueue = usePlayerQueueStore.getState().queue
|
||||
const newQueue = prevQueue.filter((_, i) => i !== index)
|
||||
|
||||
usePlayerQueueStore.getState().setQueue(newQueue)
|
||||
|
||||
// If queue is now empty, reset player state to hide miniplayer
|
||||
if (newQueue.length === 0) {
|
||||
usePlayerQueueStore.getState().setCurrentTrack(undefined)
|
||||
usePlayerQueueStore.getState().setCurrentIndex(undefined)
|
||||
await TrackPlayer.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +247,13 @@ export const useRemoveUpcomingTracks = () => {
|
||||
const newQueue = await TrackPlayer.getQueue()
|
||||
|
||||
usePlayerQueueStore.getState().setQueue(newQueue as JellifyTrack[])
|
||||
|
||||
// If queue is now empty, reset player state to hide miniplayer
|
||||
if (newQueue.length === 0) {
|
||||
usePlayerQueueStore.getState().setCurrentTrack(undefined)
|
||||
usePlayerQueueStore.getState().setCurrentIndex(undefined)
|
||||
await TrackPlayer.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user