Commit 0e13f6a
Changed files (4)
app
lib
components
features
l10n
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