import React, { useEffect, useMemo, useRef, useState } from 'react'; import { BackHandler, Platform, StatusBar, Modal, View, Image, StyleSheet, Button } from 'react-native'; import * as ScreenOrientation from 'expo-screen-orientation'; import * as SplashScreen from 'expo-splash-screen'; import { Accelerometer } from 'expo-sensors'; import { Audio } from 'expo-av'; import FocusScreen from './src/screens/FocusScreen'; import HomeScreen from './src/screens/HomeScreen'; import TimeUntilScreen from './src/screens/TimeUntilScreen'; import TimerScreen from './src/screens/TimerScreen'; import { createStyles } from './src/styles'; import { getTheme } from './src/theme'; // Keep the splash screen visible while we fetch resources SplashScreen.preventAutoHideAsync().catch(() => { /* reloading the app might cause this to error in dev */ }); export default function App() { const styles = useMemo(() => createStyles(), []); const [screen, setScreen] = useState('home'); const [focusMode, setFocusMode] = useState(false); const [darkMode, setDarkMode] = useState(true); const [pinkMode, setPinkMode] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const [now, setNow] = useState(new Date()); const [showMinion, setShowMinion] = useState(false); const [targetTime, setTargetTime] = useState(null); const [tuHour, setTuHour] = useState(''); const [tuMinute, setTuMinute] = useState(''); const [tuIsOver, setTuIsOver] = useState(false); const [timerRunning, setTimerRunning] = useState(false); const [timerDone, setTimerDone] = useState(false); const [timerRemaining, setTimerRemaining] = useState(0); const [timerHInput, setTimerHInput] = useState(''); const [timerMInput, setTimerMInput] = useState(''); const [timerSInput, setTimerSInput] = useState(''); const timerRef = useRef(null); const theme = getTheme(darkMode, pinkMode); useEffect(() => { // Hide splash screen after initialization SplashScreen.hideAsync().catch(() => {}); }, []); useEffect(() => { if (Platform.OS !== 'web') { ScreenOrientation.unlockAsync(); } }, []); useEffect(() => { if (Platform.OS === 'android' || Platform.OS === 'ios') { Accelerometer.setUpdateInterval(500); const subscription = Accelerometer.addListener(({ x, y, z }) => { const acceleration = Math.sqrt(x * x + y * y + z * z); if (acceleration > 2.5) { setShowMinion(true); setTimeout(() => setShowMinion(false), 3000); } }); return () => subscription.remove(); } }, []); useEffect(() => { const interval = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(interval); }, []); useEffect(() => { if (targetTime && now >= targetTime) { setTuIsOver(true); } else { setTuIsOver(false); } }, [now, targetTime]); useEffect(() => { if (timerRunning) { timerRef.current = setInterval(() => { setTimerRemaining((r) => { if (r <= 1) { clearInterval(timerRef.current); setTimerRunning(false); setTimerDone(true); return 0; } return r - 1; }); }, 1000); } return () => clearInterval(timerRef.current); }, [timerRunning]); useEffect(() => { if (Platform.OS !== 'android') return undefined; const sub = BackHandler.addEventListener('hardwareBackPress', () => { if (focusMode) { setFocusMode(false); return true; } if (screen !== 'home') { setScreen('home'); return true; } return false; }); return () => sub.remove(); }, [focusMode, screen]); const toggleFullscreen = () => { if (Platform.OS !== 'web') return; if (!document.fullscreenElement) { document.documentElement.requestFullscreen(); setIsFullscreen(true); } else { document.exitFullscreen(); setIsFullscreen(false); } }; const setTuTimer = () => { const h = parseInt(tuHour, 10); const m = parseInt(tuMinute, 10); if (isNaN(h) || isNaN(m) || h < 0 || h > 23 || m < 0 || m > 59) return; const target = new Date(); target.setHours(h, m, 0, 0); if (target <= new Date()) { target.setDate(target.getDate() + 1); } setTargetTime(target); setTuIsOver(false); }; const resetTuTimer = () => { setTargetTime(null); setTuIsOver(false); setTuHour(''); setTuMinute(''); }; const getTuCountdown = () => { if (!targetTime) return null; const diff = targetTime - now; if (diff <= 0) return null; const t = Math.floor(diff / 1000); return { hours: Math.floor(t / 3600), minutes: Math.floor((t % 3600) / 60), seconds: t % 60, }; }; const startTimer = () => { const h = parseInt(timerHInput, 10) || 0; const m = parseInt(timerMInput, 10) || 0; const s = parseInt(timerSInput, 10) || 0; const total = h * 3600 + m * 60 + s; if (total <= 0) return; setTimerRemaining(total); setTimerDone(false); setTimerRunning(true); }; const resetTimerState = () => { clearInterval(timerRef.current); setTimerRunning(false); setTimerDone(false); setTimerRemaining(0); setTimerHInput(''); setTimerMInput(''); setTimerSInput(''); }; const timerHr = Math.floor(timerRemaining / 3600); const timerMin = Math.floor((timerRemaining % 3600) / 60); const timerSec = timerRemaining % 60; const tuCountdown = getTuCountdown(); const playSound = async () => { try { const { sound } = await Audio.Sound.createAsync( require('./assets/alert.mp3'), { shouldPlay: true, volume: 1.0 } ); sound.setOnPlaybackStatusUpdate((status) => { if (status.didJustFinish) { sound.unloadAsync(); } }); } catch (error) { console.log('Error playing sound:', error); } }; useEffect(() => { if (timerDone || tuIsOver) { playSound(); } }, [timerDone, tuIsOver]); if (focusMode) { return ( setFocusMode(false)} /> ); } const barStyle = darkMode ? 'light-content' : 'dark-content'; if (screen === 'home') { return ( <> setDarkMode((d) => !d)} onTogglePink={() => setPinkMode((p) => !p)} onToggleFullscreen={toggleFullscreen} onSelectTimeUntil={() => setScreen('timeuntil')} onSelectTimer={() => setScreen('timer')} /> ); } if (screen === 'timeuntil') { return ( <> setScreen('home')} onToggleDark={() => setDarkMode((d) => !d)} onTogglePink={() => setPinkMode((p) => !p)} onToggleFullscreen={toggleFullscreen} onFocus={() => setFocusMode(true)} /> ); } return ( <>