Commit 4984a5d

derdilla <derdilla06@gmail.com>
2023-08-27 15:37:06
implement horizontal lines
1 parent dbfa86e
lib/components/input_dialoge.dart
@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:provider/provider.dart';
 
+// TODO: redo dialoges in flutter style
 class InputDialoge extends StatefulWidget {
   final String hintText;
   final void Function(String text) onSubmit;
lib/components/measurement_graph.dart
@@ -3,6 +3,7 @@ import 'dart:math';
 import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/components/display_interval_picker.dart';
 import 'package:blood_pressure_app/model/blood_pressure.dart';
+import 'package:blood_pressure_app/model/horizontal_graph_line.dart';
 import 'package:blood_pressure_app/model/settings_store.dart';
 import 'package:collection/collection.dart';
 import 'package:fl_chart/fl_chart.dart';
@@ -42,6 +43,8 @@ class _LineChartState extends State<_LineChart> {
                     // calculate lines for graph
                     List<FlSpot> pulSpots = [], diaSpots = [], sysSpots = [];
                     int maxValue = 0;
+                    double? graphBegin;
+                    double? graphEnd;
                     for (var e in data) {
                       final x = e.creationTime.millisecondsSinceEpoch.toDouble();
                       if (e.diastolic != null) {
@@ -56,9 +59,13 @@ class _LineChartState extends State<_LineChart> {
                         pulSpots.add(FlSpot(x, e.pulse!.toDouble()));
                         maxValue = max(maxValue, e.pulse!);
                       }
+                      graphBegin ??= x;
+                      graphEnd ??= x;
+                      if (x < graphBegin) graphBegin = x;
+                      if (x > graphEnd) graphEnd = x;
                     }
 
-                    if (diaSpots.length < 2 && sysSpots.length < 2 && pulSpots.length < 2) {
+                    if (diaSpots.length < 2 && sysSpots.length < 2 && pulSpots.length < 2 || graphBegin == null || graphEnd == null) {
                       return Text(AppLocalizations.of(context)!.errNotEnoughDataToGraph);
                     }
 
@@ -86,6 +93,8 @@ class _LineChartState extends State<_LineChart> {
                                   _buildRegressionLine(diaSpots),
                                 if (settings.drawRegressionLines)
                                   _buildRegressionLine(pulSpots),
+                                for (final horizontalLine in settings.horizontalGraphLines)
+                                  _buildHorizontalLine(horizontalLine, graphBegin!, graphEnd!),
                               ]
                           ),
                         );
@@ -197,6 +206,21 @@ class _LineChartState extends State<_LineChart> {
       ),
     );
   }
+
+  LineChartBarData _buildHorizontalLine(HorizontalGraphLine line, double start, double end) {
+    return LineChartBarData(
+      color: line.color,
+      spots: [
+        FlSpot(start, line.height.toDouble()),
+        FlSpot(end, line.height.toDouble())
+      ],
+      barWidth: 1,
+      dotData: const FlDotData(
+        show: false,
+      ),
+      dashArray: [10,5,]
+    );
+  }
 }
 
 class MeasurementGraph extends StatelessWidget {
lib/l10n/app_en.arb
@@ -409,5 +409,11 @@
   "startWithAddMeasurementPage": "Measurement on launch",
   "@startWithAddMeasurementPage": {},
   "startWithAddMeasurementPageDescription": "Upon app launch, measurement input screen shown.",
-  "@startWithAddMeasurementPageDescription": {}
+  "@startWithAddMeasurementPageDescription": {},
+  "horizontalLines": "Horizontal lines",
+  "@horizontalLines": {},
+  "linePositionY": "Line position (y)",
+  "@linePositionY": {},
+  "customGraphMarkings": "Custom markings",
+  "@customGraphMarkings": {}
 }
lib/model/horizontal_graph_line.dart
@@ -0,0 +1,17 @@
+import 'package:flutter/material.dart';
+
+class HorizontalGraphLine {
+  Color color;
+  int height;
+
+  HorizontalGraphLine(this.color, this.height);
+
+  HorizontalGraphLine.fromJson(Map<String, dynamic> json)
+      : color = Color(json['color']),
+        height = json['height'];
+
+  Map<String, dynamic> toJson() => {
+    'color': color.value,
+    'height': height,
+  };
+}
\ No newline at end of file
lib/model/ram_only_implementations.dart
@@ -508,15 +508,15 @@ class RamSettings extends ChangeNotifier implements Settings {
     notifyListeners();
   }
 
-  Iterable<int> _horizontalGraphLines = [];
+  Iterable<HorizontalGraphLine> _horizontalGraphLines = [];
 
   @override
-  Iterable<int> get horizontalGraphLines {
+  Iterable<HorizontalGraphLine> get horizontalGraphLines {
     return _horizontalGraphLines;
   }
 
   @override
-  set horizontalGraphLines(Iterable<int> value) {
+  set horizontalGraphLines(Iterable<HorizontalGraphLine> value) {
     _horizontalGraphLines = value;
     notifyListeners();
   }
lib/model/settings_store.dart
@@ -1,6 +1,9 @@
+import 'dart:convert';
+
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/export_import.dart';
 import 'package:blood_pressure_app/model/export_options.dart';
+import 'package:blood_pressure_app/model/horizontal_graph_line.dart';
 import 'package:file_saver/file_saver.dart' show MimeType;
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -615,13 +618,13 @@ class Settings extends ChangeNotifier {
     notifyListeners();
   }
 
-  Iterable<int> get horizontalGraphLines {
+  Iterable<HorizontalGraphLine> get horizontalGraphLines {
     final linesStr = _prefs.getStringList('horizontalGraphLines') ?? [];
-    return linesStr.map((e) => int.parse(e));
+    return linesStr.map((e) => HorizontalGraphLine.fromJson(jsonDecode(e)));
   }
 
-  set horizontalGraphLines(Iterable<int> value) {
-    _prefs.setStringList('horizontalGraphLines', value.map((e) => e.toString()).toList());
+  set horizontalGraphLines(Iterable<HorizontalGraphLine> value) {
+    _prefs.setStringList('horizontalGraphLines', value.map((e) => jsonEncode(e)).toList());
     notifyListeners();
   }
 }
lib/screens/subsettings/graph_markings.dart
@@ -1,58 +1,86 @@
 import 'package:blood_pressure_app/components/input_dialoge.dart';
+import 'package:blood_pressure_app/model/horizontal_graph_line.dart';
+import 'package:blood_pressure_app/model/settings_store.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
 import 'package:provider/provider.dart';
 
-import '../../model/settings_store.dart';
-
 class GraphMarkingsScreen extends StatelessWidget {
   const GraphMarkingsScreen({super.key});
 
   @override
   Widget build(BuildContext context) {
+    final localizations = AppLocalizations.of(context)!;
+     // IMPORTANT: When adding more option, like vertical lines, add navigation bar
     return Scaffold(
       appBar: AppBar(
+        title: Text(AppLocalizations.of(context)!.customGraphMarkings),
         backgroundColor: Theme.of(context).primaryColor,
       ),
       body: Center(child: Consumer<Settings>(
         builder: (context, settings, child) {
           final lines = settings.horizontalGraphLines.toList();
-          return Container(
-            padding: const EdgeInsets.all(20.0),
-            child: ListView.builder(
-              itemCount: lines.length+1,
-              itemBuilder: (context, i) {
-                if(i == 0) {
-                  return Row(
-                  children: [
-                    Text('Horizontal Lines'), // TODO localize, implement
-                    const Spacer(),
-                    IconButton(
-                      icon: const Icon(Icons.add),
-                      onPressed: () {
-                        showDialog(
-                          context: context,
-                          builder: (context) => NumberInputDialoge(
-                              hintText: 'hintText',
-                              onParsableSubmit: (value) {
-                                lines.add(value);
-                                Navigator.of(context).pop();
-                              }
-                          ),
-                        );
-                      },
-                    )
-                  ],
+          return ListView.builder(
+            itemCount: lines.length + 2, // support first and last row
+            itemBuilder: (context, i) {
+              if(i == 0) { // first row
+                return Container(
+                  padding: const EdgeInsets.all(10),
+                  child: DefaultTextStyle.merge(
+                    child: Text(localizations.horizontalLines),
+                    style: Theme.of(context).textTheme.headlineLarge
+                  ),
                 );
-                }
-                return Row(
-                  children: [
-                    Text(lines[i-1].toString()),
-                    Spacer(),
-                    Icon(Icons.delete)
-                  ],
+              }
+              if (i > lines.length) { // last row
+                return ListTile(
+                  leading: const Icon(Icons.add),
+                  onTap: () async {
+                    final color = await showDialog<Color>(context: context,
+                        builder: (context) => SimpleDialog(
+                          children: [MaterialColorPicker(
+                              circleSize: 53,
+                              onMainColorChange: (color) {
+                                Navigator.of(context).pop(color);
+                              },
+                            )],));
+                    if (!context.mounted) return;
+                    final height = await showDialog<int>(context: context,
+                      builder: (context) => NumberInputDialoge(
+                        hintText: localizations.linePositionY,
+                        onParsableSubmit: (value) {
+                          Navigator.of(context).pop(value);
+                        }
+                      )
+                    );
+
+                    if (color == null || height == null) return;
+                    lines.add(HorizontalGraphLine(color, height));
+                    settings.horizontalGraphLines = lines;
+                  },
+                  title: Text(localizations.addEntry),
                 );
               }
-            )
+              return ListTile(
+                leading: Container(
+                  width: 40.0,
+                  height: 40.0,
+                  decoration: BoxDecoration(
+                    color: lines[i-1].color,
+                    shape: BoxShape.circle,
+                  ),
+                ),
+                title: Text(lines[i-1].height.toString()),
+                trailing: IconButton(
+                  icon: Icon(Icons.delete),
+                  onPressed: () {
+                    lines.removeAt(i-1);
+                    settings.horizontalGraphLines = lines;
+                  },
+                ),
+              );
+            }
           );
         }),
       ),
lib/screens/settings.dart
@@ -258,8 +258,7 @@ class SettingsPage extends StatelessWidget {
               ),
               SettingsTile(
                 key: const Key('GraphMarkingsScreen'),
-                title: Text(AppLocalizations.of(context)!.text),
-                description: Text(AppLocalizations.of(context)!.text), // TODO desc u title
+                title: Text(AppLocalizations.of(context)!.customGraphMarkings),
                 leading: const Icon(Icons.legend_toggle_outlined),
                 trailing: const Icon(Icons.arrow_forward_ios),
                 onPressed: (context) {