Add basic CRUD for sets
This commit is contained in:
parent
73ce91f111
commit
bb9a6c5f37
86
Alarm.tsx
Normal file
86
Alarm.tsx
Normal file
|
@ -0,0 +1,86 @@
|
|||
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';
|
||||
|
||||
export default function Alarm({onClose}: {onClose: () => void}) {
|
||||
const [seconds, setSeconds] = useState(0);
|
||||
const [minutes, setMinutes] = useState(0);
|
||||
let intervalId: number;
|
||||
|
||||
useEffect(() => {
|
||||
AsyncStorage.getItem('nextAlarm').then(async next => {
|
||||
if (!next) return;
|
||||
const ms = new Date(next).getTime() - new Date().getTime();
|
||||
if (ms <= 0) return;
|
||||
let secondsLeft = ms / 1000;
|
||||
console.log({secondsLeft});
|
||||
setSeconds(secondsLeft % 60);
|
||||
setMinutes(Math.floor(secondsLeft / 60));
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
console.log({seconds, secondsLeft});
|
||||
secondsLeft--;
|
||||
if (secondsLeft <= 0) return clearInterval(intervalId);
|
||||
setSeconds(Math.ceil(secondsLeft % 60));
|
||||
setMinutes(Math.floor(secondsLeft / 60));
|
||||
}, 1000);
|
||||
});
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
const stop = () => {
|
||||
BackgroundTimer.clearInterval(intervalId);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
animationType="none"
|
||||
transparent={true}
|
||||
visible
|
||||
onRequestClose={onClose}>
|
||||
<View style={styles.modal}>
|
||||
<Text style={styles.title}>Rest</Text>
|
||||
<Text style={styles.timer}>
|
||||
{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>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
timer: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
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,
|
||||
},
|
||||
});
|
104
App.tsx
104
App.tsx
|
@ -1,65 +1,61 @@
|
|||
import React, {useState} from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
|
||||
import {
|
||||
Button,
|
||||
SafeAreaView,
|
||||
StatusBar,
|
||||
TextInput,
|
||||
useColorScheme,
|
||||
Vibration,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import BackgroundTimer from 'react-native-background-timer';
|
||||
import {Notifications} from 'react-native-notifications';
|
||||
import Sound from 'react-native-sound';
|
||||
DarkTheme,
|
||||
DefaultTheme,
|
||||
NavigationContainer,
|
||||
} from '@react-navigation/native';
|
||||
import React, {useEffect} from 'react';
|
||||
import {StatusBar, useColorScheme} from 'react-native';
|
||||
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>();
|
||||
export type RootStackParamList = {
|
||||
Home: {};
|
||||
Exercises: {};
|
||||
Settings: {};
|
||||
Alarm: {};
|
||||
};
|
||||
|
||||
setupSchema();
|
||||
|
||||
const App = () => {
|
||||
const dark = useColorScheme() === 'dark';
|
||||
const alarm = new Sound('argon.mp3', Sound.MAIN_BUNDLE, error => {
|
||||
if (error) throw new Error(error);
|
||||
});
|
||||
const [timer, setTimer] = useState('0');
|
||||
|
||||
Notifications.registerRemoteNotifications();
|
||||
Notifications.events().registerNotificationOpened(
|
||||
(notification, completion) => {
|
||||
console.log('Notification opened:', notification);
|
||||
alarm.stop();
|
||||
Vibration.cancel();
|
||||
completion();
|
||||
},
|
||||
);
|
||||
|
||||
const press = () => {
|
||||
BackgroundTimer.setTimeout(() => {
|
||||
alarm.play(_onEnd => Vibration.cancel());
|
||||
Vibration.vibrate([0, 400, 600], /*repeat=*/ true);
|
||||
Notifications.postLocalNotification({
|
||||
title: 'title',
|
||||
body: 'body',
|
||||
badge: 1,
|
||||
identifier: 'identifier',
|
||||
payload: {},
|
||||
sound: 'sound',
|
||||
thread: 'thread',
|
||||
type: 'type',
|
||||
});
|
||||
}, Number(timer));
|
||||
};
|
||||
useEffect(() => {
|
||||
AsyncStorage.getItem('minutes').then(async minutes => {
|
||||
if (!minutes) await AsyncStorage.setItem('minutes', '3');
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{flex: 1}}>
|
||||
<NavigationContainer theme={dark ? DarkTheme : DefaultTheme}>
|
||||
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
|
||||
<View
|
||||
style={{
|
||||
margin: 10,
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<TextInput placeholder="Timer" value={timer} onChangeText={setTimer} />
|
||||
</View>
|
||||
<View style={{margin: 30, marginTop: 'auto'}}>
|
||||
<Button title="Run timer" onPress={press} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
<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.Screen name="Home" component={Home} />
|
||||
<Tab.Screen name="Exercises" component={Exercises} />
|
||||
<Tab.Screen name="Settings" component={Settings} />
|
||||
</Tab.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
152
EditSet.tsx
Normal file
152
EditSet.tsx
Normal file
|
@ -0,0 +1,152 @@
|
|||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {Modal, StyleSheet, Text, TextInput, View} from 'react-native';
|
||||
import {Button} from 'react-native-paper';
|
||||
import {getDb} from './db';
|
||||
import Set from './Set';
|
||||
|
||||
export default function EditSet({
|
||||
id,
|
||||
onSave,
|
||||
show,
|
||||
setShow,
|
||||
setId,
|
||||
}: {
|
||||
id?: number;
|
||||
setId: (id?: number) => void;
|
||||
onSave: () => void;
|
||||
show: boolean;
|
||||
setShow: (visible: boolean) => void;
|
||||
}) {
|
||||
const [name, setName] = useState('');
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
getDb().then(async db => {
|
||||
const [result] = await db.executeSql(`SELECT * FROM sets WHERE id = ?`, [
|
||||
id,
|
||||
]);
|
||||
if (!result.rows.item(0)) throw new Error("Can't find specified Set.");
|
||||
const set: Set = result.rows.item(0);
|
||||
setName(set.name);
|
||||
setReps(set.reps.toString());
|
||||
setWeight(set.weight.toString());
|
||||
setUnit(set.unit);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const save = async () => {
|
||||
if (!name || !reps || !weight) return;
|
||||
const db = await getDb();
|
||||
if (!id)
|
||||
await db.executeSql(
|
||||
`INSERT INTO sets(name, reps, weight, created, unit) VALUES (?,?,?,?,?)`,
|
||||
[name, reps, weight, new Date().toISOString(), unit || 'kg'],
|
||||
);
|
||||
else
|
||||
await db.executeSql(
|
||||
`UPDATE sets SET name = ?, reps = ?, weight = ?, unit = ? WHERE id = ?`,
|
||||
[name, reps, weight, unit, id],
|
||||
);
|
||||
setShow(false);
|
||||
onSave();
|
||||
};
|
||||
|
||||
const remove = async () => {
|
||||
if (!id) return;
|
||||
const db = await getDb();
|
||||
await db.executeSql(`DELETE FROM sets WHERE id = ?`, [id]);
|
||||
setShow(false);
|
||||
onSave();
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Modal
|
||||
animationType="none"
|
||||
transparent={true}
|
||||
visible={show}
|
||||
onRequestClose={() => setShow(false)}>
|
||||
<View style={styles.modal}>
|
||||
<Text style={styles.title}>Add a set</Text>
|
||||
<TextInput
|
||||
autoFocus
|
||||
placeholder="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 *"
|
||||
keyboardType="numeric"
|
||||
value={reps}
|
||||
onChangeText={setReps}
|
||||
ref={repsRef}
|
||||
onSubmitEditing={() => unitRef.current?.focus()}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Unit (kg)"
|
||||
value={unit}
|
||||
onChangeText={setUnit}
|
||||
ref={unitRef}
|
||||
onSubmitEditing={save}
|
||||
/>
|
||||
<View style={styles.bottom}>
|
||||
<Button mode="contained" icon="save" onPress={save}>
|
||||
Save
|
||||
</Button>
|
||||
<Button icon="close" onPress={() => setShow(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button icon="trash" onPress={remove} disabled={!id}>
|
||||
Delete
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setId(undefined);
|
||||
setShow(true);
|
||||
}}>
|
||||
Add
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
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,
|
||||
},
|
||||
});
|
15
Exercises.tsx
Normal file
15
Exercises.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import {NativeStackScreenProps} from '@react-navigation/native-stack';
|
||||
import {Button, Text, View} from 'react-native';
|
||||
import {RootStackParamList} from './App';
|
||||
import React from 'react';
|
||||
|
||||
export default function Exercises({
|
||||
navigation,
|
||||
}: NativeStackScreenProps<RootStackParamList, 'Exercises'>) {
|
||||
return (
|
||||
<View>
|
||||
<Text>Pull ups - 1 rep</Text>
|
||||
<Button title="Go home" onPress={() => navigation.navigate('Home', {})} />
|
||||
</View>
|
||||
);
|
||||
}
|
188
Home.tsx
Normal file
188
Home.tsx
Normal file
|
@ -0,0 +1,188 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
FlatList,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
TextInput,
|
||||
Vibration,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import BackgroundTimer from 'react-native-background-timer';
|
||||
import {Button, List} from 'react-native-paper';
|
||||
import PushNotification from 'react-native-push-notification';
|
||||
import Sound from 'react-native-sound';
|
||||
import Alarm from './Alarm';
|
||||
import {RootStackParamList} from './App';
|
||||
import {ALARM} from './channels';
|
||||
import {getDb} from './db';
|
||||
import EditSet from './EditSet';
|
||||
|
||||
import Set from './Set';
|
||||
|
||||
const limit = 20;
|
||||
|
||||
export default function Home({
|
||||
navigation,
|
||||
}: NativeStackScreenProps<RootStackParamList, '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 refresh = async () => {
|
||||
setRefreshing(true);
|
||||
const db = await getDb();
|
||||
const [result] = await db.executeSql(
|
||||
`SELECT * from sets WHERE name LIKE ? LIMIT ? OFFSET ?`,
|
||||
[`%${search}%`, limit, 0],
|
||||
);
|
||||
setRefreshing(false);
|
||||
if (!result) return setSets([]);
|
||||
setSets(result.rows.raw());
|
||||
setOffset(0);
|
||||
};
|
||||
|
||||
const alarm = new Sound('argon.mp3', Sound.MAIN_BUNDLE, error => {
|
||||
if (error) throw new Error(error);
|
||||
});
|
||||
|
||||
const focus = async () => {
|
||||
alarm.stop();
|
||||
Vibration.cancel();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [search]);
|
||||
|
||||
useEffect(() => navigation.addListener('focus', focus), [navigation]);
|
||||
|
||||
const renderItem = ({item}: {item: Set}) => (
|
||||
<List.Item
|
||||
onPress={() => {
|
||||
setId(item.id);
|
||||
setShowEdit(true);
|
||||
}}
|
||||
key={item.id}
|
||||
title={item.name}
|
||||
description={`${item.reps} x ${item.weight}${item.unit}`}
|
||||
/>
|
||||
);
|
||||
|
||||
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');
|
||||
const milliseconds = Number(minutes) * 60 * 1000 + Number(seconds) * 1000;
|
||||
const when = new Date();
|
||||
when.setTime(when.getTime() + milliseconds);
|
||||
await AsyncStorage.setItem('nextAlarm', when.toISOString());
|
||||
const timeoutId = BackgroundTimer.setTimeout(() => {
|
||||
alarm.play(_onEnd => Vibration.cancel());
|
||||
Vibration.vibrate([0, 400, 600], /*repeat=*/ true);
|
||||
PushNotification.localNotification({
|
||||
message: 'Timer up',
|
||||
channelId: ALARM,
|
||||
vibrate: true,
|
||||
});
|
||||
}, Number(milliseconds));
|
||||
BackgroundTimer.clearTimeout(
|
||||
Number(await AsyncStorage.getItem('timeoutId')),
|
||||
);
|
||||
await AsyncStorage.setItem('timeoutId', timeoutId.toString());
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
alarm.stop();
|
||||
Vibration.cancel();
|
||||
setShowTimer(false);
|
||||
};
|
||||
|
||||
const next = async () => {
|
||||
const newOffset = offset + limit;
|
||||
setRefreshing(true);
|
||||
const db = await getDb();
|
||||
const [result] = await db.executeSql(
|
||||
`SELECT * from sets WHERE name LIKE ? LIMIT ? OFFSET ?`,
|
||||
[`%${search}%`, limit, newOffset],
|
||||
);
|
||||
setRefreshing(false);
|
||||
if (!result) return;
|
||||
if (!sets) return;
|
||||
setSets([...sets, ...result.rows.raw()]);
|
||||
setOffset(newOffset);
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<TextInput placeholder="Search" value={search} onChangeText={setSearch} />
|
||||
<FlatList
|
||||
style={{height: '100%'}}
|
||||
data={sets}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={set => set.id.toString()}
|
||||
onRefresh={refresh}
|
||||
refreshing={refreshing}
|
||||
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}>
|
||||
<EditSet
|
||||
id={id}
|
||||
setId={setId}
|
||||
show={showEdit}
|
||||
setShow={setShowEdit}
|
||||
onSave={save}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{showTimer && <Alarm onClose={close} />}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
name: {
|
||||
fontSize: 18,
|
||||
},
|
||||
button: {
|
||||
marginRight: 10,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingLeft: 20,
|
||||
paddingRight: 20,
|
||||
},
|
||||
bottom: {
|
||||
alignSelf: 'center',
|
||||
marginBottom: 10,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
set: {
|
||||
marginBottom: 10,
|
||||
fontSize: 18,
|
||||
shadowColor: 'red',
|
||||
shadowRadius: 10,
|
||||
shadowOffset: {width: 2, height: 40},
|
||||
shadowOpacity: 8,
|
||||
},
|
||||
});
|
8
Set.ts
Normal file
8
Set.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default interface Set {
|
||||
id: number;
|
||||
name: string;
|
||||
reps: number;
|
||||
weight: number;
|
||||
created: string;
|
||||
unit: string;
|
||||
}
|
69
Settings.tsx
Normal file
69
Settings.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
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 {RootStackParamList} from './App';
|
||||
import {getDb} from './db';
|
||||
|
||||
export default function Settings({
|
||||
navigation,
|
||||
}: NativeStackScreenProps<RootStackParamList, 'Settings'>) {
|
||||
const [minutes, setMinutes] = useState<string>('');
|
||||
const [seconds, setSeconds] = useState<string>('');
|
||||
const [alarmEnabled, setAlarmEnabled] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setMinutes((await AsyncStorage.getItem('minutes')) || '3');
|
||||
setSeconds((await AsyncStorage.getItem('seconds')) || '');
|
||||
setAlarmEnabled((await AsyncStorage.getItem('alarmEnabled')) === 'true');
|
||||
})();
|
||||
}, [navigation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (minutes) AsyncStorage.setItem('minutes', minutes);
|
||||
if (seconds) AsyncStorage.setItem('seconds', seconds);
|
||||
AsyncStorage.setItem('alarmEnabled', alarmEnabled ? 'true' : 'false');
|
||||
}, [minutes, seconds, alarmEnabled]);
|
||||
|
||||
const clear = async () => {
|
||||
const db = await getDb();
|
||||
await db.executeSql(`DELETE FROM sets`);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Rest minutes</Text>
|
||||
<TextInput
|
||||
value={minutes}
|
||||
keyboardType="numeric"
|
||||
placeholder="3"
|
||||
onChangeText={setMinutes}
|
||||
/>
|
||||
<Text>Rest seconds</Text>
|
||||
<TextInput
|
||||
value={seconds}
|
||||
keyboardType="numeric"
|
||||
placeholder="30"
|
||||
onChangeText={setSeconds}
|
||||
/>
|
||||
<Text>Alarm enabled?</Text>
|
||||
<Switch
|
||||
style={{alignSelf: 'flex-start'}}
|
||||
value={alarmEnabled}
|
||||
onValueChange={setAlarmEnabled}
|
||||
/>
|
||||
<Button icon="trash" onPress={clear}>
|
||||
Clear sets
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 10,
|
||||
},
|
||||
});
|
|
@ -82,6 +82,7 @@ project.ext.react = [
|
|||
]
|
||||
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
|
||||
|
||||
/**
|
||||
* Set this to true to create two separate APKs instead of one:
|
||||
|
@ -129,6 +130,12 @@ def reactNativeArchitectures() {
|
|||
}
|
||||
|
||||
android {
|
||||
packagingOptions {
|
||||
pickFirst '**/armeabi-v7a/libfolly_runtime.so'
|
||||
pickFirst '**/x86/libfolly_runtime.so'
|
||||
pickFirst '**/arm64-v8a/libfolly_runtime.so'
|
||||
pickFirst '**/x86_64/libfolly_runtime.so'
|
||||
}
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
@ -262,7 +269,7 @@ dependencies {
|
|||
//noinspection GradleDynamicVersion
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
|
||||
implementation project(':react-native-notifications');
|
||||
implementation project(':react-native-sqlite-storage')
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
package="com.massive">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
|
@ -10,6 +11,7 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/AppTheme">
|
||||
<meta-data android:name="com.dieam.reactnativepushnotification.notification_foreground" android:value="false"/>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
|
|
|
@ -12,7 +12,7 @@ import com.facebook.soloader.SoLoader;
|
|||
import com.massive.newarchitecture.MainApplicationReactNativeHost;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import com.wix.reactnativenotifications.RNNotificationsPackage;
|
||||
import org.pgsqlite.SQLitePluginPackage;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
|
@ -27,9 +27,7 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
protected List<ReactPackage> getPackages() {
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
List<ReactPackage> packages = new PackageList(this).getPackages();
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// packages.add(new MyReactNativePackage());
|
||||
packages.add(new RNNotificationsPackage(MainApplication.this));
|
||||
packages.add(new SQLitePluginPackage());
|
||||
return packages;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ rootProject.name = 'massive'
|
|||
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||
include ':app'
|
||||
includeBuild('../node_modules/react-native-gradle-plugin')
|
||||
include ':react-native-notifications'
|
||||
project(':react-native-notifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/lib/android/app')
|
||||
include ':react-native-sqlite-storage'
|
||||
project(':react-native-sqlite-storage').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sqlite-storage/platforms/android')
|
||||
|
||||
if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") {
|
||||
include(":ReactAndroid")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: ['react-native-reanimated/plugin', 'react-native-paper/babel'],
|
||||
};
|
||||
|
|
13
channels.ts
Normal file
13
channels.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import PushNotification, {Importance} from 'react-native-push-notification';
|
||||
|
||||
export const ALARM = 'alarm';
|
||||
PushNotification.createChannel(
|
||||
{
|
||||
channelId: ALARM,
|
||||
channelName: 'Alarms',
|
||||
channelDescription: 'Notifications of when alarms are triggered.',
|
||||
importance: Importance.HIGH,
|
||||
vibrate: false,
|
||||
},
|
||||
created => console.log(`Created channel ${created}`),
|
||||
);
|
17
db.ts
Normal file
17
db.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {enablePromise, openDatabase} from 'react-native-sqlite-storage';
|
||||
|
||||
enablePromise(true);
|
||||
export const getDb = () => openDatabase({name: 'massive.db'});
|
||||
|
||||
const schema = `
|
||||
CREATE TABLE IF NOT EXISTS sets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
reps INTEGER NOT NULL,
|
||||
weight INTEGER NOT NULL,
|
||||
created TEXT NOT NULL,
|
||||
unit TEXT DEFAULT 'kg'
|
||||
);
|
||||
`;
|
||||
|
||||
export const setupSchema = () => getDb().then(db => db.executeSql(schema));
|
44
index.js
44
index.js
|
@ -1,9 +1,43 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
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 PushNotification from 'react-native-push-notification';
|
||||
import {Provider as PaperProvider, DarkTheme} from 'react-native-paper';
|
||||
import Ionicon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App);
|
||||
export default function Main() {
|
||||
return (
|
||||
<PaperProvider
|
||||
theme={DarkTheme}
|
||||
settings={{icon: props => <Ionicon {...props} />}}>
|
||||
<App />
|
||||
</PaperProvider>
|
||||
);
|
||||
}
|
||||
|
||||
AppRegistry.registerComponent(appName, () => Main);
|
||||
|
||||
PushNotification.configure({
|
||||
onRegister: function (token) {
|
||||
console.log('TOKEN:', token);
|
||||
},
|
||||
|
||||
onNotification: function (notification) {
|
||||
console.log('NOTIFICATION:', notification);
|
||||
},
|
||||
|
||||
onAction: function (notification) {
|
||||
console.log('ACTION:', notification.action);
|
||||
console.log('NOTIFICATION:', notification);
|
||||
},
|
||||
|
||||
onRegistrationError: function (err) {
|
||||
console.error(err.message, err);
|
||||
},
|
||||
|
||||
popInitialNotification: true,
|
||||
requestPermissions: false,
|
||||
});
|
||||
|
|
21
package.json
21
package.json
|
@ -10,12 +10,29 @@
|
|||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.1.6",
|
||||
"@react-native-async-storage/async-storage": "^1.17.7",
|
||||
"@react-native-community/push-notification-ios": "^1.10.1",
|
||||
"@react-navigation/bottom-tabs": "^6.3.1",
|
||||
"@react-navigation/native": "^6.0.10",
|
||||
"@react-navigation/native-stack": "^6.6.2",
|
||||
"@types/react-native-background-timer": "^2.0.0",
|
||||
"@types/react-native-push-notification": "^8.1.1",
|
||||
"@types/react-native-sqlite-storage": "^5.0.2",
|
||||
"@types/react-native-vector-icons": "^6.4.11",
|
||||
"react": "18.0.0",
|
||||
"react-devtools": "^4.24.7",
|
||||
"react-native": "0.69.1",
|
||||
"react-native-background-timer": "^2.4.1",
|
||||
"react-native-notifications": "^4.3.1",
|
||||
"react-native-sound": "^0.11.2"
|
||||
"react-native-gesture-handler": "^2.5.0",
|
||||
"react-native-paper": "^4.12.2",
|
||||
"react-native-push-notification": "^8.1.1",
|
||||
"react-native-reanimated": "^2.9.0",
|
||||
"react-native-safe-area-context": "^4.3.1",
|
||||
"react-native-screens": "^3.14.0",
|
||||
"react-native-sound": "^0.11.2",
|
||||
"react-native-sqlite-storage": "^6.0.1",
|
||||
"react-native-vector-icons": "^9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
|
|
Loading…
Reference in New Issue
Block a user