Add playlists
This commit is contained in:
@@ -8,6 +8,8 @@ import Search from './stacks/Search';
|
||||
import { THEME_COLOR } from 'CONSTANTS';
|
||||
import { t } from '@localisation';
|
||||
import useDefaultStyles from 'components/Colors';
|
||||
import Playlists from './stacks/Playlists';
|
||||
import Playlist from './stacks/Playlist';
|
||||
|
||||
const Stack = createStackNavigator<StackParams>();
|
||||
|
||||
@@ -22,6 +24,8 @@ function MusicStack() {
|
||||
<Stack.Screen name="RecentAlbums" component={RecentAlbums} options={{ headerTitle: t('recent-albums') }} />
|
||||
<Stack.Screen name="Albums" component={Albums} options={{ headerTitle: t('albums') }} />
|
||||
<Stack.Screen name="Album" component={Album} options={{ headerTitle: t('album') }} />
|
||||
<Stack.Screen name="Playlists" component={Playlists} options={{ headerTitle: t('playlists') }} />
|
||||
<Stack.Screen name="Playlist" component={Playlist} options={{ headerTitle: t('playlist') }} />
|
||||
<Stack.Screen name="Search" component={Search} options={{ headerTitle: t('search') }} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
|
||||
@@ -1,133 +1,43 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { StackParams } from '../types';
|
||||
import { Text, ScrollView, Dimensions, RefreshControl, StyleSheet, View } from 'react-native';
|
||||
import { useGetImage } from 'utility/JellyfinApi';
|
||||
import styled, { css } from 'styled-components/native';
|
||||
import { useRoute, RouteProp, useNavigation } from '@react-navigation/native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { useTypedSelector } from 'store';
|
||||
import { useRoute, RouteProp } from '@react-navigation/native';
|
||||
import { useAppDispatch, useTypedSelector } from 'store';
|
||||
import TrackListView from './components/TrackListView';
|
||||
import { fetchTracksByAlbum } from 'store/music/actions';
|
||||
import { ALBUM_CACHE_AMOUNT_OF_DAYS, THEME_COLOR } from 'CONSTANTS';
|
||||
import usePlayAlbum from 'utility/usePlayAlbum';
|
||||
import TouchableHandler from 'components/TouchableHandler';
|
||||
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||
import TrackPlayer from 'react-native-track-player';
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
|
||||
import { t } from '@localisation';
|
||||
import Button from 'components/Button';
|
||||
import Play from 'assets/play.svg';
|
||||
import useDefaultStyles from 'components/Colors';
|
||||
|
||||
type Route = RouteProp<StackParams, 'Album'>;
|
||||
|
||||
const Screen = Dimensions.get('screen');
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
name: {
|
||||
fontSize: 36,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
artist: {
|
||||
fontSize: 24,
|
||||
opacity: 0.5,
|
||||
marginBottom: 24
|
||||
},
|
||||
index: {
|
||||
width: 20,
|
||||
opacity: 0.5,
|
||||
marginRight: 5
|
||||
}
|
||||
});
|
||||
|
||||
const AlbumImage = styled(FastImage)`
|
||||
border-radius: 10px;
|
||||
width: ${Screen.width * 0.6}px;
|
||||
height: ${Screen.width * 0.6}px;
|
||||
margin: 10px auto;
|
||||
`;
|
||||
|
||||
const TrackContainer = styled.View<{isPlaying: boolean}>`
|
||||
padding: 15px;
|
||||
border-bottom-width: 1px;
|
||||
flex-direction: row;
|
||||
|
||||
${props => props.isPlaying && css`
|
||||
background-color: ${THEME_COLOR}16;
|
||||
margin: 0 -20px;
|
||||
padding: 15px 35px;
|
||||
`}
|
||||
`;
|
||||
|
||||
const Album: React.FC = () => {
|
||||
const defaultStyles = useDefaultStyles();
|
||||
|
||||
// Retrieve state
|
||||
const { params: { id } } = useRoute<Route>();
|
||||
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// Retrieve the album data from the store
|
||||
const album = useTypedSelector((state) => state.music.albums.entities[id]);
|
||||
const isLoading = useTypedSelector((state) => state.music.tracks.isLoading);
|
||||
const albumTracks = useTypedSelector((state) => state.music.tracks.byAlbum[id]);
|
||||
|
||||
// Retrieve helpers
|
||||
const dispatch = useDispatch();
|
||||
const getImage = useGetImage();
|
||||
const playAlbum = usePlayAlbum();
|
||||
const { track: currentTrack } = useCurrentTrack();
|
||||
const navigation = useNavigation();
|
||||
// Define a function for refreshing this entity
|
||||
const refresh = useCallback(() => dispatch(fetchTracksByAlbum(id)), [id, dispatch]);
|
||||
|
||||
// Setup callbacks
|
||||
const selectAlbum = useCallback(() => { playAlbum(id); }, [playAlbum, id]);
|
||||
const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id, dispatch]);
|
||||
const selectTrack = useCallback(async (index: number) => {
|
||||
await playAlbum(id, false);
|
||||
await TrackPlayer.skip(index);
|
||||
await TrackPlayer.play();
|
||||
}, [playAlbum, id]);
|
||||
const longPressTrack = useCallback((index: number) => {
|
||||
navigation.navigate('TrackPopupMenu', { trackId: album?.Tracks?.[index] });
|
||||
}, [navigation, album]);
|
||||
|
||||
// Retrieve album tracks on load
|
||||
// Auto-fetch the track data periodically
|
||||
useEffect(() => {
|
||||
if (!album?.lastRefreshed || differenceInDays(album?.lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
|
||||
refresh();
|
||||
}
|
||||
}, [album?.lastRefreshed, refresh]);
|
||||
|
||||
// GUARD: If there is no album, we cannot render a thing
|
||||
if (!album) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={{ padding: 20, paddingBottom: 50 }}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
|
||||
}
|
||||
>
|
||||
<AlbumImage source={{ uri: getImage(album?.Id) }} style={defaultStyles.imageBackground} />
|
||||
<Text style={[ defaultStyles.text, styles.name ]} >{album?.Name}</Text>
|
||||
<Text style={[ defaultStyles.text, styles.artist ]}>{album?.AlbumArtist}</Text>
|
||||
<Button title={t('play-album')} icon={Play} onPress={selectAlbum} />
|
||||
<View style={{ marginTop: 15 }}>
|
||||
{album?.Tracks?.length ? album.Tracks.map((trackId, i) =>
|
||||
<TouchableHandler
|
||||
key={trackId}
|
||||
id={i}
|
||||
onPress={selectTrack}
|
||||
onLongPress={longPressTrack}
|
||||
>
|
||||
<TrackContainer isPlaying={currentTrack?.backendId === trackId || false} style={defaultStyles.border}>
|
||||
<Text style={[ defaultStyles.text, styles.index ]}>
|
||||
{tracks[trackId]?.IndexNumber}
|
||||
</Text>
|
||||
<Text style={defaultStyles.text}>{tracks[trackId]?.Name}</Text>
|
||||
</TrackContainer>
|
||||
</TouchableHandler>
|
||||
) : undefined}
|
||||
</View>
|
||||
</ScrollView>
|
||||
<TrackListView
|
||||
trackIds={albumTracks || []}
|
||||
title={album?.Name}
|
||||
artist={album?.AlbumArtist}
|
||||
entityId={id}
|
||||
refresh={refresh}
|
||||
playButtonText={t('play-album')}
|
||||
shuffleButtonText={t('shuffle-album')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useRef, ReactText } from 'react';
|
||||
import { useGetImage } from 'utility/JellyfinApi';
|
||||
import { Album, NavigationProp } from '../types';
|
||||
import { NavigationProp } from '../types';
|
||||
import { Text, SafeAreaView, SectionList, View } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
@@ -15,6 +15,7 @@ import AlphabetScroller from 'components/AlphabetScroller';
|
||||
import { EntityId } from '@reduxjs/toolkit';
|
||||
import styled from 'styled-components/native';
|
||||
import useDefaultStyles from 'components/Colors';
|
||||
import { Album } from 'store/music/types';
|
||||
|
||||
interface VirtualizedItemInfo {
|
||||
section: SectionedId,
|
||||
@@ -92,7 +93,7 @@ const Albums: React.FC = () => {
|
||||
// Retrieve data from store
|
||||
const { entities: albums } = useTypedSelector((state) => state.music.albums);
|
||||
const isLoading = useTypedSelector((state) => state.music.albums.isLoading);
|
||||
const lastRefreshed = useTypedSelector((state) => state.music.lastRefreshed);
|
||||
const lastRefreshed = useTypedSelector((state) => state.music.albums.lastRefreshed);
|
||||
const sections = useTypedSelector(selectAlbumsByAlphabet);
|
||||
|
||||
// Initialise helpers
|
||||
|
||||
44
src/screens/Music/stacks/Playlist.tsx
Normal file
44
src/screens/Music/stacks/Playlist.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { StackParams } from '../types';
|
||||
import { useRoute, RouteProp } from '@react-navigation/native';
|
||||
import { useAppDispatch, useTypedSelector } from 'store';
|
||||
import TrackListView from './components/TrackListView';
|
||||
import { fetchTracksByPlaylist } from 'store/music/actions';
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
|
||||
import { t } from '@localisation';
|
||||
|
||||
type Route = RouteProp<StackParams, 'Album'>;
|
||||
|
||||
const Playlist: React.FC = () => {
|
||||
const { params: { id } } = useRoute<Route>();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// Retrieve the album data from the store
|
||||
const playlist = useTypedSelector((state) => state.music.playlists.entities[id]);
|
||||
const playlistTracks = useTypedSelector((state) => state.music.tracks.byPlaylist[id]);
|
||||
|
||||
// Define a function for refreshing this entity
|
||||
const refresh = useCallback(() => dispatch(fetchTracksByPlaylist(id)), [dispatch, id]);
|
||||
|
||||
// Auto-fetch the track data periodically
|
||||
useEffect(() => {
|
||||
if (!playlist?.lastRefreshed || differenceInDays(playlist?.lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
|
||||
refresh();
|
||||
}
|
||||
}, [playlist?.lastRefreshed, refresh]);
|
||||
|
||||
return (
|
||||
<TrackListView
|
||||
trackIds={playlistTracks || []}
|
||||
title={playlist?.Name}
|
||||
entityId={id}
|
||||
refresh={refresh}
|
||||
listNumberingStyle='index'
|
||||
playButtonText={t('play-playlist')}
|
||||
shuffleButtonText={t('shuffle-playlist')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Playlist;
|
||||
111
src/screens/Music/stacks/Playlists.tsx
Normal file
111
src/screens/Music/stacks/Playlists.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import React, { useCallback, useEffect, useRef, ReactText } from 'react';
|
||||
import { useGetImage } from 'utility/JellyfinApi';
|
||||
import { NavigationProp } from '../types';
|
||||
import { Text, SafeAreaView, View, FlatList, ListRenderItem } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { useTypedSelector } from 'store';
|
||||
import { fetchAllPlaylists } from 'store/music/actions';
|
||||
import { PLAYLIST_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
|
||||
import TouchableHandler from 'components/TouchableHandler';
|
||||
import AlbumImage, { AlbumItem } from './components/AlbumImage';
|
||||
import { EntityId } from '@reduxjs/toolkit';
|
||||
import useDefaultStyles from 'components/Colors';
|
||||
import { Playlist } from 'store/music/types';
|
||||
|
||||
interface GeneratedAlbumItemProps {
|
||||
id: ReactText;
|
||||
imageUrl: string;
|
||||
name: string;
|
||||
onPress: (id: string) => void;
|
||||
}
|
||||
|
||||
const GeneratedPlaylistItem = React.memo(function GeneratedPlaylistItem(props: GeneratedAlbumItemProps) {
|
||||
const defaultStyles = useDefaultStyles();
|
||||
const { id, imageUrl, name, onPress } = props;
|
||||
|
||||
return (
|
||||
<TouchableHandler id={id as string} onPress={onPress}>
|
||||
<AlbumItem>
|
||||
<AlbumImage source={{ uri: imageUrl }} style={defaultStyles.imageBackground} />
|
||||
<Text numberOfLines={1} style={defaultStyles.text}>{name}</Text>
|
||||
</AlbumItem>
|
||||
</TouchableHandler>
|
||||
);
|
||||
});
|
||||
|
||||
const Playlists: React.FC = () => {
|
||||
// Retrieve data from store
|
||||
const { entities, ids } = useTypedSelector((state) => state.music.playlists);
|
||||
const isLoading = useTypedSelector((state) => state.music.playlists.isLoading);
|
||||
const lastRefreshed = useTypedSelector((state) => state.music.playlists.lastRefreshed);
|
||||
|
||||
// Initialise helpers
|
||||
const dispatch = useDispatch();
|
||||
const navigation = useNavigation<NavigationProp>();
|
||||
const getImage = useGetImage();
|
||||
const listRef = useRef<FlatList<EntityId>>(null);
|
||||
|
||||
const getItemLayout = useCallback((data: EntityId[] | null | undefined, index: number): { offset: number, length: number, index: number } => {
|
||||
const length = 220;
|
||||
const offset = length * index;
|
||||
return { index, length, offset };
|
||||
}, []);
|
||||
|
||||
// Set callbacks
|
||||
const retrieveData = useCallback(() => dispatch(fetchAllPlaylists()), [dispatch]);
|
||||
const selectAlbum = useCallback((id: string) => navigation.navigate('Playlist', { id, playlist: entities[id] as Playlist }), [navigation, entities]);
|
||||
const generateItem: ListRenderItem<EntityId> = useCallback(({ item, index }) => {
|
||||
if (index % 2 === 1) {
|
||||
return <View key={item} />;
|
||||
}
|
||||
|
||||
const nextItemId = ids[index + 1];
|
||||
const nextItem = entities[nextItemId];
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', marginLeft: 10, marginRight: 10 }} key={item}>
|
||||
<GeneratedPlaylistItem
|
||||
id={item}
|
||||
imageUrl={getImage(item as string)}
|
||||
name={entities[item]?.Name || ''}
|
||||
onPress={selectAlbum}
|
||||
/>
|
||||
{nextItem &&
|
||||
<GeneratedPlaylistItem
|
||||
id={nextItemId}
|
||||
imageUrl={getImage(nextItemId as string)}
|
||||
name={nextItem.Name || ''}
|
||||
onPress={selectAlbum}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}, [entities, getImage, selectAlbum, ids]);
|
||||
|
||||
// Retrieve data on mount
|
||||
useEffect(() => {
|
||||
// GUARD: Only refresh this API call every set amounts of days
|
||||
if (!lastRefreshed || differenceInDays(lastRefreshed, new Date()) > PLAYLIST_CACHE_AMOUNT_OF_DAYS) {
|
||||
retrieveData();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<FlatList
|
||||
data={ids}
|
||||
refreshing={isLoading}
|
||||
onRefresh={retrieveData}
|
||||
getItemLayout={getItemLayout}
|
||||
ref={listRef}
|
||||
keyExtractor={(item, index) => `${item}_${index}`}
|
||||
renderItem={generateItem}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default Playlists;
|
||||
@@ -27,11 +27,13 @@ const NavigationHeader: React.FC = () => {
|
||||
const navigation = useNavigation();
|
||||
const defaultStyles = useDefaultStyles();
|
||||
const handleAllAlbumsClick = useCallback(() => { navigation.navigate('Albums'); }, [navigation]);
|
||||
const handlePlaylistsClick = useCallback(() => { navigation.navigate('Playlists'); }, [navigation]);
|
||||
const handleSearchClick = useCallback(() => { navigation.navigate('Search'); }, [navigation]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListButton onPress={handleAllAlbumsClick}>{t('all-albums')}</ListButton>
|
||||
<ListButton onPress={handlePlaylistsClick}>{t('playlists')}</ListButton>
|
||||
<ListButton onPress={handleSearchClick}>{t('search')}</ListButton>
|
||||
<ListContainer>
|
||||
<Header style={defaultStyles.text}>{t('recent-albums')}</Header>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef, useCallback, Ref } from 'react';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import Input from 'components/Input';
|
||||
import { ActivityIndicator, Text, TextInput, View } from 'react-native';
|
||||
import styled from 'styled-components/native';
|
||||
|
||||
154
src/screens/Music/stacks/components/TrackListView.tsx
Normal file
154
src/screens/Music/stacks/components/TrackListView.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Text, ScrollView, Dimensions, RefreshControl, StyleSheet, View } from 'react-native';
|
||||
import { useGetImage } from 'utility/JellyfinApi';
|
||||
import styled, { css } from 'styled-components/native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { useTypedSelector } from 'store';
|
||||
import { THEME_COLOR } from 'CONSTANTS';
|
||||
import TouchableHandler from 'components/TouchableHandler';
|
||||
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||
import TrackPlayer from 'react-native-track-player';
|
||||
import Button from 'components/Button';
|
||||
import Play from 'assets/play.svg';
|
||||
import Shuffle from 'assets/shuffle.svg';
|
||||
import useDefaultStyles from 'components/Colors';
|
||||
import usePlayTracks from 'utility/usePlayTracks';
|
||||
import { EntityId } from '@reduxjs/toolkit';
|
||||
|
||||
const Screen = Dimensions.get('screen');
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
name: {
|
||||
fontSize: 36,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
artist: {
|
||||
fontSize: 24,
|
||||
opacity: 0.5,
|
||||
marginBottom: 24
|
||||
},
|
||||
index: {
|
||||
width: 20,
|
||||
opacity: 0.5,
|
||||
marginRight: 5
|
||||
}
|
||||
});
|
||||
|
||||
const AlbumImage = styled(FastImage)`
|
||||
border-radius: 10px;
|
||||
width: ${Screen.width * 0.6}px;
|
||||
height: ${Screen.width * 0.6}px;
|
||||
margin: 10px auto;
|
||||
`;
|
||||
|
||||
const TrackContainer = styled.View<{isPlaying: boolean}>`
|
||||
padding: 15px;
|
||||
border-bottom-width: 1px;
|
||||
flex-direction: row;
|
||||
|
||||
${props => props.isPlaying && css`
|
||||
background-color: ${THEME_COLOR}16;
|
||||
margin: 0 -20px;
|
||||
padding: 15px 35px;
|
||||
`}
|
||||
`;
|
||||
|
||||
interface TrackListViewProps {
|
||||
title?: string;
|
||||
artist?: string;
|
||||
trackIds: EntityId[];
|
||||
entityId: string;
|
||||
refresh: () => void;
|
||||
playButtonText: string;
|
||||
shuffleButtonText: string;
|
||||
listNumberingStyle?: 'album' | 'index';
|
||||
}
|
||||
|
||||
const TrackListView: React.FC<TrackListViewProps> = ({
|
||||
trackIds,
|
||||
entityId,
|
||||
title,
|
||||
artist,
|
||||
refresh,
|
||||
playButtonText,
|
||||
shuffleButtonText,
|
||||
listNumberingStyle = 'album',
|
||||
}) => {
|
||||
const defaultStyles = useDefaultStyles();
|
||||
|
||||
// Retrieve state
|
||||
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
||||
const isLoading = useTypedSelector((state) => state.music.tracks.isLoading);
|
||||
|
||||
// Retrieve helpers
|
||||
const getImage = useGetImage();
|
||||
const playTracks = usePlayTracks();
|
||||
const { track: currentTrack } = useCurrentTrack();
|
||||
const navigation = useNavigation();
|
||||
|
||||
// Setup callbacks
|
||||
const playEntity = useCallback(() => { playTracks(trackIds); }, [playTracks, trackIds]);
|
||||
const shuffleEntity = useCallback(() => { playTracks(trackIds, true, true); }, [playTracks, trackIds]);
|
||||
const selectTrack = useCallback(async (index: number) => {
|
||||
await playTracks(trackIds, false);
|
||||
await TrackPlayer.skip(index);
|
||||
await TrackPlayer.play();
|
||||
}, [playTracks, trackIds]);
|
||||
const longPressTrack = useCallback((index: number) => {
|
||||
navigation.navigate('TrackPopupMenu', { trackId: trackIds[index] });
|
||||
}, [navigation, trackIds]);
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={{ padding: 20, paddingBottom: 50 }}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
|
||||
}
|
||||
>
|
||||
<AlbumImage source={{ uri: getImage(entityId) }} style={defaultStyles.imageBackground} />
|
||||
<Text style={[ defaultStyles.text, styles.name ]} >{title}</Text>
|
||||
<Text style={[ defaultStyles.text, styles.artist ]}>{artist}</Text>
|
||||
<Button title={playButtonText} icon={Play} onPress={playEntity} />
|
||||
<View style={{ height: 4 }}></View>
|
||||
<Button title={shuffleButtonText} icon={Shuffle} onPress={shuffleEntity} />
|
||||
<View style={{ marginTop: 15 }}>
|
||||
{trackIds.map((trackId, i) =>
|
||||
<TouchableHandler
|
||||
key={trackId}
|
||||
id={i}
|
||||
onPress={selectTrack}
|
||||
onLongPress={longPressTrack}
|
||||
>
|
||||
<TrackContainer isPlaying={currentTrack?.backendId === trackId || false} style={defaultStyles.border}>
|
||||
<Text
|
||||
style={[
|
||||
defaultStyles.text,
|
||||
styles.index,
|
||||
currentTrack?.backendId === trackId && {
|
||||
color: THEME_COLOR,
|
||||
opacity: 1
|
||||
}
|
||||
]}
|
||||
>
|
||||
{listNumberingStyle === 'index'
|
||||
? i + 1
|
||||
: tracks[trackId]?.IndexNumber}
|
||||
</Text>
|
||||
<Text
|
||||
style={currentTrack?.backendId === trackId
|
||||
? { color: THEME_COLOR, fontWeight: '700' }
|
||||
: defaultStyles.text
|
||||
}
|
||||
>
|
||||
{tracks[trackId]?.Name}
|
||||
</Text>
|
||||
</TrackContainer>
|
||||
</TouchableHandler>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrackListView;
|
||||
Reference in New Issue
Block a user