1 Commits

Author SHA1 Message Date
89fd4c2f44 feat(#6): add randomized fake startup loader with suitcase progress
All checks were successful
Luggage List Build / build-web (push) Successful in 41s
Luggage List Build / build-android (push) Successful in 7m54s
Luggage List Build / release (push) Successful in 13s
2026-04-18 18:32:34 +02:00
2 changed files with 53 additions and 2 deletions

View File

@@ -63,6 +63,8 @@ export default function AppRoot() {
const scrollRef = useRef(null); const scrollRef = useRef(null);
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
const [fakeLoadDone, setFakeLoadDone] = useState(false);
const [fakeLoadProgress, setFakeLoadProgress] = useState(0);
const [tab, setTab] = useState('trips'); const [tab, setTab] = useState('trips');
const [data, setData] = useState(emptyData); const [data, setData] = useState(emptyData);
@@ -83,6 +85,8 @@ export default function AppRoot() {
const [dialogState, setDialogState] = useState({ visible: false, title: '', message: '', buttons: [] }); const [dialogState, setDialogState] = useState({ visible: false, title: '', message: '', buttons: [] });
const topInset = Platform.OS === 'android' ? (RNStatusBar.currentHeight || 0) + 10 : 0; const topInset = Platform.OS === 'android' ? (RNStatusBar.currentHeight || 0) + 10 : 0;
const fakeLoadTotalMs = useMemo(() => 1200 + Math.floor(Math.random() * 2801), []);
const appReady = loaded && fakeLoadDone;
const visibleTrips = useMemo(() => data.trips.filter((trip) => !trip.archived), [data.trips]); const visibleTrips = useMemo(() => data.trips.filter((trip) => !trip.archived), [data.trips]);
@@ -165,6 +169,26 @@ export default function AppRoot() {
})(); })();
}, []); }, []);
useEffect(() => {
const startedAt = Date.now();
const interval = setInterval(() => {
const elapsed = Date.now() - startedAt;
setFakeLoadProgress(Math.min(1, elapsed / fakeLoadTotalMs));
}, 60);
const timeout = setTimeout(() => {
setFakeLoadProgress(1);
setFakeLoadDone(true);
clearInterval(interval);
}, fakeLoadTotalMs);
return () => {
clearInterval(interval);
clearTimeout(timeout);
};
}, [fakeLoadTotalMs]);
useEffect(() => { useEffect(() => {
if (!loaded) return; if (!loaded) return;
AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(data)).catch(() => { AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(data)).catch(() => {
@@ -733,12 +757,17 @@ export default function AppRoot() {
openAddItemModal(); openAddItemModal();
} }
if (!loaded) { if (!appReady) {
return ( return (
<SafeAreaView style={[styles.safe, { paddingTop: topInset }]}> <SafeAreaView style={[styles.safe, { paddingTop: topInset }]}>
<StatusBar style="light" translucent={false} /> <StatusBar style="light" translucent={false} />
<View style={styles.center}> <View style={styles.center}>
<Text style={styles.muted}>Loading local data...</Text> <Text style={styles.loadingEmoji}>🧳</Text>
<Text style={styles.loadingTitle}>Packing your list...</Text>
<View style={styles.loadingBarTrack}>
<View style={[styles.loadingBarFill, { width: `${Math.round(fakeLoadProgress * 100)}%` }]} />
</View>
<Text style={styles.muted}>{Math.round(fakeLoadProgress * 100)}%</Text>
</View> </View>
</SafeAreaView> </SafeAreaView>
); );

View File

@@ -26,6 +26,28 @@ export const styles = StyleSheet.create({
muted: { muted: {
color: '#8793a5', color: '#8793a5',
}, },
loadingEmoji: {
fontSize: 52,
marginBottom: 8,
},
loadingTitle: {
color: '#f8fafc',
fontWeight: '700',
fontSize: 17,
marginBottom: 10,
},
loadingBarTrack: {
width: 220,
height: 10,
borderRadius: 999,
backgroundColor: '#1e293b',
overflow: 'hidden',
marginBottom: 8,
},
loadingBarFill: {
height: '100%',
backgroundColor: '#2563eb',
},
tripPickerWrap: { tripPickerWrap: {
marginBottom: 6, marginBottom: 6,