Show leave time instead of countdown

Replace live countdown with formatted leave time in headers and list items
Remove ticking interval and calculateCountdown import from EventHeader
Update EventListScreen to derive leave countdown and labels and adjust badge,
dot color, status logic and fallbacks
Update tests to expect 'Losgehen in'
This commit is contained in:
2026-05-18 13:14:05 +02:00
parent b3edf2c47b
commit 834025e560
3 changed files with 36 additions and 38 deletions
+1 -1
View File
@@ -197,7 +197,7 @@ describe('EventListScreen', () => {
expect(getByText('Test Destination')).toBeTruthy(); expect(getByText('Test Destination')).toBeTruthy();
}); });
await waitFor(() => { await waitFor(() => {
expect(getByText('Losgehen')).toBeTruthy(); expect(getByText('Losgehen in')).toBeTruthy();
expect(getByText('S1 -> Wien')).toBeTruthy(); expect(getByText('S1 -> Wien')).toBeTruthy();
expect(getByText('Inkl. Fußweg zur Station: 10 min')).toBeTruthy(); expect(getByText('Inkl. Fußweg zur Station: 10 min')).toBeTruthy();
}); });
+6 -22
View File
@@ -1,6 +1,4 @@
import { useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import { calculateCountdown } from '@timetoleave/core';
import type { Event } from '@timetoleave/core'; import type { Event } from '@timetoleave/core';
import type { AppColors } from '../hooks/useColors'; import type { AppColors } from '../hooks/useColors';
@@ -13,34 +11,20 @@ interface Props {
} }
/** /**
* Renders the event title, destination, leave countdown, data source, and a * Renders the event title, destination, leave-by time, data source, and a
* three-column grid with leave-by time, arrive-by time, and buffer. * three-column grid with leave-by time, arrive-by time, and buffer.
*/ */
export function EventHeader({ event, leaveByTime, arrivalBufferMinutes, colors }: Props) { export function EventHeader({ event, leaveByTime, arrivalBufferMinutes, colors }: Props) {
const [, setTick] = useState(0);
const arriveByTime = new Date(event.eventTime.getTime() - arrivalBufferMinutes * 60_000); const arriveByTime = new Date(event.eventTime.getTime() - arrivalBufferMinutes * 60_000);
const leaveCountdown = leaveByTime ? calculateCountdown(leaveByTime) : null;
useEffect(() => {
const interval = setInterval(() => setTick((tick) => tick + 1), 30_000);
return () => clearInterval(interval);
}, []);
return ( return (
<View style={[styles.container, { backgroundColor: colors.card }]}> <View style={[styles.container, { backgroundColor: colors.card }]}>
<Text style={[styles.title, { color: colors.text }]}>{event.title}</Text> <Text style={[styles.title, { color: colors.text }]}>{event.title}</Text>
<Text style={[styles.destination, { color: colors.subtext }]}>{event.destination}</Text> <Text style={[styles.destination, { color: colors.subtext }]}>{event.destination}</Text>
<Text <Text style={[styles.leaveTime, { color: leaveByTime ? colors.accent : colors.subtext }]}>
style={[ {leaveByTime
styles.countdown, ? `Losgehen um ${leaveByTime.toLocaleTimeString('de-AT', { hour: '2-digit', minute: '2-digit' })}`
{ color: leaveCountdown?.urgent ? colors.delete : colors.text }, : 'Losgehzeit wird berechnet'}
]}
>
{leaveCountdown
? leaveCountdown.label === 'Now'
? 'Jetzt losgehen'
: `Noch ${leaveCountdown.label} bis Losgehen`
: 'Countdown wird berechnet'}
</Text> </Text>
<Text style={[styles.source, { color: colors.subtext }]}>Quelle: {event.source}</Text> <Text style={[styles.source, { color: colors.subtext }]}>Quelle: {event.source}</Text>
@@ -72,7 +56,7 @@ const styles = StyleSheet.create({
container: { padding: 20, marginBottom: 12 }, container: { padding: 20, marginBottom: 12 },
title: { fontSize: 22, fontWeight: '700' }, title: { fontSize: 22, fontWeight: '700' },
destination: { fontSize: 16, marginTop: 4 }, destination: { fontSize: 16, marginTop: 4 },
countdown: { fontSize: 18, fontWeight: '700', marginTop: 10 }, leaveTime: { fontSize: 18, fontWeight: '700', marginTop: 10 },
source: { fontSize: 12, marginTop: 4 }, source: { fontSize: 12, marginTop: 4 },
infoGrid: { infoGrid: {
flexDirection: 'row', flexDirection: 'row',
+29 -15
View File
@@ -28,9 +28,9 @@ type ScreenProps = {
}; };
/** /**
* Home screen showing a scrollable list of upcoming events with countdown * Home screen showing a scrollable list of upcoming events with leave-time
* badges. Pull-to-refresh reloads events from storage. Reloads automatically * countdowns. Pull-to-refresh reloads events from storage. Reloads
* when the screen gains focus (so edits on other screens are reflected). * automatically when the screen gains focus so edits are reflected.
*/ */
export function EventListScreen({ navigation }: ScreenProps) { export function EventListScreen({ navigation }: ScreenProps) {
const colors = useColors(); const colors = useColors();
@@ -173,13 +173,18 @@ export function EventListScreen({ navigation }: ScreenProps) {
]); ]);
const renderItem = ({ item }: { item: CalendarEvent }) => { const renderItem = ({ item }: { item: CalendarEvent }) => {
const countdown = calculateCountdown(item.eventTime);
const leaveBy = departureInfo.departureTime; const leaveBy = departureInfo.departureTime;
const leaveCountdown = leaveBy ? calculateCountdown(leaveBy) : null;
const leaveCountdownLabel = leaveCountdown?.label === 'Now' ? 'Jetzt' : leaveCountdown?.label;
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(', ') : 'Zugverbindung wird gesucht';
const status = leaveCountdown
// Derive a simple status — journeys aren't loaded on the list screen for MVP ? leaveCountdown.label === 'Now'
// so we show countdown-based status instead ? 'Jetzt losgehen'
const status = countdown.urgent ? 'Bald!' : countdown.label; : `Losgehen in ${leaveCountdown.label}`
: journeysLoading || destStation.loading
? 'Losgehzeit wird berechnet'
: 'Keine Verbindung';
return ( return (
<View style={styles.cardWrapper}> <View style={styles.cardWrapper}>
@@ -190,16 +195,25 @@ export function EventListScreen({ navigation }: ScreenProps) {
> >
<View style={[styles.card, { backgroundColor: colors.card }]}> <View style={[styles.card, { backgroundColor: colors.card }]}>
<View style={styles.dotRow}> <View style={styles.dotRow}>
<View style={[styles.dot, { backgroundColor: countdown.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: countdown.urgent ? colors.delete : colors.accent }]}> <Text style={[styles.badge, { color: leaveByLabel ? colors.accent : colors.subtext }]}>
{countdown.label} {leaveByLabel ?? '--:--'}
</Text> </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</Text> <Text style={[styles.leaveLabel, { color: colors.subtext }]}>Losgehen in</Text>
<Text style={[styles.leaveTime, { color: leaveBy ? colors.accent : colors.subtext }]}> <Text
{leaveBy ? formatTime(leaveBy) : journeysLoading || destStation.loading ? '--:--' : 'Keine Verbindung'} style={[
styles.leaveTime,
{ color: leaveCountdownLabel ? colors.accent : colors.subtext },
]}
>
{leaveCountdownLabel
? leaveCountdownLabel
: journeysLoading || destStation.loading
? '--'
: 'Keine Verbindung'}
</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}>
@@ -233,7 +247,7 @@ export function EventListScreen({ navigation }: ScreenProps) {
minute: '2-digit', minute: '2-digit',
})} })}
</Text> </Text>
<Text style={[styles.status, { color: countdown.urgent ? colors.delete : '#34C759' }]}>{status}</Text> <Text style={[styles.status, { color: leaveCountdown?.urgent ? colors.delete : '#34C759' }]}>{status}</Text>
</View> </View>
</TouchableOpacity> </TouchableOpacity>