parent
1cdc15dd69
commit
a20a0a1832
|
@ -13,7 +13,7 @@ import {SnackbarContext} from './MassiveSnack';
|
||||||
import Set from './set';
|
import Set from './set';
|
||||||
import {addSet, updateSet} from './set.service';
|
import {addSet, updateSet} from './set.service';
|
||||||
import SetForm from './SetForm';
|
import SetForm from './SetForm';
|
||||||
import {settings} from './settings.service';
|
import {getSettings, settings, updateSettings} from './settings.service';
|
||||||
|
|
||||||
export default function EditSet() {
|
export default function EditSet() {
|
||||||
const {params} = useRoute<RouteProp<HomePageParams, 'EditSet'>>();
|
const {params} = useRoute<RouteProp<HomePageParams, 'EditSet'>>();
|
||||||
|
@ -47,6 +47,10 @@ export default function EditSet() {
|
||||||
!!settings.vibrate,
|
!!settings.vibrate,
|
||||||
settings.sound,
|
settings.sound,
|
||||||
);
|
);
|
||||||
|
const next = new Date();
|
||||||
|
next.setTime(next.getTime() + milliseconds);
|
||||||
|
await updateSettings({...settings, nextAlarm: next.toISOString()});
|
||||||
|
await getSettings();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
|
|
|
@ -13,7 +13,6 @@ export default function MassiveFab(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FAB
|
<FAB
|
||||||
{...props}
|
|
||||||
icon="add"
|
icon="add"
|
||||||
color={fabColor}
|
color={fabColor}
|
||||||
style={{
|
style={{
|
||||||
|
@ -22,6 +21,7 @@ export default function MassiveFab(
|
||||||
bottom: 60,
|
bottom: 60,
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
}}
|
}}
|
||||||
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import PlanPage from './PlanPage';
|
||||||
import Route from './route';
|
import Route from './route';
|
||||||
import {getSettings, settings} from './settings.service';
|
import {getSettings, settings} from './settings.service';
|
||||||
import SettingsPage from './SettingsPage';
|
import SettingsPage from './SettingsPage';
|
||||||
|
import TimerPage from './TimerPage';
|
||||||
import WorkoutsPage from './WorkoutsPage';
|
import WorkoutsPage from './WorkoutsPage';
|
||||||
|
|
||||||
const Drawer = createDrawerNavigator<DrawerParamList>();
|
const Drawer = createDrawerNavigator<DrawerParamList>();
|
||||||
|
@ -36,6 +37,7 @@ export default function Routes() {
|
||||||
{name: 'Plans', component: PlanPage, icon: 'calendar'},
|
{name: 'Plans', component: PlanPage, icon: 'calendar'},
|
||||||
{name: 'Best', component: BestPage, icon: 'stats-chart'},
|
{name: 'Best', component: BestPage, icon: 'stats-chart'},
|
||||||
{name: 'Workouts', component: WorkoutsPage, icon: 'barbell'},
|
{name: 'Workouts', component: WorkoutsPage, icon: 'barbell'},
|
||||||
|
{name: 'Timer', component: TimerPage, icon: 'time'},
|
||||||
{name: 'Settings', component: SettingsPage, icon: 'settings'},
|
{name: 'Settings', component: SettingsPage, icon: 'settings'},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
93
TimerPage.tsx
Normal file
93
TimerPage.tsx
Normal file
|
@ -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 (
|
||||||
|
<View style={{padding: PADDING, alignItems: 'center'}}>
|
||||||
|
<Text>
|
||||||
|
{minutes}:{seconds}
|
||||||
|
</Text>
|
||||||
|
<Button style={{marginTop: MARGIN}} onPress={stop}>
|
||||||
|
Stop
|
||||||
|
</Button>
|
||||||
|
<Button style={{marginTop: MARGIN}} onPress={add}>
|
||||||
|
Add 1 min
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
|
@ -25,6 +25,26 @@ class AlarmModule internal constructor(context: ReactApplicationContext?) :
|
||||||
return "AlarmModule"
|
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)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
fun timer(milliseconds: Int, vibrate: Boolean, sound: String?) {
|
fun timer(milliseconds: Int, vibrate: Boolean, sound: String?) {
|
||||||
|
|
3
db.ts
3
db.ts
|
@ -100,6 +100,9 @@ const migrations = [
|
||||||
`
|
`
|
||||||
ALTER TABLE settings ADD COLUMN steps BOOLEAN DEFAULT 1
|
ALTER TABLE settings ADD COLUMN steps BOOLEAN DEFAULT 1
|
||||||
`,
|
`,
|
||||||
|
`
|
||||||
|
ALTER TABLE settings ADD COLUMN nextAlarm TEXT NULL
|
||||||
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
export let db: SQLiteDatabase;
|
export let db: SQLiteDatabase;
|
||||||
|
|
|
@ -4,5 +4,5 @@ export type DrawerParamList = {
|
||||||
Best: {};
|
Best: {};
|
||||||
Plans: {};
|
Plans: {};
|
||||||
Workouts: {};
|
Workouts: {};
|
||||||
Loading: {};
|
Timer: {};
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,3 +17,10 @@ export const updateSettings = async (value: Settings) => {
|
||||||
const values = keys.map(key => value[key]);
|
const values = keys.map(key => value[key]);
|
||||||
return db.executeSql(update, values);
|
return db.executeSql(update, values);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getNext = async (): Promise<string | undefined> => {
|
||||||
|
const [result] = await db.executeSql(
|
||||||
|
`SELECT nextAlarm FROM settings LIMIT 1`,
|
||||||
|
);
|
||||||
|
return result.rows.item(0)?.nextAlarm;
|
||||||
|
};
|
||||||
|
|
|
@ -9,4 +9,5 @@ export default interface Settings {
|
||||||
color?: string;
|
color?: string;
|
||||||
workouts: number;
|
workouts: number;
|
||||||
steps: number;
|
steps: number;
|
||||||
|
nextAlarm?: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user