Commit 0e13f6a

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-08-04 07:31:06
Split date and time input fields (#376)
* split date and time input fields Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * unify input style Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * simplify code Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * fix format Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> --------- Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent f1e7e44
Changed files (4)
app/lib/components/date_time_picker.dart
@@ -1,45 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-
-/// First shows a DatePicker for the day then shows a TimePicker for the time of
-/// day.
-///
-/// As per the decision of the material design team a TimePicker isn't able to
-/// limit the range (https://github.com/flutter/flutter/issues/23717#issuecomment-966601311),
-/// therefore a manual check for the time of day will be needed. Refer to the
-/// validator on the AddMeasurementPage for an example.
-Future<DateTime?> showDateTimePicker({
-  required BuildContext context,
-  DateTime? initialDate,
-  DateTime? firstDate,
-  DateTime? lastDate,
-}) async {
-  initialDate ??= DateTime.now();
-  firstDate ??= initialDate.subtract(const Duration(days: 365 * 100));
-  lastDate ??= firstDate.add(const Duration(days: 365 * 200));
-
-  final DateTime? selectedDate = await showDatePicker(
-    context: context,
-    initialDate: initialDate,
-    firstDate: firstDate,
-    lastDate: lastDate,
-    confirmText: AppLocalizations.of(context)!.btnNext,
-  );
-
-  if (selectedDate == null) return null;
-  if (!context.mounted) return null;
-
-  final TimeOfDay? selectedTime = await showTimePicker(
-    context: context,
-    initialTime: TimeOfDay.fromDateTime(initialDate),
-  );
-
-  if (selectedTime == null) return null;
-  return DateTime(
-    selectedDate.year,
-    selectedDate.month,
-    selectedDate.day,
-    selectedTime.hour,
-    selectedTime.minute,
-  );
-}
app/lib/features/input/forms/date_time_form.dart
@@ -1,23 +1,16 @@
-import 'dart:math';
-
-import 'package:blood_pressure_app/components/date_time_picker.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:intl/intl.dart';
 
 /// Input to allow date and time input.
-class DateTimeForm extends StatelessWidget {
+class DateTimeForm extends StatefulWidget {
   /// Create input to allow date and time input.
   const DateTimeForm({super.key,
-    required this.dateFormatString,
     required this.initialTime,
     required this.validate,
     required this.onTimeSelected,
   });
 
-  /// String to display datetime as
-  final String dateFormatString;
-
   /// Initial time to display
   final DateTime initialTime;
 
@@ -28,31 +21,66 @@ class DateTimeForm extends StatelessWidget {
   final void Function(DateTime time) onTimeSelected;
 
   @override
-  Widget build(BuildContext context) => ListTile(
-    title: Text(DateFormat(dateFormatString).format(initialTime)),
-    trailing: const Icon(Icons.edit),
-    onTap: () async {
-      final messenger = ScaffoldMessenger.of(context);
-      var selectedTime = await showDateTimePicker(
-        context: context,
-        firstDate: DateTime.fromMillisecondsSinceEpoch(1),
-        lastDate: DateTime.now(),
-        initialDate: initialTime,
-      );
-      if (selectedTime == null) {
-        return;
-      }
-      final now = DateTime.now();
-      if (validate && selectedTime.isAfter(now)) {
-        messenger.showSnackBar(SnackBar(
-          content: Text(AppLocalizations.of(context)!.errTimeAfterNow),),);
-        selectedTime = selectedTime.copyWith(
-          hour: max(selectedTime.hour, now.hour),
-          minute: max(selectedTime.minute, now.minute),
-        );
-      }
-      if (selectedTime != initialTime) onTimeSelected(selectedTime);
-    },
+  State<DateTimeForm> createState() => _DateTimeFormState();
+}
+
+class _DateTimeFormState extends State<DateTimeForm> {
+  Future<void> _openDatePicker() async {
+    final now = DateTime.now();
+    final date = await showDatePicker(
+      context: context,
+      initialDate: widget.initialTime,
+      firstDate: DateTime.fromMillisecondsSinceEpoch(1),
+      lastDate: widget.initialTime.isAfter(now) ? widget.initialTime : now,
+    );
+    if (date == null) return;
+    _validateAndInvoke(date.copyWith(
+      hour: widget.initialTime.hour,
+      minute: widget.initialTime.minute,
+    ));
+  }
+
+  Future<void> _openTimePicker() async {
+    final time = await showTimePicker(
+      context: context,
+      initialTime: TimeOfDay.fromDateTime(widget.initialTime),
+    );
+    if (time == null) return;
+    _validateAndInvoke(widget.initialTime.copyWith(
+      hour: time.hour,
+      minute: time.minute,
+    ));
+  }
+
+  void _validateAndInvoke(DateTime time) {
+    if (widget.validate && time.isAfter(DateTime.now())) {
+      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+        content: Text(AppLocalizations.of(context)!.errTimeAfterNow),
+      ));
+      return;
+    }
+    widget.onTimeSelected(time);
+  }
+
+  Widget _buildInput(String content, void Function() onTap, String label) => Expanded(
+    child: InputDecorator(
+      child: GestureDetector(onTap: onTap, child: Text(content)),
+      decoration: InputDecoration(
+        labelText: label,
+      ),
+    ),
   );
 
+  @override
+  Widget build(BuildContext context) {
+    final date = DateFormat('yyyy-MM-dd').format(widget.initialTime);
+    final time = DateFormat('HH:mm').format(widget.initialTime);
+    return Row(
+      children: [
+        _buildInput(date, _openDatePicker, AppLocalizations.of(context)!.date),
+        SizedBox(width: 8,),
+        _buildInput(time, _openTimePicker, AppLocalizations.of(context)!.time),
+      ],
+    );
+  }
 }
app/lib/features/input/add_measurement_dialoge.dart
@@ -186,14 +186,6 @@ class _AddEntryDialogeState extends State<AddEntryDialoge> {
     ),
   );
 
-  /// Build the border all fields have.
-  RoundedRectangleBorder _buildShapeBorder([Color? color]) =>
-      RoundedRectangleBorder(
-    side: Theme.of(context).inputDecorationTheme.border?.borderSide
-        ?? const BorderSide(width: 3),
-    borderRadius: BorderRadius.circular(20),
-  );
-
   @override
   Widget build(BuildContext context) {
     final localizations = AppLocalizations.of(context)!;
@@ -263,16 +255,12 @@ class _AddEntryDialogeState extends State<AddEntryDialoge> {
                 ),
               ),
             if (settings.allowManualTimeInput)
-              ListTileTheme(
-                shape: _buildShapeBorder(),
-                child: DateTimeForm(
-                  validate: settings.validateInputs,
-                  dateFormatString: settings.dateFormatString,
-                  initialTime: time,
-                  onTimeSelected: (newTime) => setState(() {
-                    time = newTime;
-                  }),
-                ),
+              DateTimeForm(
+                validate: settings.validateInputs,
+                initialTime: time,
+                onTimeSelected: (newTime) => setState(() {
+                  time = newTime;
+                }),
               ),
             Form(
               key: recordFormKey,
@@ -289,7 +277,7 @@ class _AddEntryDialogeState extends State<AddEntryDialoge> {
                         onSaved: (value) =>
                             setState(() => systolic = int.tryParse(value ?? '')),
                       ),
-                      const SizedBox(width: 16,),
+                      const SizedBox(width: 8,),
                       _buildValueInput(localizations, settings,
                         labelText: localizations.diaLong,
                         controller: diaController,
@@ -306,7 +294,7 @@ class _AddEntryDialogeState extends State<AddEntryDialoge> {
                           return null;
                         },
                       ),
-                      const SizedBox(width: 16,),
+                      const SizedBox(width: 8,),
                       _buildValueInput(localizations, settings,
                         labelText: localizations.pulLong,
                         controller: pulController,
@@ -331,13 +319,17 @@ class _AddEntryDialogeState extends State<AddEntryDialoge> {
                 maxLines: 4,
               ),
             ),
-            ColorSelectionListTile(
-              title: Text(localizations.color),
-              onMainColorChanged: (Color value) => setState(() {
-                color = (value == Colors.transparent) ? null : value;
-              }),
-              initialColor: color ?? Colors.transparent,
-              shape: _buildShapeBorder(color),
+            InputDecorator(
+              decoration: InputDecoration(
+                contentPadding: EdgeInsets.zero
+              ),
+              child: ColorSelectionListTile(
+                title: Text(localizations.color),
+                onMainColorChanged: (Color value) => setState(() {
+                  color = (value == Colors.transparent) ? null : value;
+                }),
+                initialColor: color ?? Colors.transparent,
+              ),
             ),
             if (widget.initialRecord == null && widget.availableMeds.isNotEmpty)
               Form(
app/lib/l10n/app_en.arb
@@ -510,5 +510,7 @@
     "deleteAllMedicineIntakes": "Delete all medicine intakes",
     "@deleteAllMedicineIntakes": {},
     "deleteAllNotes": "Delete all notes",
-    "@deleteAllNotes": {}
+    "@deleteAllNotes": {},
+    "date": "Date",
+    "@date": {}
 }
\ No newline at end of file