mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 09:29:29 +01:00
now playing header now shows context menu
fixed now playing image gradient
This commit is contained in:
parent
ad25972774
commit
b5392b6731
@ -1,6 +1,6 @@
|
|||||||
import PressableOpacity from '@app/components/PressableOpacity'
|
import PressableOpacity from '@app/components/PressableOpacity'
|
||||||
import { useStarred } from '@app/hooks/music'
|
import { useStarred } from '@app/hooks/music'
|
||||||
import { AlbumListItem, Artist, Song } from '@app/models/music'
|
import { AlbumListItem, Artist, Song, StarrableItemType } from '@app/models/music'
|
||||||
import { selectMusic } from '@app/state/music'
|
import { selectMusic } from '@app/state/music'
|
||||||
import { useStore } from '@app/state/store'
|
import { useStore } from '@app/state/store'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
@ -25,6 +25,7 @@ type ContextMenuProps = {
|
|||||||
triggerOuterWrapperStyle?: StyleProp<ViewStyle>
|
triggerOuterWrapperStyle?: StyleProp<ViewStyle>
|
||||||
triggerTouchableStyle?: StyleProp<ViewStyle>
|
triggerTouchableStyle?: StyleProp<ViewStyle>
|
||||||
onPress?: () => any
|
onPress?: () => any
|
||||||
|
triggerOnLongPress?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type InternalContextMenuProps = ContextMenuProps & {
|
type InternalContextMenuProps = ContextMenuProps & {
|
||||||
@ -41,6 +42,7 @@ const ContextMenu: React.FC<InternalContextMenuProps> = ({
|
|||||||
menuHeader,
|
menuHeader,
|
||||||
menuOptions,
|
menuOptions,
|
||||||
children,
|
children,
|
||||||
|
triggerOnLongPress,
|
||||||
}) => {
|
}) => {
|
||||||
menuStyle = menuStyle || { flex: 1 }
|
menuStyle = menuStyle || { flex: 1 }
|
||||||
triggerWrapperStyle = triggerWrapperStyle || { flex: 1 }
|
triggerWrapperStyle = triggerWrapperStyle || { flex: 1 }
|
||||||
@ -49,7 +51,7 @@ const ContextMenu: React.FC<InternalContextMenuProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Menu renderer={SlideInMenu} style={menuStyle}>
|
<Menu renderer={SlideInMenu} style={menuStyle}>
|
||||||
<MenuTrigger
|
<MenuTrigger
|
||||||
triggerOnLongPress={true}
|
triggerOnLongPress={triggerOnLongPress === undefined ? true : triggerOnLongPress}
|
||||||
customStyles={{
|
customStyles={{
|
||||||
triggerOuterWrapper: triggerOuterWrapperStyle,
|
triggerOuterWrapper: triggerOuterWrapperStyle,
|
||||||
triggerWrapper: triggerWrapperStyle,
|
triggerWrapper: triggerWrapperStyle,
|
||||||
@ -146,15 +148,16 @@ const MenuHeader = React.memo<{
|
|||||||
|
|
||||||
const OptionStar = React.memo<{
|
const OptionStar = React.memo<{
|
||||||
id: string
|
id: string
|
||||||
type: string
|
type: StarrableItemType
|
||||||
}>(({ id, type }) => {
|
additionalText?: string
|
||||||
|
}>(({ id, type, additionalText: text }) => {
|
||||||
const starred = useStarred(id, type)
|
const starred = useStarred(id, type)
|
||||||
const setStarred = useStore(selectMusic.starItem)
|
const setStarred = useStore(selectMusic.starItem)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenuIconTextOption
|
<ContextMenuIconTextOption
|
||||||
IconComponentRaw={<Star starred={starred} size={26} />}
|
IconComponentRaw={<Star starred={starred} size={26} />}
|
||||||
text={starred ? 'Unstar' : 'Star'}
|
text={(starred ? 'Unstar' : 'Star') + (text ? ` ${text}` : '')}
|
||||||
onSelect={() => setStarred(id, type, starred)}
|
onSelect={() => setStarred(id, type, starred)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -277,6 +280,30 @@ export const ArtistContextPressable: React.FC<ArtistContextPressableProps> = pro
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NowPlayingContextPressableProps = ContextMenuProps & {
|
||||||
|
song: Song
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NowPlayingContextPressable: React.FC<NowPlayingContextPressableProps> = props => {
|
||||||
|
const navigation = useNavigation()
|
||||||
|
const { song, children } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu
|
||||||
|
{...props}
|
||||||
|
menuHeader={<MenuHeader title={song.title} subtitle={song.artist} coverArt={song.coverArt} />}
|
||||||
|
menuOptions={
|
||||||
|
<>
|
||||||
|
<OptionStar id={song.id} type={song.itemType} />
|
||||||
|
<OptionViewArtist artist={song.artist} artistId={song.artistId} navigation={navigation} />
|
||||||
|
<OptionViewAlbum album={song.album} albumId={song.albumId} navigation={navigation} />
|
||||||
|
</>
|
||||||
|
}>
|
||||||
|
{children}
|
||||||
|
</ContextMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
optionsContainer: {
|
optionsContainer: {
|
||||||
backgroundColor: 'rgba(45, 45, 45, 0.95)',
|
backgroundColor: 'rgba(45, 45, 45, 0.95)',
|
||||||
|
|||||||
@ -8,13 +8,15 @@ 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 { NowPlayingContextPressable } from './ContextMenu'
|
||||||
|
|
||||||
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
|
||||||
onMore?: () => void
|
contextItem?: Song
|
||||||
}>(({ title, headerStyle, HeaderCenter, onMore }) => {
|
}>(({ title, headerStyle, HeaderCenter, contextItem }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
const back = useCallback(() => {
|
const back = useCallback(() => {
|
||||||
@ -23,9 +25,23 @@ 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} ripple={true}>
|
<PressableOpacity onPress={back} style={styles.icons}>
|
||||||
<IconMat name="arrow-back" color="white" size={25} />
|
<IconMat name="arrow-back" color="white" size={25} />
|
||||||
</PressableOpacity>
|
</PressableOpacity>
|
||||||
<View style={styles.center}>
|
<View style={styles.center}>
|
||||||
@ -37,13 +53,7 @@ const HeaderBar = React.memo<{
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{onMore ? (
|
{more}
|
||||||
<PressableOpacity style={styles.icons} ripple={true} onPress={onMore}>
|
|
||||||
<IconMat name="more-vert" color="white" size={25} />
|
|
||||||
</PressableOpacity>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -62,6 +72,8 @@ const styles = StyleSheet.create({
|
|||||||
height: 42,
|
height: 42,
|
||||||
width: 42,
|
width: 42,
|
||||||
marginHorizontal: 8,
|
marginHorizontal: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
center: {
|
center: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@ -18,7 +18,6 @@ const ImageGradientBackground: React.FC<{
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getColors() {
|
async function getColors() {
|
||||||
console.log(`imagePath: ${imagePath}`)
|
|
||||||
if (imagePath === undefined) {
|
if (imagePath === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -27,7 +26,6 @@ const ImageGradientBackground: React.FC<{
|
|||||||
|
|
||||||
let res: AndroidImageColors
|
let res: AndroidImageColors
|
||||||
if (cachedResult) {
|
if (cachedResult) {
|
||||||
console.log(`cachedResult: ${JSON.stringify(cachedResult)}`)
|
|
||||||
res = cachedResult as AndroidImageColors
|
res = cachedResult as AndroidImageColors
|
||||||
} else {
|
} else {
|
||||||
const path = `file://${imagePath}`
|
const path = `file://${imagePath}`
|
||||||
@ -35,7 +33,6 @@ const ImageGradientBackground: React.FC<{
|
|||||||
cache: true,
|
cache: true,
|
||||||
key: imagePath,
|
key: imagePath,
|
||||||
})) as AndroidImageColors
|
})) as AndroidImageColors
|
||||||
console.log(`res: ${JSON.stringify(res)}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.muted && res.muted !== '#000000') {
|
if (res.muted && res.muted !== '#000000') {
|
||||||
|
|||||||
@ -116,7 +116,6 @@ export const useArtistArtFile = (artistId: string) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!file && artistInfo && artistInfo.largeImageUrl) {
|
if (!file && artistInfo && artistInfo.largeImageUrl) {
|
||||||
console.log(artistInfo.largeImageUrl)
|
|
||||||
cacheItem('artistArt', artistId, artistInfo.largeImageUrl)
|
cacheItem('artistArt', artistId, artistInfo.largeImageUrl)
|
||||||
}
|
}
|
||||||
}, [artistId, artistInfo, artistInfo?.largeImageUrl, cacheItem, file])
|
}, [artistId, artistInfo, artistInfo?.largeImageUrl, cacheItem, file])
|
||||||
|
|||||||
@ -279,11 +279,11 @@ function mapSongToTrack(song: Song, coverArtPaths: { [coverArt: string]: string
|
|||||||
album: song.album || 'Unknown Album',
|
album: song.album || 'Unknown Album',
|
||||||
url: song.streamUri,
|
url: song.streamUri,
|
||||||
artwork:
|
artwork:
|
||||||
song.coverArt && coverArtPaths[song.coverArt]
|
song.coverArt && coverArtPaths[song.coverArt] ? coverArtPaths[song.coverArt] : require('@res/fallback.png'),
|
||||||
? `file://${coverArtPaths[song.coverArt]}`
|
|
||||||
: require('@res/fallback.png'),
|
|
||||||
coverArt: song.coverArt,
|
coverArt: song.coverArt,
|
||||||
duration: song.duration,
|
duration: song.duration,
|
||||||
|
artistId: song.artistId,
|
||||||
|
albumId: song.albumId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,5 +297,7 @@ export function mapTrackExtToSong(track: TrackExt): Song {
|
|||||||
streamUri: track.url as string,
|
streamUri: track.url as string,
|
||||||
coverArt: track.coverArt,
|
coverArt: track.coverArt,
|
||||||
duration: track.duration,
|
duration: track.duration,
|
||||||
|
artistId: track.artistId,
|
||||||
|
albumId: track.albumId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,6 +80,8 @@ export type ListableItem = Song | AlbumListItem | Artist | PlaylistListItem
|
|||||||
|
|
||||||
export type HomeLists = { [key: string]: AlbumListItem[] }
|
export type HomeLists = { [key: string]: AlbumListItem[] }
|
||||||
|
|
||||||
|
export type StarrableItemType = 'song' | 'album' | 'artist'
|
||||||
|
|
||||||
export enum CacheItemType {
|
export enum CacheItemType {
|
||||||
coverArt,
|
coverArt,
|
||||||
artistArt,
|
artistArt,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import PressableOpacity from '@app/components/PressableOpacity'
|
|||||||
import Star from '@app/components/Star'
|
import Star from '@app/components/Star'
|
||||||
import { useStarred } from '@app/hooks/music'
|
import { useStarred } from '@app/hooks/music'
|
||||||
import {
|
import {
|
||||||
|
mapTrackExtToSong,
|
||||||
useNext,
|
useNext,
|
||||||
usePause,
|
usePause,
|
||||||
usePlay,
|
usePlay,
|
||||||
@ -15,7 +16,7 @@ import {
|
|||||||
} from '@app/hooks/trackplayer'
|
} from '@app/hooks/trackplayer'
|
||||||
import { selectMusic } from '@app/state/music'
|
import { selectMusic } from '@app/state/music'
|
||||||
import { useStore } from '@app/state/store'
|
import { useStore } from '@app/state/store'
|
||||||
import { QueueContextType, selectTrackPlayer } from '@app/state/trackplayer'
|
import { QueueContextType, selectTrackPlayer, TrackExt } 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 formatDuration from '@app/util/formatDuration'
|
import formatDuration from '@app/util/formatDuration'
|
||||||
@ -45,34 +46,28 @@ function getContextName(type?: QueueContextType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NowPlayingHeader = React.memo(() => {
|
const NowPlayingHeader = React.memo<{
|
||||||
const navigation = useNavigation()
|
track?: TrackExt
|
||||||
|
}>(({ track }) => {
|
||||||
const queueName = useStore(selectTrackPlayer.name)
|
const queueName = useStore(selectTrackPlayer.name)
|
||||||
const queueContextType = useStore(selectTrackPlayer.queueContextType)
|
const queueContextType = useStore(selectTrackPlayer.queueContextType)
|
||||||
const queueContextId = useStore(selectTrackPlayer.queueContextId)
|
|
||||||
|
if (!track) {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
let contextName = getContextName(queueContextType)
|
let contextName = getContextName(queueContextType)
|
||||||
|
|
||||||
const goToContext = useCallback(() => {
|
|
||||||
if (!queueContextType || !queueContextId || queueContextType === 'song') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
navigation.navigate('library')
|
|
||||||
navigation.navigate(queueContextType, { id: queueContextId, title: queueName })
|
|
||||||
}, [navigation, queueContextId, queueContextType, queueName])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderBar
|
<HeaderBar
|
||||||
headerStyle={{ backgroundColor: 'transparent' }}
|
headerStyle={{ backgroundColor: 'transparent' }}
|
||||||
onMore={goToContext}
|
contextItem={mapTrackExtToSong(track)}
|
||||||
HeaderCenter={() => (
|
HeaderCenter={() => (
|
||||||
<View style={headerStyles.center}>
|
<View style={headerStyles.center}>
|
||||||
{contextName ? (
|
{contextName !== undefined && (
|
||||||
<Text numberOfLines={1} style={headerStyles.queueType}>
|
<Text numberOfLines={1} style={headerStyles.queueType}>
|
||||||
{contextName}
|
{contextName}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
)}
|
||||||
<Text numberOfLines={1} style={headerStyles.queueName}>
|
<Text numberOfLines={1} style={headerStyles.queueName}>
|
||||||
{queueName || 'Nothing playing...'}
|
{queueName || 'Nothing playing...'}
|
||||||
@ -395,7 +390,7 @@ const NowPlayingView: React.FC<NowPlayingProps> = ({ navigation }) => {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<ImageGradientBackground imagePath={imagePath} />
|
<ImageGradientBackground imagePath={imagePath} />
|
||||||
<NowPlayingHeader />
|
<NowPlayingHeader track={track} />
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<SongCoverArt />
|
<SongCoverArt />
|
||||||
<SongInfo />
|
<SongInfo />
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
PlaylistWithSongs,
|
PlaylistWithSongs,
|
||||||
SearchResults,
|
SearchResults,
|
||||||
Song,
|
Song,
|
||||||
|
StarrableItemType,
|
||||||
} from '@app/models/music'
|
} from '@app/models/music'
|
||||||
import { Store } from '@app/state/store'
|
import { Store } from '@app/state/store'
|
||||||
import { GetAlbumList2Type, StarParams } from '@app/subsonic/params'
|
import { GetAlbumList2Type, StarParams } from '@app/subsonic/params'
|
||||||
@ -65,7 +66,7 @@ export type MusicSlice = {
|
|||||||
starredSongs: { [id: string]: boolean }
|
starredSongs: { [id: string]: boolean }
|
||||||
starredAlbums: { [id: string]: boolean }
|
starredAlbums: { [id: string]: boolean }
|
||||||
starredArtists: { [id: string]: boolean }
|
starredArtists: { [id: string]: boolean }
|
||||||
starItem: (id: string, type: string, unstar?: boolean) => Promise<void>
|
starItem: (id: string, type: StarrableItemType, unstar?: boolean) => Promise<void>
|
||||||
|
|
||||||
albumIdCoverArt: { [id: string]: string | undefined }
|
albumIdCoverArt: { [id: string]: string | undefined }
|
||||||
albumIdCoverArtRequests: { [id: string]: Promise<void> }
|
albumIdCoverArtRequests: { [id: string]: Promise<void> }
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { Store } from './store'
|
|||||||
export type TrackExt = Track & {
|
export type TrackExt = Track & {
|
||||||
id: string
|
id: string
|
||||||
coverArt?: string
|
coverArt?: string
|
||||||
|
artistId?: string
|
||||||
|
albumId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Progress = {
|
export type Progress = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user