mirror of
https://github.com/Jellify-Music/App.git
synced 2026-01-04 10:05:35 -06:00
Unit Testing Additions, Fix Favorites Library (#298)
Additions to testing files - including ensuring QueueProvider functionality Fix issue where artists in the favorites wouldn't show up
This commit is contained in:
@@ -1,26 +1,18 @@
|
||||
import { AlbumsProps } from '../types'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import { FlatList, RefreshControl } from 'react-native'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchFavoriteAlbums } from '../../api/queries/functions/favorites'
|
||||
import { fetchRecentlyAdded } from '../../api/queries/functions/recents'
|
||||
import { QueryConfig } from '../../api/queries/query.config'
|
||||
|
||||
export default function Albums({ navigation, route }: AlbumsProps): React.JSX.Element {
|
||||
const fetchRecentlyAddedAlbums = route.params.query === QueryKeys.RecentlyAdded
|
||||
|
||||
const {
|
||||
data: albums,
|
||||
refetch,
|
||||
isPending,
|
||||
} = useQuery({
|
||||
queryKey: [route.params.query],
|
||||
queryFn: () =>
|
||||
fetchRecentlyAddedAlbums
|
||||
? fetchRecentlyAdded(QueryConfig.limits.recents * 4, QueryConfig.limits.recents)
|
||||
: fetchFavoriteAlbums(),
|
||||
queryKey: [QueryKeys.FavoriteAlbums],
|
||||
queryFn: fetchFavoriteAlbums,
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -31,7 +23,7 @@ export default function Albums({ navigation, route }: AlbumsProps): React.JSX.El
|
||||
}}
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
numColumns={2}
|
||||
data={albums}
|
||||
data={route.params.albums ? route.params.albums : albums ? albums : []}
|
||||
refreshControl={<RefreshControl refreshing={isPending} onRefresh={refetch} />}
|
||||
renderItem={({ index, item: album }) => (
|
||||
<ItemCard
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import React from 'react'
|
||||
import { FlatList, RefreshControl } from 'react-native'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import { ArtistsProps } from '../types'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchRecentlyPlayedArtists } from '../../api/queries/functions/recents'
|
||||
import { fetchFavoriteArtists } from '../../api/queries/functions/favorites'
|
||||
import { QueryConfig } from '../../api/queries/query.config'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { YStack } from 'tamagui'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
|
||||
interface ArtistsComponentProps extends ArtistsProps {
|
||||
artists: BaseItemDto[]
|
||||
}
|
||||
import { FlatList } from 'react-native'
|
||||
|
||||
export default function Artists({ navigation, route }: ArtistsProps): React.JSX.Element {
|
||||
const {
|
||||
data: favoriteArtists,
|
||||
refetch,
|
||||
isPending,
|
||||
} = useQuery({
|
||||
queryKey: [QueryKeys.FavoriteArtists],
|
||||
queryFn: fetchFavoriteArtists,
|
||||
})
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
@@ -25,7 +26,9 @@ export default function Artists({ navigation, route }: ArtistsProps): React.JSX.
|
||||
}}
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
numColumns={2}
|
||||
data={route.params.artists}
|
||||
data={
|
||||
route.params.artists ? route.params.artists : favoriteArtists ? favoriteArtists : []
|
||||
}
|
||||
renderItem={({ index, item: artist }) => (
|
||||
<ItemCard
|
||||
item={artist}
|
||||
|
||||
@@ -24,7 +24,6 @@ export default function Index({
|
||||
paddingBottom={'$15'}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={refresh} />}
|
||||
>
|
||||
<H2>{`Recently added`}</H2>
|
||||
<RecentlyAdded navigation={navigation} />
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { StackParamList } from '../../../components/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import { ItemCard } from '../../../components/Global/components/item-card'
|
||||
import { useDiscoverContext } from '../provider'
|
||||
import { View, XStack } from 'tamagui'
|
||||
import { H2 } from '../../../components/Global/helpers/text'
|
||||
import Icon from '../../../components/Global/helpers/icon'
|
||||
|
||||
export default function RecentlyAdded({
|
||||
navigation,
|
||||
@@ -13,28 +15,42 @@ export default function RecentlyAdded({
|
||||
const { recentlyAdded } = useDiscoverContext()
|
||||
|
||||
return (
|
||||
<HorizontalCardList
|
||||
squared
|
||||
data={recentlyAdded}
|
||||
onSeeMore={() => {
|
||||
navigation.navigate('Albums', {
|
||||
query: QueryKeys.RecentlyAdded,
|
||||
})
|
||||
}}
|
||||
renderItem={({ item }) => (
|
||||
<ItemCard
|
||||
caption={item.Name}
|
||||
subCaption={`${item.Artists?.join(', ')}`}
|
||||
squared
|
||||
size={'$12'}
|
||||
item={item}
|
||||
onPress={() => {
|
||||
navigation.navigate('Album', {
|
||||
album: item,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<View>
|
||||
<XStack
|
||||
alignItems='center'
|
||||
onPress={() => {
|
||||
navigation.navigate('Albums', {
|
||||
albums: recentlyAdded,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<H2 marginLeft={'$2'}>Recently Added</H2>
|
||||
<Icon name='arrow-right' />
|
||||
</XStack>
|
||||
|
||||
<HorizontalCardList
|
||||
squared
|
||||
data={recentlyAdded?.length ?? 0 > 10 ? recentlyAdded!.slice(0, 10) : recentlyAdded}
|
||||
onSeeMore={() => {
|
||||
navigation.navigate('Albums', {
|
||||
albums: recentlyAdded,
|
||||
})
|
||||
}}
|
||||
renderItem={({ item }) => (
|
||||
<ItemCard
|
||||
caption={item.Name}
|
||||
subCaption={`${item.Artists?.join(', ')}`}
|
||||
squared
|
||||
size={'$12'}
|
||||
item={item}
|
||||
onPress={() => {
|
||||
navigation.navigate('Album', {
|
||||
album: item,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,19 +7,15 @@ interface CategoryRoute {
|
||||
name: any // ¯\_(ツ)_/¯
|
||||
iconName: string
|
||||
params?: {
|
||||
query?: QueryKeys
|
||||
queue?: Queue
|
||||
tracks?: BaseItemDto[]
|
||||
artists?: BaseItemDto[]
|
||||
}
|
||||
}
|
||||
|
||||
const Categories: CategoryRoute[] = [
|
||||
{
|
||||
name: 'Artists',
|
||||
iconName: 'microphone-variant',
|
||||
params: { query: QueryKeys.FavoriteArtists },
|
||||
},
|
||||
{ name: 'Albums', iconName: 'music-box-multiple', params: { query: QueryKeys.FavoriteAlbums } },
|
||||
{ name: 'Artists', iconName: 'microphone-variant', params: {} },
|
||||
{ name: 'Albums', iconName: 'music-box-multiple', params: {} },
|
||||
{ name: 'Tracks', iconName: 'music-note', params: { queue: 'Favorite Tracks' } },
|
||||
{ name: 'Playlists', iconName: 'playlist-music' },
|
||||
]
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function PlayerScreen({
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { nowPlayingIsFavorite, setNowPlayingIsFavorite, nowPlaying } = usePlayerContext()
|
||||
const { nowPlaying } = usePlayerContext()
|
||||
|
||||
const { queueRef } = useQueueContext()
|
||||
|
||||
@@ -155,10 +155,7 @@ export default function PlayerScreen({
|
||||
|
||||
<Spacer />
|
||||
|
||||
<FavoriteButton
|
||||
item={nowPlaying!.item}
|
||||
onToggle={() => setNowPlayingIsFavorite(!nowPlayingIsFavorite)}
|
||||
/>
|
||||
<FavoriteButton item={nowPlaying!.item} />
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
|
||||
2
components/types.d.ts
vendored
2
components/types.d.ts
vendored
@@ -34,7 +34,7 @@ export type StackParamList = {
|
||||
artists: BaseItemDto[] | undefined
|
||||
}
|
||||
Albums: {
|
||||
query: QueryKeys.FavoriteAlbums | QueryKeys.RecentlyAdded
|
||||
albums: BaseItemDto[] | undefined
|
||||
}
|
||||
Tracks: {
|
||||
tracks: BaseItemDto[] | undefined
|
||||
|
||||
@@ -3,11 +3,13 @@ module.exports = {
|
||||
preset: 'jest-expo',
|
||||
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
|
||||
setupFilesAfterEnv: [
|
||||
'./jest/setup.js',
|
||||
'./jest/setup-carplay.js',
|
||||
'./jest/setup-blurhash.js',
|
||||
'./jest/setup-reanimated.js',
|
||||
'./jest/setup-rnfs.js',
|
||||
'./jest/setup.ts',
|
||||
'./jest/setup-carplay.ts',
|
||||
'./jest/setup-blurhash.ts',
|
||||
'./jest/setup-device-info.js', // JS to prevent Typescript implicit any warning
|
||||
'./jest/setup-reanimated.ts',
|
||||
'./jest/setup-rnfs.ts',
|
||||
'./jest/setup-rntp.ts',
|
||||
'./tamagui.config.ts',
|
||||
],
|
||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
import 'react-native'
|
||||
import React from 'react'
|
||||
import App from '../App'
|
||||
|
||||
22
jest/PlayerProvider.test.tsx
Normal file
22
jest/PlayerProvider.test.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'react-native'
|
||||
import React from 'react'
|
||||
import { render } from '@testing-library/react-native'
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { QueueProvider } from '../player/queue-provider'
|
||||
import { PlayerProvider } from '../player/player-provider'
|
||||
import { View } from 'react-native'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
test(`${PlayerProvider.name} renders correctly`, () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<QueueProvider>
|
||||
<PlayerProvider>
|
||||
<View />
|
||||
</PlayerProvider>
|
||||
</QueueProvider>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
})
|
||||
47
jest/QueueProvider.test.tsx
Normal file
47
jest/QueueProvider.test.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'react-native'
|
||||
import React from 'react'
|
||||
import { render, screen, waitFor } from '@testing-library/react-native'
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { QueueProvider, useQueueContext } from '../player/queue-provider'
|
||||
import { Button, Text } from 'react-native'
|
||||
import { Event } from 'react-native-track-player'
|
||||
import { eventHandler } from './setup-rntp'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
const QueueConsumer = () => {
|
||||
const { currentIndex, useSkip } = useQueueContext()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text testID='current-index'>{currentIndex}</Text>
|
||||
|
||||
<Button title='skip' testID='use-skip' onPress={() => useSkip.mutate(undefined)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
test(`${QueueProvider.name} renders and functions correctly`, async () => {
|
||||
const queueProvider = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<QueueProvider>
|
||||
<QueueConsumer />
|
||||
</QueueProvider>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
|
||||
const currentIndex = screen.getByTestId('current-index')
|
||||
|
||||
expect(currentIndex.props.children).toBe(-1)
|
||||
|
||||
eventHandler({
|
||||
type: Event.PlaybackActiveTrackChanged,
|
||||
index: 3,
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
const updatedIndex = screen.getByTestId('current-index')
|
||||
expect(updatedIndex.props.children).toBe(3)
|
||||
})
|
||||
})
|
||||
4
jest/setup-device-info.js
Normal file
4
jest/setup-device-info.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// https://github.com/react-native-device-info/react-native-device-info/issues/1360
|
||||
import mockRNDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock'
|
||||
|
||||
jest.mock('react-native-device-info', () => mockRNDeviceInfo)
|
||||
@@ -1,30 +1,11 @@
|
||||
// https://github.com/react-native-device-info/react-native-device-info/issues/1360
|
||||
import mockRNDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
jest.mock('../api/client')
|
||||
|
||||
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter')
|
||||
|
||||
jest.mock('react-native-device-info', () => mockRNDeviceInfo)
|
||||
|
||||
jest.mock('react-native-haptic-feedback', () => {
|
||||
return {
|
||||
default: {
|
||||
trigger: jest.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('burnt', () => {
|
||||
return {
|
||||
default: {
|
||||
alert: jest.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
import * as TrackPlayer from 'react-native-track-player'
|
||||
|
||||
// https://github.com/doublesymmetry/react-native-track-player/issues/501
|
||||
jest.mock('react-native-track-player', () => {
|
||||
const listeners = new Map()
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
default: {
|
||||
@@ -68,6 +49,12 @@ jest.mock('react-native-track-player', () => {
|
||||
buffered: 150,
|
||||
duration: 200,
|
||||
}),
|
||||
usePlaybackState: () => 'playing',
|
||||
|
||||
// eslint-disable @typescript-eslint/no-explicit-any
|
||||
useTrackPlayerEvents: (events: TrackPlayer.Event[], handler: (variables: any) => void) => {
|
||||
eventHandler = handler
|
||||
},
|
||||
Capability: {
|
||||
Play: 1,
|
||||
PlayFromId: 2,
|
||||
@@ -108,5 +95,18 @@ jest.mock('react-native-track-player', () => {
|
||||
Record: 'record',
|
||||
PlayAndRecord: 'playAndRecord',
|
||||
},
|
||||
Event: {
|
||||
PlaybackActiveTrackChanged: 'playbackActiveTrackChanged',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
export let eventHandler: any
|
||||
|
||||
beforeEach(() => {
|
||||
const player = TrackPlayer as any
|
||||
|
||||
player.useTrackPlayerEvents = (events: Event[], handler: (variables: any) => void) => {
|
||||
eventHandler = handler
|
||||
}
|
||||
})
|
||||
19
jest/setup.ts
Normal file
19
jest/setup.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
jest.mock('../api/client')
|
||||
|
||||
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter')
|
||||
|
||||
jest.mock('react-native-haptic-feedback', () => {
|
||||
return {
|
||||
default: {
|
||||
trigger: jest.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('burnt', () => {
|
||||
return {
|
||||
default: {
|
||||
alert: jest.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
243
package.json
243
package.json
@@ -1,122 +1,123 @@
|
||||
{
|
||||
"name": "jellify",
|
||||
"version": "0.11.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"init:ios": "yarn install && yarn pod:install",
|
||||
"reinstall": "rm -rf ./node_modules && yarn install",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"lint": "eslint .",
|
||||
"start": "react-native start",
|
||||
"test": "jest",
|
||||
"clean:ios": "cd ios && pod deintegrate",
|
||||
"clean:android": "cd android && rm -rf app/ build/",
|
||||
"pod:install": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=0 bundle exec pod install",
|
||||
"pod:install-new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
|
||||
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
|
||||
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
|
||||
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
|
||||
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
|
||||
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
|
||||
"prepare": "husky",
|
||||
"format:check": "prettier --check .",
|
||||
"format": "prettier --write .",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jellyfin/sdk": "^0.11.0",
|
||||
"@react-native-community/blur": "^4.4.1",
|
||||
"@react-native-community/cli": "^15.1.3",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/material-top-tabs": "^7.2.10",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.10",
|
||||
"@react-navigation/stack": "^7.2.10",
|
||||
"@tamagui/config": "^1.126.1",
|
||||
"@tamagui/toast": "^1.126.1",
|
||||
"@tanstack/query-sync-storage-persister": "^5.74.6",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-query-persist-client": "^5.74.6",
|
||||
"axios": "^1.8.4",
|
||||
"bundle": "^2.1.0",
|
||||
"burnt": "^0.13.0",
|
||||
"expo": "^52.0.46",
|
||||
"expo-image": "^2.0.7",
|
||||
"gem": "^2.4.3",
|
||||
"invert-color": "^2.0.0",
|
||||
"jest-expo": "^52.0.6",
|
||||
"lodash": "^4.17.21",
|
||||
"npm-bundle": "^3.0.3",
|
||||
"patch-package": "^8.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-freeze": "^1.0.4",
|
||||
"react-native": "0.77.0",
|
||||
"react-native-background-actions": "^4.0.1",
|
||||
"react-native-blurhash": "^2.1.1",
|
||||
"react-native-boost": "^0.5.6",
|
||||
"react-native-carplay": "^2.4.1-beta.0",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-draggable-flatlist": "^4.0.2",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "^2.25.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-mmkv": "^2.12.2",
|
||||
"react-native-pager-view": "^6.7.1",
|
||||
"react-native-reanimated": "^3.17.5",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
"react-native-screens": "^4.10.0",
|
||||
"react-native-swipeable-item": "^2.0.9",
|
||||
"react-native-text-ticker": "^1.14.0",
|
||||
"react-native-track-player": "^4.1.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-vector-icons": "^10.2.0",
|
||||
"ruby": "^0.6.1",
|
||||
"tamagui": "^1.126.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"@react-native-community/cli-platform-android": "15.1.3",
|
||||
"@react-native-community/cli-platform-ios": "15.1.3",
|
||||
"@react-native/babel-preset": "0.77.0",
|
||||
"@react-native/eslint-config": "0.77.0",
|
||||
"@react-native/metro-config": "0.77.0",
|
||||
"@react-native/typescript-config": "0.77.0",
|
||||
"@types/jest": "^29.5.13",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/react-test-renderer": "^18.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
||||
"@typescript-eslint/parser": "^8.29.1",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.6.3",
|
||||
"jscodeshift": "^0.15.2",
|
||||
"lint-staged": "^15.5.0",
|
||||
"prettier": "^2.8.8",
|
||||
"react-native-cli-bump-version": "^1.5.1",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"typescript": "5.7.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
"name": "jellify",
|
||||
"version": "0.11.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"init:ios": "yarn install && yarn pod:install",
|
||||
"reinstall": "rm -rf ./node_modules && yarn install",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"lint": "eslint .",
|
||||
"start": "react-native start",
|
||||
"test": "jest",
|
||||
"clean:ios": "cd ios && pod deintegrate",
|
||||
"clean:android": "cd android && rm -rf app/ build/",
|
||||
"pod:install": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=0 bundle exec pod install",
|
||||
"pod:install-new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
|
||||
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
|
||||
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
|
||||
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
|
||||
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
|
||||
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
|
||||
"prepare": "husky",
|
||||
"format:check": "prettier --check .",
|
||||
"format": "prettier --write .",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jellyfin/sdk": "^0.11.0",
|
||||
"@react-native-community/blur": "^4.4.1",
|
||||
"@react-native-community/cli": "^15.1.3",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/material-top-tabs": "^7.2.10",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.10",
|
||||
"@react-navigation/stack": "^7.2.10",
|
||||
"@tamagui/config": "^1.126.1",
|
||||
"@tamagui/toast": "^1.126.1",
|
||||
"@tanstack/query-sync-storage-persister": "^5.74.6",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-query-persist-client": "^5.74.6",
|
||||
"@testing-library/react-native": "^13.2.0",
|
||||
"axios": "^1.8.4",
|
||||
"bundle": "^2.1.0",
|
||||
"burnt": "^0.13.0",
|
||||
"expo": "^52.0.46",
|
||||
"expo-image": "^2.0.7",
|
||||
"gem": "^2.4.3",
|
||||
"invert-color": "^2.0.0",
|
||||
"jest-expo": "^52.0.6",
|
||||
"lodash": "^4.17.21",
|
||||
"npm-bundle": "^3.0.3",
|
||||
"patch-package": "^8.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-freeze": "^1.0.4",
|
||||
"react-native": "0.77.0",
|
||||
"react-native-background-actions": "^4.0.1",
|
||||
"react-native-blurhash": "^2.1.1",
|
||||
"react-native-boost": "^0.5.6",
|
||||
"react-native-carplay": "^2.4.1-beta.0",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-draggable-flatlist": "^4.0.2",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "^2.25.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-mmkv": "^2.12.2",
|
||||
"react-native-pager-view": "^6.7.1",
|
||||
"react-native-reanimated": "^3.17.5",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
"react-native-screens": "^4.10.0",
|
||||
"react-native-swipeable-item": "^2.0.9",
|
||||
"react-native-text-ticker": "^1.14.0",
|
||||
"react-native-track-player": "^4.1.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-vector-icons": "^10.2.0",
|
||||
"ruby": "^0.6.1",
|
||||
"tamagui": "^1.126.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"@react-native-community/cli-platform-android": "15.1.3",
|
||||
"@react-native-community/cli-platform-ios": "15.1.3",
|
||||
"@react-native/babel-preset": "0.77.0",
|
||||
"@react-native/eslint-config": "0.77.0",
|
||||
"@react-native/metro-config": "0.77.0",
|
||||
"@react-native/typescript-config": "0.77.0",
|
||||
"@types/jest": "^29.5.13",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/react-test-renderer": "^18.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
||||
"@typescript-eslint/parser": "^8.29.1",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.6.3",
|
||||
"jscodeshift": "^0.15.2",
|
||||
"lint-staged": "^15.5.0",
|
||||
"prettier": "^2.8.8",
|
||||
"react-native-cli-bump-version": "^1.5.1",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"typescript": "5.7.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,56 +5,59 @@ import { convertSecondsToRunTimeTicks } from '../helpers/runtimeticks'
|
||||
|
||||
export async function handlePlaybackState(
|
||||
sessionId: string,
|
||||
playstateApi: PlaystateApi,
|
||||
playstateApi: PlaystateApi | undefined,
|
||||
track: JellifyTrack,
|
||||
state: State,
|
||||
) {
|
||||
switch (state) {
|
||||
case State.Playing: {
|
||||
console.debug('Report playback started')
|
||||
await playstateApi.reportPlaybackStart({
|
||||
playbackStartInfo: {
|
||||
SessionId: sessionId,
|
||||
ItemId: track.item.Id,
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
if (playstateApi)
|
||||
switch (state) {
|
||||
case State.Playing: {
|
||||
console.debug('Report playback started')
|
||||
await playstateApi.reportPlaybackStart({
|
||||
playbackStartInfo: {
|
||||
SessionId: sessionId,
|
||||
ItemId: track.item.Id,
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case State.Ended:
|
||||
case State.Paused:
|
||||
case State.Stopped: {
|
||||
console.debug('Report playback stopped')
|
||||
await playstateApi.reportPlaybackStopped({
|
||||
playbackStopInfo: {
|
||||
SessionId: sessionId,
|
||||
ItemId: track.item.Id,
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
case State.Ended:
|
||||
case State.Paused:
|
||||
case State.Stopped: {
|
||||
console.debug('Report playback stopped')
|
||||
await playstateApi.reportPlaybackStopped({
|
||||
playbackStopInfo: {
|
||||
SessionId: sessionId,
|
||||
ItemId: track.item.Id,
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
return
|
||||
default: {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function handlePlaybackProgressUpdated(
|
||||
sessionId: string,
|
||||
playstateApi: PlaystateApi,
|
||||
playstateApi: PlaystateApi | undefined,
|
||||
track: JellifyTrack,
|
||||
progress: Progress,
|
||||
) {
|
||||
if (Math.floor(progress.duration - progress.position) === 5) {
|
||||
console.debug('Track finished, scrobbling...')
|
||||
await playstateApi.reportPlaybackStopped({
|
||||
playbackStopInfo: {
|
||||
SessionId: sessionId,
|
||||
ItemId: track.item.Id,
|
||||
PositionTicks: convertSecondsToRunTimeTicks(track.duration!),
|
||||
},
|
||||
})
|
||||
console.debug(`Track finished. ${playstateApi ? 'scrobbling...' : ''}`)
|
||||
|
||||
if (playstateApi)
|
||||
await playstateApi.reportPlaybackStopped({
|
||||
playbackStopInfo: {
|
||||
SessionId: sessionId,
|
||||
ItemId: track.item.Id,
|
||||
PositionTicks: convertSecondsToRunTimeTicks(track.duration!),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// DO NOTHING, reporting playback will just eat up power
|
||||
// Jellyfin can keep track of progress, we're going to intentionally
|
||||
|
||||
@@ -18,6 +18,7 @@ import Client from '../api/client'
|
||||
import { getPlaystateApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { useNetworkContext } from '../components/Network/provider'
|
||||
import { useQueueContext } from './queue-provider'
|
||||
import { PlaystateApi } from '@jellyfin/sdk/lib/generated-client/api/playstate-api'
|
||||
|
||||
interface PlayerContext {
|
||||
nowPlaying: JellifyTrack | undefined
|
||||
@@ -30,7 +31,9 @@ interface PlayerContext {
|
||||
const PlayerContextInitializer = () => {
|
||||
const nowPlayingJson = storage.getString(MMKVStorageKeys.NowPlaying)
|
||||
|
||||
const playStateApi = getPlaystateApi(Client.api!)
|
||||
let playStateApi: PlaystateApi | undefined
|
||||
|
||||
if (Client.api) getPlaystateApi(Client.api)
|
||||
|
||||
//#region State
|
||||
const [nowPlaying, setNowPlaying] = useState<JellifyTrack | undefined>(
|
||||
|
||||
30
yarn.lock
30
yarn.lock
@@ -3583,6 +3583,16 @@
|
||||
dependencies:
|
||||
"@tanstack/query-core" "5.74.4"
|
||||
|
||||
"@testing-library/react-native@^13.2.0":
|
||||
version "13.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-13.2.0.tgz#b4f53c69a889728abe8bc3115ba803824bcafe10"
|
||||
integrity sha512-3FX+vW/JScXkoH8VSCRTYF4KCHC56y4AI6TMDISfLna6r+z8kaSEmxH1I6NVaFOxoWX9yaHDyI26xh7BykmqKw==
|
||||
dependencies:
|
||||
chalk "^4.1.2"
|
||||
jest-matcher-utils "^29.7.0"
|
||||
pretty-format "^29.7.0"
|
||||
redent "^3.0.0"
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz"
|
||||
@@ -8478,6 +8488,11 @@ mimic-function@^5.0.0:
|
||||
resolved "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz"
|
||||
integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==
|
||||
|
||||
min-indent@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
||||
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
||||
|
||||
"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||
@@ -9704,6 +9719,14 @@ recast@^0.23.3, recast@^0.23.9:
|
||||
tiny-invariant "^1.3.3"
|
||||
tslib "^2.0.1"
|
||||
|
||||
redent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
|
||||
dependencies:
|
||||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz"
|
||||
@@ -10594,6 +10617,13 @@ strip-final-newline@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz"
|
||||
integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==
|
||||
|
||||
strip-indent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
|
||||
integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
|
||||
dependencies:
|
||||
min-indent "^1.0.0"
|
||||
|
||||
strip-json-comments@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
|
||||
|
||||
Reference in New Issue
Block a user