import { NavigationProp, useNavigation } from "@react-navigation/native"; import { format } from "date-fns"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { FlatList, NativeModules } from "react-native"; import DocumentPicker from "react-native-document-picker"; import { Dirs, FileSystem } from "react-native-file-access"; import { Button } from "react-native-paper"; import AppInput from "./AppInput"; import ConfirmDialog from "./ConfirmDialog"; import { PADDING } from "./constants"; import { AppDataSource } from "./data-source"; import { setRepo, settingsRepo } from "./db"; import { DrawerParams } from "./drawer-params"; import DrawerHeader from "./DrawerHeader"; import { darkOptions, lightOptions, themeOptions } from "./options"; import Page from "./Page"; import Select from "./Select"; import Settings from "./settings"; import Switch from "./Switch"; import { toast } from "./toast"; import { useAppTheme } from "./use-theme"; const twelveHours = [ "dd/LL/yyyy", "dd/LL/yyyy, p", "ccc p", "p", "yyyy-MM-dd", "yyyy-MM-dd, p", "yyyy.MM.dd", ]; const twentyFours = [ "dd/LL/yyyy", "dd/LL/yyyy, k:mm", "ccc k:mm", "k:mm", "yyyy-MM-dd", "yyyy-MM-dd, k:mm", "yyyy.MM.dd", ]; interface Item { name: string; renderItem: (name: string) => React.JSX.Element; } export default function SettingsPage() { const [ignoring, setIgnoring] = useState(false); const [term, setTerm] = useState(""); const [formatOptions, setFormatOptions] = useState(twelveHours); const [importing, setImporting] = useState(false); const [deleting, setDeleting] = useState(false); const [error, setError] = useState(""); const { reset } = useNavigation>(); const { watch, setValue } = useForm({ defaultValues: () => settingsRepo.findOne({ where: {} }), }); const settings = watch(); const { theme, setTheme, lightColor, setLightColor, darkColor, setDarkColor, } = useAppTheme(); useEffect(() => { NativeModules.SettingsModule.ignoringBattery().then(setIgnoring); NativeModules.SettingsModule.is24().then((is24: boolean) => { console.log(`${SettingsPage.name}.focus:`, { is24 }); if (is24) setFormatOptions(twentyFours); else setFormatOptions(twelveHours); }); }, []); const backupString = useMemo(() => { if (!settings.backupDir) return null; const split = decodeURIComponent(settings.backupDir).split(":"); return split.pop(); }, [settings.backupDir]); const soundString = useMemo(() => { if (!settings.sound) return null; const split = settings.sound.split("/"); return split.pop(); }, [settings.sound]); const confirmDelete = useCallback(async () => { setDeleting(false); await AppDataSource.dropDatabase(); await AppDataSource.destroy(); await AppDataSource.initialize(); toast("Database deleted."); }, []); const confirmImport = useCallback(async () => { setImporting(false); await FileSystem.cp( Dirs.DatabaseDir + "/massive.db", Dirs.DatabaseDir + "/massive-backup.db" ); await AppDataSource.destroy(); const file = await DocumentPicker.pickSingle(); if (!file.uri.endsWith('.db')) return toast("File name must end with .db") await FileSystem.cp(file.uri, Dirs.DatabaseDir + "/massive.db"); try { await AppDataSource.initialize(); } catch (e) { setError(e.toString()); await FileSystem.cp( Dirs.DatabaseDir + "/massive-backup.db", Dirs.DatabaseDir + "/massive.db" ); await AppDataSource.initialize(); return; } await setRepo.update({}, { image: null }); await settingsRepo.update({}, { sound: null, backup: false }); reset({ index: 0, routes: [{ name: "Settings" }] }); toast("Imported database successfully.") }, [reset]); const today = new Date(); const data: Item[] = [ { name: "Start up page", renderItem: (name: string) => ( { setValue("theme", value); setTheme(value); await settingsRepo.update({}, { theme: value }); if (value === "dark") toast("Theme will always be dark."); else if (value === "light") toast("Theme will always be light."); else if (value === "system") toast("Theme will follow system."); }} /> ), }, { name: "Date format", renderItem: (name: string) => ( { setValue("autoConvert", value); await settingsRepo.update({}, { autoConvert: value }); if (value) toast(`Sets now automatically convert to ${value}`); else toast("Stopped automatically converting sets."); }} /> ), }, { name: "Vibration duration (ms)", renderItem: (name: string) => ( setValue("duration", Number(value))} onSubmitEditing={async (e) => { const value = Number(e.nativeEvent.text); setValue("duration", value); await settingsRepo.update({}, { duration: value }); toast("Changed duration of alarm vibrations."); }} keyboardType="numeric" blurOnSubmit /> ), }, { name: "Default sets", renderItem: (name: string) => ( setValue("defaultSets", Number(value))} onSubmitEditing={async (e) => { const value = Number(e.nativeEvent.text); setValue("defaultSets", value); await settingsRepo.update({}, { defaultSets: value }); toast(`New exercises now have ${value} sets by default.`); }} keyboardType="numeric" blurOnSubmit /> ), }, { name: "Default minutes", renderItem: (name: string) => ( setValue("defaultMinutes", Number(value))} onSubmitEditing={async (e) => { const value = Number(e.nativeEvent.text); setValue("defaultMinutes", value); await settingsRepo.update({}, { defaultMinutes: value }); toast(`New exercises now wait ${value} minutes by default.`); }} keyboardType="numeric" blurOnSubmit /> ), }, { name: "Default seconds", renderItem: (name: string) => ( setValue("defaultSeconds", Number(value))} onSubmitEditing={async (e) => { const value = Number(e.nativeEvent.text); setValue("defaultSeconds", value); await settingsRepo.update({}, { defaultSeconds: value }); toast(`New exercises now wait ${value} seconds by default.`); }} keyboardType="numeric" blurOnSubmit /> ), }, { name: "Dark color", renderItem: (name: string) => ( { setValue("lightColor", value); setLightColor(value); await settingsRepo.update({}, { lightColor: value }); toast("Set primary color for light mode."); }} /> ), }, { name: "Rest timers", renderItem: (name: string) => ( { setValue("alarm", value); if (value && !ignoring) { NativeModules.SettingsModule.ignoreBattery(); } await settingsRepo.update({}, { alarm: value }); if (value) toast("Timers will now run after each set."); else toast("Stopped timers running after each set."); }} title={name} /> ), }, { name: "Vibrate", renderItem: (name: string) => ( { setValue("vibrate", value); await settingsRepo.update({}, { vibrate: value }); if (value) toast("Alarms will vibrate."); else toast("Stopped alarms from vibrating."); }} title={name} /> ), }, { name: "Sound", renderItem: (name: string) => ( { setValue("noSound", !value); await settingsRepo.update({}, { noSound: !value }); if (!value) toast("Alarms will no longer make a sound."); else toast("Enabled sound for alarms."); }} title={name} /> ), }, { name: "Notifications", renderItem: (name: string) => ( { setValue("notify", value); await settingsRepo.update({}, { notify: value }); if (value) toast("Show notifications for new records."); else toast("Stopped notifications for new records."); }} title={name} /> ), }, { name: "Show images", renderItem: (name: string) => ( { setValue("images", value); await settingsRepo.update({}, { images: value }); if (value) toast("Show images for sets."); else toast("Hid images for sets."); }} title={name} /> ), }, { name: "Show unit", renderItem: (name: string) => ( { setValue("showUnit", value); await settingsRepo.update({}, { showUnit: value }); if (value) toast("Show option to select unit for sets."); else toast("Hid unit option for sets."); }} title={name} /> ), }, { name: "Show date", renderItem: (name: string) => ( { setValue("showDate", value); await settingsRepo.update({}, { showDate: value }); if (value) toast("Show date for sets."); else toast("Hid date on sets."); }} title={name} /> ), }, { name: "Automatic backup", renderItem: (name: string) => ( { setValue("backup", value); await settingsRepo.update({}, { backup: value }); if (value) { const result = await DocumentPicker.pickDirectory(); setValue("backupDir", result.uri); await settingsRepo.update({}, { backupDir: result.uri }); console.log(`${SettingsPage.name}.backup:`, { result }); toast("Backup database daily."); NativeModules.BackupModule.start(result.uri); } else { toast("Stopped backing up daily"); NativeModules.BackupModule.stop(); } }} title={name} /> ), }, { name: `Backup directory: ${backupString || "Not set yet!"}`, renderItem: (name: string) => ( ), }, { name: `Alarm sound: ${soundString || "Default"}`, renderItem: (name: string) => ( ), }, { name: "Export database", renderItem: (name: string) => ( ), }, { name: "Export sets as CSV", renderItem: (name: string) => ( ), }, { name: "Export plans as CSV", renderItem: (name: string) => ( ), }, { name: "Import database", renderItem: (name: string) => ( ), }, { name: "Delete database", renderItem: (name: string) => ( ), }, ]; return ( <> item.name.toLowerCase().includes(term.toLowerCase()) )} renderItem={({ item }) => item.renderItem(item.name)} style={{ flex: 1, paddingTop: PADDING }} /> setError("")} setShow={() => setError("")} show={!!error} > {error} Importing a database overwrites your current data. This action cannot be reversed! Deleting your database wipes your current data. This action cannot be reversed! ); }