mirror of
https://github.com/Jellify-Music/App.git
synced 2026-04-20 08:43:42 -05:00
Merge branch 'feature/nitro-player' of github.com:Jellify-Music/App into feature/nitro-player
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { ListItem } from 'tamagui'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { Text } from '../../Global/helpers/text'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { removeFromPlaylist } from '../../../api/mutations/playlists'
|
||||
import { useApi } from '../../../stores'
|
||||
import navigationRef from '../../../../navigation'
|
||||
import { triggerHaptic } from '../../../hooks/use-haptic-feedback'
|
||||
import { queryClient } from '../../../constants/query-client'
|
||||
import { PlaylistTracksQueryKey } from '../../../api/queries/playlist/keys'
|
||||
|
||||
export default function RemoveFromPlaylistRow({
|
||||
track,
|
||||
playlist,
|
||||
}: {
|
||||
track: BaseItemDto
|
||||
playlist: BaseItemDto
|
||||
}): React.JSX.Element {
|
||||
const api = useApi()
|
||||
|
||||
const { mutate: removeTrack, isPending } = useMutation({
|
||||
mutationFn: () => removeFromPlaylist(api, track, playlist),
|
||||
onSuccess: () => {
|
||||
triggerHaptic('notificationSuccess')
|
||||
queryClient.invalidateQueries({ queryKey: PlaylistTracksQueryKey(playlist) })
|
||||
navigationRef.goBack()
|
||||
},
|
||||
onError: () => {
|
||||
triggerHaptic('notificationError')
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
animation={'quick'}
|
||||
backgroundColor={'transparent'}
|
||||
disabled={isPending}
|
||||
flex={1}
|
||||
gap={'$2.5'}
|
||||
justifyContent='flex-start'
|
||||
onPress={() => removeTrack()}
|
||||
pressStyle={{ opacity: 0.5 }}
|
||||
>
|
||||
<Icon small color='$warning' name='playlist-remove' />
|
||||
|
||||
<Text bold>Remove from Playlist</Text>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { triggerHaptic } from '../../hooks/use-haptic-feedback'
|
||||
import { Platform } from 'react-native'
|
||||
import { useApi } from '../../stores'
|
||||
import DeletePlaylistRow from './components/delete-playlist-row'
|
||||
import RemoveFromPlaylistRow from './components/remove-from-playlist-row'
|
||||
import useDownloadTracks, { useDeleteDownloads } from '../../hooks/downloads/mutations'
|
||||
import { useIsDownloaded } from '../../hooks/downloads'
|
||||
import { useDownloadProgress } from 'react-native-nitro-player'
|
||||
@@ -37,6 +38,7 @@ type StackNavigation = Pick<NativeStackNavigationProp<BaseStackParamList>, 'navi
|
||||
|
||||
interface ContextProps {
|
||||
item: BaseItemDto
|
||||
playlist?: BaseItemDto
|
||||
streamingMediaSourceInfo?: MediaSourceInfo
|
||||
downloadedMediaSourceInfo?: MediaSourceInfo
|
||||
stackNavigation?: StackNavigation
|
||||
@@ -46,6 +48,7 @@ interface ContextProps {
|
||||
|
||||
export default function ItemContext({
|
||||
item,
|
||||
playlist,
|
||||
streamingMediaSourceInfo,
|
||||
downloadedMediaSourceInfo,
|
||||
stackNavigation,
|
||||
@@ -85,6 +88,8 @@ export default function ItemContext({
|
||||
|
||||
const renderAddToPlaylistRow = isTrack || isAlbum
|
||||
|
||||
const renderRemoveFromPlaylistRow = isTrack && !!playlist
|
||||
|
||||
const renderViewAlbumRow = isAlbum || (isTrack && album)
|
||||
|
||||
const renderDeletePlaylistRow = isPlaylist && item.CanDelete
|
||||
@@ -107,15 +112,13 @@ export default function ItemContext({
|
||||
useEffect(() => triggerHaptic('impactLight'), [item?.Id])
|
||||
|
||||
return (
|
||||
<YGroup scrollable={Platform.OS === 'android'} marginBottom={'$8'}>
|
||||
<YGroup scrollable={Platform.OS === 'android'} marginBottom={'$3'}>
|
||||
<FavoriteContextMenuRow item={item} />
|
||||
|
||||
{renderDeletePlaylistRow && <DeletePlaylistRow playlist={item} />}
|
||||
|
||||
{renderAddToQueueRow && <AddToQueueMenuRow tracks={itemTracks} />}
|
||||
|
||||
{renderAddToQueueRow && <DownloadMenuRow items={itemTracks} />}
|
||||
|
||||
{renderAddToPlaylistRow && (
|
||||
<AddToPlaylistRow
|
||||
tracks={isAlbum && discs ? discs.flatMap((d) => d.data) : [item]}
|
||||
@@ -123,6 +126,12 @@ export default function ItemContext({
|
||||
/>
|
||||
)}
|
||||
|
||||
{renderAddToQueueRow && <DownloadMenuRow items={itemTracks} />}
|
||||
|
||||
{renderRemoveFromPlaylistRow && playlist && (
|
||||
<RemoveFromPlaylistRow track={item} playlist={playlist} />
|
||||
)}
|
||||
|
||||
{(streamingMediaSourceInfo || downloadedMediaSourceInfo) && (
|
||||
<StatsRow
|
||||
item={item}
|
||||
|
||||
@@ -19,7 +19,6 @@ export interface TrackRowContentProps {
|
||||
textColor?: string
|
||||
indexNumber: string
|
||||
trackName: string
|
||||
shouldShowArtists: boolean
|
||||
artistsText: string
|
||||
runtimeComponent: React.ReactNode
|
||||
editing?: boolean
|
||||
@@ -90,7 +89,6 @@ export default function TrackRowContent({
|
||||
textColor,
|
||||
indexNumber,
|
||||
trackName,
|
||||
shouldShowArtists,
|
||||
artistsText,
|
||||
runtimeComponent,
|
||||
editing,
|
||||
@@ -129,7 +127,7 @@ export default function TrackRowContent({
|
||||
<Text
|
||||
key={`${track.Id}-number`}
|
||||
marginHorizontal={'auto'}
|
||||
minWidth={'$4'}
|
||||
minWidth={'$3'}
|
||||
color={textColor}
|
||||
textAlign='center'
|
||||
fontVariant={['tabular-nums']}
|
||||
@@ -140,7 +138,7 @@ export default function TrackRowContent({
|
||||
</XStack>
|
||||
|
||||
<SlidingTextArea leftGapWidth={artworkAreaWidth} hasArtwork={!!showArtwork}>
|
||||
<YStack alignItems='flex-start' justifyContent='center'>
|
||||
<YStack alignItems='flex-start' justifyContent='center' gap={'$0'}>
|
||||
<XStack alignItems='center'>
|
||||
<Text
|
||||
key={`${track.Id}-name`}
|
||||
@@ -148,41 +146,34 @@ export default function TrackRowContent({
|
||||
color={textColor}
|
||||
lineBreakStrategyIOS='standard'
|
||||
numberOfLines={1}
|
||||
fontSize={'$4'}
|
||||
>
|
||||
{trackName}
|
||||
</Text>
|
||||
{!shouldShowArtists && isExplicit(track) && (
|
||||
<XStack alignSelf='center' paddingLeft='$2'>
|
||||
</XStack>
|
||||
|
||||
<XStack alignItems='center' gap={'$1'}>
|
||||
<DownloadedIcon item={track} size='xxxsmall' />
|
||||
<Text
|
||||
key={`${track.Id}-artists`}
|
||||
lineBreakStrategyIOS='standard'
|
||||
numberOfLines={1}
|
||||
color={'$borderColor'}
|
||||
fontSize={'$2'}
|
||||
marginVertical={'$-1.5'}
|
||||
>
|
||||
{artistsText}
|
||||
</Text>
|
||||
{isExplicit(track) && (
|
||||
<XStack alignSelf='center' paddingTop='0.5'>
|
||||
<Icon
|
||||
name='alpha-e-box-outline'
|
||||
color={'$borderColor'}
|
||||
xxsmall
|
||||
xxxsmall
|
||||
/>
|
||||
</XStack>
|
||||
)}
|
||||
</XStack>
|
||||
|
||||
{shouldShowArtists && (
|
||||
<XStack alignItems='center'>
|
||||
<Text
|
||||
key={`${track.Id}-artists`}
|
||||
lineBreakStrategyIOS='standard'
|
||||
numberOfLines={1}
|
||||
color={'$borderColor'}
|
||||
>
|
||||
{artistsText}
|
||||
</Text>
|
||||
{isExplicit(track) && (
|
||||
<XStack alignSelf='center' paddingTop='$1' paddingLeft='$1'>
|
||||
<Icon
|
||||
name='alpha-e-box-outline'
|
||||
color={'$borderColor'}
|
||||
xxsmall
|
||||
/>
|
||||
</XStack>
|
||||
)}
|
||||
</XStack>
|
||||
)}
|
||||
</YStack>
|
||||
</SlidingTextArea>
|
||||
|
||||
@@ -193,7 +184,6 @@ export default function TrackRowContent({
|
||||
flexShrink={1}
|
||||
gap='$1'
|
||||
>
|
||||
<DownloadedIcon item={track} />
|
||||
<FavoriteIcon item={track} />
|
||||
{runtimeComponent}
|
||||
{!editing && <Icon name={'dots-horizontal'} onPress={handleIconPress} />}
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface TrackProps {
|
||||
tracklist?: BaseItemDto[] | undefined
|
||||
index: number
|
||||
queue: Queue
|
||||
playlist?: BaseItemDto
|
||||
showArtwork?: boolean | undefined
|
||||
onPress?: () => Promise<void> | undefined
|
||||
onLongPress?: () => void | undefined
|
||||
@@ -45,6 +46,7 @@ export default function Track({
|
||||
tracklist,
|
||||
index,
|
||||
queue,
|
||||
playlist,
|
||||
showArtwork,
|
||||
onPress,
|
||||
onLongPress,
|
||||
@@ -104,6 +106,7 @@ export default function Track({
|
||||
navigationRef.navigate('Context', {
|
||||
item: track,
|
||||
navigation,
|
||||
...(playlist && { playlist }),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -112,6 +115,7 @@ export default function Track({
|
||||
navigationRef.navigate('Context', {
|
||||
item: track,
|
||||
navigation,
|
||||
...(playlist && { playlist }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -140,9 +144,6 @@ export default function Track({
|
||||
// Memoize index number
|
||||
const indexNumber = track.IndexNumber?.toString() ?? ''
|
||||
|
||||
// Memoize show artists condition
|
||||
const shouldShowArtists = showArtwork || (track.ArtistItems && track.ArtistItems.length > 1)
|
||||
|
||||
const swipeHandlers = {
|
||||
addToQueue: async () => {
|
||||
console.info('Running add to queue swipe action')
|
||||
@@ -196,7 +197,6 @@ export default function Track({
|
||||
textColor={textColor}
|
||||
indexNumber={indexNumber}
|
||||
trackName={trackName}
|
||||
shouldShowArtists={shouldShowArtists ?? false}
|
||||
artistsText={artistsText}
|
||||
runtimeComponent={runtimeComponent}
|
||||
editing={editing}
|
||||
@@ -222,7 +222,6 @@ export default function Track({
|
||||
textColor={textColor}
|
||||
indexNumber={indexNumber}
|
||||
trackName={trackName}
|
||||
shouldShowArtists={shouldShowArtists ?? false}
|
||||
artistsText={artistsText}
|
||||
runtimeComponent={runtimeComponent}
|
||||
editing={editing}
|
||||
|
||||
@@ -5,28 +5,38 @@ import { useIsDownloaded } from '../../../hooks/downloads'
|
||||
import { useDownloadProgress } from 'react-native-nitro-player'
|
||||
import CircularProgressIndicator from './circular-progress-indicator'
|
||||
|
||||
function DownloadedIcon({ item }: { item: BaseItemDto }) {
|
||||
function DownloadedIcon({
|
||||
item,
|
||||
size = 'small',
|
||||
}: {
|
||||
item: BaseItemDto
|
||||
size?: 'xxxsmall' | 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large'
|
||||
}) {
|
||||
const isDownloaded = useIsDownloaded([item.Id])
|
||||
|
||||
const { overallProgress } = useDownloadProgress({
|
||||
const { overallProgress, isDownloading } = useDownloadProgress({
|
||||
trackIds: [item.Id!],
|
||||
activeOnly: true,
|
||||
})
|
||||
|
||||
return isDownloaded ? (
|
||||
<Animated.View
|
||||
entering={FadeIn.easing(Easing.in(Easing.ease))}
|
||||
exiting={FadeOut.easing(Easing.out(Easing.ease))}
|
||||
>
|
||||
<Icon small name='download-circle' color={'$success'} flex={1} />
|
||||
</Animated.View>
|
||||
return isDownloaded || isDownloading ? (
|
||||
isDownloaded ? (
|
||||
<Animated.View
|
||||
entering={FadeIn.easing(Easing.in(Easing.ease))}
|
||||
exiting={FadeOut.easing(Easing.out(Easing.ease))}
|
||||
>
|
||||
<Icon {...{ [size]: true }} name='download-circle' color={'$success'} flex={1} />
|
||||
</Animated.View>
|
||||
) : (
|
||||
<Animated.View
|
||||
entering={FadeIn.easing(Easing.in(Easing.ease))}
|
||||
exiting={FadeOut.easing(Easing.out(Easing.ease))}
|
||||
>
|
||||
<CircularProgressIndicator progress={overallProgress} size={12} strokeWidth={4} />
|
||||
</Animated.View>
|
||||
)
|
||||
) : (
|
||||
<Animated.View
|
||||
entering={FadeIn.easing(Easing.in(Easing.ease))}
|
||||
exiting={FadeOut.easing(Easing.out(Easing.ease))}
|
||||
>
|
||||
<CircularProgressIndicator progress={overallProgress} size={24} strokeWidth={4} />
|
||||
</Animated.View>
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,22 +12,27 @@ import {
|
||||
YStack,
|
||||
} from 'tamagui'
|
||||
import MaterialDesignIcon from '@react-native-vector-icons/material-design-icons'
|
||||
import { on } from 'events'
|
||||
|
||||
const xxxsmallSize = 12
|
||||
const xxsmallSize = 16
|
||||
|
||||
const xsmallSize = 20
|
||||
|
||||
const smallSize = 28
|
||||
|
||||
const regularSize = 34
|
||||
|
||||
const largeSize = 44
|
||||
|
||||
const SIZE_ENTRIES = [
|
||||
['large', largeSize],
|
||||
['small', smallSize],
|
||||
['xsmall', xsmallSize],
|
||||
['xxsmall', xxsmallSize],
|
||||
['xxxsmall', xxxsmallSize],
|
||||
] as const
|
||||
|
||||
export default function Icon({
|
||||
name,
|
||||
onPress,
|
||||
onPressIn,
|
||||
xxxsmall,
|
||||
xxsmall,
|
||||
xsmall,
|
||||
small,
|
||||
@@ -40,6 +45,7 @@ export default function Icon({
|
||||
name: string
|
||||
onPress?: () => void
|
||||
onPressIn?: () => void
|
||||
xxxsmall?: boolean
|
||||
xxsmall?: boolean
|
||||
xsmall?: boolean
|
||||
small?: boolean
|
||||
@@ -50,15 +56,8 @@ export default function Icon({
|
||||
testID?: string | undefined
|
||||
}): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
const size = large
|
||||
? largeSize
|
||||
: small
|
||||
? smallSize
|
||||
: xsmall
|
||||
? xsmallSize
|
||||
: xxsmall
|
||||
? xxsmallSize
|
||||
: regularSize
|
||||
const sizeProps = { large, small, xsmall, xxsmall, xxxsmall }
|
||||
const size = SIZE_ENTRIES.find(([key]) => sizeProps[key])?.[1] ?? regularSize
|
||||
|
||||
const animation = onPress || onPressIn ? 'quick' : undefined
|
||||
|
||||
|
||||
@@ -297,6 +297,7 @@ export default function Playlist({
|
||||
rootNavigation.navigate('Context', {
|
||||
item: track,
|
||||
navigation,
|
||||
playlist,
|
||||
})
|
||||
}}
|
||||
>
|
||||
@@ -306,6 +307,7 @@ export default function Playlist({
|
||||
tracklist={playlistTracks ?? []}
|
||||
index={index}
|
||||
queue={playlist}
|
||||
playlist={playlist}
|
||||
showArtwork
|
||||
editing={editing}
|
||||
/>
|
||||
@@ -333,6 +335,7 @@ export default function Playlist({
|
||||
tracklist={playlistTracks ?? []}
|
||||
index={index}
|
||||
queue={playlist}
|
||||
playlist={playlist}
|
||||
showArtwork
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ export default function ItemContextScreen({ route, navigation }: ContextProps):
|
||||
<ItemContext
|
||||
navigation={navigation}
|
||||
item={route.params.item}
|
||||
playlist={route.params.playlist}
|
||||
stackNavigation={route.params.navigation}
|
||||
navigationCallback={route.params.navigationCallback}
|
||||
streamingMediaSourceInfo={route.params.streamingMediaSourceInfo}
|
||||
|
||||
1
src/screens/types.d.ts
vendored
1
src/screens/types.d.ts
vendored
@@ -54,6 +54,7 @@ export type RootStackParamList = {
|
||||
|
||||
Context: {
|
||||
item: BaseItemDto
|
||||
playlist?: BaseItemDto
|
||||
streamingMediaSourceInfo?: MediaSourceInfo
|
||||
downloadedMediaSourceInfo?: MediaSourceInfo
|
||||
navigation?: Pick<NativeStackNavigationProp<BaseStackParamList>, 'navigate' | 'dispatch'>
|
||||
|
||||
Reference in New Issue
Block a user