UI changes and Android compatibility

This commit is contained in:
2026-03-05 13:14:26 +01:00
parent fe07112b1f
commit 0d944a6e46
10 changed files with 104 additions and 96 deletions

View File

@@ -3,6 +3,7 @@ import { StyleSheet, Text, View, Pressable } from "react-native";
import { NavigationContainer } from "@react-navigation/native"; import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { createContext, useState, useContext } from "react"; import { createContext, useState, useContext } from "react";
import { SafeAreaProvider } from "react-native-safe-area-context";
import MediaPlayer from "./src/components/MediaPlayer"; import MediaPlayer from "./src/components/MediaPlayer";
@@ -26,9 +27,7 @@ export function UsePlayer() {
} }
function PlayerProvider({ children }) { function PlayerProvider({ children }) {
const [currentTrack, setCurrentTrack] = useState({ const [currentTrack, setCurrentTrack] = useState();
title: "Swans - A Piece Of The Sky",
});
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
@@ -61,7 +60,7 @@ function RootLayout() {
const { currentTrack } = UsePlayer(); const { currentTrack } = UsePlayer();
return ( return (
<View style={styles.container}> <SafeAreaProvider style={styles.container}>
<StatusBar style="auto" /> <StatusBar style="auto" />
<AppNavigator /> <AppNavigator />
@@ -69,7 +68,7 @@ function RootLayout() {
<View style={styles.mediaPlayerWrapper} pointerEvents="box-none"> <View style={styles.mediaPlayerWrapper} pointerEvents="box-none">
<MediaPlayer title={currentTrack?.title ?? "No track"} /> <MediaPlayer title={currentTrack?.title ?? "No track"} />
</View> </View>
</View> </SafeAreaProvider>
); );
} }

View File

@@ -1,33 +1,38 @@
import { useState } from "react"; import { useState } from "react";
import { TouchableOpacity, Text, StyleSheet } from "react-native"; import { View, Text, StyleSheet } from "react-native";
import { View } from "react-native-web";
import { Button, Slider } from "rn-inkpad"; import { Button, Slider } from "rn-inkpad";
import { LinearGradient } from "expo-linear-gradient"; import { LinearGradient } from "expo-linear-gradient";
export default function MediaPlayer({ title, onPress }) { export default function MediaPlayer() {
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [progress, setProgress] = useState(35);
return ( return (
<LinearGradient <LinearGradient
colors={[ colors={[
"rgba(25,25,25,0.95)", // top "rgba(25,25,25,0.95)",
"rgba(15,15,15,0.98)", // middle-top "rgba(15,15,15,0.98)",
"rgba(5,5,5,1)", // middle-bottom "rgba(5,5,5,1)",
"rgba(5,5,5,1)", // bottom "rgba(5,5,5,1)",
]} ]}
locations={[0, 0.55, 1]} locations={[0, 0.55, 0.85, 1]}
start={{ x: 0.5, y: 0 }} start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }} end={{ x: 0.5, y: 1 }}
style={styles.box} style={styles.box}
> >
<Text style={styles.name_album_and_artist}>{title}</Text> <Text style={styles.name_album_and_artist}>
PlaceholderArtist - PlaceholderTrack
</Text>
<View style={styles.playback_row}> <View style={styles.playback_row}>
<Text style={styles.time}>1:20</Text> <Text style={styles.time}>1:20</Text>
<View style={styles.slider_wrapper}> <View style={styles.slider_wrapper}>
<Slider <Slider
value={35} value={progress}
min={0}
max={100}
onChange={setProgress}
onValueChange={setProgress}
trackStyles={{ trackStyles={{
height: 5, height: 5,
borderRadius: 5, borderRadius: 5,
@@ -39,7 +44,6 @@ export default function MediaPlayer({ title, onPress }) {
}} }}
/> />
</View> </View>
<Text style={styles.time}>4:08</Text> <Text style={styles.time}>4:08</Text>
</View> </View>
@@ -57,12 +61,14 @@ export default function MediaPlayer({ title, onPress }) {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
box: { box: {
borderRadius: 15, borderRadius: 15,
padding: 20, paddingHorizontal: 16,
paddingTop: 12,
paddingBottom: 10,
alignSelf: "stretch", alignSelf: "stretch",
marginHorizontal: 5, marginHorizontal: 5,
height: "20%", minHeight: 120,
justifyContent: "space-between",
alignItems: "center", alignItems: "center",
opacity: 0.98,
}, },
name_album_and_artist: { name_album_and_artist: {
color: "#ffffff", color: "#ffffff",
@@ -70,11 +76,10 @@ const styles = StyleSheet.create({
fontWeight: "bold", fontWeight: "bold",
}, },
playback_row: { playback_row: {
flex: 1,
flexDirection: "row", flexDirection: "row",
width: "100%", width: "100%",
alignItems: "center", alignItems: "center",
marginTop: 15, marginTop: 10,
}, },
slider_wrapper: { slider_wrapper: {
flex: 1, flex: 1,
@@ -84,4 +89,4 @@ const styles = StyleSheet.create({
color: "#ffffff", color: "#ffffff",
fontWeight: "bold", fontWeight: "bold",
}, },
}); });

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { Pressable, View, Text, StyleSheet, Image } from 'react-native'; import { Pressable, View, Text, StyleSheet, Image } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { durationFormatter } from './DurationFormatter'; import { durationFormatter } from './DurationFormatter';
export default function TrackRow({ export default function TrackRow({
@@ -16,20 +17,22 @@ export default function TrackRow({
{cover ? <Image source={cover} style={styles.trackCover} resizeMode="cover" /> : null} {cover ? <Image source={cover} style={styles.trackCover} resizeMode="cover" /> : null}
<View style={styles.trackTextBlock}> <View style={styles.trackTextBlock}>
<Text style={styles.trackTitle} numberOfLines={1}> <Text style={styles.trackTitle} numberOfLines={1}>{title}</Text>
{title} <Text style={styles.trackArtist} numberOfLines={1}>{artist}</Text>
</Text>
<Text style={styles.trackArtist} numberOfLines={1}>
{artist}
</Text>
</View> </View>
{duration ? <Text style={styles.trackDuration}>{durationFormatter(duration)}</Text> : "0"} {duration ? (
<Text style={styles.trackDuration}>{durationFormatter(duration)}</Text>
) : (
<Text style={styles.trackDuration}>0:00</Text>
)}
<Pressable onPress={onToggleLike} hitSlop={10}> <Pressable onPress={onToggleLike} hitSlop={10} style={styles.heartBtn}>
<Text style={[styles.heart, liked && styles.heartLiked]}> <Ionicons
{liked ? '♥' : '♡'} name={liked ? "heart" : "heart-outline"}
</Text> size={22}
color={liked ? "#ff4d6d" : "#fff"}
/>
</Pressable> </Pressable>
</Pressable> </Pressable>
); );
@@ -67,14 +70,9 @@ const styles = StyleSheet.create({
fontSize: 15, fontSize: 15,
paddingHorizontal: 18, paddingHorizontal: 18,
}, },
heart: { heartBtn: {
fontSize: 22, width: 32,
color: '#fff', alignItems: 'center',
paddingHorizontal: 10, justifyContent: 'center',
},
heartLiked: {
fontSize: 22,
color: '#ff4d6d',
paddingHorizontal: 10,
}, },
}); });

View File

@@ -1,5 +1,6 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { View, Text, StyleSheet, FlatList, Pressable, Image } from 'react-native'; import { View, Text, StyleSheet, FlatList, Pressable, Image } from 'react-native';
import { SafeAreaView } from "react-native-safe-area-context";
import TrackRow from '../components/TrackRow'; import TrackRow from '../components/TrackRow';
import { useLibrary } from '../contexts/LibraryContext'; import { useLibrary } from '../contexts/LibraryContext';
import { durationFormatter } from '../components/DurationFormatter'; import { durationFormatter } from '../components/DurationFormatter';
@@ -8,14 +9,13 @@ export default function AlbumScreen({ route, navigation }) {
const { album: routeAlbum } = route.params; const { album: routeAlbum } = route.params;
const { albums, toggleLike } = useLibrary(); const { albums, toggleLike } = useLibrary();
// Always use latest album from context (so likes stay in sync)
const album = useMemo( const album = useMemo(
() => albums.find((a) => a.id === routeAlbum.id) ?? routeAlbum, () => albums.find((a) => a.id === routeAlbum.id) ?? routeAlbum,
[albums, routeAlbum] [albums, routeAlbum]
); );
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.container}>
<Pressable onPress={() => navigation.goBack()} style={styles.backBtn}> <Pressable onPress={() => navigation.goBack()} style={styles.backBtn}>
<Text style={styles.backText}> Back</Text> <Text style={styles.backText}> Back</Text>
</Pressable> </Pressable>
@@ -46,7 +46,7 @@ export default function AlbumScreen({ route, navigation }) {
)} )}
contentContainerStyle={styles.trackList} contentContainerStyle={styles.trackList}
/> />
</View> </SafeAreaView>
); );
} }
@@ -54,48 +54,11 @@ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
backgroundColor: '#000', backgroundColor: '#000',
paddingTop: 28,
paddingHorizontal: 16, paddingHorizontal: 16,
paddingBottom: 24, 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: { backBtn: {
marginBottom: 10, marginBottom: 8,
alignSelf: 'flex-start', alignSelf: 'flex-start',
}, },
backText: { backText: {
@@ -103,4 +66,41 @@ const styles = StyleSheet.create({
fontSize: 16, fontSize: 16,
fontWeight: '600', fontWeight: '600',
}, },
header: {
alignItems: 'center',
marginBottom: 10,
},
cover: {
width: 140,
height: 140,
borderRadius: 8,
marginBottom: 8,
},
title: {
color: '#fff',
fontSize: 24,
fontWeight: '700',
textAlign: 'center',
marginBottom: 0,
},
artist: {
color: '#fff',
fontSize: 18,
fontWeight: '500',
textAlign: 'center',
marginTop: 1,
},
meta: {
color: '#cfcfcf',
fontSize: 12,
fontWeight: '500',
textAlign: 'center',
marginTop: 6,
marginBottom: 10,
},
trackList: {
width: '100%',
paddingHorizontal: 4,
paddingBottom: 180,
},
}); });

View File

@@ -11,6 +11,7 @@ import { Ionicons } from "@expo/vector-icons";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import TrackRow from "../components/TrackRow"; import TrackRow from "../components/TrackRow";
import { useLibrary } from "../contexts/LibraryContext"; import { useLibrary } from "../contexts/LibraryContext";
import { SafeAreaView } from "react-native-safe-area-context";
export default function HomeScreen() { export default function HomeScreen() {
const navigation = useNavigation(); const navigation = useNavigation();
@@ -19,7 +20,7 @@ export default function HomeScreen() {
const homeLikedTracks = useMemo(() => likedTracks.slice(0, 5), [likedTracks]); const homeLikedTracks = useMemo(() => likedTracks.slice(0, 5), [likedTracks]);
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.container}>
<View style={styles.headerRow}> <View style={styles.headerRow}>
<Text style={styles.homeTitle}>Home</Text> <Text style={styles.homeTitle}>Home</Text>
<View style={styles.headerActions}> <View style={styles.headerActions}>
@@ -77,7 +78,7 @@ export default function HomeScreen() {
)} )}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
/> />
</View> </SafeAreaView>
); );
} }

View File

@@ -9,6 +9,7 @@ import {
} from "react-native"; } from "react-native";
import TrackRow from "../components/TrackRow"; import TrackRow from "../components/TrackRow";
import { useLibrary } from "../contexts/LibraryContext"; import { useLibrary } from "../contexts/LibraryContext";
import { SafeAreaView } from "react-native-safe-area-context";
export default function LikedTracksScreen({ navigation }) { export default function LikedTracksScreen({ navigation }) {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
@@ -48,7 +49,7 @@ export default function LikedTracksScreen({ navigation }) {
}, [likedTracks, query, sortBy, sortDir]); }, [likedTracks, query, sortBy, sortDir]);
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.container}>
<Pressable onPress={() => navigation.goBack()} style={styles.backBtn}> <Pressable onPress={() => navigation.goBack()} style={styles.backBtn}>
<Text style={styles.backText}> Back</Text> <Text style={styles.backText}> Back</Text>
</Pressable> </Pressable>
@@ -123,7 +124,7 @@ export default function LikedTracksScreen({ navigation }) {
keyboardShouldPersistTaps="handled" keyboardShouldPersistTaps="handled"
contentContainerStyle={{ paddingBottom: 180 }} contentContainerStyle={{ paddingBottom: 180 }}
/> />
</View> </SafeAreaView>
); );
} }

View File

@@ -1,5 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { View, Text, StyleSheet, TextInput, Pressable } from "react-native"; import { View, Text, StyleSheet, TextInput, Pressable } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
export default function LoginScreen({ navigation }) { export default function LoginScreen({ navigation }) {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
@@ -18,7 +19,7 @@ export default function LoginScreen({ navigation }) {
}; };
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.container}>
<Pressable onPress={() => navigation.goBack()} style={styles.backBtn}> <Pressable onPress={() => navigation.goBack()} style={styles.backBtn}>
<Text style={styles.backText}> Back</Text> <Text style={styles.backText}> Back</Text>
</Pressable> </Pressable>
@@ -55,7 +56,7 @@ export default function LoginScreen({ navigation }) {
<Pressable style={styles.SignupBtn} onPress={OnSignUp}> <Pressable style={styles.SignupBtn} onPress={OnSignUp}>
<Text style={styles.SignupBtnText}>Sign Up</Text> <Text style={styles.SignupBtnText}>Sign Up</Text>
</Pressable> </Pressable>
</View> </SafeAreaView>
); );
} }

View File

@@ -1,5 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { View, Text, StyleSheet, TextInput, Pressable } from "react-native"; import { View, Text, StyleSheet, TextInput, Pressable } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
export default function PasswordResetScreen({ navigation }) { export default function PasswordResetScreen({ navigation }) {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
@@ -9,7 +10,7 @@ export default function PasswordResetScreen({ navigation }) {
}; };
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.container}>
<Pressable onPress={() => navigation.goBack()} style={styles.backBtn}> <Pressable onPress={() => navigation.goBack()} style={styles.backBtn}>
<Text style={styles.backText}> Back</Text> <Text style={styles.backText}> Back</Text>
</Pressable> </Pressable>
@@ -29,7 +30,7 @@ export default function PasswordResetScreen({ navigation }) {
<Pressable style={styles.primaryBtn} onPress={onResetButtonPress}> <Pressable style={styles.primaryBtn} onPress={onResetButtonPress}>
<Text style={styles.primaryBtnText}>Send email</Text> <Text style={styles.primaryBtnText}>Send email</Text>
</Pressable> </Pressable>
</View> </SafeAreaView>
); );
} }

View File

@@ -1,12 +1,13 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { View, Text, StyleSheet, Pressable, Switch } from "react-native"; import { View, Text, StyleSheet, Pressable, Switch } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
export default function SettingsScreen({ navigation }) { export default function SettingsScreen({ navigation }) {
const [notificationsEnabled, setNotificationsEnabled] = useState(true); const [notificationsEnabled, setNotificationsEnabled] = useState(true);
const [highQuality, setHighQuality] = useState(false); const [highQuality, setHighQuality] = useState(false);
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.container}>
<Pressable onPress={() => navigation.goBack()} style={styles.backBtn}> <Pressable onPress={() => navigation.goBack()} style={styles.backBtn}>
<Text style={styles.backText}> Back</Text> <Text style={styles.backText}> Back</Text>
</Pressable> </Pressable>
@@ -32,7 +33,7 @@ export default function SettingsScreen({ navigation }) {
> >
<Text style={styles.logoutBtnText}>Log out</Text> <Text style={styles.logoutBtnText}>Log out</Text>
</Pressable> </Pressable>
</View> </SafeAreaView>
); );
} }

View File

@@ -1,5 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { View, Text, StyleSheet, TextInput, Pressable } from "react-native"; import { View, Text, StyleSheet, TextInput, Pressable } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
export default function SignUpScreen({ navigation }) { export default function SignUpScreen({ navigation }) {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
@@ -10,7 +11,7 @@ export default function SignUpScreen({ navigation }) {
}; };
return ( return (
<View style={styles.container}> <SafeAreaView style={styles.container}>
<Pressable onPress={() => navigation.goBack()} style={styles.backBtn}> <Pressable onPress={() => navigation.goBack()} style={styles.backBtn}>
<Text style={styles.backText}> Back</Text> <Text style={styles.backText}> Back</Text>
</Pressable> </Pressable>
@@ -48,7 +49,7 @@ export default function SignUpScreen({ navigation }) {
<Pressable style={styles.primaryBtn} onPress={onSignUp}> <Pressable style={styles.primaryBtn} onPress={onSignUp}>
<Text style={styles.primaryBtnText}>Sign Up</Text> <Text style={styles.primaryBtnText}>Sign Up</Text>
</Pressable> </Pressable>
</View> </SafeAreaView>
); );
} }