From 43405269df5b226c50d790db663962c7b4c625de Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Wed, 6 Jul 2022 17:40:53 +1200 Subject: [PATCH] Add half completed Plans --- App.tsx | 3 ++ DayMenu.tsx | 63 ++++++++++++++++++++++++++ EditPlan.tsx | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++ PlanItem.tsx | 52 +++++++++++++++++++++ Plans.tsx | 67 +++++++++++++++++++++++++++ db.ts | 30 ++++++++++-- plan.ts | 10 ++++ 7 files changed, 347 insertions(+), 4 deletions(-) create mode 100644 DayMenu.tsx create mode 100644 EditPlan.tsx create mode 100644 PlanItem.tsx create mode 100644 Plans.tsx create mode 100644 plan.ts diff --git a/App.tsx b/App.tsx index 2c91f24..5e30811 100644 --- a/App.tsx +++ b/App.tsx @@ -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(); @@ -17,6 +18,7 @@ export type RootStackParamList = { Home: {}; Settings: {}; Exercises: {}; + Plans: {}; }; setupSchema(); @@ -35,6 +37,7 @@ const App = () => { + diff --git a/DayMenu.tsx b/DayMenu.tsx new file mode 100644 index 0000000..ca527a9 --- /dev/null +++ b/DayMenu.tsx @@ -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 ( + setShow(false)} + anchor={ + + }> + {days.map(day => ( + select(day)} + title={day} + /> + ))} + + + {index > 0 && ( + + )} + + ); +} diff --git a/EditPlan.tsx b/EditPlan.tsx new file mode 100644 index 0000000..b0bedf1 --- /dev/null +++ b/EditPlan.tsx @@ -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 ( + + setShow(false)}> + {id ? `Edit "${days}"` : 'Add a plan'} + + {days.split(',').map((day, index) => ( + removeDay(index)} + onSelect={option => selectDay(option, index)} + onAdd={() => setDays(days + ',Monday')} + selected={day} + key={index} + /> + ))} + + + + + + {id && ( + + )} + + + + ); +} + +const styles = StyleSheet.create({ + modal: { + backgroundColor: 'black', + padding: 20, + }, + text: { + marginBottom: 10, + }, + bottom: { + flexDirection: 'row', + }, + title: { + fontSize: 20, + marginBottom: 10, + }, +}); diff --git a/PlanItem.tsx b/PlanItem.tsx new file mode 100644 index 0000000..01a34d8 --- /dev/null +++ b/PlanItem.tsx @@ -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 ( + <> + { + setId(item.id); + setShowEdit(true); + }} + title={item.days} + description={item.workouts} + onLongPress={() => setShow(true)} + right={() => ( + setShow(true)} + /> + } + visible={show} + onDismiss={() => setShow(false)}> + + + )} + /> + + ); +} diff --git a/Plans.tsx b/Plans.tsx new file mode 100644 index 0000000..c734e04 --- /dev/null +++ b/Plans.tsx @@ -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([]); + const [refreshing, setRefresing] = useState(false); + const [id, setId] = useState(); + const [showEdit, setShowEdit] = useState(false); + + const refresh = async () => { + const [result] = await getPlans({search}); + setPlans(result.rows.raw()); + }; + + useEffect(() => { + refresh(); + }, []); + + const renderItem = ({item}: {item: Plan}) => ( + + ); + + return ( + + + set.id.toString()} + refreshing={refreshing} + onRefresh={refresh} + /> + + setId(undefined)} + onSave={refresh} + setShow={setShowEdit} + show={showEdit} + id={id} + /> + + { + setId(undefined); + setShowEdit(true); + }} + /> + + ); +} diff --git a/db.ts b/db.ts index 8d93e07..b0de527 100644 --- a/db.ts +++ b/db.ts @@ -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])); diff --git a/plan.ts b/plan.ts new file mode 100644 index 0000000..62b49bc --- /dev/null +++ b/plan.ts @@ -0,0 +1,10 @@ +export interface Plan { + id: number; + days: string; + workouts: string; +} + +export interface Workout { + name: string; + sets: number; +}