Switch mobile app to dark theme and fix calendar filtering

Switch mobile app to dark theme and fix calendar filtering

- Default to dark theme in mobile app, matching web app style
- Filter native calendar events by location to exclude empty ones
- Add batch edit panel for destinations on web calendar
- Add edit support to AddEventModal
- Update notification trigger input type export
This commit is contained in:
2026-05-13 09:45:55 +02:00
parent de9a8606ab
commit 9e461aab1a
22 changed files with 632 additions and 168 deletions
+36 -11
View File
@@ -29,6 +29,7 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
const [query, setQuery] = useState('');
const [results, setResults] = useState<Station[]>([]);
const [searching, setSearching] = useState(false);
const [searchError, setSearchError] = useState<string | null>(null);
const [notifSettings, setNotifSettings] = useState<ReminderSettings>({
bufferMinutes: 30,
enabled: true,
@@ -41,11 +42,11 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const colors = dark ? {
background: '#1c1c1e',
card: '#2c2c2e',
text: '#f2f2f2',
subtext: '#aeaeb2',
accent: '#0a84ff',
background: '#090816',
card: '#17112A',
text: '#F4F1EA',
subtext: 'rgba(244,241,234,0.5)',
accent: '#8B5CF6',
border: '#38383a',
success: '#30d158',
} : {
@@ -53,7 +54,7 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
card: '#ffffff',
text: '#1c1c1e',
subtext: '#8e8e93',
accent: '#007AFF',
accent: '#B23CFF',
border: '#e5e5ea',
success: '#34C759',
};
@@ -72,14 +73,18 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
const searchStation = useCallback(async (q: string) => {
if (q.trim().length < 2) {
setResults([]);
setSearchError(null);
return;
}
setSearching(true);
setSearchError(null);
try {
const stations = await api.searchStation(q.trim());
setResults(stations);
if (stations.length === 0) setSearchError('Keine Stationen gefunden.');
} catch {
setResults([]);
setSearchError('API nicht erreichbar. Läuft der Server auf deinem Gerät? Überprüfe EXPO_PUBLIC_API_BASE_URL in .env.');
} finally {
setSearching(false);
}
@@ -87,6 +92,7 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
const onQueryChange = (text: string) => {
setQuery(text);
setSearchError(null);
// Proper debounce using useRef — no `any`
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
searchTimerRef.current = setTimeout(() => searchStation(text), 400);
@@ -121,8 +127,10 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
// Try the WienerLinien nearby-stops proxy first
let stops: Awaited<ReturnType<typeof api.findNearbyStops>> | null = null;
let apiReachable = false;
try {
stops = await api.findNearbyStops(userLat, userLng, 2000);
apiReachable = true;
} catch {
// API unavailable, will fall back to HAFAS LocMatch below
}
@@ -146,15 +154,28 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
}
// Fallback: use HAFAS LocMatch directly (same pattern as the web app)
const nearestStation = await api.findNearestStationByCoords(userLat, userLng);
if (!nearestStation) {
Alert.alert('Keine Station gefunden', 'Kein ÖPNV-Halt in der Nähe gefunden.');
try {
const nearestStation = await api.findNearestStationByCoords(userLat, userLng);
if (!nearestStation) {
Alert.alert('Keine Station gefunden', 'Kein ÖPNV-Halt in der Nähe gefunden.');
return;
}
await selectStation(nearestStation);
return;
} catch {
// API not reachable
}
await selectStation(nearestStation);
if (!apiReachable) {
Alert.alert(
'API nicht erreichbar',
'Der Server konnte nicht erreicht werden. Stelle sicher, dass EXPO_PUBLIC_API_BASE_URL in der .env-Datei auf die LAN-IP deines Entwicklungsrechners zeigt (z. B. http://192.168.1.x:3000) und nicht auf localhost.'
);
} else {
Alert.alert('Keine Station gefunden', 'Kein ÖPNV-Halt in der Nähe gefunden.');
}
} catch (_err) {
Alert.alert('Fehler', 'Standort konnte nicht ermittelt werden.');
Alert.alert('Fehler', 'Standortermittlung fehlgeschlagen. Bitte überprüfe die Berechtigungen in den Systemeinstellungen.');
}
};
@@ -232,6 +253,9 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
accessibilityLabel="Station suchen"
/>
{searching && <ActivityIndicator style={{ marginVertical: 8 }} color={colors.accent} />}
{searchError && (
<Text style={[styles.errorText, { color: '#ff453a' }]}>{searchError}</Text>
)}
{origin && (
<Text style={[styles.currentStation, { color: colors.success }]}>Aktuell: {origin.name}</Text>
)}
@@ -344,6 +368,7 @@ const styles = StyleSheet.create({
},
locBtnText: { fontSize: 15, fontWeight: '500' },
locStatus: { fontSize: 12, marginTop: 6 },
errorText: { fontSize: 13, marginTop: 6 },
advancedToggle: {
marginTop: 12,
marginBottom: 12,