Make CRUD work for Plans

This commit is contained in:
Brandon Presley 2023-11-10 23:43:06 +13:00
parent fb278e4487
commit ea745c1cec
8 changed files with 173 additions and 97 deletions

View File

@ -1,6 +1,6 @@
import 'dart:io';
import 'package:fmassive/gym_set.dart';
import 'package:fmassive/gym_sets.dart';
import 'package:fmassive/main.dart';
import 'package:fmassive/plans.dart';
import 'package:moor/ffi.dart';

View File

@ -1248,8 +1248,8 @@ class $GymSetsTable extends GymSets with TableInfo<$GymSetsTable, GymSet> {
class Plan extends DataClass implements Insertable<Plan> {
final int id;
final String days;
final String workouts;
Plan({required this.id, required this.days, required this.workouts});
final String? exercises;
Plan({required this.id, required this.days, this.exercises});
factory Plan.fromData(Map<String, dynamic> data, GeneratedDatabase db,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
@ -1258,8 +1258,8 @@ class Plan extends DataClass implements Insertable<Plan> {
.mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
days: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}days'])!,
workouts: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}workouts'])!,
exercises: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}exercises']),
);
}
@override
@ -1267,7 +1267,9 @@ class Plan extends DataClass implements Insertable<Plan> {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['days'] = Variable<String>(days);
map['workouts'] = Variable<String>(workouts);
if (!nullToAbsent || exercises != null) {
map['exercises'] = Variable<String?>(exercises);
}
return map;
}
@ -1275,7 +1277,9 @@ class Plan extends DataClass implements Insertable<Plan> {
return PlansCompanion(
id: Value(id),
days: Value(days),
workouts: Value(workouts),
exercises: exercises == null && nullToAbsent
? const Value.absent()
: Value(exercises),
);
}
@ -1285,7 +1289,7 @@ class Plan extends DataClass implements Insertable<Plan> {
return Plan(
id: serializer.fromJson<int>(json['id']),
days: serializer.fromJson<String>(json['days']),
workouts: serializer.fromJson<String>(json['workouts']),
exercises: serializer.fromJson<String?>(json['exercises']),
);
}
@override
@ -1294,69 +1298,68 @@ class Plan extends DataClass implements Insertable<Plan> {
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'days': serializer.toJson<String>(days),
'workouts': serializer.toJson<String>(workouts),
'exercises': serializer.toJson<String?>(exercises),
};
}
Plan copyWith({int? id, String? days, String? workouts}) => Plan(
Plan copyWith({int? id, String? days, String? exercises}) => Plan(
id: id ?? this.id,
days: days ?? this.days,
workouts: workouts ?? this.workouts,
exercises: exercises ?? this.exercises,
);
@override
String toString() {
return (StringBuffer('Plan(')
..write('id: $id, ')
..write('days: $days, ')
..write('workouts: $workouts')
..write('exercises: $exercises')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, days, workouts);
int get hashCode => Object.hash(id, days, exercises);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is Plan &&
other.id == this.id &&
other.days == this.days &&
other.workouts == this.workouts);
other.exercises == this.exercises);
}
class PlansCompanion extends UpdateCompanion<Plan> {
final Value<int> id;
final Value<String> days;
final Value<String> workouts;
final Value<String?> exercises;
const PlansCompanion({
this.id = const Value.absent(),
this.days = const Value.absent(),
this.workouts = const Value.absent(),
this.exercises = const Value.absent(),
});
PlansCompanion.insert({
this.id = const Value.absent(),
required String days,
required String workouts,
}) : days = Value(days),
workouts = Value(workouts);
this.exercises = const Value.absent(),
}) : days = Value(days);
static Insertable<Plan> custom({
Expression<int>? id,
Expression<String>? days,
Expression<String>? workouts,
Expression<String?>? exercises,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (days != null) 'days': days,
if (workouts != null) 'workouts': workouts,
if (exercises != null) 'exercises': exercises,
});
}
PlansCompanion copyWith(
{Value<int>? id, Value<String>? days, Value<String>? workouts}) {
{Value<int>? id, Value<String>? days, Value<String?>? exercises}) {
return PlansCompanion(
id: id ?? this.id,
days: days ?? this.days,
workouts: workouts ?? this.workouts,
exercises: exercises ?? this.exercises,
);
}
@ -1369,8 +1372,8 @@ class PlansCompanion extends UpdateCompanion<Plan> {
if (days.present) {
map['days'] = Variable<String>(days.value);
}
if (workouts.present) {
map['workouts'] = Variable<String>(workouts.value);
if (exercises.present) {
map['exercises'] = Variable<String?>(exercises.value);
}
return map;
}
@ -1380,7 +1383,7 @@ class PlansCompanion extends UpdateCompanion<Plan> {
return (StringBuffer('PlansCompanion(')
..write('id: $id, ')
..write('days: $days, ')
..write('workouts: $workouts')
..write('exercises: $exercises')
..write(')'))
.toString();
}
@ -1403,13 +1406,13 @@ class $PlansTable extends Plans with TableInfo<$PlansTable, Plan> {
late final GeneratedColumn<String?> days = GeneratedColumn<String?>(
'days', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _workoutsMeta = const VerificationMeta('workouts');
final VerificationMeta _exercisesMeta = const VerificationMeta('exercises');
@override
late final GeneratedColumn<String?> workouts = GeneratedColumn<String?>(
'workouts', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
late final GeneratedColumn<String?> exercises = GeneratedColumn<String?>(
'exercises', aliasedName, true,
type: const StringType(), requiredDuringInsert: false);
@override
List<GeneratedColumn> get $columns => [id, days, workouts];
List<GeneratedColumn> get $columns => [id, days, exercises];
@override
String get aliasedName => _alias ?? 'plans';
@override
@ -1428,11 +1431,9 @@ class $PlansTable extends Plans with TableInfo<$PlansTable, Plan> {
} else if (isInserting) {
context.missing(_daysMeta);
}
if (data.containsKey('workouts')) {
context.handle(_workoutsMeta,
workouts.isAcceptableOrUnknown(data['workouts']!, _workoutsMeta));
} else if (isInserting) {
context.missing(_workoutsMeta);
if (data.containsKey('exercises')) {
context.handle(_exercisesMeta,
exercises.isAcceptableOrUnknown(data['exercises']!, _exercisesMeta));
}
return context;
}

View File

@ -2,15 +2,15 @@ import 'package:flutter/material.dart';
class Days extends StatefulWidget {
final ValueChanged<String> onChanged;
final String days;
const Days({required this.onChanged, super.key});
const Days({required this.onChanged, required this.days, super.key});
@override
createState() => _DaysState();
}
class _DaysState extends State<Days> {
final List<bool> _selections = List.generate(7, (_) => false);
final List<String> _days = [
'Monday',
'Tuesday',
@ -20,14 +20,19 @@ class _DaysState extends State<Days> {
'Saturday',
'Sunday'
];
late List<bool> _selections;
@override
void initState() {
super.initState();
final dayList = widget.days.split(',');
_selections = _days.map((day) => dayList.contains(day)).toList();
}
String _getSelectedDaysString() {
List<String> selectedDays = [];
for (int i = 0; i < _selections.length; i++) {
if (_selections[i]) {
selectedDays.add(_days[i]);
}
}
for (int i = 0; i < _selections.length; i++)
if (_selections[i]) selectedDays.add(_days[i]);
return selectedDays.join(",");
}
@ -40,14 +45,19 @@ class _DaysState extends State<Days> {
@override
Widget build(BuildContext context) {
return Column(
children: List.generate(7, (index) {
return SwitchListTile(
title: Text(_days[index]),
value: _selections[index],
onChanged: (value) => _updateSelections(index),
);
}),
);
return Column(children: [
Text('Days', style: Theme.of(context).textTheme.headlineSmall),
Expanded(
child: ListView(
children: List.generate(7, (index) {
return SwitchListTile(
title: Text(_days[index]),
value: _selections[index],
onChanged: (value) => _updateSelections(index),
);
}),
),
),
]);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as material;
import 'package:fmassive/database.dart';
import 'package:fmassive/days.dart';
import 'package:fmassive/exercises.dart';
import 'package:fmassive/main.dart';
import 'package:moor_flutter/moor_flutter.dart';
@ -15,28 +16,31 @@ class EditPlanPage extends StatefulWidget {
}
class _EditPlanPageState extends State<EditPlanPage> {
final TextEditingController _daysController = TextEditingController();
final TextEditingController _workoutsController = TextEditingController();
late PlansCompanion plan;
final daysNode = FocusNode();
final workoutsNode = FocusNode();
List<String?> names = [];
Future<List<String?>> getDistinctNames() async {
final names = await (db.gymSets.selectOnly(distinct: true)
..addColumns([db.gymSets.name]))
.get();
return names.map((name) => name.read(db.gymSets.name)).toList();
}
@override
void initState() {
super.initState();
getDistinctNames().then((value) {
setState(() {
names = value;
});
});
plan = widget.plan;
_daysController.text = plan.days.value;
_workoutsController.text = plan.workouts.value;
if (plan.id.present)
workoutsNode.requestFocus();
else
daysNode.requestFocus();
}
@override
dispose() {
daysNode.dispose();
workoutsNode.dispose();
super.dispose();
}
@ -59,38 +63,11 @@ class _EditPlanPageState extends State<EditPlanPage> {
padding: const EdgeInsets.all(16.0),
child: material.Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Days(onChanged: (days) {
setState(() {
days = days;
});
}),
TextFormField(
controller: _workoutsController,
focusNode: workoutsNode,
onTap: () {
_workoutsController.selection = TextSelection(
baseOffset: 0,
extentOffset: _workoutsController.text.length);
},
decoration: const InputDecoration(labelText: 'Workouts'),
onChanged: (value) {
setState(() {
plan = plan.copyWith(workouts: Value(value));
});
},
),
],
children: getChildren,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
if (_daysController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please enter days')));
daysNode.requestFocus();
return;
}
if (plan.id.present)
await db.update(db.plans).replace(plan);
else
@ -102,4 +79,32 @@ class _EditPlanPageState extends State<EditPlanPage> {
),
));
}
List<Widget> get getChildren {
return [
Expanded(
child: Days(
onChanged: (days) {
setState(() {
plan = plan.copyWith(days: Value(days));
});
},
days: plan.days.value,
),
),
names.isNotEmpty
? Expanded(
child: Exercises(
onChanged: (exercises) {
setState(() {
plan = plan.copyWith(exercises: Value(exercises));
});
},
exercises: plan.exercises.value,
names: names,
),
)
: const CircularProgressIndicator(),
];
}
}

60
lib/exercises.dart Normal file
View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
class Exercises extends StatefulWidget {
final ValueChanged<String> onChanged;
final String? exercises;
final List<String?> names;
const Exercises(
{required this.onChanged,
required this.exercises,
super.key,
required this.names});
@override
createState() => _ExercisesState();
}
class _ExercisesState extends State<Exercises> {
late List<bool> _selections;
@override
initState() {
super.initState();
final exercises = widget.exercises?.split(',');
_selections =
widget.names.map((name) => exercises?.contains(name) ?? false).toList();
}
String _getExercises() {
List<String> exercises = [];
for (int i = 0; i < _selections.length; i++)
if (_selections[i]) exercises.add(widget.names[i] ?? '');
return exercises.join(",");
}
void _updateSelections(int index) {
setState(() {
_selections[index] = !_selections[index];
widget.onChanged(_getExercises());
});
}
@override
Widget build(BuildContext context) {
return Column(children: [
Text('Exercises', style: Theme.of(context).textTheme.headlineSmall),
Expanded(
child: ListView(
children: List.generate(widget.names.length, (index) {
return SwitchListTile(
title: Text(widget.names[index] ?? ''),
value: _selections[index],
onChanged: (value) => _updateSelections(index),
);
}),
),
),
]);
}
}

View File

@ -3,5 +3,5 @@ import 'package:moor/moor.dart';
class Plans extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get days => text()();
TextColumn get workouts => text()();
TextColumn get exercises => text().nullable()();
}

View File

@ -73,8 +73,8 @@ class _PlansPageState extends State<_PlansPage> {
itemBuilder: (context, index) {
return ListTile(
title: Text(plans[index].days.replaceAll(',', ', ')),
subtitle:
Text(plans[index].workouts.replaceAll(',', ', ')),
subtitle: Text(
plans[index].exercises?.replaceAll(',', ', ') ?? ''),
onTap: () async {
await Navigator.push(
context,
@ -93,8 +93,8 @@ class _PlansPageState extends State<_PlansPage> {
context,
MaterialPageRoute(
builder: (context) => const EditPlanPage(
plan:
PlansCompanion(days: Value(''), workouts: Value(''))),
plan: PlansCompanion(
days: Value(''), exercises: Value(''))),
),
);
},