diff --git a/EditSet.tsx b/EditSet.tsx index 9d15f9e..83ca82e 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -13,7 +13,7 @@ import {SnackbarContext} from './MassiveSnack'; import Set from './set'; import {addSet, updateSet} from './set.service'; import SetForm from './SetForm'; -import {settings} from './settings.service'; +import {getSettings, settings, updateSettings} from './settings.service'; export default function EditSet() { const {params} = useRoute>(); @@ -47,6 +47,10 @@ export default function EditSet() { !!settings.vibrate, settings.sound, ); + const next = new Date(); + next.setTime(next.getTime() + milliseconds); + await updateSettings({...settings, nextAlarm: next.toISOString()}); + await getSettings(); }, []); const update = useCallback( diff --git a/MassiveFab.tsx b/MassiveFab.tsx index 6bb1bbc..207463b 100644 --- a/MassiveFab.tsx +++ b/MassiveFab.tsx @@ -13,7 +13,6 @@ export default function MassiveFab( return ( ); } diff --git a/Routes.tsx b/Routes.tsx index e1332fe..6129940 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -11,6 +11,7 @@ import PlanPage from './PlanPage'; import Route from './route'; import {getSettings, settings} from './settings.service'; import SettingsPage from './SettingsPage'; +import TimerPage from './TimerPage'; import WorkoutsPage from './WorkoutsPage'; const Drawer = createDrawerNavigator(); @@ -36,6 +37,7 @@ export default function Routes() { {name: 'Plans', component: PlanPage, icon: 'calendar'}, {name: 'Best', component: BestPage, icon: 'stats-chart'}, {name: 'Workouts', component: WorkoutsPage, icon: 'barbell'}, + {name: 'Timer', component: TimerPage, icon: 'time'}, {name: 'Settings', component: SettingsPage, icon: 'settings'}, ]; diff --git a/TimerPage.tsx b/TimerPage.tsx new file mode 100644 index 0000000..2569055 --- /dev/null +++ b/TimerPage.tsx @@ -0,0 +1,93 @@ +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useEffect, useState} from 'react'; +import {NativeModules, View} from 'react-native'; +import {Button, Text} from 'react-native-paper'; +import {MARGIN, PADDING} from './constants'; +import { + getNext, + getSettings, + settings, + updateSettings, +} from './settings.service'; + +export default function TimerPage() { + const [next, setNext] = useState(new Date()); + const [ms, setMs] = useState(0); + const [intervalId, setIntervalId] = useState(0); + + const seconds = + ms > 0 + ? Math.floor((ms / 1000) % 60) + .toString() + .padStart(2, '0') + : '00'; + + const minutes = + ms > 0 + ? Math.floor(ms / 1000 / 60) + .toString() + .padStart(2, '0') + : '00'; + + useFocusEffect( + useCallback(() => { + getNext().then(nextIso => + setNext(nextIso ? new Date(nextIso) : new Date()), + ); + }, []), + ); + + const tick = (date: Date) => { + const remaining = date.getTime() - new Date().getTime(); + console.log(`${TimerPage.name}.useEffect`, {remaining}); + if (remaining <= 0) return; + setMs(remaining); + }; + + useEffect(() => { + console.log(`${TimerPage.name}.useEffect:`, {next}); + const date = next || new Date(); + tick(date); + const id = setInterval(() => { + tick(date); + }, 1000); + setIntervalId(oldId => { + clearInterval(oldId); + return id; + }); + return () => clearInterval(id); + }, [next]); + + const stop = () => { + NativeModules.AlarmModule.stop(); + setNext(new Date()); + updateSettings({...settings, nextAlarm: undefined}); + getSettings(); + tick(new Date()); + setMs(0); + }; + + const add = async () => { + console.log(`${TimerPage.name}.add:`, {intervalId, next}); + const date = next || new Date(); + date.setTime(date.getTime() + 1000 * 60); + await updateSettings({...settings, nextAlarm: date.toISOString()}); + setNext(date); + NativeModules.AlarmModule.add(ms, !!settings.vibrate, settings.sound); + tick(date); + }; + + return ( + + + {minutes}:{seconds} + + + + + ); +} diff --git a/android/app/src/main/java/com/massive/AlarmModule.kt b/android/app/src/main/java/com/massive/AlarmModule.kt index 6374733..cd1178a 100644 --- a/android/app/src/main/java/com/massive/AlarmModule.kt +++ b/android/app/src/main/java/com/massive/AlarmModule.kt @@ -25,6 +25,26 @@ class AlarmModule internal constructor(context: ReactApplicationContext?) : return "AlarmModule" } + @RequiresApi(api = Build.VERSION_CODES.O) + @ReactMethod + fun add(milliseconds: Int, vibrate: Boolean, sound: String?) { + Log.d("AlarmModule", "Add 1 min to alarm.") + val addIntent = Intent(reactApplicationContext, TimerService::class.java) + addIntent.action = "add" + addIntent.putExtra("vibrate", vibrate) + addIntent.putExtra("sound", sound) + addIntent.data = Uri.parse("$milliseconds") + reactApplicationContext.startService(addIntent) + } + + @RequiresApi(api = Build.VERSION_CODES.O) + @ReactMethod + fun stop() { + Log.d("AlarmModule", "Stop alarm.") + val intent = Intent(reactApplicationContext, TimerService::class.java) + reactApplicationContext.stopService(intent) + } + @RequiresApi(api = Build.VERSION_CODES.O) @ReactMethod fun timer(milliseconds: Int, vibrate: Boolean, sound: String?) { diff --git a/db.ts b/db.ts index 71314cf..024ffc6 100644 --- a/db.ts +++ b/db.ts @@ -100,6 +100,9 @@ const migrations = [ ` ALTER TABLE settings ADD COLUMN steps BOOLEAN DEFAULT 1 `, + ` + ALTER TABLE settings ADD COLUMN nextAlarm TEXT NULL + `, ]; export let db: SQLiteDatabase; diff --git a/drawer-param-list.ts b/drawer-param-list.ts index a2676d3..8fea9ed 100644 --- a/drawer-param-list.ts +++ b/drawer-param-list.ts @@ -4,5 +4,5 @@ export type DrawerParamList = { Best: {}; Plans: {}; Workouts: {}; - Loading: {}; + Timer: {}; }; diff --git a/settings.service.ts b/settings.service.ts index 3e6333f..6e94451 100644 --- a/settings.service.ts +++ b/settings.service.ts @@ -17,3 +17,10 @@ export const updateSettings = async (value: Settings) => { const values = keys.map(key => value[key]); return db.executeSql(update, values); }; + +export const getNext = async (): Promise => { + const [result] = await db.executeSql( + `SELECT nextAlarm FROM settings LIMIT 1`, + ); + return result.rows.item(0)?.nextAlarm; +}; diff --git a/settings.ts b/settings.ts index 8da7ce3..a1690ce 100644 --- a/settings.ts +++ b/settings.ts @@ -9,4 +9,5 @@ export default interface Settings { color?: string; workouts: number; steps: number; + nextAlarm?: string; }