diff --git a/src/app/(tabs)/library/album/[key].tsx b/src/app/(tabs)/library/album/[key].tsx index 4dabaf0..1fd767b 100644 --- a/src/app/(tabs)/library/album/[key].tsx +++ b/src/app/(tabs)/library/album/[key].tsx @@ -1,9 +1,9 @@ -import { useCallback, useMemo, useState } from 'react'; -import { BackHandler, View, Pressable, StyleSheet } from 'react-native'; +import { useMemo, useState } from 'react'; +import { View, Pressable, StyleSheet } from 'react-native'; import { Image } from 'expo-image'; import { Ionicons } from '@expo/vector-icons'; import { FlashList } from '@shopify/flash-list'; -import { useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router'; +import { useLocalSearchParams } from 'expo-router'; import { Screen } from '@/components/Screen'; import { Text } from '@/components/Text'; import { AstraLogo } from '@/components/AstraLogo'; @@ -16,15 +16,15 @@ import { playTracks, shuffleTracks } from '@/audio/playbackController'; import { dbTrackToTrack } from '@/library/trackAdapter'; import { albumArtworkSource } from '@/library/artwork'; import { formatDuration } from '@/lib/format'; +import { useLibraryDetailBack } from '@/navigation/useLibraryDetailBack'; import type { DbTrack } from '@/types/library'; export default function AlbumScreen() { - const router = useRouter(); const { key, from } = useLocalSearchParams<{ key: string; from?: string }>(); const albums = useLibraryStore((s) => s.albums); const allTracks = useLibraryStore((s) => s.tracks); const currentPath = usePlayerStore((s) => s.currentTrack?.path); - const openedFromHome = from === 'home'; + const handleBack = useLibraryDetailBack(from); const album = albums.find((entry) => entry.identity_key === key); // Store tracks are ordered artist/album/disc/track, so the filtered slice @@ -41,27 +41,6 @@ export default function AlbumScreen() { void playTracks(tracks.map(dbTrackToTrack), index); }; - const handleBack = useCallback(() => { - if (openedFromHome) { - router.replace('/library'); - return; - } - router.back(); - }, [openedFromHome, router]); - - useFocusEffect( - useCallback(() => { - if (!openedFromHome) return; - - const subscription = BackHandler.addEventListener('hardwareBackPress', () => { - handleBack(); - return true; - }); - - return () => subscription.remove(); - }, [handleBack, openedFromHome]) - ); - return ( diff --git a/src/app/(tabs)/library/artist/[name].tsx b/src/app/(tabs)/library/artist/[name].tsx index 93981be..9a04be8 100644 --- a/src/app/(tabs)/library/artist/[name].tsx +++ b/src/app/(tabs)/library/artist/[name].tsx @@ -17,6 +17,7 @@ import { playTracks, shuffleTracks } from '@/audio/playbackController'; import { dbTrackToTrack } from '@/library/trackAdapter'; import { artworkUri } from '@/library/artwork'; import { buildArtistDetail, type ArtistAlbum, type ArtistDetail } from '@/library/artistDetail'; +import { useLibraryDetailBack } from '@/navigation/useLibraryDetailBack'; import type { DbTrack } from '@/types/library'; type IconName = ComponentProps['name']; @@ -41,7 +42,8 @@ type ArtistPageItem = export default function ArtistScreen() { const router = useRouter(); - const { name = 'Artist' } = useLocalSearchParams<{ name: string }>(); + const { name = 'Artist', from } = useLocalSearchParams<{ name: string; from?: string }>(); + const handleBack = useLibraryDetailBack(from); const { width } = useWindowDimensions(); const allTracks = useLibraryStore((s) => s.tracks); const groupingMode = useSettingsStore((s) => s.artistGroupingMode); @@ -134,7 +136,7 @@ export default function ArtistScreen() { return ( - router.back()} hitSlop={8}> + Library diff --git a/src/app/(tabs)/library/playlist/[id].tsx b/src/app/(tabs)/library/playlist/[id].tsx index de45a12..e6d2263 100644 --- a/src/app/(tabs)/library/playlist/[id].tsx +++ b/src/app/(tabs)/library/playlist/[id].tsx @@ -3,7 +3,7 @@ import { View, Pressable, StyleSheet } from 'react-native'; import { Image } from 'expo-image'; import { Ionicons } from '@expo/vector-icons'; import { FlashList } from '@shopify/flash-list'; -import { useLocalSearchParams, useRouter } from 'expo-router'; +import { useLocalSearchParams } from 'expo-router'; import { Screen } from '@/components/Screen'; import { Text } from '@/components/Text'; import { TrackRow } from '@/components/library/TrackRow'; @@ -16,6 +16,7 @@ import { playTracks, shuffleTracks } from '@/audio/playbackController'; import { dbTrackToTrack } from '@/library/trackAdapter'; import { artworkUri } from '@/library/artwork'; import { formatDuration } from '@/lib/format'; +import { useLibraryDetailBack } from '@/navigation/useLibraryDetailBack'; import type { DbTrack } from '@/types/library'; import type { PlaylistTrackEntry } from '@/types/playlist'; @@ -41,8 +42,8 @@ function MissingRow({ entry, onLongPress }: { entry: PlaylistTrackEntry; onLongP } export default function PlaylistScreen() { - const router = useRouter(); - const { id } = useLocalSearchParams<{ id: string }>(); + const { id, from } = useLocalSearchParams<{ id: string; from?: string }>(); + const handleBack = useLibraryDetailBack(from); const isFavorites = id === 'favorites'; const playlistId = isFavorites ? null : Number(id); @@ -155,7 +156,7 @@ export default function PlaylistScreen() { return ( - router.back()} hitSlop={8}> + Library diff --git a/src/components/search/QuickSearchOverlay.tsx b/src/components/search/QuickSearchOverlay.tsx index 8a0ecba..92d789d 100644 --- a/src/components/search/QuickSearchOverlay.tsx +++ b/src/components/search/QuickSearchOverlay.tsx @@ -830,7 +830,7 @@ function QuickSearchPanel({ if (result.kind === 'album') { router.push({ pathname: '/library/album/[key]', - params: { key: result.album.identity_key }, + params: { key: result.album.identity_key, from: 'search' }, }); return; } @@ -838,17 +838,16 @@ function QuickSearchPanel({ if (result.kind === 'artist') { router.push({ pathname: '/library/artist/[name]', - params: { name: result.artist.artist }, + params: { name: result.artist.artist, from: 'search' }, }); return; } if (result.kind === 'playlist') { - router.push( - result.playlist.id === 'favorites' - ? '/library/playlist/favorites' - : `/library/playlist/${result.playlist.id}` - ); + router.push({ + pathname: '/library/playlist/[id]', + params: { id: result.playlist.id, from: 'search' }, + }); return; } diff --git a/src/navigation/useLibraryDetailBack.ts b/src/navigation/useLibraryDetailBack.ts new file mode 100644 index 0000000..f45dedd --- /dev/null +++ b/src/navigation/useLibraryDetailBack.ts @@ -0,0 +1,37 @@ +import { useCallback } from 'react'; +import { BackHandler } from 'react-native'; +import { useFocusEffect, useRouter } from 'expo-router'; + +type LibraryDetailSource = 'home' | 'search'; + +function shouldReturnToLibraryRoot(from: string | string[] | undefined): from is LibraryDetailSource { + return from === 'home' || from === 'search'; +} + +export function useLibraryDetailBack(from?: string | string[]) { + const router = useRouter(); + const returnToLibraryRoot = shouldReturnToLibraryRoot(from); + + const handleBack = useCallback(() => { + if (returnToLibraryRoot) { + router.replace('/library'); + return; + } + router.back(); + }, [returnToLibraryRoot, router]); + + useFocusEffect( + useCallback(() => { + if (!returnToLibraryRoot) return; + + const subscription = BackHandler.addEventListener('hardwareBackPress', () => { + handleBack(); + return true; + }); + + return () => subscription.remove(); + }, [handleBack, returnToLibraryRoot]) + ); + + return handleBack; +}