fix: add error boundary, harden player setup, catch init errors

This commit is contained in:
Honey committed 2026-06-10 09:51:42 +02:00
1 parent 2327778136
commit a65a3eeb5b
2 files changed
+80 -42

No files matched your search

+63 -23
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState, Component } from 'react';
import { View, StatusBar } from 'react-native'; import { View, StatusBar, Text, ScrollView } from 'react-native';
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
@@ -19,6 +19,30 @@ import LoginScreen from './src/screens/Login';
import NowPlayingScreen from './src/screens/NowPlaying'; import NowPlayingScreen from './src/screens/NowPlaying';
import PlayerBar from './src/components/PlayerBar'; import PlayerBar from './src/components/PlayerBar';
// ─── ERROR BOUNDARY ───
class ErrorBoundary extends Component {
state = { error: null };
static getDerivedStateFromError(e) { return { error: e }; }
render() {
if (this.state.error) {
return (
<ScrollView style={{ flex: 1, backgroundColor: '#121212', padding: 24, paddingTop: 60 }}>
<Text style={{ color: '#e05252', fontSize: 16, fontWeight: '700', marginBottom: 12 }}>
Fehler beim Starten
</Text>
<Text style={{ color: '#fff', fontSize: 13, fontFamily: 'monospace' }}>
{this.state.error?.message || String(this.state.error)}
</Text>
<Text style={{ color: '#aaa', fontSize: 12, marginTop: 16 }}>
{this.state.error?.stack}
</Text>
</ScrollView>
);
}
return this.props.children;
}
}
const Tab = createBottomTabNavigator(); const Tab = createBottomTabNavigator();
const Stack = createStackNavigator(); const Stack = createStackNavigator();
@@ -63,11 +87,16 @@ function RootNavigator() {
export default function App() { export default function App() {
const { loadConfig } = useStore(); const { loadConfig } = useStore();
const [ready, setReady] = useState(false); const [ready, setReady] = useState(false);
const [initError, setInitError] = useState(null);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
await loadConfig(); try {
await setupPlayer(); await loadConfig();
await setupPlayer();
} catch (e) {
setInitError(e?.message || String(e));
}
setReady(true); setReady(true);
})(); })();
}, []); }, []);
@@ -82,26 +111,37 @@ export default function App() {
return () => { sub1.remove(); sub2.remove(); }; return () => { sub1.remove(); sub2.remove(); };
}, []); }, []);
if (!ready) return null; if (!ready) return (
<View style={{ flex: 1, backgroundColor: '#121212' }} />
);
if (initError) return (
<ScrollView style={{ flex: 1, backgroundColor: '#121212', padding: 24, paddingTop: 60 }}>
<Text style={{ color: '#e05252', fontSize: 16, fontWeight: '700', marginBottom: 12 }}>Init-Fehler</Text>
<Text style={{ color: '#fff', fontSize: 13 }}>{initError}</Text>
</ScrollView>
);
return ( return (
<GestureHandlerRootView style={{ flex: 1 }}> <ErrorBoundary>
<SafeAreaProvider> <GestureHandlerRootView style={{ flex: 1 }}>
<StatusBar barStyle="light-content" backgroundColor="#121212" /> <SafeAreaProvider>
<NavigationContainer theme={{ <StatusBar barStyle="light-content" backgroundColor="#121212" />
dark: true, <NavigationContainer theme={{
colors: { dark: true,
primary: '#1db954', colors: {
background: '#121212', primary: '#1db954',
card: '#1e1e1e', background: '#121212',
text: '#ffffff', card: '#1e1e1e',
border: '#2a2a2a', text: '#ffffff',
notification: '#1db954', border: '#2a2a2a',
}, notification: '#1db954',
}}> },
<RootNavigator /> }}>
</NavigationContainer> <RootNavigator />
</SafeAreaProvider> </NavigationContainer>
</GestureHandlerRootView> </SafeAreaProvider>
</GestureHandlerRootView>
</ErrorBoundary>
); );
} }
+17 -19
View File
@@ -6,25 +6,23 @@ let _setupDone = false;
export async function setupPlayer() { export async function setupPlayer() {
if (_setupDone) return; if (_setupDone) return;
await TrackPlayer.setupPlayer({ autoHandleInterruptions: true }); try {
await TrackPlayer.updateOptions({ await TrackPlayer.setupPlayer();
capabilities: [ await TrackPlayer.updateOptions({
Capability.Play, capabilities: [
Capability.Pause, Capability.Play,
Capability.SkipToNext, Capability.Pause,
Capability.SkipToPrevious, Capability.SkipToNext,
Capability.SeekTo, Capability.SkipToPrevious,
Capability.Stop, Capability.SeekTo,
], Capability.Stop,
compactCapabilities: [Capability.Play, Capability.Pause, Capability.SkipToNext, Capability.SkipToPrevious], ],
notificationCapabilities: [ compactCapabilities: [Capability.Play, Capability.Pause, Capability.SkipToNext, Capability.SkipToPrevious],
Capability.Play, });
Capability.Pause, _setupDone = true;
Capability.SkipToNext, } catch (e) {
Capability.SkipToPrevious, console.error('TrackPlayer setup failed:', e);
], }
});
_setupDone = true;
} }
// Convert a Navidrome song object to a react-native-track-player track object // Convert a Navidrome song object to a react-native-track-player track object