mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 01:19:28 +01:00
first-pass context menu for albums
This commit is contained in:
parent
f3341355e1
commit
0416c0ad0d
19
app/App.tsx
19
app/App.tsx
@ -6,6 +6,7 @@ import { StatusBar, View } from 'react-native'
|
|||||||
import ProgressHook from './components/ProgressHook'
|
import ProgressHook from './components/ProgressHook'
|
||||||
import { useStore } from './state/store'
|
import { useStore } from './state/store'
|
||||||
import { selectTrackPlayer } from './state/trackplayer'
|
import { selectTrackPlayer } from './state/trackplayer'
|
||||||
|
import { MenuProvider } from 'react-native-popup-menu'
|
||||||
|
|
||||||
const Debug = () => {
|
const Debug = () => {
|
||||||
const currentTrack = useStore(selectTrackPlayer.currentTrack)
|
const currentTrack = useStore(selectTrackPlayer.currentTrack)
|
||||||
@ -14,14 +15,16 @@ const Debug = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<View style={{ flex: 1, backgroundColor: colors.gradient.high }}>
|
<MenuProvider>
|
||||||
<StatusBar animated={true} backgroundColor={'rgba(0, 0, 0, 0.4)'} barStyle={'light-content'} translucent={true} />
|
<View style={{ flex: 1, backgroundColor: colors.gradient.high }}>
|
||||||
<SplashPage>
|
<StatusBar animated={true} backgroundColor={'rgba(0, 0, 0, 0.4)'} barStyle={'light-content'} translucent={true} />
|
||||||
<ProgressHook />
|
<SplashPage>
|
||||||
<Debug />
|
<ProgressHook />
|
||||||
<RootNavigator />
|
<Debug />
|
||||||
</SplashPage>
|
<RootNavigator />
|
||||||
</View>
|
</SplashPage>
|
||||||
|
</View>
|
||||||
|
</MenuProvider>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
212
app/components/ContextMenu.tsx
Normal file
212
app/components/ContextMenu.tsx
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import PressableOpacity from '@app/components/PressableOpacity'
|
||||||
|
import { AlbumListItem } from '@app/models/music'
|
||||||
|
import colors from '@app/styles/colors'
|
||||||
|
import font from '@app/styles/font'
|
||||||
|
import { 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 IconMat from 'react-native-vector-icons/MaterialIcons'
|
||||||
|
import CoverArt from './CoverArt'
|
||||||
|
|
||||||
|
const { SlideInMenu } = renderers
|
||||||
|
|
||||||
|
type ContextMenuProps = {
|
||||||
|
menuStyle?: StyleProp<ViewStyle>
|
||||||
|
triggerWrapperStyle?: StyleProp<ViewStyle>
|
||||||
|
triggerOuterWrapperStyle?: StyleProp<ViewStyle>
|
||||||
|
triggerTouchableStyle?: StyleProp<ViewStyle>
|
||||||
|
onPress?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalContextMenuProps = ContextMenuProps & {
|
||||||
|
menuHeader: React.ReactNode
|
||||||
|
menuOptions: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContextMenu: React.FC<InternalContextMenuProps> = ({
|
||||||
|
menuStyle,
|
||||||
|
triggerWrapperStyle,
|
||||||
|
triggerOuterWrapperStyle,
|
||||||
|
triggerTouchableStyle,
|
||||||
|
onPress,
|
||||||
|
menuHeader,
|
||||||
|
menuOptions,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
menuStyle = menuStyle || { flex: 1 }
|
||||||
|
triggerWrapperStyle = triggerWrapperStyle || { flex: 1 }
|
||||||
|
triggerOuterWrapperStyle = triggerOuterWrapperStyle || { flex: 1 }
|
||||||
|
triggerTouchableStyle = triggerTouchableStyle || { flex: 1 }
|
||||||
|
return (
|
||||||
|
<Menu renderer={SlideInMenu} style={menuStyle}>
|
||||||
|
<MenuTrigger
|
||||||
|
triggerOnLongPress={true}
|
||||||
|
customStyles={{
|
||||||
|
triggerOuterWrapper: triggerOuterWrapperStyle,
|
||||||
|
triggerWrapper: triggerWrapperStyle,
|
||||||
|
triggerTouchable: { style: triggerTouchableStyle },
|
||||||
|
TriggerTouchableComponent: PressableOpacity,
|
||||||
|
}}
|
||||||
|
onAlternativeAction={onPress}>
|
||||||
|
{children}
|
||||||
|
</MenuTrigger>
|
||||||
|
<MenuOptions
|
||||||
|
customStyles={styles}
|
||||||
|
renderOptionsContainer={(options: any) => (
|
||||||
|
<ScrollView>
|
||||||
|
{menuHeader}
|
||||||
|
{options}
|
||||||
|
</ScrollView>
|
||||||
|
)}>
|
||||||
|
{menuOptions}
|
||||||
|
</MenuOptions>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContextMenuOptionProps = {
|
||||||
|
onSelect?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContextMenuOption: React.FC<ContextMenuOptionProps> = ({ onSelect, children }) => (
|
||||||
|
<MenuOption style={styles.option} onSelect={onSelect}>
|
||||||
|
{children}
|
||||||
|
</MenuOption>
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContextMenuIconTextOptionProps = ContextMenuOptionProps & {
|
||||||
|
IconComponent: ReactComponentLike
|
||||||
|
name: string
|
||||||
|
size: number
|
||||||
|
color?: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContextMenuIconTextOption = React.memo<ContextMenuIconTextOptionProps>(
|
||||||
|
({ onSelect, IconComponent, name, color, size, text }) => (
|
||||||
|
<ContextMenuOption onSelect={onSelect}>
|
||||||
|
<View style={styles.icon}>
|
||||||
|
<IconComponent name={name} size={size} color={color || colors.text.primary} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.optionText}>{text}</Text>
|
||||||
|
</ContextMenuOption>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
export type AlbumContextPressableProps = ContextMenuProps & {
|
||||||
|
album: AlbumListItem
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlbumContextPressable: React.FC<AlbumContextPressableProps> = ({
|
||||||
|
menuStyle,
|
||||||
|
triggerWrapperStyle,
|
||||||
|
triggerOuterWrapperStyle,
|
||||||
|
triggerTouchableStyle,
|
||||||
|
onPress,
|
||||||
|
album,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const navigation = useNavigation()
|
||||||
|
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
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" />
|
||||||
|
</>
|
||||||
|
}>
|
||||||
|
{children}
|
||||||
|
</ContextMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
optionsContainer: {
|
||||||
|
backgroundColor: 'rgba(45, 45, 45, 0.95)',
|
||||||
|
maxHeight: 365,
|
||||||
|
},
|
||||||
|
optionsWrapper: {
|
||||||
|
// marginBottom: 10,
|
||||||
|
},
|
||||||
|
menuHeader: {
|
||||||
|
paddingTop: 14,
|
||||||
|
paddingBottom: 10,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
coverArt: {
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
},
|
||||||
|
menuHeaderText: {
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: 10,
|
||||||
|
},
|
||||||
|
menuTitle: {
|
||||||
|
fontFamily: font.semiBold,
|
||||||
|
fontSize: 16,
|
||||||
|
color: colors.text.primary,
|
||||||
|
},
|
||||||
|
menuSubtitle: {
|
||||||
|
fontFamily: font.regular,
|
||||||
|
fontSize: 14,
|
||||||
|
color: colors.text.secondary,
|
||||||
|
},
|
||||||
|
option: {
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
marginRight: 10,
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
// backgroundColor: 'red',
|
||||||
|
},
|
||||||
|
optionText: {
|
||||||
|
fontFamily: font.semiBold,
|
||||||
|
fontSize: 16,
|
||||||
|
color: colors.text.primary,
|
||||||
|
// backgroundColor: 'green',
|
||||||
|
},
|
||||||
|
})
|
||||||
@ -1,15 +1,16 @@
|
|||||||
import { ListableItem } from '@app/models/music'
|
import { AlbumListItem, ListableItem } 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'
|
||||||
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, { useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { GestureResponderEvent, StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
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 CoverArt from './CoverArt'
|
import CoverArt from './CoverArt'
|
||||||
import PressableOpacity from './PressableOpacity'
|
import PressableOpacity from './PressableOpacity'
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ const TitleText = React.memo<{
|
|||||||
|
|
||||||
const ListItem: React.FC<{
|
const ListItem: React.FC<{
|
||||||
item: ListableItem
|
item: ListableItem
|
||||||
onPress?: (event: GestureResponderEvent) => void
|
onPress?: () => void
|
||||||
showArt?: boolean
|
showArt?: boolean
|
||||||
showStar?: boolean
|
showStar?: boolean
|
||||||
listStyle?: 'big' | 'small'
|
listStyle?: 'big' | 'small'
|
||||||
@ -81,9 +82,31 @@ const ListItem: React.FC<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemPressable = useCallback(
|
||||||
|
({ children }) => (
|
||||||
|
<PressableOpacity onPress={onPress} style={styles.item}>
|
||||||
|
{children}
|
||||||
|
</PressableOpacity>
|
||||||
|
),
|
||||||
|
[onPress],
|
||||||
|
)
|
||||||
|
const albumPressable = useCallback(
|
||||||
|
({ children }) => (
|
||||||
|
<AlbumContextPressable album={item as AlbumListItem} onPress={onPress} triggerWrapperStyle={styles.item}>
|
||||||
|
{children}
|
||||||
|
</AlbumContextPressable>
|
||||||
|
),
|
||||||
|
[item, onPress],
|
||||||
|
)
|
||||||
|
|
||||||
|
let PressableComponent = itemPressable
|
||||||
|
if (item.itemType === 'album') {
|
||||||
|
PressableComponent = albumPressable
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, sizeStyle.container]}>
|
<View style={[styles.container, sizeStyle.container]}>
|
||||||
<PressableOpacity onPress={onPress} style={styles.item}>
|
<PressableComponent>
|
||||||
{showArt ? (
|
{showArt ? (
|
||||||
<CoverArt
|
<CoverArt
|
||||||
{...artSource}
|
{...artSource}
|
||||||
@ -117,7 +140,7 @@ const ListItem: React.FC<{
|
|||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</PressableOpacity>
|
</PressableComponent>
|
||||||
<View style={styles.controls}>
|
<View style={styles.controls}>
|
||||||
{showStar ? (
|
{showStar ? (
|
||||||
<PressableOpacity onPress={() => setStarred(!starred)} style={styles.controlItem}>
|
<PressableOpacity onPress={() => setStarred(!starred)} style={styles.controlItem}>
|
||||||
@ -146,6 +169,7 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
art: {
|
art: {
|
||||||
marginRight: 10,
|
marginRight: 10,
|
||||||
|
|||||||
@ -43,6 +43,12 @@ const PressableOpacity: React.FC<PressableOpacityProps> = props => {
|
|||||||
if (!props.disabled) {
|
if (!props.disabled) {
|
||||||
setOpacity(1)
|
setOpacity(1)
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
|
onLongPress={data => {
|
||||||
|
if (!props.disabled) {
|
||||||
|
setOpacity(1)
|
||||||
|
props.onLongPress ? props.onLongPress(data) : null
|
||||||
|
}
|
||||||
}}>
|
}}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export interface AlbumListItem {
|
|||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
artist?: string
|
artist?: string
|
||||||
|
artistId?: string
|
||||||
starred?: Date
|
starred?: Date
|
||||||
coverArt?: string
|
coverArt?: string
|
||||||
}
|
}
|
||||||
@ -148,6 +149,7 @@ export function mapAlbumID3toAlbumListItem(album: AlbumID3Element): AlbumListIte
|
|||||||
id: album.id,
|
id: album.id,
|
||||||
name: album.name,
|
name: album.name,
|
||||||
artist: album.artist,
|
artist: album.artist,
|
||||||
|
artistId: album.artistId,
|
||||||
starred: album.starred,
|
starred: album.starred,
|
||||||
coverArt: album.coverArt,
|
coverArt: album.coverArt,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
import { AlbumContextPressable } from '@app/components/ContextMenu'
|
||||||
import CoverArt from '@app/components/CoverArt'
|
import CoverArt from '@app/components/CoverArt'
|
||||||
|
import GradientBackground from '@app/components/GradientBackground'
|
||||||
import GradientScrollView from '@app/components/GradientScrollView'
|
import GradientScrollView from '@app/components/GradientScrollView'
|
||||||
import Header from '@app/components/Header'
|
import Header from '@app/components/Header'
|
||||||
import ListItem from '@app/components/ListItem'
|
import ListItem from '@app/components/ListItem'
|
||||||
import PressableOpacity from '@app/components/PressableOpacity'
|
|
||||||
import { useArtistInfo } from '@app/hooks/music'
|
import { useArtistInfo } from '@app/hooks/music'
|
||||||
import { useSetQueue } from '@app/hooks/trackplayer'
|
import { useSetQueue } from '@app/hooks/trackplayer'
|
||||||
import { Album, Song } from '@app/models/music'
|
import { Album, Song } from '@app/models/music'
|
||||||
@ -11,7 +12,7 @@ import font from '@app/styles/font'
|
|||||||
import { useLayout } from '@react-native-community/hooks'
|
import { useLayout } from '@react-native-community/hooks'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
|
|
||||||
const AlbumItem = React.memo<{
|
const AlbumItem = React.memo<{
|
||||||
@ -21,14 +22,20 @@ const AlbumItem = React.memo<{
|
|||||||
}>(({ album, height, width }) => {
|
}>(({ album, height, width }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
|
if (height <= 0 || width <= 0) {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PressableOpacity
|
<AlbumContextPressable
|
||||||
|
album={album}
|
||||||
onPress={() => navigation.navigate('album', { id: album.id, title: album.name })}
|
onPress={() => navigation.navigate('album', { id: album.id, title: album.name })}
|
||||||
style={[styles.albumItem, { width }]}>
|
menuStyle={[styles.albumItem, { width }]}
|
||||||
|
triggerOuterWrapperStyle={{ width }}>
|
||||||
<CoverArt coverArt={album.coverArt} style={{ height, width }} resizeMode={FastImage.resizeMode.cover} />
|
<CoverArt coverArt={album.coverArt} style={{ height, width }} resizeMode={FastImage.resizeMode.cover} />
|
||||||
<Text style={styles.albumTitle}>{album.name}</Text>
|
<Text style={styles.albumTitle}>{album.name}</Text>
|
||||||
<Text style={styles.albumYear}> {album.year ? album.year : ''}</Text>
|
<Text style={styles.albumYear}> {album.year ? album.year : ''}</Text>
|
||||||
</PressableOpacity>
|
</AlbumContextPressable>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -54,6 +61,12 @@ const TopSongs = React.memo<{
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const ArtistDetailsFallback = React.memo(() => (
|
||||||
|
<GradientBackground style={styles.fallback}>
|
||||||
|
<ActivityIndicator size="large" color={colors.accent} />
|
||||||
|
</GradientBackground>
|
||||||
|
))
|
||||||
|
|
||||||
const ArtistDetails: React.FC<{ id: string }> = ({ id }) => {
|
const ArtistDetails: React.FC<{ id: string }> = ({ id }) => {
|
||||||
const artist = useArtistInfo(id)
|
const artist = useArtistInfo(id)
|
||||||
const albumsLayout = useLayout()
|
const albumsLayout = useLayout()
|
||||||
@ -62,7 +75,7 @@ const ArtistDetails: React.FC<{ id: string }> = ({ id }) => {
|
|||||||
const albumSize = albumsLayout.width / 2 - styles.container.paddingHorizontal / 2
|
const albumSize = albumsLayout.width / 2 - styles.container.paddingHorizontal / 2
|
||||||
|
|
||||||
if (!artist) {
|
if (!artist) {
|
||||||
return <></>
|
return <ArtistDetailsFallback />
|
||||||
}
|
}
|
||||||
|
|
||||||
const _albums = [...artist.albums]
|
const _albums = [...artist.albums]
|
||||||
@ -117,6 +130,10 @@ const styles = StyleSheet.create({
|
|||||||
scroll: {
|
scroll: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
fallback: {
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: 100,
|
||||||
|
},
|
||||||
scrollContent: {
|
scrollContent: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
import { AlbumContextPressable } from '@app/components/ContextMenu'
|
||||||
import CoverArt from '@app/components/CoverArt'
|
import CoverArt from '@app/components/CoverArt'
|
||||||
import GradientScrollView from '@app/components/GradientScrollView'
|
import GradientScrollView from '@app/components/GradientScrollView'
|
||||||
import Header from '@app/components/Header'
|
import Header from '@app/components/Header'
|
||||||
import NothingHere from '@app/components/NothingHere'
|
import NothingHere from '@app/components/NothingHere'
|
||||||
import PressableOpacity from '@app/components/PressableOpacity'
|
|
||||||
import { useActiveListRefresh2 } from '@app/hooks/server'
|
import { useActiveListRefresh2 } from '@app/hooks/server'
|
||||||
import { AlbumListItem } from '@app/models/music'
|
import { AlbumListItem } from '@app/models/music'
|
||||||
import { selectMusic } from '@app/state/music'
|
import { selectMusic } from '@app/state/music'
|
||||||
@ -14,6 +14,7 @@ import { GetAlbumListType } from '@app/subsonic/params'
|
|||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { RefreshControl, ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native'
|
import { RefreshControl, ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import FastImage from 'react-native-fast-image'
|
||||||
|
|
||||||
const titles: { [key in GetAlbumListType]?: string } = {
|
const titles: { [key in GetAlbumListType]?: string } = {
|
||||||
recent: 'Recent Albums',
|
recent: 'Recent Albums',
|
||||||
@ -28,18 +29,22 @@ const AlbumItem = React.memo<{
|
|||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PressableOpacity
|
<AlbumContextPressable
|
||||||
onPress={() => navigation.navigate('album', { id: album.id, title: album.name })}
|
album={album}
|
||||||
key={album.id}
|
triggerWrapperStyle={styles.item}
|
||||||
style={styles.item}>
|
onPress={() => navigation.navigate('album', { id: album.id, title: album.name })}>
|
||||||
<CoverArt coverArt={album.coverArt} style={{ height: styles.item.width, width: styles.item.width }} />
|
<CoverArt
|
||||||
|
coverArt={album.coverArt}
|
||||||
|
style={{ height: styles.item.width, width: styles.item.width }}
|
||||||
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
|
/>
|
||||||
<Text style={styles.title} numberOfLines={1}>
|
<Text style={styles.title} numberOfLines={1}>
|
||||||
{album.name}
|
{album.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.subtitle} numberOfLines={1}>
|
<Text style={styles.subtitle} numberOfLines={1}>
|
||||||
{album.artist}
|
{album.artist}
|
||||||
</Text>
|
</Text>
|
||||||
</PressableOpacity>
|
</AlbumContextPressable>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -138,9 +143,9 @@ const styles = StyleSheet.create({
|
|||||||
paddingLeft: 20,
|
paddingLeft: 20,
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
|
flex: 1,
|
||||||
marginRight: 10,
|
marginRight: 10,
|
||||||
width: 150,
|
width: 150,
|
||||||
alignItems: 'flex-start',
|
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontFamily: font.semiBold,
|
fontFamily: font.semiBold,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
import { AlbumContextPressable } from '@app/components/ContextMenu'
|
||||||
import CoverArt from '@app/components/CoverArt'
|
import CoverArt from '@app/components/CoverArt'
|
||||||
import GradientFlatList from '@app/components/GradientFlatList'
|
import GradientFlatList from '@app/components/GradientFlatList'
|
||||||
import PressableOpacity from '@app/components/PressableOpacity'
|
|
||||||
import { useActiveListRefresh2 } from '@app/hooks/server'
|
import { useActiveListRefresh2 } from '@app/hooks/server'
|
||||||
import { Album } from '@app/models/music'
|
import { Album, AlbumListItem } 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'
|
||||||
@ -13,44 +13,37 @@ import { StyleSheet, Text, useWindowDimensions, View } from 'react-native'
|
|||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
|
|
||||||
const AlbumItem = React.memo<{
|
const AlbumItem = React.memo<{
|
||||||
id: string
|
album: AlbumListItem
|
||||||
name: string
|
|
||||||
size: number
|
size: number
|
||||||
height: number
|
height: number
|
||||||
artist?: string
|
}>(({ album, size, height }) => {
|
||||||
coverArt?: string
|
|
||||||
}>(({ id, name, artist, size, height, coverArt }) => {
|
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PressableOpacity
|
<AlbumContextPressable
|
||||||
style={[styles.item, { maxWidth: size, height }]}
|
album={album}
|
||||||
onPress={() => navigation.navigate('album', { id, title: name })}>
|
triggerWrapperStyle={[styles.item, { maxWidth: size, height }]}
|
||||||
<CoverArt coverArt={coverArt} style={{ height: size, width: size }} resizeMode={FastImage.resizeMode.cover} />
|
onPress={() => navigation.navigate('album', { id: album.id, title: album.name })}>
|
||||||
|
<CoverArt
|
||||||
|
coverArt={album.coverArt}
|
||||||
|
style={{ height: size, width: size }}
|
||||||
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
|
/>
|
||||||
<View style={styles.itemDetails}>
|
<View style={styles.itemDetails}>
|
||||||
<Text style={styles.title} numberOfLines={1}>
|
<Text style={styles.title} numberOfLines={1}>
|
||||||
{name}
|
{album.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.subtitle} numberOfLines={1}>
|
<Text style={styles.subtitle} numberOfLines={1}>
|
||||||
{artist}
|
{album.artist}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</PressableOpacity>
|
</AlbumContextPressable>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const AlbumListRenderItem: React.FC<{
|
const AlbumListRenderItem: React.FC<{
|
||||||
item: { album: Album; size: number; height: number }
|
item: { album: Album; size: number; height: number }
|
||||||
}> = ({ item }) => (
|
}> = ({ item }) => <AlbumItem album={item.album} size={item.size} height={item.height} />
|
||||||
<AlbumItem
|
|
||||||
id={item.album.id}
|
|
||||||
coverArt={item.album.coverArt}
|
|
||||||
name={item.album.name}
|
|
||||||
artist={item.album.artist}
|
|
||||||
size={item.size}
|
|
||||||
height={item.height}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const AlbumsList = () => {
|
const AlbumsList = () => {
|
||||||
const list = useStore(selectMusic.albums)
|
const list = useStore(selectMusic.albums)
|
||||||
@ -96,14 +89,14 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
alignItems: 'center',
|
// alignItems: 'center',
|
||||||
marginVertical: 4,
|
marginVertical: 4,
|
||||||
marginHorizontal: 3,
|
marginHorizontal: 3,
|
||||||
flex: 1 / 3,
|
flex: 1 / 3,
|
||||||
},
|
},
|
||||||
itemDetails: {
|
itemDetails: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
width: '100%',
|
// width: '100%',
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
"react-native-get-random-values": "^1.7.0",
|
"react-native-get-random-values": "^1.7.0",
|
||||||
"react-native-image-colors": "^1.3.0",
|
"react-native-image-colors": "^1.3.0",
|
||||||
"react-native-linear-gradient": "^2.5.6",
|
"react-native-linear-gradient": "^2.5.6",
|
||||||
|
"react-native-popup-menu": "^0.15.11",
|
||||||
"react-native-reanimated": "^2.2.0",
|
"react-native-reanimated": "^2.2.0",
|
||||||
"react-native-safe-area-context": "^3.2.0",
|
"react-native-safe-area-context": "^3.2.0",
|
||||||
"react-native-screens": "^3.4.0",
|
"react-native-screens": "^3.4.0",
|
||||||
|
|||||||
@ -5549,6 +5549,11 @@ react-native-linear-gradient@^2.5.6:
|
|||||||
resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.5.6.tgz#96215cbc5ec7a01247a20890888aa75b834d44a0"
|
resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.5.6.tgz#96215cbc5ec7a01247a20890888aa75b834d44a0"
|
||||||
integrity sha512-HDwEaXcQIuXXCV70O+bK1rizFong3wj+5Q/jSyifKFLg0VWF95xh8XQgfzXwtq0NggL9vNjPKXa016KuFu+VFg==
|
integrity sha512-HDwEaXcQIuXXCV70O+bK1rizFong3wj+5Q/jSyifKFLg0VWF95xh8XQgfzXwtq0NggL9vNjPKXa016KuFu+VFg==
|
||||||
|
|
||||||
|
react-native-popup-menu@^0.15.11:
|
||||||
|
version "0.15.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-popup-menu/-/react-native-popup-menu-0.15.11.tgz#df96b1a909ecbba84487821061ce6e29e7c7bb20"
|
||||||
|
integrity sha512-f5q2GoDN99bkA24wHiwasaErcdQEgyqYZ8IYuZPOrZNFr66E4rg6f4LElSVBA3EZJTSq5OddVeaOcU340bSTEg==
|
||||||
|
|
||||||
react-native-reanimated@^2.2.0:
|
react-native-reanimated@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.2.0.tgz#a6412c56b4e591d1f00fac949f62d0c72c357c78"
|
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.2.0.tgz#a6412c56b4e591d1f00fac949f62d0c72c357c78"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user