From 4d3de751f371b82a30eccda9aaaf5bf208026529 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Tue, 11 Apr 2023 11:41:10 +1200 Subject: [PATCH] Fix searching on home page --- lib/best_page.dart | 7 +- lib/database.dart | 3 +- lib/database.g.dart | 543 +++++++++++++++++++++++++++++++++++++++- lib/edit_set.dart | 64 +++-- lib/gym_set.dart | 113 ++------- lib/home_page.dart | 215 +++++++++------- lib/main.dart | 16 +- lib/massive_drawer.dart | 31 +++ lib/plans_page.dart | 158 +++++++++++- lib/settings_page.dart | 38 +-- 10 files changed, 909 insertions(+), 279 deletions(-) create mode 100644 lib/massive_drawer.dart diff --git a/lib/best_page.dart b/lib/best_page.dart index 7ebe945..5c363f7 100644 --- a/lib/best_page.dart +++ b/lib/best_page.dart @@ -5,11 +5,8 @@ class BestPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Best'), - ), - body: const Center( + return const Scaffold( + body: Center( child: Text('Welcome to the Best Page!'), ), ); diff --git a/lib/database.dart b/lib/database.dart index 51ff6ab..5cfe49b 100644 --- a/lib/database.dart +++ b/lib/database.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:fmassive/gym_set.dart'; import 'package:moor/ffi.dart'; import 'package:moor/moor.dart'; import 'package:path/path.dart'; @@ -8,7 +9,7 @@ import 'settings.dart'; part 'database.g.dart'; -@UseMoor(tables: [Settings]) +@UseMoor(tables: [Settings, GymSets]) class MyDatabase extends _$MyDatabase { MyDatabase() : super(_openConnection()); diff --git a/lib/database.g.dart b/lib/database.g.dart index 0c6b0f7..3337bd6 100644 --- a/lib/database.g.dart +++ b/lib/database.g.dart @@ -699,11 +699,552 @@ class $SettingsTable extends Settings with TableInfo<$SettingsTable, Setting> { } } +class GymSet extends DataClass implements Insertable { + final int id; + final String name; + final int reps; + final int weight; + final int sets; + final int minutes; + final int seconds; + final bool hidden; + final String created; + final String unit; + final String image; + final String? steps; + GymSet( + {required this.id, + required this.name, + required this.reps, + required this.weight, + required this.sets, + required this.minutes, + required this.seconds, + required this.hidden, + required this.created, + required this.unit, + required this.image, + this.steps}); + factory GymSet.fromData(Map data, GeneratedDatabase db, + {String? prefix}) { + final effectivePrefix = prefix ?? ''; + return GymSet( + id: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}id'])!, + name: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}name'])!, + reps: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}reps'])!, + weight: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}weight'])!, + sets: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}sets'])!, + minutes: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}minutes'])!, + seconds: const IntType() + .mapFromDatabaseResponse(data['${effectivePrefix}seconds'])!, + hidden: const BoolType() + .mapFromDatabaseResponse(data['${effectivePrefix}hidden'])!, + created: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}created'])!, + unit: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}unit'])!, + image: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}image'])!, + steps: const StringType() + .mapFromDatabaseResponse(data['${effectivePrefix}steps']), + ); + } + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['reps'] = Variable(reps); + map['weight'] = Variable(weight); + map['sets'] = Variable(sets); + map['minutes'] = Variable(minutes); + map['seconds'] = Variable(seconds); + map['hidden'] = Variable(hidden); + map['created'] = Variable(created); + map['unit'] = Variable(unit); + map['image'] = Variable(image); + if (!nullToAbsent || steps != null) { + map['steps'] = Variable(steps); + } + return map; + } + + GymSetsCompanion toCompanion(bool nullToAbsent) { + return GymSetsCompanion( + id: Value(id), + name: Value(name), + reps: Value(reps), + weight: Value(weight), + sets: Value(sets), + minutes: Value(minutes), + seconds: Value(seconds), + hidden: Value(hidden), + created: Value(created), + unit: Value(unit), + image: Value(image), + steps: + steps == null && nullToAbsent ? const Value.absent() : Value(steps), + ); + } + + factory GymSet.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return GymSet( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + reps: serializer.fromJson(json['reps']), + weight: serializer.fromJson(json['weight']), + sets: serializer.fromJson(json['sets']), + minutes: serializer.fromJson(json['minutes']), + seconds: serializer.fromJson(json['seconds']), + hidden: serializer.fromJson(json['hidden']), + created: serializer.fromJson(json['created']), + unit: serializer.fromJson(json['unit']), + image: serializer.fromJson(json['image']), + steps: serializer.fromJson(json['steps']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'reps': serializer.toJson(reps), + 'weight': serializer.toJson(weight), + 'sets': serializer.toJson(sets), + 'minutes': serializer.toJson(minutes), + 'seconds': serializer.toJson(seconds), + 'hidden': serializer.toJson(hidden), + 'created': serializer.toJson(created), + 'unit': serializer.toJson(unit), + 'image': serializer.toJson(image), + 'steps': serializer.toJson(steps), + }; + } + + GymSet copyWith( + {int? id, + String? name, + int? reps, + int? weight, + int? sets, + int? minutes, + int? seconds, + bool? hidden, + String? created, + String? unit, + String? image, + String? steps}) => + GymSet( + id: id ?? this.id, + name: name ?? this.name, + reps: reps ?? this.reps, + weight: weight ?? this.weight, + sets: sets ?? this.sets, + minutes: minutes ?? this.minutes, + seconds: seconds ?? this.seconds, + hidden: hidden ?? this.hidden, + created: created ?? this.created, + unit: unit ?? this.unit, + image: image ?? this.image, + steps: steps ?? this.steps, + ); + @override + String toString() { + return (StringBuffer('GymSet(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('reps: $reps, ') + ..write('weight: $weight, ') + ..write('sets: $sets, ') + ..write('minutes: $minutes, ') + ..write('seconds: $seconds, ') + ..write('hidden: $hidden, ') + ..write('created: $created, ') + ..write('unit: $unit, ') + ..write('image: $image, ') + ..write('steps: $steps') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, reps, weight, sets, minutes, + seconds, hidden, created, unit, image, steps); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GymSet && + other.id == this.id && + other.name == this.name && + other.reps == this.reps && + other.weight == this.weight && + other.sets == this.sets && + other.minutes == this.minutes && + other.seconds == this.seconds && + other.hidden == this.hidden && + other.created == this.created && + other.unit == this.unit && + other.image == this.image && + other.steps == this.steps); +} + +class GymSetsCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value reps; + final Value weight; + final Value sets; + final Value minutes; + final Value seconds; + final Value hidden; + final Value created; + final Value unit; + final Value image; + final Value steps; + const GymSetsCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.reps = const Value.absent(), + this.weight = const Value.absent(), + this.sets = const Value.absent(), + this.minutes = const Value.absent(), + this.seconds = const Value.absent(), + this.hidden = const Value.absent(), + this.created = const Value.absent(), + this.unit = const Value.absent(), + this.image = const Value.absent(), + this.steps = const Value.absent(), + }); + GymSetsCompanion.insert({ + this.id = const Value.absent(), + required String name, + required int reps, + required int weight, + this.sets = const Value.absent(), + this.minutes = const Value.absent(), + this.seconds = const Value.absent(), + this.hidden = const Value.absent(), + required String created, + this.unit = const Value.absent(), + required String image, + this.steps = const Value.absent(), + }) : name = Value(name), + reps = Value(reps), + weight = Value(weight), + created = Value(created), + image = Value(image); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? reps, + Expression? weight, + Expression? sets, + Expression? minutes, + Expression? seconds, + Expression? hidden, + Expression? created, + Expression? unit, + Expression? image, + Expression? steps, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (reps != null) 'reps': reps, + if (weight != null) 'weight': weight, + if (sets != null) 'sets': sets, + if (minutes != null) 'minutes': minutes, + if (seconds != null) 'seconds': seconds, + if (hidden != null) 'hidden': hidden, + if (created != null) 'created': created, + if (unit != null) 'unit': unit, + if (image != null) 'image': image, + if (steps != null) 'steps': steps, + }); + } + + GymSetsCompanion copyWith( + {Value? id, + Value? name, + Value? reps, + Value? weight, + Value? sets, + Value? minutes, + Value? seconds, + Value? hidden, + Value? created, + Value? unit, + Value? image, + Value? steps}) { + return GymSetsCompanion( + id: id ?? this.id, + name: name ?? this.name, + reps: reps ?? this.reps, + weight: weight ?? this.weight, + sets: sets ?? this.sets, + minutes: minutes ?? this.minutes, + seconds: seconds ?? this.seconds, + hidden: hidden ?? this.hidden, + created: created ?? this.created, + unit: unit ?? this.unit, + image: image ?? this.image, + steps: steps ?? this.steps, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (reps.present) { + map['reps'] = Variable(reps.value); + } + if (weight.present) { + map['weight'] = Variable(weight.value); + } + if (sets.present) { + map['sets'] = Variable(sets.value); + } + if (minutes.present) { + map['minutes'] = Variable(minutes.value); + } + if (seconds.present) { + map['seconds'] = Variable(seconds.value); + } + if (hidden.present) { + map['hidden'] = Variable(hidden.value); + } + if (created.present) { + map['created'] = Variable(created.value); + } + if (unit.present) { + map['unit'] = Variable(unit.value); + } + if (image.present) { + map['image'] = Variable(image.value); + } + if (steps.present) { + map['steps'] = Variable(steps.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GymSetsCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('reps: $reps, ') + ..write('weight: $weight, ') + ..write('sets: $sets, ') + ..write('minutes: $minutes, ') + ..write('seconds: $seconds, ') + ..write('hidden: $hidden, ') + ..write('created: $created, ') + ..write('unit: $unit, ') + ..write('image: $image, ') + ..write('steps: $steps') + ..write(')')) + .toString(); + } +} + +class $GymSetsTable extends GymSets with TableInfo<$GymSetsTable, GymSet> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $GymSetsTable(this.attachedDatabase, [this._alias]); + final VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: const IntType(), + requiredDuringInsert: false, + defaultConstraints: 'PRIMARY KEY AUTOINCREMENT'); + final VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: const StringType(), requiredDuringInsert: true); + final VerificationMeta _repsMeta = const VerificationMeta('reps'); + @override + late final GeneratedColumn reps = GeneratedColumn( + 'reps', aliasedName, false, + type: const IntType(), requiredDuringInsert: true); + final VerificationMeta _weightMeta = const VerificationMeta('weight'); + @override + late final GeneratedColumn weight = GeneratedColumn( + 'weight', aliasedName, false, + type: const IntType(), requiredDuringInsert: true); + final VerificationMeta _setsMeta = const VerificationMeta('sets'); + @override + late final GeneratedColumn sets = GeneratedColumn( + 'sets', aliasedName, false, + type: const IntType(), + requiredDuringInsert: false, + defaultValue: const Constant(3)); + final VerificationMeta _minutesMeta = const VerificationMeta('minutes'); + @override + late final GeneratedColumn minutes = GeneratedColumn( + 'minutes', aliasedName, false, + type: const IntType(), + requiredDuringInsert: false, + defaultValue: const Constant(3)); + final VerificationMeta _secondsMeta = const VerificationMeta('seconds'); + @override + late final GeneratedColumn seconds = GeneratedColumn( + 'seconds', aliasedName, false, + type: const IntType(), + requiredDuringInsert: false, + defaultValue: const Constant(30)); + final VerificationMeta _hiddenMeta = const VerificationMeta('hidden'); + @override + late final GeneratedColumn hidden = GeneratedColumn( + 'hidden', aliasedName, false, + type: const BoolType(), + requiredDuringInsert: false, + defaultConstraints: 'CHECK (hidden IN (0, 1))', + defaultValue: const Constant(false)); + final VerificationMeta _createdMeta = const VerificationMeta('created'); + @override + late final GeneratedColumn created = GeneratedColumn( + 'created', aliasedName, false, + type: const StringType(), requiredDuringInsert: true); + final VerificationMeta _unitMeta = const VerificationMeta('unit'); + @override + late final GeneratedColumn unit = GeneratedColumn( + 'unit', aliasedName, false, + type: const StringType(), + requiredDuringInsert: false, + defaultValue: const Constant('kg')); + final VerificationMeta _imageMeta = const VerificationMeta('image'); + @override + late final GeneratedColumn image = GeneratedColumn( + 'image', aliasedName, false, + type: const StringType(), requiredDuringInsert: true); + final VerificationMeta _stepsMeta = const VerificationMeta('steps'); + @override + late final GeneratedColumn steps = GeneratedColumn( + 'steps', aliasedName, true, + type: const StringType(), requiredDuringInsert: false); + @override + List get $columns => [ + id, + name, + reps, + weight, + sets, + minutes, + seconds, + hidden, + created, + unit, + image, + steps + ]; + @override + String get aliasedName => _alias ?? 'gym_sets'; + @override + String get actualTableName => 'gym_sets'; + @override + VerificationContext validateIntegrity(Insertable 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('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('reps')) { + context.handle( + _repsMeta, reps.isAcceptableOrUnknown(data['reps']!, _repsMeta)); + } else if (isInserting) { + context.missing(_repsMeta); + } + if (data.containsKey('weight')) { + context.handle(_weightMeta, + weight.isAcceptableOrUnknown(data['weight']!, _weightMeta)); + } else if (isInserting) { + context.missing(_weightMeta); + } + if (data.containsKey('sets')) { + context.handle( + _setsMeta, sets.isAcceptableOrUnknown(data['sets']!, _setsMeta)); + } + if (data.containsKey('minutes')) { + context.handle(_minutesMeta, + minutes.isAcceptableOrUnknown(data['minutes']!, _minutesMeta)); + } + if (data.containsKey('seconds')) { + context.handle(_secondsMeta, + seconds.isAcceptableOrUnknown(data['seconds']!, _secondsMeta)); + } + if (data.containsKey('hidden')) { + context.handle(_hiddenMeta, + hidden.isAcceptableOrUnknown(data['hidden']!, _hiddenMeta)); + } + if (data.containsKey('created')) { + context.handle(_createdMeta, + created.isAcceptableOrUnknown(data['created']!, _createdMeta)); + } else if (isInserting) { + context.missing(_createdMeta); + } + if (data.containsKey('unit')) { + context.handle( + _unitMeta, unit.isAcceptableOrUnknown(data['unit']!, _unitMeta)); + } + if (data.containsKey('image')) { + context.handle( + _imageMeta, image.isAcceptableOrUnknown(data['image']!, _imageMeta)); + } else if (isInserting) { + context.missing(_imageMeta); + } + if (data.containsKey('steps')) { + context.handle( + _stepsMeta, steps.isAcceptableOrUnknown(data['steps']!, _stepsMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + GymSet map(Map data, {String? tablePrefix}) { + return GymSet.fromData(data, attachedDatabase, + prefix: tablePrefix != null ? '$tablePrefix.' : null); + } + + @override + $GymSetsTable createAlias(String alias) { + return $GymSetsTable(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); @override Iterable get allTables => allSchemaEntities.whereType(); @override - List get allSchemaEntities => [settings]; + List get allSchemaEntities => [settings, gymSets]; } diff --git a/lib/edit_set.dart b/lib/edit_set.dart index 5ccbda3..4af2925 100644 --- a/lib/edit_set.dart +++ b/lib/edit_set.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:fmassive/gym_set.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 EditGymSetPage extends StatefulWidget { - final GymSet gymSet; + final GymSetsCompanion gymSet; const EditGymSetPage({required this.gymSet, super.key}); @@ -14,40 +17,34 @@ class _EditGymSetPageState extends State { final TextEditingController _nameController = TextEditingController(); final TextEditingController _repsController = TextEditingController(); final TextEditingController _weightController = TextEditingController(); - - late GymSet _editedGymSet; - late GymSetDatabaseHelper _db; + late GymSetsCompanion gymSet; @override void initState() { super.initState(); - _db = GymSetDatabaseHelper.instance; - _editedGymSet = GymSet( - id: widget.gymSet.id, - name: widget.gymSet.name, - reps: widget.gymSet.reps, - weight: widget.gymSet.weight, - created: DateTime.now()); - _nameController.text = _editedGymSet.name; - _repsController.text = _editedGymSet.reps.toString(); - _weightController.text = _editedGymSet.weight.toString(); + gymSet = widget.gymSet; + _nameController.text = gymSet.name.value; + _repsController.text = gymSet.reps.value.toString(); + _weightController.text = gymSet.weight.value.toString(); } @override Widget build(BuildContext context) { + List actions = []; + if (widget.gymSet.id.present) + actions.add(IconButton( + onPressed: () async { + await db.gymSets.deleteOne(widget.gymSet); + if (!mounted) return; + Navigator.pop(context); + }, + icon: const Icon(Icons.delete))); + return Scaffold( - appBar: AppBar(title: const Text('Edit Gym Set'), actions: [ - IconButton( - onPressed: () async { - await _db.delete(_editedGymSet); - if (!mounted) return; - Navigator.pop(context, _editedGymSet); - }, - icon: const Icon(Icons.delete)) - ]), + appBar: AppBar(title: const Text('Edit Gym Set'), actions: actions), body: Padding( padding: const EdgeInsets.all(16.0), - child: Column( + child: material.Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( @@ -55,7 +52,7 @@ class _EditGymSetPageState extends State { decoration: const InputDecoration(labelText: 'Name'), onChanged: (value) { setState(() { - _editedGymSet.name = value; + gymSet = gymSet.copyWith(name: Value(value)); }); }, ), @@ -65,7 +62,8 @@ class _EditGymSetPageState extends State { keyboardType: TextInputType.number, onChanged: (value) { setState(() { - _editedGymSet.reps = int.tryParse(value) ?? 0; + gymSet = + gymSet.copyWith(reps: Value(int.tryParse(value) ?? 0)); }); }, ), @@ -75,7 +73,8 @@ class _EditGymSetPageState extends State { keyboardType: TextInputType.number, onChanged: (value) { setState(() { - _editedGymSet.weight = int.tryParse(value) ?? 0; + gymSet = + gymSet.copyWith(weight: Value(int.tryParse(value) ?? 0)); }); }, ), @@ -84,13 +83,12 @@ class _EditGymSetPageState extends State { ), floatingActionButton: FloatingActionButton( onPressed: () async { - print('edited gym set id: ${_editedGymSet.id}'); - if (_editedGymSet.id != null) - await _db.update(_editedGymSet); + if (gymSet.id.present) + await db.update(db.gymSets).write(gymSet); else - await _db.insert(_editedGymSet); + await db.into(db.gymSets).insert(gymSet); if (!mounted) return; - Navigator.pop(context, _editedGymSet); + Navigator.pop(context); }, child: const Icon(Icons.check), ), diff --git a/lib/gym_set.dart b/lib/gym_set.dart index 598452c..d77dadc 100644 --- a/lib/gym_set.dart +++ b/lib/gym_set.dart @@ -1,101 +1,16 @@ -import 'package:path/path.dart'; -import 'package:sqflite/sqflite.dart'; +import 'package:moor_flutter/moor_flutter.dart'; -class GymSet { - int? id; - String name; - int reps; - int weight; - DateTime created; - - GymSet( - {this.id, - required this.name, - required this.reps, - required this.weight, - required this.created}); -} - -class GymSetDatabaseHelper { - static const _databaseName = "gym_set_database.db"; - static const _databaseVersion = 1; - static const table = 'gym_sets'; - static const columnId = '_id'; - static const columnName = 'name'; - static const columnReps = 'reps'; - static const columnWeight = 'weight'; - static const columnCreated = 'created'; - - GymSetDatabaseHelper._privateConstructor(); - static final GymSetDatabaseHelper instance = - GymSetDatabaseHelper._privateConstructor(); - - static Database? _database; - Future get database async { - if (_database != null) return _database!; - _database = await _initDatabase(); - return _database!; - } - - _initDatabase() async { - String path = join(await getDatabasesPath(), _databaseName); - return await openDatabase(path, - version: _databaseVersion, onCreate: _onCreate); - } - - Future _onCreate(Database db, int version) async { - await db.execute(''' - CREATE TABLE $table ( - $columnId INTEGER PRIMARY KEY, - $columnName TEXT NOT NULL, - $columnReps INTEGER NOT NULL, - $columnWeight INTEGER NOT NULL, - $columnCreated TEXT NOT NULL - ) - '''); - } - - Future insert(GymSet gymSet) async { - Database db = await instance.database; - return await db.insert(table, { - columnName: gymSet.name, - columnReps: gymSet.reps, - columnWeight: gymSet.weight, - columnCreated: gymSet.created.toIso8601String() - }); - } - - Future> getAll() async { - Database db = await instance.database; - List> result = await db.query(table); - return result - .map((gymSetMap) => GymSet( - id: gymSetMap[columnId], - name: gymSetMap[columnName], - reps: gymSetMap[columnReps], - weight: gymSetMap[columnWeight], - created: DateTime.parse(gymSetMap[columnCreated]), - )) - .toList(); - } - - Future update(GymSet gymSet) async { - Database db = await instance.database; - return await db.update( - table, - { - columnName: gymSet.name, - columnReps: gymSet.reps, - columnWeight: gymSet.weight, - columnCreated: gymSet.created.toIso8601String() - }, - where: '$columnId = ?', - whereArgs: [gymSet.id]); - } - - Future delete(GymSet gymSet) async { - Database db = await instance.database; - return await db - .delete(table, where: '$columnId = ?', whereArgs: [gymSet.id]); - } +class GymSets extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + IntColumn get reps => integer()(); + IntColumn get weight => integer()(); + IntColumn get sets => integer().withDefault(const Constant(3))(); + IntColumn get minutes => integer().withDefault(const Constant(3))(); + IntColumn get seconds => integer().withDefault(const Constant(30))(); + BoolColumn get hidden => boolean().withDefault(const Constant(false))(); + TextColumn get created => text()(); + TextColumn get unit => text().withDefault(const Constant('kg'))(); + TextColumn get image => text()(); + TextColumn get steps => text().nullable()(); } diff --git a/lib/home_page.dart b/lib/home_page.dart index d9464ae..ca068cc 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -1,9 +1,26 @@ import 'package:flutter/material.dart'; +import 'package:fmassive/best_page.dart'; +import 'package:fmassive/database.dart'; import 'package:fmassive/edit_set.dart'; -import 'package:fmassive/gym_set.dart'; +import 'package:fmassive/main.dart'; +import 'package:fmassive/plans_page.dart'; +import 'package:fmassive/settings_page.dart'; +import 'package:fmassive/timer_page.dart'; +import 'package:fmassive/workouts_page.dart'; +import 'package:moor_flutter/moor_flutter.dart'; -class HomePage extends StatelessWidget { - HomePage({super.key}); +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + createState() => RootPage(); +} + +class RootPage extends State { + bool showSearch = false; + int selected = 0; + String search = ''; + final focusNode = FocusNode(); final List> routes = [ {'title': 'Home', 'icon': Icons.home}, @@ -14,134 +31,160 @@ class HomePage extends StatelessWidget { {'title': 'Settings', 'icon': Icons.settings}, ]; + void toggleSearch() { + setState(() { + showSearch = !showSearch; + }); + focusNode.requestFocus(); + } + + Widget getBody() { + switch (routes[selected]['title']) { + case 'Settings': + return const SettingsPage(); + case 'Timer': + return const TimerPage(); + case 'Workouts': + return const WorkoutsPage(); + case 'Best': + return const BestPage(); + case 'Plans': + return const PlansPage(); + default: + return _HomePageWidget(search: search); + } + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(routes[0]['title']), - ), drawer: Drawer( child: ListView.builder( itemCount: routes.length, itemBuilder: (context, index) { return ListTile( + selected: selected == index, leading: Icon(routes[index]['icon']), title: Text(routes[index]['title']), onTap: () { + setState(() { + selected = index; + }); Navigator.pop(context); - Navigator.pushNamed( - context, '/${routes[index]['title'].toLowerCase()}'); }, ); }, ), ), - body: const Center( - child: GymSetPage(), + appBar: AppBar( + title: showSearch + ? TextField( + focusNode: focusNode, + cursorColor: Colors.white, + onChanged: (String value) { + setState(() { + search = value; + }); + }, + decoration: const InputDecoration( + hintText: 'Search...', + border: InputBorder.none, + hintStyle: TextStyle(color: Colors.white), + ), + ) + : Text(routes[selected]['title']), + actions: [ + IconButton( + icon: const Icon(Icons.search), + onPressed: toggleSearch, + ), + ], ), + body: getBody(), ); } } -class GymSetPage extends StatefulWidget { - const GymSetPage({Key? key}) : super(key: key); +class _HomePageWidget extends StatefulWidget { + final String search; + + const _HomePageWidget({Key? key, required this.search}) : super(key: key); @override - createState() => GymSetPageState(); + createState() => _HomePage(); } -class GymSetPageState extends State { - late GymSetDatabaseHelper db; - List sets = []; +class _HomePage extends State<_HomePageWidget> { + bool showSearch = false; + late Stream> stream; - List searchResults = []; - - final TextEditingController searchController = TextEditingController(); - - void search(String searchQuery) { - List results = []; - - if (searchQuery.isEmpty) { - results = sets; - } else { - for (int i = 0; i < sets.length; i++) { - if (sets[i].reps.toString().contains(searchQuery) || - sets[i].weight.toString().contains(searchQuery) || - sets[i].created.toString().contains(searchQuery) || - sets[i].name.contains(searchQuery)) { - results.add(sets[i]); - } - } - } - - setState(() { - searchResults = results; - }); + @override + initState() { + super.initState(); + setStream(); } - void reset() async { - print('Resetting...'); - final data = await db.getAll(); - setState(() { - sets = data; - searchResults = data; - }); + void setStream() { + stream = (db.select(db.gymSets) + ..where((gymSet) => gymSet.name.contains(widget.search)) + ..limit(10, offset: 0)) + .watch(); } @override - void initState() { - super.initState(); - db = GymSetDatabaseHelper.instance; - reset(); + didUpdateWidget(covariant _HomePageWidget oldWidget) { + super.didUpdateWidget(oldWidget); + setStream(); + } + + void toggleSearch() { + setState(() { + showSearch = !showSearch; + }); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: TextField( - controller: searchController, - decoration: const InputDecoration( - hintText: 'Search Gym Sets', - border: InputBorder.none, - ), - onChanged: (searchQuery) { - search(searchQuery); - }, - ), - ), - body: ListView.builder( - itemCount: searchResults.length, - itemBuilder: (context, index) { - return ListTile( - title: Text( - '${searchResults[index].name}: ${searchResults[index].reps}x${searchResults[index].weight}kg'), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - EditGymSetPage(gymSet: searchResults[index]), - ), - ); - reset(); - }); - }, - ), + body: StreamBuilder>( + stream: stream, + builder: (context, snapshot) { + final gymSets = snapshot.data; + + if (gymSets == null) + return const Center(child: CircularProgressIndicator()); + + return ListView.builder( + itemCount: gymSets.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(gymSets[index].name), + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EditGymSetPage( + gymSet: gymSets[index].toCompanion(false)), + ), + ); + }); + }, + ); + }), floatingActionButton: FloatingActionButton( onPressed: () async { await Navigator.push( context, MaterialPageRoute( builder: (context) => EditGymSetPage( - gymSet: GymSet( - name: '', - reps: 0, - weight: 0, - created: DateTime.now())), + gymSet: GymSetsCompanion( + name: const Value(''), + reps: const Value(0), + weight: const Value(0), + image: const Value(''), + created: Value(DateTime.now().toString()))), ), ); - reset(); }, child: const Icon(Icons.add))); } diff --git a/lib/main.dart b/lib/main.dart index 925219d..8476d9f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:fmassive/best_page.dart'; import 'package:fmassive/database.dart'; import 'package:fmassive/edit_set.dart'; -import 'package:fmassive/gym_set.dart'; import 'package:fmassive/home_page.dart'; -import 'package:fmassive/plans_page.dart'; -import 'package:fmassive/settings_page.dart'; -import 'package:fmassive/timer_page.dart'; -import 'package:fmassive/workouts_page.dart'; MyDatabase db = MyDatabase(); @@ -22,16 +16,10 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - final edit = EditGymSetPage( - gymSet: GymSet(name: '', reps: 0, weight: 0, created: DateTime.now())); + const edit = EditGymSetPage(gymSet: GymSetsCompanion()); final Map routes = { - '/home': (context) => HomePage(), - '/plans': (context) => const PlansPage(), - '/best': (context) => const BestPage(), - '/workouts': (context) => const WorkoutsPage(), - '/timer': (context) => const TimerPage(), - '/settings': (context) => SettingsPage(), + '/home': (context) => const HomePage(), '/edit-set': (context) => edit, }; diff --git a/lib/massive_drawer.dart b/lib/massive_drawer.dart new file mode 100644 index 0000000..12af037 --- /dev/null +++ b/lib/massive_drawer.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class MassiveDrawer extends StatefulWidget { + final Widget body; + final PreferredSizeWidget appBar; + + const MassiveDrawer({super.key, required this.body, required this.appBar}); + + @override + createState() => _MassiveDrawer(); +} + +class _MassiveDrawer extends State { + final List> routes = [ + {'title': 'Home', 'icon': Icons.home}, + {'title': 'Plans', 'icon': Icons.calendar_today}, + {'title': 'Best', 'icon': Icons.star}, + {'title': 'Workouts', 'icon': Icons.fitness_center}, + {'title': 'Timer', 'icon': Icons.timer}, + {'title': 'Settings', 'icon': Icons.settings}, + ]; + int selected = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: widget.body, + appBar: widget.appBar, + ); + } +} diff --git a/lib/plans_page.dart b/lib/plans_page.dart index 8bff1b4..74788ac 100644 --- a/lib/plans_page.dart +++ b/lib/plans_page.dart @@ -1,17 +1,163 @@ import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' as material; +import 'package:fmassive/database.dart'; +import 'package:fmassive/main.dart'; +import 'package:fmassive/settings.dart'; +import 'package:moor/moor.dart'; class PlansPage extends StatelessWidget { const PlansPage({super.key}); @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Plans'), - ), - body: const Center( - child: Text('Welcome to the Plans Page!'), + return const Scaffold( + body: Center( + child: _PlansPage(), ), ); } } + +class _PlansPage extends StatefulWidget { + const _PlansPage({Key? key}) : super(key: key); + + @override + createState() => _PlansPageState(); +} + +class _PlansPageState extends State<_PlansPage> { + late Stream stream; + + final TextEditingController searchController = TextEditingController(); + + 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; + }); + } + + @override + void initState() { + super.initState(); + stream = db.select(db.settings).watchSingle(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: StreamBuilder( + stream: stream, + builder: (context, snapshot) { + final settings = snapshot.data; + + if (settings == 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))); + }, + ), + 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))); + }, + ), + ], + ), + ); + }), + ); + } +} diff --git a/lib/settings_page.dart b/lib/settings_page.dart index 9579056..f586634 100644 --- a/lib/settings_page.dart +++ b/lib/settings_page.dart @@ -6,40 +6,12 @@ import 'package:fmassive/settings.dart'; import 'package:moor/moor.dart'; class SettingsPage extends StatelessWidget { - SettingsPage({super.key}); - - final List> routes = [ - {'title': 'Home', 'icon': Icons.home}, - {'title': 'Plans', 'icon': Icons.calendar_today}, - {'title': 'Best', 'icon': Icons.star}, - {'title': 'Workouts', 'icon': Icons.fitness_center}, - {'title': 'Timer', 'icon': Icons.timer}, - {'title': 'Settings', 'icon': Icons.settings}, - ]; + const SettingsPage({super.key}); @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Settings'), - ), - drawer: Drawer( - child: ListView.builder( - itemCount: routes.length, - itemBuilder: (context, index) { - return ListTile( - leading: Icon(routes[index]['icon']), - title: Text(routes[index]['title']), - onTap: () { - Navigator.pop(context); - Navigator.pushNamed( - context, '/${routes[index]['title'].toLowerCase()}'); - }, - ); - }, - ), - ), - body: const Center( + return const Scaffold( + body: Center( child: _SettingsPage(), ), ); @@ -61,14 +33,12 @@ class _SettingsPageState extends State<_SettingsPage> { 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; - }); } @override void initState() { super.initState(); + reset(); stream = db.select(db.settings).watchSingle(); }