From 29dd03c56bfeb6fb9eb8b2b830e2012546a50083 Mon Sep 17 00:00:00 2001 From: Mairramer <50643541+Mairramer@users.noreply.github.com> Date: Sat, 28 Sep 2024 17:21:48 -0300 Subject: [PATCH] Fixes column text width calculation in CupertinoDatePicker (#151128) Fixes #138305, #110319 --- .../lib/src/cupertino/date_picker.dart | 74 +++++++++---------- .../test/cupertino/date_picker_test.dart | 55 ++++++++++++++ 2 files changed, 90 insertions(+), 39 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/date_picker.dart b/packages/flutter/lib/src/cupertino/date_picker.dart index ea68b327906..5dcb413ae2a 100644 --- a/packages/flutter/lib/src/cupertino/date_picker.dart +++ b/packages/flutter/lib/src/cupertino/date_picker.dart @@ -487,54 +487,38 @@ class CupertinoDatePicker extends StatefulWidget { bool showDayOfWeek, { bool standaloneMonth = false, }) { - String longestText = ''; + final List longTexts = []; switch (columnType) { case _PickerColumnType.date: - // Measuring the length of all possible date is impossible, so here - // just some dates are measured. for (int i = 1; i <= 12; i++) { - // An arbitrary date. - final String date = - localizations.datePickerMediumDate(DateTime(2018, i, 25)); - if (longestText.length < date.length) { - longestText = date; - } + final String date = localizations.datePickerMediumDate(DateTime(2018, i, 25)); + longTexts.add(date); } case _PickerColumnType.hour: for (int i = 0; i < 24; i++) { final String hour = localizations.datePickerHour(i); - if (longestText.length < hour.length) { - longestText = hour; - } + longTexts.add(hour); } case _PickerColumnType.minute: for (int i = 0; i < 60; i++) { final String minute = localizations.datePickerMinute(i); - if (longestText.length < minute.length) { - longestText = minute; - } + longTexts.add(minute); } case _PickerColumnType.dayPeriod: - longestText = - localizations.anteMeridiemAbbreviation.length > localizations.postMeridiemAbbreviation.length - ? localizations.anteMeridiemAbbreviation - : localizations.postMeridiemAbbreviation; + longTexts.add(localizations.anteMeridiemAbbreviation); + longTexts.add(localizations.postMeridiemAbbreviation); case _PickerColumnType.dayOfMonth: int longestDayOfMonth = 1; - for (int i = 1; i <=31; i++) { + for (int i = 1; i <= 31; i++) { final String dayOfMonth = localizations.datePickerDayOfMonth(i); - if (longestText.length < dayOfMonth.length) { - longestText = dayOfMonth; - longestDayOfMonth = i; - } + longTexts.add(dayOfMonth); + longestDayOfMonth = i; } if (showDayOfWeek) { for (int wd = 1; wd < DateTime.daysPerWeek; wd++) { final String dayOfMonth = localizations.datePickerDayOfMonth(longestDayOfMonth, wd); - if (longestText.length < dayOfMonth.length) { - longestText = dayOfMonth; - } + longTexts.add(dayOfMonth); } } case _PickerColumnType.month: @@ -542,23 +526,35 @@ class CupertinoDatePicker extends StatefulWidget { final String month = standaloneMonth ? localizations.datePickerStandaloneMonth(i) : localizations.datePickerMonth(i); - if (longestText.length < month.length) { - longestText = month; - } + longTexts.add(month); } case _PickerColumnType.year: - longestText = localizations.datePickerYear(2018); + longTexts.add(localizations.datePickerYear(2018)); } - assert(longestText != '', 'column type is not appropriate'); + assert(longTexts.isNotEmpty && longTexts.every((String text) => text.isNotEmpty), 'column type is not appropriate'); - return TextPainter.computeMaxIntrinsicWidth( - text: TextSpan( - style: _themeTextStyle(context), - text: longestText, - ), - textDirection: Directionality.of(context), - ); + return getColumnWidth(texts: longTexts, context: context); + } + + /// Returns the width of column in the picker. + /// + /// This method is intended for testing only. It calculates the width of the + /// widest column in the picker based on the provided list of texts and the + /// given [BuildContext]. + @visibleForTesting + static double getColumnWidth({ + required List texts, + required BuildContext context, + TextStyle? textStyle, + }) { + return texts.map((String text) => TextPainter.computeMaxIntrinsicWidth( + text: TextSpan( + style: textStyle ?? _themeTextStyle(context), + text: text, + ), + textDirection: Directionality.of(context), + )).reduce(math.max); } } diff --git a/packages/flutter/test/cupertino/date_picker_test.dart b/packages/flutter/test/cupertino/date_picker_test.dart index 19ef615703f..fb1724a23eb 100644 --- a/packages/flutter/test/cupertino/date_picker_test.dart +++ b/packages/flutter/test/cupertino/date_picker_test.dart @@ -8,6 +8,7 @@ @Tags(['reduced-test-set']) library; +import 'dart:math' as math; import 'dart:ui'; import 'package:flutter/cupertino.dart'; @@ -2414,6 +2415,50 @@ void main() { expect(find.byType(CupertinoPickerDefaultSelectionOverlay), findsExactly(4)); }); + + testWidgets('CupertinoDatePicker accommodates widest text using table codepoints', (WidgetTester tester) async { + // |---------| + // | 0x2002 | // EN SPACE - 1/2 Advance + // | 0x2005 | // FOUR-PER-EM SPACE - 1/4 Advance + // |---------| + final List testWords = [ + '\u2002' * 10, // Output: 10 * 1/2 = 5 + '\u2005' * 20, // Output: 20 * 1/4 = 5 + ]; + + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoDatePicker( + onDateTimeChanged: (DateTime date) {}, + initialDateTime: DateTime(2018, 9, 15), + ), + ), + ), + ); + + final BuildContext context = tester.element(find.byType(CupertinoDatePicker)); + + const TextStyle textStyle = TextStyle( + fontSize: 21, + letterSpacing: 0.4, + fontWeight: FontWeight.normal, + color: CupertinoColors.label, + ); + + final List widths = testWords.map((String word) => getColumnWidth(word, textStyle, context)).toList(); + + final double largestWidth = widths.reduce(math.max); + + final double testWidth = CupertinoDatePicker.getColumnWidth( + texts: testWords, + context: context, + textStyle: textStyle, + ); + + expect(testWidth, equals(largestWidth)); + expect(widths.indexOf(largestWidth), equals(1)); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/39998 } Widget _buildPicker({ @@ -2438,3 +2483,13 @@ Widget _buildPicker({ ), ); } + +double getColumnWidth(String text, TextStyle textStyle, BuildContext context) { + return TextPainter.computeMaxIntrinsicWidth( + text: TextSpan( + text: text, + style: textStyle, + ), + textDirection: Directionality.of(context), + ); +}