Translate UI text to English
Translate mobile app UI text to English
This commit is contained in:
@@ -166,7 +166,7 @@ describe('EventListScreen', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(getByText('Keine kommenden Termine')).toBeTruthy();
|
expect(getByText('No upcoming events')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,9 +197,9 @@ describe('EventListScreen', () => {
|
|||||||
expect(getByText('Test Destination')).toBeTruthy();
|
expect(getByText('Test Destination')).toBeTruthy();
|
||||||
});
|
});
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(getByText('Losgehen in')).toBeTruthy();
|
expect(getByText('Leave in')).toBeTruthy();
|
||||||
expect(getByText('S1 -> Wien')).toBeTruthy();
|
expect(getByText('S1 -> Wien')).toBeTruthy();
|
||||||
expect(getByText('Inkl. Fußweg zur Station: 10 min')).toBeTruthy();
|
expect(getByText('Incl. walk to station: 10 min')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -241,12 +241,12 @@ describe('AddEventScreen', () => {
|
|||||||
<AddEventScreen navigation={createMockNavigationProp<RootStack, 'AddEvent'>()} route={mockRouteAddEvent} />
|
<AddEventScreen navigation={createMockNavigationProp<RootStack, 'AddEvent'>()} route={mockRouteAddEvent} />
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(getByPlaceholderText('z.B. Team Meeting')).toBeTruthy();
|
expect(getByPlaceholderText('e.g. Team Meeting')).toBeTruthy();
|
||||||
expect(getByPlaceholderText('z.B. Technikum Wien')).toBeTruthy();
|
expect(getByPlaceholderText('e.g. Technikum Wien')).toBeTruthy();
|
||||||
expect(getByPlaceholderText('JJJJ-MM-TT')).toBeTruthy();
|
expect(getByPlaceholderText('YYYY-MM-DD')).toBeTruthy();
|
||||||
expect(getByPlaceholderText('SS:MM')).toBeTruthy();
|
expect(getByPlaceholderText('HH:MM')).toBeTruthy();
|
||||||
expect(getByText('Speichern')).toBeTruthy();
|
expect(getByText('Save')).toBeTruthy();
|
||||||
expect(getByText('Abbrechen')).toBeTruthy();
|
expect(getByText('Cancel')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show validation errors', () => {
|
it('should show validation errors', () => {
|
||||||
@@ -255,11 +255,11 @@ describe('AddEventScreen', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Try to save without filling form
|
// Try to save without filling form
|
||||||
const saveButton = getByText('Speichern');
|
const saveButton = getByText('Save');
|
||||||
fireEvent.press(saveButton);
|
fireEvent.press(saveButton);
|
||||||
|
|
||||||
// Should show error text
|
// Should show error text
|
||||||
expect(getByText('Titel erforderlich')).toBeTruthy();
|
expect(getByText('Title required')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate date format', () => {
|
it('should validate date format', () => {
|
||||||
@@ -268,15 +268,15 @@ describe('AddEventScreen', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Fill in all required fields except date format is invalid
|
// Fill in all required fields except date format is invalid
|
||||||
fireEvent.changeText(getByPlaceholderText('z.B. Team Meeting'), 'Meeting');
|
fireEvent.changeText(getByPlaceholderText('e.g. Team Meeting'), 'Meeting');
|
||||||
fireEvent.changeText(getByPlaceholderText('z.B. Technikum Wien'), 'Wien');
|
fireEvent.changeText(getByPlaceholderText('e.g. Technikum Wien'), 'Wien');
|
||||||
fireEvent.changeText(getByPlaceholderText('JJJJ-MM-TT'), 'invalid-date');
|
fireEvent.changeText(getByPlaceholderText('YYYY-MM-DD'), 'invalid-date');
|
||||||
fireEvent.changeText(getByPlaceholderText('SS:MM'), '12:00');
|
fireEvent.changeText(getByPlaceholderText('HH:MM'), '12:00');
|
||||||
|
|
||||||
const saveButton = getByText('Speichern');
|
const saveButton = getByText('Save');
|
||||||
fireEvent.press(saveButton);
|
fireEvent.press(saveButton);
|
||||||
|
|
||||||
expect(getByText('Ungültiges Datum')).toBeTruthy();
|
expect(getByText('Invalid date')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate future date', () => {
|
it('should validate future date', () => {
|
||||||
@@ -285,14 +285,14 @@ describe('AddEventScreen', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Fill in all required fields with a past date
|
// Fill in all required fields with a past date
|
||||||
fireEvent.changeText(getByPlaceholderText('z.B. Team Meeting'), 'Meeting');
|
fireEvent.changeText(getByPlaceholderText('e.g. Team Meeting'), 'Meeting');
|
||||||
fireEvent.changeText(getByPlaceholderText('z.B. Technikum Wien'), 'Wien');
|
fireEvent.changeText(getByPlaceholderText('e.g. Technikum Wien'), 'Wien');
|
||||||
fireEvent.changeText(getByPlaceholderText('JJJJ-MM-TT'), '2020-01-01');
|
fireEvent.changeText(getByPlaceholderText('YYYY-MM-DD'), '2020-01-01');
|
||||||
fireEvent.changeText(getByPlaceholderText('SS:MM'), '12:00');
|
fireEvent.changeText(getByPlaceholderText('HH:MM'), '12:00');
|
||||||
|
|
||||||
const saveButton = getByText('Speichern');
|
const saveButton = getByText('Save');
|
||||||
fireEvent.press(saveButton);
|
fireEvent.press(saveButton);
|
||||||
|
|
||||||
expect(getByText('Datum muss in der Zukunft liegen')).toBeTruthy();
|
expect(getByText('Date must be in the future')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ interface Props {
|
|||||||
export function BikeSection({ bikeRoute, loading, origin, colors }: Props) {
|
export function BikeSection({ bikeRoute, loading, origin, colors }: Props) {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>Radroute</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Bike route</Text>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<View style={styles.centered}>
|
<View style={styles.centered}>
|
||||||
<ActivityIndicator size="small" color={colors.accent} />
|
<ActivityIndicator size="small" color={colors.accent} />
|
||||||
<Text style={[styles.hint, { color: colors.subtext }]}>Radroute wird geladen…</Text>
|
<Text style={[styles.hint, { color: colors.subtext }]}>Loading bike route…</Text>
|
||||||
</View>
|
</View>
|
||||||
) : bikeRoute ? (
|
) : bikeRoute ? (
|
||||||
<View style={[styles.card, { backgroundColor: colors.card, borderColor: colors.border }]}>
|
<View style={[styles.card, { backgroundColor: colors.card, borderColor: colors.border }]}>
|
||||||
@@ -37,12 +37,12 @@ export function BikeSection({ bikeRoute, loading, origin, colors }: Props) {
|
|||||||
<Text style={[styles.value, { color: colors.accent }]}>{formatDistance(bikeRoute.distance)}</Text>
|
<Text style={[styles.value, { color: colors.accent }]}>{formatDistance(bikeRoute.distance)}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.mapPlaceholder, { backgroundColor: colors.background, borderColor: colors.border }]}>
|
<View style={[styles.mapPlaceholder, { backgroundColor: colors.background, borderColor: colors.border }]}>
|
||||||
<Text style={[styles.mapText, { color: colors.subtext }]}>🗺 Karte (post-MVP)</Text>
|
<Text style={[styles.mapText, { color: colors.subtext }]}>🗺 Map (post-MVP)</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<Text style={[styles.empty, { color: colors.subtext }]}>
|
<Text style={[styles.empty, { color: colors.subtext }]}>
|
||||||
{origin ? 'Keine Radroute verfügbar' : 'Ursprungstation festlegen'}
|
{origin ? 'No bike route available' : 'Set origin station'}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -45,25 +45,25 @@ export function JourneyList({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>Zugverbindungen</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Train connections</Text>
|
||||||
|
|
||||||
{destStationLoading && (
|
{destStationLoading && (
|
||||||
<View style={styles.centered}>
|
<View style={styles.centered}>
|
||||||
<ActivityIndicator size="small" color={colors.accent} />
|
<ActivityIndicator size="small" color={colors.accent} />
|
||||||
<Text style={[styles.hint, { color: colors.subtext }]}>Ziel-Station wird aufgelöst…</Text>
|
<Text style={[styles.hint, { color: colors.subtext }]}>Resolving destination station…</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{journeys.length === 0 && !destStationLoading ? (
|
{journeys.length === 0 && !destStationLoading ? (
|
||||||
<Text style={[styles.empty, { color: colors.subtext }]}>
|
<Text style={[styles.empty, { color: colors.subtext }]}>
|
||||||
{origin ? 'Keine Verbindungen gefunden' : 'Ursprungstation festlegen'}
|
{origin ? 'No connections found' : 'Set origin station'}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={rankedJourneys.length > 1 ? 0.75 : 1}
|
activeOpacity={rankedJourneys.length > 1 ? 0.75 : 1}
|
||||||
onPress={() => rankedJourneys.length > 1 && setExpanded((current) => !current)}
|
onPress={() => rankedJourneys.length > 1 && setExpanded((current) => !current)}
|
||||||
accessibilityRole={rankedJourneys.length > 1 ? 'button' : undefined}
|
accessibilityRole={rankedJourneys.length > 1 ? 'button' : undefined}
|
||||||
accessibilityLabel={expanded ? 'Weniger Zugverbindungen anzeigen' : 'Alle Zugverbindungen anzeigen'}
|
accessibilityLabel={expanded ? 'Show fewer train connections' : 'Show all train connections'}
|
||||||
>
|
>
|
||||||
{visibleJourneys.map(({ journey: j }, index) => {
|
{visibleJourneys.map(({ journey: j }, index) => {
|
||||||
const finalArrival = new Date(j.rA.getTime() + walkDurationMs);
|
const finalArrival = new Date(j.rA.getTime() + walkDurationMs);
|
||||||
@@ -90,24 +90,24 @@ export function JourneyList({
|
|||||||
<Text style={[styles.delayBadge, { backgroundColor: colors.error }]}>+{j.delay} min</Text>
|
<Text style={[styles.delayBadge, { backgroundColor: colors.error }]}>+{j.delay} min</Text>
|
||||||
)}
|
)}
|
||||||
{j.cancelled && (
|
{j.cancelled && (
|
||||||
<Text style={[styles.cancelBadge, { backgroundColor: colors.text }]}>Storniert</Text>
|
<Text style={[styles.cancelBadge, { backgroundColor: colors.text }]}>Cancelled</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.detail, { color: colors.text }]}>
|
<Text style={[styles.detail, { color: colors.text }]}>
|
||||||
Abfahrt: {new Date(j.sD).toLocaleTimeString('de-AT', { hour: '2-digit', minute: '2-digit' })}
|
Departure: {new Date(j.sD).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}
|
||||||
{' '}(Plattform {j.platform || '—'})
|
{' '}(Platform {j.platform || '—'})
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.detail, { color: colors.subtext }]}>
|
<Text style={[styles.detail, { color: colors.subtext }]}>
|
||||||
Ankunft: {new Date(j.sA).toLocaleTimeString('de-AT', { hour: '2-digit', minute: '2-digit' })}
|
Arrival: {new Date(j.sA).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}
|
||||||
{' '}({j.changes === 0 ? 'Direkt' : `${j.changes} Umst.`})
|
{' '}({j.changes === 0 ? 'Direct' : `${j.changes} tr.`})
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.detail, { color: colors.subtext }]}>
|
<Text style={[styles.detail, { color: colors.subtext }]}>
|
||||||
Dauer: {durationMinutes} min
|
Duration: {durationMinutes} min
|
||||||
</Text>
|
</Text>
|
||||||
{walkDurationMs > 0 && (
|
{walkDurationMs > 0 && (
|
||||||
<Text style={[styles.detail, { color: arrivesTooLate ? colors.error : colors.subtext }]}>
|
<Text style={[styles.detail, { color: arrivesTooLate ? colors.error : colors.subtext }]}>
|
||||||
Ziel: {finalArrival.toLocaleTimeString('de-AT', { hour: '2-digit', minute: '2-digit' })}
|
Arrive: {finalArrival.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}
|
||||||
{arrivesTooLate ? ' (zu spät)' : ''}
|
{arrivesTooLate ? ' (too late)' : ''}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -115,7 +115,7 @@ export function JourneyList({
|
|||||||
})}
|
})}
|
||||||
{rankedJourneys.length > 1 && (
|
{rankedJourneys.length > 1 && (
|
||||||
<Text style={[styles.expandHint, { color: colors.accent }]}>
|
<Text style={[styles.expandHint, { color: colors.accent }]}>
|
||||||
{expanded ? 'Weniger Verbindungen anzeigen' : `${rankedJourneys.length - 1} weitere Verbindungen anzeigen`}
|
{expanded ? 'Show fewer connections' : `Show ${rankedJourneys.length - 1} more connections`}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -123,13 +123,13 @@ export function JourneyList({
|
|||||||
|
|
||||||
{showWalkingOption && walkRoute && (
|
{showWalkingOption && walkRoute && (
|
||||||
<View style={[styles.card, { backgroundColor: colors.card, borderColor: colors.border, marginTop: 10 }]}>
|
<View style={[styles.card, { backgroundColor: colors.card, borderColor: colors.border, marginTop: 10 }]}>
|
||||||
<Text style={[styles.walkTitle, { color: colors.text }]}>🚶 Finaler Fußweg</Text>
|
<Text style={[styles.walkTitle, { color: colors.text }]}>🚶 Final walk</Text>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Text style={[styles.walkLabel, { color: colors.text }]}>⏱ Dauer</Text>
|
<Text style={[styles.walkLabel, { color: colors.text }]}>⏱ Duration</Text>
|
||||||
<Text style={[styles.walkValue, { color: colors.accent }]}>{formatDuration(walkRoute.duration)}</Text>
|
<Text style={[styles.walkValue, { color: colors.accent }]}>{formatDuration(walkRoute.duration)}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Text style={[styles.walkLabel, { color: colors.text }]}>📏 Distanz</Text>
|
<Text style={[styles.walkLabel, { color: colors.text }]}>📏 Distance</Text>
|
||||||
<Text style={[styles.walkValue, { color: colors.accent }]}>{formatDistance(walkRoute.distance)}</Text>
|
<Text style={[styles.walkValue, { color: colors.accent }]}>{formatDistance(walkRoute.distance)}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -138,7 +138,7 @@ export function JourneyList({
|
|||||||
{showWalkingOption && loadingWalk && (
|
{showWalkingOption && loadingWalk && (
|
||||||
<View style={styles.centered}>
|
<View style={styles.centered}>
|
||||||
<ActivityIndicator size="small" color={colors.accent} />
|
<ActivityIndicator size="small" color={colors.accent} />
|
||||||
<Text style={[styles.hint, { color: colors.subtext }]}>Fußweg wird geladen…</Text>
|
<Text style={[styles.hint, { color: colors.subtext }]}>Loading walk route…</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ export function NearbyStops({ stops, departures, loading, error, colors }: Props
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>🚏 ÖPNV in der Nähe des Ziels</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>🚏 Public transit near destination</Text>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<View style={styles.centered}>
|
<View style={styles.centered}>
|
||||||
<ActivityIndicator size="small" color={colors.accent} />
|
<ActivityIndicator size="small" color={colors.accent} />
|
||||||
<Text style={[styles.hint, { color: colors.subtext }]}>Haltestellen werden geladen…</Text>
|
<Text style={[styles.hint, { color: colors.subtext }]}>Loading stops…</Text>
|
||||||
</View>
|
</View>
|
||||||
) : departures.length > 0 ? (
|
) : departures.length > 0 ? (
|
||||||
departures.slice(0, 8).map((dep, i) => (
|
departures.slice(0, 8).map((dep, i) => (
|
||||||
@@ -43,7 +43,7 @@ export function NearbyStops({ stops, departures, loading, error, colors }: Props
|
|||||||
{dep.direction}
|
{dep.direction}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.minutes, { color: dep.minutes <= 2 ? colors.error : colors.accent }]}>
|
<Text style={[styles.minutes, { color: dep.minutes <= 2 ? colors.error : colors.accent }]}>
|
||||||
{dep.minutes === 0 ? 'jetzt' : `${dep.minutes} min`}
|
{dep.minutes === 0 ? 'now' : `${dep.minutes} min`}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export function useWienerLinien(
|
|||||||
await fetchMonitor(ids);
|
await fetchMonitor(ids);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (cancelledRef.current) return;
|
if (cancelledRef.current) return;
|
||||||
setError(err instanceof Error ? err.message : 'Haltestellen konnten nicht geladen werden');
|
setError(err instanceof Error ? err.message : 'Stops could not be loaded');
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, DEBOUNCE_MS);
|
}, DEBOUNCE_MS);
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ export function AddEventScreen({ navigation, route }: ScreenProps) {
|
|||||||
}, [route.params?.editEventId]);
|
}, [route.params?.editEventId]);
|
||||||
|
|
||||||
const validate = (): boolean => {
|
const validate = (): boolean => {
|
||||||
if (!title.trim()) { setError('Titel erforderlich'); return false; }
|
if (!title.trim()) { setError('Title required'); return false; }
|
||||||
if (!destination.trim()) { setError('Ziel erforderlich'); return false; }
|
if (!destination.trim()) { setError('Destination required'); return false; }
|
||||||
if (!dateStr || !timeStr) { setError('Datum und Zeit erforderlich'); return false; }
|
if (!dateStr || !timeStr) { setError('Date and time required'); return false; }
|
||||||
const eventTime = new Date(`${dateStr}T${timeStr}`);
|
const eventTime = new Date(`${dateStr}T${timeStr}`);
|
||||||
if (isNaN(eventTime.getTime())) { setError('Ungültiges Datum'); return false; }
|
if (isNaN(eventTime.getTime())) { setError('Invalid date'); return false; }
|
||||||
if (eventTime <= new Date()) { setError('Datum muss in der Zukunft liegen'); return false; }
|
if (eventTime <= new Date()) { setError('Date must be in the future'); return false; }
|
||||||
setError('');
|
setError('');
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -96,40 +96,40 @@ export function AddEventScreen({ navigation, route }: ScreenProps) {
|
|||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: colors.background, position: 'relative' }]}>
|
<View style={[styles.container, { backgroundColor: colors.background, position: 'relative' }]}>
|
||||||
<View style={styles.form}>
|
<View style={styles.form}>
|
||||||
<Text style={[styles.label, { color: colors.text }]}>Titel</Text>
|
<Text style={[styles.label, { color: colors.text }]}>Title</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
||||||
placeholder="z.B. Team Meeting"
|
placeholder="e.g. Team Meeting"
|
||||||
placeholderTextColor={colors.subtext}
|
placeholderTextColor={colors.subtext}
|
||||||
value={title}
|
value={title}
|
||||||
onChangeText={setTitle}
|
onChangeText={setTitle}
|
||||||
autoCapitalize="words"
|
autoCapitalize="words"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text style={[styles.label, { color: colors.text }]}>Ziel</Text>
|
<Text style={[styles.label, { color: colors.text }]}>Destination</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
||||||
placeholder="z.B. Technikum Wien"
|
placeholder="e.g. Technikum Wien"
|
||||||
placeholderTextColor={colors.subtext}
|
placeholderTextColor={colors.subtext}
|
||||||
value={destination}
|
value={destination}
|
||||||
onChangeText={setDestination}
|
onChangeText={setDestination}
|
||||||
autoCapitalize="words"
|
autoCapitalize="words"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text style={[styles.label, { color: colors.text }]}>Datum</Text>
|
<Text style={[styles.label, { color: colors.text }]}>Date</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
||||||
placeholder="JJJJ-MM-TT"
|
placeholder="YYYY-MM-DD"
|
||||||
placeholderTextColor={colors.subtext}
|
placeholderTextColor={colors.subtext}
|
||||||
value={dateStr}
|
value={dateStr}
|
||||||
onChangeText={setDateStr}
|
onChangeText={setDateStr}
|
||||||
keyboardType="numbers-and-punctuation"
|
keyboardType="numbers-and-punctuation"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text style={[styles.label, { color: colors.text }]}>Zeit</Text>
|
<Text style={[styles.label, { color: colors.text }]}>Time</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
||||||
placeholder="SS:MM"
|
placeholder="HH:MM"
|
||||||
placeholderTextColor={colors.subtext}
|
placeholderTextColor={colors.subtext}
|
||||||
value={timeStr}
|
value={timeStr}
|
||||||
onChangeText={setTimeStr}
|
onChangeText={setTimeStr}
|
||||||
@@ -139,14 +139,14 @@ export function AddEventScreen({ navigation, route }: ScreenProps) {
|
|||||||
{error ? <Text style={[styles.errorText, { color: colors.error }]}>{error}</Text> : null}
|
{error ? <Text style={[styles.errorText, { color: colors.error }]}>{error}</Text> : null}
|
||||||
|
|
||||||
<TouchableOpacity style={styles.saveBtn} onPress={handleSave}>
|
<TouchableOpacity style={styles.saveBtn} onPress={handleSave}>
|
||||||
<Text style={styles.saveBtnText}>{route.params?.editEventId ? 'Aktualisieren' : 'Speichern'}</Text>
|
<Text style={styles.saveBtnText}>{route.params?.editEventId ? 'Update' : 'Save'}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.saveBtn, styles.cancelBtn, { backgroundColor: colors.border }]}
|
style={[styles.saveBtn, styles.cancelBtn, { backgroundColor: colors.border }]}
|
||||||
onPress={() => navigation.goBack()}
|
onPress={() => navigation.goBack()}
|
||||||
>
|
>
|
||||||
<Text style={[styles.cancelText, { color: colors.text }]}>Abbrechen</Text>
|
<Text style={[styles.cancelText, { color: colors.text }]}>Cancel</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -156,9 +156,9 @@ export function AddEventScreen({ navigation, route }: ScreenProps) {
|
|||||||
<View style={styles.successCircle}>
|
<View style={styles.successCircle}>
|
||||||
<Text style={styles.checkmark}>✓</Text>
|
<Text style={styles.checkmark}>✓</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.successTitle, { color: colors.text }]}>Erfolg!</Text>
|
<Text style={[styles.successTitle, { color: colors.text }]}>Success!</Text>
|
||||||
<Text style={[styles.successSubtitle, { color: colors.subtext }]}>
|
<Text style={[styles.successSubtitle, { color: colors.subtext }]}>
|
||||||
{route.params?.editEventId ? 'Termin aktualisiert' : 'Termin hinzugefügt'}
|
{route.params?.editEventId ? 'Event updated' : 'Event added'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
|
|
||||||
const handleImport = async () => {
|
const handleImport = async () => {
|
||||||
if (!url.trim()) {
|
if (!url.trim()) {
|
||||||
setError('Bitte ICS-URL eingeben');
|
setError('Please enter ICS URL');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
}
|
}
|
||||||
setCount(added);
|
setCount(added);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Import fehlgeschlagen');
|
setError(err instanceof Error ? err.message : 'Import failed');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
|
|
||||||
setCount(added);
|
setCount(added);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Sync fehlgeschlagen');
|
setError(err instanceof Error ? err.message : 'Sync failed');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -240,14 +240,14 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
return (
|
return (
|
||||||
<ScrollView style={[styles.container, { backgroundColor: colors.background }]}>
|
<ScrollView style={[styles.container, { backgroundColor: colors.background }]}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
<Text style={[styles.heading, { color: colors.text }]}>Kalender-Import</Text>
|
<Text style={[styles.heading, { color: colors.text }]}>Calendar Import</Text>
|
||||||
<Text style={[styles.description, { color: colors.subtext }]}>
|
<Text style={[styles.description, { color: colors.subtext }]}>
|
||||||
Importiere Termine über eine ICS-URL oder sync mit dem Geräte-Kalender.
|
Import events via an ICS URL or sync with the device calendar.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* ── ICS URL Import ── */}
|
{/* ── ICS URL Import ── */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>ICS-URL Import</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>ICS URL Import</Text>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
||||||
@@ -267,7 +267,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<ActivityIndicator color="#fff" />
|
<ActivityIndicator color="#fff" />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.importBtnText}>ICS Importieren</Text>
|
<Text style={styles.importBtnText}>Import ICS</Text>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -275,33 +275,33 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
{/* ── Calendar Selection ── */}
|
{/* ── Calendar Selection ── */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<View style={styles.sectionHeader}>
|
<View style={styles.sectionHeader}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>Kalender auswählen</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Select calendars</Text>
|
||||||
<View style={styles.selectionActions}>
|
<View style={styles.selectionActions}>
|
||||||
<TouchableOpacity onPress={selectAll} accessibilityLabel="Alle auswählen">
|
<TouchableOpacity onPress={selectAll} accessibilityLabel="Select all">
|
||||||
<Text style={[styles.linkText, { color: colors.accent }]}>Alle</Text>
|
<Text style={[styles.linkText, { color: colors.accent }]}>All</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={[styles.dividerText, { color: colors.subtext }]}> · </Text>
|
<Text style={[styles.dividerText, { color: colors.subtext }]}> · </Text>
|
||||||
<TouchableOpacity onPress={deselectAll} accessibilityLabel="Alle abwählen">
|
<TouchableOpacity onPress={deselectAll} accessibilityLabel="Deselect all">
|
||||||
<Text style={[styles.linkText, { color: colors.accent }]}>Keine</Text>
|
<Text style={[styles.linkText, { color: colors.accent }]}>None</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={[styles.sectionDesc, { color: colors.subtext }]}>
|
<Text style={[styles.sectionDesc, { color: colors.subtext }]}>
|
||||||
Wähle die Kalender aus, die gesynct werden sollen. Ohne Auswahl werden alle Kalender verwendet.
|
Select the calendars to sync. If no selection is made, all calendars will be used.
|
||||||
{'\n'}
|
{'\n'}
|
||||||
CalDAV-Quellen (DAVx5, Apple Calendar, etc.) werden automatisch erkannt.
|
CalDAV sources (DAVx5, Apple Calendar, etc.) are detected automatically.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{!calendarsLoaded ? (
|
{!calendarsLoaded ? (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
<ActivityIndicator color={colors.accent} />
|
<ActivityIndicator color={colors.accent} />
|
||||||
<Text style={[styles.loadingText, { color: colors.subtext }]}>Kalender werden geladen…</Text>
|
<Text style={[styles.loadingText, { color: colors.subtext }]}>Loading calendars…</Text>
|
||||||
</View>
|
</View>
|
||||||
) : availableCalendars.length === 0 ? (
|
) : availableCalendars.length === 0 ? (
|
||||||
<View style={styles.emptyContainer}>
|
<View style={styles.emptyContainer}>
|
||||||
<Text style={[styles.emptyText, { color: colors.subtext }]}>
|
<Text style={[styles.emptyText, { color: colors.subtext }]}>
|
||||||
Keine Kalender auf diesem Gerät gefunden.
|
No calendars found on this device.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
@@ -332,7 +332,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
{hasSelection && (
|
{hasSelection && (
|
||||||
<View style={[styles.selectionInfo, { backgroundColor: colors.highlight }]}>
|
<View style={[styles.selectionInfo, { backgroundColor: colors.highlight }]}>
|
||||||
<Text style={[styles.selectionInfoText, { color: colors.text }]}>
|
<Text style={[styles.selectionInfoText, { color: colors.text }]}>
|
||||||
{selectedCalendarIds.size} von {availableCalendars.length} Kalendern ausgewählt
|
{selectedCalendarIds.size} of {availableCalendars.length} calendars selected
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -342,9 +342,9 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
|
|
||||||
{/* ── Device Calendar Sync ── */}
|
{/* ── Device Calendar Sync ── */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>Geräte-Kalender Sync</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Device Calendar Sync</Text>
|
||||||
<Text style={[styles.sectionDesc, { color: colors.subtext }]}>
|
<Text style={[styles.sectionDesc, { color: colors.subtext }]}>
|
||||||
Hole Termine der nächsten 30 Tage aus den Kalendern auf deinem Gerät.
|
Fetch events for the next 30 days from the calendars on your device.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -352,7 +352,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
onPress={handleSyncNative}
|
onPress={handleSyncNative}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<Text style={styles.importBtnText}>📅 Kalender Sync</Text>
|
<Text style={styles.importBtnText}>📅 Calendar Sync</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
{count !== null && (
|
{count !== null && (
|
||||||
<View style={[styles.successBanner, { backgroundColor: colors.success }]}>
|
<View style={[styles.successBanner, { backgroundColor: colors.success }]}>
|
||||||
<Text style={styles.successText}>
|
<Text style={styles.successText}>
|
||||||
✓ {count} Termin(e) erfolgreich importiert!
|
✓ {count} event(s) successfully imported!
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -374,7 +374,7 @@ export function CalendarImportScreen({ navigation }: ScreenProps) {
|
|||||||
style={styles.backBtn}
|
style={styles.backBtn}
|
||||||
onPress={() => navigation.goBack()}
|
onPress={() => navigation.goBack()}
|
||||||
>
|
>
|
||||||
<Text style={[styles.backBtnText, { color: colors.accent }]}>← Zurück</Text>
|
<Text style={[styles.backBtnText, { color: colors.accent }]}>← Back</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
|||||||
setShowWalkingOption(settings.showWalkingOption);
|
setShowWalkingOption(settings.showWalkingOption);
|
||||||
|
|
||||||
const found = events.find((e) => e.id === eventId);
|
const found = events.find((e) => e.id === eventId);
|
||||||
if (!found) { setError('Termin nicht gefunden'); return; }
|
if (!found) { setError('Event not found'); return; }
|
||||||
setEvent(found);
|
setEvent(found);
|
||||||
|
|
||||||
if (originStation) {
|
if (originStation) {
|
||||||
@@ -112,7 +112,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
|||||||
const results = await api.searchJourneys(originStation.extId, destExtId, target, { arriveBy: true });
|
const results = await api.searchJourneys(originStation.extId, destExtId, target, { arriveBy: true });
|
||||||
setJourneys(results);
|
setJourneys(results);
|
||||||
} else if (destStation.error) {
|
} else if (destStation.error) {
|
||||||
setError(`Ziel-Station nicht auflösbar: ${destStation.error}`);
|
setError(`Destination station not resolvable: ${destStation.error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -131,7 +131,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Fehler beim Laden');
|
setError(err instanceof Error ? err.message : 'Error loading');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
|||||||
<View style={[styles.center, { backgroundColor: colors.background }]}>
|
<View style={[styles.center, { backgroundColor: colors.background }]}>
|
||||||
<ActivityIndicator size="large" color={colors.accent} />
|
<ActivityIndicator size="large" color={colors.accent} />
|
||||||
<Text style={[styles.loadingText, { color: colors.subtext }]}>
|
<Text style={[styles.loadingText, { color: colors.subtext }]}>
|
||||||
{destStation.loading && !event ? 'Ziel-Station wird aufgelöst…' : 'Termine werden geladen…'}
|
{destStation.loading && !event ? 'Resolving destination station…' : 'Loading events…'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -200,9 +200,9 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
|||||||
{!origin && !error && (
|
{!origin && !error && (
|
||||||
<View style={[styles.warningBanner, { backgroundColor: colors.warning }]}>
|
<View style={[styles.warningBanner, { backgroundColor: colors.warning }]}>
|
||||||
<Text style={styles.bannerText}>
|
<Text style={styles.bannerText}>
|
||||||
Keine Ursprungstation festgelegt.{' '}
|
No origin station set.{' '}
|
||||||
<Text style={styles.bannerLink} onPress={() => navigation.navigate('Settings')}>
|
<Text style={styles.bannerLink} onPress={() => navigation.navigate('Settings')}>
|
||||||
Einstellungen öffnen
|
Open settings
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -218,15 +218,15 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
|||||||
onPress={() => setActiveMode('train')}
|
onPress={() => setActiveMode('train')}
|
||||||
>
|
>
|
||||||
<View style={styles.modeHeader}>
|
<View style={styles.modeHeader}>
|
||||||
<Text style={[styles.modeLabel, { color: colors.text }]}>🚆 Zug</Text>
|
<Text style={[styles.modeLabel, { color: colors.text }]}>🚆 Train</Text>
|
||||||
{effectiveMode === 'train' && (
|
{effectiveMode === 'train' && (
|
||||||
<View style={[styles.activeBadge, { backgroundColor: colors.accent }]}>
|
<View style={[styles.activeBadge, { backgroundColor: colors.accent }]}>
|
||||||
<Text style={styles.activeBadgeText}>Aktiv</Text>
|
<Text style={styles.activeBadgeText}>Active</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.modeMeta, { color: colors.subtext }]}>
|
<Text style={[styles.modeMeta, { color: colors.subtext }]}>
|
||||||
{showWalkingOption ? 'Bahn + finaler Fußweg' : 'Nur Bahn'}
|
{showWalkingOption ? 'Train + final walk' : 'Train only'}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
@@ -240,19 +240,19 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
|||||||
disabled={bikeDisabled}
|
disabled={bikeDisabled}
|
||||||
>
|
>
|
||||||
<View style={styles.modeHeader}>
|
<View style={styles.modeHeader}>
|
||||||
<Text style={[styles.modeLabel, { color: colors.text }]}>🚲 Rad</Text>
|
<Text style={[styles.modeLabel, { color: colors.text }]}>🚲 Bike</Text>
|
||||||
{effectiveMode === 'bike' && (
|
{effectiveMode === 'bike' && (
|
||||||
<View style={[styles.activeBadge, { backgroundColor: colors.accent }]}>
|
<View style={[styles.activeBadge, { backgroundColor: colors.accent }]}>
|
||||||
<Text style={styles.activeBadgeText}>Aktiv</Text>
|
<Text style={styles.activeBadgeText}>Active</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.modeMeta, { color: colors.subtext }]}>
|
<Text style={[styles.modeMeta, { color: colors.subtext }]}>
|
||||||
{bikeDisabled
|
{bikeDisabled
|
||||||
? 'In Einstellungen deaktiviert'
|
? 'Disabled in settings'
|
||||||
: loadingBike
|
: loadingBike
|
||||||
? 'Route wird berechnet...'
|
? 'Calculating route...'
|
||||||
: 'Direktweg'}
|
: 'Direct route'}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -293,7 +293,7 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
|||||||
style={[styles.refreshBtn, { backgroundColor: colors.border }]}
|
style={[styles.refreshBtn, { backgroundColor: colors.border }]}
|
||||||
onPress={handleRefresh}
|
onPress={handleRefresh}
|
||||||
>
|
>
|
||||||
<Text style={[styles.refreshBtnText, { color: colors.text }]}>🔄 Neu laden</Text>
|
<Text style={[styles.refreshBtnText, { color: colors.text }]}>🔄 Refresh</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<View style={{ height: 40 }} />
|
<View style={{ height: 40 }} />
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export function EventListScreen({ navigation }: ScreenProps) {
|
|||||||
setJourneys(results);
|
setJourneys(results);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
setJourneysError(err instanceof Error ? err.message : 'Verbindungen konnten nicht geladen werden');
|
setJourneysError(err instanceof Error ? err.message : 'Connections could not be loaded');
|
||||||
} finally {
|
} finally {
|
||||||
if (isMounted) setJourneysLoading(false);
|
if (isMounted) setJourneysLoading(false);
|
||||||
}
|
}
|
||||||
@@ -175,16 +175,16 @@ export function EventListScreen({ navigation }: ScreenProps) {
|
|||||||
const renderItem = ({ item }: { item: CalendarEvent }) => {
|
const renderItem = ({ item }: { item: CalendarEvent }) => {
|
||||||
const leaveBy = departureInfo.departureTime;
|
const leaveBy = departureInfo.departureTime;
|
||||||
const leaveCountdown = leaveBy ? calculateCountdown(leaveBy) : null;
|
const leaveCountdown = leaveBy ? calculateCountdown(leaveBy) : null;
|
||||||
const leaveCountdownLabel = leaveCountdown?.label === 'Now' ? 'Jetzt' : leaveCountdown?.label;
|
const leaveCountdownLabel = leaveCountdown?.label;
|
||||||
const leaveByLabel = leaveBy ? formatTime(leaveBy) : null;
|
const leaveByLabel = leaveBy ? formatTime(leaveBy) : null;
|
||||||
const trainLabel = selectedJourney?.trains.length ? selectedJourney.trains.join(', ') : 'Zugverbindung wird gesucht';
|
const trainLabel = selectedJourney?.trains.length ? selectedJourney.trains.join(', ') : 'Searching for train connection';
|
||||||
const status = leaveCountdown
|
const status = leaveCountdown
|
||||||
? leaveCountdown.label === 'Now'
|
? leaveCountdown.label === 'Now'
|
||||||
? 'Jetzt losgehen'
|
? 'Leave now'
|
||||||
: `Losgehen in ${leaveCountdown.label}`
|
: `Leave in ${leaveCountdown.label}`
|
||||||
: journeysLoading || destStation.loading
|
: journeysLoading || destStation.loading
|
||||||
? 'Losgehzeit wird berechnet'
|
? 'Calculating departure time'
|
||||||
: 'Keine Verbindung';
|
: 'No connection';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.cardWrapper}>
|
<View style={styles.cardWrapper}>
|
||||||
@@ -197,12 +197,13 @@ export function EventListScreen({ navigation }: ScreenProps) {
|
|||||||
<View style={styles.dotRow}>
|
<View style={styles.dotRow}>
|
||||||
<View style={[styles.dot, { backgroundColor: leaveCountdown?.urgent ? colors.delete : '#34C759' }]} />
|
<View style={[styles.dot, { backgroundColor: leaveCountdown?.urgent ? colors.delete : '#34C759' }]} />
|
||||||
<Text style={[styles.title, { color: colors.text }]}>{item.title}</Text>
|
<Text style={[styles.title, { color: colors.text }]}>{item.title}</Text>
|
||||||
<Text style={[styles.badge, { color: leaveByLabel ? colors.accent : colors.subtext }]}>
|
|
||||||
{leaveByLabel ?? '--:--'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.subtitle, { color: colors.subtext }]}>{item.destination}</Text>
|
<Text style={[styles.subtitle, { color: colors.subtext }]}>{item.destination}</Text>
|
||||||
<Text style={[styles.leaveLabel, { color: colors.subtext }]}>Losgehen in</Text>
|
<Text style={[styles.leaveLabel, { color: colors.subtext }]}>Time To Leave</Text>
|
||||||
|
<Text style={[styles.leaveTime, { color: leaveByLabel ? colors.accent : colors.subtext }]}>
|
||||||
|
{leaveByLabel ?? '--:--'}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.leaveLabel, { color: colors.subtext }]}>Leave in</Text>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.leaveTime,
|
styles.leaveTime,
|
||||||
@@ -213,41 +214,32 @@ export function EventListScreen({ navigation }: ScreenProps) {
|
|||||||
? leaveCountdownLabel
|
? leaveCountdownLabel
|
||||||
: journeysLoading || destStation.loading
|
: journeysLoading || destStation.loading
|
||||||
? '--'
|
? '--'
|
||||||
: 'Keine Verbindung'}
|
: 'No connection'}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={[styles.trainBox, { borderColor: colors.border }]}>
|
<View style={[styles.trainBox, { borderColor: colors.border }]}>
|
||||||
<Text style={[styles.trainTitle, { color: colors.text }]} numberOfLines={2}>
|
<Text style={[styles.trainTitle, { color: colors.text }]} numberOfLines={2}>
|
||||||
{journeysError ? 'Zugverbindung nicht erreichbar' : trainLabel}
|
{journeysError ? 'Train connection unavailable' : trainLabel}
|
||||||
</Text>
|
</Text>
|
||||||
{selectedJourney ? (
|
{selectedJourney ? (
|
||||||
<Text style={[styles.trainMeta, { color: colors.subtext }]}>
|
<Text style={[styles.trainMeta, { color: colors.subtext }]}>
|
||||||
Abfahrt {formatTime(selectedJourney.rD)}
|
Departure {formatTime(selectedJourney.rD)}
|
||||||
{selectedJourney.platform ? ` · Gleis ${selectedJourney.platform}` : ''}
|
{selectedJourney.platform ? ` · Platform ${selectedJourney.platform}` : ''}
|
||||||
{' · '}
|
{' · '}
|
||||||
Ankunft {formatTime(selectedJourney.rA)}
|
Arrival {formatTime(selectedJourney.rA)}
|
||||||
{' · '}
|
{' · '}
|
||||||
{selectedJourney.changes === 0 ? 'Direkt' : `${selectedJourney.changes} Umstiege`}
|
{selectedJourney.changes === 0 ? 'Direct' : `${selectedJourney.changes} transfers`}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text style={[styles.trainMeta, { color: journeysError ? colors.delete : colors.subtext }]}>
|
<Text style={[styles.trainMeta, { color: journeysError ? colors.delete : colors.subtext }]}>
|
||||||
{journeysError ?? (origin ? 'Beste Verbindung für den nächsten Termin' : 'Ursprungstation festlegen')}
|
{journeysError ?? (origin ? 'Best connection for the next event' : 'Set origin station')}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{originWalk.walkRoute && originWalk.walkRoute.duration > 30 && (
|
{originWalk.walkRoute && originWalk.walkRoute.duration > 30 && (
|
||||||
<Text style={[styles.trainMeta, { color: colors.subtext }]}>
|
<Text style={[styles.trainMeta, { color: colors.subtext }]}>
|
||||||
Inkl. Fußweg zur Station: {Math.ceil(originWalk.walkRoute.duration / 60)} min
|
Incl. walk to station: {Math.ceil(originWalk.walkRoute.duration / 60)} min
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.time, { color: colors.subtext }]}>
|
|
||||||
Termin: {item.eventTime.toLocaleString('de-AT', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
<Text style={[styles.status, { color: leaveCountdown?.urgent ? colors.delete : '#34C759' }]}>{status}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
@@ -256,12 +248,12 @@ export function EventListScreen({ navigation }: ScreenProps) {
|
|||||||
onPress={() => navigation.navigate('AddEvent', { editEventId: item.id })}
|
onPress={() => navigation.navigate('AddEvent', { editEventId: item.id })}
|
||||||
style={styles.editBtn}
|
style={styles.editBtn}
|
||||||
>
|
>
|
||||||
<Text style={[styles.editText, { color: colors.accent }]}>Bearbeiten</Text>
|
<Text style={[styles.editText, { color: colors.accent }]}>edit</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
{/* Delete button */}
|
{/* Delete button */}
|
||||||
<TouchableOpacity onPress={() => removeEvent(item.id, reload)} style={styles.deleteBtn}>
|
<TouchableOpacity onPress={() => removeEvent(item.id, reload)} style={styles.deleteBtn}>
|
||||||
<Text style={[styles.deleteText, { color: colors.delete }]}>Entfernen</Text>
|
<Text style={[styles.deleteText, { color: colors.delete }]}>delete</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -270,12 +262,12 @@ export function EventListScreen({ navigation }: ScreenProps) {
|
|||||||
if (!upcomingEvent) {
|
if (!upcomingEvent) {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.center, { backgroundColor: colors.background }]}>
|
<View style={[styles.center, { backgroundColor: colors.background }]}>
|
||||||
<Text style={[styles.empty, { color: colors.subtext }]}>Keine kommenden Termine</Text>
|
<Text style={[styles.empty, { color: colors.subtext }]}>No upcoming events</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.addBtn}
|
style={styles.addBtn}
|
||||||
onPress={() => navigation.navigate('AddEvent')}
|
onPress={() => navigation.navigate('AddEvent')}
|
||||||
>
|
>
|
||||||
<Text style={styles.addBtnText}>+ Termin hinzufügen</Text>
|
<Text style={styles.addBtnText}>+ Add event</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -73,10 +73,10 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
|
|||||||
try {
|
try {
|
||||||
const stations = await api.searchStation(q.trim());
|
const stations = await api.searchStation(q.trim());
|
||||||
setResults(stations);
|
setResults(stations);
|
||||||
if (stations.length === 0) setSearchError('Keine Stationen gefunden.');
|
if (stations.length === 0) setSearchError('No stations found.');
|
||||||
} catch {
|
} catch {
|
||||||
setResults([]);
|
setResults([]);
|
||||||
setSearchError('API nicht erreichbar. Läuft der Server auf deinem Gerät? Überprüfe EXPO_PUBLIC_API_BASE_URL in .env.');
|
setSearchError('API not reachable. Is the server running on your device? Check EXPO_PUBLIC_API_BASE_URL in .env.');
|
||||||
} finally {
|
} finally {
|
||||||
setSearching(false);
|
setSearching(false);
|
||||||
}
|
}
|
||||||
@@ -97,9 +97,9 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
|
|||||||
try {
|
try {
|
||||||
await saveOriginStation(station);
|
await saveOriginStation(station);
|
||||||
await rescheduleAllNotifications();
|
await rescheduleAllNotifications();
|
||||||
Alert.alert('Station gespeichert', station.name);
|
Alert.alert('Station saved', station.name);
|
||||||
} catch {
|
} catch {
|
||||||
Alert.alert('Fehler', 'Station konnte nicht gespeichert werden.');
|
Alert.alert('Error', 'Station could not be saved.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
|
|||||||
setLocPermission(status === 'granted' ? 'granted' : 'denied');
|
setLocPermission(status === 'granted' ? 'granted' : 'denied');
|
||||||
|
|
||||||
if (status !== 'granted') {
|
if (status !== 'granted') {
|
||||||
Alert.alert('Berechtigung erforderlich', 'Standortzugriff ist nötig für die automatische Stationssuche.');
|
Alert.alert('Permission required', 'Location access is required for automatic station search.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
|
|||||||
try {
|
try {
|
||||||
const nearestStation = await api.findNearestStationByCoords(userLat, userLng);
|
const nearestStation = await api.findNearestStationByCoords(userLat, userLng);
|
||||||
if (!nearestStation) {
|
if (!nearestStation) {
|
||||||
Alert.alert('Keine Station gefunden', 'Kein ÖPNV-Halt in der Nähe gefunden.');
|
Alert.alert('No station found', 'No public transport stop found nearby.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await selectStation({
|
await selectStation({
|
||||||
@@ -164,14 +164,14 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
|
|||||||
|
|
||||||
if (!apiReachable) {
|
if (!apiReachable) {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'API nicht erreichbar',
|
'API not reachable',
|
||||||
'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.'
|
'The server could not be reached. Make sure EXPO_PUBLIC_API_BASE_URL in your .env file points to the LAN IP of your development machine (e.g. http://192.168.1.x:3000) and not localhost.'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Alert.alert('Keine Station gefunden', 'Kein ÖPNV-Halt in der Nähe gefunden.');
|
Alert.alert('No station found', 'No public transport stop found nearby.');
|
||||||
}
|
}
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
Alert.alert('Fehler', 'Standortermittlung fehlgeschlagen. Bitte überprüfe die Berechtigungen in den Systemeinstellungen.');
|
Alert.alert('Error', 'Location detection failed. Please check the permissions in your system settings.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -228,36 +228,36 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
|
|||||||
>
|
>
|
||||||
{/* Appearance */}
|
{/* Appearance */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>Erscheinungsbild</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Appearance</Text>
|
||||||
<View style={styles.settingRow}>
|
<View style={styles.settingRow}>
|
||||||
<Text style={[styles.settingLabel, { color: colors.text }]}>Dunkelmodus</Text>
|
<Text style={[styles.settingLabel, { color: colors.text }]}>Dark mode</Text>
|
||||||
<Switch
|
<Switch
|
||||||
value={dark}
|
value={dark}
|
||||||
onValueChange={toggleTheme}
|
onValueChange={toggleTheme}
|
||||||
trackColor={{ true: colors.accent, false: colors.border }}
|
trackColor={{ true: colors.accent, false: colors.border }}
|
||||||
accessibilityLabel="Dunkelmodus umschalten"
|
accessibilityLabel="Toggle dark mode"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Origin Station */}
|
{/* Origin Station */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>Ursprungstation</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Origin station</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
style={[styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
||||||
placeholder="Station suchen …"
|
placeholder="Search station …"
|
||||||
placeholderTextColor={colors.subtext}
|
placeholderTextColor={colors.subtext}
|
||||||
value={query}
|
value={query}
|
||||||
onChangeText={onQueryChange}
|
onChangeText={onQueryChange}
|
||||||
autoCapitalize="words"
|
autoCapitalize="words"
|
||||||
accessibilityLabel="Station suchen"
|
accessibilityLabel="Search station"
|
||||||
/>
|
/>
|
||||||
{searching && <ActivityIndicator style={{ marginVertical: 8 }} color={colors.accent} />}
|
{searching && <ActivityIndicator style={{ marginVertical: 8 }} color={colors.accent} />}
|
||||||
{searchError && (
|
{searchError && (
|
||||||
<Text style={[styles.errorText, { color: '#ff453a' }]}>{searchError}</Text>
|
<Text style={[styles.errorText, { color: '#ff453a' }]}>{searchError}</Text>
|
||||||
)}
|
)}
|
||||||
{origin && (
|
{origin && (
|
||||||
<Text style={[styles.currentStation, { color: colors.success }]}>Aktuell: {origin.name}</Text>
|
<Text style={[styles.currentStation, { color: colors.success }]}>Current: {origin.name}</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{results.map((s) => (
|
{results.map((s) => (
|
||||||
@@ -267,72 +267,72 @@ export function SettingsScreen({ navigation: _navigation }: ScreenProps) {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<TouchableOpacity style={[styles.locBtn, { backgroundColor: colors.highlight }]} onPress={useCurrentLocation}>
|
<TouchableOpacity style={[styles.locBtn, { backgroundColor: colors.highlight }]} onPress={useCurrentLocation}>
|
||||||
<Text style={[styles.locBtnText, { color: colors.accent }]}>📍 Aktuelle Position verwenden</Text>
|
<Text style={[styles.locBtnText, { color: colors.accent }]}>📍 Use current location</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={[styles.locStatus, { color: colors.subtext }]}>
|
<Text style={[styles.locStatus, { color: colors.subtext }]}>
|
||||||
Standort: {locPermission === 'granted' ? 'Erlaubt ✓' : locPermission === 'denied' ? 'Verweigert ✗' : 'Noch nicht angefragt'}
|
Location: {locPermission === 'granted' ? 'Granted ✓' : locPermission === 'denied' ? 'Denied ✗' : 'Not yet requested'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Notification Settings */}
|
{/* Notification Settings */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={[styles.sectionTitle, { color: colors.text }]}>Benachrichtigungen</Text>
|
<Text style={[styles.sectionTitle, { color: colors.text }]}>Notifications</Text>
|
||||||
<View style={styles.settingRow}>
|
<View style={styles.settingRow}>
|
||||||
<Text style={[styles.settingLabel, { color: colors.text }]}>Benachrichtigungen aktivieren</Text>
|
<Text style={[styles.settingLabel, { color: colors.text }]}>Enable notifications</Text>
|
||||||
<Switch
|
<Switch
|
||||||
value={notifSettings.enabled}
|
value={notifSettings.enabled}
|
||||||
onValueChange={toggleNotifications}
|
onValueChange={toggleNotifications}
|
||||||
trackColor={{ true: colors.accent, false: colors.border }}
|
trackColor={{ true: colors.accent, false: colors.border }}
|
||||||
accessibilityLabel="Benachrichtigungen umschalten"
|
accessibilityLabel="Toggle notifications"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.settingLabel, { color: colors.text }]}>Pufferzeit (Minuten)</Text>
|
<Text style={[styles.settingLabel, { color: colors.text }]}>Buffer time (minutes)</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, styles.numberInput, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
style={[styles.input, styles.numberInput, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
||||||
value={String(notifSettings.bufferMinutes)}
|
value={String(notifSettings.bufferMinutes)}
|
||||||
onChangeText={updateBufferMinutes}
|
onChangeText={updateBufferMinutes}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
accessibilityLabel="Pufferzeit in Minuten"
|
accessibilityLabel="Buffer time in minutes"
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.hint, { color: colors.subtext }]}>
|
<Text style={[styles.hint, { color: colors.subtext }]}>
|
||||||
Du wirst {notifSettings.bufferMinutes} Minuten vor der geplanten Abfahrt erinnert.
|
You will be reminded {notifSettings.bufferMinutes} minutes before the scheduled departure.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<TouchableOpacity style={[styles.advancedToggle, styles.locBtn, { backgroundColor: colors.highlight }]} onPress={toggleAdvanced}>
|
<TouchableOpacity style={[styles.advancedToggle, styles.locBtn, { backgroundColor: colors.highlight }]} onPress={toggleAdvanced}>
|
||||||
<Text style={[styles.locBtnText, { color: colors.accent }]}>
|
<Text style={[styles.locBtnText, { color: colors.accent }]}>
|
||||||
{showAdvanced ? '↑ Weniger Optionen zeigen' : '↓ Mehr Optionen zeigen'}
|
{showAdvanced ? '↑ Show fewer options' : '↓ Show more options'}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
{showAdvanced && (
|
{showAdvanced && (
|
||||||
<View style={[styles.advancedSection, { borderTopColor: colors.border }]}>
|
<View style={[styles.advancedSection, { borderTopColor: colors.border }]}>
|
||||||
<Text style={[styles.settingLabel, { color: colors.text }]}>Ankunfts-Puffer (Minuten)</Text>
|
<Text style={[styles.settingLabel, { color: colors.text }]}>Arrival buffer (minutes)</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, styles.numberInput, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
style={[styles.input, styles.numberInput, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border }]}
|
||||||
value={String(notifSettings.arrivalBufferMinutes)}
|
value={String(notifSettings.arrivalBufferMinutes)}
|
||||||
onChangeText={updateArrivalBuffer}
|
onChangeText={updateArrivalBuffer}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
accessibilityLabel="Ankunfts-Puffer in Minuten"
|
accessibilityLabel="Arrival buffer in minutes"
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.hint, { color: colors.subtext }]}>Wie viele Minuten vor der Event-Zeit du am Ziel ankommen möchtest</Text>
|
<Text style={[styles.hint, { color: colors.subtext }]}>How many minutes before the event time you want to arrive at the destination</Text>
|
||||||
|
|
||||||
<View style={styles.settingRow}>
|
<View style={styles.settingRow}>
|
||||||
<Text style={[styles.settingLabel, { color: colors.text }]}>Zu Fuß-Option anzeigen</Text>
|
<Text style={[styles.settingLabel, { color: colors.text }]}>Show walking option</Text>
|
||||||
<Switch
|
<Switch
|
||||||
value={notifSettings.showWalkingOption}
|
value={notifSettings.showWalkingOption}
|
||||||
onValueChange={toggleWalking}
|
onValueChange={toggleWalking}
|
||||||
trackColor={{ true: colors.accent, false: colors.border }}
|
trackColor={{ true: colors.accent, false: colors.border }}
|
||||||
accessibilityLabel="Zu Fuß-Option umschalten"
|
accessibilityLabel="Toggle walking option"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.settingRow}>
|
<View style={styles.settingRow}>
|
||||||
<Text style={[styles.settingLabel, { color: colors.text }]}>Fahrrad-Option anzeigen</Text>
|
<Text style={[styles.settingLabel, { color: colors.text }]}>Show bike option</Text>
|
||||||
<Switch
|
<Switch
|
||||||
value={notifSettings.showBikeOption}
|
value={notifSettings.showBikeOption}
|
||||||
onValueChange={toggleBike}
|
onValueChange={toggleBike}
|
||||||
trackColor={{ true: colors.accent, false: colors.border }}
|
trackColor={{ true: colors.accent, false: colors.border }}
|
||||||
accessibilityLabel="Fahrrad-Option umschalten"
|
accessibilityLabel="Toggle bike option"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user