diff --git a/jukebox/App.js b/jukebox/App.js
index aedafe3..c6eae34 100644
--- a/jukebox/App.js
+++ b/jukebox/App.js
@@ -7,6 +7,7 @@ import HomeScreen from "./src/screens/HomeScreen";
import { createContext, useState, useContext } from "react";
import LikedTracksScreen from "./src/screens/LikedTracksScreen";
import AlbumScreen from "./src/screens/AlbumScreen";
+import { LibraryProvider } from "./src/contexts/LibraryContext";
const Stack = createNativeStackNavigator();
@@ -63,7 +64,9 @@ function RootLayout() {
export default function App() {
return (
-
+
+
+
);
}
diff --git a/jukebox/package-lock.json b/jukebox/package-lock.json
index 1f27ad9..04b6227 100644
--- a/jukebox/package-lock.json
+++ b/jukebox/package-lock.json
@@ -8939,9 +8939,9 @@
}
},
"node_modules/tar": {
- "version": "7.5.9",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz",
- "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==",
+ "version": "7.5.10",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz",
+ "integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
diff --git a/jukebox/src/components/DurationFormatter.js b/jukebox/src/components/DurationFormatter.js
new file mode 100644
index 0000000..21b465e
--- /dev/null
+++ b/jukebox/src/components/DurationFormatter.js
@@ -0,0 +1,13 @@
+export const durationFormatter = (totalSeconds = 0) => {
+ const s = Math.max(0, Math.floor(totalSeconds));
+
+ const hours = Math.floor(s / 3600);
+ const minutes = Math.floor((s % 3600) / 60);
+ const seconds = s % 60;
+
+ if (hours > 0) {
+ return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
+ }
+
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
+};
\ No newline at end of file
diff --git a/jukebox/src/components/TrackRow.js b/jukebox/src/components/TrackRow.js
index 35c99d4..53500f2 100644
--- a/jukebox/src/components/TrackRow.js
+++ b/jukebox/src/components/TrackRow.js
@@ -1,5 +1,6 @@
import React from 'react';
import { Pressable, View, Text, StyleSheet, Image } from 'react-native';
+import { durationFormatter } from './DurationFormatter';
export default function TrackRow({
title,
@@ -7,8 +8,8 @@ export default function TrackRow({
duration,
cover,
onPress,
- showHeart = true,
- liked = false, // new prop
+ liked = false,
+ onToggleLike,
}) {
return (
@@ -23,13 +24,13 @@ export default function TrackRow({
- {duration ? {duration} : null}
+ {duration ? {durationFormatter(duration)} : "0"}
- {showHeart ? (
+
{liked ? '♥' : '♡'}
- ) : null}
+
);
}
diff --git a/jukebox/src/components/album_cover.js b/jukebox/src/components/album_cover.js
deleted file mode 100644
index e69de29..0000000
diff --git a/jukebox/src/contexts/LibraryContext.js b/jukebox/src/contexts/LibraryContext.js
new file mode 100644
index 0000000..1226193
--- /dev/null
+++ b/jukebox/src/contexts/LibraryContext.js
@@ -0,0 +1,51 @@
+import React, { createContext, useContext, useMemo, useState } from "react";
+import initialLibrary from "../data/library";
+
+const LibraryContext = createContext(null);
+
+export function LibraryProvider({ children }) {
+ const [albums, setAlbums] = useState(initialLibrary);
+
+ const toggleLike = (albumId, trackId) => {
+ setAlbums((prev) =>
+ prev.map((album) =>
+ album.id !== albumId
+ ? album
+ : {
+ ...album,
+ tracks: album.tracks.map((t) =>
+ t.id === trackId ? { ...t, liked: !t.liked } : t
+ ),
+ }
+ )
+ );
+ };
+
+ const likedTracks = useMemo(
+ () =>
+ albums.flatMap((album) =>
+ album.tracks
+ .filter((track) => track.liked)
+ .map((track) => ({
+ ...track,
+ albumId: album.id,
+ albumTitle: album.title,
+ artist: album.artist,
+ cover: album.cover,
+ }))
+ ),
+ [albums]
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useLibrary() {
+ const ctx = useContext(LibraryContext);
+ if (!ctx) throw new Error("useLibrary must be used inside LibraryProvider");
+ return ctx;
+}
\ No newline at end of file
diff --git a/jukebox/src/data/library.js b/jukebox/src/data/library.js
index c76e2dc..2f5e4d3 100644
--- a/jukebox/src/data/library.js
+++ b/jukebox/src/data/library.js
@@ -5,35 +5,35 @@ const library = [
artist: 'Swans',
date: '1996-11-29',
label: 'Young God Records',
- duration: '2:21:25',
+ duration: 8485,
cover: require('../../assets/covers/soundtracksfortheblind.jpg'),
tracks: [
- { id: 'a1t1', title: 'Red Velvet Corridor', duration: '3:03', liked: true },
- { id: 'a1t2', title: 'I Was a Prisoner in Your Skull', duration: '6:39', liked: false },
- { id: 'a1t3', title: 'Helpless Child', duration: '15:47', liked: true },
- { id: 'a1t4', title: 'Live Through Me', duration: '2:19', liked: false },
- { id: 'a1t5', title: 'Yum-Yab Killers', duration: '5:07', liked: false },
- { id: 'a1t6', title: 'The Beautiful Days', duration: '1:50', liked: false },
- { id: 'a1t7', title: 'Volcano', duration: '5:18', liked: false },
- { id: 'a1t8', title: 'Mellothumb', duration: '2:45', liked: false },
- { id: 'a1t9', title: 'All Lined Up', duration: '4:48', liked: false },
- { id: 'a1t10', title: 'Surrogate Drone', duration: '2:03', liked: false },
- { id: 'a1t11', title: 'How They Suffer', duration: '5:52', liked: false },
- { id: 'a1t12', title: 'Animus', duration: '10:43', liked: false },
- { id: 'a1t13', title: 'Red Velvet Wound', duration: '1:01', liked: false },
- { id: 'a1t14', title: 'The Sound', duration: '13:11', liked: true },
- { id: 'a1t15', title: 'Her Mouth Is Filled with Honey', duration: '3:18', liked: false },
- { id: 'a1t16', title: 'Blood Section', duration: '2:45', liked: false },
- { id: 'a1t17', title: 'Hypogirl', duration: '3:42', liked: false },
- { id: 'a1t18', title: 'Minus Something', duration: '4:14', liked: false },
- { id: 'a1t19', title: 'Empathy', duration: '6:45', liked: false },
- { id: 'a1t20', title: 'I Love You This Much', duration: '7:23', liked: false },
- { id: 'a1t21', title: "YRP", duration: '7:58', liked: false },
- { id: 'a1t22', title: "Fan's Lament", duration: '1:47', liked: false },
- { id: 'a1t23', title: 'Secret Friends', duration: '3:08', liked: false },
- { id: 'a1t24', title: 'The Final Sacrifice', duration: '9:51', liked: false },
- { id: 'a1t25', title: 'YRP 2', duration: '2:09', liked: false },
- { id: 'a1t26', title: 'Surrogate 2', duration: '1:55', liked: false },
+ { id: 'a1t1', title: 'Red Velvet Corridor', duration: 183, liked: true },
+ { id: 'a1t2', title: 'I Was a Prisoner in Your Skull', duration: 399, liked: false },
+ { id: 'a1t3', title: 'Helpless Child', duration: 947, liked: true },
+ { id: 'a1t4', title: 'Live Through Me', duration: 139, liked: false },
+ { id: 'a1t5', title: 'Yum-Yab Killers', duration: 307, liked: false },
+ { id: 'a1t6', title: 'The Beautiful Days', duration: 110, liked: false },
+ { id: 'a1t7', title: 'Volcano', duration: 318, liked: false },
+ { id: 'a1t8', title: 'Mellothumb', duration: 165, liked: false },
+ { id: 'a1t9', title: 'All Lined Up', duration: 288, liked: false },
+ { id: 'a1t10', title: 'Surrogate Drone', duration: 123, liked: false },
+ { id: 'a1t11', title: 'How They Suffer', duration: 352, liked: false },
+ { id: 'a1t12', title: 'Animus', duration: 643, liked: false },
+ { id: 'a1t13', title: 'Red Velvet Wound', duration: 61, liked: false },
+ { id: 'a1t14', title: 'The Sound', duration: 791, liked: true },
+ { id: 'a1t15', title: 'Her Mouth Is Filled with Honey', duration: 198, liked: false },
+ { id: 'a1t16', title: 'Blood Section', duration: 165, liked: false },
+ { id: 'a1t17', title: 'Hypogirl', duration: 222, liked: false },
+ { id: 'a1t18', title: 'Minus Something', duration: 254, liked: false },
+ { id: 'a1t19', title: 'Empathy', duration: 405, liked: false },
+ { id: 'a1t20', title: 'I Love You This Much', duration: 443, liked: false },
+ { id: 'a1t21', title: "YRP", duration: 478, liked: false },
+ { id: 'a1t22', title: "Fan's Lament", duration: 107, liked: false },
+ { id: 'a1t23', title: 'Secret Friends', duration: 188, liked: false },
+ { id: 'a1t24', title: 'The Final Sacrifice', duration: 591, liked: false },
+ { id: 'a1t25', title: 'YRP 2', duration: 129, liked: false },
+ { id: 'a1t26', title: 'Surrogate 2', duration: 115, liked: false },
],
},
{
@@ -42,23 +42,23 @@ const library = [
artist: 'Daft Punk',
date: '2001-03-12',
label: 'Virgin Records',
- duration: '1:00:50',
+ duration: 3650,
cover: require('../../assets/covers/discovery.jpg'),
tracks: [
- { id: 'a2t1', title: 'One More Time', duration: '5:20', liked: true },
- { id: 'a2t2', title: 'Aerodynamic', duration: '3:27', liked: false },
- { id: 'a2t3', title: 'Digital Love', duration: '4:58', liked: false },
- { id: 'a2t4', title: 'Harder, Better, Faster, Stronger', duration: '3:45', liked: false },
- { id: 'a2t5', title: 'Crescendolls', duration: '3:31', liked: false },
- { id: 'a2t6', title: 'Nightvision', duration: '1:44', liked: false },
- { id: 'a2t7', title: 'Superheroes', duration: '3:57', liked: false },
- { id: 'a2t8', title: 'High Life', duration: '3:21', liked: false },
- { id: 'a2t9', title: 'Something About Us', duration: '3:51', liked: false },
- { id: 'a2t10', title: 'Voyager', duration: '3:47', liked: false },
- { id: 'a2t11', title: 'Veridis Quo', duration: '5:44', liked: false },
- { id: 'a2t12', title: 'Short Circuit', duration: '3:26', liked: false },
- { id: 'a2t13', title: 'Face to Face', duration: '4:00', liked: false },
- { id: 'a2t14', title: 'Too Long', duration: '10:00', liked: false },
+ { id: 'a2t1', title: 'One More Time', duration: 320, liked: true },
+ { id: 'a2t2', title: 'Aerodynamic', duration: 207, liked: false },
+ { id: 'a2t3', title: 'Digital Love', duration: 298, liked: false },
+ { id: 'a2t4', title: 'Harder, Better, Faster, Stronger', duration: 225, liked: false },
+ { id: 'a2t5', title: 'Crescendolls', duration: 211, liked: false },
+ { id: 'a2t6', title: 'Nightvision', duration: 104, liked: false },
+ { id: 'a2t7', title: 'Superheroes', duration: 237, liked: false },
+ { id: 'a2t8', title: 'High Life', duration: 201, liked: false },
+ { id: 'a2t9', title: 'Something About Us', duration: 231, liked: false },
+ { id: 'a2t10', title: 'Voyager', duration: 227, liked: false },
+ { id: 'a2t11', title: 'Veridis Quo', duration: 344, liked: false },
+ { id: 'a2t12', title: 'Short Circuit', duration: 206, liked: false },
+ { id: 'a2t13', title: 'Face to Face', duration: 240, liked: false },
+ { id: 'a2t14', title: 'Too Long', duration: 600, liked: false },
],
},
];
diff --git a/jukebox/src/screens/AlbumScreen.js b/jukebox/src/screens/AlbumScreen.js
index 5a23488..bd7148e 100644
--- a/jukebox/src/screens/AlbumScreen.js
+++ b/jukebox/src/screens/AlbumScreen.js
@@ -1,122 +1,106 @@
-import React from 'react';
+import React, { useMemo } from 'react';
import { View, Text, StyleSheet, FlatList, Pressable, Image } from 'react-native';
import TrackRow from '../components/TrackRow';
+import { useLibrary } from '../contexts/LibraryContext';
+import { durationFormatter } from '../components/DurationFormatter';
export default function AlbumScreen({ route, navigation }) {
- const { album } = route.params;
- return (
-
- navigation.goBack()} style={styles.backBtn}>
- ← Back
-
-
-
- {album.title}
- {album.artist}
- {album.date} · {album.label} · {album.duration}
-
+ const { album: routeAlbum } = route.params;
+ const { albums, toggleLike } = useLibrary();
- item.id ?? index.toString()}
- renderItem={({ item }) => (
- { }}
- showHeart={true}
- liked={item.liked}
- />
- )}
- contentContainerStyle={styles.trackList}
- />
-
- );
+ // Always use latest album from context (so likes stay in sync)
+ const album = useMemo(
+ () => albums.find((a) => a.id === routeAlbum.id) ?? routeAlbum,
+ [albums, routeAlbum]
+ );
+
+ return (
+
+ navigation.goBack()} style={styles.backBtn}>
+ ← Back
+
+
+
+
+ {album.title}
+ {album.artist}
+
+ {album.date} · {album.label} · {durationFormatter(album.duration)}
+
+
+
+ item.id ?? index.toString()}
+ renderItem={({ item }) => (
+ {}}
+ showHeart={true}
+ liked={item.liked}
+ onToggleLike={() => toggleLike(album.id, item.id)}
+ />
+ )}
+ contentContainerStyle={styles.trackList}
+ />
+
+ );
}
const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#000',
- paddingTop: 28,
- paddingHorizontal: 16,
- paddingBottom: 24,
- },
- header: {
- alignItems: 'center',
- },
- cover: {
- width: 180,
- height: 180,
- borderRadius: 8,
- marginBottom: 12,
- },
- title: {
- color: '#fff',
- fontSize: 32,
- fontWeight: '700',
- textAlign: 'center',
- marginBottom: 1,
- },
- artist: {
- color: '#fff',
- fontSize: 26,
- fontWeight: '500',
- textAlign: 'center',
- marginTop: 1,
- },
- meta: {
- color: '#fff',
- fontSize: 14,
- fontWeight: '500',
- textAlign: 'center',
- marginTop: 8,
- marginBottom: 24,
- },
- trackList: {
- width: '100%',
- paddingHorizontal: 16,
- paddingBottom: 180,
- },
- trackItem: {
- width: '100%',
- minHeight: 62,
- padding: 0,
- flexDirection: 'row',
- },
- trackTextBlock: {
- flex: 1,
- },
- trackTitle: {
- color: '#ffffff',
- fontSize: 15,
- fontWeight: '700',
- },
- trackArtist: {
- color: '#ffffff',
- fontSize: 15,
- marginTop: 2,
- },
- trackDuration: {
- color: '#ffffff',
- fontSize: 15,
- paddingHorizontal: 18,
- marginTop: 10,
- },
- heart: {
- fontSize: 22,
- color: '#fff',
- paddingHorizontal: 10,
- marginTop: 5.5,
- },
- backBtn: {
- marginBottom: 10,
- alignSelf: 'flex-start',
- },
- backText: {
- color: '#fff',
- fontSize: 16,
- fontWeight: '600',
- },
+ container: {
+ flex: 1,
+ backgroundColor: '#000',
+ paddingTop: 28,
+ paddingHorizontal: 16,
+ paddingBottom: 24,
+ },
+ header: {
+ alignItems: 'center',
+ },
+ cover: {
+ width: 180,
+ height: 180,
+ borderRadius: 8,
+ marginBottom: 12,
+ },
+ title: {
+ color: '#fff',
+ fontSize: 32,
+ fontWeight: '700',
+ textAlign: 'center',
+ marginBottom: 1,
+ },
+ artist: {
+ color: '#fff',
+ fontSize: 26,
+ fontWeight: '500',
+ textAlign: 'center',
+ marginTop: 1,
+ },
+ meta: {
+ color: '#fff',
+ fontSize: 14,
+ fontWeight: '500',
+ textAlign: 'center',
+ marginTop: 8,
+ marginBottom: 24,
+ },
+ trackList: {
+ width: '100%',
+ paddingHorizontal: 16,
+ paddingBottom: 180,
+ },
+ backBtn: {
+ marginBottom: 10,
+ alignSelf: 'flex-start',
+ },
+ backText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
});
\ No newline at end of file
diff --git a/jukebox/src/screens/HomeScreen.js b/jukebox/src/screens/HomeScreen.js
index 3be6647..3c3a90d 100644
--- a/jukebox/src/screens/HomeScreen.js
+++ b/jukebox/src/screens/HomeScreen.js
@@ -1,78 +1,67 @@
-import React from "react";
+import React, { useMemo } from "react";
import {
View,
Text,
StyleSheet,
FlatList,
- ScrollView,
Pressable,
Image,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native";
-import library from '../data/library';
-import TrackRow from '../components/TrackRow';
-
-const likedAlbums = library;
-
-const likedTracks = library.flatMap((album) =>
- album.tracks
- .filter((t) => t.liked)
- .map((t) => ({
- ...t,
- albumId: album.id,
- albumTitle: album.title,
- artist: album.artist,
- cover: album.cover,
- }))
-);
+import TrackRow from "../components/TrackRow";
+import { useLibrary } from "../contexts/LibraryContext";
export default function HomeScreen() {
const navigation = useNavigation();
+ const { albums, likedTracks, toggleLike } = useLibrary();
+
+ // Same behavior as before: show all albums in "Liked albums" section
+ const likedAlbums = albums;
+
+ // Optional: limit shown liked tracks on Home
+ const homeLikedTracks = useMemo(() => likedTracks.slice(0, 5), [likedTracks]);
+
return (
Home
-
- {
- // navigation.navigate("Login")
- console.log("Login pressed");
- }}
- >
-
-
-
- {
- // navigation.navigate("Settings")
- console.log("Settings pressed");
- }}
- >
-
-
+
+ {
+ console.log("Login pressed");
+ }}
+ >
+
+
+ {
+ console.log("Settings pressed");
+ }}
+ >
+
+
- navigation.navigate("LikedTracks", { tracks: likedTracks })}
- >
+
+ navigation.navigate("LikedTracks")}>
Liked tracks ➚
- {likedTracks.map((track) => {
- const album = likedAlbums.find((a) => a.id === track.albumId);
-
+ {homeLikedTracks.map((track) => {
+ const album = albums.find((a) => a.id === track.albumId);
return (
toggleLike(track.albumId, track.id)}
onPress={() => album && navigation.navigate("Album", { album })}
/>
);
@@ -116,12 +105,9 @@ const styles = StyleSheet.create({
headerActions: {
flexDirection: "row",
alignItems: "center",
- gap: 8, // if gap not supported on your RN version, use marginLeft in iconBtn
+ gap: 8,
},
- settingsBtn: {
- padding: 2,
- },
- loginBtn: {
+ iconBtn: {
padding: 2,
},
homeTitle: {
@@ -136,38 +122,6 @@ const styles = StyleSheet.create({
fontWeight: "700",
marginBottom: 12,
},
- trackItem: {
- minHeight: 62,
- flexDirection: 'row',
- alignItems: 'center',
- },
-
- trackCover: {
- width: 46,
- height: 46,
- borderRadius: 6,
- marginRight: 10,
- },
-
- trackTextBlock: {
- flex: 1,
- justifyContent: 'center',
- },
-
- trackTitle: {
- color: '#ffffff',
- fontSize: 15,
- fontWeight: '700',
- },
-
- trackArtist: {
- color: '#ffffff',
- fontSize: 13,
- marginTop: 2,
- },
- tracksBox: {
- maxHeight: 260,
- },
albumList: {
paddingBottom: 120,
},
@@ -176,14 +130,14 @@ const styles = StyleSheet.create({
marginBottom: 12,
},
albumItem: {
- width: '48%',
+ width: "48%",
aspectRatio: 1,
borderRadius: 8,
- overflow: 'hidden',
- backgroundColor: '#222',
+ overflow: "hidden",
+ backgroundColor: "#222",
},
cover: {
- width: '100%',
- height: '100%',
+ width: "100%",
+ height: "100%",
},
-});
+});
\ No newline at end of file
diff --git a/jukebox/src/screens/LikedTracksScreen.js b/jukebox/src/screens/LikedTracksScreen.js
index 0e83da1..d78c266 100644
--- a/jukebox/src/screens/LikedTracksScreen.js
+++ b/jukebox/src/screens/LikedTracksScreen.js
@@ -1,23 +1,51 @@
-import React from "react";
-import { View, Text, StyleSheet, FlatList, Pressable } from "react-native";
-import library from "../data/library";
+import { useMemo, useState } from "react";
+import {
+ View,
+ Text,
+ StyleSheet,
+ FlatList,
+ Pressable,
+ TextInput,
+} from "react-native";
import TrackRow from "../components/TrackRow";
+import { useLibrary } from "../contexts/LibraryContext";
export default function LikedTracksScreen({ navigation }) {
- const likedTracks = library.flatMap((album) =>
- album.tracks
- .filter((track) => track.liked)
- .map((track) => ({
- ...track,
- albumId: album.id,
- albumTitle: album.title,
- artist: album.artist,
- cover: album.cover,
- }))
- );
+ const [query, setQuery] = useState("");
+ const [sortDir, setSortDir] = useState("desc");
+ const { albums, likedTracks, toggleLike } = useLibrary();
- // testing only
- const tracks = likedTracks.slice(0, 2);
+ const [sortBy, setSortBy] = useState("dateAdded");
+ const [showSortMenu, setShowSortMenu] = useState(false);
+
+ const filteredAndSortedTracks = useMemo(() => {
+ const q = query.trim().toLowerCase();
+
+ let result = !q
+ ? [...likedTracks]
+ : likedTracks.filter(
+ (item) =>
+ item.title.toLowerCase().includes(q) ||
+ item.artist.toLowerCase().includes(q)
+ );
+
+ if (sortBy === "name") {
+ result.sort((a, b) => a.title.localeCompare(b.title));
+ } else if (sortBy === "length") {
+ result.sort((a, b) => a.duration - b.duration);
+ } else {
+ result.sort((a, b) => {
+ if (a.dateAdded && b.dateAdded) {
+ return new Date(b.dateAdded) - new Date(a.dateAdded);
+ }
+ return 0;
+ });
+ }
+
+ if (sortDir === "desc") result.reverse();
+
+ return result;
+ }, [likedTracks, query, sortBy, sortDir]);
return (
@@ -27,8 +55,52 @@ export default function LikedTracksScreen({ navigation }) {
Your liked tracks
+
+
+
+ Sort by:
+ setShowSortMenu((v) => !v)}
+ >
+
+ {sortBy === "dateAdded" ? "Date added" : sortBy === "name" ? "Name" : "Length"} ▾
+
+
+ setSortDir((d) => (d === "asc" ? "desc" : "asc"))}
+ >
+
+ {sortDir === "asc" ? "ASC ↑" : "DESC ↓"}
+
+
+
+
+ {showSortMenu && (
+
+ { setSortBy("dateAdded"); setShowSortMenu(false); }}>
+ Date added
+
+ { setSortBy("name"); setShowSortMenu(false); }}>
+ Name
+
+ { setSortBy("length"); setShowSortMenu(false); }}>
+ Length
+
+
+ )}
+
item.id}
renderItem={({ item }) => (
toggleLike(item.albumId, item.id)}
onPress={() => {
- const album = library.find((a) => a.id === item.albumId);
+ const album = albums.find((a) => a.id === item.albumId);
if (album) navigation.navigate("Album", { album });
}}
/>
)}
+ ListEmptyComponent={
+ No liked tracks found.
+ }
+ keyboardShouldPersistTaps="handled"
contentContainerStyle={{ paddingBottom: 180 }}
/>
@@ -72,4 +149,69 @@ const styles = StyleSheet.create({
fontWeight: "700",
marginBottom: 12,
},
+ searchInput: {
+ backgroundColor: "#1f2937",
+ color: "#fff",
+ borderRadius: 10,
+ paddingHorizontal: 12,
+ paddingVertical: 10,
+ fontSize: 16,
+ marginBottom: 12,
+ },
+ emptyText: {
+ color: "#9ca3af",
+ fontSize: 15,
+ marginTop: 16,
+ },
+ sortRow: {
+ flexDirection: "row",
+ alignItems: "center",
+ marginBottom: 8,
+ },
+ sortByText: {
+ color: "#9ca3af",
+ marginRight: 8,
+ fontSize: 14,
+ },
+ sortButton: {
+ backgroundColor: "#111827",
+ borderRadius: 8,
+ paddingHorizontal: 10,
+ paddingVertical: 6,
+ },
+ sortButtonText: {
+ color: "#fff",
+ fontSize: 14,
+ fontWeight: "600",
+ },
+ sortMenu: {
+ backgroundColor: "#111827",
+ borderRadius: 10,
+ marginBottom: 10,
+ overflow: "hidden",
+ borderWidth: 1,
+ borderColor: "#1f2937",
+ },
+ sortOption: {
+ paddingHorizontal: 12,
+ paddingVertical: 10,
+ borderBottomWidth: 1,
+ borderBottomColor: "#1f2937",
+ },
+ sortOptionText: {
+ color: "#fff",
+ fontSize: 15,
+ },
+ sortDirButton: {
+ backgroundColor: "#111827",
+ borderRadius: 8,
+ paddingHorizontal: 10,
+ paddingVertical: 6,
+ marginLeft: 8,
+ },
+ sortDirButtonText: {
+ color: "#fff",
+ fontSize: 14,
+ fontWeight: "600",
+ },
});
\ No newline at end of file