Fixes column text width calculation in CupertinoDatePicker (#151128)

Fixes #138305, #110319
This commit is contained in:
Mairramer 2024-09-28 17:21:48 -03:00 committed by GitHub
parent a9b648f750
commit 29dd03c56b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 90 additions and 39 deletions

View File

@ -487,54 +487,38 @@ class CupertinoDatePicker extends StatefulWidget {
bool showDayOfWeek, {
bool standaloneMonth = false,
}) {
String longestText = '';
final List<String> longTexts = <String>[];
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<String> 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);
}
}

View File

@ -8,6 +8,7 @@
@Tags(<String>['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<String> testWords = <String>[
'\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<double> 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),
);
}