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 {
DarkTheme,
@ -7,6 +8,11 @@ import {
} from '@react-navigation/native';
import React, {useEffect} from 'react';
import {StatusBar, useColorScheme} from 'react-native';
import {
DarkTheme as DarkThemePaper,
DefaultTheme as DefaultThemePaper,
Provider,
} from 'react-native-paper';
import {setupSchema} from './db';
import Exercises from './Exercises';
import Home from './Home';
@ -25,23 +31,38 @@ setupSchema();
const App = () => {
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(() => {
AsyncStorage.getItem('minutes').then(async minutes => {
if (!minutes) await AsyncStorage.setItem('minutes', '3');
});
defaults();
}, []);
return (
<NavigationContainer theme={dark ? DarkTheme : DefaultTheme}>
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
<Tab.Navigator>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Plans" component={Plans} />
<Tab.Screen name="Exercises" component={Exercises} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
</NavigationContainer>
<Provider
theme={dark ? DarkThemePaper : DefaultThemePaper}
settings={{icon: props => <Ionicon {...props} />}}>
<NavigationContainer theme={dark ? DarkTheme : DefaultTheme}>
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
<Tab.Navigator>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Plans" component={Plans} />
<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 {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 WorkoutMenu from './WorkoutMenu';
import {getDb} from './db';
@ -85,12 +85,9 @@ export default function EditPlan({
return (
<Portal>
<Modal
visible={show}
contentContainerStyle={styles.modal}
onDismiss={() => setShow(false)}>
<Text style={styles.title}>{id ? `Edit "${days}"` : 'Add a plan'}</Text>
<View style={{alignItems: 'flex-start'}}>
<Dialog visible={show} onDismiss={() => setShow(false)}>
<Dialog.Title>{id ? `Edit "${days}"` : 'Add a plan'}</Dialog.Title>
<Dialog.Content>
{days.split(',').map((day, index) => (
<DayMenu
index={index}
@ -112,39 +109,16 @@ export default function EditPlan({
key={index}
/>
))}
</View>
<View style={styles.bottom}>
</Dialog.Content>
<Dialog.Actions>
<Button mode="contained" icon="save" onPress={save}>
Save
</Button>
<Button icon="close" onPress={() => setShow(false)}>
Cancel
</Button>
{id && (
<Button icon="copy" onPress={clearId}>
Duplicate
</Button>
)}
</View>
</Modal>
</Dialog.Actions>
</Dialog>
</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 {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 Set from './set';
import {format} from 'date-fns';
@ -62,75 +62,62 @@ export default function EditSet({
return (
<Portal>
<Modal
visible={show}
contentContainerStyle={styles.modal}
onDismiss={() => setShow(false)}>
<Text style={styles.title}>{id ? `Edit "${name}"` : 'Add a set'}</Text>
<TextInput
style={styles.text}
autoFocus
label="Name *"
value={name}
onChangeText={setName}
onSubmitEditing={() => repsRef.current?.focus()}
/>
<TextInput
style={styles.text}
label="Reps *"
keyboardType="numeric"
value={reps}
onChangeText={setReps}
ref={repsRef}
onSubmitEditing={() => weightRef.current?.focus()}
/>
<TextInput
style={styles.text}
label="Weight *"
keyboardType="numeric"
value={weight}
onChangeText={setWeight}
onSubmitEditing={save}
ref={weightRef}
/>
<TextInput
style={styles.text}
label="Unit (kg)"
value={unit}
onChangeText={setUnit}
ref={unitRef}
onSubmitEditing={save}
/>
<Text style={styles.text}>{format(created, 'PPPP p')}</Text>
<View style={styles.bottom}>
<Button mode="contained" icon="save" onPress={save}>
Save
</Button>
<Dialog visible={show} onDismiss={() => setShow(false)}>
<Dialog.Title>{id ? `Edit "${name}"` : 'Add a set'}</Dialog.Title>
<Dialog.Content>
<TextInput
style={styles.text}
autoFocus
label="Name *"
value={name}
onChangeText={setName}
onSubmitEditing={() => repsRef.current?.focus()}
/>
<TextInput
style={styles.text}
label="Reps *"
keyboardType="numeric"
value={reps}
onChangeText={setReps}
ref={repsRef}
onSubmitEditing={() => weightRef.current?.focus()}
/>
<TextInput
style={styles.text}
label="Weight *"
keyboardType="numeric"
value={weight}
onChangeText={setWeight}
onSubmitEditing={save}
ref={weightRef}
/>
<TextInput
style={styles.text}
label="Unit (kg)"
value={unit}
onChangeText={setUnit}
ref={unitRef}
onSubmitEditing={save}
/>
<Text style={styles.text}>{format(created, 'PPPP p')}</Text>
</Dialog.Content>
<Dialog.Actions>
<Button icon="close" onPress={() => setShow(false)}>
Cancel
</Button>
{id && (
<Button icon="copy" onPress={clearId}>
Duplicate
</Button>
)}
</View>
</Modal>
<Button mode="contained" icon="save" onPress={save}>
Save
</Button>
</Dialog.Actions>
</Dialog>
</Portal>
);
}
const styles = StyleSheet.create({
modal: {
backgroundColor: 'black',
padding: 20,
},
text: {
marginBottom: 10,
},
bottom: {
flexDirection: 'row',
},
title: {
fontSize: 20,
marginBottom: 10,

View File

@ -86,15 +86,13 @@ export default function Home() {
refreshing={refreshing}
onRefresh={refresh}
/>
<View style={styles.bottom}>
<EditSet
clearId={() => setId(undefined)}
id={id}
show={showEdit}
setShow={setShowEdit}
onSave={save}
/>
</View>
<EditSet
clearId={() => setId(undefined)}
id={id}
show={showEdit}
setShow={setShowEdit}
onSave={save}
/>
<AnimatedFAB
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 {RootStackParamList} from './App';
import {getDb} from './db';
import BatteryDialog from './BatteryDialog';
export default function Settings({
navigation,
@ -13,6 +14,7 @@ export default function Settings({
const [seconds, setSeconds] = useState<string>('');
const [alarmEnabled, setAlarmEnabled] = useState<boolean>(true);
const [snackbar, setSnackbar] = useState('');
const [showBattery, setShowBattery] = useState(false);
useEffect(() => {
(async () => {
@ -43,6 +45,16 @@ export default function Settings({
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 (
<View style={styles.container}>
<TextInput
@ -65,7 +77,7 @@ export default function Settings({
<Switch
style={[styles.text, {alignSelf: 'flex-start'}]}
value={alarmEnabled}
onValueChange={setAlarmEnabled}
onValueChange={changeAlarmEnabled}
/>
<Button
style={{alignSelf: 'flex-start'}}
@ -86,6 +98,8 @@ export default function Settings({
Delete all data
</Button>
<BatteryDialog show={showBattery} setShow={setShowBattery} />
<Snackbar
visible={!!snackbar}
onDismiss={() => setSnackbar('')}

View File

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

View File

@ -1,13 +1,19 @@
package com.massive
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import android.util.Log
import androidx.annotation.RequiresApi
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
class AlarmModule internal constructor(context: ReactApplicationContext?) :
ReactContextBaseJavaModule(context) {
@ -23,4 +29,26 @@ class AlarmModule internal constructor(context: ReactApplicationContext?) :
intent.putExtra("milliseconds", milliseconds)
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-sqlite-storage';
import React from 'react';
import {AppRegistry} from 'react-native';
import App from './App';
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() {
return (
<PaperProvider
theme={DarkTheme}
settings={{icon: props => <Ionicon {...props} />}}>
<App />
</PaperProvider>
);
}
AppRegistry.registerComponent(appName, () => Main);
AppRegistry.registerComponent(appName, () => App);