fix: safe area and UI polish pass
- Enable dark navigation headers in Tab navigator (fixes status bar overlap on all 5 tab screens automatically) - Use useSafeAreaInsets for AlbumDetail/ArtistDetail back buttons - Use useSafeAreaInsets for NowPlaying header and bottom padding - Remove redundant paddingTop from Home heading and Login content - Cleaner tab bar (height 60, better label spacing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
98394bab75
commit
b25e162806
6 files changed
+37
-17
No files matched your search
+20
-5
@@ -50,17 +50,32 @@ function HomeTabs() {
|
||||
return (
|
||||
<Tab.Navigator
|
||||
screenOptions={({ route }) => ({
|
||||
headerShown: false,
|
||||
tabBarStyle: { backgroundColor: '#1e1e1e', borderTopColor: '#2a2a2a' },
|
||||
headerStyle: {
|
||||
backgroundColor: '#121212',
|
||||
elevation: 0,
|
||||
shadowOpacity: 0,
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
headerTintColor: '#fff',
|
||||
headerTitleStyle: { fontWeight: '800', fontSize: 20 },
|
||||
tabBarStyle: {
|
||||
backgroundColor: '#1a1a1a',
|
||||
borderTopColor: '#2a2a2a',
|
||||
borderTopWidth: 1,
|
||||
height: 60,
|
||||
paddingBottom: 8,
|
||||
paddingTop: 4,
|
||||
},
|
||||
tabBarActiveTintColor: '#1db954',
|
||||
tabBarInactiveTintColor: '#888',
|
||||
tabBarInactiveTintColor: '#666',
|
||||
tabBarLabelStyle: { fontSize: 10, fontWeight: '600', marginTop: 2 },
|
||||
tabBarIcon: ({ color, size }) => {
|
||||
const icons = { Home: 'home', Artists: 'person', Albums: 'disc', Search: 'search', Settings: 'settings-outline' };
|
||||
return <Ionicons name={icons[route.name] || 'ellipse'} size={size} color={color} />;
|
||||
return <Ionicons name={icons[route.name] || 'ellipse'} size={22} color={color} />;
|
||||
},
|
||||
})}
|
||||
>
|
||||
<Tab.Screen name="Home" component={HomeScreen} options={{ title: 'Start' }} />
|
||||
<Tab.Screen name="Home" component={HomeScreen} options={{ title: 'Start', headerTitle: 'NovaSonic' }} />
|
||||
<Tab.Screen name="Artists" component={ArtistsScreen} options={{ title: 'Künstler' }} />
|
||||
<Tab.Screen name="Albums" component={AlbumsScreen} options={{ title: 'Alben' }} />
|
||||
<Tab.Screen name="Search" component={SearchScreen} options={{ title: 'Suche' }} />
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
StyleSheet, ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { api, ensureArtistSortMap, dispAlbumName, dispArtistById, coverUrl } from '../api';
|
||||
import { playQueue, skipToIndex } from '../audio/player';
|
||||
@@ -12,6 +13,7 @@ import SongItem from '../components/SongItem';
|
||||
|
||||
export default function AlbumDetailScreen() {
|
||||
const navigation = useNavigation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { album } = useRoute().params;
|
||||
const { cfg } = useStore();
|
||||
const [songs, setSongs] = useState([]);
|
||||
@@ -49,7 +51,7 @@ export default function AlbumDetailScreen() {
|
||||
ListHeaderComponent={() => (
|
||||
<View>
|
||||
{/* Back button */}
|
||||
<TouchableOpacity style={styles.backBtn} onPress={() => navigation.goBack()}>
|
||||
<TouchableOpacity style={[styles.backBtn, { top: insets.top + 8 }]} onPress={() => navigation.goBack()}>
|
||||
<Ionicons name="chevron-back" size={24} color="#fff" />
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -111,10 +113,10 @@ export default function AlbumDetailScreen() {
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#121212' },
|
||||
backBtn: {
|
||||
position: 'absolute', top: 44, left: 12, zIndex: 10,
|
||||
position: 'absolute', left: 12, zIndex: 10,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 20, padding: 6,
|
||||
},
|
||||
artWrap: { alignItems: 'center', paddingTop: 60, paddingBottom: 16 },
|
||||
artWrap: { alignItems: 'center', paddingTop: 72, paddingBottom: 16 },
|
||||
art: {
|
||||
width: 220, height: 220, borderRadius: 8,
|
||||
shadowColor: '#000', shadowOpacity: 0.5, shadowRadius: 16,
|
||||
|
||||
@@ -6,12 +6,14 @@ import {
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { api, apiCached, dispArtistById, dispAlbumName, coverUrl, getArtistImage, cacheArtistSortNames } from '../api';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
|
||||
export default function ArtistDetailScreen() {
|
||||
const navigation = useNavigation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { artist } = useRoute().params;
|
||||
const [albums, setAlbums] = useState([]);
|
||||
const [img, setImg] = useState(null);
|
||||
@@ -43,7 +45,7 @@ export default function ArtistDetailScreen() {
|
||||
<Text style={{ fontSize: 64 }}>🎤</Text>
|
||||
</View>
|
||||
)}
|
||||
<TouchableOpacity style={styles.backBtn} onPress={() => navigation.goBack()}>
|
||||
<TouchableOpacity style={[styles.backBtn, { top: insets.top + 8 }]} onPress={() => navigation.goBack()}>
|
||||
<Ionicons name="chevron-back" size={24} color="#fff" />
|
||||
</TouchableOpacity>
|
||||
<View style={styles.heroOverlay}>
|
||||
@@ -91,7 +93,7 @@ const styles = StyleSheet.create({
|
||||
heroName: { color: '#fff', fontSize: 24, fontWeight: '800', textShadowColor: '#000', textShadowRadius: 8 },
|
||||
heroSub: { color: '#ddd', fontSize: 13, marginTop: 2 },
|
||||
backBtn: {
|
||||
position: 'absolute', top: 44, left: 12,
|
||||
position: 'absolute', left: 12,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 20,
|
||||
padding: 6,
|
||||
},
|
||||
|
||||
@@ -92,8 +92,7 @@ export default function HomeScreen() {
|
||||
style={styles.container}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={() => { setRefreshing(true); load(true); }} tintColor="#1db954" />}
|
||||
>
|
||||
<Text style={styles.heading}>Willkommen zurück 👋</Text>
|
||||
<AlbumRow title="Zuletzt gehört" albums={recent} onAlbumPress={goAlbum} />
|
||||
<AlbumRow title="Zuletzt gehört" albums={recent} onAlbumPress={goAlbum} />
|
||||
<AlbumRow title="Meistgespielt" albums={frequent} onAlbumPress={goAlbum} />
|
||||
<AlbumRow title="Neu hinzugefügt" albums={newest} onAlbumPress={goAlbum} />
|
||||
<View style={{ height: 16 }} />
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function LoginScreen() {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#121212' },
|
||||
content: { padding: 24, paddingTop: 60 },
|
||||
content: { padding: 24, paddingTop: 24 },
|
||||
logo: { fontSize: 32, textAlign: 'center', marginBottom: 8 },
|
||||
subtitle: { color: '#aaa', textAlign: 'center', marginBottom: 32, fontSize: 14 },
|
||||
label: { color: '#ccc', fontSize: 13, marginBottom: 6, marginTop: 16 },
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View, Text, Image, TouchableOpacity,
|
||||
StyleSheet, Dimensions, PanResponder,
|
||||
StyleSheet, Dimensions,
|
||||
} from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { usePlaybackState, useProgress, State } from 'react-native-track-player';
|
||||
import Slider from '@react-native-community/slider';
|
||||
@@ -15,6 +16,7 @@ const { width } = Dimensions.get('window');
|
||||
|
||||
export default function NowPlayingScreen() {
|
||||
const navigation = useNavigation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { currentTrack, repeat, shuffle, setShuffle } = useStore();
|
||||
const playbackState = usePlaybackState();
|
||||
const { position, duration } = useProgress(500);
|
||||
@@ -38,7 +40,7 @@ export default function NowPlayingScreen() {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom + 16 }]}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.headerBtn}>
|
||||
@@ -119,13 +121,13 @@ export default function NowPlayingScreen() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: '#121212', paddingBottom: 40 },
|
||||
container: { flex: 1, backgroundColor: '#121212' },
|
||||
empty: { flex: 1, backgroundColor: '#121212', alignItems: 'center', justifyContent: 'center' },
|
||||
emptyText: { color: '#aaa', fontSize: 16 },
|
||||
back: { color: '#1db954', marginTop: 12, fontSize: 14 },
|
||||
header: {
|
||||
flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between',
|
||||
paddingTop: 52, paddingHorizontal: 16, paddingBottom: 12,
|
||||
paddingTop: 12, paddingHorizontal: 16, paddingBottom: 12,
|
||||
},
|
||||
headerBtn: { width: 40, alignItems: 'flex-start' },
|
||||
headerTitle: { color: '#fff', fontSize: 14, fontWeight: '700', letterSpacing: 0.5 },
|
||||
|
||||
Reference in new issue
Block a user