Fix searching on home page

This commit is contained in:
Brandon Presley 2023-04-11 11:41:10 +12:00
parent 84837de3de
commit 4d3de751f3
10 changed files with 909 additions and 279 deletions

View File

@ -5,11 +5,8 @@ class BestPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return const Scaffold(
appBar: AppBar( body: Center(
title: const Text('Best'),
),
body: const Center(
child: Text('Welcome to the Best Page!'), child: Text('Welcome to the Best Page!'),
), ),
); );

View File

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:fmassive/gym_set.dart';
import 'package:moor/ffi.dart'; import 'package:moor/ffi.dart';
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
@ -8,7 +9,7 @@ import 'settings.dart';
part 'database.g.dart'; part 'database.g.dart';
@UseMoor(tables: [Settings]) @UseMoor(tables: [Settings, GymSets])
class MyDatabase extends _$MyDatabase { class MyDatabase extends _$MyDatabase {
MyDatabase() : super(_openConnection()); MyDatabase() : super(_openConnection());

View File

@ -699,11 +699,552 @@ class $SettingsTable extends Settings with TableInfo<$SettingsTable, Setting> {
} }
} }
class GymSet extends DataClass implements Insertable<GymSet> {
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<String, dynamic> 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<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['name'] = Variable<String>(name);
map['reps'] = Variable<int>(reps);
map['weight'] = Variable<int>(weight);
map['sets'] = Variable<int>(sets);
map['minutes'] = Variable<int>(minutes);
map['seconds'] = Variable<int>(seconds);
map['hidden'] = Variable<bool>(hidden);
map['created'] = Variable<String>(created);
map['unit'] = Variable<String>(unit);
map['image'] = Variable<String>(image);
if (!nullToAbsent || steps != null) {
map['steps'] = Variable<String?>(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<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return GymSet(
id: serializer.fromJson<int>(json['id']),
name: serializer.fromJson<String>(json['name']),
reps: serializer.fromJson<int>(json['reps']),
weight: serializer.fromJson<int>(json['weight']),
sets: serializer.fromJson<int>(json['sets']),
minutes: serializer.fromJson<int>(json['minutes']),
seconds: serializer.fromJson<int>(json['seconds']),
hidden: serializer.fromJson<bool>(json['hidden']),
created: serializer.fromJson<String>(json['created']),
unit: serializer.fromJson<String>(json['unit']),
image: serializer.fromJson<String>(json['image']),
steps: serializer.fromJson<String?>(json['steps']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'name': serializer.toJson<String>(name),
'reps': serializer.toJson<int>(reps),
'weight': serializer.toJson<int>(weight),
'sets': serializer.toJson<int>(sets),
'minutes': serializer.toJson<int>(minutes),
'seconds': serializer.toJson<int>(seconds),
'hidden': serializer.toJson<bool>(hidden),
'created': serializer.toJson<String>(created),
'unit': serializer.toJson<String>(unit),
'image': serializer.toJson<String>(image),
'steps': serializer.toJson<String?>(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<GymSet> {
final Value<int> id;
final Value<String> name;
final Value<int> reps;
final Value<int> weight;
final Value<int> sets;
final Value<int> minutes;
final Value<int> seconds;
final Value<bool> hidden;
final Value<String> created;
final Value<String> unit;
final Value<String> image;
final Value<String?> 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<GymSet> custom({
Expression<int>? id,
Expression<String>? name,
Expression<int>? reps,
Expression<int>? weight,
Expression<int>? sets,
Expression<int>? minutes,
Expression<int>? seconds,
Expression<bool>? hidden,
Expression<String>? created,
Expression<String>? unit,
Expression<String>? image,
Expression<String?>? 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<int>? id,
Value<String>? name,
Value<int>? reps,
Value<int>? weight,
Value<int>? sets,
Value<int>? minutes,
Value<int>? seconds,
Value<bool>? hidden,
Value<String>? created,
Value<String>? unit,
Value<String>? image,
Value<String?>? 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<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (name.present) {
map['name'] = Variable<String>(name.value);
}
if (reps.present) {
map['reps'] = Variable<int>(reps.value);
}
if (weight.present) {
map['weight'] = Variable<int>(weight.value);
}
if (sets.present) {
map['sets'] = Variable<int>(sets.value);
}
if (minutes.present) {
map['minutes'] = Variable<int>(minutes.value);
}
if (seconds.present) {
map['seconds'] = Variable<int>(seconds.value);
}
if (hidden.present) {
map['hidden'] = Variable<bool>(hidden.value);
}
if (created.present) {
map['created'] = Variable<String>(created.value);
}
if (unit.present) {
map['unit'] = Variable<String>(unit.value);
}
if (image.present) {
map['image'] = Variable<String>(image.value);
}
if (steps.present) {
map['steps'] = Variable<String?>(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<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _nameMeta = const VerificationMeta('name');
@override
late final GeneratedColumn<String?> name = GeneratedColumn<String?>(
'name', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _repsMeta = const VerificationMeta('reps');
@override
late final GeneratedColumn<int?> reps = GeneratedColumn<int?>(
'reps', aliasedName, false,
type: const IntType(), requiredDuringInsert: true);
final VerificationMeta _weightMeta = const VerificationMeta('weight');
@override
late final GeneratedColumn<int?> weight = GeneratedColumn<int?>(
'weight', aliasedName, false,
type: const IntType(), requiredDuringInsert: true);
final VerificationMeta _setsMeta = const VerificationMeta('sets');
@override
late final GeneratedColumn<int?> sets = GeneratedColumn<int?>(
'sets', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultValue: const Constant(3));
final VerificationMeta _minutesMeta = const VerificationMeta('minutes');
@override
late final GeneratedColumn<int?> minutes = GeneratedColumn<int?>(
'minutes', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultValue: const Constant(3));
final VerificationMeta _secondsMeta = const VerificationMeta('seconds');
@override
late final GeneratedColumn<int?> seconds = GeneratedColumn<int?>(
'seconds', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultValue: const Constant(30));
final VerificationMeta _hiddenMeta = const VerificationMeta('hidden');
@override
late final GeneratedColumn<bool?> hidden = GeneratedColumn<bool?>(
'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<String?> created = GeneratedColumn<String?>(
'created', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _unitMeta = const VerificationMeta('unit');
@override
late final GeneratedColumn<String?> unit = GeneratedColumn<String?>(
'unit', aliasedName, false,
type: const StringType(),
requiredDuringInsert: false,
defaultValue: const Constant('kg'));
final VerificationMeta _imageMeta = const VerificationMeta('image');
@override
late final GeneratedColumn<String?> image = GeneratedColumn<String?>(
'image', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _stepsMeta = const VerificationMeta('steps');
@override
late final GeneratedColumn<String?> steps = GeneratedColumn<String?>(
'steps', aliasedName, true,
type: const StringType(), requiredDuringInsert: false);
@override
List<GeneratedColumn> 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<GymSet> 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<GeneratedColumn> get $primaryKey => {id};
@override
GymSet map(Map<String, dynamic> 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 { abstract class _$MyDatabase extends GeneratedDatabase {
_$MyDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e); _$MyDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
late final $SettingsTable settings = $SettingsTable(this); late final $SettingsTable settings = $SettingsTable(this);
late final $GymSetsTable gymSets = $GymSetsTable(this);
@override @override
Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>(); Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
@override @override
List<DatabaseSchemaEntity> get allSchemaEntities => [settings]; List<DatabaseSchemaEntity> get allSchemaEntities => [settings, gymSets];
} }

View File

@ -1,8 +1,11 @@
import 'package:flutter/material.dart'; 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 { class EditGymSetPage extends StatefulWidget {
final GymSet gymSet; final GymSetsCompanion gymSet;
const EditGymSetPage({required this.gymSet, super.key}); const EditGymSetPage({required this.gymSet, super.key});
@ -14,40 +17,34 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
final TextEditingController _nameController = TextEditingController(); final TextEditingController _nameController = TextEditingController();
final TextEditingController _repsController = TextEditingController(); final TextEditingController _repsController = TextEditingController();
final TextEditingController _weightController = TextEditingController(); final TextEditingController _weightController = TextEditingController();
late GymSetsCompanion gymSet;
late GymSet _editedGymSet;
late GymSetDatabaseHelper _db;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_db = GymSetDatabaseHelper.instance; gymSet = widget.gymSet;
_editedGymSet = GymSet( _nameController.text = gymSet.name.value;
id: widget.gymSet.id, _repsController.text = gymSet.reps.value.toString();
name: widget.gymSet.name, _weightController.text = gymSet.weight.value.toString();
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();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( List<Widget> actions = [];
appBar: AppBar(title: const Text('Edit Gym Set'), actions: [ if (widget.gymSet.id.present)
IconButton( actions.add(IconButton(
onPressed: () async { onPressed: () async {
await _db.delete(_editedGymSet); await db.gymSets.deleteOne(widget.gymSet);
if (!mounted) return; if (!mounted) return;
Navigator.pop(context, _editedGymSet); Navigator.pop(context);
}, },
icon: const Icon(Icons.delete)) icon: const Icon(Icons.delete)));
]),
return Scaffold(
appBar: AppBar(title: const Text('Edit Gym Set'), actions: actions),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: material.Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
TextFormField( TextFormField(
@ -55,7 +52,7 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
decoration: const InputDecoration(labelText: 'Name'), decoration: const InputDecoration(labelText: 'Name'),
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
_editedGymSet.name = value; gymSet = gymSet.copyWith(name: Value(value));
}); });
}, },
), ),
@ -65,7 +62,8 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
_editedGymSet.reps = int.tryParse(value) ?? 0; gymSet =
gymSet.copyWith(reps: Value(int.tryParse(value) ?? 0));
}); });
}, },
), ),
@ -75,7 +73,8 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
_editedGymSet.weight = int.tryParse(value) ?? 0; gymSet =
gymSet.copyWith(weight: Value(int.tryParse(value) ?? 0));
}); });
}, },
), ),
@ -84,13 +83,12 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () async { onPressed: () async {
print('edited gym set id: ${_editedGymSet.id}'); if (gymSet.id.present)
if (_editedGymSet.id != null) await db.update(db.gymSets).write(gymSet);
await _db.update(_editedGymSet);
else else
await _db.insert(_editedGymSet); await db.into(db.gymSets).insert(gymSet);
if (!mounted) return; if (!mounted) return;
Navigator.pop(context, _editedGymSet); Navigator.pop(context);
}, },
child: const Icon(Icons.check), child: const Icon(Icons.check),
), ),

View File

@ -1,101 +1,16 @@
import 'package:path/path.dart'; import 'package:moor_flutter/moor_flutter.dart';
import 'package:sqflite/sqflite.dart';
class GymSet { class GymSets extends Table {
int? id; IntColumn get id => integer().autoIncrement()();
String name; TextColumn get name => text()();
int reps; IntColumn get reps => integer()();
int weight; IntColumn get weight => integer()();
DateTime created; IntColumn get sets => integer().withDefault(const Constant(3))();
IntColumn get minutes => integer().withDefault(const Constant(3))();
GymSet( IntColumn get seconds => integer().withDefault(const Constant(30))();
{this.id, BoolColumn get hidden => boolean().withDefault(const Constant(false))();
required this.name, TextColumn get created => text()();
required this.reps, TextColumn get unit => text().withDefault(const Constant('kg'))();
required this.weight, TextColumn get image => text()();
required this.created}); TextColumn get steps => text().nullable()();
}
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<Database> 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<int> 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<List<GymSet>> getAll() async {
Database db = await instance.database;
List<Map<String, dynamic>> 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<int> 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<int> delete(GymSet gymSet) async {
Database db = await instance.database;
return await db
.delete(table, where: '$columnId = ?', whereArgs: [gymSet.id]);
}
} }

View File

@ -1,9 +1,26 @@
import 'package:flutter/material.dart'; 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/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 { class HomePage extends StatefulWidget {
HomePage({super.key}); const HomePage({super.key});
@override
createState() => RootPage();
}
class RootPage extends State<HomePage> {
bool showSearch = false;
int selected = 0;
String search = '';
final focusNode = FocusNode();
final List<Map<String, dynamic>> routes = [ final List<Map<String, dynamic>> routes = [
{'title': 'Home', 'icon': Icons.home}, {'title': 'Home', 'icon': Icons.home},
@ -14,134 +31,160 @@ class HomePage extends StatelessWidget {
{'title': 'Settings', 'icon': Icons.settings}, {'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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(
title: Text(routes[0]['title']),
),
drawer: Drawer( drawer: Drawer(
child: ListView.builder( child: ListView.builder(
itemCount: routes.length, itemCount: routes.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return ListTile( return ListTile(
selected: selected == index,
leading: Icon(routes[index]['icon']), leading: Icon(routes[index]['icon']),
title: Text(routes[index]['title']), title: Text(routes[index]['title']),
onTap: () { onTap: () {
setState(() {
selected = index;
});
Navigator.pop(context); Navigator.pop(context);
Navigator.pushNamed(
context, '/${routes[index]['title'].toLowerCase()}');
}, },
); );
}, },
), ),
), ),
body: const Center( appBar: AppBar(
child: GymSetPage(), 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 { class _HomePageWidget extends StatefulWidget {
const GymSetPage({Key? key}) : super(key: key); final String search;
const _HomePageWidget({Key? key, required this.search}) : super(key: key);
@override @override
createState() => GymSetPageState(); createState() => _HomePage();
} }
class GymSetPageState extends State<GymSetPage> { class _HomePage extends State<_HomePageWidget> {
late GymSetDatabaseHelper db; bool showSearch = false;
List<GymSet> sets = []; late Stream<List<GymSet>> stream;
List<GymSet> searchResults = [];
final TextEditingController searchController = TextEditingController();
void search(String searchQuery) {
List<GymSet> 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;
});
}
void reset() async {
print('Resetting...');
final data = await db.getAll();
setState(() {
sets = data;
searchResults = data;
});
}
@override @override
void initState() { initState() {
super.initState(); super.initState();
db = GymSetDatabaseHelper.instance; setStream();
reset(); }
void setStream() {
stream = (db.select(db.gymSets)
..where((gymSet) => gymSet.name.contains(widget.search))
..limit(10, offset: 0))
.watch();
}
@override
didUpdateWidget(covariant _HomePageWidget oldWidget) {
super.didUpdateWidget(oldWidget);
setStream();
}
void toggleSearch() {
setState(() {
showSearch = !showSearch;
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( body: StreamBuilder<List<GymSet>>(
title: TextField( stream: stream,
controller: searchController, builder: (context, snapshot) {
decoration: const InputDecoration( final gymSets = snapshot.data;
hintText: 'Search Gym Sets',
border: InputBorder.none, if (gymSets == null)
), return const Center(child: CircularProgressIndicator());
onChanged: (searchQuery) {
search(searchQuery); return ListView.builder(
}, itemCount: gymSets.length,
),
),
body: ListView.builder(
itemCount: searchResults.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return ListTile( return ListTile(
title: Text( title: Text(gymSets[index].name),
'${searchResults[index].name}: ${searchResults[index].reps}x${searchResults[index].weight}kg'),
onTap: () async { onTap: () async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) => EditGymSetPage(
EditGymSetPage(gymSet: searchResults[index]), gymSet: gymSets[index].toCompanion(false)),
), ),
); );
reset();
}); });
}, },
), );
}),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () async { onPressed: () async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => EditGymSetPage( builder: (context) => EditGymSetPage(
gymSet: GymSet( gymSet: GymSetsCompanion(
name: '', name: const Value(''),
reps: 0, reps: const Value(0),
weight: 0, weight: const Value(0),
created: DateTime.now())), image: const Value(''),
created: Value(DateTime.now().toString()))),
), ),
); );
reset();
}, },
child: const Icon(Icons.add))); child: const Icon(Icons.add)));
} }

View File

@ -1,13 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fmassive/best_page.dart';
import 'package:fmassive/database.dart'; import 'package:fmassive/database.dart';
import 'package:fmassive/edit_set.dart'; import 'package:fmassive/edit_set.dart';
import 'package:fmassive/gym_set.dart';
import 'package:fmassive/home_page.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(); MyDatabase db = MyDatabase();
@ -22,16 +16,10 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final edit = EditGymSetPage( const edit = EditGymSetPage(gymSet: GymSetsCompanion());
gymSet: GymSet(name: '', reps: 0, weight: 0, created: DateTime.now()));
final Map<String, WidgetBuilder> routes = { final Map<String, WidgetBuilder> routes = {
'/home': (context) => HomePage(), '/home': (context) => const HomePage(),
'/plans': (context) => const PlansPage(),
'/best': (context) => const BestPage(),
'/workouts': (context) => const WorkoutsPage(),
'/timer': (context) => const TimerPage(),
'/settings': (context) => SettingsPage(),
'/edit-set': (context) => edit, '/edit-set': (context) => edit,
}; };

31
lib/massive_drawer.dart Normal file
View File

@ -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<MassiveDrawer> {
final List<Map<String, dynamic>> 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,
);
}
}

View File

@ -1,17 +1,163 @@
import 'package:flutter/material.dart'; 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 { class PlansPage extends StatelessWidget {
const PlansPage({super.key}); const PlansPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return const Scaffold(
appBar: AppBar( body: Center(
title: const Text('Plans'), child: _PlansPage(),
),
body: const Center(
child: Text('Welcome to the Plans Page!'),
), ),
); );
} }
} }
class _PlansPage extends StatefulWidget {
const _PlansPage({Key? key}) : super(key: key);
@override
createState() => _PlansPageState();
}
class _PlansPageState extends State<_PlansPage> {
late Stream<Setting> 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<Setting>(
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)));
},
),
],
),
);
}),
);
}
}

View File

@ -6,40 +6,12 @@ import 'package:fmassive/settings.dart';
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
SettingsPage({super.key}); const SettingsPage({super.key});
final List<Map<String, dynamic>> 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},
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return const Scaffold(
appBar: AppBar( body: Center(
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(
child: _SettingsPage(), child: _SettingsPage(),
), ),
); );
@ -61,14 +33,12 @@ class _SettingsPageState extends State<_SettingsPage> {
void reset() async { void reset() async {
var data = await db.select(db.settings).get(); var data = await db.select(db.settings).get();
if (data.isEmpty) await db.into(db.settings).insert(defaultSettings); if (data.isEmpty) await db.into(db.settings).insert(defaultSettings);
setState(() {
if (data.isEmpty) return;
});
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
reset();
stream = db.select(db.settings).watchSingle(); stream = db.select(db.settings).watchSingle();
} }