Added styling for liked tracks, added album screen
This commit is contained in:
@@ -6,6 +6,7 @@ import MediaPlayer from "./src/components/MediaPlayer";
|
||||
import HomeScreen from "./src/screens/HomeScreen";
|
||||
import { createContext, useState, useContext } from "react";
|
||||
import LikedTracksScreen from "./src/screens/LikedTracksScreen";
|
||||
import AlbumScreen from "./src/screens/AlbumScreen";
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
@@ -37,6 +38,7 @@ function AppNavigator() {
|
||||
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="Home" component={HomeScreen} />
|
||||
<Stack.Screen name="LikedTracks" component={LikedTracksScreen} />
|
||||
<Stack.Screen name="Album" component={AlbumScreen} />
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
BIN
jukebox/assets/covers/discovery.jpg
Normal file
BIN
jukebox/assets/covers/discovery.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
jukebox/assets/covers/soundtracksfortheblind.jpg
Normal file
BIN
jukebox/assets/covers/soundtracksfortheblind.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
75
jukebox/package-lock.json
generated
75
jukebox/package-lock.json
generated
@@ -64,6 +64,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -1344,7 +1345,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
|
||||
"integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
},
|
||||
@@ -1434,6 +1434,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
|
||||
"integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -3000,9 +3001,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/codegen/node_modules/minimatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
@@ -3170,6 +3171,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.31.tgz",
|
||||
"integrity": "sha512-+YCUwtfDgsux59Q0LDHc3Zid9ih93ecUCFWZOH6/+eNoUGnWx77wjS6ZfvBO/7E+EiIup11IVShDzCHR4of8hw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^7.15.1",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
@@ -4005,6 +4007,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -4677,6 +4680,7 @@
|
||||
"resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz",
|
||||
"integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.0",
|
||||
"@expo/cli": "54.0.23",
|
||||
@@ -4729,6 +4733,7 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz",
|
||||
"integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fontfaceobserver": "^2.1.0"
|
||||
},
|
||||
@@ -5382,9 +5387,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/minimatch": {
|
||||
"version": "10.2.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz",
|
||||
"integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==",
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.2"
|
||||
@@ -6992,12 +6997,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz",
|
||||
"integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==",
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
||||
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.2"
|
||||
"brace-expansion": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
@@ -7006,6 +7011,21 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/minimatch/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
@@ -7668,6 +7688,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -7687,6 +7708,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@@ -7717,6 +7739,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz",
|
||||
"integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/create-cache-key-function": "^29.7.0",
|
||||
"@react-native/assets-registry": "0.81.5",
|
||||
@@ -7774,6 +7797,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz",
|
||||
"integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
@@ -7827,6 +7851,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
|
||||
"integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
@@ -7837,6 +7862,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz",
|
||||
"integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"react-freeze": "^1.0.0",
|
||||
"react-native-is-edge-to-edge": "^1.2.1",
|
||||
@@ -7939,7 +7965,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.4.tgz",
|
||||
"integrity": "sha512-NYOdM1MwBb3n+AtMqy1tFy3Mn8DliQtd8sbzAVRf9Gc+uvQ0zRfxN7dS8ZzoyX7t6cyQL5THuGhlnX+iFlQTag==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-arrow-functions": "7.27.1",
|
||||
"@babel/plugin-transform-class-properties": "7.27.1",
|
||||
@@ -7964,7 +7989,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
|
||||
"integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.27.1",
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
@@ -7981,7 +8005,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
|
||||
"integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.27.3",
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
@@ -8002,7 +8025,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
|
||||
"integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
},
|
||||
@@ -8018,7 +8040,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
|
||||
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
|
||||
@@ -8035,7 +8056,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz",
|
||||
"integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1",
|
||||
"@babel/helper-validator-option": "^7.27.1",
|
||||
@@ -8055,7 +8075,6 @@
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
@@ -8133,9 +8152,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native/node_modules/minimatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
@@ -8158,6 +8177,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -8384,9 +8404,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/minimatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
@@ -9044,9 +9064,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/minimatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
@@ -9120,6 +9140,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
@@ -10,9 +10,10 @@ export default function MediaPlayer({ title, onPress }) {
|
||||
return (
|
||||
<LinearGradient
|
||||
colors={[
|
||||
"rgba(70,70,70,0.92)", // top
|
||||
"rgba(35,35,35,0.95)", // middle
|
||||
"rgba(15,15,15,0.98)", // bottom
|
||||
"rgba(25,25,25,0.95)", // top
|
||||
"rgba(15,15,15,0.98)", // middle-top
|
||||
"rgba(5,5,5,1)", // middle-bottom
|
||||
"rgba(5,5,5,1)", // bottom
|
||||
]}
|
||||
locations={[0, 0.55, 1]}
|
||||
start={{ x: 0.5, y: 0 }}
|
||||
|
||||
79
jukebox/src/components/TrackRow.js
Normal file
79
jukebox/src/components/TrackRow.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import { Pressable, View, Text, StyleSheet, Image } from 'react-native';
|
||||
|
||||
export default function TrackRow({
|
||||
title,
|
||||
artist,
|
||||
duration,
|
||||
cover,
|
||||
onPress,
|
||||
showHeart = true,
|
||||
liked = false, // new prop
|
||||
}) {
|
||||
return (
|
||||
<Pressable style={styles.trackItem} onPress={onPress}>
|
||||
{cover ? <Image source={cover} style={styles.trackCover} resizeMode="cover" /> : null}
|
||||
|
||||
<View style={styles.trackTextBlock}>
|
||||
<Text style={styles.trackTitle} numberOfLines={1}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={styles.trackArtist} numberOfLines={1}>
|
||||
{artist}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{duration ? <Text style={styles.trackDuration}>{duration}</Text> : null}
|
||||
|
||||
{showHeart ? (
|
||||
<Text style={[styles.heart, liked && styles.heartLiked]}>
|
||||
{liked ? '♥' : '♡'}
|
||||
</Text>
|
||||
) : null}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
trackItem: {
|
||||
width: '100%',
|
||||
minHeight: 62,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
trackCover: {
|
||||
width: 46,
|
||||
height: 46,
|
||||
borderRadius: 6,
|
||||
marginRight: 10,
|
||||
},
|
||||
trackTextBlock: {
|
||||
flex: 1,
|
||||
},
|
||||
trackTitle: {
|
||||
color: '#ffffff',
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
},
|
||||
trackArtist: {
|
||||
color: '#ffffff',
|
||||
fontSize: 15,
|
||||
marginTop: 2,
|
||||
},
|
||||
trackDuration: {
|
||||
color: '#ffffff',
|
||||
fontSize: 15,
|
||||
paddingHorizontal: 18,
|
||||
},
|
||||
heart: {
|
||||
fontSize: 22,
|
||||
color: '#fff',
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
heartLiked: {
|
||||
fontSize: 22,
|
||||
color: '#ff4d6d',
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
});
|
||||
66
jukebox/src/data/library.js
Normal file
66
jukebox/src/data/library.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const library = [
|
||||
{
|
||||
id: 'album1',
|
||||
title: 'Soundtracks For The Blind',
|
||||
artist: 'Swans',
|
||||
date: '1996-11-29',
|
||||
label: 'Young God Records',
|
||||
duration: '2:21:25',
|
||||
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: 'album2',
|
||||
title: 'Discovery',
|
||||
artist: 'Daft Punk',
|
||||
date: '2001-03-12',
|
||||
label: 'Virgin Records',
|
||||
duration: '1:00:50',
|
||||
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 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default library;
|
||||
122
jukebox/src/screens/AlbumScreen.js
Normal file
122
jukebox/src/screens/AlbumScreen.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import { View, Text, StyleSheet, FlatList, Pressable, Image } from 'react-native';
|
||||
import TrackRow from '../components/TrackRow';
|
||||
|
||||
export default function AlbumScreen({ route, navigation }) {
|
||||
const { album } = route.params;
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Pressable onPress={() => navigation.goBack()} style={styles.backBtn}>
|
||||
<Text style={styles.backText}>← Back</Text>
|
||||
</Pressable>
|
||||
<View style={styles.header}>
|
||||
<Image source={album.cover} style={styles.cover} resizeMode="cover" />
|
||||
<Text style={styles.title}>{album.title}</Text>
|
||||
<Text style={styles.artist}>{album.artist}</Text>
|
||||
<Text style={styles.meta}>{album.date} · {album.label} · {album.duration}</Text>
|
||||
</View>
|
||||
|
||||
<FlatList
|
||||
data={album.tracks}
|
||||
keyExtractor={(item, index) => item.id ?? index.toString()}
|
||||
renderItem={({ item }) => (
|
||||
<TrackRow
|
||||
title={typeof item === 'string' ? item : item.title}
|
||||
artist={album.artist}
|
||||
duration={item.duration}
|
||||
onPress={() => { }}
|
||||
showHeart={true}
|
||||
liked={item.liked}
|
||||
/>
|
||||
)}
|
||||
contentContainerStyle={styles.trackList}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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',
|
||||
},
|
||||
});
|
||||
@@ -6,56 +6,94 @@ import {
|
||||
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 likedTracks = [
|
||||
"Track 1",
|
||||
"Track 2",
|
||||
"Track 3",
|
||||
"Track 4",
|
||||
"Track 5",
|
||||
"Track 6",
|
||||
];
|
||||
const likedAlbums = library;
|
||||
|
||||
const likedAlbums = [
|
||||
"Album 1",
|
||||
"Album 2",
|
||||
"Album 3",
|
||||
"Album 4",
|
||||
"Album 5",
|
||||
"Album 6",
|
||||
"Album 7",
|
||||
"Album 8",
|
||||
];
|
||||
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,
|
||||
}))
|
||||
);
|
||||
|
||||
export default function HomeScreen() {
|
||||
const navigation = useNavigation();
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.headerRow}>
|
||||
<Text style={styles.homeTitle}>Home</Text>
|
||||
<View style={styles.headerActions}>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
navigation.navigate("LikedTracks", { tracks: likedTracks })
|
||||
}
|
||||
style={styles.iconBtn}
|
||||
onPress={() => {
|
||||
// navigation.navigate("Login")
|
||||
console.log("Login pressed");
|
||||
}}
|
||||
>
|
||||
<Ionicons name="person-outline" size={24} color="#fff" />
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
style={styles.iconBtn}
|
||||
onPress={() => {
|
||||
// navigation.navigate("Settings")
|
||||
console.log("Settings pressed");
|
||||
}}
|
||||
>
|
||||
<Ionicons name="settings-outline" size={24} color="#fff" />
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
<Pressable
|
||||
onPress={() => navigation.navigate("LikedTracks", { tracks: likedTracks })}
|
||||
>
|
||||
<Text style={styles.sectionTitle}>Liked tracks ➚</Text>
|
||||
</Pressable>
|
||||
|
||||
{likedTracks.map((item, index) => (
|
||||
<View key={index} style={styles.trackItem} />
|
||||
))}
|
||||
{likedTracks.map((track) => {
|
||||
const album = likedAlbums.find((a) => a.id === track.albumId);
|
||||
|
||||
<Text style={[styles.sectionTitle, { marginTop: 24 }]}>
|
||||
Liked albums ➚
|
||||
</Text>
|
||||
return (
|
||||
<TrackRow
|
||||
key={track.id}
|
||||
title={track.title}
|
||||
artist={album?.artist ?? "Unknown artist"}
|
||||
duration={track.duration}
|
||||
cover={album?.cover}
|
||||
showHeart={true}
|
||||
liked={track.liked}
|
||||
onPress={() => album && navigation.navigate("Album", { album })}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<Text style={[styles.sectionTitle, { marginTop: 24 }]}>Liked albums</Text>
|
||||
|
||||
<FlatList
|
||||
data={likedAlbums}
|
||||
keyExtractor={(_, index) => index.toString()}
|
||||
keyExtractor={(item) => item.id}
|
||||
numColumns={2}
|
||||
columnWrapperStyle={styles.albumRow}
|
||||
contentContainerStyle={styles.albumList}
|
||||
renderItem={() => <View style={styles.albumItem} />}
|
||||
renderItem={({ item }) => (
|
||||
<Pressable
|
||||
style={styles.albumItem}
|
||||
onPress={() => navigation.navigate("Album", { album: item })}
|
||||
>
|
||||
<Image source={item.cover} style={styles.cover} resizeMode="cover" />
|
||||
</Pressable>
|
||||
)}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
</View>
|
||||
@@ -69,34 +107,83 @@ const styles = StyleSheet.create({
|
||||
paddingTop: 24,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
headerRow: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 12,
|
||||
},
|
||||
headerActions: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8, // if gap not supported on your RN version, use marginLeft in iconBtn
|
||||
},
|
||||
settingsBtn: {
|
||||
padding: 2,
|
||||
},
|
||||
loginBtn: {
|
||||
padding: 2,
|
||||
},
|
||||
homeTitle: {
|
||||
color: "#ffffff",
|
||||
fontSize: 32,
|
||||
fontWeight: "700",
|
||||
marginBottom: 12,
|
||||
},
|
||||
sectionTitle: {
|
||||
color: "#fff",
|
||||
fontSize: 34,
|
||||
color: "#ffffff",
|
||||
fontSize: 24,
|
||||
fontWeight: "700",
|
||||
marginBottom: 12,
|
||||
},
|
||||
trackItem: {
|
||||
height: 42,
|
||||
backgroundColor: "#ffd9d9",
|
||||
borderWidth: 2,
|
||||
borderColor: "#ff4d4d",
|
||||
borderRadius: 8,
|
||||
marginBottom: 10,
|
||||
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, // about 5 track rows
|
||||
maxHeight: 260,
|
||||
},
|
||||
albumList: {
|
||||
paddingBottom: 120, // keep room for your media player at bottom
|
||||
paddingBottom: 120,
|
||||
},
|
||||
albumRow: {
|
||||
justifyContent: "space-between",
|
||||
marginBottom: 12,
|
||||
},
|
||||
albumItem: {
|
||||
width: "48%",
|
||||
aspectRatio: 1, // square
|
||||
backgroundColor: "#f7d7a6",
|
||||
borderRadius: 18,
|
||||
width: '48%',
|
||||
aspectRatio: 1,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#222',
|
||||
},
|
||||
cover: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
import React from "react";
|
||||
import { View, Text, StyleSheet, FlatList, Pressable } from "react-native";
|
||||
import library from "../data/library";
|
||||
import TrackRow from "../components/TrackRow";
|
||||
|
||||
export default function LikedTracksScreen({ route, navigation }) {
|
||||
const tracks = route.params?.tracks ?? [];
|
||||
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,
|
||||
}))
|
||||
);
|
||||
|
||||
// testing only
|
||||
const tracks = likedTracks.slice(0, 2);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@@ -14,13 +29,22 @@ export default function LikedTracksScreen({ route, navigation }) {
|
||||
|
||||
<FlatList
|
||||
data={tracks}
|
||||
keyExtractor={(_, index) => index.toString()}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.trackItem}>
|
||||
<Text style={styles.trackText}>{item}</Text>
|
||||
</View>
|
||||
<TrackRow
|
||||
title={item.title}
|
||||
artist={item.artist}
|
||||
duration={item.duration}
|
||||
cover={item.cover}
|
||||
showHeart={true}
|
||||
liked={item.liked}
|
||||
onPress={() => {
|
||||
const album = library.find((a) => a.id === item.albumId);
|
||||
if (album) navigation.navigate("Album", { album });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
contentContainerStyle={{ paddingBottom: 24 }}
|
||||
contentContainerStyle={{ paddingBottom: 180 }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -33,18 +57,19 @@ const styles = StyleSheet.create({
|
||||
paddingTop: 24,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
backBtn: { marginBottom: 10 },
|
||||
backText: { color: "#fff", fontSize: 16 },
|
||||
title: { color: "#fff", fontSize: 30, fontWeight: "700", marginBottom: 12 },
|
||||
trackItem: {
|
||||
height: 50,
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 12,
|
||||
backgroundColor: "#ffd9d9",
|
||||
borderWidth: 2,
|
||||
borderColor: "#ff4d4d",
|
||||
borderRadius: 8,
|
||||
backBtn: {
|
||||
marginBottom: 10,
|
||||
alignSelf: "flex-start",
|
||||
},
|
||||
backText: {
|
||||
color: "#fff",
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
},
|
||||
title: {
|
||||
color: "#fff",
|
||||
fontSize: 30,
|
||||
fontWeight: "700",
|
||||
marginBottom: 12,
|
||||
},
|
||||
trackText: { color: "#111", fontWeight: "600" },
|
||||
});
|
||||
Reference in New Issue
Block a user