mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
improved large album/playlist performance
switched to flatlist for all of those
This commit is contained in:
parent
81b5bfb56b
commit
fac2b62ec2
54
app/components/BackgroundHeaderFlatList.tsx
Normal file
54
app/components/BackgroundHeaderFlatList.tsx
Normal file
@ -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<ItemT> = FlatListProps<ItemT> & {
|
||||||
|
contentMarginTop?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BackgroundHeaderFlatListProp<ItemT> = BackgroundHeaderFlatListPropsBase<ItemT> & {
|
||||||
|
BackgroundComponent: typeof ImageGradientBackground | typeof GradientBackground
|
||||||
|
backgroundProps?: ImageGradientBackgroundProps | GradientBackgroundProps
|
||||||
|
}
|
||||||
|
|
||||||
|
function BackgroundHeaderFlatList<ItemT>(props: BackgroundHeaderFlatListProp<ItemT>) {
|
||||||
|
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 (
|
||||||
|
<FlatList
|
||||||
|
{...props}
|
||||||
|
contentContainerStyle={[props.contentContainerStyle, { minHeight: window.height }]}
|
||||||
|
style={[props.style, styles.list]}
|
||||||
|
ListHeaderComponent={
|
||||||
|
<props.BackgroundComponent position="relative" {...props.backgroundProps}>
|
||||||
|
<View onLayout={headerLayout.onLayout}>{props.ListHeaderComponent}</View>
|
||||||
|
</props.BackgroundComponent>
|
||||||
|
}
|
||||||
|
ListHeaderComponentStyle={[headerStyle]}
|
||||||
|
ListEmptyComponent={<NothingHere style={styles.nothing} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
list: {
|
||||||
|
backgroundColor: colors.gradient.low,
|
||||||
|
},
|
||||||
|
nothing: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default BackgroundHeaderFlatList
|
||||||
@ -3,14 +3,27 @@ import { useWindowDimensions, ViewStyle } from 'react-native'
|
|||||||
import LinearGradient from 'react-native-linear-gradient'
|
import LinearGradient from 'react-native-linear-gradient'
|
||||||
import colorStyles from '@app/styles/colors'
|
import colorStyles from '@app/styles/colors'
|
||||||
|
|
||||||
const GradientBackground: React.FC<{
|
export type GradientBackgroundPropsBase = {
|
||||||
height?: number | string
|
height?: number | string
|
||||||
width?: number | string
|
width?: number | string
|
||||||
position?: 'relative' | 'absolute'
|
position?: 'relative' | 'absolute'
|
||||||
style?: ViewStyle
|
style?: ViewStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GradientBackgroundProps = GradientBackgroundPropsBase & {
|
||||||
colors?: string[]
|
colors?: string[]
|
||||||
locations?: number[]
|
locations?: number[]
|
||||||
}> = ({ height, width, position, style, colors, locations, children }) => {
|
}
|
||||||
|
|
||||||
|
const GradientBackground: React.FC<GradientBackgroundProps> = ({
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
position,
|
||||||
|
style,
|
||||||
|
colors,
|
||||||
|
locations,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
const layout = useWindowDimensions()
|
const layout = useWindowDimensions()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,28 +1,13 @@
|
|||||||
|
import GradientBackground, { GradientBackgroundProps } from '@app/components/GradientBackground'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { FlatList, FlatListProps, StyleSheet, useWindowDimensions } from 'react-native'
|
import BackgroundHeaderFlatList, { BackgroundHeaderFlatListPropsBase } from './BackgroundHeaderFlatList'
|
||||||
import colors from '@app/styles/colors'
|
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
|
||||||
|
|
||||||
function GradientFlatList<ItemT>(props: FlatListProps<ItemT>) {
|
export type GradientFlatListProps<ItemT> = BackgroundHeaderFlatListPropsBase<ItemT> & {
|
||||||
const layout = useWindowDimensions()
|
backgroundProps?: GradientBackgroundProps
|
||||||
|
}
|
||||||
|
|
||||||
const contentContainerStyle = StyleSheet.flatten(props.contentContainerStyle)
|
function GradientFlatList<ItemT>(props: GradientFlatListProps<ItemT>) {
|
||||||
|
return <BackgroundHeaderFlatList BackgroundComponent={GradientBackground} {...props} />
|
||||||
return (
|
|
||||||
<FlatList
|
|
||||||
{...props}
|
|
||||||
style={{
|
|
||||||
...(props.style as any),
|
|
||||||
backgroundColor: colors.gradient.low,
|
|
||||||
}}
|
|
||||||
ListHeaderComponent={() => <GradientBackground position="relative" />}
|
|
||||||
ListHeaderComponentStyle={{
|
|
||||||
marginBottom: -layout.height,
|
|
||||||
marginHorizontal: -(contentContainerStyle.paddingHorizontal || 0),
|
|
||||||
top: -(contentContainerStyle.paddingTop || 0),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GradientFlatList
|
export default GradientFlatList
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { ViewStyle } from 'react-native'
|
|
||||||
import ImageColors from 'react-native-image-colors'
|
import ImageColors from 'react-native-image-colors'
|
||||||
import { AndroidImageColors } from 'react-native-image-colors/lib/typescript/types'
|
import { AndroidImageColors } from 'react-native-image-colors/lib/typescript/types'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
import GradientBackground, { GradientBackgroundPropsBase } from '@app/components/GradientBackground'
|
||||||
|
|
||||||
export type ImageGradientBackgroundProps = {
|
export type ImageGradientBackgroundProps = GradientBackgroundPropsBase & {
|
||||||
height?: number | string
|
|
||||||
width?: number | string
|
|
||||||
position?: 'relative' | 'absolute'
|
|
||||||
style?: ViewStyle
|
|
||||||
imagePath?: string
|
imagePath?: string
|
||||||
onGetColor?: (color: string) => void
|
onGetColor?: (color: string) => void
|
||||||
}
|
}
|
||||||
|
|||||||
13
app/components/ImageGradientFlatList.tsx
Normal file
13
app/components/ImageGradientFlatList.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import BackgroundHeaderFlatList, { BackgroundHeaderFlatListPropsBase } from './BackgroundHeaderFlatList'
|
||||||
|
import ImageGradientBackground, { ImageGradientBackgroundProps } from './ImageGradientBackground'
|
||||||
|
|
||||||
|
export type ImageGradientFlatListProps<ItemT> = BackgroundHeaderFlatListPropsBase<ItemT> & {
|
||||||
|
backgroundProps?: ImageGradientBackgroundProps
|
||||||
|
}
|
||||||
|
|
||||||
|
function ImageGradientFlatList<ItemT>(props: ImageGradientFlatListProps<ItemT>) {
|
||||||
|
return <BackgroundHeaderFlatList BackgroundComponent={ImageGradientBackground} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageGradientFlatList
|
||||||
@ -7,7 +7,7 @@ import colors from '@app/styles/colors'
|
|||||||
import font from '@app/styles/font'
|
import font from '@app/styles/font'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import React, { useCallback } from 'react'
|
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 IconFA5 from 'react-native-vector-icons/FontAwesome5'
|
||||||
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
||||||
import { AlbumContextPressable, ArtistContextPressable, SongContextPressable } from './ContextMenu'
|
import { AlbumContextPressable, ArtistContextPressable, SongContextPressable } from './ContextMenu'
|
||||||
@ -55,7 +55,8 @@ const ListItem: React.FC<{
|
|||||||
showStar?: boolean
|
showStar?: boolean
|
||||||
listStyle?: 'big' | 'small'
|
listStyle?: 'big' | 'small'
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
}> = ({ item, contextId, queueId, onPress, showArt, showStar, subtitle, listStyle }) => {
|
style?: StyleProp<ViewStyle>
|
||||||
|
}> = ({ item, contextId, queueId, onPress, showArt, showStar, subtitle, listStyle, style }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const starred = useStarred(item.id, item.itemType)
|
const starred = useStarred(item.id, item.itemType)
|
||||||
|
|
||||||
@ -156,7 +157,7 @@ const ListItem: React.FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, sizeStyle.container]}>
|
<View style={[styles.container, sizeStyle.container, style]}>
|
||||||
<PressableComponent>
|
<PressableComponent>
|
||||||
{showArt && coverArt}
|
{showArt && coverArt}
|
||||||
<View style={styles.text}>
|
<View style={styles.text}>
|
||||||
|
|||||||
@ -72,7 +72,6 @@ const AlbumsList = () => {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<GradientFlatList
|
<GradientFlatList
|
||||||
contentContainerStyle={styles.listContent}
|
|
||||||
data={list.map(album => ({ album, size, height }))}
|
data={list.map(album => ({ album, size, height }))}
|
||||||
renderItem={AlbumListRenderItem}
|
renderItem={AlbumListRenderItem}
|
||||||
keyExtractor={item => item.album.id}
|
keyExtractor={item => item.album.id}
|
||||||
@ -100,9 +99,6 @@ const AlbumsList = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
listContent: {
|
|
||||||
minHeight: '100%',
|
|
||||||
},
|
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import React, { useEffect, useState } from 'react'
|
|||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
const ArtistRenderItem: React.FC<{ item: Artist }> = ({ item }) => (
|
const ArtistRenderItem: React.FC<{ item: Artist }> = ({ item }) => (
|
||||||
<ListItem item={item} showArt={true} showStar={false} listStyle="big" />
|
<ListItem item={item} showArt={true} showStar={false} listStyle="big" style={styles.listItem} />
|
||||||
)
|
)
|
||||||
|
|
||||||
const filterOptions: OptionData[] = [
|
const filterOptions: OptionData[] = [
|
||||||
@ -44,7 +44,6 @@ const ArtistsList = () => {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<GradientFlatList
|
<GradientFlatList
|
||||||
contentContainerStyle={styles.listContent}
|
|
||||||
data={sortedList}
|
data={sortedList}
|
||||||
renderItem={ArtistRenderItem}
|
renderItem={ArtistRenderItem}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
@ -52,6 +51,7 @@ const ArtistsList = () => {
|
|||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
overScrollMode="never"
|
overScrollMode="never"
|
||||||
windowSize={3}
|
windowSize={3}
|
||||||
|
contentMarginTop={6}
|
||||||
/>
|
/>
|
||||||
<FilterButton
|
<FilterButton
|
||||||
data={filterOptions}
|
data={filterOptions}
|
||||||
@ -71,10 +71,8 @@ const styles = StyleSheet.create({
|
|||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
listContent: {
|
listItem: {
|
||||||
minHeight: '100%',
|
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
paddingTop: 6,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import React from 'react'
|
|||||||
import { StyleSheet } from 'react-native'
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
const PlaylistRenderItem: React.FC<{ item: PlaylistListItem }> = ({ item }) => (
|
const PlaylistRenderItem: React.FC<{ item: PlaylistListItem }> = ({ item }) => (
|
||||||
<ListItem item={item} showArt={true} showStar={false} listStyle="big" />
|
<ListItem item={item} showArt={true} showStar={false} listStyle="big" style={styles.listItem} />
|
||||||
)
|
)
|
||||||
|
|
||||||
const PlaylistsList = () => {
|
const PlaylistsList = () => {
|
||||||
@ -17,7 +17,6 @@ const PlaylistsList = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GradientFlatList
|
<GradientFlatList
|
||||||
contentContainerStyle={styles.listContent}
|
|
||||||
data={list}
|
data={list}
|
||||||
renderItem={PlaylistRenderItem}
|
renderItem={PlaylistRenderItem}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
@ -25,15 +24,14 @@ const PlaylistsList = () => {
|
|||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
overScrollMode="never"
|
overScrollMode="never"
|
||||||
windowSize={5}
|
windowSize={5}
|
||||||
|
contentMarginTop={6}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
listContent: {
|
listItem: {
|
||||||
minHeight: '100%',
|
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
paddingTop: 6,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,49 +1,56 @@
|
|||||||
import GradientScrollView from '@app/components/GradientScrollView'
|
import GradientFlatList from '@app/components/GradientFlatList'
|
||||||
import ListItem from '@app/components/ListItem'
|
import ListItem from '@app/components/ListItem'
|
||||||
import NowPlayingBar from '@app/components/NowPlayingBar'
|
import NowPlayingBar from '@app/components/NowPlayingBar'
|
||||||
import { useSkipTo } from '@app/hooks/trackplayer'
|
import { useSkipTo } from '@app/hooks/trackplayer'
|
||||||
|
import { Song } from '@app/models/music'
|
||||||
import { useStore } from '@app/state/store'
|
import { useStore } from '@app/state/store'
|
||||||
import { selectTrackPlayer } from '@app/state/trackplayer'
|
import { selectTrackPlayer } from '@app/state/trackplayer'
|
||||||
import { selectTrackPlayerMap } from '@app/state/trackplayermap'
|
import { selectTrackPlayerMap } from '@app/state/trackplayermap'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
const SongRenderItem: React.FC<{
|
||||||
|
item: {
|
||||||
|
song: Song
|
||||||
|
i: number
|
||||||
|
onPress: () => void
|
||||||
|
}
|
||||||
|
}> = ({ item }) => (
|
||||||
|
<ListItem
|
||||||
|
item={item.song}
|
||||||
|
queueId={item.i}
|
||||||
|
onPress={item.onPress}
|
||||||
|
showArt={true}
|
||||||
|
style={styles.listItem}
|
||||||
|
subtitle={`${item.song.artist} • ${item.song.album}`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
const NowPlayingQueue = React.memo<{}>(() => {
|
const NowPlayingQueue = React.memo<{}>(() => {
|
||||||
const queue = useStore(selectTrackPlayer.queue)
|
const queue = useStore(selectTrackPlayer.queue)
|
||||||
const mapTrackExtToSong = useStore(selectTrackPlayerMap.mapTrackExtToSong)
|
const mapTrackExtToSong = useStore(selectTrackPlayerMap.mapTrackExtToSong)
|
||||||
const skipTo = useSkipTo()
|
const skipTo = useSkipTo()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.outerContainer}>
|
<View style={styles.container}>
|
||||||
<GradientScrollView style={styles.container}>
|
<GradientFlatList
|
||||||
<View style={styles.content}>
|
data={queue.map(mapTrackExtToSong).map((song, i) => ({ song, i, onPress: () => skipTo(i) }))}
|
||||||
{queue.map(mapTrackExtToSong).map((song, i) => (
|
renderItem={SongRenderItem}
|
||||||
<ListItem
|
keyExtractor={(item, i) => i.toString()}
|
||||||
key={i}
|
overScrollMode="never"
|
||||||
item={song}
|
windowSize={7}
|
||||||
queueId={i}
|
contentMarginTop={10}
|
||||||
onPress={() => skipTo(i)}
|
/>
|
||||||
showArt={true}
|
|
||||||
subtitle={`${song.artist} • ${song.album}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
</GradientScrollView>
|
|
||||||
<NowPlayingBar />
|
<NowPlayingBar />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
outerContainer: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
content: {
|
listItem: {
|
||||||
alignItems: 'center',
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const ResultsListItem: React.FC<{ item: SearchListItemType }> = ({ item }) => {
|
|||||||
showStar={false}
|
showStar={false}
|
||||||
listStyle="small"
|
listStyle="small"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
style={styles.listItem}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -69,25 +70,24 @@ const SearchResultsView: React.FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GradientFlatList
|
<GradientFlatList
|
||||||
contentContainerStyle={styles.listContent}
|
|
||||||
data={list}
|
data={list}
|
||||||
renderItem={SearchResultsRenderItem}
|
renderItem={SearchResultsRenderItem}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={(item, i) => i.toString()}
|
||||||
onRefresh={refresh}
|
onRefresh={refresh}
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
overScrollMode="never"
|
overScrollMode="never"
|
||||||
onEndReached={fetchNextPage}
|
onEndReached={fetchNextPage}
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
onEndReachedThreshold={2}
|
onEndReachedThreshold={2}
|
||||||
|
contentMarginTop={6}
|
||||||
|
windowSize={5}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
listContent: {
|
listItem: {
|
||||||
minHeight: '100%',
|
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
paddingTop: 6,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import CoverArt from '@app/components/CoverArt'
|
import CoverArt from '@app/components/CoverArt'
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
import GradientBackground from '@app/components/GradientBackground'
|
||||||
import HeaderBar from '@app/components/HeaderBar'
|
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 ListItem from '@app/components/ListItem'
|
||||||
import ListPlayerControls from '@app/components/ListPlayerControls'
|
import ListPlayerControls from '@app/components/ListPlayerControls'
|
||||||
import NothingHere from '@app/components/NothingHere'
|
|
||||||
import { useCoverArtFile } from '@app/hooks/cache'
|
import { useCoverArtFile } from '@app/hooks/cache'
|
||||||
import { useAlbumWithSongs, usePlaylistWithSongs } from '@app/hooks/music'
|
import { useAlbumWithSongs, usePlaylistWithSongs } from '@app/hooks/music'
|
||||||
import { AlbumWithSongs, PlaylistWithSongs, Song } from '@app/models/music'
|
import { AlbumWithSongs, PlaylistWithSongs, Song } from '@app/models/music'
|
||||||
@ -23,15 +22,42 @@ const SongListDetailsFallback = React.memo(() => (
|
|||||||
</GradientBackground>
|
</GradientBackground>
|
||||||
))
|
))
|
||||||
|
|
||||||
const Songs = React.memo<{
|
const SongRenderItem: React.FC<{
|
||||||
songs: Song[]
|
item: {
|
||||||
name: string
|
song: Song
|
||||||
|
contextId?: string
|
||||||
|
queueId?: number
|
||||||
|
subtitle?: string
|
||||||
|
onPress?: () => void
|
||||||
|
showArt?: boolean
|
||||||
|
}
|
||||||
|
}> = ({ item }) => (
|
||||||
|
<ListItem
|
||||||
|
item={item.song}
|
||||||
|
contextId={item.contextId}
|
||||||
|
queueId={item.queueId}
|
||||||
|
subtitle={item.subtitle}
|
||||||
|
onPress={item.onPress}
|
||||||
|
showArt={item.showArt}
|
||||||
|
style={styles.listItem}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const SongListDetails = React.memo<{
|
||||||
|
title: string
|
||||||
type: SongListType
|
type: SongListType
|
||||||
itemId: string
|
songList?: AlbumWithSongs | PlaylistWithSongs
|
||||||
}>(({ songs, name, type, itemId }) => {
|
subtitle?: string
|
||||||
|
}>(({ title, songList, subtitle, type }) => {
|
||||||
|
const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail')
|
||||||
|
const [headerColor, setHeaderColor] = useState<string | undefined>(undefined)
|
||||||
const setQueue = useStore(selectTrackPlayer.setQueue)
|
const setQueue = useStore(selectTrackPlayer.setQueue)
|
||||||
|
|
||||||
const _songs = [...songs]
|
if (!songList) {
|
||||||
|
return <SongListDetailsFallback />
|
||||||
|
}
|
||||||
|
|
||||||
|
const _songs = [...songList.songs]
|
||||||
let typeName = ''
|
let typeName = ''
|
||||||
|
|
||||||
if (type === 'album') {
|
if (type === 'album') {
|
||||||
@ -49,46 +75,6 @@ const Songs = React.memo<{
|
|||||||
typeName = 'Playlist'
|
typeName = 'Playlist'
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ListPlayerControls
|
|
||||||
style={styles.controls}
|
|
||||||
songs={_songs}
|
|
||||||
typeName={typeName}
|
|
||||||
queueName={name}
|
|
||||||
queueContextId={itemId}
|
|
||||||
queueContextType={type}
|
|
||||||
/>
|
|
||||||
<View style={styles.songs}>
|
|
||||||
{_songs.map((s, i) => (
|
|
||||||
<ListItem
|
|
||||||
key={i}
|
|
||||||
item={s}
|
|
||||||
contextId={itemId}
|
|
||||||
queueId={i}
|
|
||||||
subtitle={s.artist}
|
|
||||||
onPress={() => setQueue(_songs, name, type, itemId, i)}
|
|
||||||
showArt={false}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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<string | undefined>(undefined)
|
|
||||||
|
|
||||||
if (!songList) {
|
|
||||||
return <SongListDetailsFallback />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<HeaderBar
|
<HeaderBar
|
||||||
@ -96,21 +82,43 @@ const SongListDetails = React.memo<{
|
|||||||
title={title}
|
title={title}
|
||||||
contextItem={songList.itemType === 'album' ? songList : undefined}
|
contextItem={songList.itemType === 'album' ? songList : undefined}
|
||||||
/>
|
/>
|
||||||
<ImageGradientScrollView
|
<ImageGradientFlatList
|
||||||
imagePath={coverArtFile?.file?.path}
|
data={_songs.map((s, i) => ({
|
||||||
style={styles.container}
|
song: s,
|
||||||
onGetColor={setHeaderColor}>
|
contextId: songList.id,
|
||||||
<View style={styles.content}>
|
queueId: i,
|
||||||
<CoverArt type="cover" size="original" coverArt={songList.coverArt} style={styles.cover} />
|
subtitle: s.artist,
|
||||||
<Text style={styles.title}>{songList.name}</Text>
|
onPress: () => setQueue(_songs, songList.name, type, songList.id, i),
|
||||||
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
|
showArt: songList.itemType === 'playlist',
|
||||||
{songList.songs.length > 0 ? (
|
}))}
|
||||||
<Songs songs={songList.songs} name={songList.name} type={type} itemId={songList.id} />
|
renderItem={SongRenderItem}
|
||||||
) : (
|
keyExtractor={(item, i) => i.toString()}
|
||||||
<NothingHere height={300} width={250} />
|
backgroundProps={{
|
||||||
)}
|
imagePath: coverArtFile?.file?.path,
|
||||||
</View>
|
style: styles.container,
|
||||||
</ImageGradientScrollView>
|
onGetColor: setHeaderColor,
|
||||||
|
}}
|
||||||
|
overScrollMode="never"
|
||||||
|
windowSize={7}
|
||||||
|
contentMarginTop={26}
|
||||||
|
ListHeaderComponent={
|
||||||
|
<View style={styles.content}>
|
||||||
|
<CoverArt type="cover" size="original" coverArt={songList.coverArt} style={styles.cover} />
|
||||||
|
<Text style={styles.title}>{songList.name}</Text>
|
||||||
|
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
|
||||||
|
{songList.songs.length > 0 && (
|
||||||
|
<ListPlayerControls
|
||||||
|
style={styles.controls}
|
||||||
|
songs={_songs}
|
||||||
|
typeName={typeName}
|
||||||
|
queueName={songList.name}
|
||||||
|
queueContextId={songList.id}
|
||||||
|
queueContextType={type}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -185,6 +193,9 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingTop: 100,
|
paddingTop: 100,
|
||||||
},
|
},
|
||||||
|
listItem: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default SongListView
|
export default SongListView
|
||||||
|
|||||||
@ -135,8 +135,7 @@ export const createMusicMapSlice = (set: SetState<Store>, get: GetState<Store>):
|
|||||||
mapPlaylistWithSongs: async playlist => {
|
mapPlaylistWithSongs: async playlist => {
|
||||||
return {
|
return {
|
||||||
...get().mapPlaylistListItem(playlist),
|
...get().mapPlaylistListItem(playlist),
|
||||||
// passing cover art here is a temp fix to improve large playlist performance
|
songs: await get().mapChildrenToSongs(playlist.songs),
|
||||||
songs: await get().mapChildrenToSongs(playlist.songs, playlist.coverArt),
|
|
||||||
coverArt: playlist.coverArt,
|
coverArt: playlist.coverArt,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user