import React, { useState, useEffect, useRef } from 'react';
import {
StyleSheet,
View,
Text,
TouchableOpacity,
ScrollView,
Modal,
Switch,
Alert,
Platform,
Vibration
} from 'react-native';
import { Audio } from 'expo-av';
import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
import AsyncStorage from '@react-native-async-storage/async-storage';
// Components
import TimerDisplay from './components/TimerDisplay';
import IntervalList from './components/IntervalList';
import PresetManager from './components/PresetManager';
import SettingsModal from './components/SettingsModal';
import IntervalEditor from './components/IntervalEditor';
const App = () => {
const [timerState, setTimerState] = useState('idle'); // idle, running, paused
const [currentInterval, setCurrentInterval] = useState(0);
const [timeLeft, setTimeLeft] = useState(0);
const [intervals, setIntervals] = useState([
{ id: 1, duration: 300, label: 'Work', color: '#34C759', sound: 'beep' },
{ id: 2, duration: 60, label: 'Rest', color: '#FF3B30', sound: 'beep' }
]);
const [presets, setPresets] = useState([]);
const [selectedPreset, setSelectedPreset] = useState(null);
const [isDarkMode, setIsDarkMode] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [showEditor, setShowEditor] = useState(false);
const [editingInterval, setEditingInterval] = useState(null);
const timerRef = useRef(null);
const soundRef = useRef(null);
const colors = {
light: {
background: '#FFFFFF',
card: '#F2F2F7',
text: '#000000',
primary: '#007AFF',
secondary: '#8E8E93'
},
dark: {
background: '#000000',
card: '#1C1C1E',
text: '#FFFFFF',
primary: '#0A84FF',
secondary: '#8E8E93'
}
};
const theme = isDarkMode ? [Link] : [Link];
useEffect(() => {
loadPresets();
loadSettings();
if ([Link] > 0) {
setTimeLeft(intervals[0].duration);
}
}, []);
useEffect(() => {
return () => {
if ([Link]) clearInterval([Link]);
if ([Link]) [Link]();
};
}, []);
const loadPresets = async () => {
try {
const savedPresets = await [Link]('timerPresets');
if (savedPresets) {
setPresets([Link](savedPresets));
}
} catch (error) {
[Link]('Error loading presets:', error);
}
};
const loadSettings = async () => {
try {
const settings = await [Link]('appSettings');
if (settings) {
const { darkMode } = [Link](settings);
setIsDarkMode(darkMode || false);
}
} catch (error) {
[Link]('Error loading settings:', error);
}
};
const saveSettings = async () => {
try {
await [Link]('appSettings', [Link]({
darkMode: isDarkMode
}));
} catch (error) {
[Link]('Error saving settings:', error);
}
};
const startTimer = () => {
if (timerState === 'running') return;
setTimerState('running');
[Link] = setInterval(() => {
setTimeLeft(prev => {
if (prev <= 1) {
nextInterval();
return 0;
}
return prev - 1;
});
}, 1000);
};
const pauseTimer = () => {
if ([Link]) {
clearInterval([Link]);
}
setTimerState('paused');
};
const resetTimer = () => {
if ([Link]) {
clearInterval([Link]);
}
setTimerState('idle');
setCurrentInterval(0);
setTimeLeft(intervals[0]?.duration || 0);
};
const nextInterval = async () => {
if (currentInterval < [Link] - 1) {
const nextIndex = currentInterval + 1;
setCurrentInterval(nextIndex);
setTimeLeft(intervals[nextIndex].duration);
// Play sound and/or vibrate
await playNotification(intervals[nextIndex].sound);
} else {
// End of all intervals
clearInterval([Link]);
setTimerState('completed');
[Link]('Complete!', 'All intervals completed!');
}
};
const prevInterval = () => {
if (currentInterval > 0) {
const prevIndex = currentInterval - 1;
setCurrentInterval(prevIndex);
setTimeLeft(intervals[prevIndex].duration);
}
};
const playNotification = async (soundType) => {
if (soundType === 'vibrate') {
[Link](500);
} else if (soundType === 'beep') {
try {
const { sound } = await [Link](
require('./assets/beep.mp3')
);
[Link] = sound;
await [Link]();
} catch (error) {
[Link]('Error playing sound:', error);
[Link](500);
}
}
};
const formatTime = (seconds) => {
const mins = [Link](seconds / 60);
const secs = seconds % 60;
return `${[Link]().padStart(2, '0')}:${[Link]().padStart(2,
'0')}`;
};
const addInterval = (interval) => {
const newInterval = {
...interval,
id: [Link]()
};
setIntervals(prev => [...prev, newInterval]);
};
const updateInterval = (updatedInterval) => {
setIntervals(prev => [Link](i =>
[Link] === [Link] ? updatedInterval : i
));
};
const deleteInterval = (id) => {
setIntervals(prev => [Link](i => [Link] !== id));
};
const savePreset = async (name) => {
const newPreset = {
id: [Link](),
name,
intervals: [...intervals],
createdAt: new Date().toISOString()
};
const updatedPresets = [...presets, newPreset];
setPresets(updatedPresets);
try {
await [Link]('timerPresets', [Link](updatedPresets));
} catch (error) {
[Link]('Error saving preset:', error);
}
};
const loadPreset = (preset) => {
setIntervals([...[Link]]);
setSelectedPreset([Link]);
resetTimer();
};
const deletePreset = async (id) => {
const updatedPresets = [Link](p => [Link] !== id);
setPresets(updatedPresets);
try {
await [Link]('timerPresets', [Link](updatedPresets));
} catch (error) {
[Link]('Error deleting preset:', error);
}
};
const exportPresets = async () => {
try {
const jsonString = [Link](presets, null, 2);
const fileUri = [Link] + 'interval_timer_presets.json';
await [Link](fileUri, jsonString);
if ([Link] === 'ios') {
await [Link](fileUri);
} else {
await [Link](fileUri, {
mimeType: 'application/json',
dialogTitle: 'Export Timer Presets'
});
}
} catch (error) {
[Link]('Error exporting presets:', error);
[Link]('Error', 'Failed to export presets');
}
};
const importPresets = async (jsonData) => {
try {
const parsedData = [Link](jsonData);
if ([Link](parsedData)) {
setPresets(parsedData);
await [Link]('timerPresets', [Link](parsedData));
[Link]('Success', 'Presets imported successfully');
} else {
throw new Error('Invalid format');
}
} catch (error) {
[Link]('Error importing presets:', error);
[Link]('Error', 'Failed to import presets. Invalid format.');
}
};
return (
<View style={[[Link], { backgroundColor: [Link] }]}>
<View style={[[Link], { backgroundColor: [Link] }]}>
<Text style={[[Link], { color: [Link] }]}>INTVL
Timer</Text>
<TouchableOpacity
onPress={() => setShowSettings(true)}
style={[Link]}
>
<Text style={[[Link], { color: [Link] }]}>⚙️</Text>
</TouchableOpacity>
</View>
<ScrollView style={[Link]}>
<TimerDisplay
timeLeft={formatTime(timeLeft)}
currentInterval={intervals[currentInterval]}
theme={theme}
isDarkMode={isDarkMode}
/>
<View style={[[Link], { backgroundColor: [Link] }]}>
<TouchableOpacity
style={[[Link], { backgroundColor: [Link] }]}
onPress={prevInterval}
disabled={currentInterval === 0}
>
<Text style={[Link]}>⏮</Text>
</TouchableOpacity>
{timerState === 'running' ? (
<TouchableOpacity
style={[[Link], [Link], { backgroundColor:
'#FF9500' }]}
onPress={pauseTimer}
>
<Text style={[Link]}>⏸</Text>
</TouchableOpacity>
) : (
<TouchableOpacity
style={[[Link], [Link], { backgroundColor:
[Link] }]}
onPress={startTimer}
>
<Text style={[Link]}>▶️</Text>
</TouchableOpacity>
)}
<TouchableOpacity
style={[[Link], { backgroundColor: [Link] }]}
onPress={nextInterval}
disabled={currentInterval === [Link] - 1}
>
<Text style={[Link]}>⏭</Text>
</TouchableOpacity>
<TouchableOpacity
style={[[Link], { backgroundColor: '#FF3B30' }]}
onPress={resetTimer}
>
<Text style={[Link]}>⏹</Text>
</TouchableOpacity>
</View>
<IntervalList
intervals={intervals}
currentInterval={currentInterval}
onEdit={setEditingInterval}
onAdd={() => setEditingInterval({})}
onDelete={deleteInterval}
theme={theme}
/>
<PresetManager
presets={presets}
selectedPreset={selectedPreset}
onSelect={loadPreset}
onSave={savePreset}
onDelete={deletePreset}
onExport={exportPresets}
onImport={importPresets}
theme={theme}
/>
</ScrollView>
<SettingsModal
visible={showSettings}
onClose={() => setShowSettings(false)}
isDarkMode={isDarkMode}
onDarkModeToggle={(value) => {
setIsDarkMode(value);
saveSettings();
}}
theme={theme}
/>
<IntervalEditor
visible={showEditor}
onClose={() => setShowEditor(false)}
interval={editingInterval}
onSave={(interval) => {
if ([Link]) {
updateInterval(interval);
} else {
addInterval(interval);
}
setShowEditor(false);
}}
theme={theme}
/>
</View>
);
};
const styles = [Link]({
container: {
flex: 1,
paddingTop: [Link] === 'ios' ? 50 : 30,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 20,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
elevation: 4,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 4,
},
headerTitle: {
fontSize: 24,
fontWeight: 'bold',
},
settingsButton: {
padding: 8,
},
settingsText: {
fontSize: 20,
},
content: {
flex: 1,
padding: 20,
},
controls: {
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
padding: 20,
borderRadius: 20,
marginVertical: 20,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
controlButton: {
width: 60,
height: 60,
borderRadius: 30,
justifyContent: 'center',
alignItems: 'center',
},
startButton: {
width: 70,
height: 70,
borderRadius: 35,
},
pauseButton: {
width: 70,
height: 70,
borderRadius: 35,
},
controlText: {
color: '#FFFFFF',
fontSize: 20,
fontWeight: 'bold',
},
});
export default App;