Commit 16091f7

derdilla <derdilla06@gmail.com>
2023-05-06 12:49:18
FEAT: modifiable time format string
1 parent 771102d
lib/components/measurement_list.dart
@@ -69,42 +69,47 @@ class MeasurementList extends StatelessWidget {
   }
 
   Widget buildListItem(BloodPressureRecord record) {
-    final DateFormat formater = DateFormat('yy-MM-dd H:mm:s');
-    return Container(
-      margin: const EdgeInsets.only(bottom: 5),
-      child: Row(
-          children: [
-            Expanded(
-              flex: _sideFlex,
-              child: const SizedBox(),
-            ),
-            Expanded(
-                flex: _tableElementsSizes[0],
-                child: Text(formater.format(record.creationTime))
-            ),
-            Expanded(
-                flex: _tableElementsSizes[1],
-                child: Text(record.systolic.toString())
-            ),
-            Expanded(
-                flex: _tableElementsSizes[2],
-                child: Text(record.diastolic.toString())
-            ),
-            Expanded(
-                flex: _tableElementsSizes[3],
-                child: Text(record.pulse.toString())
-            ),
-            Expanded(
-                flex: _tableElementsSizes[4],
-                child: Text(record.notes)
-            ),
-            Expanded(
-              flex: _sideFlex,
-              child: const SizedBox(),
+    return Consumer<Settings>(
+        builder: (context, settings, child) {
+          final formatter = DateFormat(settings.dateFormatString);
+          return Container(
+            margin: const EdgeInsets.only(bottom: 5),
+            child: Row(
+                children: [
+                  Expanded(
+                    flex: _sideFlex,
+                    child: const SizedBox(),
+                  ),
+                  Expanded(
+                      flex: _tableElementsSizes[0],
+                      child: Text(formatter.format(record.creationTime))
+                  ),
+                  Expanded(
+                      flex: _tableElementsSizes[1],
+                      child: Text(record.systolic.toString())
+                  ),
+                  Expanded(
+                      flex: _tableElementsSizes[2],
+                      child: Text(record.diastolic.toString())
+                  ),
+                  Expanded(
+                      flex: _tableElementsSizes[3],
+                      child: Text(record.pulse.toString())
+                  ),
+                  Expanded(
+                      flex: _tableElementsSizes[4],
+                      child: Text(record.notes)
+                  ),
+                  Expanded(
+                    flex: _sideFlex,
+                    child: const SizedBox(),
+                  ),
+                ]
             ),
-          ]
-      ),
+          );
+        }
     );
+
   }
 
 
@@ -161,8 +166,3 @@ class MeasurementList extends StatelessWidget {
         });
   }
 }
-
-class _MeasuredValueListItem {
-
-
-}
lib/model/settings.dart
@@ -19,6 +19,7 @@ class Settings extends ChangeNotifier {
   late MaterialColor _diaColor;
   late MaterialColor _pulColor;
   late bool _allowManualTimeInput;
+  late String _dateFormatString;
 
   Settings._create();
   Future<void> _asyncInit() async {
@@ -65,6 +66,7 @@ class Settings extends ChangeNotifier {
     var pDiaColor = _getSetting('_diaColor');
     var pPulColor = _getSetting('_pulColor');
     var pAllowManualTimeInput = _getSetting('_allowManualTimeInput');
+    var pDateFormatString = _getSetting('_dateFormatString');
     // var ...
 
     _graphStepSize = (await pGraphStepSize as int?) ?? TimeStep.day;
@@ -77,6 +79,7 @@ class Settings extends ChangeNotifier {
     _diaColor = createMaterialColor(await pDiaColor as int? ?? 0xFF4CAF50);
     _pulColor = createMaterialColor(await pPulColor as int? ?? 0xFFF44336);
     _allowManualTimeInput = ((await pAllowManualTimeInput as int?) ?? 1) == 1 ? true : false;
+    _dateFormatString = (await pDateFormatString as String?) ?? 'yy-MM-dd H:mm';
     // ...
     return;
   }
@@ -184,6 +187,14 @@ class Settings extends ChangeNotifier {
     _saveSetting('_allowManualTimeInput', newSetting ? 1 : 0);
     notifyListeners();
   }
+  String get dateFormatString {
+    return _dateFormatString;
+  }
+  set dateFormatString(String newFormatString) {
+    _dateFormatString = newFormatString;
+    _saveSetting('_dateFormatString', newFormatString);
+    notifyListeners();
+  }
 
 }
 
lib/screens/add_measurement.dart
@@ -14,7 +14,6 @@ class AddMeasurementPage extends StatefulWidget {
 }
 
 class _AddMeasurementPageState extends State<AddMeasurementPage> {
-  final _formatter = DateFormat('yy-MM-dd H:mm:s');
   final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
   DateTime _time = DateTime.now();
   int _systolic = -1;
@@ -39,9 +38,10 @@ class _AddMeasurementPageState extends State<AddMeasurementPage> {
               children: [
                 Consumer<Settings>(
                     builder: (context, settings, child) {
+                      final formatter = DateFormat(settings.dateFormatString);
                       if(settings.allowManualTimeInput) {
                         return TextFormField(
-                          initialValue: _formatter.format(_time),
+                          initialValue: formatter.format(_time),
                           decoration: const InputDecoration(
                               hintText: 'time'
                           ),
@@ -50,9 +50,9 @@ class _AddMeasurementPageState extends State<AddMeasurementPage> {
                               return 'Please enter a value';
                             } else {
                               try {
-                                _time = _formatter.parse(value);
+                                _time = formatter.parse(value);
                               } on FormatException {
-                                return 'date format: ${_formatter.pattern}';
+                                return 'date format: ${formatter.pattern}';
                               }
                             }
                             return null;
lib/screens/enter_timeformat.dart
@@ -0,0 +1,106 @@
+import 'package:blood_pressure_app/model/blood_pressure.dart';
+import 'package:blood_pressure_app/model/settings.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:intl/intl.dart';
+import 'package:provider/provider.dart';
+import 'package:url_launcher/url_launcher.dart' show launch;
+
+
+class EnterTimeFormatScreen extends StatefulWidget {
+  const EnterTimeFormatScreen({super.key});
+
+  @override
+  State<EnterTimeFormatScreen> createState() => _EnterTimeFormatScreenState();
+}
+
+class _EnterTimeFormatScreenState extends State<EnterTimeFormatScreen> {
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+  final _firstNode = FocusNode();
+  late String _newVal;
+
+  @override
+  Widget build(BuildContext context) {
+    _firstNode.requestFocus();
+    return Scaffold(
+      body: Center(
+        child: Form(
+          key: _formKey,
+          child: Container(
+            padding: const EdgeInsets.all(90.0),
+            child: Column(
+              mainAxisAlignment: MainAxisAlignment.center,
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: [
+                const Text('A formatter String consists of a mixture of predefined ICU/Skeleton Strings and any other text you want to include.'),
+                const SizedBox(height: 5,),
+                RichText(
+                  text: TextSpan(
+                    text: 'For a full list of valid formats please look here.',
+                    style: const TextStyle(color: Colors.blue),
+                    recognizer: TapGestureRecognizer()
+                      ..onTap = () { launch('https://pub.dev/documentation/intl/latest/intl/DateFormat-class.html');
+                      },
+                  ),
+                ),
+                const SizedBox(height: 7,),
+                const Text('Please note that having longer/shorter format Strings wont magically change the width of the table columns, so it might come to awkward line breaks.'),
+                const SizedBox(height: 7,),
+                const Text('default: "yy-MM-dd H:mm"'),
+
+                const SizedBox(height: 10,),
+                Consumer<Settings>(
+                    builder: (context, settings, child) {
+                      _newVal = settings.dateFormatString;
+                      return TextFormField(
+                        initialValue: _newVal,
+                        decoration: const InputDecoration(
+                            hintText: 'format string'
+                        ),
+                        validator: (String? value) {
+                          if (value == null || value.isEmpty) {
+                            return 'Please enter a value';
+                          } else {
+                            _newVal = value;
+                          }
+                          return null;
+                        },
+                      );
+                    }
+                ),
+                SizedBox(height: 25,),
+                Row(
+                  children: [
+                    ElevatedButton(
+                        onPressed: () {
+                          Navigator.of(context).pop();
+                        },
+                        style: ElevatedButton.styleFrom(
+                            backgroundColor: Theme.of(context).unselectedWidgetColor
+                        ),
+                        child: const Text('CANCEL')),
+                    const Spacer(),
+                    ElevatedButton(
+                        onPressed: () {
+                          if (_formKey.currentState!.validate()) {
+                            Provider.of<Settings>(context, listen: false).dateFormatString = _newVal;
+                            Navigator.of(context).pop();
+                          }
+                        },
+                        style: ElevatedButton.styleFrom(
+                            backgroundColor: Theme.of(context).primaryColor
+                        ),
+                        child: const Text('SAVE'))
+                  ],
+                )
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+  
+
+}
\ No newline at end of file
lib/screens/settings.dart
@@ -1,6 +1,7 @@
 import 'package:blood_pressure_app/components/complex_settings.dart';
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/settings.dart';
+import 'package:blood_pressure_app/screens/enter_timeformat.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:settings_ui/settings_ui.dart';
@@ -17,93 +18,110 @@ class SettingsScreen extends StatelessWidget {
       ),
       body: Consumer<Settings>(
           builder: (context, settings, child) {
-            return SettingsList(sections: [
-              SettingsSection(
-                  title: const Text('layout'),
-                  tiles: <SettingsTile>[
-                    SettingsTile.switchTile(
-                        initialValue: settings.allowManualTimeInput,
-                        onToggle: (value) {
-                          settings.allowManualTimeInput = value;
-                        },
-                        leading: const Icon(Icons.details),
-                        title: const Text('allow manual time input')
-                    ),
-                    SettingsTile.switchTile(
-                        initialValue: settings.followSystemDarkMode,
-                        onToggle: (value) {
-                          settings.followSystemDarkMode = value;
-                        },
-                        leading: const Icon(Icons.auto_mode),
-                        title: const Text('follow system dark mode')
-                    ),
-                    SettingsTile.switchTile(
-                      initialValue: (() {
-                          if (settings.followSystemDarkMode) {
-                            return MediaQuery.of(context).platformBrightness == Brightness.dark;
-                          }
-                          return settings.darkMode;
-                        })(),
-                        onToggle: (value) {
-                          settings.darkMode = value;
+            return SettingsList(
+              darkTheme: SettingsThemeData(
+                settingsListBackground: Theme.of(context).canvasColor,
+                dividerColor: Theme.of(context).dividerColor,
+              ),
+              sections: [
+                SettingsSection(
+                    title: const Text('layout'),
+                    tiles: <SettingsTile>[
+                      SettingsTile.switchTile(
+                          initialValue: settings.allowManualTimeInput,
+                          onToggle: (value) {
+                            settings.allowManualTimeInput = value;
+                          },
+                          leading: const Icon(Icons.details),
+                          title: const Text('allow manual time input')
+                      ),
+                      SettingsTile(
+                        title: const Text('time format'),
+                        leading: const Icon(Icons.schedule),
+                        trailing: Icon(Icons.arrow_forward_ios, color: Theme.of(context).highlightColor,),
+                        description: Text(settings.dateFormatString),
+                        onPressed: (context) {
+                          Navigator.push(
+                            context,
+                            MaterialPageRoute(builder: (context) => EnterTimeFormatScreen()),
+                          );
                         },
-                      leading: const Icon(Icons.dark_mode),
-                      title: const Text('enable dark mode'),
-                      enabled: !settings.followSystemDarkMode,
+                      ),
+                      SettingsTile.switchTile(
+                          initialValue: settings.followSystemDarkMode,
+                          onToggle: (value) {
+                            settings.followSystemDarkMode = value;
+                          },
+                          leading: const Icon(Icons.auto_mode),
+                          title: const Text('follow system dark mode')
+                      ),
+                      SettingsTile.switchTile(
+                        initialValue: (() {
+                            if (settings.followSystemDarkMode) {
+                              return MediaQuery.of(context).platformBrightness == Brightness.dark;
+                            }
+                            return settings.darkMode;
+                          })(),
+                          onToggle: (value) {
+                            settings.darkMode = value;
+                          },
+                        leading: const Icon(Icons.dark_mode),
+                        title: const Text('enable dark mode'),
+                        enabled: !settings.followSystemDarkMode,
+                      ),
+                      //settings.accentColor = settings.createMaterialColor((color ?? Colors.teal).value);
+                      ColorSelectionTile(
+                          onMainColorChanged: (color) => settings.accentColor = settings.createMaterialColor((color ?? Colors.teal).value),
+                          initialColor: settings.accentColor,
+                          title: const Text('theme color')
+                      ).build(context),
+                      ColorSelectionTile(
+                          onMainColorChanged: (color) => settings.sysColor = settings.createMaterialColor((color ?? Colors.green).value),
+                          initialColor: settings.sysColor,
+                          title: const Text('systolic color')
+                      ).build(context),
+                      ColorSelectionTile(
+                          onMainColorChanged: (color) => settings.diaColor = settings.createMaterialColor((color ?? Colors.teal).value),
+                          initialColor: settings.diaColor,
+                          title: const Text('diastolic color')
+                      ).build(context),
+                      ColorSelectionTile(
+                          onMainColorChanged: (color) => settings.pulColor = settings.createMaterialColor((color ?? Colors.red).value),
+                          initialColor: settings.pulColor,
+                          title: const Text('pulse color')
+                      ).build(context),
+                    ]
+                ),
+                SettingsSection(
+                  title: const Text('data'),
+                  tiles: <SettingsTile>[
+                    SettingsTile(
+                      title: const Text('export'),
+                      leading: const Icon(Icons.save),
+                      onPressed: (context) =>  Provider.of<BloodPressureModel>(context, listen: false).save((success, msg) {
+                        if (success && msg != null) {
+                          ScaffoldMessenger.of(context).showSnackBar(
+                            SnackBar(content: Text(msg)));
+                        } else if (!success && msg != null) {
+                          ScaffoldMessenger.of(context).showSnackBar(
+                              SnackBar(content: Text('Error: $msg')));
+                        }
+                      }),
                     ),
-                    //settings.accentColor = settings.createMaterialColor((color ?? Colors.teal).value);
-                    ColorSelectionTile(
-                        onMainColorChanged: (color) => settings.accentColor = settings.createMaterialColor((color ?? Colors.teal).value),
-                        initialColor: settings.accentColor,
-                        title: const Text('theme color')
-                    ).build(context),
-                    ColorSelectionTile(
-                        onMainColorChanged: (color) => settings.sysColor = settings.createMaterialColor((color ?? Colors.green).value),
-                        initialColor: settings.sysColor,
-                        title: const Text('systolic color')
-                    ).build(context),
-                    ColorSelectionTile(
-                        onMainColorChanged: (color) => settings.diaColor = settings.createMaterialColor((color ?? Colors.teal).value),
-                        initialColor: settings.diaColor,
-                        title: const Text('diastolic color')
-                    ).build(context),
-                    ColorSelectionTile(
-                        onMainColorChanged: (color) => settings.pulColor = settings.createMaterialColor((color ?? Colors.red).value),
-                        initialColor: settings.pulColor,
-                        title: const Text('pulse color')
-                    ).build(context),
-                  ]
-              ),
-              SettingsSection(
-                title: const Text('data'),
-                tiles: <SettingsTile>[
-                  SettingsTile(
-                    title: const Text('export'),
-                    leading: const Icon(Icons.save),
-                    onPressed: (context) =>  Provider.of<BloodPressureModel>(context, listen: false).save((success, msg) {
-                      if (success && msg != null) {
-                        ScaffoldMessenger.of(context).showSnackBar(
-                          SnackBar(content: Text(msg)));
-                      } else if (!success && msg != null) {
-                        ScaffoldMessenger.of(context).showSnackBar(
-                            SnackBar(content: Text('Error: $msg')));
-                      }
-                    }),
-                  ),
-                  SettingsTile(
-                    title: const Text('import'),
-                    leading: const Icon(Icons.file_upload),
-                    onPressed: (context) =>  Provider.of<BloodPressureModel>(context, listen: false).import((res, String? err) {
-                      if (res) {
+                    SettingsTile(
+                      title: const Text('import'),
+                      leading: const Icon(Icons.file_upload),
+                      onPressed: (context) =>  Provider.of<BloodPressureModel>(context, listen: false).import((res, String? err) {
+                        if (res) {
 
-                      } else {
-                        ScaffoldMessenger.of(context).showSnackBar(
-                            SnackBar(content: Text('Error: ${err ?? 'unknown error'}')));
-                      }
-                    }),
-                  ),
-                ],
-              )
+                        } else {
+                          ScaffoldMessenger.of(context).showSnackBar(
+                              SnackBar(content: Text('Error: ${err ?? 'unknown error'}')));
+                        }
+                      }),
+                    ),
+                  ],
+                )
             ]);
           }
       ),
pubspec.lock
@@ -486,7 +486,7 @@ packages:
     source: hosted
     version: "1.3.1"
   url_launcher:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: url_launcher
       sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e"
pubspec.yaml
@@ -42,6 +42,7 @@ dependencies:
   flutter_material_color_picker: ^1.1.0+2  # MIT
   file_picker: ^5.2.11  # MIT
   csv: ^5.0.2  # MIT
+  url_launcher: ^6.1.10  # BSD-3-Clause
 
 dev_dependencies:
   flutter_test: