From 75e8ece60a637327bf869982d704fdd8852a6eaf Mon Sep 17 00:00:00 2001 From: Lei Nelissen Date: Sat, 1 Jan 2022 19:09:21 +0100 Subject: [PATCH] Add playlists --- src/CONSTANTS.ts | 1 + src/localisation/lang/en/locale.json | 7 +- src/localisation/lang/nl/locale.json | 2 +- src/localisation/types.ts | 7 +- src/screens/Music/index.tsx | 4 + src/screens/Music/stacks/Album.tsx | 132 +++------------ src/screens/Music/stacks/Albums.tsx | 5 +- src/screens/Music/stacks/Playlist.tsx | 44 +++++ src/screens/Music/stacks/Playlists.tsx | 111 +++++++++++++ src/screens/Music/stacks/RecentAlbums.tsx | 2 + src/screens/Music/stacks/Search.tsx | 2 +- .../Music/stacks/components/TrackListView.tsx | 154 ++++++++++++++++++ src/screens/Player/components/Queue.tsx | 5 +- .../components/CredentialGenerator.tsx | 4 +- src/store/music/actions.ts | 31 +++- src/store/music/index.ts | 72 +++++++- src/store/music/selectors.ts | 6 +- src/store/music/types.ts | 20 +++ src/utility/JellyfinApi.ts | 39 +++++ .../{usePlayAlbum.ts => usePlayTracks.ts} | 26 +-- 20 files changed, 529 insertions(+), 145 deletions(-) create mode 100644 src/screens/Music/stacks/Playlist.tsx create mode 100644 src/screens/Music/stacks/Playlists.tsx create mode 100644 src/screens/Music/stacks/components/TrackListView.tsx rename src/utility/{usePlayAlbum.ts => usePlayTracks.ts} (58%) diff --git a/src/CONSTANTS.ts b/src/CONSTANTS.ts index e7f872d..05637da 100644 --- a/src/CONSTANTS.ts +++ b/src/CONSTANTS.ts @@ -1,3 +1,4 @@ export const ALBUM_CACHE_AMOUNT_OF_DAYS = 7; +export const PLAYLIST_CACHE_AMOUNT_OF_DAYS = 7; export const ALPHABET_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '; export const THEME_COLOR = '#FF3C00'; \ No newline at end of file diff --git a/src/localisation/lang/en/locale.json b/src/localisation/lang/en/locale.json index 550bbe0..2d98441 100644 --- a/src/localisation/lang/en/locale.json +++ b/src/localisation/lang/en/locale.json @@ -39,5 +39,10 @@ "enable": "Enable", "disable": "Disable", "more-info": "More Info", - "track": "Track" + "track": "Track", + "playlists": "Playlists", + "playlist": "Playlist", + "play-playlist": "Play Playlist", + "shuffle-album": "Shuffle Album", + "shuffle-playlist": "Shuffle Playlist" } \ No newline at end of file diff --git a/src/localisation/lang/nl/locale.json b/src/localisation/lang/nl/locale.json index 0f293a2..085df1b 100644 --- a/src/localisation/lang/nl/locale.json +++ b/src/localisation/lang/nl/locale.json @@ -1,6 +1,6 @@ { "play-next": "Speel volgende", - "play-album": "Speel album", + "play-album": "Speel Album", "queue": "Wachtrij", "add-to-queue": "Voeg toe aan wachtrij", "clear-queue": "Wis wachtrij", diff --git a/src/localisation/types.ts b/src/localisation/types.ts index bfe4771..076f6b9 100644 --- a/src/localisation/types.ts +++ b/src/localisation/types.ts @@ -38,4 +38,9 @@ export type LocaleKeys = 'play-next' | 'enable' | 'disable' | 'more-info' -| 'track' \ No newline at end of file +| 'track' +| 'playlists' +| 'playlist' +| 'play-playlist' +| 'shuffle-album' +| 'shuffle-playlist' \ No newline at end of file diff --git a/src/screens/Music/index.tsx b/src/screens/Music/index.tsx index 56e39a2..491aefe 100644 --- a/src/screens/Music/index.tsx +++ b/src/screens/Music/index.tsx @@ -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(); @@ -22,6 +24,8 @@ function MusicStack() { + + ); diff --git a/src/screens/Music/stacks/Album.tsx b/src/screens/Music/stacks/Album.tsx index 0042841..82bf597 100644 --- a/src/screens/Music/stacks/Album.tsx +++ b/src/screens/Music/stacks/Album.tsx @@ -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; -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(); - 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 ( - - } - > - - {album?.Name} - {album?.AlbumArtist} -