Move to top bar navigation
This commit is contained in:
parent
00a801b44a
commit
5a1257a859
79
Alarm.tsx
79
Alarm.tsx
|
@ -1,86 +1,59 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Modal, StyleSheet, Text, View} from 'react-native';
|
||||
import BackgroundTimer from 'react-native-background-timer';
|
||||
import {StyleSheet, Text} from 'react-native';
|
||||
import {Button, Modal, Portal} from 'react-native-paper';
|
||||
|
||||
export default function Alarm({onClose}: {onClose: () => void}) {
|
||||
export default function Alarm() {
|
||||
const [show, setShow] = useState(false);
|
||||
const [seconds, setSeconds] = useState(0);
|
||||
const [minutes, setMinutes] = useState(0);
|
||||
let intervalId: number;
|
||||
|
||||
useEffect(() => {
|
||||
AsyncStorage.getItem('nextAlarm').then(async next => {
|
||||
intervalId = setInterval(async () => {
|
||||
const next = await AsyncStorage.getItem('nextAlarm');
|
||||
if (!next) return;
|
||||
const ms = new Date(next).getTime() - new Date().getTime();
|
||||
if (ms <= 0) return;
|
||||
let secondsLeft = ms / 1000;
|
||||
console.log({secondsLeft});
|
||||
setSeconds(Math.floor(secondsLeft % 60));
|
||||
setMinutes(Math.floor(secondsLeft / 60));
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
console.log({seconds, secondsLeft});
|
||||
secondsLeft--;
|
||||
if (secondsLeft <= 0) return clearInterval(intervalId);
|
||||
setSeconds(Math.floor(secondsLeft % 60));
|
||||
setMinutes(Math.floor(secondsLeft / 60));
|
||||
}, 1000);
|
||||
});
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
const stop = () => {
|
||||
BackgroundTimer.clearInterval(intervalId);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Portal>
|
||||
<Modal
|
||||
animationType="none"
|
||||
transparent={true}
|
||||
visible
|
||||
onRequestClose={onClose}>
|
||||
<View style={styles.modal}>
|
||||
<Text style={styles.title}>Rest</Text>
|
||||
<Text style={styles.timer}>
|
||||
visible={show}
|
||||
style={styles.center}
|
||||
onDismiss={() => setShow(false)}>
|
||||
<Text style={[styles.center, styles.title]}>Resting</Text>
|
||||
<Text style={styles.center}>
|
||||
{minutes}:{seconds}
|
||||
</Text>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<View style={styles.button}>
|
||||
<Button title="Close" color="#014B44" onPress={onClose} />
|
||||
</View>
|
||||
<View style={styles.button}>
|
||||
<Button title="Stop" onPress={stop} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<Button mode="contained" onPress={() => setShow(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</Modal>
|
||||
</Portal>
|
||||
<Button icon="time" onPress={() => setShow(true)}>
|
||||
Time left
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
timer: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
center: {
|
||||
alignItems: 'center',
|
||||
alignSelf: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
modal: {
|
||||
margin: 20,
|
||||
backgroundColor: '#20232a',
|
||||
padding: 20,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 4,
|
||||
elevation: 5,
|
||||
alignItems: 'center',
|
||||
},
|
||||
button: {
|
||||
marginRight: 10,
|
||||
title: {
|
||||
fontSize: 18,
|
||||
},
|
||||
});
|
||||
|
|
25
App.tsx
25
App.tsx
|
@ -1,5 +1,5 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
|
||||
import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs';
|
||||
import {
|
||||
DarkTheme,
|
||||
DefaultTheme,
|
||||
|
@ -11,14 +11,12 @@ import {setupSchema} from './db';
|
|||
import Exercises from './Exercises';
|
||||
import Home from './Home';
|
||||
import Settings from './Settings';
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
const Tab = createBottomTabNavigator<RootStackParamList>();
|
||||
const Tab = createMaterialTopTabNavigator<RootStackParamList>();
|
||||
export type RootStackParamList = {
|
||||
Home: {};
|
||||
Exercises: {};
|
||||
Settings: {};
|
||||
Alarm: {};
|
||||
Exercises: {};
|
||||
};
|
||||
|
||||
setupSchema();
|
||||
|
@ -35,22 +33,7 @@ const App = () => {
|
|||
return (
|
||||
<NavigationContainer theme={dark ? DarkTheme : DefaultTheme}>
|
||||
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
|
||||
<Tab.Navigator
|
||||
screenOptions={({route}) => ({
|
||||
tabBarIcon: ({focused, color, size}) => {
|
||||
let icon = '';
|
||||
|
||||
if (route.name === 'Home') icon = focused ? 'home' : 'home-outline';
|
||||
else if (route.name === 'Settings')
|
||||
icon = focused ? 'settings' : 'settings-outline';
|
||||
else if (route.name === 'Exercises')
|
||||
icon = focused ? 'barbell' : 'barbell-outline';
|
||||
// You can return any component that you like here!
|
||||
return <Ionicons name={icon} size={size} color={color} />;
|
||||
},
|
||||
tabBarActiveTintColor: 'tomato',
|
||||
tabBarInactiveTintColor: 'gray',
|
||||
})}>
|
||||
<Tab.Navigator>
|
||||
<Tab.Screen name="Home" component={Home} />
|
||||
<Tab.Screen name="Exercises" component={Exercises} />
|
||||
<Tab.Screen name="Settings" component={Settings} />
|
||||
|
|
77
EditSet.tsx
77
EditSet.tsx
|
@ -1,8 +1,8 @@
|
|||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {Modal, StyleSheet, Text, TextInput, View} from 'react-native';
|
||||
import {Button} from 'react-native-paper';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
import {Button, Modal, Portal, TextInput} from 'react-native-paper';
|
||||
import {getDb} from './db';
|
||||
import Set from './Set';
|
||||
import Set from './set';
|
||||
|
||||
export default function EditSet({
|
||||
id,
|
||||
|
@ -21,9 +21,9 @@ export default function EditSet({
|
|||
const [reps, setReps] = useState('');
|
||||
const [weight, setWeight] = useState('');
|
||||
const [unit, setUnit] = useState('');
|
||||
const weightRef = useRef<TextInput>(null);
|
||||
const repsRef = useRef<TextInput>(null);
|
||||
const unitRef = useRef<TextInput>(null);
|
||||
const weightRef = useRef<any>(null);
|
||||
const repsRef = useRef<any>(null);
|
||||
const unitRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
|
@ -66,39 +66,42 @@ export default function EditSet({
|
|||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<>
|
||||
<Portal>
|
||||
<Modal
|
||||
animationType="none"
|
||||
transparent={true}
|
||||
visible={show}
|
||||
onRequestClose={() => setShow(false)}>
|
||||
<View style={styles.modal}>
|
||||
contentContainerStyle={styles.modal}
|
||||
onDismiss={() => setShow(false)}>
|
||||
<Text style={styles.title}>Add a set</Text>
|
||||
<TextInput
|
||||
style={styles.text}
|
||||
autoFocus
|
||||
placeholder="Name *"
|
||||
label="Name *"
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
onSubmitEditing={() => weightRef.current?.focus()}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Weight *"
|
||||
keyboardType="numeric"
|
||||
value={weight}
|
||||
onChangeText={setWeight}
|
||||
onSubmitEditing={() => repsRef.current?.focus()}
|
||||
ref={weightRef}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Reps *"
|
||||
style={styles.text}
|
||||
label="Reps *"
|
||||
keyboardType="numeric"
|
||||
value={reps}
|
||||
onChangeText={setReps}
|
||||
ref={repsRef}
|
||||
onSubmitEditing={() => unitRef.current?.focus()}
|
||||
onSubmitEditing={() => weightRef.current?.focus()}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Unit (kg)"
|
||||
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}
|
||||
|
@ -115,9 +118,11 @@ export default function EditSet({
|
|||
Delete
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</Portal>
|
||||
|
||||
<Button
|
||||
icon="add"
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setId(undefined);
|
||||
|
@ -125,28 +130,24 @@ export default function EditSet({
|
|||
}}>
|
||||
Add
|
||||
</Button>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modal: {
|
||||
height: '100%',
|
||||
backgroundColor: 'black',
|
||||
padding: 20,
|
||||
},
|
||||
text: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
bottom: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
},
|
||||
modal: {
|
||||
margin: 20,
|
||||
backgroundColor: '#20232a',
|
||||
padding: 20,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 4,
|
||||
elevation: 5,
|
||||
marginBottom: 10,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,15 +1,61 @@
|
|||
import {NativeStackScreenProps} from '@react-navigation/native-stack';
|
||||
import {Button, Text, View} from 'react-native';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {FlatList, StyleSheet, View} from 'react-native';
|
||||
import {List, TextInput} from 'react-native-paper';
|
||||
import {RootStackParamList} from './App';
|
||||
import React from 'react';
|
||||
import {getDb} from './db';
|
||||
import Exercise from './exercise';
|
||||
|
||||
export default function Exercises({
|
||||
navigation,
|
||||
}: NativeStackScreenProps<RootStackParamList, 'Exercises'>) {
|
||||
const [exercises, setExercises] = useState<Exercise[]>([]);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const refresh = async () => {
|
||||
setRefreshing(true);
|
||||
const db = await getDb();
|
||||
const [result] = await db.executeSql(
|
||||
`SELECT name, reps, unit, MAX(weight) AS weight
|
||||
FROM sets
|
||||
WHERE name LIKE ?
|
||||
GROUP BY name;`,
|
||||
[`%${search}%`],
|
||||
);
|
||||
setRefreshing(false);
|
||||
if (!result) return setExercises([]);
|
||||
setExercises(result.rows.raw());
|
||||
};
|
||||
|
||||
useEffect(() => navigation.addListener('focus', refresh), [navigation]);
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [search]);
|
||||
|
||||
const renderItem = ({item}: {item: Exercise}) => (
|
||||
<List.Item
|
||||
key={item.name}
|
||||
title={item.name}
|
||||
description={`Best: ${item.reps} x ${item.weight}${item.unit}`}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text>Pull ups - 1 rep</Text>
|
||||
<Button title="Go home" onPress={() => navigation.navigate('Home', {})} />
|
||||
<View style={styles.container}>
|
||||
<TextInput label="Search" value={search} onChangeText={setSearch} />
|
||||
<FlatList
|
||||
onRefresh={refresh}
|
||||
refreshing={refreshing}
|
||||
renderItem={renderItem}
|
||||
data={exercises}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
padding: 10,
|
||||
},
|
||||
});
|
||||
|
|
55
Home.tsx
55
Home.tsx
|
@ -1,42 +1,38 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
FlatList,
|
||||
NativeModules,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
TextInput,
|
||||
Vibration,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {Button, List} from 'react-native-paper';
|
||||
import {List, TextInput} from 'react-native-paper';
|
||||
import Sound from 'react-native-sound';
|
||||
import Alarm from './Alarm';
|
||||
import {RootStackParamList} from './App';
|
||||
import {getDb} from './db';
|
||||
import EditSet from './EditSet';
|
||||
|
||||
import Set from './Set';
|
||||
import Set from './set';
|
||||
|
||||
const limit = 20;
|
||||
|
||||
export default function Home({
|
||||
navigation,
|
||||
}: NativeStackScreenProps<RootStackParamList, 'Home'>) {
|
||||
export default function Home() {
|
||||
const [sets, setSets] = useState<Set[]>();
|
||||
const [id, setId] = useState<number>();
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [showTimer, setShowTimer] = useState(false);
|
||||
const [showEdit, setShowEdit] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const navigation = useNavigation();
|
||||
|
||||
const refresh = async () => {
|
||||
setRefreshing(true);
|
||||
const db = await getDb();
|
||||
const [result] = await db.executeSql(
|
||||
`SELECT * from sets WHERE name LIKE ? LIMIT ? OFFSET ?`,
|
||||
`SELECT * from sets WHERE name LIKE ? ORDER BY created DESC LIMIT ? OFFSET ?`,
|
||||
[`%${search}%`, limit, 0],
|
||||
);
|
||||
setRefreshing(false);
|
||||
|
@ -75,7 +71,6 @@ export default function Home({
|
|||
const save = async () => {
|
||||
refresh();
|
||||
const enabled = await AsyncStorage.getItem('alarmEnabled');
|
||||
console.log({enabled});
|
||||
if (enabled !== 'true') return;
|
||||
const minutes = await AsyncStorage.getItem('minutes');
|
||||
const seconds = await AsyncStorage.getItem('seconds');
|
||||
|
@ -86,12 +81,6 @@ export default function Home({
|
|||
await AsyncStorage.setItem('nextAlarm', when.toISOString());
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
alarm.stop();
|
||||
Vibration.cancel();
|
||||
setShowTimer(false);
|
||||
};
|
||||
|
||||
const next = async () => {
|
||||
const newOffset = offset + limit;
|
||||
setRefreshing(true);
|
||||
|
@ -109,7 +98,7 @@ export default function Home({
|
|||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<TextInput placeholder="Search" value={search} onChangeText={setSearch} />
|
||||
<TextInput label="Search" value={search} onChangeText={setSearch} />
|
||||
<FlatList
|
||||
style={{height: '100%'}}
|
||||
data={sets}
|
||||
|
@ -120,18 +109,7 @@ export default function Home({
|
|||
onScrollEndDrag={next}
|
||||
/>
|
||||
<View style={styles.bottom}>
|
||||
<View style={styles.button}></View>
|
||||
<View style={styles.button}>
|
||||
<Button icon="time" onPress={() => setShowTimer(true)}>
|
||||
Time left
|
||||
</Button>
|
||||
</View>
|
||||
<View style={styles.button}>
|
||||
<Button icon="stop" onPress={close}>
|
||||
Stop
|
||||
</Button>
|
||||
</View>
|
||||
<View style={styles.button}>
|
||||
<Alarm />
|
||||
<EditSet
|
||||
id={id}
|
||||
setId={setId}
|
||||
|
@ -140,8 +118,6 @@ export default function Home({
|
|||
onSave={save}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{showTimer && <Alarm onClose={close} />}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
@ -150,25 +126,12 @@ const styles = StyleSheet.create({
|
|||
name: {
|
||||
fontSize: 18,
|
||||
},
|
||||
button: {
|
||||
marginRight: 10,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
padding: 10,
|
||||
},
|
||||
bottom: {
|
||||
alignSelf: 'center',
|
||||
marginBottom: 10,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
set: {
|
||||
marginBottom: 10,
|
||||
fontSize: 18,
|
||||
shadowColor: 'red',
|
||||
shadowRadius: 10,
|
||||
shadowOffset: {width: 2, height: 40},
|
||||
shadowOpacity: 8,
|
||||
},
|
||||
});
|
||||
|
|
13
Settings.tsx
13
Settings.tsx
|
@ -1,8 +1,8 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {StyleSheet, Switch, Text, TextInput, View} from 'react-native';
|
||||
import {Button} from 'react-native-paper';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
import {Button, Switch, TextInput} from 'react-native-paper';
|
||||
import {RootStackParamList} from './App';
|
||||
import {getDb} from './db';
|
||||
|
||||
|
@ -34,27 +34,27 @@ export default function Settings({
|
|||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Rest minutes</Text>
|
||||
<TextInput
|
||||
label="Rest minutes"
|
||||
value={minutes}
|
||||
keyboardType="numeric"
|
||||
placeholder="3"
|
||||
onChangeText={setMinutes}
|
||||
/>
|
||||
<Text>Rest seconds</Text>
|
||||
<TextInput
|
||||
label="Rest seconds"
|
||||
value={seconds}
|
||||
keyboardType="numeric"
|
||||
placeholder="30"
|
||||
onChangeText={setSeconds}
|
||||
/>
|
||||
<Text>Alarm enabled?</Text>
|
||||
<Text style={{marginTop: 10, marginBottom: 10}}>Alarm enabled?</Text>
|
||||
<Switch
|
||||
style={{alignSelf: 'flex-start'}}
|
||||
value={alarmEnabled}
|
||||
onValueChange={setAlarmEnabled}
|
||||
/>
|
||||
<Button icon="trash" onPress={clear}>
|
||||
<Button style={{alignSelf: 'flex-start'}} icon="trash" onPress={clear}>
|
||||
Clear sets
|
||||
</Button>
|
||||
</View>
|
||||
|
@ -63,7 +63,6 @@ export default function Settings({
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
},
|
||||
});
|
||||
|
|
6
exercise.ts
Normal file
6
exercise.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export default interface Exercise {
|
||||
name: string;
|
||||
reps: number;
|
||||
weight: number;
|
||||
unit: string;
|
||||
}
|
4
index.js
4
index.js
|
@ -21,10 +21,6 @@ export default function Main() {
|
|||
AppRegistry.registerComponent(appName, () => Main);
|
||||
|
||||
PushNotification.configure({
|
||||
onRegister: function (token) {
|
||||
console.log('TOKEN:', token);
|
||||
},
|
||||
|
||||
onNotification: function (notification) {
|
||||
console.log('NOTIFICATION:', notification);
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue
Block a user