From 00a046c46f6740219ed44d187ec26f3892c471a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Mon, 3 Jul 2023 23:56:51 +0200 Subject: [PATCH] feat: Added support for `dateFormat` to `MacosDatePicker` --- CHANGELOG.md | 3 + README.md | 12 +++ lib/src/selectors/date_picker.dart | 133 +++++++++++++++++++-------- test/selectors/date_picker_test.dart | 53 +++++++++++ 4 files changed, 161 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d8d057..7d42a459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ ## [2.0.0-beta.3] +✨ New ✨ +* Added support for `dateFormat` to `MacosDatePicker`. + 🛠️ Fixed 🛠️ * Better UX of the click on the calendar elements in `MacosDatePicker` diff --git a/README.md b/README.md index 8164314d..97f5475b 100644 --- a/README.md +++ b/README.md @@ -974,6 +974,18 @@ There are three styles of `MacosDatePickers`: calendar-like interface to select a date. * `combined`: provides both `textual` and `graphical` interfaces. +You can also define the `dateFormat` to change the way dates are displayed in the textual interface. +It takes a string of tokens (case-insensitive) and replaces them with their corresponding values. +The following tokens are supported: +* `D`: day of the month (1-31) +* `DD`: day of the month (01-31) +* `M`: month of the year (1-12) +* `MM`: month of the year (01-12) +* `YYYY`: year (0000-9999) +* Any separator between tokens is preserved (e.g. `/`, `-`, `.`) + +The default format is `M/D/YYYY`. + Example usage: ```dart MacosDatePicker( diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index d5145dc4..8820712f 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -44,6 +44,7 @@ class MacosDatePicker extends StatefulWidget { this.style = DatePickerStyle.combined, required this.onDateChanged, this.initialDate, + this.dateFormat, }); /// The [DatePickerStyle] to use. @@ -59,6 +60,19 @@ class MacosDatePicker extends StatefulWidget { /// Defaults to `DateTime.now()`. final DateTime? initialDate; + /// Changes the way dates are displayed in the textual interface. + /// + /// The following tokens are supported (case-insensitive): + /// * `D`: day of the month (1-31) + /// * `DD`: day of the month (01-31) + /// * `M`: month of the year (1-12) + /// * `MM`: month of the year (01-12) + /// * `YYYY`: year (0000-9999) + /// * Any separator between tokens is preserved (e.g. `/`, `-`, `.`) + /// + /// Defaults to `M/D/YYYY`. + final String? dateFormat; + @override State createState() => _MacosDatePickerState(); } @@ -175,6 +189,84 @@ class _MacosDatePickerState extends State { return result; } + // Creates textual date presentation based on "dateFormat" property + List _textualDateElements() { + final separator = widget.dateFormat != null + ? widget.dateFormat!.toLowerCase().replaceAll(RegExp(r'[dmy]'), '')[0] + : '/'; + + final List dateElements = widget.dateFormat != null + ? widget.dateFormat!.toLowerCase().split(RegExp(r'[^dmy]')) + : ['m', 'd', 'y']; + + final List dateFields = []; + for (var dateElement in dateElements) { + if (dateElement.startsWith('d')) { + String value = dateElement == 'dd' && _selectedDay < 10 + // Add a leading zero + ? '0$_selectedDay' + : '$_selectedDay'; + + dateFields.add( + DatePickerFieldElement( + isSelected: _isDaySelected, + element: value, + onSelected: () { + setState(() { + _focusNode.requestFocus(); + _isDaySelected = !_isDaySelected; + _isMonthSelected = false; + _isYearSelected = false; + }); + }, + ), + ); + } else if (dateElement.startsWith('m')) { + String value = dateElement == 'mm' && _selectedMonth < 10 + // Add a leading zero + ? '0$_selectedMonth' + : '$_selectedMonth'; + + dateFields.add( + DatePickerFieldElement( + isSelected: _isMonthSelected, + element: value, + onSelected: () { + setState(() { + _focusNode.requestFocus(); + _isMonthSelected = !_isMonthSelected; + _isDaySelected = false; + _isYearSelected = false; + }); + }, + ), + ); + } else if (dateElement.startsWith('y')) { + dateFields.add( + DatePickerFieldElement( + isSelected: _isYearSelected, + element: '$_selectedYear', + onSelected: () { + setState(() { + _focusNode.requestFocus(); + _isYearSelected = !_isYearSelected; + _isDaySelected = false; + _isMonthSelected = false; + }); + }, + ), + ); + } + dateFields.add( + Text(separator), + ); + } + + dateFields.removeLast(); + + return dateFields; + } + Widget _buildTextualPicker(MacosDatePickerThemeData datePickerTheme) { return KeyboardShortcutRunner( onUpArrowKeypress: _incrementElement, @@ -195,46 +287,7 @@ class _MacosDatePickerState extends State { ), child: Row( mainAxisSize: MainAxisSize.min, - children: [ - DatePickerFieldElement( - isSelected: _isMonthSelected, - element: '$_selectedMonth', - onSelected: () { - setState(() { - _focusNode.requestFocus(); - _isMonthSelected = !_isMonthSelected; - _isDaySelected = false; - _isYearSelected = false; - }); - }, - ), - const Text('/'), - DatePickerFieldElement( - isSelected: _isDaySelected, - element: '$_selectedDay', - onSelected: () { - setState(() { - _focusNode.requestFocus(); - _isDaySelected = !_isDaySelected; - _isMonthSelected = false; - _isYearSelected = false; - }); - }, - ), - const Text('/'), - DatePickerFieldElement( - isSelected: _isYearSelected, - element: '$_selectedYear', - onSelected: () { - setState(() { - _focusNode.requestFocus(); - _isYearSelected = !_isYearSelected; - _isDaySelected = false; - _isMonthSelected = false; - }); - }, - ), - ], + children: _textualDateElements(), ), ), ), diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index b0e50b5d..3fd9ebd1 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -82,6 +82,59 @@ void main() { }, ); + testWidgets( + 'Textual MacosDatePicker renders the date with respect to "dateFormat" property', + (tester) async { + renderWidget(String dateFormat) => MacosApp( + home: MacosWindow( + disableWallpaperTinting: true, + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + dateFormat: dateFormat, + style: DatePickerStyle.textual, + ), + ); + }, + ), + ], + ), + ), + ); + + getNthTextFromWidget(int index) => (find.byType(Text).at(index).evaluate().first.widget as Text).data as String; + + await tester.pumpWidget(renderWidget('dd.mm.yyyy')); + String firstDateElement = getNthTextFromWidget(0); + expect(firstDateElement, '01'); + String secondDateElement = getNthTextFromWidget(1); + expect(secondDateElement, '.'); + String thirdDateElement = getNthTextFromWidget(2); + expect(thirdDateElement, '04'); + String fourthDateElement = getNthTextFromWidget(3); + expect(fourthDateElement, '.'); + String fifthDateElement = getNthTextFromWidget(4); + expect(fifthDateElement, '2023'); + + await tester.pumpWidget(renderWidget('yyyy-m-d')); + firstDateElement = getNthTextFromWidget(0); + expect(firstDateElement, '2023'); + secondDateElement = getNthTextFromWidget(1); + expect(secondDateElement, '-'); + thirdDateElement = getNthTextFromWidget(2); + expect(thirdDateElement, '4'); + fourthDateElement = getNthTextFromWidget(3); + expect(fourthDateElement, '-'); + fifthDateElement = getNthTextFromWidget(4); + expect(fifthDateElement, '1'); + }, + ); + testWidgets( 'Can select the date field element and change the value', (tester) async {