Files
Jellify/App.tsx
skalthoff 238dd0340a Fixing Le Bugers: UI Polish & Performance Tuning (#724)
* fix: update sort icon name and label in ArtistTabBar

* fix: optimize image URL generation and improve performance in Playlists and Tracks components

* homescreen improvements

* deduplicate tracks in FrequentlyPlayedTracks and RecentlyPlayed components

* enhance storage management with versioning and slimmed track persistence

* refactor HorizontalCardList to allow customizable estimatedItemSize

* update sort button label in ArtistTabBar for clarity

* refactor media info fetching and improve search debounce logic

* refactor navigation parameters in artist and track components for simplicity

* refactor PlayPauseButton to manage optimistic UI state and improve playback handling

* Cut queue remap churn, speed lyric lookup, clean cast listener cleanup, and avoid redundant HEADs on downloads

* Revert "Cut queue remap churn, speed lyric lookup, clean cast listener cleanup, and avoid redundant HEADs on downloads"

This reverts commit 1c63b748b6.

* Cut queue remap churn, speed lyric lookup, clean cast listener cleanup, and avoid redundant HEADs on downloads

* Revert "Cut queue remap churn, speed lyric lookup, clean cast listener cleanup, and avoid redundant HEADs on downloads"

This reverts commit f9e0e82e57.

* Reapply "Cut queue remap churn, speed lyric lookup, clean cast listener cleanup, and avoid redundant HEADs on downloads"

This reverts commit 6710d3404c.

* Update project configuration: refine build phases, adjust code signing identity, and format flags

* Fix TypeScript errors in Search component and improve playback state handler in Player queries

* Refactor ItemRow component to accept queueName prop and update queue handling

* lot's o fixes to item cards and item rows

* memoize tracks component

* fix jest

* simplify navigation in FrequentArtists, FrequentlyPlayedTracks, RecentArtists, and RecentlyPlayed components

* Update axios version and enhance image handling options in components

* Enhance ItemImage component with imageOptions for better image handling in PlayerHeader and Miniplayer

* moves buffers to player config

---------

Co-authored-by: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com>
Co-authored-by: Violet Caulfield <violet@cosmonautical.cloud>
2025-12-03 20:07:30 -06:00

135 lines
4.3 KiB
TypeScript

import './gesture-handler'
import React, { useEffect, useRef, useState } from 'react'
import 'react-native-url-polyfill/auto'
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
import Jellify from './src/components/jellify'
import { TamaguiProvider } from 'tamagui'
import { LogBox, Platform, useColorScheme } from 'react-native'
import jellifyConfig from './tamagui.config'
import { queryClientPersister } from './src/constants/storage'
import { ONE_DAY, queryClient } from './src/constants/query-client'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import TrackPlayer, {
AndroidAudioContentType,
AppKilledPlaybackBehavior,
IOSCategory,
IOSCategoryOptions,
} from 'react-native-track-player'
import { CAPABILITIES } from './src/player/constants'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { NavigationContainer } from '@react-navigation/native'
import { JellifyDarkTheme, JellifyLightTheme, JellifyOLEDTheme } from './src/components/theme'
import { requestStoragePermission } from './src/utils/permisson-helpers'
import ErrorBoundary from './src/components/ErrorBoundary'
import OTAUpdateScreen from './src/components/OtaUpdates'
import { usePerformanceMonitor } from './src/hooks/use-performance-monitor'
import navigationRef from './navigation'
import { BUFFERS, PROGRESS_UPDATE_EVENT_INTERVAL } from './src/player/config'
import { useThemeSetting } from './src/stores/settings/app'
LogBox.ignoreAllLogs()
export default function App(): React.JSX.Element {
// Add performance monitoring to track app-level re-renders
const performanceMetrics = usePerformanceMonitor('App', 3)
const [playerIsReady, setPlayerIsReady] = useState<boolean>(false)
const playerInitializedRef = useRef<boolean>(false)
useEffect(() => {
// Guard against double initialization (React StrictMode, hot reload)
if (playerInitializedRef.current) return
playerInitializedRef.current = true
TrackPlayer.setupPlayer({
autoHandleInterruptions: true,
iosCategory: IOSCategory.Playback,
iosCategoryOptions: [
IOSCategoryOptions.AllowAirPlay,
IOSCategoryOptions.AllowBluetooth,
],
androidAudioContentType: AndroidAudioContentType.Music,
minBuffer: 30, // 30 seconds minimum buffer
...BUFFERS,
})
.then(() =>
TrackPlayer.updateOptions({
capabilities: CAPABILITIES,
notificationCapabilities: CAPABILITIES,
// Reduced interval for smoother progress tracking and earlier prefetch detection
progressUpdateEventInterval: PROGRESS_UPDATE_EVENT_INTERVAL,
// Stop playback and remove notification when app is killed to prevent battery drain
android: {
appKilledPlaybackBehavior:
AppKilledPlaybackBehavior.StopPlaybackAndRemoveNotification,
},
}),
)
.catch((error) => {
// Player may already be initialized (e.g., after hot reload)
// This is expected and not a fatal error
console.log('[TrackPlayer] Setup caught:', error?.message ?? error)
})
.finally(() => {
setPlayerIsReady(true)
requestStoragePermission()
})
}, []) // Empty deps - only run once on mount
const [reloader, setReloader] = useState(0)
const handleRetry = () => setReloader((r) => r + 1)
return (
<React.StrictMode>
<SafeAreaProvider>
<OTAUpdateScreen />
<ErrorBoundary reloader={reloader} onRetry={handleRetry}>
<PersistQueryClientProvider
client={queryClient}
persistOptions={{
persister: queryClientPersister,
/**
* Maximum query data age of one day
*/
maxAge: ONE_DAY,
}}
>
<Container playerIsReady={playerIsReady} />
</PersistQueryClientProvider>
</ErrorBoundary>
</SafeAreaProvider>
</React.StrictMode>
)
}
function Container({ playerIsReady }: { playerIsReady: boolean }): React.JSX.Element {
const [theme] = useThemeSetting()
const isDarkMode = useColorScheme() === 'dark'
return (
<NavigationContainer
ref={navigationRef}
theme={
theme === 'system'
? isDarkMode
? JellifyDarkTheme
: JellifyLightTheme
: theme === 'dark'
? JellifyDarkTheme
: theme === 'oled'
? JellifyOLEDTheme
: JellifyLightTheme
}
>
<GestureHandlerRootView>
<TamaguiProvider config={jellifyConfig}>
{playerIsReady && <Jellify />}
</TamaguiProvider>
</GestureHandlerRootView>
</NavigationContainer>
)
}