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:
HoneyandClaude Sonnet 4.6 committed 2026-06-10 14:22:02 +02:00
1 parent 98394bab75
commit b25e162806
6 files changed
+36 -16

No files matched your search

+20 -5
View File
@@ -50,17 +50,32 @@ function HomeTabs() {
return ( return (
<Tab.Navigator <Tab.Navigator
screenOptions={({ route }) => ({ screenOptions={({ route }) => ({
headerShown: false, headerStyle: {
tabBarStyle: { backgroundColor: '#1e1e1e', borderTopColor: '#2a2a2a' }, 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', tabBarActiveTintColor: '#1db954',
tabBarInactiveTintColor: '#888', tabBarInactiveTintColor: '#666',
tabBarLabelStyle: { fontSize: 10, fontWeight: '600', marginTop: 2 },
tabBarIcon: ({ color, size }) => { tabBarIcon: ({ color, size }) => {
const icons = { Home: 'home', Artists: 'person', Albums: 'disc', Search: 'search', Settings: 'settings-outline' }; 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="Artists" component={ArtistsScreen} options={{ title: 'Künstler' }} />
<Tab.Screen name="Albums" component={AlbumsScreen} options={{ title: 'Alben' }} /> <Tab.Screen name="Albums" component={AlbumsScreen} options={{ title: 'Alben' }} />
<Tab.Screen name="Search" component={SearchScreen} options={{ title: 'Suche' }} /> <Tab.Screen name="Search" component={SearchScreen} options={{ title: 'Suche' }} />
+5 -3
View File
@@ -4,6 +4,7 @@ import {
StyleSheet, ActivityIndicator, StyleSheet, ActivityIndicator,
} from 'react-native'; } from 'react-native';
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { api, ensureArtistSortMap, dispAlbumName, dispArtistById, coverUrl } from '../api'; import { api, ensureArtistSortMap, dispAlbumName, dispArtistById, coverUrl } from '../api';
import { playQueue, skipToIndex } from '../audio/player'; import { playQueue, skipToIndex } from '../audio/player';
@@ -12,6 +13,7 @@ import SongItem from '../components/SongItem';
export default function AlbumDetailScreen() { export default function AlbumDetailScreen() {
const navigation = useNavigation(); const navigation = useNavigation();
const insets = useSafeAreaInsets();
const { album } = useRoute().params; const { album } = useRoute().params;
const { cfg } = useStore(); const { cfg } = useStore();
const [songs, setSongs] = useState([]); const [songs, setSongs] = useState([]);
@@ -49,7 +51,7 @@ export default function AlbumDetailScreen() {
ListHeaderComponent={() => ( ListHeaderComponent={() => (
<View> <View>
{/* Back button */} {/* 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" /> <Ionicons name="chevron-back" size={24} color="#fff" />
</TouchableOpacity> </TouchableOpacity>
@@ -111,10 +113,10 @@ export default function AlbumDetailScreen() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#121212' }, container: { flex: 1, backgroundColor: '#121212' },
backBtn: { 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, 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: { art: {
width: 220, height: 220, borderRadius: 8, width: 220, height: 220, borderRadius: 8,
shadowColor: '#000', shadowOpacity: 0.5, shadowRadius: 16, shadowColor: '#000', shadowOpacity: 0.5, shadowRadius: 16,
+4 -2
View File
@@ -6,12 +6,14 @@ import {
const SCREEN_WIDTH = Dimensions.get('window').width; const SCREEN_WIDTH = Dimensions.get('window').width;
import { useNavigation, useRoute } from '@react-navigation/native'; import { useNavigation, useRoute } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { api, apiCached, dispArtistById, dispAlbumName, coverUrl, getArtistImage, cacheArtistSortNames } from '../api'; import { api, apiCached, dispArtistById, dispAlbumName, coverUrl, getArtistImage, cacheArtistSortNames } from '../api';
import AlbumCard from '../components/AlbumCard'; import AlbumCard from '../components/AlbumCard';
export default function ArtistDetailScreen() { export default function ArtistDetailScreen() {
const navigation = useNavigation(); const navigation = useNavigation();
const insets = useSafeAreaInsets();
const { artist } = useRoute().params; const { artist } = useRoute().params;
const [albums, setAlbums] = useState([]); const [albums, setAlbums] = useState([]);
const [img, setImg] = useState(null); const [img, setImg] = useState(null);
@@ -43,7 +45,7 @@ export default function ArtistDetailScreen() {
<Text style={{ fontSize: 64 }}>🎤</Text> <Text style={{ fontSize: 64 }}>🎤</Text>
</View> </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" /> <Ionicons name="chevron-back" size={24} color="#fff" />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.heroOverlay}> <View style={styles.heroOverlay}>
@@ -91,7 +93,7 @@ const styles = StyleSheet.create({
heroName: { color: '#fff', fontSize: 24, fontWeight: '800', textShadowColor: '#000', textShadowRadius: 8 }, heroName: { color: '#fff', fontSize: 24, fontWeight: '800', textShadowColor: '#000', textShadowRadius: 8 },
heroSub: { color: '#ddd', fontSize: 13, marginTop: 2 }, heroSub: { color: '#ddd', fontSize: 13, marginTop: 2 },
backBtn: { backBtn: {
position: 'absolute', top: 44, left: 12, position: 'absolute', left: 12,
backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 20, backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 20,
padding: 6, padding: 6,
}, },
-1
View File
@@ -92,7 +92,6 @@ export default function HomeScreen() {
style={styles.container} style={styles.container}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={() => { setRefreshing(true); load(true); }} tintColor="#1db954" />} 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="Meistgespielt" albums={frequent} onAlbumPress={goAlbum} />
<AlbumRow title="Neu hinzugefügt" albums={newest} onAlbumPress={goAlbum} /> <AlbumRow title="Neu hinzugefügt" albums={newest} onAlbumPress={goAlbum} />
+1 -1
View File
@@ -120,7 +120,7 @@ export default function LoginScreen() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#121212' }, container: { flex: 1, backgroundColor: '#121212' },
content: { padding: 24, paddingTop: 60 }, content: { padding: 24, paddingTop: 24 },
logo: { fontSize: 32, textAlign: 'center', marginBottom: 8 }, logo: { fontSize: 32, textAlign: 'center', marginBottom: 8 },
subtitle: { color: '#aaa', textAlign: 'center', marginBottom: 32, fontSize: 14 }, subtitle: { color: '#aaa', textAlign: 'center', marginBottom: 32, fontSize: 14 },
label: { color: '#ccc', fontSize: 13, marginBottom: 6, marginTop: 16 }, label: { color: '#ccc', fontSize: 13, marginBottom: 6, marginTop: 16 },
+6 -4
View File
@@ -1,9 +1,10 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { import {
View, Text, Image, TouchableOpacity, View, Text, Image, TouchableOpacity,
StyleSheet, Dimensions, PanResponder, StyleSheet, Dimensions,
} from 'react-native'; } from 'react-native';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { usePlaybackState, useProgress, State } from 'react-native-track-player'; import { usePlaybackState, useProgress, State } from 'react-native-track-player';
import Slider from '@react-native-community/slider'; import Slider from '@react-native-community/slider';
@@ -15,6 +16,7 @@ const { width } = Dimensions.get('window');
export default function NowPlayingScreen() { export default function NowPlayingScreen() {
const navigation = useNavigation(); const navigation = useNavigation();
const insets = useSafeAreaInsets();
const { currentTrack, repeat, shuffle, setShuffle } = useStore(); const { currentTrack, repeat, shuffle, setShuffle } = useStore();
const playbackState = usePlaybackState(); const playbackState = usePlaybackState();
const { position, duration } = useProgress(500); const { position, duration } = useProgress(500);
@@ -38,7 +40,7 @@ export default function NowPlayingScreen() {
} }
return ( return (
<View style={styles.container}> <View style={[styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom + 16 }]}>
{/* Header */} {/* Header */}
<View style={styles.header}> <View style={styles.header}>
<TouchableOpacity onPress={() => navigation.goBack()} style={styles.headerBtn}> <TouchableOpacity onPress={() => navigation.goBack()} style={styles.headerBtn}>
@@ -119,13 +121,13 @@ export default function NowPlayingScreen() {
} }
const styles = StyleSheet.create({ 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' }, empty: { flex: 1, backgroundColor: '#121212', alignItems: 'center', justifyContent: 'center' },
emptyText: { color: '#aaa', fontSize: 16 }, emptyText: { color: '#aaa', fontSize: 16 },
back: { color: '#1db954', marginTop: 12, fontSize: 14 }, back: { color: '#1db954', marginTop: 12, fontSize: 14 },
header: { header: {
flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', 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' }, headerBtn: { width: 40, alignItems: 'flex-start' },
headerTitle: { color: '#fff', fontSize: 14, fontWeight: '700', letterSpacing: 0.5 }, headerTitle: { color: '#fff', fontSize: 14, fontWeight: '700', letterSpacing: 0.5 },