From dba87247d86826d3c113f0e5f79e87a3271789e1 Mon Sep 17 00:00:00 2001 From: Lei Nelissen Date: Sun, 23 Apr 2023 01:04:30 +0200 Subject: [PATCH] feat: add extra metadata to the album view --- src/components/Typography.tsx | 6 ++ src/localisation/lang/en/locale.json | 3 +- src/localisation/lang/nl/locale.json | 3 +- src/localisation/types.ts | 3 +- src/screens/Music/stacks/Album.tsx | 70 +++++++++++++++++-- .../Music/stacks/components/AlbumImage.tsx | 2 +- .../Music/stacks/components/TrackListView.tsx | 15 +++- src/screens/Settings/components/Cache.tsx | 4 +- src/screens/Settings/components/Library.tsx | 8 +-- src/screens/Settings/components/Sentry.tsx | 10 +-- src/store/music/actions.ts | 8 +++ src/store/music/index.ts | 12 +++- src/store/music/types.ts | 6 ++ src/utility/JellyfinApi.ts | 11 ++- src/utility/ticksToDuration.ts | 4 +- 15 files changed, 135 insertions(+), 30 deletions(-) diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx index b1d60879..52834d26 100644 --- a/src/components/Typography.tsx +++ b/src/components/Typography.tsx @@ -25,3 +25,9 @@ export const SubHeader = styled(Text)` font-weight: 400; opacity: 0.5; `; + +export const Paragraph = styled(Text)` + opacity: 0.5; + font-size: 12px; + line-height: 20px; +`; \ No newline at end of file diff --git a/src/localisation/lang/en/locale.json b/src/localisation/lang/en/locale.json index 603fbe11..b5eb8bac 100644 --- a/src/localisation/lang/en/locale.json +++ b/src/localisation/lang/en/locale.json @@ -59,5 +59,6 @@ "you-are-offline-message": "You are currently offline. You can only play previously downloaded music.", "playing-on": "Playing on", "local-playback": "Local playback", - "streaming": "Streaming" + "streaming": "Streaming", + "total-duration": "Total duration" } \ No newline at end of file diff --git a/src/localisation/lang/nl/locale.json b/src/localisation/lang/nl/locale.json index 8dacee03..210d8512 100644 --- a/src/localisation/lang/nl/locale.json +++ b/src/localisation/lang/nl/locale.json @@ -59,5 +59,6 @@ "you-are-offline-message": "Je bent op dit moment offline. Je kunt alleen eerder gedownloade nummers afspelen.", "playing-on": "Speelt af op", "local-playback": "Lokaal afspelen", - "streaming": "Streamen" + "streaming": "Streamen", + "total-duration": "Totale duur" } \ No newline at end of file diff --git a/src/localisation/types.ts b/src/localisation/types.ts index 5ae9922b..1a9690bc 100644 --- a/src/localisation/types.ts +++ b/src/localisation/types.ts @@ -57,4 +57,5 @@ export type LocaleKeys = 'play-next' | 'you-are-offline-message' | 'playing-on' | 'local-playback' -| 'streaming' \ No newline at end of file +| 'streaming' +| 'total-duration' \ No newline at end of file diff --git a/src/screens/Music/stacks/Album.tsx b/src/screens/Music/stacks/Album.tsx index c876c679..09c84a69 100644 --- a/src/screens/Music/stacks/Album.tsx +++ b/src/screens/Music/stacks/Album.tsx @@ -1,15 +1,56 @@ -import React, { useCallback, useEffect } from 'react'; -import { useRoute, RouteProp } from '@react-navigation/native'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useRoute, RouteProp, useNavigation } from '@react-navigation/native'; import { useAppDispatch, useTypedSelector } from 'store'; import TrackListView from './components/TrackListView'; -import { fetchTracksByAlbum } from 'store/music/actions'; +import { fetchAlbum, fetchTracksByAlbum } from 'store/music/actions'; import { differenceInDays } from 'date-fns'; import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS'; import { t } from '@localisation'; -import { StackParams } from 'screens/types'; +import { NavigationProp, StackParams } from 'screens/types'; +import { SubHeader, Text } from 'components/Typography'; +import { ScrollView } from 'react-native-gesture-handler'; +import { useGetImage } from 'utility/JellyfinApi'; +import styled from 'styled-components'; +import FastImage from 'react-native-fast-image'; +import { Dimensions, Pressable, useColorScheme } from 'react-native'; +import { Container } from '@shopify/react-native-skia/lib/typescript/src/renderer/Container'; +import AlbumImage from './components/AlbumImage'; type Route = RouteProp; +const Screen = Dimensions.get('screen'); + +const Cover = styled(AlbumImage)` + height: ${Screen.width / 2.8}; + width: ${Screen.width / 2.8}; + border-radius: 12px; +`; + +function SimilarAlbum({ id }: { id: string }) { + const navigation = useNavigation(); + const getImage = useGetImage(); + const album = useTypedSelector((state) => state.music.albums.entities[id]); + + const handlePress = useCallback(() => { + album && navigation.push('Album', { id, album }); + }, [id, album, navigation]); + + return ( + ({ + opacity: pressed ? 0.5 : 1.0, + width: Screen.width / 2.8, + marginRight: 12 + })} + onPress={handlePress} + > + + {album?.Name} + {album?.Artists.join(', ')} + + ); +} + const Album: React.FC = () => { const { params: { id } } = useRoute(); const dispatch = useAppDispatch(); @@ -19,7 +60,10 @@ const Album: React.FC = () => { const albumTracks = useTypedSelector((state) => state.music.tracks.byAlbum[id]); // Define a function for refreshing this entity - const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id, dispatch]); + const refresh = useCallback(() => { + dispatch(fetchTracksByAlbum(id)); + dispatch(fetchAlbum(id)); + }, [id, dispatch]); // Auto-fetch the track data periodically useEffect(() => { @@ -39,7 +83,21 @@ const Album: React.FC = () => { shuffleButtonText={t('shuffle-album')} downloadButtonText={t('download-album')} deleteButtonText={t('delete-album')} - /> + > + {album?.Overview && ( + {album?.Overview} + )} + {album?.Similar && ( + <> + Similar albums + + {album.Similar.map((id) => ( + + ))} + + + )} + ); }; diff --git a/src/screens/Music/stacks/components/AlbumImage.tsx b/src/screens/Music/stacks/components/AlbumImage.tsx index 945fce3b..de08cb41 100644 --- a/src/screens/Music/stacks/components/AlbumImage.tsx +++ b/src/screens/Music/stacks/components/AlbumImage.tsx @@ -27,7 +27,7 @@ function AlbumImage(props: FastImageProps) { if (!props.source || hasError) { return ( - + ); } diff --git a/src/screens/Music/stacks/components/TrackListView.tsx b/src/screens/Music/stacks/components/TrackListView.tsx index eaf8783c..8eb0a6d8 100644 --- a/src/screens/Music/stacks/components/TrackListView.tsx +++ b/src/screens/Music/stacks/components/TrackListView.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { PropsWithChildren, useCallback, useMemo } from 'react'; import { ScrollView, RefreshControl, StyleSheet, View } from 'react-native'; import { useGetImage } from 'utility/JellyfinApi'; import styled, { css } from 'styled-components/native'; @@ -26,6 +26,7 @@ import { Text } from 'components/Typography'; import CoverImage from 'components/CoverImage'; import ticksToDuration from 'utility/ticksToDuration'; import { useNavigatorPadding } from 'utility/SafeNavigatorView'; +import { t } from '@localisation'; const styles = StyleSheet.create({ index: { @@ -55,7 +56,7 @@ const TrackContainer = styled.View<{ isPlaying: boolean }>` `} `; -interface TrackListViewProps { +export interface TrackListViewProps extends PropsWithChildren<{}> { title?: string; artist?: string; trackIds: EntityId[]; @@ -81,6 +82,7 @@ const TrackListView: React.FC = ({ deleteButtonText, listNumberingStyle = 'album', itemDisplayStyle = 'album', + children }) => { const defaultStyles = useDefaultStyles(); const navigatorPadding = useNavigatorPadding(); @@ -89,6 +91,11 @@ const TrackListView: React.FC = ({ const tracks = useTypedSelector((state) => state.music.tracks.entities); const isLoading = useTypedSelector((state) => state.music.tracks.isLoading); const downloadedTracks = useTypedSelector(selectDownloadedTracks(trackIds)); + const totalDuration = useMemo(() => ( + trackIds.reduce((sum, trackId) => ( + sum + (tracks[trackId]?.RunTimeTicks || 0) + ), 0) + ), [trackIds, tracks]); // Retrieve helpers const getImage = useGetImage(); @@ -157,7 +164,7 @@ const TrackListView: React.FC = ({ ? i + 1 : tracks[trackId]?.IndexNumber} - + = ({ )} + {t('total-duration')}: {ticksToDuration(totalDuration)} = ({ /> + {children} ); }; diff --git a/src/screens/Settings/components/Cache.tsx b/src/screens/Settings/components/Cache.tsx index 7b3caba1..a7289c0f 100644 --- a/src/screens/Settings/components/Cache.tsx +++ b/src/screens/Settings/components/Cache.tsx @@ -4,7 +4,7 @@ import music from 'store/music'; import { t } from '@localisation'; import Button from 'components/Button'; import styled from 'styled-components/native'; -import { Text } from 'components/Typography'; +import { Paragraph } from 'components/Typography'; import { useAppDispatch } from 'store'; import { useHeaderHeight } from '@react-navigation/elements'; @@ -30,7 +30,7 @@ export default function CacheSettings() { return ( - {t('setting-cache-description')} + {t('setting-cache-description')} ); diff --git a/src/screens/Settings/components/Library.tsx b/src/screens/Settings/components/Library.tsx index e97e544a..797231ec 100644 --- a/src/screens/Settings/components/Library.tsx +++ b/src/screens/Settings/components/Library.tsx @@ -6,7 +6,7 @@ import { NavigationProp } from '../..'; import { useTypedSelector } from 'store'; import { t } from '@localisation'; import Button from 'components/Button'; -import { Text } from 'components/Typography'; +import { Paragraph } from 'components/Typography'; import { useHeaderHeight } from '@react-navigation/elements'; @@ -34,15 +34,15 @@ export default function LibrarySettings() { return ( - {t('jellyfin-server-url')} + {t('jellyfin-server-url')} - {t('jellyfin-access-token')} + {t('jellyfin-access-token')} - {t('jellyfin-user-id')} + {t('jellyfin-user-id')}