Switch some modals to dialogs and fix light mode

This commit is contained in:
Brandon Presley 2022-07-06 21:03:56 +12:00
parent d938ec775a
commit 07fa6f7ab2
9 changed files with 174 additions and 131 deletions

47
App.tsx
View File

@ -1,4 +1,5 @@
import AsyncStorage from '@react-native-async-storage/async-storage'; import {useAsyncStorage} from '@react-native-async-storage/async-storage';
import Ionicon from 'react-native-vector-icons/Ionicons';
import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs'; import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs';
import { import {
DarkTheme, DarkTheme,
@ -7,6 +8,11 @@ import {
} from '@react-navigation/native'; } from '@react-navigation/native';
import React, {useEffect} from 'react'; import React, {useEffect} from 'react';
import {StatusBar, useColorScheme} from 'react-native'; import {StatusBar, useColorScheme} from 'react-native';
import {
DarkTheme as DarkThemePaper,
DefaultTheme as DefaultThemePaper,
Provider,
} from 'react-native-paper';
import {setupSchema} from './db'; import {setupSchema} from './db';
import Exercises from './Exercises'; import Exercises from './Exercises';
import Home from './Home'; import Home from './Home';
@ -25,23 +31,38 @@ setupSchema();
const App = () => { const App = () => {
const dark = useColorScheme() === 'dark'; const dark = useColorScheme() === 'dark';
const {getItem: getMinutes, setItem: setMinutes} = useAsyncStorage('minutes');
const {getItem: getSeconds, setItem: setSeconds} = useAsyncStorage('seconds');
const {getItem: getAlarmEnabled, setItem: setAlarmEnabled} =
useAsyncStorage('alarmEnabled');
const defaults = async () => {
const minutes = await getMinutes();
if (minutes === null) await setMinutes('3');
const seconds = await getSeconds();
if (seconds === null) await setSeconds('30');
const alarmEnabled = await getAlarmEnabled();
if (alarmEnabled === null) await setAlarmEnabled('false');
};
useEffect(() => { useEffect(() => {
AsyncStorage.getItem('minutes').then(async minutes => { defaults();
if (!minutes) await AsyncStorage.setItem('minutes', '3');
});
}, []); }, []);
return ( return (
<NavigationContainer theme={dark ? DarkTheme : DefaultTheme}> <Provider
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} /> theme={dark ? DarkThemePaper : DefaultThemePaper}
<Tab.Navigator> settings={{icon: props => <Ionicon {...props} />}}>
<Tab.Screen name="Home" component={Home} /> <NavigationContainer theme={dark ? DarkTheme : DefaultTheme}>
<Tab.Screen name="Plans" component={Plans} /> <StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
<Tab.Screen name="Exercises" component={Exercises} /> <Tab.Navigator>
<Tab.Screen name="Settings" component={Settings} /> <Tab.Screen name="Home" component={Home} />
</Tab.Navigator> <Tab.Screen name="Plans" component={Plans} />
</NavigationContainer> <Tab.Screen name="Exercises" component={Exercises} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
</NavigationContainer>
</Provider>
); );
}; };

33
BatteryDialog.tsx Normal file
View File

@ -0,0 +1,33 @@
import React from 'react';
import {NativeModules, Text} from 'react-native';
import {Button, Dialog, Portal} from 'react-native-paper';
export default function BatteryDialog({
show,
setShow,
}: {
show: boolean;
setShow: (show: boolean) => void;
}) {
const ok = () => {
NativeModules.AlarmModule.openBatteryOptimizations();
setShow(false);
};
return (
<Portal>
<Dialog visible={show} onDismiss={() => setShow(false)}>
<Dialog.Title>Battery optimizations</Dialog.Title>
<Dialog.Content>
<Text>
Disable battery optimizations for Massive to use rest timers. Settings -> Apps -> Massive -> Battery -> Unrestricted.
</Text>
</Dialog.Content>
<Dialog.Actions>
<Button onPress={ok}>Open settings</Button>
<Button onPress={() => setShow(false)}>Cancel</Button>
</Dialog.Actions>
</Dialog>
</Portal>
);
}

View File

@ -1,6 +1,6 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import {StyleSheet, Text, View} from 'react-native'; import {StyleSheet, Text, View} from 'react-native';
import {Button, Modal, Portal, TextInput} from 'react-native-paper'; import {Button, Dialog, Modal, Portal, TextInput} from 'react-native-paper';
import DayMenu from './DayMenu'; import DayMenu from './DayMenu';
import WorkoutMenu from './WorkoutMenu'; import WorkoutMenu from './WorkoutMenu';
import {getDb} from './db'; import {getDb} from './db';
@ -85,12 +85,9 @@ export default function EditPlan({
return ( return (
<Portal> <Portal>
<Modal <Dialog visible={show} onDismiss={() => setShow(false)}>
visible={show} <Dialog.Title>{id ? `Edit "${days}"` : 'Add a plan'}</Dialog.Title>
contentContainerStyle={styles.modal} <Dialog.Content>
onDismiss={() => setShow(false)}>
<Text style={styles.title}>{id ? `Edit "${days}"` : 'Add a plan'}</Text>
<View style={{alignItems: 'flex-start'}}>
{days.split(',').map((day, index) => ( {days.split(',').map((day, index) => (
<DayMenu <DayMenu
index={index} index={index}
@ -112,39 +109,16 @@ export default function EditPlan({
key={index} key={index}
/> />
))} ))}
</View> </Dialog.Content>
<View style={styles.bottom}> <Dialog.Actions>
<Button mode="contained" icon="save" onPress={save}> <Button mode="contained" icon="save" onPress={save}>
Save Save
</Button> </Button>
<Button icon="close" onPress={() => setShow(false)}> <Button icon="close" onPress={() => setShow(false)}>
Cancel Cancel
</Button> </Button>
{id && ( </Dialog.Actions>
<Button icon="copy" onPress={clearId}> </Dialog>
Duplicate
</Button>
)}
</View>
</Modal>
</Portal> </Portal>
); );
} }
const styles = StyleSheet.create({
modal: {
backgroundColor: 'black',
padding: 20,
},
text: {
marginBottom: 10,
},
bottom: {
flexDirection: 'row',
marginTop: 10,
},
title: {
fontSize: 20,
marginBottom: 10,
},
});

View File

@ -1,6 +1,6 @@
import React, {useEffect, useRef, useState} from 'react'; import React, {useEffect, useRef, useState} from 'react';
import {StyleSheet, Text, View} from 'react-native'; import {StyleSheet, Text, View} from 'react-native';
import {Button, Modal, Portal, TextInput} from 'react-native-paper'; import {Button, Dialog, Modal, Portal, TextInput} from 'react-native-paper';
import {getDb} from './db'; import {getDb} from './db';
import Set from './set'; import Set from './set';
import {format} from 'date-fns'; import {format} from 'date-fns';
@ -62,75 +62,62 @@ export default function EditSet({
return ( return (
<Portal> <Portal>
<Modal <Dialog visible={show} onDismiss={() => setShow(false)}>
visible={show} <Dialog.Title>{id ? `Edit "${name}"` : 'Add a set'}</Dialog.Title>
contentContainerStyle={styles.modal} <Dialog.Content>
onDismiss={() => setShow(false)}> <TextInput
<Text style={styles.title}>{id ? `Edit "${name}"` : 'Add a set'}</Text> style={styles.text}
<TextInput autoFocus
style={styles.text} label="Name *"
autoFocus value={name}
label="Name *" onChangeText={setName}
value={name} onSubmitEditing={() => repsRef.current?.focus()}
onChangeText={setName} />
onSubmitEditing={() => repsRef.current?.focus()} <TextInput
/> style={styles.text}
<TextInput label="Reps *"
style={styles.text} keyboardType="numeric"
label="Reps *" value={reps}
keyboardType="numeric" onChangeText={setReps}
value={reps} ref={repsRef}
onChangeText={setReps} onSubmitEditing={() => weightRef.current?.focus()}
ref={repsRef} />
onSubmitEditing={() => weightRef.current?.focus()} <TextInput
/> style={styles.text}
<TextInput label="Weight *"
style={styles.text} keyboardType="numeric"
label="Weight *" value={weight}
keyboardType="numeric" onChangeText={setWeight}
value={weight} onSubmitEditing={save}
onChangeText={setWeight} ref={weightRef}
onSubmitEditing={save} />
ref={weightRef} <TextInput
/> style={styles.text}
<TextInput label="Unit (kg)"
style={styles.text} value={unit}
label="Unit (kg)" onChangeText={setUnit}
value={unit} ref={unitRef}
onChangeText={setUnit} onSubmitEditing={save}
ref={unitRef} />
onSubmitEditing={save} <Text style={styles.text}>{format(created, 'PPPP p')}</Text>
/> </Dialog.Content>
<Text style={styles.text}>{format(created, 'PPPP p')}</Text> <Dialog.Actions>
<View style={styles.bottom}>
<Button mode="contained" icon="save" onPress={save}>
Save
</Button>
<Button icon="close" onPress={() => setShow(false)}> <Button icon="close" onPress={() => setShow(false)}>
Cancel Cancel
</Button> </Button>
{id && ( <Button mode="contained" icon="save" onPress={save}>
<Button icon="copy" onPress={clearId}> Save
Duplicate </Button>
</Button> </Dialog.Actions>
)} </Dialog>
</View>
</Modal>
</Portal> </Portal>
); );
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
modal: {
backgroundColor: 'black',
padding: 20,
},
text: { text: {
marginBottom: 10, marginBottom: 10,
}, },
bottom: {
flexDirection: 'row',
},
title: { title: {
fontSize: 20, fontSize: 20,
marginBottom: 10, marginBottom: 10,

View File

@ -86,15 +86,13 @@ export default function Home() {
refreshing={refreshing} refreshing={refreshing}
onRefresh={refresh} onRefresh={refresh}
/> />
<View style={styles.bottom}> <EditSet
<EditSet clearId={() => setId(undefined)}
clearId={() => setId(undefined)} id={id}
id={id} show={showEdit}
show={showEdit} setShow={setShowEdit}
setShow={setShowEdit} onSave={save}
onSave={save} />
/>
</View>
<AnimatedFAB <AnimatedFAB
extended={false} extended={false}

View File

@ -5,6 +5,7 @@ import {NativeModules, StyleSheet, Text, View} from 'react-native';
import {Button, Snackbar, Switch, TextInput} from 'react-native-paper'; import {Button, Snackbar, Switch, TextInput} from 'react-native-paper';
import {RootStackParamList} from './App'; import {RootStackParamList} from './App';
import {getDb} from './db'; import {getDb} from './db';
import BatteryDialog from './BatteryDialog';
export default function Settings({ export default function Settings({
navigation, navigation,
@ -13,6 +14,7 @@ export default function Settings({
const [seconds, setSeconds] = useState<string>(''); const [seconds, setSeconds] = useState<string>('');
const [alarmEnabled, setAlarmEnabled] = useState<boolean>(true); const [alarmEnabled, setAlarmEnabled] = useState<boolean>(true);
const [snackbar, setSnackbar] = useState(''); const [snackbar, setSnackbar] = useState('');
const [showBattery, setShowBattery] = useState(false);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@ -43,6 +45,16 @@ export default function Settings({
NativeModules.ImportModule.sets(); NativeModules.ImportModule.sets();
}; };
const changeAlarmEnabled = (enabled: boolean) => {
if (!enabled) return setAlarmEnabled(enabled);
NativeModules.AlarmModule.ignoringBatteryOptimizations(
(ignoring: boolean) => {
if (ignoring) return setAlarmEnabled(true);
setShowBattery(true);
},
);
};
return ( return (
<View style={styles.container}> <View style={styles.container}>
<TextInput <TextInput
@ -65,7 +77,7 @@ export default function Settings({
<Switch <Switch
style={[styles.text, {alignSelf: 'flex-start'}]} style={[styles.text, {alignSelf: 'flex-start'}]}
value={alarmEnabled} value={alarmEnabled}
onValueChange={setAlarmEnabled} onValueChange={changeAlarmEnabled}
/> />
<Button <Button
style={{alignSelf: 'flex-start'}} style={{alignSelf: 'flex-start'}}
@ -86,6 +98,8 @@ export default function Settings({
Delete all data Delete all data
</Button> </Button>
<BatteryDialog show={showBattery} setShow={setShowBattery} />
<Snackbar <Snackbar
visible={!!snackbar} visible={!!snackbar}
onDismiss={() => setSnackbar('')} onDismiss={() => setSnackbar('')}

View File

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application <application
android:name=".MainApplication" android:name=".MainApplication"

View File

@ -1,13 +1,19 @@
package com.massive package com.massive
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReactMethod
class AlarmModule internal constructor(context: ReactApplicationContext?) : class AlarmModule internal constructor(context: ReactApplicationContext?) :
ReactContextBaseJavaModule(context) { ReactContextBaseJavaModule(context) {
@ -23,4 +29,26 @@ class AlarmModule internal constructor(context: ReactApplicationContext?) :
intent.putExtra("milliseconds", milliseconds) intent.putExtra("milliseconds", milliseconds)
reactApplicationContext.startService(intent) reactApplicationContext.startService(intent)
} }
@RequiresApi(Build.VERSION_CODES.M)
@ReactMethod
fun ignoringBatteryOptimizations(callback: Callback) {
val packageName = reactApplicationContext.packageName
val pm =
reactApplicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
callback.invoke(pm.isIgnoringBatteryOptimizations(packageName))
} else {
callback.invoke(true)
}
}
@RequiresApi(Build.VERSION_CODES.M)
@ReactMethod
fun openBatteryOptimizations() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:" + reactApplicationContext.packageName)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
reactApplicationContext.startActivity(intent)
}
} }

View File

@ -1,20 +1,7 @@
import {AppRegistry} from 'react-native';
import 'react-native-gesture-handler'; import 'react-native-gesture-handler';
import 'react-native-sqlite-storage'; import 'react-native-sqlite-storage';
import React from 'react';
import {AppRegistry} from 'react-native';
import App from './App'; import App from './App';
import {name as appName} from './app.json'; import {name as appName} from './app.json';
import {Provider as PaperProvider, DarkTheme} from 'react-native-paper';
import Ionicon from 'react-native-vector-icons/Ionicons';
export default function Main() { AppRegistry.registerComponent(appName, () => App);
return (
<PaperProvider
theme={DarkTheme}
settings={{icon: props => <Ionicon {...props} />}}>
<App />
</PaperProvider>
);
}
AppRegistry.registerComponent(appName, () => Main);