mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 17:39:27 +01:00
added context menu for album view
This commit is contained in:
parent
12cbe842ce
commit
e69555f05c
@ -8,14 +8,48 @@ import Animated from 'react-native-reanimated'
|
|||||||
import PressableOpacity from './PressableOpacity'
|
import PressableOpacity from './PressableOpacity'
|
||||||
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
||||||
import { ReactComponentLike } from 'prop-types'
|
import { ReactComponentLike } from 'prop-types'
|
||||||
import { Song } from '@app/models/music'
|
import { AlbumListItem, Song } from '@app/models/music'
|
||||||
import { NowPlayingContextPressable } from './ContextMenu'
|
import { AlbumContextPressable, NowPlayingContextPressable } from './ContextMenu'
|
||||||
|
|
||||||
|
export type HeaderContextItem = Song | AlbumListItem
|
||||||
|
|
||||||
|
const More = React.memo<{ contextItem?: HeaderContextItem }>(({ contextItem }) => {
|
||||||
|
let context: JSX.Element
|
||||||
|
switch (contextItem?.itemType) {
|
||||||
|
case 'song':
|
||||||
|
context = (
|
||||||
|
<NowPlayingContextPressable
|
||||||
|
menuStyle={styles.icons}
|
||||||
|
triggerWrapperStyle={styles.icons}
|
||||||
|
song={contextItem}
|
||||||
|
triggerOnLongPress={false}>
|
||||||
|
<IconMat name="more-vert" color="white" size={25} />
|
||||||
|
</NowPlayingContextPressable>
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'album':
|
||||||
|
context = (
|
||||||
|
<AlbumContextPressable
|
||||||
|
menuStyle={styles.icons}
|
||||||
|
triggerWrapperStyle={styles.icons}
|
||||||
|
album={contextItem}
|
||||||
|
triggerOnLongPress={false}>
|
||||||
|
<IconMat name="more-vert" color="white" size={25} />
|
||||||
|
</AlbumContextPressable>
|
||||||
|
)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
context = <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
})
|
||||||
|
|
||||||
const HeaderBar = React.memo<{
|
const HeaderBar = React.memo<{
|
||||||
title?: string
|
title?: string
|
||||||
headerStyle?: Animated.AnimatedStyleProp<ViewStyle> | Animated.AnimatedStyleProp<ViewStyle>[]
|
headerStyle?: Animated.AnimatedStyleProp<ViewStyle> | Animated.AnimatedStyleProp<ViewStyle>[]
|
||||||
HeaderCenter?: ReactComponentLike
|
HeaderCenter?: ReactComponentLike
|
||||||
contextItem?: Song
|
contextItem?: HeaderContextItem
|
||||||
}>(({ title, headerStyle, HeaderCenter, contextItem }) => {
|
}>(({ title, headerStyle, HeaderCenter, contextItem }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
@ -25,20 +59,6 @@ const HeaderBar = React.memo<{
|
|||||||
|
|
||||||
const _headerStyle = Array.isArray(headerStyle) ? headerStyle : [headerStyle]
|
const _headerStyle = Array.isArray(headerStyle) ? headerStyle : [headerStyle]
|
||||||
|
|
||||||
const moreIcon = <IconMat name="more-vert" color="white" size={25} />
|
|
||||||
let more = <></>
|
|
||||||
if (contextItem) {
|
|
||||||
more = (
|
|
||||||
<NowPlayingContextPressable
|
|
||||||
menuStyle={styles.icons}
|
|
||||||
triggerWrapperStyle={styles.icons}
|
|
||||||
song={contextItem}
|
|
||||||
triggerOnLongPress={false}>
|
|
||||||
{moreIcon}
|
|
||||||
</NowPlayingContextPressable>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.container, ..._headerStyle]}>
|
<Animated.View style={[styles.container, ..._headerStyle]}>
|
||||||
<PressableOpacity onPress={back} style={styles.icons}>
|
<PressableOpacity onPress={back} style={styles.icons}>
|
||||||
@ -53,7 +73,7 @@ const HeaderBar = React.memo<{
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{more}
|
<More contextItem={contextItem} />
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,13 +6,24 @@ import { AndroidImageColors } from 'react-native-image-colors/lib/typescript/typ
|
|||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
import GradientBackground from '@app/components/GradientBackground'
|
||||||
|
|
||||||
const ImageGradientBackground: React.FC<{
|
export type ImageGradientBackgroundProps = {
|
||||||
height?: number | string
|
height?: number | string
|
||||||
width?: number | string
|
width?: number | string
|
||||||
position?: 'relative' | 'absolute'
|
position?: 'relative' | 'absolute'
|
||||||
style?: ViewStyle
|
style?: ViewStyle
|
||||||
imagePath?: string
|
imagePath?: string
|
||||||
}> = ({ height, width, position, style, imagePath, children }) => {
|
onGetColor?: (color: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageGradientBackground: React.FC<ImageGradientBackgroundProps> = ({
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
position,
|
||||||
|
style,
|
||||||
|
imagePath,
|
||||||
|
children,
|
||||||
|
onGetColor,
|
||||||
|
}) => {
|
||||||
const [highColor, setHighColor] = useState<string>(colors.gradient.high)
|
const [highColor, setHighColor] = useState<string>(colors.gradient.high)
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
@ -60,6 +71,10 @@ const ImageGradientBackground: React.FC<{
|
|||||||
})
|
})
|
||||||
}, [navigation, highColor])
|
}, [navigation, highColor])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onGetColor && onGetColor(highColor)
|
||||||
|
}, [onGetColor, highColor])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GradientBackground
|
<GradientBackground
|
||||||
height={height}
|
height={height}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import ImageGradientBackground from '@app/components/ImageGradientBackground'
|
import ImageGradientBackground, { ImageGradientBackgroundProps } from '@app/components/ImageGradientBackground'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
import dimensions from '@app/styles/dimensions'
|
import dimensions from '@app/styles/dimensions'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ScrollView, ScrollViewProps, useWindowDimensions } from 'react-native'
|
import { ScrollView, ScrollViewProps, useWindowDimensions } from 'react-native'
|
||||||
|
|
||||||
const ImageGradientScrollView: React.FC<ScrollViewProps & { imagePath?: string }> = props => {
|
const ImageGradientScrollView: React.FC<ScrollViewProps & ImageGradientBackgroundProps> = props => {
|
||||||
const layout = useWindowDimensions()
|
const layout = useWindowDimensions()
|
||||||
|
|
||||||
const minHeight = layout.height - (dimensions.top() + dimensions.bottom())
|
const minHeight = layout.height - (dimensions.top() + dimensions.bottom())
|
||||||
@ -20,7 +20,7 @@ const ImageGradientScrollView: React.FC<ScrollViewProps & { imagePath?: string }
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
contentContainerStyle={[{ minHeight }, props.contentContainerStyle]}>
|
contentContainerStyle={[{ minHeight }, props.contentContainerStyle]}>
|
||||||
<ImageGradientBackground height={minHeight} imagePath={props.imagePath} />
|
<ImageGradientBackground height={minHeight} imagePath={props.imagePath} onGetColor={props.onGetColor} />
|
||||||
{props.children}
|
{props.children}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -102,9 +102,9 @@ function createTabStackNavigator(Component: React.ComponentType<any>) {
|
|||||||
return (
|
return (
|
||||||
<Stack.Navigator initialRouteName="main">
|
<Stack.Navigator initialRouteName="main">
|
||||||
<Stack.Screen name="main" component={Component} options={{ headerShown: false }} />
|
<Stack.Screen name="main" component={Component} options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="album" component={AlbumScreen} options={itemScreenOptions} />
|
<Stack.Screen name="album" component={AlbumScreen} options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="artist" component={ArtistScreen} options={{ headerShown: false }} />
|
<Stack.Screen name="artist" component={ArtistScreen} options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="playlist" component={PlaylistScreen} options={itemScreenOptions} />
|
<Stack.Screen name="playlist" component={PlaylistScreen} options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="results" component={ResultsScreen} options={itemScreenOptions} />
|
<Stack.Screen name="results" component={ResultsScreen} options={itemScreenOptions} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
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 ImageGradientScrollView from '@app/components/ImageGradientScrollView'
|
import ImageGradientScrollView from '@app/components/ImageGradientScrollView'
|
||||||
import ListItem from '@app/components/ListItem'
|
import ListItem from '@app/components/ListItem'
|
||||||
import ListPlayerControls from '@app/components/ListPlayerControls'
|
import ListPlayerControls from '@app/components/ListPlayerControls'
|
||||||
@ -12,7 +13,7 @@ import { selectTrackPlayer } from '@app/state/trackplayer'
|
|||||||
import colors from '@app/styles/colors'
|
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, { useEffect } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
|
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
|
||||||
|
|
||||||
type SongListType = 'album' | 'playlist'
|
type SongListType = 'album' | 'playlist'
|
||||||
@ -71,45 +72,58 @@ const Songs = React.memo<{
|
|||||||
})
|
})
|
||||||
|
|
||||||
const SongListDetails = React.memo<{
|
const SongListDetails = React.memo<{
|
||||||
|
title: string
|
||||||
type: SongListType
|
type: SongListType
|
||||||
songList?: AlbumWithSongs | PlaylistWithSongs
|
songList?: AlbumWithSongs | PlaylistWithSongs
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
}>(({ songList, subtitle, type }) => {
|
}>(({ title, songList, subtitle, type }) => {
|
||||||
const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail')
|
const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail')
|
||||||
|
const [headerColor, setHeaderColor] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
if (!songList) {
|
if (!songList) {
|
||||||
return <SongListDetailsFallback />
|
return <SongListDetailsFallback />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageGradientScrollView imagePath={coverArtFile?.file?.path} style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.content}>
|
{songList.itemType === 'album' && (
|
||||||
<CoverArt type="cover" size="original" coverArt={songList.coverArt} style={styles.cover} />
|
<HeaderBar headerStyle={{ backgroundColor: headerColor }} title={title} contextItem={songList} />
|
||||||
<Text style={styles.title}>{songList.name}</Text>
|
)}
|
||||||
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
|
<ImageGradientScrollView
|
||||||
{songList.songs.length > 0 ? (
|
imagePath={coverArtFile?.file?.path}
|
||||||
<Songs songs={songList.songs} name={songList.name} type={type} itemId={songList.id} />
|
style={styles.container}
|
||||||
) : (
|
onGetColor={setHeaderColor}>
|
||||||
<NothingHere height={300} width={250} />
|
<View style={styles.content}>
|
||||||
)}
|
<CoverArt type="cover" size="original" coverArt={songList.coverArt} style={styles.cover} />
|
||||||
</View>
|
<Text style={styles.title}>{songList.name}</Text>
|
||||||
</ImageGradientScrollView>
|
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
|
||||||
|
{songList.songs.length > 0 ? (
|
||||||
|
<Songs songs={songList.songs} name={songList.name} type={type} itemId={songList.id} />
|
||||||
|
) : (
|
||||||
|
<NothingHere height={300} width={250} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</ImageGradientScrollView>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const PlaylistView = React.memo<{
|
const PlaylistView = React.memo<{
|
||||||
id: string
|
id: string
|
||||||
}>(({ id }) => {
|
title: string
|
||||||
|
}>(({ id, title }) => {
|
||||||
const playlist = usePlaylistWithSongs(id)
|
const playlist = usePlaylistWithSongs(id)
|
||||||
return <SongListDetails songList={playlist} subtitle={playlist?.comment} type="playlist" />
|
return <SongListDetails title={title} songList={playlist} subtitle={playlist?.comment} type="playlist" />
|
||||||
})
|
})
|
||||||
|
|
||||||
const AlbumView = React.memo<{
|
const AlbumView = React.memo<{
|
||||||
id: string
|
id: string
|
||||||
}>(({ id }) => {
|
title: string
|
||||||
|
}>(({ id, title }) => {
|
||||||
const album = useAlbumWithSongs(id)
|
const album = useAlbumWithSongs(id)
|
||||||
return (
|
return (
|
||||||
<SongListDetails
|
<SongListDetails
|
||||||
|
title={title}
|
||||||
songList={album}
|
songList={album}
|
||||||
subtitle={(album?.artist || '') + (album?.year ? ' • ' + album?.year : '')}
|
subtitle={(album?.artist || '') + (album?.year ? ' • ' + album?.year : '')}
|
||||||
type="album"
|
type="album"
|
||||||
@ -128,7 +142,7 @@ const SongListView = React.memo<{
|
|||||||
navigation.setOptions({ title })
|
navigation.setOptions({ title })
|
||||||
})
|
})
|
||||||
|
|
||||||
return type === 'album' ? <AlbumView id={id} /> : <PlaylistView id={id} />
|
return type === 'album' ? <AlbumView id={id} title={title} /> : <PlaylistView id={id} title={title} />
|
||||||
})
|
})
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user