Compare commits

..

No commits in common. "0f7d938ad72a20463790a81a4e4daa5e70d70a42" and "ff9f3263aefdd63f1a67e4ebd21929c9a22b71c7" have entirely different histories.

8 changed files with 161 additions and 411 deletions

View File

@ -1,5 +1,30 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
linter: linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules: rules:
curly_braces_in_flow_control_structures: false curly_braces_in_flow_control_structures: false
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -2,7 +2,6 @@ import 'dart:io';
import 'package:fmassive/gym_set.dart'; import 'package:fmassive/gym_set.dart';
import 'package:fmassive/main.dart'; import 'package:fmassive/main.dart';
import 'package:fmassive/plans.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';
@ -12,7 +11,7 @@ import 'settings.dart';
part 'database.g.dart'; part 'database.g.dart';
@UseMoor(tables: [Settings, GymSets, Plans]) @UseMoor(tables: [Settings, GymSets])
class MyDatabase extends _$MyDatabase { class MyDatabase extends _$MyDatabase {
MyDatabase() : super(_openConnection()); MyDatabase() : super(_openConnection());
@ -36,6 +35,7 @@ LazyDatabase _openConnection() {
return LazyDatabase(() async { return LazyDatabase(() async {
final dbFolder = await getDatabasesPath(); final dbFolder = await getDatabasesPath();
final file = File(join(dbFolder, 'massive.db')); final file = File(join(dbFolder, 'massive.db'));
print('file.path=${file.path}');
return VmDatabase(file, logStatements: true); return VmDatabase(file, logStatements: true);
}); });
} }

View File

@ -1239,220 +1239,12 @@ 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 { 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); late final $GymSetsTable gymSets = $GymSetsTable(this);
late final $PlansTable plans = $PlansTable(this);
@override @override
Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>(); Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
@override @override
List<DatabaseSchemaEntity> get allSchemaEntities => List<DatabaseSchemaEntity> get allSchemaEntities => [settings, gymSets];
[settings, gymSets, plans];
} }

View File

@ -1,113 +0,0 @@
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),
),
));
}
}

View File

@ -54,8 +54,7 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
}, },
icon: const Icon(Icons.delete))); icon: const Icon(Icons.delete)));
return SafeArea( return Scaffold(
child: Scaffold(
appBar: AppBar(title: const Text('Edit Gym Set'), actions: actions), 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),
@ -112,12 +111,6 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () async { 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) if (gymSet.id.present)
await db.update(db.gymSets).write(gymSet); await db.update(db.gymSets).write(gymSet);
else { else {
@ -130,6 +123,6 @@ class _EditGymSetPageState extends State<EditGymSetPage> {
}, },
child: const Icon(Icons.check), child: const Icon(Icons.check),
), ),
)); );
} }
} }

View File

@ -51,7 +51,7 @@ class RootPage extends State<HomePage> {
case 'Best': case 'Best':
return const BestPage(); return const BestPage();
case 'Plans': case 'Plans':
return PlansPage(search: search); return const PlansPage();
default: default:
return _HomePageWidget(search: search); return _HomePageWidget(search: search);
} }
@ -59,8 +59,7 @@ class RootPage extends State<HomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return Scaffold(
child: Scaffold(
drawer: Drawer( drawer: Drawer(
child: ListView.builder( child: ListView.builder(
itemCount: routes.length, itemCount: routes.length,
@ -104,7 +103,7 @@ class RootPage extends State<HomePage> {
], ],
), ),
body: getBody(), body: getBody(),
)); );
} }
} }

View File

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

View File

@ -1,102 +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/database.dart';
import 'package:fmassive/edit_plan.dart';
import 'package:fmassive/main.dart'; import 'package:fmassive/main.dart';
import 'package:fmassive/settings.dart';
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
class PlansPage extends StatelessWidget { class PlansPage extends StatelessWidget {
const PlansPage({super.key, required this.search}); const PlansPage({super.key});
final String search;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return const Scaffold(
body: Center( body: Center(
child: _PlansPage(search: search), child: _PlansPage(),
), ),
); );
} }
} }
class _PlansPage extends StatefulWidget { class _PlansPage extends StatefulWidget {
const _PlansPage({Key? key, required this.search}) : super(key: key); const _PlansPage({Key? key}) : super(key: key);
final String search;
@override @override
createState() => _PlansPageState(); createState() => _PlansPageState();
} }
class _PlansPageState extends State<_PlansPage> { class _PlansPageState extends State<_PlansPage> {
bool showSearch = false; late Stream<Setting> stream;
late Stream<List<Plan>> stream;
@override final TextEditingController searchController = TextEditingController();
initState() {
super.initState();
setStream();
}
void setStream() { void reset() async {
stream = (db.select(db.plans) var data = await db.select(db.settings).get();
..where((gymSet) => gymSet.days.contains(widget.search)) if (data.isEmpty) await db.into(db.settings).insert(defaultSettings);
..limit(10, offset: 0))
.watch();
}
@override
didUpdateWidget(covariant _PlansPage oldWidget) {
super.didUpdateWidget(oldWidget);
setStream();
}
void toggleSearch() {
setState(() { setState(() {
showSearch = !showSearch; if (data.isEmpty) return;
}); });
} }
@override
void initState() {
super.initState();
stream = db.select(db.settings).watchSingle();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: StreamBuilder<List<Plan>>( body: StreamBuilder<Setting>(
stream: stream, stream: stream,
builder: (context, snapshot) { builder: (context, snapshot) {
final plans = snapshot.data; final settings = snapshot.data;
if (plans == null) if (settings == null)
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
return ListView.builder( return SingleChildScrollView(
itemCount: plans.length, padding: const EdgeInsets.all(8.0),
itemBuilder: (context, index) { child: material.Column(
return ListTile( crossAxisAlignment: CrossAxisAlignment.start,
title: Text(plans[index].days), children: [
subtitle: Text(plans[index].workouts), SwitchListTile(
onTap: () async { title: const Text('Alarm'),
await Navigator.push( value: settings.alarm,
context, onChanged: (value) {
MaterialPageRoute( db
builder: (context) => EditPlanPage( .update(db.settings)
plan: plans[index].toCompanion(false)), .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)));
},
),
],
),
); );
}), }),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EditPlanPage(
plan:
PlansCompanion(days: Value(''), workouts: Value(''))),
),
); );
},
child: const Icon(Icons.add)));
} }
} }