Add half completed Plans

This commit is contained in:
Brandon Presley 2022-07-06 17:40:53 +12:00
parent e4cc893bfc
commit 43405269df
7 changed files with 347 additions and 4 deletions

View File

@ -10,6 +10,7 @@ import {StatusBar, useColorScheme} from 'react-native';
import {setupSchema} from './db';
import Exercises from './Exercises';
import Home from './Home';
import Plans from './Plans';
import Settings from './Settings';
const Tab = createMaterialTopTabNavigator<RootStackParamList>();
@ -17,6 +18,7 @@ export type RootStackParamList = {
Home: {};
Settings: {};
Exercises: {};
Plans: {};
};
setupSchema();
@ -35,6 +37,7 @@ const App = () => {
<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>

63
DayMenu.tsx Normal file
View 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
View 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
View 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
View 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
View File

@ -3,7 +3,7 @@ import {enablePromise, openDatabase} from 'react-native-sqlite-storage';
enablePromise(true);
export const getDb = () => openDatabase({name: 'massive.db'});
const schema = `
const createSets = `
CREATE TABLE IF NOT EXISTS sets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
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
WHERE name LIKE ?
ORDER BY created DESC
@ -31,4 +52,5 @@ export const getSets = ({
search: string;
limit: number;
offset: number;
}) => getDb().then(db => db.executeSql(select, [`%${search}%`, limit, offset]));
}) =>
getDb().then(db => db.executeSql(selectSets, [`%${search}%`, limit, offset]));

10
plan.ts Normal file
View File

@ -0,0 +1,10 @@
export interface Plan {
id: number;
days: string;
workouts: string;
}
export interface Workout {
name: string;
sets: number;
}