Network Context Optimization, Bottom Tab Bar Styling, Settings Page Styling (#529)

* update Tanstack

fix issue where tracks didn't restore properly, meaning that playback on a cold start wasn't possible

styling changes to bottom tab bar icons, settings tab pages

network context optimization
This commit is contained in:
Violet Caulfield
2025-09-13 21:24:05 -05:00
committed by GitHub
parent 26e409955d
commit 87aad65ae9
14 changed files with 106 additions and 83 deletions
+3 -3
View File
@@ -48,9 +48,9 @@
"@sentry/react-native": "7.0.1",
"@shopify/flash-list": "^2.0.3",
"@tamagui/config": "^1.132.23",
"@tanstack/query-async-storage-persister": "^5.87.1",
"@tanstack/react-query": "^5.87.1",
"@tanstack/react-query-persist-client": "^5.87.1",
"@tanstack/query-async-storage-persister": "^5.87.4",
"@tanstack/react-query": "^5.87.4",
"@tanstack/react-query-persist-client": "^5.87.4",
"@testing-library/react-native": "^13.2.3",
"@typedigital/telemetrydeck-react": "^0.4.1",
"axios": "^1.11.0",
+2 -2
View File
@@ -40,7 +40,7 @@ export function Album(): React.JSX.Element {
const { album, discs, isPending } = useAlbumContext()
const { api } = useJellifyContext()
const { useDownloadMultiple, pendingDownloads } = useNetworkContext()
const { addToDownloadQueue, pendingDownloads } = useNetworkContext()
const [networkStatus] = useNetworkStatus()
const streamingDeviceProfile = useStreamingDeviceProfile()
const downloadingDeviceProfile = useDownloadingDeviceProfile()
@@ -51,7 +51,7 @@ export function Album(): React.JSX.Element {
const jellifyTracks = item.map((item) =>
mapDtoToTrack(api, item, [], downloadingDeviceProfile),
)
useDownloadMultiple(jellifyTracks)
addToDownloadQueue(jellifyTracks)
}
const playAlbum = useCallback(
+3 -3
View File
@@ -202,7 +202,7 @@ function AddToQueueMenuRow({ tracks }: { tracks: BaseItemDto[] }): React.JSX.Ele
function DownloadMenuRow({ items }: { items: BaseItemDto[] }): React.JSX.Element {
const { api } = useJellifyContext()
const { useDownloadMultiple, pendingDownloads } = useNetworkContext()
const { addToDownloadQueue, pendingDownloads } = useNetworkContext()
const useRemoveDownload = useDeleteDownloads()
@@ -214,8 +214,8 @@ function DownloadMenuRow({ items }: { items: BaseItemDto[] }): React.JSX.Element
if (!api) return
const tracks = items.map((item) => mapDtoToTrack(api, item, [], deviceProfile))
useDownloadMultiple(tracks)
}, [useDownloadMultiple, items])
addToDownloadQueue(tracks)
}, [addToDownloadQueue, items])
const removeDownloads = useCallback(
() => useRemoveDownload(items.map(({ Id }) => Id)),
+6 -7
View File
@@ -79,19 +79,18 @@ export const Miniplayer = React.memo(function Miniplayer(): React.JSX.Element {
[translateX, translateY, handleSwipe],
)
const openPlayer = useCallback(
() => navigation.navigate('PlayerRoot', { screen: 'PlayerScreen' }),
[navigation],
)
return (
<View testID='miniplayer-test-id'>
{nowPlaying && (
<GestureDetector gesture={gesture}>
<YStack>
<MiniPlayerProgress />
<XStack
paddingBottom={'$1'}
alignItems='center'
onPress={() =>
navigation.navigate('PlayerRoot', { screen: 'PlayerScreen' })
}
>
<XStack paddingBottom={'$1'} alignItems='center' onPress={openPlayer}>
<YStack justify='center' alignItems='center' marginLeft={'$2'}>
{api && (
<Animated.View
@@ -150,7 +150,7 @@ function PlaylistHeaderControls({
playlistTracks: BaseItemDto[]
canEdit: boolean | undefined
}): React.JSX.Element {
const { useDownloadMultiple, pendingDownloads } = useNetworkContext()
const { addToDownloadQueue, pendingDownloads } = useNetworkContext()
const streamingDeviceProfile = useStreamingDeviceProfile()
const downloadingDeviceProfile = useDownloadingDeviceProfile()
const { mutate: loadNewQueue } = useLoadNewQueue()
@@ -166,7 +166,7 @@ function PlaylistHeaderControls({
const jellifyTracks = playlistTracks.map((item) =>
mapDtoToTrack(api, item, [], downloadingDeviceProfile),
)
useDownloadMultiple(jellifyTracks)
addToDownloadQueue(jellifyTracks)
}
const playPlaylist = (shuffled: boolean = false) => {
@@ -28,8 +28,8 @@ export default function InfoTabIndex() {
{
title: `Jellify ${version}`,
subTitle: caption,
iconName: 'jellyfish-outline',
iconColor: '$secondary',
iconName: 'jellyfish',
iconColor: '$primary',
children: (
<XStack gap={'$3'} marginTop={'$2'}>
<XStack
@@ -54,7 +54,7 @@ export default function InfoTabIndex() {
{
title: 'Caught a bug?',
subTitle: "Let's squash it!",
iconName: 'bug-outline',
iconName: 'bug',
iconColor: '$danger',
children: (
<XStack gap={'$3'} marginTop={'$2'}>
@@ -76,7 +76,7 @@ export default function InfoTabIndex() {
title: 'Powered by listeners like you',
subTitle: 'Sponsor on GitHub or Patreon',
iconName: 'heart',
iconColor: '$primary',
iconColor: '$secondary',
children: (
<XStack justifyContent='flex-start' gap={'$4'} marginVertical={'$2'}>
<XStack
@@ -106,7 +106,7 @@ export default function InfoTabIndex() {
title: 'Patreon Wall of Fame',
subTitle: 'Thank you to these paid members',
iconName: 'patreon',
iconColor: '$primary',
iconColor: '$secondary',
children: (
<FlashList
data={patrons}
@@ -8,12 +8,16 @@ import {
useAutoDownload,
useDownloadQuality,
} from '../../../stores/settings/usage'
import { useNetworkContext } from '../../../providers/Network'
import { useMemo } from 'react'
export default function StorageTab(): React.JSX.Element {
const [autoDownload, setAutoDownload] = useAutoDownload()
const [downloadQuality, setDownloadQuality] = useDownloadQuality()
const { data: downloadedTracks } = useAllDownloadedTracks()
const { pendingDownloads } = useNetworkContext()
return (
<SettingsListGroup
settingsList={[
+4 -15
View File
@@ -1,18 +1,17 @@
import React, { createContext, ReactNode, useContext, useEffect, useState, useMemo } from 'react'
import { JellifyDownloadProgress } from '../../types/JellifyDownload'
import { UseMutateFunction, useMutation } from '@tanstack/react-query'
import { saveAudio } from '../../api/mutations/download/offlineModeUtils'
import JellifyTrack from '../../types/JellifyTrack'
import { useAllDownloadedTracks } from '../../api/queries/download'
import { usePerformanceMonitor } from '../../hooks/use-performance-monitor'
interface NetworkContext {
useDownloadMultiple: UseMutateFunction<boolean, Error, JellifyTrack[], unknown>
activeDownloads: JellifyDownloadProgress | undefined
pendingDownloads: JellifyTrack[]
downloadingDownloads: JellifyTrack[]
completedDownloads: JellifyTrack[]
failedDownloads: JellifyTrack[]
addToDownloadQueue: (items: JellifyTrack[]) => boolean
}
const MAX_CONCURRENT_DOWNLOADS = 1
@@ -60,38 +59,29 @@ const NetworkContextInitializer = () => {
}
}, [pending, downloading])
const addToQueue = async (items: JellifyTrack[]) => {
const addToDownloadQueue = (items: JellifyTrack[]) => {
setPending((prev) => [...prev, ...items])
return true
}
const { mutate: useDownloadMultiple } = useMutation({
mutationFn: (tracks: JellifyTrack[]) => {
return addToQueue(tracks)
},
onSuccess: (data, variables) => {
console.debug(`Added ${variables?.length} tracks to queue`)
},
})
return {
activeDownloads: downloadProgress,
downloadedTracks,
useDownloadMultiple,
pendingDownloads: pending,
downloadingDownloads: downloading,
completedDownloads: completed,
failedDownloads: failed,
addToDownloadQueue,
}
}
const NetworkContext = createContext<NetworkContext>({
activeDownloads: {},
useDownloadMultiple: () => {},
pendingDownloads: [],
downloadingDownloads: [],
completedDownloads: [],
failedDownloads: [],
addToDownloadQueue: () => true,
})
export const NetworkContextProvider: ({
@@ -110,7 +100,6 @@ export const NetworkContextProvider: ({
context.downloadingDownloads.length,
context.completedDownloads.length,
context.failedDownloads.length,
// Don't include mutation objects as they're stable
],
)
+7 -2
View File
@@ -1,6 +1,11 @@
import JellifyTrack from '../../../types/JellifyTrack'
import PlayerQueryKeys from '../enums/queue-keys'
import { NOW_PLAYING_QUERY_KEY, PLAY_QUEUE_QUERY_KEY, REPEAT_MODE_QUERY_KEY } from './query-keys'
import {
ACTIVE_INDEX_QUERY_KEY,
NOW_PLAYING_QUERY_KEY,
PLAY_QUEUE_QUERY_KEY,
REPEAT_MODE_QUERY_KEY,
} from './query-keys'
import TrackPlayer, { Track } from 'react-native-track-player'
const PLAYER_QUERY_OPTIONS = {
@@ -21,7 +26,7 @@ export const QUEUE_QUERY = {
}
export const CURRENT_INDEX_QUERY = {
queryKey: [PlayerQueryKeys.ActiveIndex],
queryKey: ACTIVE_INDEX_QUERY_KEY,
queryFn: TrackPlayer.getActiveTrackIndex,
...PLAYER_QUERY_OPTIONS,
}
@@ -1,13 +1,14 @@
import { isUndefined } from 'lodash'
import { getActiveIndex, getPlayQueue } from '.'
import { getActiveIndex, getCurrentTrack, getPlayQueue } from '.'
import TrackPlayer from 'react-native-track-player'
export default async function Initialize() {
const storedPlayQueue = getPlayQueue()
const storedIndex = getActiveIndex()
const storedTrack = getCurrentTrack()
console.debug(
`StoredIndex: ${storedIndex}, storedPlayQueue: ${storedPlayQueue?.map((track, index) => index)}`,
`StoredIndex: ${storedIndex}, storedPlayQueue: ${storedPlayQueue?.map((track, index) => index)}, track: ${storedTrack?.item.Id}`,
)
if (!isUndefined(storedPlayQueue) && !isUndefined(storedIndex)) {
+10 -2
View File
@@ -15,6 +15,11 @@ type LoadQueueOperation = QueueMutation & {
downloadedTracks: JellifyDownload[] | undefined
}
type LoadQueueResult = {
finalStartIndex: number
tracks: JellifyTrack[]
}
export async function loadQueue({
index,
tracklist,
@@ -24,7 +29,7 @@ export async function loadQueue({
deviceProfile,
networkStatus = networkStatusTypes.ONLINE,
downloadedTracks,
}: LoadQueueOperation) {
}: LoadQueueOperation): Promise<LoadQueueResult> {
setQueueRef(queueRef)
setShuffled(shuffled)
@@ -100,7 +105,10 @@ export async function loadQueue({
`Queued ${queue.length} tracks, starting at ${finalStartIndex}${shuffled ? ' (shuffled)' : ''}`,
)
return finalStartIndex
return {
finalStartIndex,
tracks: queue,
}
}
type PlayNextOperation = AddToQueueMutation & {
+13 -8
View File
@@ -7,13 +7,7 @@ import { AddToQueueMutation, QueueMutation, QueueOrderMutation } from '../interf
import { refetchNowPlaying, refetchPlayerQueue, invalidateRepeatMode } from '../functions/queries'
import { QueuingType } from '../../../enums/queuing-type'
import Toast from 'react-native-toast-message'
import {
getActiveIndex,
getPlayQueue,
setQueueRef,
setShuffled,
setUnshuffledQueue,
} from '../functions'
import { setQueueRef, setShuffled, setUnshuffledQueue } from '../functions'
import { handleDeshuffle, handleShuffle } from '../functions/shuffle'
import JellifyTrack from '@/src/types/JellifyTrack'
import calculateTrackVolume from '../utils/normalization'
@@ -25,6 +19,13 @@ import { RootStackParamList } from '../../../screens/types'
import { useNavigation } from '@react-navigation/native'
import { useAllDownloadedTracks } from '../../../api/queries/download'
import useHapticFeedback from '../../../hooks/use-haptic-feedback'
import { queryClient } from '../../../constants/query-client'
import { QUEUE_QUERY } from '../constants/queries'
import {
ACTIVE_INDEX_QUERY_KEY,
NOW_PLAYING_QUERY_KEY,
PLAY_QUEUE_QUERY_KEY,
} from '../constants/query-keys'
const PLAYER_MUTATION_OPTIONS = {
retry: false,
@@ -211,7 +212,7 @@ export const useLoadNewQueue = () => {
await TrackPlayer.pause()
},
mutationFn: (variables: QueueMutation) => loadQueue({ ...variables, downloadedTracks }),
onSuccess: async (finalStartIndex, { startPlayback }) => {
onSuccess: async ({ finalStartIndex, tracks }, { startPlayback }) => {
console.debug('Successfully loaded new queue')
if (isCasting && remoteClient) {
await TrackPlayer.skip(finalStartIndex)
@@ -222,6 +223,10 @@ export const useLoadNewQueue = () => {
await TrackPlayer.skip(finalStartIndex)
if (startPlayback) await TrackPlayer.play()
queryClient.setQueryData(PLAY_QUEUE_QUERY_KEY, tracks)
queryClient.setQueryData(ACTIVE_INDEX_QUERY_KEY, finalStartIndex)
queryClient.setQueryData(NOW_PLAYING_QUERY_KEY, tracks[finalStartIndex])
},
onError: async (error: Error) => {
trigger('notificationError')
+19 -7
View File
@@ -33,8 +33,12 @@ export default function Tabs({ route, navigation }: TabProps): React.JSX.Element
options={{
title: 'Home',
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialDesignIcons name='jellyfish-outline' color={color} size={size} />
tabBarIcon: ({ color, size, focused }) => (
<MaterialDesignIcons
name={`jellyfish${!focused ? '-outline' : ''}`}
color={color}
size={size}
/>
),
tabBarButtonTestID: 'home-tab-button',
}}
@@ -46,8 +50,12 @@ export default function Tabs({ route, navigation }: TabProps): React.JSX.Element
options={{
title: 'Library',
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialDesignIcons name='music-box-multiple' color={color} size={size} />
tabBarIcon: ({ color, size, focused }) => (
<MaterialDesignIcons
name={`music-box-multiple${!focused ? '-outline' : ''}`}
color={color}
size={size}
/>
),
tabBarButtonTestID: 'library-tab-button',
}}
@@ -72,8 +80,12 @@ export default function Tabs({ route, navigation }: TabProps): React.JSX.Element
options={{
title: 'Discover',
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialDesignIcons name='earth' color={color} size={size} />
tabBarIcon: ({ color, size, focused }) => (
<MaterialDesignIcons
name={`compass${!focused ? '-outline' : ''}`}
color={color}
size={size}
/>
),
tabBarButtonTestID: 'discover-tab-button',
}}
@@ -86,7 +98,7 @@ export default function Tabs({ route, navigation }: TabProps): React.JSX.Element
title: 'Settings',
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialDesignIcons name='dip-switch' color={color} size={size} />
<MaterialDesignIcons name='cogs' color={color} size={size} />
),
tabBarButtonTestID: 'settings-tab-button',
}}
+25 -25
View File
@@ -3394,39 +3394,39 @@
resolved "https://registry.yarnpkg.com/@tamagui/z-index-stack/-/z-index-stack-1.132.23.tgz#a74f06f3b6a6191951f396105f39a10aec0144aa"
integrity sha512-djbRW7FWzuc9bCIVXG00pVa6McM8/H8R4JOL+szxSy1iAo0P2k0OzWfBb+ZbbjTye068fBPGIniq4X7+3huS1Q==
"@tanstack/query-async-storage-persister@^5.87.1":
version "5.87.1"
resolved "https://registry.yarnpkg.com/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.87.1.tgz#2483b6fa846036b65de3bec3d629e1c2ef65758e"
integrity sha512-ASIo9Jv+Tqv80Itadoda9vByD2ODotraaAgY1gB1lCeJQP7lT7Tf8G3od3TW9+0x/x90FqMZvehMJG3RisdzKg==
"@tanstack/query-async-storage-persister@^5.87.4":
version "5.87.4"
resolved "https://registry.yarnpkg.com/@tanstack/query-async-storage-persister/-/query-async-storage-persister-5.87.4.tgz#f4d4255b56ab7305a73b21ec376347446ae1561a"
integrity sha512-O12m5zSpNsMj6RT+Oy5T3JkUZSGhMcd6l6NUOlP6OWVrTvz5rro2f5PQKFo9zjXRnu/lK5lOfr3YY+MApF6png==
dependencies:
"@tanstack/query-core" "5.87.1"
"@tanstack/query-persist-client-core" "5.87.1"
"@tanstack/query-core" "5.87.4"
"@tanstack/query-persist-client-core" "5.87.4"
"@tanstack/query-core@5.87.1":
version "5.87.1"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.87.1.tgz#9b8b9331714749d505a7ceb0ae52b6ad6ae8a461"
integrity sha512-HOFHVvhOCprrWvtccSzc7+RNqpnLlZ5R6lTmngb8aq7b4rc2/jDT0w+vLdQ4lD9bNtQ+/A4GsFXy030Gk4ollA==
"@tanstack/query-core@5.87.4":
version "5.87.4"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.87.4.tgz#1601d05ee5bf611328f2852cec763ed0c9a090cd"
integrity sha512-uNsg6zMxraEPDVO2Bn+F3/ctHi+Zsk+MMpcN8h6P7ozqD088F6mFY5TfGM7zuyIrL7HKpDyu6QHfLWiDxh3cuw==
"@tanstack/query-persist-client-core@5.87.1":
version "5.87.1"
resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.87.1.tgz#97b987d854e3e1ceeb1c8d2994ca887799baf9a0"
integrity sha512-NL3YiHNxyz49N1+97ppKxgMtk8zITxa15Edu8a07w7XLgT/xH1lz5wzElJCGeNbe84rvdbp0/4XKwSiS9rAL9Q==
"@tanstack/query-persist-client-core@5.87.4":
version "5.87.4"
resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.87.4.tgz#d44ed6220ee232db87d02164302ff41edd0415c1"
integrity sha512-71jHVxFvRBjPfiLQ4cJ71sRICCf989s+wCdynmwvAJqW6NgWx7GkdhQC1F7tXb56ZlcK19kQPPa5X2EPqP94wA==
dependencies:
"@tanstack/query-core" "5.87.1"
"@tanstack/query-core" "5.87.4"
"@tanstack/react-query-persist-client@^5.87.1":
version "5.87.1"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.87.1.tgz#206dccb3a684f2d0fe1d1c2a5996d935e96a9ffc"
integrity sha512-S3nJD31YRdv0q0n/IYY9pdeIPkEx319ygHSpDieeTbTFuYxn2b6LMUCNZhPw96ZYi182uwH53Z3KGcTGnWjzYQ==
"@tanstack/react-query-persist-client@^5.87.4":
version "5.87.4"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.87.4.tgz#16ac7c66118d580f3de3a5052d618253cf7263ac"
integrity sha512-RFnkAfYJcQ8nEQUWx0rhcPVPdRmvHAYG+mJwcykiJKVHaFeCuiHaEoW3KseGTBZcliV//UjI07bplAaT2ElR8g==
dependencies:
"@tanstack/query-persist-client-core" "5.87.1"
"@tanstack/query-persist-client-core" "5.87.4"
"@tanstack/react-query@^5.87.1":
version "5.87.1"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.87.1.tgz#074bd2238173f49ef8804a9b6d94f63374828a78"
integrity sha512-YKauf8jfMowgAqcxj96AHs+Ux3m3bWT1oSVKamaRPXSnW2HqSznnTCEkAVqctF1e/W9R/mPcyzzINIgpOH94qg==
"@tanstack/react-query@^5.87.4":
version "5.87.4"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.87.4.tgz#f0b0af38ffab642e8999c156e986dfc384662fe6"
integrity sha512-T5GT/1ZaNsUXf5I3RhcYuT17I4CPlbZgyLxc/ZGv7ciS6esytlbjb3DgUFO6c8JWYMDpdjSWInyGZUErgzqhcA==
dependencies:
"@tanstack/query-core" "5.87.1"
"@tanstack/query-core" "5.87.4"
"@telemetrydeck/sdk@^2.0.4":
version "2.0.4"