From 14ceed6665d1faaedab062537294441caaf79170 Mon Sep 17 00:00:00 2001 From: Satvik Sharma <94671974+SATVIKSH@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:26:33 +0530 Subject: [PATCH] Input time in textFields using keyboard (#153) * add time using in text field using keyboard and readme updated * Add Feature: Input Time in text fields using keayboard and readme updated --- README.md | 2 +- .../controllers/input_time_controller.dart | 90 ++++++++++ .../views/add_or_update_alarm_view.dart | 161 +++++++++++++++--- pubspec.lock | 44 +++-- windows/runner/flutter_window.cpp | 5 + 5 files changed, 258 insertions(+), 44 deletions(-) create mode 100644 lib/app/modules/addOrUpdateAlarm/controllers/input_time_controller.dart diff --git a/README.md b/README.md index 1105a73c..4a9c9596 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ The "Ultimate Alarm Clock" offers a user-friendly and versatile interface design - The floating action button in the Home View allows you to create alarms, directing you to the Add or Update Alarm View. - In this view, you can: - - Set the alarm time using a time picker. + - Set the alarm time using a time picker or manually entering time using keyboard. - Configure alarm repetition. - Set the snooze duration. - Add a label to the alarm. diff --git a/lib/app/modules/addOrUpdateAlarm/controllers/input_time_controller.dart b/lib/app/modules/addOrUpdateAlarm/controllers/input_time_controller.dart new file mode 100644 index 00000000..c615d8cb --- /dev/null +++ b/lib/app/modules/addOrUpdateAlarm/controllers/input_time_controller.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/controllers/add_or_update_alarm_controller.dart'; + +class InputTimeController extends GetxController { + AddOrUpdateAlarmController addOrUpdateAlarmController = + Get.find(); + final isTimePicker = false.obs; + TextEditingController inputHrsController = TextEditingController(); + TextEditingController inputMinutesController = TextEditingController(); + final selectedDateTime = DateTime.now().obs; + @override + void onInit() { + isTimePicker.value = true; + selectedDateTime.value = addOrUpdateAlarmController.selectedTime.value; + isAM.value = addOrUpdateAlarmController.selectedTime.value.hour < 12; + super.onInit(); + } + + final isAM = true.obs; + changePeriod(String period) { + isAM.value = period == 'AM'; + } + + changeDatePicker() { + isTimePicker.value = !isTimePicker.value; + } + + void setTime() { + int hour = int.parse(inputHrsController.text); + if (hour == 0 && inputHrsController.text.length == 2) { + inputHrsController.text = '12'; + } + + if (isAM.value) { + if (hour == 12) { + hour = hour - 12; + } + } else { + if (hour != 12) { + hour = hour + 12; + } + } + int minute = int.parse(inputMinutesController.text); + final time = TimeOfDay(hour: hour, minute: minute); + DateTime today = DateTime.now(); + DateTime tomorrow = today.add(const Duration(days: 1)); + bool isNextDay = (time.hour == today.hour && time.minute < today.minute) || + (time.hour < today.hour); + bool isNextMonth = isNextDay && (today.day > tomorrow.day); + bool isNextYear = isNextMonth && (today.month > tomorrow.month); + int day = isNextDay ? tomorrow.day : today.day; + int month = isNextMonth ? tomorrow.month : today.month; + int year = isNextYear ? tomorrow.month : today.month; + selectedDateTime.value = DateTime(year, month, day, time.hour, time.minute); + addOrUpdateAlarmController.selectedTime.value = selectedDateTime.value; + } + + @override + void onClose() { + inputHrsController.dispose(); + inputMinutesController.dispose(); + super.onClose(); + } +} + +class LimitRange extends TextInputFormatter { + LimitRange( + this.minRange, + this.maxRange, + ) : assert( + minRange < maxRange, + ); + + final int minRange; + final int maxRange; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + var value = int.parse(newValue.text); + if (value < minRange) { + return TextEditingValue(text: minRange.toString()); + } else if (value > maxRange) { + return TextEditingValue(text: maxRange.toString()); + } + return newValue; + } +} diff --git a/lib/app/modules/addOrUpdateAlarm/views/add_or_update_alarm_view.dart b/lib/app/modules/addOrUpdateAlarm/views/add_or_update_alarm_view.dart index d07e089d..1145f3bc 100644 --- a/lib/app/modules/addOrUpdateAlarm/views/add_or_update_alarm_view.dart +++ b/lib/app/modules/addOrUpdateAlarm/views/add_or_update_alarm_view.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_time_picker_spinner/flutter_time_picker_spinner.dart'; import 'package:get/get.dart'; import 'package:ultimate_alarm_clock/app/data/models/alarm_model.dart'; +import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/controllers/input_time_controller.dart'; import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/views/alarm_id_tile.dart'; import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/views/alarm_offset_tile.dart'; import 'package:ultimate_alarm_clock/app/modules/addOrUpdateAlarm/views/label_tile.dart'; @@ -28,6 +30,7 @@ class AddOrUpdateAlarmView extends GetView { AddOrUpdateAlarmView({Key? key}) : super(key: key); ThemeController themeController = Get.find(); + InputTimeController inputTimeController = Get.put(InputTimeController()); @override Widget build(BuildContext context) { @@ -250,31 +253,139 @@ class AddOrUpdateAlarmView extends GetView { : ListView( children: [ Container( - color: themeController.isLightMode.value - ? kLightSecondaryBackgroundColor - : ksecondaryBackgroundColor, - height: height * 0.32, - width: width, - child: TimePickerSpinner( - time: controller.selectedTime.value, - isForce2Digits: true, - alignment: Alignment.center, - is24HourMode: false, - normalTextStyle: - Theme.of(context).textTheme.displayMedium!.copyWith( - fontWeight: FontWeight.normal, - color: themeController.isLightMode.value - ? kLightPrimaryDisabledTextColor - : kprimaryDisabledTextColor, - ), - highlightedTextStyle: - Theme.of(context).textTheme.displayMedium, - onTimeChange: (dateTime) { - Utils.hapticFeedback(); - controller.selectedTime.value = dateTime; - }, - ), - ), + color: themeController.isLightMode.value + ? kLightSecondaryBackgroundColor + : ksecondaryBackgroundColor, + height: height * 0.32, + width: width, + child: Obx(() { + return InkWell( + onTap: () { + Utils.hapticFeedback(); + inputTimeController.changeDatePicker(); + }, + child: inputTimeController.isTimePicker.value + ? TimePickerSpinner( + time: controller.selectedTime.value, + isForce2Digits: true, + alignment: Alignment.center, + is24HourMode: false, + normalTextStyle: Theme.of(context) + .textTheme + .displayMedium! + .copyWith( + fontWeight: FontWeight.normal, + color: themeController + .isLightMode.value + ? kLightPrimaryDisabledTextColor + : kprimaryDisabledTextColor, + ), + highlightedTextStyle: Theme.of(context) + .textTheme + .displayMedium, + onTimeChange: (dateTime) { + Utils.hapticFeedback(); + controller.selectedTime.value = dateTime; + inputTimeController.inputHrsController + .text = dateTime.hour == + 0 + ? 12.toString() + : (dateTime.hour > 12 + ? (dateTime.hour - 12).toString() + : dateTime.hour.toString()); + inputTimeController.inputMinutesController + .text = dateTime.minute.toString(); + inputTimeController.changePeriod( + dateTime.hour >= 12 ? 'PM' : 'AM'); + }, + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 80, + child: TextField( + onChanged: (_) { + inputTimeController.setTime(); + }, + decoration: const InputDecoration( + hintText: 'HH', + border: InputBorder.none), + textAlign: TextAlign.center, + controller: inputTimeController + .inputHrsController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp( + '[1,2,3,4,5,6,7,8,9,0]')), + LengthLimitingTextInputFormatter(2), + LimitRange(0, 12) + ], + ), + ), + SizedBox( + width: 16, + child: Text( + ':', + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .titleMedium, + ), + ), + SizedBox( + width: 80, + child: TextField( + onChanged: (_) { + inputTimeController.setTime(); + }, + decoration: const InputDecoration( + hintText: 'MM', + border: InputBorder.none), + textAlign: TextAlign.center, + controller: inputTimeController + .inputMinutesController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp( + '[1,2,3,4,5,6,7,8,9,0]')), + LengthLimitingTextInputFormatter(2), + LimitRange(00, 59) + ], + ), + ), + const SizedBox( + width: 16, + ), + DropdownButton( + underline: Container(), + value: inputTimeController.isAM.value + ? 'AM' + : 'PM', + dropdownColor: + themeController.isLightMode.value + ? kLightPrimaryBackgroundColor + : kprimaryBackgroundColor, + items: + ['AM', 'PM'].map((String period) { + return DropdownMenuItem( + value: period, + child: Text(period), + ); + }).toList(), + onChanged: (getPeriod) { + inputTimeController + .changePeriod(getPeriod!); + + inputTimeController.setTime(); + }, + ) + ], + ), + ); + })), RepeatTile( controller: controller, themeController: themeController, diff --git a/pubspec.lock b/pubspec.lock index 0efecc2c..95660a79 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" checked_yaml: dependency: transitive description: @@ -197,10 +197,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.2" convert: dependency: transitive description: @@ -684,26 +684,26 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" mgrs_dart: dependency: transitive description: @@ -748,10 +748,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" path_parsing: dependency: transitive description: @@ -1073,10 +1073,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -1121,10 +1121,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.6.0" time: dependency: transitive description: @@ -1293,6 +1293,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index b25e363e..955ee303 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -31,6 +31,11 @@ bool FlutterWindow::OnCreate() { this->Show(); }); + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + return true; }