Commit 7ca6c8ad authored by Stefan Haslinger's avatar Stefan Haslinger

heart rate zone and heart rate zone schema management

parent a9164811
......@@ -8,6 +8,8 @@ import 'package:encrateia/models/weight.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'dart:io';
import 'heart_rate_zone_schema.dart';
class Athlete extends ChangeNotifier {
String email;
String password;
......@@ -78,6 +80,7 @@ class Athlete extends ChangeNotifier {
get activities => Activity.all(athlete: this);
get weights => Weight.all(athlete: this);
get powerZoneSchemas => PowerZoneSchema.all(athlete: this);
get heartRateZoneSchemas => HeartRateZoneSchema.all(athlete: this);
delete() async {
var appDocDir = await getApplicationDocumentsDirectory();
......
import 'package:flutter/material.dart';
import 'package:encrateia/model/model.dart';
import 'package:encrateia/models/heart_rate_zone_schema.dart';
class HeartRateZone extends ChangeNotifier {
DbHeartRateZone db;
HeartRateZone(
{@required HeartRateZoneSchema heartRateZoneSchema,
String name,
int lowerPercentage,
int upperPercentage,
int lowerLimit,
int upperLimit,
int color}) {
db = DbHeartRateZone()
..heartRateZoneSchemataId = heartRateZoneSchema.db.id
..name = name ?? "my Zone"
..lowerLimit = lowerLimit ?? 70
..upperLimit = upperLimit ?? 100
..lowerPercentage = lowerPercentage ?? 0
..upperPercentage = upperPercentage ?? 0
..color = color ?? 0xFFFFc107;
if (lowerPercentage != null)
db.lowerLimit = (lowerPercentage * heartRateZoneSchema.db.base / 100).round();
if (upperPercentage != null)
db.upperLimit = (upperPercentage * heartRateZoneSchema.db.base / 100).round();
}
HeartRateZone.fromDb(this.db);
String toString() => '$db.date $db.name';
delete() async {
await this.db.delete();
}
static Future<List<HeartRateZone>> all(
{@required HeartRateZoneSchema heartRateZoneSchema}) async {
var dbHeartRateZoneList = await heartRateZoneSchema.db
.getDbHeartRateZones()
.orderByDesc('lowerlimit')
.toList();
var heartRateZones = dbHeartRateZoneList
.map((dbHeartRateZone) => HeartRateZone.fromDb(dbHeartRateZone))
.toList();
return heartRateZones;
}
}
import 'package:encrateia/models/heart_rate_zone.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/model/model.dart';
import 'package:encrateia/models/athlete.dart';
class HeartRateZoneSchema extends ChangeNotifier {
DbHeartRateZoneSchema db;
HeartRateZoneSchema({@required Athlete athlete}) {
db = DbHeartRateZoneSchema()
..athletesId = athlete.db.id
..base = 180
..name = "MySchema"
..date = DateTime.now();
}
HeartRateZoneSchema.fromDb(this.db);
get heartRateZones => HeartRateZone.all(heartRateZoneSchema: this);
HeartRateZoneSchema.likeGarmin({Athlete athlete}) {
db = DbHeartRateZoneSchema()
..athletesId = athlete.db.id
..name = "like Garmin"
..date = DateTime(1970, 01, 01)
..base = 180;
}
addGarminZones() async {
await HeartRateZone(
heartRateZoneSchema: this,
name: "Warmup",
lowerPercentage: 50,
upperPercentage: 60,
color: Colors.grey.value,
).db.save();
await HeartRateZone(
heartRateZoneSchema: this,
name: "Easy",
lowerPercentage: 60,
upperPercentage: 70,
color: Colors.blue.value,
).db.save();
await HeartRateZone(
heartRateZoneSchema: this,
name: "Aerobic",
lowerPercentage: 70,
upperPercentage: 80,
color: Colors.green.value,
).db.save();
await HeartRateZone(
heartRateZoneSchema: this,
name: "Threshold",
lowerPercentage: 80,
upperPercentage: 90,
color: Colors.orange.value,
).db.save();
await HeartRateZone(
heartRateZoneSchema: this,
name: "Maximum",
lowerPercentage: 90,
upperPercentage: 100,
color: Colors.red.value,
).db.save();
}
String toString() => '$db.date $db.name';
delete() async {
await this.db.delete();
}
static Future<List<HeartRateZoneSchema>> all({@required Athlete athlete}) async {
var dbHeartRateZoneSchemaList =
await athlete.db.getDbHeartRateZoneSchemas().orderByDesc('date').toList();
var heartRateZoneSchemas = dbHeartRateZoneSchemaList
.map((dbHeartRateZoneSchema) => HeartRateZoneSchema.fromDb(dbHeartRateZoneSchema))
.toList();
return heartRateZoneSchemas;
}
static getBy({
int athletesId,
DateTime date,
}) async {
var dbHeartRateZoneSchemas = await DbHeartRateZoneSchema()
.select()
.athletesId
.equals(athletesId)
.and
.date
.lessThanOrEquals(date)
.orderByDesc("date")
.top(1)
.toList();
if (dbHeartRateZoneSchemas.length != 0)
return HeartRateZoneSchema.fromDb(dbHeartRateZoneSchemas.first);
}
}
import 'package:encrateia/utils/icon_utils.dart';
import 'package:encrateia/utils/my_button.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/heart_rate_zone_schema.dart';
import 'package:encrateia/models/heart_rate_zone.dart';
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
import 'package:intl/intl.dart';
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
import 'package:encrateia/model/model.dart';
import 'add_heart_rate_zone_screen.dart';
class AddHeartRateZoneSchemaScreen extends StatefulWidget {
final HeartRateZoneSchema heartRateZoneSchema;
const AddHeartRateZoneSchemaScreen({Key key, this.heartRateZoneSchema})
: super(key: key);
@override
_AddHeartRateZoneSchemaScreenState createState() =>
_AddHeartRateZoneSchemaScreenState();
}
class _AddHeartRateZoneSchemaScreenState extends State<AddHeartRateZoneSchemaScreen> {
List<HeartRateZone> heartRateZones = [];
int offset = 0;
int rows;
@override
void initState() {
getData();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add Heart Rate Zone Schema'),
),
body: ListView(
padding: EdgeInsets.only(left: 20, right: 20),
children: <Widget>[
DateTimeField(
decoration: InputDecoration(
labelText: "Valid from",
),
format: DateFormat("yyyy-MM-dd"),
initialValue: widget.heartRateZoneSchema.db.date,
onShowPicker: (context, currentValue) {
return showDatePicker(
context: context,
firstDate: DateTime(1969),
initialDate: DateTime.now(),
lastDate: DateTime(2100),
);
},
onChanged: (value) => copyHeartRateZoneSchema(date: value),
),
TextFormField(
decoration: InputDecoration(labelText: "Name"),
initialValue: widget.heartRateZoneSchema.db.name,
onChanged: (value) => widget.heartRateZoneSchema.db.name = value,
),
TextFormField(
decoration: InputDecoration(
labelText: "Base value in bpm",
helperText: "e.g. maximum heart rate, threshold heart rate",
),
initialValue: widget.heartRateZoneSchema.db.base.toString(),
keyboardType: TextInputType.number,
onChanged: (value) => updateHeartRateZoneBase(base: int.parse(value)),
),
SizedBox(height: 10),
DataTable(
headingRowHeight: kMinInteractiveDimension * 0.80,
dataRowHeight: kMinInteractiveDimension * 0.75,
columnSpacing: 20,
horizontalMargin: 10,
columns: <DataColumn>[
DataColumn(label: Text("Zone")),
DataColumn(label: Text("Limits (W)")),
DataColumn(label: Text("Color")),
DataColumn(label: Text("Edit")),
],
rows: heartRateZones.map((HeartRateZone heartRateZone) {
return DataRow(
key: Key(heartRateZone.db.id.toString()),
cells: [
DataCell(Text(heartRateZone.db.name)),
DataCell(Text(heartRateZone.db.lowerLimit.toString() +
" - " +
heartRateZone.db.upperLimit.toString())),
DataCell(CircleColor(
circleSize: 20,
elevation: 0,
color: Color(heartRateZone.db.color),
)),
DataCell(
MyIcon.edit,
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddHeartRateZoneScreen(
heartRateZone: heartRateZone,
base: widget.heartRateZoneSchema.db.base,
),
),
);
getData();
},
)
],
);
}).toList(),
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
MyButton.add(
child: Text("Add heart rate zone"),
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddHeartRateZoneScreen(
heartRateZone:
HeartRateZone(heartRateZoneSchema: widget.heartRateZoneSchema),
base: widget.heartRateZoneSchema.db.base,
),
),
);
getData();
},
),
],
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
MyButton.delete(
onPressed: () => deleteHeartRateZoneSchema(
heartRateZoneSchema: widget.heartRateZoneSchema,
),
),
SizedBox(width: 5),
MyButton.cancel(onPressed: () => Navigator.of(context).pop()),
SizedBox(width: 5),
MyButton.save(onPressed: () => saveHeartRateZoneSchema(context)),
],
),
],
),
);
}
saveHeartRateZoneSchema(BuildContext context) async {
await widget.heartRateZoneSchema.db.save();
await DbHeartRateZone()
.upsertAll(heartRateZones.map((heartRateZone) => heartRateZone.db).toList());
Navigator.of(context).pop();
}
getData() async {
heartRateZones = await widget.heartRateZoneSchema.heartRateZones;
setState(() {});
}
deleteHeartRateZoneSchema({HeartRateZoneSchema heartRateZoneSchema}) async {
await heartRateZoneSchema.delete();
Navigator.of(context).pop();
}
updateHeartRateZoneBase({int base}) {
setState(() {
widget.heartRateZoneSchema.db.base = base;
for (HeartRateZone heartRateZone in heartRateZones) {
heartRateZone.db.lowerLimit =
(heartRateZone.db.lowerPercentage * base / 100).round();
heartRateZone.db.upperLimit =
(heartRateZone.db.upperPercentage * base / 100).round();
}
});
}
copyHeartRateZoneSchema({DateTime date}) async {
widget.heartRateZoneSchema.db
..date = date
..id = null;
int heartRateZoneSchemaId = await widget.heartRateZoneSchema.db.save();
for (HeartRateZone heartRateZone in heartRateZones) {
heartRateZone.db
..heartRateZoneSchemataId = heartRateZoneSchemaId
..id = null;
}
await DbHeartRateZone()
.upsertAll(heartRateZones.map((heartRateZone) => heartRateZone.db).toList());
await getData();
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text("Heart Rate Zone Schema has been copied"),
content: Text(
"If you only wanted to fix the date you need to delete the old heart rate zone schema manually."),
actions: [
FlatButton(
child: Text("OK"),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
}
}
import 'package:encrateia/utils/my_button.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/heart_rate_zone.dart';
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
class AddHeartRateZoneScreen extends StatefulWidget {
final HeartRateZone heartRateZone;
final int base;
const AddHeartRateZoneScreen({
Key key,
this.heartRateZone,
this.base,
}) : super(key: key);
@override
_AddHeartRateZoneScreenState createState() => _AddHeartRateZoneScreenState();
}
class _AddHeartRateZoneScreenState extends State<AddHeartRateZoneScreen> {
void _openDialog(Widget content) {
showDialog(
context: context,
builder: (_) {
return AlertDialog(
contentPadding: const EdgeInsets.all(6.0),
title: Text("Select Color"),
content: content,
actions: [
MyButton.cancel(onPressed: Navigator.of(context).pop),
MyButton.save(
child: Text('Select'),
onPressed: () {
Navigator.of(context).pop();
MaterialColorPicker(
onColorChange: (color) =>
widget.heartRateZone.db.color = color.value,
selectedColor: Color(widget.heartRateZone.db.color));
},
),
],
);
},
);
}
void openColorPicker() async {
_openDialog(
MaterialColorPicker(
selectedColor: Color(widget.heartRateZone.db.color),
onColorChange: (color) => setState(() => widget.heartRateZone.db.color = color.value),
onBack: () => {},
),
);
}
@override
Widget build(BuildContext context) {
var lowerLimitController =
TextEditingController(text: widget.heartRateZone.db.lowerLimit.toString());
var upperLimitController =
TextEditingController(text: widget.heartRateZone.db.upperLimit.toString());
var lowerPercentageController = TextEditingController(
text: widget.heartRateZone.db.lowerPercentage.toString());
var upperPercentageController = TextEditingController(
text: widget.heartRateZone.db.upperPercentage.toString());
return Scaffold(
appBar: AppBar(
title: Text('Add your HeartRateZone'),
),
body: ListView(
padding: EdgeInsets.all(20),
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: "Name"),
initialValue: widget.heartRateZone.db.name,
onChanged: (value) => widget.heartRateZone.db.name = value,
),
TextFormField(
decoration: InputDecoration(labelText: "Lower Limit in bpm"),
controller: lowerLimitController,
keyboardType: TextInputType.number,
onChanged: (value) {
widget.heartRateZone.db.lowerLimit = int.parse(value);
widget.heartRateZone.db.lowerPercentage =
(int.parse(value) * 100 / widget.base).round();
lowerPercentageController.text =
(int.parse(value) * 100 / widget.base).round().toString();
},
),
TextFormField(
decoration: InputDecoration(labelText: "Upper Limit in bpm"),
controller: upperLimitController,
keyboardType: TextInputType.number,
onChanged: (value) {
widget.heartRateZone.db.upperLimit = int.parse(value);
widget.heartRateZone.db.upperPercentage =
(int.parse(value) * 100 / widget.base).round();
upperPercentageController.text =
(int.parse(value) * 100 / widget.base).round().toString();
},
),
TextFormField(
decoration: InputDecoration(labelText: "Lower Percentage in %"),
controller: lowerPercentageController,
keyboardType: TextInputType.number,
onChanged: (value) {
widget.heartRateZone.db.lowerPercentage = int.parse(value);
widget.heartRateZone.db.lowerLimit =
(int.parse(value) * widget.base / 100).round();
lowerLimitController.text =
(int.parse(value) * widget.base / 100).round().toString();
},
),
TextFormField(
decoration: InputDecoration(labelText: "Upper Percentage in %"),
controller: upperPercentageController,
keyboardType: TextInputType.number,
onChanged: (value) {
widget.heartRateZone.db.upperPercentage = int.parse(value);
widget.heartRateZone.db.upperLimit =
(int.parse(value) * widget.base / 100).round();
upperLimitController.text =
(int.parse(value) * widget.base / 100).round().toString();
},
),
SizedBox(height: 10),
Row(children: [
Text("Color"),
Spacer(),
CircleAvatar(
backgroundColor: Color(widget.heartRateZone.db.color),
radius: 20.0,
),
Spacer(),
MyButton.detail(
onPressed: openColorPicker,
child: Text('Edit'),
),
]),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
MyButton.delete(onPressed: () => deleteHeartRateZone(context)),
SizedBox(width: 5),
MyButton.cancel(onPressed: () => Navigator.of(context).pop()),
SizedBox(width: 5),
MyButton.save(onPressed: () => saveHeartRateZone(context)),
],
),
],
),
);
}
saveHeartRateZone(BuildContext context) async {
await widget.heartRateZone.db.save();
Navigator.of(context).pop();
}
deleteHeartRateZone(BuildContext context) async {
await widget.heartRateZone.db.delete();
Navigator.of(context).pop();
}
}
import 'package:encrateia/utils/my_color.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_power_ratio_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_power_zone_schema_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_heart_rate_zone_schema_widget.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/activity.dart';
......@@ -126,6 +127,12 @@ class _ShowAthleteScreenState extends State<ShowAthleteScreen> {
icon: MyIcon.power,
nextWidget: AthletePowerZoneSchemaWidget(athlete: widget.athlete),
),
navigationButton(
color: MyColor.settings,
title: "Heart Rate\nZone Schemas",
icon: MyIcon.heartRate,
nextWidget: AthleteHeartRateZoneSchemaWidget(athlete: widget.athlete),
),
navigationButton(
color: MyColor.settings,
title: "Settings",
......
import 'package:encrateia/screens/add_heart_rate_zone_schema_screen.dart';
import 'package:encrateia/utils/my_button.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/heart_rate_zone_schema.dart';
import 'package:encrateia/utils/icon_utils.dart';
import 'package:intl/intl.dart';
class AthleteHeartRateZoneSchemaWidget extends StatefulWidget {
final Athlete athlete;
AthleteHeartRateZoneSchemaWidget({this.athlete});
@override
_AthleteHeartRateZoneSchemaWidgetState createState() =>
_AthleteHeartRateZoneSchemaWidgetState();
}
class _AthleteHeartRateZoneSchemaWidgetState
extends State<AthleteHeartRateZoneSchemaWidget> {
List<HeartRateZoneSchema> heartRateZoneSchemas = [];
int offset = 0;
int rows;
@override
void initState() {