Add plans page + edit
This commit is contained in:
parent
b1c7fc8882
commit
0f7d938ad7
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:fmassive/gym_set.dart';
|
||||
import 'package:fmassive/main.dart';
|
||||
import 'package:fmassive/plans.dart';
|
||||
import 'package:moor/ffi.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
@ -11,7 +12,7 @@ import 'settings.dart';
|
|||
|
||||
part 'database.g.dart';
|
||||
|
||||
@UseMoor(tables: [Settings, GymSets])
|
||||
@UseMoor(tables: [Settings, GymSets, Plans])
|
||||
class MyDatabase extends _$MyDatabase {
|
||||
MyDatabase() : super(_openConnection());
|
||||
|
||||
|
|
|
@ -1239,12 +1239,220 @@ 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});
|
||||
factory Plan.fromData(Map<String, dynamic> data, GeneratedDatabase db,
|
||||
{String? prefix}) {
|
||||
final effectivePrefix = prefix ?? '';
|
||||
return Plan(
|
||||
id: const IntType()
|
||||
.mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
|
||||
days: const StringType()
|
||||
.mapFromDatabaseResponse(data['${effectivePrefix}days'])!,
|
||||
workouts: const StringType()
|
||||
.mapFromDatabaseResponse(data['${effectivePrefix}workouts'])!,
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<int>(id);
|
||||
map['days'] = Variable<String>(days);
|
||||
map['workouts'] = Variable<String>(workouts);
|
||||
return map;
|
||||
}
|
||||
|
||||
PlansCompanion toCompanion(bool nullToAbsent) {
|
||||
return PlansCompanion(
|
||||
id: Value(id),
|
||||
days: Value(days),
|
||||
workouts: Value(workouts),
|
||||
);
|
||||
}
|
||||
|
||||
factory Plan.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= moorRuntimeOptions.defaultSerializer;
|
||||
return Plan(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
days: serializer.fromJson<String>(json['days']),
|
||||
workouts: serializer.fromJson<String>(json['workouts']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= moorRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'days': serializer.toJson<String>(days),
|
||||
'workouts': serializer.toJson<String>(workouts),
|
||||
};
|
||||
}
|
||||
|
||||
Plan copyWith({int? id, String? days, String? workouts}) => Plan(
|
||||
id: id ?? this.id,
|
||||
days: days ?? this.days,
|
||||
workouts: workouts ?? this.workouts,
|
||||
);
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('Plan(')
|
||||
..write('id: $id, ')
|
||||
..write('days: $days, ')
|
||||
..write('workouts: $workouts')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, days, workouts);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is Plan &&
|
||||
other.id == this.id &&
|
||||
other.days == this.days &&
|
||||
other.workouts == this.workouts);
|
||||
}
|
||||
|
||||
class PlansCompanion extends UpdateCompanion<Plan> {
|
||||
final Value<int> id;
|
||||
final Value<String> days;
|
||||
final Value<String> workouts;
|
||||
const PlansCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.days = const Value.absent(),
|
||||
this.workouts = const Value.absent(),
|
||||
});
|
||||
PlansCompanion.insert({
|
||||
this.id = const Value.absent(),
|
||||
required String days,
|
||||
required String workouts,
|
||||
}) : days = Value(days),
|
||||
workouts = Value(workouts);
|
||||
static Insertable<Plan> custom({
|
||||
Expression<int>? id,
|
||||
Expression<String>? days,
|
||||
Expression<String>? workouts,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (days != null) 'days': days,
|
||||
if (workouts != null) 'workouts': workouts,
|
||||
});
|
||||
}
|
||||
|
||||
PlansCompanion copyWith(
|
||||
{Value<int>? id, Value<String>? days, Value<String>? workouts}) {
|
||||
return PlansCompanion(
|
||||
id: id ?? this.id,
|
||||
days: days ?? this.days,
|
||||
workouts: workouts ?? this.workouts,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<int>(id.value);
|
||||
}
|
||||
if (days.present) {
|
||||
map['days'] = Variable<String>(days.value);
|
||||
}
|
||||
if (workouts.present) {
|
||||
map['workouts'] = Variable<String>(workouts.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('PlansCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('days: $days, ')
|
||||
..write('workouts: $workouts')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class $PlansTable extends Plans with TableInfo<$PlansTable, Plan> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$PlansTable(this.attachedDatabase, [this._alias]);
|
||||
final VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
|
||||
'id', aliasedName, false,
|
||||
type: const IntType(),
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
|
||||
final VerificationMeta _daysMeta = const VerificationMeta('days');
|
||||
@override
|
||||
late final GeneratedColumn<String?> days = GeneratedColumn<String?>(
|
||||
'days', aliasedName, false,
|
||||
type: const StringType(), requiredDuringInsert: true);
|
||||
final VerificationMeta _workoutsMeta = const VerificationMeta('workouts');
|
||||
@override
|
||||
late final GeneratedColumn<String?> workouts = GeneratedColumn<String?>(
|
||||
'workouts', aliasedName, false,
|
||||
type: const StringType(), requiredDuringInsert: true);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, days, workouts];
|
||||
@override
|
||||
String get aliasedName => _alias ?? 'plans';
|
||||
@override
|
||||
String get actualTableName => 'plans';
|
||||
@override
|
||||
VerificationContext validateIntegrity(Insertable<Plan> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
}
|
||||
if (data.containsKey('days')) {
|
||||
context.handle(
|
||||
_daysMeta, days.isAcceptableOrUnknown(data['days']!, _daysMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_daysMeta);
|
||||
}
|
||||
if (data.containsKey('workouts')) {
|
||||
context.handle(_workoutsMeta,
|
||||
workouts.isAcceptableOrUnknown(data['workouts']!, _workoutsMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_workoutsMeta);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
Plan map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
return Plan.fromData(data, attachedDatabase,
|
||||
prefix: tablePrefix != null ? '$tablePrefix.' : null);
|
||||
}
|
||||
|
||||
@override
|
||||
$PlansTable createAlias(String alias) {
|
||||
return $PlansTable(attachedDatabase, alias);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$MyDatabase extends GeneratedDatabase {
|
||||
_$MyDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
|
||||
late final $SettingsTable settings = $SettingsTable(this);
|
||||
late final $GymSetsTable gymSets = $GymSetsTable(this);
|
||||
late final $PlansTable plans = $PlansTable(this);
|
||||
@override
|
||||
Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [settings, gymSets];
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
||||
[settings, gymSets, plans];
|
||||
}
|
||||
|
|
113
lib/edit_plan.dart
Normal file
113
lib/edit_plan.dart
Normal file
|
@ -0,0 +1,113 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' as material;
|
||||
import 'package:fmassive/database.dart';
|
||||
import 'package:fmassive/main.dart';
|
||||
import 'package:moor_flutter/moor_flutter.dart';
|
||||
|
||||
class EditPlanPage extends StatefulWidget {
|
||||
final PlansCompanion plan;
|
||||
|
||||
const EditPlanPage({required this.plan, super.key});
|
||||
|
||||
@override
|
||||
createState() => _EditPlanPageState();
|
||||
}
|
||||
|
||||
class _EditPlanPageState extends State<EditPlanPage> {
|
||||
final TextEditingController _daysController = TextEditingController();
|
||||
final TextEditingController _workoutsController = TextEditingController();
|
||||
late PlansCompanion plan;
|
||||
final daysNode = FocusNode();
|
||||
final workoutsNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
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();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> actions = [];
|
||||
if (widget.plan.id.present)
|
||||
actions.add(IconButton(
|
||||
onPressed: () async {
|
||||
await db.plans.deleteOne(widget.plan);
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(Icons.delete)));
|
||||
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(title: const Text('Edit Plan'), actions: actions),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: material.Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _daysController,
|
||||
focusNode: daysNode,
|
||||
decoration: const InputDecoration(labelText: 'Days'),
|
||||
onTap: () {
|
||||
_daysController.selection = TextSelection(
|
||||
baseOffset: 0, extentOffset: _daysController.text.length);
|
||||
},
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
plan = plan.copyWith(days: Value(value));
|
||||
});
|
||||
},
|
||||
),
|
||||
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));
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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).write(plan);
|
||||
else
|
||||
await db.into(db.plans).insert(plan);
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Icon(Icons.check),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -54,7 +54,8 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
|
|||
},
|
||||
icon: const Icon(Icons.delete)));
|
||||
|
||||
return Scaffold(
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(title: const Text('Edit Gym Set'), actions: actions),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
|
@ -111,6 +112,12 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
|
|||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () async {
|
||||
if (_nameController.text.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Please enter a name')));
|
||||
nameNode.requestFocus();
|
||||
return;
|
||||
}
|
||||
if (gymSet.id.present)
|
||||
await db.update(db.gymSets).write(gymSet);
|
||||
else {
|
||||
|
@ -123,6 +130,6 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
|
|||
},
|
||||
child: const Icon(Icons.check),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ class RootPage extends State<HomePage> {
|
|||
case 'Best':
|
||||
return const BestPage();
|
||||
case 'Plans':
|
||||
return const PlansPage();
|
||||
return PlansPage(search: search);
|
||||
default:
|
||||
return _HomePageWidget(search: search);
|
||||
}
|
||||
|
|
7
lib/plans.dart
Normal file
7
lib/plans.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
import 'package:moor/moor.dart';
|
||||
|
||||
class Plans extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get days => text()();
|
||||
TextColumn get workouts => text()();
|
||||
}
|
|
@ -1,163 +1,102 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' as material;
|
||||
import 'package:fmassive/database.dart';
|
||||
import 'package:fmassive/edit_plan.dart';
|
||||
import 'package:fmassive/main.dart';
|
||||
import 'package:fmassive/settings.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
|
||||
class PlansPage extends StatelessWidget {
|
||||
const PlansPage({super.key});
|
||||
const PlansPage({super.key, required this.search});
|
||||
|
||||
final String search;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: _PlansPage(),
|
||||
child: _PlansPage(search: search),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlansPage extends StatefulWidget {
|
||||
const _PlansPage({Key? key}) : super(key: key);
|
||||
const _PlansPage({Key? key, required this.search}) : super(key: key);
|
||||
|
||||
final String search;
|
||||
|
||||
@override
|
||||
createState() => _PlansPageState();
|
||||
}
|
||||
|
||||
class _PlansPageState extends State<_PlansPage> {
|
||||
late Stream<Setting> stream;
|
||||
bool showSearch = false;
|
||||
late Stream<List<Plan>> stream;
|
||||
|
||||
final TextEditingController searchController = TextEditingController();
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
setStream();
|
||||
}
|
||||
|
||||
void reset() async {
|
||||
var data = await db.select(db.settings).get();
|
||||
if (data.isEmpty) await db.into(db.settings).insert(defaultSettings);
|
||||
setState(() {
|
||||
if (data.isEmpty) return;
|
||||
});
|
||||
void setStream() {
|
||||
stream = (db.select(db.plans)
|
||||
..where((gymSet) => gymSet.days.contains(widget.search))
|
||||
..limit(10, offset: 0))
|
||||
.watch();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
stream = db.select(db.settings).watchSingle();
|
||||
didUpdateWidget(covariant _PlansPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
setStream();
|
||||
}
|
||||
|
||||
void toggleSearch() {
|
||||
setState(() {
|
||||
showSearch = !showSearch;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: StreamBuilder<Setting>(
|
||||
body: StreamBuilder<List<Plan>>(
|
||||
stream: stream,
|
||||
builder: (context, snapshot) {
|
||||
final settings = snapshot.data;
|
||||
final plans = snapshot.data;
|
||||
|
||||
if (settings == null)
|
||||
if (plans == null)
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: material.Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SwitchListTile(
|
||||
title: const Text('Alarm'),
|
||||
value: settings.alarm,
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(alarm: Value(value)));
|
||||
return ListView.builder(
|
||||
itemCount: plans.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
title: Text(plans[index].days),
|
||||
subtitle: Text(plans[index].workouts),
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => EditPlanPage(
|
||||
plan: plans[index].toCompanion(false)),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text('Vibrate'),
|
||||
value: settings.vibrate,
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(vibrate: Value(value)));
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text('Notify'),
|
||||
value: settings.notify,
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(notify: Value(value)));
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text('Images'),
|
||||
value: settings.images,
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(images: Value(value)));
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text('Show Unit'),
|
||||
value: settings.showUnit,
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(showUnit: Value(value)));
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text('Steps'),
|
||||
value: settings.steps,
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(steps: Value(value)));
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Sound',
|
||||
),
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(sound: Value(value)));
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Light Color',
|
||||
),
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(lightColor: Value(value)));
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Dark Color',
|
||||
),
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(darkColor: Value(value)));
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Date',
|
||||
),
|
||||
onChanged: (value) {
|
||||
db
|
||||
.update(db.settings)
|
||||
.write(SettingsCompanion(date: Value(value)));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const EditPlanPage(
|
||||
plan:
|
||||
PlansCompanion(days: Value(''), workouts: Value(''))),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Icon(Icons.add)));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user