From c62a8df3df0a0433c8178ccaaeecad108e0035a6 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Mon, 25 Mar 2024 17:05:02 -0400 Subject: [PATCH] (feat:local-data) Persistently store favorites and recently played songs --- src/backend/local.ts | 51 ++++++++++++++++++++++++++++++++---------- src/backend/search.ts | 5 +++-- src/backend/stores.tsx | 26 +++++++++++++++++++-- src/backend/user.ts | 11 +++++++++ src/ui/Summary.tsx | 4 ++-- 5 files changed, 79 insertions(+), 18 deletions(-) diff --git a/src/backend/local.ts b/src/backend/local.ts index f9ec653..edff40c 100644 --- a/src/backend/local.ts +++ b/src/backend/local.ts @@ -1,10 +1,12 @@ import * as FileSystem from "expo-file-system"; import { logger } from "react-native-logs"; import { EventRegister } from "react-native-event-listeners"; +import TrackPlayer, { Event, PlaybackActiveTrackChangedEvent } from "react-native-track-player"; +import User from "@backend/user"; import Playlist from "@backend/playlist"; -import { OwnedPlaylist, User } from "@backend/types"; -import { usePlaylists } from "@backend/stores"; +import { usePlaylists, useRecents } from "@backend/stores"; +import { OwnedPlaylist, RemoteInfo, TrackInfo, User as UserType } from "@backend/types"; const log = logger.createLogger(); @@ -34,6 +36,9 @@ async function mkdir(name: string): Promise { * Should be called after the user is authenticated. */ async function setup(): Promise { + // Listen for track player events. + TrackPlayer.addEventListener(Event.PlaybackActiveTrackChanged, addToRecents); + // Wait for the user data to finish loading. EventRegister.addEventListener("user:login", _setup); @@ -49,16 +54,11 @@ async function setup(): Promise { /** * Internal setup function. */ -async function _setup(_: false | User): Promise { +async function _setup(_: false | UserType): Promise { // Resolve local playlists. const playlistsDir = `${FileSystem.documentDirectory}playlists`; const playlists = await FileSystem.readDirectoryAsync(playlistsDir); await loadPlaylists(playlists); - - // Resolve local data. - const localDataDir = `${FileSystem.documentDirectory}localData`; - const localData = await FileSystem.readDirectoryAsync(localDataDir); - await loadLocalData(localData); } /** @@ -95,12 +95,39 @@ async function loadPlaylists(files: string[]) { } /** - * Loads local user data from the given files. - * - * @param files The path to the local data files. + * Adds the new track to the recents. */ -async function loadLocalData(files: string[]) { +function addToRecents({ track }: PlaybackActiveTrackChangedEvent): void { + if (!track || User.isLoggedIn()) return; + + // Get the track info. + const info = track.source as TrackInfo; + if (!info) return; + + // Add the track to the recents. + const recents = Object.values(useRecents.getState()); + + const mostRecent = recents[0]; + if (mostRecent?.id != info?.id) { + if (info.type != "remote") return; + // Add the track to the user's recently played. + if (recents.length >= 10) { + // Remove the oldest track. + recents.pop(); + } + recents.unshift(info); + + // Remove any duplicates. + const newList: RemoteInfo[] = []; + recents.forEach(track => { + if (!newList.find(t => t.id === track.id)) { + newList.push(track); + } + }); + + useRecents.setState(newList, true); + } } export default { diff --git a/src/backend/search.ts b/src/backend/search.ts index d6879fb..67ca108 100644 --- a/src/backend/search.ts +++ b/src/backend/search.ts @@ -43,8 +43,9 @@ export function tracks({ results, top }: SearchResult): RemoteInfo[] { const tracks: { [key: string]: RemoteInfo } = {}; - top.type = "remote"; - tracks[top.id] = top; + const clone: RemoteInfo = { ...top }; + clone.type = "remote"; + tracks[clone.id] = clone; for (const track of results) { if (!tracks[track.id]) { diff --git a/src/backend/stores.tsx b/src/backend/stores.tsx index 8440ae5..6e2752a 100644 --- a/src/backend/stores.tsx +++ b/src/backend/stores.tsx @@ -200,6 +200,28 @@ export const useDownloads = create()(persist( )); export const useUser = create(() => null); -export const useRecents = create(() => []); -export const useFavorites = create(() => []); + +export const useRecents = create()(persist( + (): RemoteInfo[] => [], + { + name: "recents", + version: 1, + storage: createJSONStorage(() => AsyncStorage), + migrate: (oldState, _) => { + return oldState; + } + } +)); +export const useFavorites = create()(persist( + (): RemoteInfo[] => [], + { + name: "recents", + version: 1, + storage: createJSONStorage(() => AsyncStorage), + migrate: (oldState, _) => { + return oldState; + } + } +)); + export const usePlaylists = create(() => []); diff --git a/src/backend/user.ts b/src/backend/user.ts index 841cb01..879bd0a 100644 --- a/src/backend/user.ts +++ b/src/backend/user.ts @@ -253,6 +253,17 @@ function loadFavorites(): void { async function favoriteTrack( track: RemoteInfo, add: boolean = true ): Promise { + if (!isLoggedIn()) { + const favorites: RemoteInfo[] = Object.values(useFavorites.getState()); + if (add) { + useFavorites.setState([...favorites, track]); + } else { + useFavorites.setState(favorites.filter(t => t.id != track.id)); + } + + return true; + } + const response = await fetch(`${Backend.getBaseUrl()}/user/favorite`, { method: "POST", headers: { diff --git a/src/ui/Summary.tsx b/src/ui/Summary.tsx index c8d9e81..bad3cc5 100644 --- a/src/ui/Summary.tsx +++ b/src/ui/Summary.tsx @@ -40,11 +40,11 @@ function formPlaylists( showPlaylistModal: () => void ): PlaylistIcon[] { const items: PlaylistIcon[] = []; - if (user) { + if (favorites.length > 0) { items.push({ type: "info", id: "favorites", - owner: user?.userId ?? "", + owner: user?.userId ?? "local", name: "Favorites", description: "All your liked songs!", icon: `${Backend.getBaseUrl()}/Favorite.png`,