mirror of
https://github.com/Jellify-Music/App.git
synced 2026-01-06 19:09:52 -06:00
hot fix for player screen not displaying on iOS
This commit is contained in:
@@ -170,7 +170,15 @@ function AlbumTrackListHeader({ album }: { album: BaseItemDto }): React.JSX.Elem
|
||||
|
||||
return (
|
||||
<YStack alignContent='center' flex={1} marginTop={'$4'}>
|
||||
<ItemImage item={album} width={'$20'} height={'$20'} />
|
||||
<ItemImage
|
||||
item={album}
|
||||
width={'$20'}
|
||||
height={'$20'}
|
||||
imageOptions={{
|
||||
maxHeight: 500,
|
||||
maxWidth: 500,
|
||||
}}
|
||||
/>
|
||||
|
||||
<YStack marginTop={'$2'} alignContent='center' justifyContent='center' gap={'$2'}>
|
||||
<H5 lineBreakStrategyIOS='standard' textAlign='center' numberOfLines={5}>
|
||||
|
||||
@@ -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('$12'),
|
||||
}
|
||||
|
||||
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>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -14,6 +14,7 @@ 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>()
|
||||
|
||||
@@ -38,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,
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user