diff --git a/app/models/settings.ts b/app/models/settings.ts index 3904ad7..af7ce2c 100644 --- a/app/models/settings.ts +++ b/app/models/settings.ts @@ -8,5 +8,8 @@ export interface Server { export interface AppSettings { servers: Server[] + home: { + lists: string[] + } activeServer?: string } diff --git a/app/screens/Home.tsx b/app/screens/Home.tsx index e302bea..d2f0ed5 100644 --- a/app/screens/Home.tsx +++ b/app/screens/Home.tsx @@ -2,13 +2,14 @@ import CoverArt from '@app/components/CoverArt' import GradientScrollView from '@app/components/GradientScrollView' import PressableOpacity from '@app/components/PressableOpacity' import { AlbumListItem } from '@app/models/music' -import { albumLists } from '@app/state/music' +import { homeListsAtom, homeListsUpdatingAtom, useUpdateHomeLists } from '@app/state/music' +import { homeListTypesAtom, useActiveServerRefresh } from '@app/state/settings' import colors from '@app/styles/colors' import font from '@app/styles/font' import { useNavigation } from '@react-navigation/native' import { useAtomValue } from 'jotai/utils' import React from 'react' -import { ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native' +import { RefreshControl, ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native' const AlbumItem = React.memo<{ album: AlbumListItem @@ -38,25 +39,18 @@ const AlbumItem = React.memo<{ const Category = React.memo<{ name: string - type: string -}>(({ name, type }) => { - const state = albumLists[type] - const list = useAtomValue(state.listAtom) - const updating = useAtomValue(state.updatingAtom) - const updateList = state.useUpdateList() - + data: AlbumListItem[] +}>(({ name, data }) => { return ( - - - {name} - + + {name} - {list.map(album => ( + {data.map(album => ( ))} @@ -64,17 +58,34 @@ const Category = React.memo<{ ) }) -const Home = () => ( - - - - - - - - - -) +const Home = () => { + const types = useAtomValue(homeListTypesAtom) + const lists = useAtomValue(homeListsAtom) + const updating = useAtomValue(homeListsUpdatingAtom) + const update = useUpdateHomeLists() + + useActiveServerRefresh(update) + + return ( + + }> + + {types.map(type => ( + + ))} + + + ) +} const styles = StyleSheet.create({ scroll: { diff --git a/app/screens/LibraryAlbums.tsx b/app/screens/LibraryAlbums.tsx index 3898df0..4f7241e 100644 --- a/app/screens/LibraryAlbums.tsx +++ b/app/screens/LibraryAlbums.tsx @@ -2,7 +2,8 @@ import CoverArt from '@app/components/CoverArt' import GradientFlatList from '@app/components/GradientFlatList' import PressableOpacity from '@app/components/PressableOpacity' import { Album } from '@app/models/music' -import { albumLists } from '@app/state/music' +import { albumListAtom, albumListUpdatingAtom, useUpdateAlbumList } from '@app/state/music' +import { useActiveServerRefresh } from '@app/state/settings' import colors from '@app/styles/colors' import font from '@app/styles/font' import { useNavigation } from '@react-navigation/native' @@ -51,15 +52,16 @@ const AlbumListRenderItem: React.FC<{ ) const AlbumsList = () => { - const state = albumLists.alphabeticalByArtist - const list = useAtomValue(state.listAtom) - const updating = useAtomValue(state.updatingAtom) - const updateList = state.useUpdateList() + const list = useAtomValue(albumListAtom) + const updating = useAtomValue(albumListUpdatingAtom) + const updateList = useUpdateAlbumList() + + useActiveServerRefresh(updateList) const layout = useWindowDimensions() const size = layout.width / 3 - styles.item.marginHorizontal * 2 - const height = size + 44 + const height = size + 38 const albumsList = list.map(album => ({ album, size, height })) @@ -105,15 +107,10 @@ const styles = StyleSheet.create({ marginVertical: 4, marginHorizontal: 2, flex: 1 / 3, - // backgroundColor: 'green', - }, - art: { - // height: 125, }, itemDetails: { flex: 1, width: '100%', - // width: 125, }, title: { fontSize: 12, diff --git a/app/state/music.ts b/app/state/music.ts index 8035e73..ffcc978 100644 --- a/app/state/music.ts +++ b/app/state/music.ts @@ -1,11 +1,11 @@ -import { Atom, atom, useAtom, WritableAtom } from 'jotai' -import { atomFamily, useAtomValue, useUpdateAtom } from 'jotai/utils' import { Album, AlbumListItem, AlbumWithSongs, Artist, ArtistArt, ArtistInfo, Song } from '@app/models/music' +import { activeServerAtom, homeListTypesAtom } from '@app/state/settings' import { SubsonicApiClient } from '@app/subsonic/api' import { AlbumID3Element, ArtistInfo2Element, ChildElement } from '@app/subsonic/elements' -import { GetArtistResponse } from '@app/subsonic/responses' -import { activeServerAtom } from '@app/state/settings' import { GetAlbumList2Type } from '@app/subsonic/params' +import { GetArtistResponse } from '@app/subsonic/responses' +import { atom, useAtom } from 'jotai' +import { atomFamily, useAtomValue, useUpdateAtom } from 'jotai/utils' export const artistsAtom = atom([]) export const artistsUpdatingAtom = atom(false) @@ -39,15 +39,26 @@ export const useUpdateArtists = () => { } } -const useUpdateAlbumListBase = ( - type: GetAlbumList2Type, - albumListAtom: WritableAtom, - updatingAtom: WritableAtom, - size: number, -) => { +export type HomeLists = { [key: string]: AlbumListItem[] } + +export const homeListsUpdatingAtom = atom(false) +export const homeListsAtom = atom({}) +const homeListsWriteAtom = atom( + get => get(homeListsAtom), + (get, set, { type, albums }) => { + const lists = get(homeListsAtom) + set(homeListsAtom, { + ...lists, + [type]: albums, + }) + }, +) + +export const useUpdateHomeLists = () => { const server = useAtomValue(activeServerAtom) - const setAlbumList = useUpdateAtom(albumListAtom) - const [updating, setUpdating] = useAtom(updatingAtom) + const types = useAtomValue(homeListTypesAtom) + const updateHomeList = useUpdateAtom(homeListsWriteAtom) + const [updating, setUpdating] = useAtom(homeListsUpdatingAtom) if (!server) { return async () => {} @@ -60,67 +71,45 @@ const useUpdateAlbumListBase = ( setUpdating(true) const client = new SubsonicApiClient(server) - const response = await client.getAlbumList2({ type, size }) - setAlbumList(response.data.albums.map(a => mapAlbumID3toAlbumListItem(a, client))) + const promises: Promise[] = [] + for (const type of types) { + promises.push( + client.getAlbumList2({ type: type as GetAlbumList2Type, size: 20 }).then(response => { + updateHomeList({ type, albums: response.data.albums.map(a => mapAlbumID3toAlbumListItem(a, client)) }) + }), + ) + } + await Promise.all(promises) + setUpdating(false) } } -function createAlbumList(type: GetAlbumList2Type, size = 20) { - const listAtom = atom([]) - const listReadAtom = atom(get => get(listAtom)) - const updatingAtom = atom(false) - const updatingReadAtom = atom(get => get(updatingAtom)) - const useUpdateAlbumList = () => useUpdateAlbumListBase(type, listAtom, updatingAtom, size) +export const albumListUpdatingAtom = atom(false) +export const albumListAtom = atom([]) - return { listAtom, listReadAtom, updatingAtom, updatingReadAtom, useUpdateAlbumList } -} +export const useUpdateAlbumList = () => { + const server = useAtomValue(activeServerAtom) + const updateList = useUpdateAtom(albumListAtom) + const [updating, setUpdating] = useAtom(albumListUpdatingAtom) -type ListState = { - listAtom: Atom - updatingAtom: Atom - useUpdateList: () => () => Promise -} + if (!server) { + return async () => {} + } -const alphabeticalByArtist = createAlbumList('alphabeticalByArtist', 500) -const recent = createAlbumList('recent') -const starred = createAlbumList('starred') -const frequent = createAlbumList('frequent') -const random = createAlbumList('random') -const newest = createAlbumList('newest') + return async () => { + if (updating) { + return + } + setUpdating(true) -export const albumLists: { [key: string]: ListState } = { - alphabeticalByArtist: { - listAtom: alphabeticalByArtist.listReadAtom, - updatingAtom: alphabeticalByArtist.updatingReadAtom, - useUpdateList: alphabeticalByArtist.useUpdateAlbumList, - }, - recent: { - listAtom: recent.listReadAtom, - updatingAtom: recent.updatingReadAtom, - useUpdateList: recent.useUpdateAlbumList, - }, - starred: { - listAtom: starred.listReadAtom, - updatingAtom: starred.updatingReadAtom, - useUpdateList: starred.useUpdateAlbumList, - }, - frequent: { - listAtom: frequent.listReadAtom, - updatingAtom: frequent.updatingReadAtom, - useUpdateList: frequent.useUpdateAlbumList, - }, - random: { - listAtom: random.listReadAtom, - updatingAtom: random.updatingReadAtom, - useUpdateList: random.useUpdateAlbumList, - }, - newest: { - listAtom: newest.listReadAtom, - updatingAtom: newest.updatingReadAtom, - useUpdateList: newest.useUpdateAlbumList, - }, + const client = new SubsonicApiClient(server) + const response = await client.getAlbumList2({ type: 'alphabeticalByArtist', size: 500 }) + + updateList(response.data.albums.map(a => mapAlbumID3toAlbumListItem(a, client))) + setUpdating(false) + } } export const albumAtomFamily = atomFamily((id: string) => diff --git a/app/state/settings.ts b/app/state/settings.ts index 8769659..2df8395 100644 --- a/app/state/settings.ts +++ b/app/state/settings.ts @@ -1,12 +1,30 @@ import { atom } from 'jotai' import { AppSettings } from '@app/models/settings' import atomWithAsyncStorage from '@app/storage/atomWithAsyncStorage' +import { useEffect } from 'react' +import { useAtomValue } from 'jotai/utils' export const appSettingsAtom = atomWithAsyncStorage('@appSettings', { servers: [], + home: { + lists: ['recent', 'random', 'frequent', 'starred'], + }, }) export const activeServerAtom = atom(get => { const appSettings = get(appSettingsAtom) return appSettings.servers.find(x => x.id === appSettings.activeServer) }) + +export const useActiveServerRefresh = (update: () => any) => { + const activeServer = useAtomValue(activeServerAtom) + + useEffect(() => { + if (activeServer) { + update() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeServer]) +} + +export const homeListTypesAtom = atom(get => get(appSettingsAtom).home.lists)