Add half completed Plans
This commit is contained in:
parent
e4cc893bfc
commit
43405269df
3
App.tsx
3
App.tsx
|
@ -10,6 +10,7 @@ import {StatusBar, useColorScheme} from 'react-native';
|
||||||
import {setupSchema} from './db';
|
import {setupSchema} from './db';
|
||||||
import Exercises from './Exercises';
|
import Exercises from './Exercises';
|
||||||
import Home from './Home';
|
import Home from './Home';
|
||||||
|
import Plans from './Plans';
|
||||||
import Settings from './Settings';
|
import Settings from './Settings';
|
||||||
|
|
||||||
const Tab = createMaterialTopTabNavigator<RootStackParamList>();
|
const Tab = createMaterialTopTabNavigator<RootStackParamList>();
|
||||||
|
@ -17,6 +18,7 @@ export type RootStackParamList = {
|
||||||
Home: {};
|
Home: {};
|
||||||
Settings: {};
|
Settings: {};
|
||||||
Exercises: {};
|
Exercises: {};
|
||||||
|
Plans: {};
|
||||||
};
|
};
|
||||||
|
|
||||||
setupSchema();
|
setupSchema();
|
||||||
|
@ -35,6 +37,7 @@ const App = () => {
|
||||||
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
|
<StatusBar barStyle={dark ? 'light-content' : 'dark-content'} />
|
||||||
<Tab.Navigator>
|
<Tab.Navigator>
|
||||||
<Tab.Screen name="Home" component={Home} />
|
<Tab.Screen name="Home" component={Home} />
|
||||||
|
<Tab.Screen name="Plans" component={Plans} />
|
||||||
<Tab.Screen name="Exercises" component={Exercises} />
|
<Tab.Screen name="Exercises" component={Exercises} />
|
||||||
<Tab.Screen name="Settings" component={Settings} />
|
<Tab.Screen name="Settings" component={Settings} />
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
|
|
63
DayMenu.tsx
Normal file
63
DayMenu.tsx
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import {useState} from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import {Button, Divider, Menu} from 'react-native-paper';
|
||||||
|
|
||||||
|
const days = [
|
||||||
|
'Monday',
|
||||||
|
'Tuesday',
|
||||||
|
'Wednesday',
|
||||||
|
'Thursday',
|
||||||
|
'Friday',
|
||||||
|
'Saturday',
|
||||||
|
'Sunday',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function DayMenu({
|
||||||
|
onSelect,
|
||||||
|
onDelete,
|
||||||
|
onAdd,
|
||||||
|
selected,
|
||||||
|
index,
|
||||||
|
}: {
|
||||||
|
onSelect: (option: string) => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
onAdd: () => void;
|
||||||
|
selected: string;
|
||||||
|
index: number;
|
||||||
|
}) {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
|
||||||
|
const add = () => {
|
||||||
|
onAdd();
|
||||||
|
setShow(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const select = (day: string) => {
|
||||||
|
onSelect(day);
|
||||||
|
setShow(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
visible={show}
|
||||||
|
onDismiss={() => setShow(false)}
|
||||||
|
anchor={
|
||||||
|
<Button icon="today" onPress={() => setShow(true)}>
|
||||||
|
{selected}
|
||||||
|
</Button>
|
||||||
|
}>
|
||||||
|
{days.map(day => (
|
||||||
|
<Menu.Item
|
||||||
|
icon={selected === day ? 'checkmark' : ''}
|
||||||
|
onPress={() => select(day)}
|
||||||
|
title={day}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Menu.Item icon="add" title="Add" onPress={add} />
|
||||||
|
<Divider />
|
||||||
|
{index > 0 && (
|
||||||
|
<Menu.Item icon="trash" title="Delete" onPress={onDelete} />
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
126
EditPlan.tsx
Normal file
126
EditPlan.tsx
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import {setDay} from 'date-fns';
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {StyleSheet, Text, View} from 'react-native';
|
||||||
|
import {Button, Modal, Portal, TextInput} from 'react-native-paper';
|
||||||
|
import DayMenu from './DayMenu';
|
||||||
|
import {getDb} from './db';
|
||||||
|
import {Plan} from './plan';
|
||||||
|
import Set from './set';
|
||||||
|
|
||||||
|
export default function EditPlan({
|
||||||
|
id,
|
||||||
|
onSave,
|
||||||
|
show,
|
||||||
|
setShow,
|
||||||
|
clearId,
|
||||||
|
}: {
|
||||||
|
id?: number;
|
||||||
|
clearId: () => void;
|
||||||
|
onSave: () => void;
|
||||||
|
show: boolean;
|
||||||
|
setShow: (visible: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const [days, setDays] = useState('');
|
||||||
|
const [workouts, setWorkouts] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id) return;
|
||||||
|
getDb().then(async db => {
|
||||||
|
const [result] = await db.executeSql(`SELECT * FROM plans WHERE id = ?`, [
|
||||||
|
id,
|
||||||
|
]);
|
||||||
|
if (!result.rows.item(0)) throw new Error("Can't find specified Set.");
|
||||||
|
const set: Plan = result.rows.item(0);
|
||||||
|
setDays(set.days);
|
||||||
|
setWorkouts(set.workouts);
|
||||||
|
});
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
if (!days || !workouts) return;
|
||||||
|
const db = await getDb();
|
||||||
|
if (!id)
|
||||||
|
await db.executeSql(`INSERT INTO plans(days, workouts) VALUES (?, ?)`, [
|
||||||
|
days,
|
||||||
|
workouts,
|
||||||
|
]);
|
||||||
|
else
|
||||||
|
await db.executeSql(
|
||||||
|
`UPDATE plans SET days = ?, workouts = ? WHERE id = ?`,
|
||||||
|
[days, workouts, id],
|
||||||
|
);
|
||||||
|
setShow(false);
|
||||||
|
onSave();
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectDay = (day: string, index: number) => {
|
||||||
|
const newDays = days.split(',');
|
||||||
|
newDays[index] = day;
|
||||||
|
setDays(newDays.join(','));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeDay = (index: number) => {
|
||||||
|
const newDays = days.split(',');
|
||||||
|
delete newDays[index];
|
||||||
|
setDays(newDays.filter(day => day).join(','));
|
||||||
|
};
|
||||||
|
|
||||||
|
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'}}>
|
||||||
|
{days.split(',').map((day, index) => (
|
||||||
|
<DayMenu
|
||||||
|
index={index}
|
||||||
|
onDelete={() => removeDay(index)}
|
||||||
|
onSelect={option => selectDay(option, index)}
|
||||||
|
onAdd={() => setDays(days + ',Monday')}
|
||||||
|
selected={day}
|
||||||
|
key={index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<TextInput
|
||||||
|
style={styles.text}
|
||||||
|
label="Workouts *"
|
||||||
|
value={workouts}
|
||||||
|
onChangeText={setWorkouts}
|
||||||
|
/>
|
||||||
|
<View style={styles.bottom}>
|
||||||
|
<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>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
modal: {
|
||||||
|
backgroundColor: 'black',
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
bottom: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 20,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
});
|
52
PlanItem.tsx
Normal file
52
PlanItem.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import {IconButton, List, Menu} from 'react-native-paper';
|
||||||
|
import {getDb} from './db';
|
||||||
|
import {Plan} from './plan';
|
||||||
|
|
||||||
|
export default function PlanItem({
|
||||||
|
item,
|
||||||
|
setId,
|
||||||
|
setShowEdit,
|
||||||
|
onRemove,
|
||||||
|
}: {
|
||||||
|
item: Plan;
|
||||||
|
setId: (id: number) => void;
|
||||||
|
setShowEdit: (show: boolean) => void;
|
||||||
|
onRemove: () => void;
|
||||||
|
}) {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
const db = await getDb();
|
||||||
|
await db.executeSql(`DELETE FROM plans WHERE id = ?`, [item.id]);
|
||||||
|
setShow(false);
|
||||||
|
onRemove();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<List.Item
|
||||||
|
onPress={() => {
|
||||||
|
setId(item.id);
|
||||||
|
setShowEdit(true);
|
||||||
|
}}
|
||||||
|
title={item.days}
|
||||||
|
description={item.workouts}
|
||||||
|
onLongPress={() => setShow(true)}
|
||||||
|
right={() => (
|
||||||
|
<Menu
|
||||||
|
anchor={
|
||||||
|
<IconButton
|
||||||
|
icon="ellipsis-vertical"
|
||||||
|
onPress={() => setShow(true)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
visible={show}
|
||||||
|
onDismiss={() => setShow(false)}>
|
||||||
|
<Menu.Item icon="trash" onPress={remove} title="Delete" />
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
67
Plans.tsx
Normal file
67
Plans.tsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {FlatList, View} from 'react-native';
|
||||||
|
import {AnimatedFAB, Searchbar} from 'react-native-paper';
|
||||||
|
import {getPlans} from './db';
|
||||||
|
import EditPlan from './EditPlan';
|
||||||
|
import {Plan} from './plan';
|
||||||
|
import PlanItem from './PlanItem';
|
||||||
|
|
||||||
|
export default function Plans() {
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
const [plans, setPlans] = useState<Plan[]>([]);
|
||||||
|
const [refreshing, setRefresing] = useState(false);
|
||||||
|
const [id, setId] = useState<number>();
|
||||||
|
const [showEdit, setShowEdit] = useState(false);
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
const [result] = await getPlans({search});
|
||||||
|
setPlans(result.rows.raw());
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refresh();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderItem = ({item}: {item: Plan}) => (
|
||||||
|
<PlanItem
|
||||||
|
item={item}
|
||||||
|
key={item.id}
|
||||||
|
setShowEdit={setShowEdit}
|
||||||
|
setId={setId}
|
||||||
|
onRemove={refresh}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{padding: 10}}>
|
||||||
|
<Searchbar value={search} onChangeText={setSearch} placeholder="Search" />
|
||||||
|
<FlatList
|
||||||
|
style={{height: '90%'}}
|
||||||
|
data={plans}
|
||||||
|
renderItem={renderItem}
|
||||||
|
keyExtractor={set => set.id.toString()}
|
||||||
|
refreshing={refreshing}
|
||||||
|
onRefresh={refresh}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EditPlan
|
||||||
|
clearId={() => setId(undefined)}
|
||||||
|
onSave={refresh}
|
||||||
|
setShow={setShowEdit}
|
||||||
|
show={showEdit}
|
||||||
|
id={id}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AnimatedFAB
|
||||||
|
extended={false}
|
||||||
|
label="Add"
|
||||||
|
icon="add"
|
||||||
|
style={{position: 'absolute', right: 20, bottom: 20}}
|
||||||
|
onPress={() => {
|
||||||
|
setId(undefined);
|
||||||
|
setShowEdit(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
30
db.ts
30
db.ts
|
@ -3,7 +3,7 @@ import {enablePromise, openDatabase} from 'react-native-sqlite-storage';
|
||||||
enablePromise(true);
|
enablePromise(true);
|
||||||
export const getDb = () => openDatabase({name: 'massive.db'});
|
export const getDb = () => openDatabase({name: 'massive.db'});
|
||||||
|
|
||||||
const schema = `
|
const createSets = `
|
||||||
CREATE TABLE IF NOT EXISTS sets (
|
CREATE TABLE IF NOT EXISTS sets (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
|
@ -14,9 +14,30 @@ const schema = `
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const setupSchema = () => getDb().then(db => db.executeSql(schema));
|
const createPlans = `
|
||||||
|
CREATE TABLE IF NOT EXISTS plans (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
days TEXT NOT NULL,
|
||||||
|
workouts TEXT NOT NULL
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
const select = `
|
export const setupSchema = () =>
|
||||||
|
getDb().then(db => {
|
||||||
|
db.executeSql(createSets);
|
||||||
|
db.executeSql(createPlans);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectPlans = `
|
||||||
|
SELECT * from plans
|
||||||
|
WHERE days LIKE ? OR workouts LIKE ?
|
||||||
|
`;
|
||||||
|
export const getPlans = ({search}: {search: string}) =>
|
||||||
|
getDb().then(db =>
|
||||||
|
db.executeSql(selectPlans, [`%${search}%`, `%${search}%`]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectSets = `
|
||||||
SELECT * from sets
|
SELECT * from sets
|
||||||
WHERE name LIKE ?
|
WHERE name LIKE ?
|
||||||
ORDER BY created DESC
|
ORDER BY created DESC
|
||||||
|
@ -31,4 +52,5 @@ export const getSets = ({
|
||||||
search: string;
|
search: string;
|
||||||
limit: number;
|
limit: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
}) => getDb().then(db => db.executeSql(select, [`%${search}%`, limit, offset]));
|
}) =>
|
||||||
|
getDb().then(db => db.executeSql(selectSets, [`%${search}%`, limit, offset]));
|
||||||
|
|
Loading…
Reference in New Issue
Block a user