Switch some modals to dialogs and fix light mode
This commit is contained in:
parent
d938ec775a
commit
07fa6f7ab2
29
App.tsx
29
App.tsx
|
@ -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,14 +31,28 @@ 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 (
|
||||||
|
<Provider
|
||||||
|
theme={dark ? DarkThemePaper : DefaultThemePaper}
|
||||||
|
settings={{icon: props => <Ionicon {...props} />}}>
|
||||||
<NavigationContainer theme={dark ? DarkTheme : DefaultTheme}>
|
<NavigationContainer theme={dark ? DarkTheme : DefaultTheme}>
|
||||||
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
|
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
|
||||||
<Tab.Navigator>
|
<Tab.Navigator>
|
||||||
|
@ -42,6 +62,7 @@ const App = () => {
|
||||||
<Tab.Screen name="Settings" component={Settings} />
|
<Tab.Screen name="Settings" component={Settings} />
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
|
</Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
33
BatteryDialog.tsx
Normal file
33
BatteryDialog.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
42
EditPlan.tsx
42
EditPlan.tsx
|
@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
33
EditSet.tsx
33
EditSet.tsx
|
@ -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,11 +62,9 @@ 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)}>
|
|
||||||
<Text style={styles.title}>{id ? `Edit "${name}"` : 'Add a set'}</Text>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.text}
|
style={styles.text}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
@ -102,35 +100,24 @@ export default function EditSet({
|
||||||
onSubmitEditing={save}
|
onSubmitEditing={save}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.text}>{format(created, 'PPPP p')}</Text>
|
<Text style={styles.text}>{format(created, 'PPPP p')}</Text>
|
||||||
<View style={styles.bottom}>
|
</Dialog.Content>
|
||||||
<Button mode="contained" icon="save" onPress={save}>
|
<Dialog.Actions>
|
||||||
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>
|
||||||
</View>
|
</Dialog>
|
||||||
</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,
|
||||||
|
|
2
Home.tsx
2
Home.tsx
|
@ -86,7 +86,6 @@ 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}
|
||||||
|
@ -94,7 +93,6 @@ export default function Home() {
|
||||||
setShow={setShowEdit}
|
setShow={setShowEdit}
|
||||||
onSave={save}
|
onSave={save}
|
||||||
/>
|
/>
|
||||||
</View>
|
|
||||||
|
|
||||||
<AnimatedFAB
|
<AnimatedFAB
|
||||||
extended={false}
|
extended={false}
|
||||||
|
|
16
Settings.tsx
16
Settings.tsx
|
@ -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('')}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
17
index.js
17
index.js
|
@ -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);
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user