Skip to content

Commit

Permalink
(feat:local-data) Persistently store favorites and recently played songs
Browse files Browse the repository at this point in the history
  • Loading branch information
KingRainbow44 committed Mar 25, 2024
1 parent c0f827a commit c62a8df
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 18 deletions.
51 changes: 39 additions & 12 deletions src/backend/local.ts
Original file line number Diff line number Diff line change
@@ -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();

Expand Down Expand Up @@ -34,6 +36,9 @@ async function mkdir(name: string): Promise<boolean> {
* Should be called after the user is authenticated.
*/
async function setup(): Promise<void> {
// Listen for track player events.
TrackPlayer.addEventListener(Event.PlaybackActiveTrackChanged, addToRecents);

// Wait for the user data to finish loading.
EventRegister.addEventListener("user:login", _setup);

Expand All @@ -49,16 +54,11 @@ async function setup(): Promise<void> {
/**
* Internal setup function.
*/
async function _setup(_: false | User): Promise<void> {
async function _setup(_: false | UserType): Promise<void> {
// 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);
}

/**
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions src/backend/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]) {
Expand Down
26 changes: 24 additions & 2 deletions src/backend/stores.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,28 @@ export const useDownloads = create<DownloadState>()(persist(
));

export const useUser = create<User | null>(() => null);
export const useRecents = create<RemoteInfo[]>(() => []);
export const useFavorites = create<RemoteInfo[]>(() => []);

export const useRecents = create<RemoteInfo[]>()(persist(
(): RemoteInfo[] => [],
{
name: "recents",
version: 1,
storage: createJSONStorage(() => AsyncStorage),
migrate: (oldState, _) => {
return oldState;
}
}
));
export const useFavorites = create<RemoteInfo[]>()(persist(
(): RemoteInfo[] => [],
{
name: "recents",
version: 1,
storage: createJSONStorage(() => AsyncStorage),
migrate: (oldState, _) => {
return oldState;
}
}
));

export const usePlaylists = create<OwnedPlaylist[]>(() => []);
11 changes: 11 additions & 0 deletions src/backend/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ function loadFavorites(): void {
async function favoriteTrack(
track: RemoteInfo, add: boolean = true
): Promise<boolean> {
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: {
Expand Down
4 changes: 2 additions & 2 deletions src/ui/Summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down

0 comments on commit c62a8df

Please sign in to comment.