cb0e879118
Bisher pausierte Android nach ~30s im Hintergrund die JS-Engine.
WebSocket schlief ein, Trigger-Replies vom Brain kamen nicht durch,
Timer-Erinnerungen feuerten in der App nicht obwohl im Brain
ausgeloest. Nach laengerer Hintergrund-Pause warf Android den
Prozess ganz raus → beim Wiedereroeffnen Cold-Start, sah aus wie Crash.
Loesung: Foreground-Service mit persistenter Notification — die ist
ohnehin schon da fuer TTS/Mic-Aktivitaet (`AriaPlaybackService`).
Wir erweitern das Slot-System um einen `background`-Slot der dauerhaft
aktiv ist (Settings-Toggle, default an). Notification zeigt "ARIA aktiv
— Hintergrund-Modus" wenn nichts spezifisches laeuft, escaliert zu
"ARIA spricht/hoert" bei TTS/Mic. Tap → App.
Drei Dateien:
- services/backgroundAudio.ts: 'background' als 4. Slot (niedrigste
Prio, Fallback-Notification). Bestehende tts/rec/wake unveraendert.
- App.tsx: beim Start `acquireBackgroundAudio('background')` aufrufen
wenn Settings nicht explizit deaktiviert. Plus POST_NOTIFICATIONS-
Permission-Request (Android 13+).
- screens/SettingsScreen.tsx: neuer Toggle in Allgemein-Section.
Plus Hinweis auf Android-Akku-Optimierung-Whitelist falls trotzdem
was klemmt (manche Hersteller-ROMs killen aggressiv).
AndroidManifest unveraendert — foregroundServiceType="mediaPlayback|
microphone" deckt unseren Use-Case ab (ARIA spielt regelmaessig TTS
ab, was den Type rechtfertigt). Service stoppt sich selbst wenn alle
Slots leer sind, das passiert nur wenn der User in Settings den
Hintergrund-Modus deaktiviert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
187 lines
5.7 KiB
TypeScript
187 lines
5.7 KiB
TypeScript
/**
|
|
* ARIA Cockpit - Haupteinstiegspunkt
|
|
*
|
|
* Stefans primaere Schnittstelle zu ARIA.
|
|
* Bottom-Tab-Navigation mit Chat und Einstellungen.
|
|
*/
|
|
|
|
import React, { useEffect } from 'react';
|
|
import { PermissionsAndroid, Platform, StatusBar, StyleSheet } from 'react-native';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
|
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
|
|
|
import ChatScreen from './src/screens/ChatScreen';
|
|
import SettingsScreen from './src/screens/SettingsScreen';
|
|
import rvs from './src/services/rvs';
|
|
import { initLogger, installGlobalCrashReporter } from './src/services/logger';
|
|
import { acquireBackgroundAudio } from './src/services/backgroundAudio';
|
|
|
|
// --- Navigation ---
|
|
|
|
const Tab = createBottomTabNavigator();
|
|
|
|
// Dunkles Theme fuer die gesamte App
|
|
const DarkTheme = {
|
|
...DefaultTheme,
|
|
dark: true,
|
|
colors: {
|
|
...DefaultTheme.colors,
|
|
primary: '#0096FF',
|
|
background: '#0D0D1A',
|
|
card: '#12122A',
|
|
text: '#FFFFFF',
|
|
border: '#1E1E2E',
|
|
notification: '#FF3B30',
|
|
},
|
|
};
|
|
|
|
// Tab-Icons (Text-basiert, kein Icon-Paket noetig)
|
|
const TAB_ICONS: Record<string, { active: string; inactive: string }> = {
|
|
Chat: { active: '\uD83D\uDCAC', inactive: '\uD83D\uDCAC' },
|
|
Einstellungen: { active: '\u2699\uFE0F', inactive: '\u2699\uFE0F' },
|
|
};
|
|
|
|
// --- App ---
|
|
|
|
const App: React.FC = () => {
|
|
// Beim Start: gespeicherte RVS-Konfiguration laden und verbinden
|
|
useEffect(() => {
|
|
// Verbose-Logging-Setting laden BEVOR andere Module loslegen.
|
|
// initLogger ist async aber blockt nichts — solange er noch laueft,
|
|
// loggen wir normal (Default an), danach respektiert console.log das Setting.
|
|
initLogger().catch(() => {});
|
|
// Crash-Reporter installieren — ungefangene JS-Errors landen via RVS
|
|
// bei der Bridge (sichtbar in /shared/logs/app.log + Diagnostic-API)
|
|
installGlobalCrashReporter();
|
|
const initConnection = async () => {
|
|
const config = await rvs.loadConfig();
|
|
if (config) {
|
|
rvs.setConfig(config);
|
|
rvs.connect();
|
|
}
|
|
};
|
|
initConnection();
|
|
|
|
// Hintergrund-Modus: Foreground-Service starten damit JS-Engine +
|
|
// WebSocket auch ueberleben wenn die App im Hintergrund ist.
|
|
// Trigger-Replies, Reconnects, Timer-Erinnerungen kommen sonst nicht
|
|
// durch weil Android nach ~30s die JS-Engine pausiert.
|
|
//
|
|
// Default an, kann in Settings → Hintergrund-Modus deaktiviert werden.
|
|
// Braucht POST_NOTIFICATIONS Permission ab Android 13.
|
|
const initBackground = async () => {
|
|
const setting = await AsyncStorage.getItem('aria_background_mode');
|
|
if (setting === 'false') {
|
|
console.log('[App] Hintergrund-Modus deaktiviert (Settings)');
|
|
return;
|
|
}
|
|
// Permission fuer die persistente Notification
|
|
if (Platform.OS === 'android' && Platform.Version >= 33) {
|
|
try {
|
|
await PermissionsAndroid.request(
|
|
'android.permission.POST_NOTIFICATIONS' as any,
|
|
{
|
|
title: 'Hintergrund-Modus',
|
|
message: 'ARIA zeigt eine Notification damit Trigger und Reconnects auch laufen wenn die App im Hintergrund ist.',
|
|
buttonPositive: 'Erlauben',
|
|
buttonNegative: 'Spaeter',
|
|
},
|
|
);
|
|
} catch {}
|
|
}
|
|
try {
|
|
await acquireBackgroundAudio('background');
|
|
console.log('[App] Hintergrund-Modus aktiv');
|
|
} catch (err: any) {
|
|
console.warn('[App] Hintergrund-Modus konnte nicht starten:', err?.message || err);
|
|
}
|
|
};
|
|
initBackground();
|
|
|
|
// Beim Beenden: Verbindung sauber trennen
|
|
return () => {
|
|
rvs.disconnect();
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
<StatusBar barStyle="light-content" backgroundColor="#0D0D1A" />
|
|
<NavigationContainer theme={DarkTheme}>
|
|
<Tab.Navigator
|
|
screenOptions={({ route }) => ({
|
|
headerStyle: styles.header,
|
|
headerTitleStyle: styles.headerTitle,
|
|
headerTintColor: '#FFFFFF',
|
|
tabBarStyle: styles.tabBar,
|
|
tabBarActiveTintColor: '#0096FF',
|
|
tabBarInactiveTintColor: '#555570',
|
|
tabBarIcon: ({ focused }) => {
|
|
const icons = TAB_ICONS[route.name];
|
|
return (
|
|
<React.Fragment>
|
|
{/* Emoji als Icon */}
|
|
{React.createElement(
|
|
require('react-native').Text,
|
|
{
|
|
style: {
|
|
fontSize: 22,
|
|
opacity: focused ? 1 : 0.5,
|
|
},
|
|
},
|
|
icons ? (focused ? icons.active : icons.inactive) : '?',
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
},
|
|
})}
|
|
>
|
|
<Tab.Screen
|
|
name="Chat"
|
|
component={ChatScreen}
|
|
options={{
|
|
title: 'ARIA Chat',
|
|
headerTitle: 'ARIA Cockpit',
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="Einstellungen"
|
|
component={SettingsScreen}
|
|
options={{
|
|
title: 'Einstellungen',
|
|
}}
|
|
/>
|
|
</Tab.Navigator>
|
|
</NavigationContainer>
|
|
</>
|
|
);
|
|
};
|
|
|
|
// --- Styles ---
|
|
|
|
const styles = StyleSheet.create({
|
|
header: {
|
|
backgroundColor: '#12122A',
|
|
elevation: 0,
|
|
shadowOpacity: 0,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#1E1E2E',
|
|
},
|
|
headerTitle: {
|
|
color: '#FFFFFF',
|
|
fontSize: 18,
|
|
fontWeight: '700',
|
|
},
|
|
tabBar: {
|
|
backgroundColor: '#12122A',
|
|
borderTopColor: '#1E1E2E',
|
|
borderTopWidth: 1,
|
|
height: 60,
|
|
paddingBottom: 6,
|
|
paddingTop: 4,
|
|
},
|
|
});
|
|
|
|
export default App;
|