song context menu

This commit is contained in:
austinried 2021-08-08 13:09:32 +09:00
parent 0416c0ad0d
commit 26749d0458
3 changed files with 128 additions and 45 deletions

View File

@ -1,14 +1,15 @@
import PressableOpacity from '@app/components/PressableOpacity'
import { AlbumListItem } from '@app/models/music'
import { AlbumListItem, Song } from '@app/models/music'
import colors from '@app/styles/colors'
import font from '@app/styles/font'
import { useNavigation } from '@react-navigation/native'
import { NavigationProp, useNavigation } from '@react-navigation/native'
import { ReactComponentLike } from 'prop-types'
import React from 'react'
import { ScrollView, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native'
import FastImage from 'react-native-fast-image'
import { Menu, MenuOption, MenuOptions, MenuTrigger, renderers } from 'react-native-popup-menu'
import IconFA from 'react-native-vector-icons/FontAwesome'
import IconFA5 from 'react-native-vector-icons/FontAwesome5'
import IconMat from 'react-native-vector-icons/MaterialIcons'
import CoverArt from './CoverArt'
@ -97,56 +98,124 @@ const ContextMenuIconTextOption = React.memo<ContextMenuIconTextOptionProps>(
),
)
const MenuHeader = React.memo<{
coverArt?: string
title: string
subtitle?: string
}>(({ coverArt, title, subtitle }) => (
<View style={styles.menuHeader}>
{coverArt ? (
<CoverArt coverArt={coverArt} style={styles.coverArt} resizeMode={FastImage.resizeMode.cover} />
) : (
<></>
)}
<View style={styles.menuHeaderText}>
<Text numberOfLines={1} style={styles.menuTitle}>
{title}
</Text>
{subtitle ? (
<Text numberOfLines={1} style={styles.menuSubtitle}>
{subtitle}
</Text>
) : (
<></>
)}
</View>
</View>
))
const OptionStar = React.memo(() => (
<ContextMenuIconTextOption IconComponent={IconFA} name="star-o" size={26} text="Star" />
))
const OptionViewArtist = React.memo<{
navigation: NavigationProp<any>
artist?: string
artistId?: string
}>(({ navigation, artist, artistId }) => {
if (!artist || !artistId) {
return <></>
}
return (
<ContextMenuIconTextOption
IconComponent={IconFA}
name="microphone"
size={26}
text="View artist"
onSelect={() => navigation.navigate('artist', { id: artistId, title: artist })}
/>
)
})
const OptionViewAlbum = React.memo<{
navigation: NavigationProp<any>
album?: string
albumId?: string
}>(({ navigation, album, albumId }) => {
if (!album || !albumId) {
return <></>
}
return (
<ContextMenuIconTextOption
IconComponent={IconFA5}
name="compact-disc"
size={26}
text="View album"
onSelect={() => navigation.navigate('album', { id: albumId, title: album })}
/>
)
})
const OptionDownload = React.memo<{
itemType: string
}>(({ itemType }) => (
<ContextMenuIconTextOption IconComponent={IconMat} name="file-download" size={26} text={`Download ${itemType}`} />
))
export type AlbumContextPressableProps = ContextMenuProps & {
album: AlbumListItem
}
export const AlbumContextPressable: React.FC<AlbumContextPressableProps> = ({
menuStyle,
triggerWrapperStyle,
triggerOuterWrapperStyle,
triggerTouchableStyle,
onPress,
album,
children,
}) => {
export const AlbumContextPressable: React.FC<AlbumContextPressableProps> = props => {
const navigation = useNavigation()
const { album, children } = props
return (
<ContextMenu
menuStyle={menuStyle}
triggerWrapperStyle={triggerWrapperStyle}
triggerOuterWrapperStyle={triggerOuterWrapperStyle}
triggerTouchableStyle={triggerTouchableStyle}
onPress={onPress}
menuHeader={
<View style={styles.menuHeader}>
<CoverArt coverArt={album.coverArt} style={styles.coverArt} resizeMode={FastImage.resizeMode.cover} />
<View style={styles.menuHeaderText}>
<Text numberOfLines={1} style={styles.menuTitle}>
{album.name}
</Text>
{album.artist ? (
<Text numberOfLines={1} style={styles.menuSubtitle}>
{album.artist}
</Text>
) : (
<></>
)}
</View>
</View>
}
{...props}
menuHeader={<MenuHeader title={album.name} subtitle={album.artist} coverArt={album.coverArt} />}
menuOptions={
<>
<ContextMenuIconTextOption IconComponent={IconFA} name="star-o" size={26} text="Star" />
<ContextMenuIconTextOption
IconComponent={IconFA}
name="microphone"
size={26}
text="View artist"
onSelect={() => navigation.navigate('artist', { id: album.artistId, title: album.artist })}
/>
<ContextMenuIconTextOption IconComponent={IconMat} name="file-download" size={26} text="Download album" />
<OptionStar />
<OptionViewArtist artist={album.artist} artistId={album.artistId} navigation={navigation} />
<OptionDownload itemType={album.itemType} />
</>
}>
{children}
</ContextMenu>
)
}
export type SongContextPressableProps = ContextMenuProps & {
song: Song
}
export const SongContextPressable: React.FC<SongContextPressableProps> = props => {
const navigation = useNavigation()
const { song, children } = props
return (
<ContextMenu
{...props}
menuHeader={<MenuHeader title={song.title} subtitle={song.artist} coverArt={song.coverArt} />}
menuOptions={
<>
<OptionStar />
<OptionViewArtist artist={song.artist} artistId={song.artistId} navigation={navigation} />
<OptionViewAlbum album={song.album} albumId={song.albumId} navigation={navigation} />
<OptionDownload itemType={song.itemType} />
</>
}>
{children}
@ -190,7 +259,7 @@ const styles = StyleSheet.create({
color: colors.text.secondary,
},
option: {
paddingVertical: 10,
paddingVertical: 8,
paddingHorizontal: 20,
flexDirection: 'row',
alignItems: 'center',

View File

@ -1,4 +1,4 @@
import { AlbumListItem, ListableItem } from '@app/models/music'
import { AlbumListItem, ListableItem, Song } from '@app/models/music'
import { useStore } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import colors from '@app/styles/colors'
@ -10,7 +10,7 @@ import FastImage from 'react-native-fast-image'
import IconFA from 'react-native-vector-icons/FontAwesome'
import IconFA5 from 'react-native-vector-icons/FontAwesome5'
import IconMat from 'react-native-vector-icons/MaterialIcons'
import { AlbumContextPressable } from './ContextMenu'
import { AlbumContextPressable, SongContextPressable } from './ContextMenu'
import CoverArt from './CoverArt'
import PressableOpacity from './PressableOpacity'
@ -98,10 +98,20 @@ const ListItem: React.FC<{
),
[item, onPress],
)
const songPressable = useCallback(
({ children }) => (
<SongContextPressable song={item as Song} onPress={onPress} triggerWrapperStyle={styles.item}>
{children}
</SongContextPressable>
),
[item, onPress],
)
let PressableComponent = itemPressable
if (item.itemType === 'album') {
PressableComponent = albumPressable
} else if (item.itemType === 'song') {
PressableComponent = songPressable
}
return (

View File

@ -69,7 +69,9 @@ export interface Song {
itemType: 'song'
id: string
album?: string
albumId?: string
artist?: string
artistId?: string
title: string
track?: number
duration?: number
@ -168,7 +170,9 @@ export function mapChildToSong(child: ChildElement, client: SubsonicApiClient):
itemType: 'song',
id: child.id,
album: child.album,
albumId: child.albumId,
artist: child.artist,
artistId: child.artistId,
title: child.title,
track: child.track,
duration: child.duration,