diff --git a/app/components/BackgroundHeaderFlatList.tsx b/app/components/BackgroundHeaderFlatList.tsx new file mode 100644 index 0000000..87c07f8 --- /dev/null +++ b/app/components/BackgroundHeaderFlatList.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import { FlatList, FlatListProps, useWindowDimensions, View, StyleSheet } from 'react-native' +import colors from '@app/styles/colors' +import GradientBackground, { GradientBackgroundProps } from '@app/components/GradientBackground' +import { useLayout } from '@react-native-community/hooks' +import NothingHere from './NothingHere' +import ImageGradientBackground, { ImageGradientBackgroundProps } from './ImageGradientBackground' + +export type BackgroundHeaderFlatListPropsBase = FlatListProps & { + contentMarginTop?: number +} + +export type BackgroundHeaderFlatListProp = BackgroundHeaderFlatListPropsBase & { + BackgroundComponent: typeof ImageGradientBackground | typeof GradientBackground + backgroundProps?: ImageGradientBackgroundProps | GradientBackgroundProps +} + +function BackgroundHeaderFlatList(props: BackgroundHeaderFlatListProp) { + const window = useWindowDimensions() + const headerLayout = useLayout() + + let marginBottom = -window.height + (props.contentMarginTop || 0) + if (props.ListHeaderComponent) { + marginBottom += headerLayout.height || window.height + } + + const headerStyle = { marginBottom } + + return ( + + {props.ListHeaderComponent} + + } + ListHeaderComponentStyle={[headerStyle]} + ListEmptyComponent={} + /> + ) +} + +const styles = StyleSheet.create({ + list: { + backgroundColor: colors.gradient.low, + }, + nothing: { + width: '100%', + }, +}) + +export default BackgroundHeaderFlatList diff --git a/app/components/GradientBackground.tsx b/app/components/GradientBackground.tsx index 4491a74..a16e1d2 100644 --- a/app/components/GradientBackground.tsx +++ b/app/components/GradientBackground.tsx @@ -3,14 +3,27 @@ import { useWindowDimensions, ViewStyle } from 'react-native' import LinearGradient from 'react-native-linear-gradient' import colorStyles from '@app/styles/colors' -const GradientBackground: React.FC<{ +export type GradientBackgroundPropsBase = { height?: number | string width?: number | string position?: 'relative' | 'absolute' style?: ViewStyle +} + +export type GradientBackgroundProps = GradientBackgroundPropsBase & { colors?: string[] locations?: number[] -}> = ({ height, width, position, style, colors, locations, children }) => { +} + +const GradientBackground: React.FC = ({ + height, + width, + position, + style, + colors, + locations, + children, +}) => { const layout = useWindowDimensions() return ( diff --git a/app/components/GradientFlatList.tsx b/app/components/GradientFlatList.tsx index f6d85b8..953c8cd 100644 --- a/app/components/GradientFlatList.tsx +++ b/app/components/GradientFlatList.tsx @@ -1,28 +1,13 @@ +import GradientBackground, { GradientBackgroundProps } from '@app/components/GradientBackground' import React from 'react' -import { FlatList, FlatListProps, StyleSheet, useWindowDimensions } from 'react-native' -import colors from '@app/styles/colors' -import GradientBackground from '@app/components/GradientBackground' +import BackgroundHeaderFlatList, { BackgroundHeaderFlatListPropsBase } from './BackgroundHeaderFlatList' -function GradientFlatList(props: FlatListProps) { - const layout = useWindowDimensions() +export type GradientFlatListProps = BackgroundHeaderFlatListPropsBase & { + backgroundProps?: GradientBackgroundProps +} - const contentContainerStyle = StyleSheet.flatten(props.contentContainerStyle) - - return ( - } - ListHeaderComponentStyle={{ - marginBottom: -layout.height, - marginHorizontal: -(contentContainerStyle.paddingHorizontal || 0), - top: -(contentContainerStyle.paddingTop || 0), - }} - /> - ) +function GradientFlatList(props: GradientFlatListProps) { + return } export default GradientFlatList diff --git a/app/components/ImageGradientBackground.tsx b/app/components/ImageGradientBackground.tsx index 15bf056..07d41c5 100644 --- a/app/components/ImageGradientBackground.tsx +++ b/app/components/ImageGradientBackground.tsx @@ -1,16 +1,11 @@ import { useNavigation } from '@react-navigation/native' import React, { useEffect, useState } from 'react' -import { ViewStyle } from 'react-native' import ImageColors from 'react-native-image-colors' import { AndroidImageColors } from 'react-native-image-colors/lib/typescript/types' import colors from '@app/styles/colors' -import GradientBackground from '@app/components/GradientBackground' +import GradientBackground, { GradientBackgroundPropsBase } from '@app/components/GradientBackground' -export type ImageGradientBackgroundProps = { - height?: number | string - width?: number | string - position?: 'relative' | 'absolute' - style?: ViewStyle +export type ImageGradientBackgroundProps = GradientBackgroundPropsBase & { imagePath?: string onGetColor?: (color: string) => void } diff --git a/app/components/ImageGradientFlatList.tsx b/app/components/ImageGradientFlatList.tsx new file mode 100644 index 0000000..c10f706 --- /dev/null +++ b/app/components/ImageGradientFlatList.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import BackgroundHeaderFlatList, { BackgroundHeaderFlatListPropsBase } from './BackgroundHeaderFlatList' +import ImageGradientBackground, { ImageGradientBackgroundProps } from './ImageGradientBackground' + +export type ImageGradientFlatListProps = BackgroundHeaderFlatListPropsBase & { + backgroundProps?: ImageGradientBackgroundProps +} + +function ImageGradientFlatList(props: ImageGradientFlatListProps) { + return +} + +export default ImageGradientFlatList diff --git a/app/components/ListItem.tsx b/app/components/ListItem.tsx index b25ed23..0e36e1a 100644 --- a/app/components/ListItem.tsx +++ b/app/components/ListItem.tsx @@ -7,7 +7,7 @@ import colors from '@app/styles/colors' import font from '@app/styles/font' import { useNavigation } from '@react-navigation/native' import React, { useCallback } from 'react' -import { StyleSheet, Text, View } from 'react-native' +import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native' import IconFA5 from 'react-native-vector-icons/FontAwesome5' import IconMat from 'react-native-vector-icons/MaterialIcons' import { AlbumContextPressable, ArtistContextPressable, SongContextPressable } from './ContextMenu' @@ -55,7 +55,8 @@ const ListItem: React.FC<{ showStar?: boolean listStyle?: 'big' | 'small' subtitle?: string -}> = ({ item, contextId, queueId, onPress, showArt, showStar, subtitle, listStyle }) => { + style?: StyleProp +}> = ({ item, contextId, queueId, onPress, showArt, showStar, subtitle, listStyle, style }) => { const navigation = useNavigation() const starred = useStarred(item.id, item.itemType) @@ -156,7 +157,7 @@ const ListItem: React.FC<{ } return ( - + {showArt && coverArt} diff --git a/app/screens/LibraryAlbums.tsx b/app/screens/LibraryAlbums.tsx index 2d5ffc0..351a40d 100644 --- a/app/screens/LibraryAlbums.tsx +++ b/app/screens/LibraryAlbums.tsx @@ -72,7 +72,6 @@ const AlbumsList = () => { return ( ({ album, size, height }))} renderItem={AlbumListRenderItem} keyExtractor={item => item.album.id} @@ -100,9 +99,6 @@ const AlbumsList = () => { } const styles = StyleSheet.create({ - listContent: { - minHeight: '100%', - }, container: { flex: 1, }, diff --git a/app/screens/LibraryArtists.tsx b/app/screens/LibraryArtists.tsx index cdd290f..9f4f1bb 100644 --- a/app/screens/LibraryArtists.tsx +++ b/app/screens/LibraryArtists.tsx @@ -11,7 +11,7 @@ import React, { useEffect, useState } from 'react' import { StyleSheet, View } from 'react-native' const ArtistRenderItem: React.FC<{ item: Artist }> = ({ item }) => ( - + ) const filterOptions: OptionData[] = [ @@ -44,7 +44,6 @@ const ArtistsList = () => { return ( item.id} @@ -52,6 +51,7 @@ const ArtistsList = () => { refreshing={refreshing} overScrollMode="never" windowSize={3} + contentMarginTop={6} /> = ({ item }) => ( - + ) const PlaylistsList = () => { @@ -17,7 +17,6 @@ const PlaylistsList = () => { return ( item.id} @@ -25,15 +24,14 @@ const PlaylistsList = () => { refreshing={refreshing} overScrollMode="never" windowSize={5} + contentMarginTop={6} /> ) } const styles = StyleSheet.create({ - listContent: { - minHeight: '100%', + listItem: { paddingHorizontal: 10, - paddingTop: 6, }, }) diff --git a/app/screens/NowPlayingQueue.tsx b/app/screens/NowPlayingQueue.tsx index ebcf30f..247f0a7 100644 --- a/app/screens/NowPlayingQueue.tsx +++ b/app/screens/NowPlayingQueue.tsx @@ -1,49 +1,56 @@ -import GradientScrollView from '@app/components/GradientScrollView' +import GradientFlatList from '@app/components/GradientFlatList' import ListItem from '@app/components/ListItem' import NowPlayingBar from '@app/components/NowPlayingBar' import { useSkipTo } from '@app/hooks/trackplayer' +import { Song } from '@app/models/music' import { useStore } from '@app/state/store' import { selectTrackPlayer } from '@app/state/trackplayer' import { selectTrackPlayerMap } from '@app/state/trackplayermap' import React from 'react' import { StyleSheet, View } from 'react-native' +const SongRenderItem: React.FC<{ + item: { + song: Song + i: number + onPress: () => void + } +}> = ({ item }) => ( + +) + const NowPlayingQueue = React.memo<{}>(() => { const queue = useStore(selectTrackPlayer.queue) const mapTrackExtToSong = useStore(selectTrackPlayerMap.mapTrackExtToSong) const skipTo = useSkipTo() return ( - - - - {queue.map(mapTrackExtToSong).map((song, i) => ( - skipTo(i)} - showArt={true} - subtitle={`${song.artist} • ${song.album}`} - /> - ))} - - + + ({ song, i, onPress: () => skipTo(i) }))} + renderItem={SongRenderItem} + keyExtractor={(item, i) => i.toString()} + overScrollMode="never" + windowSize={7} + contentMarginTop={10} + /> ) }) const styles = StyleSheet.create({ - outerContainer: { - flex: 1, - }, container: { flex: 1, }, - content: { - alignItems: 'center', - paddingTop: 10, + listItem: { paddingHorizontal: 20, }, }) diff --git a/app/screens/SearchResultsView.tsx b/app/screens/SearchResultsView.tsx index bfa8b45..da53ff4 100644 --- a/app/screens/SearchResultsView.tsx +++ b/app/screens/SearchResultsView.tsx @@ -28,6 +28,7 @@ const ResultsListItem: React.FC<{ item: SearchListItemType }> = ({ item }) => { showStar={false} listStyle="small" onPress={onPress} + style={styles.listItem} /> ) } @@ -69,25 +70,24 @@ const SearchResultsView: React.FC<{ return ( item.id} + keyExtractor={(item, i) => i.toString()} onRefresh={refresh} refreshing={refreshing} overScrollMode="never" onEndReached={fetchNextPage} removeClippedSubviews={true} onEndReachedThreshold={2} + contentMarginTop={6} + windowSize={5} /> ) } const styles = StyleSheet.create({ - listContent: { - minHeight: '100%', + listItem: { paddingHorizontal: 10, - paddingTop: 6, }, }) diff --git a/app/screens/SongListView.tsx b/app/screens/SongListView.tsx index 8a34b8b..dd51547 100644 --- a/app/screens/SongListView.tsx +++ b/app/screens/SongListView.tsx @@ -1,10 +1,9 @@ import CoverArt from '@app/components/CoverArt' import GradientBackground from '@app/components/GradientBackground' import HeaderBar from '@app/components/HeaderBar' -import ImageGradientScrollView from '@app/components/ImageGradientScrollView' +import ImageGradientFlatList from '@app/components/ImageGradientFlatList' import ListItem from '@app/components/ListItem' import ListPlayerControls from '@app/components/ListPlayerControls' -import NothingHere from '@app/components/NothingHere' import { useCoverArtFile } from '@app/hooks/cache' import { useAlbumWithSongs, usePlaylistWithSongs } from '@app/hooks/music' import { AlbumWithSongs, PlaylistWithSongs, Song } from '@app/models/music' @@ -23,15 +22,42 @@ const SongListDetailsFallback = React.memo(() => ( )) -const Songs = React.memo<{ - songs: Song[] - name: string +const SongRenderItem: React.FC<{ + item: { + song: Song + contextId?: string + queueId?: number + subtitle?: string + onPress?: () => void + showArt?: boolean + } +}> = ({ item }) => ( + +) + +const SongListDetails = React.memo<{ + title: string type: SongListType - itemId: string -}>(({ songs, name, type, itemId }) => { + songList?: AlbumWithSongs | PlaylistWithSongs + subtitle?: string +}>(({ title, songList, subtitle, type }) => { + const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail') + const [headerColor, setHeaderColor] = useState(undefined) const setQueue = useStore(selectTrackPlayer.setQueue) - const _songs = [...songs] + if (!songList) { + return + } + + const _songs = [...songList.songs] let typeName = '' if (type === 'album') { @@ -49,46 +75,6 @@ const Songs = React.memo<{ typeName = 'Playlist' } - return ( - <> - - - {_songs.map((s, i) => ( - setQueue(_songs, name, type, itemId, i)} - showArt={false} - /> - ))} - - - ) -}) - -const SongListDetails = React.memo<{ - title: string - type: SongListType - songList?: AlbumWithSongs | PlaylistWithSongs - subtitle?: string -}>(({ title, songList, subtitle, type }) => { - const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail') - const [headerColor, setHeaderColor] = useState(undefined) - - if (!songList) { - return - } - return ( - - - - {songList.name} - {subtitle ? {subtitle} : <>} - {songList.songs.length > 0 ? ( - - ) : ( - - )} - - + ({ + song: s, + contextId: songList.id, + queueId: i, + subtitle: s.artist, + onPress: () => setQueue(_songs, songList.name, type, songList.id, i), + showArt: songList.itemType === 'playlist', + }))} + renderItem={SongRenderItem} + keyExtractor={(item, i) => i.toString()} + backgroundProps={{ + imagePath: coverArtFile?.file?.path, + style: styles.container, + onGetColor: setHeaderColor, + }} + overScrollMode="never" + windowSize={7} + contentMarginTop={26} + ListHeaderComponent={ + + + {songList.name} + {subtitle ? {subtitle} : <>} + {songList.songs.length > 0 && ( + + )} + + } + /> ) }) @@ -185,6 +193,9 @@ const styles = StyleSheet.create({ alignItems: 'center', paddingTop: 100, }, + listItem: { + paddingHorizontal: 20, + }, }) export default SongListView diff --git a/app/state/musicmap.ts b/app/state/musicmap.ts index db6d3a1..a8b4601 100644 --- a/app/state/musicmap.ts +++ b/app/state/musicmap.ts @@ -135,8 +135,7 @@ export const createMusicMapSlice = (set: SetState, get: GetState): mapPlaylistWithSongs: async playlist => { return { ...get().mapPlaylistListItem(playlist), - // passing cover art here is a temp fix to improve large playlist performance - songs: await get().mapChildrenToSongs(playlist.songs, playlist.coverArt), + songs: await get().mapChildrenToSongs(playlist.songs), coverArt: playlist.coverArt, } },