mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 09:29:29 +01:00
song context menu
This commit is contained in:
parent
0416c0ad0d
commit
26749d0458
@ -1,14 +1,15 @@
|
|||||||
import PressableOpacity from '@app/components/PressableOpacity'
|
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 colors from '@app/styles/colors'
|
||||||
import font from '@app/styles/font'
|
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 { ReactComponentLike } from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ScrollView, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native'
|
import { ScrollView, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import { Menu, MenuOption, MenuOptions, MenuTrigger, renderers } from 'react-native-popup-menu'
|
import { Menu, MenuOption, MenuOptions, MenuTrigger, renderers } from 'react-native-popup-menu'
|
||||||
import IconFA from 'react-native-vector-icons/FontAwesome'
|
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 IconMat from 'react-native-vector-icons/MaterialIcons'
|
||||||
import CoverArt from './CoverArt'
|
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 & {
|
export type AlbumContextPressableProps = ContextMenuProps & {
|
||||||
album: AlbumListItem
|
album: AlbumListItem
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AlbumContextPressable: React.FC<AlbumContextPressableProps> = ({
|
export const AlbumContextPressable: React.FC<AlbumContextPressableProps> = props => {
|
||||||
menuStyle,
|
|
||||||
triggerWrapperStyle,
|
|
||||||
triggerOuterWrapperStyle,
|
|
||||||
triggerTouchableStyle,
|
|
||||||
onPress,
|
|
||||||
album,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
const { album, children } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
menuStyle={menuStyle}
|
{...props}
|
||||||
triggerWrapperStyle={triggerWrapperStyle}
|
menuHeader={<MenuHeader title={album.name} subtitle={album.artist} coverArt={album.coverArt} />}
|
||||||
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>
|
|
||||||
}
|
|
||||||
menuOptions={
|
menuOptions={
|
||||||
<>
|
<>
|
||||||
<ContextMenuIconTextOption IconComponent={IconFA} name="star-o" size={26} text="Star" />
|
<OptionStar />
|
||||||
<ContextMenuIconTextOption
|
<OptionViewArtist artist={album.artist} artistId={album.artistId} navigation={navigation} />
|
||||||
IconComponent={IconFA}
|
<OptionDownload itemType={album.itemType} />
|
||||||
name="microphone"
|
</>
|
||||||
size={26}
|
}>
|
||||||
text="View artist"
|
{children}
|
||||||
onSelect={() => navigation.navigate('artist', { id: album.artistId, title: album.artist })}
|
</ContextMenu>
|
||||||
/>
|
)
|
||||||
<ContextMenuIconTextOption IconComponent={IconMat} name="file-download" size={26} text="Download album" />
|
}
|
||||||
|
|
||||||
|
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}
|
{children}
|
||||||
@ -190,7 +259,7 @@ const styles = StyleSheet.create({
|
|||||||
color: colors.text.secondary,
|
color: colors.text.secondary,
|
||||||
},
|
},
|
||||||
option: {
|
option: {
|
||||||
paddingVertical: 10,
|
paddingVertical: 8,
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@ -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 { useStore } from '@app/state/store'
|
||||||
import { selectTrackPlayer } from '@app/state/trackplayer'
|
import { selectTrackPlayer } from '@app/state/trackplayer'
|
||||||
import colors from '@app/styles/colors'
|
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 IconFA from 'react-native-vector-icons/FontAwesome'
|
||||||
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 } from './ContextMenu'
|
import { AlbumContextPressable, SongContextPressable } from './ContextMenu'
|
||||||
import CoverArt from './CoverArt'
|
import CoverArt from './CoverArt'
|
||||||
import PressableOpacity from './PressableOpacity'
|
import PressableOpacity from './PressableOpacity'
|
||||||
|
|
||||||
@ -98,10 +98,20 @@ const ListItem: React.FC<{
|
|||||||
),
|
),
|
||||||
[item, onPress],
|
[item, onPress],
|
||||||
)
|
)
|
||||||
|
const songPressable = useCallback(
|
||||||
|
({ children }) => (
|
||||||
|
<SongContextPressable song={item as Song} onPress={onPress} triggerWrapperStyle={styles.item}>
|
||||||
|
{children}
|
||||||
|
</SongContextPressable>
|
||||||
|
),
|
||||||
|
[item, onPress],
|
||||||
|
)
|
||||||
|
|
||||||
let PressableComponent = itemPressable
|
let PressableComponent = itemPressable
|
||||||
if (item.itemType === 'album') {
|
if (item.itemType === 'album') {
|
||||||
PressableComponent = albumPressable
|
PressableComponent = albumPressable
|
||||||
|
} else if (item.itemType === 'song') {
|
||||||
|
PressableComponent = songPressable
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -69,7 +69,9 @@ export interface Song {
|
|||||||
itemType: 'song'
|
itemType: 'song'
|
||||||
id: string
|
id: string
|
||||||
album?: string
|
album?: string
|
||||||
|
albumId?: string
|
||||||
artist?: string
|
artist?: string
|
||||||
|
artistId?: string
|
||||||
title: string
|
title: string
|
||||||
track?: number
|
track?: number
|
||||||
duration?: number
|
duration?: number
|
||||||
@ -168,7 +170,9 @@ export function mapChildToSong(child: ChildElement, client: SubsonicApiClient):
|
|||||||
itemType: 'song',
|
itemType: 'song',
|
||||||
id: child.id,
|
id: child.id,
|
||||||
album: child.album,
|
album: child.album,
|
||||||
|
albumId: child.albumId,
|
||||||
artist: child.artist,
|
artist: child.artist,
|
||||||
|
artistId: child.artistId,
|
||||||
title: child.title,
|
title: child.title,
|
||||||
track: child.track,
|
track: child.track,
|
||||||
duration: child.duration,
|
duration: child.duration,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user