diff --git a/packages/flutter/lib/src/material/date_picker_theme.dart b/packages/flutter/lib/src/material/date_picker_theme.dart index 79cce12f02..a970ffbd5d 100644 --- a/packages/flutter/lib/src/material/date_picker_theme.dart +++ b/packages/flutter/lib/src/material/date_picker_theme.dart @@ -9,6 +9,7 @@ import 'package:flutter/widgets.dart'; import 'color_scheme.dart'; import 'colors.dart'; +import 'input_decorator.dart'; import 'material_state.dart'; import 'text_theme.dart'; import 'theme.dart'; @@ -68,6 +69,7 @@ class DatePickerThemeData with Diagnosticable { this.rangeSelectionBackgroundColor, this.rangeSelectionOverlayColor, this.dividerColor, + this.inputDecorationTheme, }); /// Overrides the default value of [Dialog.backgroundColor]. @@ -288,6 +290,10 @@ class DatePickerThemeData with Diagnosticable { /// and vertical divider when the dialog is in landscape orientation. final Color? dividerColor; + /// Overrides the [InputDatePickerFormField]'s input decoration theme. + /// If this is null, [ThemeData.inputDecorationTheme] is used instead. + final InputDecorationTheme? inputDecorationTheme; + /// Creates a copy of this object with the given fields replaced with the /// new values. DatePickerThemeData copyWith({ @@ -324,6 +330,7 @@ class DatePickerThemeData with Diagnosticable { Color? rangeSelectionBackgroundColor, MaterialStateProperty? rangeSelectionOverlayColor, Color? dividerColor, + InputDecorationTheme? inputDecorationTheme, }) { return DatePickerThemeData( backgroundColor: backgroundColor ?? this.backgroundColor, @@ -359,6 +366,7 @@ class DatePickerThemeData with Diagnosticable { rangeSelectionBackgroundColor: rangeSelectionBackgroundColor ?? this.rangeSelectionBackgroundColor, rangeSelectionOverlayColor: rangeSelectionOverlayColor ?? this.rangeSelectionOverlayColor, dividerColor: dividerColor ?? this.dividerColor, + inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme, ); } @@ -401,6 +409,7 @@ class DatePickerThemeData with Diagnosticable { rangeSelectionBackgroundColor: Color.lerp(a?.rangeSelectionBackgroundColor, b?.rangeSelectionBackgroundColor, t), rangeSelectionOverlayColor: MaterialStateProperty.lerp(a?.rangeSelectionOverlayColor, b?.rangeSelectionOverlayColor, t, Color.lerp), dividerColor: Color.lerp(a?.dividerColor, b?.dividerColor, t), + inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme, ); } @@ -449,6 +458,7 @@ class DatePickerThemeData with Diagnosticable { rangeSelectionBackgroundColor, rangeSelectionOverlayColor, dividerColor, + inputDecorationTheme, ]); @override @@ -489,7 +499,8 @@ class DatePickerThemeData with Diagnosticable { && other.rangePickerHeaderHelpStyle == rangePickerHeaderHelpStyle && other.rangeSelectionBackgroundColor == rangeSelectionBackgroundColor && other.rangeSelectionOverlayColor == rangeSelectionOverlayColor - && other.dividerColor == dividerColor; + && other.dividerColor == dividerColor + && other.inputDecorationTheme == inputDecorationTheme; } @override @@ -528,6 +539,7 @@ class DatePickerThemeData with Diagnosticable { properties.add(ColorProperty('rangeSelectionBackgroundColor', rangeSelectionBackgroundColor, defaultValue: null)); properties.add(DiagnosticsProperty>('rangeSelectionOverlayColor', rangeSelectionOverlayColor, defaultValue: null)); properties.add(ColorProperty('dividerColor', dividerColor, defaultValue: null)); + properties.add(DiagnosticsProperty('inputDecorationTheme', inputDecorationTheme, defaultValue: null)); } } diff --git a/packages/flutter/lib/src/material/input_date_picker_form_field.dart b/packages/flutter/lib/src/material/input_date_picker_form_field.dart index 460ebee72d..4a82273b78 100644 --- a/packages/flutter/lib/src/material/input_date_picker_form_field.dart +++ b/packages/flutter/lib/src/material/input_date_picker_form_field.dart @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart'; import 'date.dart'; +import 'date_picker_theme.dart'; import 'input_border.dart'; import 'input_decorator.dart'; import 'material_localizations.dart'; @@ -248,16 +249,19 @@ class _InputDatePickerFormFieldState extends State { final ThemeData theme = Theme.of(context); final bool useMaterial3 = theme.useMaterial3; final MaterialLocalizations localizations = MaterialLocalizations.of(context); + final DatePickerThemeData datePickerTheme = theme.datePickerTheme; final InputDecorationTheme inputTheme = theme.inputDecorationTheme; - final InputBorder inputBorder = inputTheme.border + final InputBorder effectiveInputBorder = datePickerTheme.inputDecorationTheme?.border + ?? theme.inputDecorationTheme.border ?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder()); return TextFormField( decoration: InputDecoration( - border: inputBorder, - filled: inputTheme.filled, hintText: widget.fieldHintText ?? localizations.dateHelpText, labelText: widget.fieldLabelText ?? localizations.dateInputLabel, + ).applyDefaults(inputTheme + .merge(datePickerTheme.inputDecorationTheme) + .copyWith(border: effectiveInputBorder), ), validator: _validateDate, keyboardType: widget.keyboardType ?? TextInputType.datetime, diff --git a/packages/flutter/test/material/date_picker_theme_test.dart b/packages/flutter/test/material/date_picker_theme_test.dart index 18da0520e0..191c6faa3a 100644 --- a/packages/flutter/test/material/date_picker_theme_test.dart +++ b/packages/flutter/test/material/date_picker_theme_test.dart @@ -40,7 +40,11 @@ void main() { rangePickerHeaderHelpStyle: TextStyle(fontSize: 15), rangeSelectionBackgroundColor: Color(0xffffff2f), rangeSelectionOverlayColor: MaterialStatePropertyAll(Color(0xffffff3f)), - dividerColor: Color(0xffffff3f), + dividerColor: Color(0xffffff4f), + inputDecorationTheme: InputDecorationTheme( + fillColor: Color(0xffffff5f), + border: UnderlineInputBorder(), + ) ); Material findDialogMaterial(WidgetTester tester) { @@ -119,6 +123,7 @@ void main() { expect(theme.rangeSelectionBackgroundColor, null); expect(theme.rangeSelectionOverlayColor, null); expect(theme.dividerColor, null); + expect(theme.inputDecorationTheme, null); }); testWidgets('DatePickerTheme.defaults M3 defaults', (WidgetTester tester) async { @@ -129,7 +134,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: true), + theme: ThemeData(useMaterial3: true), home: Builder( builder: (BuildContext context) { m3 = DatePickerTheme.defaults(context); @@ -193,6 +198,7 @@ void main() { expect(m3.rangePickerHeaderHeadlineStyle, textTheme.titleLarge); expect(m3.rangePickerHeaderHelpStyle, textTheme.titleSmall); expect(m3.dividerColor, null); + expect(m3.inputDecorationTheme, null); }); testWidgets('DatePickerTheme.defaults M2 defaults', (WidgetTester tester) async { @@ -203,7 +209,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: false), + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { m2 = DatePickerTheme.defaults(context); @@ -258,6 +264,8 @@ void main() { expect(m2.rangePickerHeaderForegroundColor, colorScheme.onPrimary); expect(m2.rangePickerHeaderHeadlineStyle, textTheme.headlineSmall); expect(m2.rangePickerHeaderHelpStyle, textTheme.labelSmall); + expect(m2.dividerColor, null); + expect(m2.inputDecorationTheme, null); }); testWidgets('Default DatePickerThemeData debugFillProperties', (WidgetTester tester) async { @@ -282,7 +290,9 @@ void main() { .map((DiagnosticsNode node) => node.toString()) .toList(); - expect(description, [ + expect( + description, + equalsIgnoringHashCodes([ 'backgroundColor: Color(0xfffffff0)', 'elevation: 6.0', 'shadowColor: Color(0xfffffff1)', @@ -315,15 +325,18 @@ void main() { 'rangePickerHeaderHelpStyle: TextStyle(inherit: true, size: 15.0)', 'rangeSelectionBackgroundColor: Color(0xffffff2f)', 'rangeSelectionOverlayColor: MaterialStatePropertyAll(Color(0xffffff3f))', - 'dividerColor: Color(0xffffff3f)', - ]); + 'dividerColor: Color(0xffffff4f)', + 'inputDecorationTheme: InputDecorationTheme#00000(fillColor: Color(0xffffff5f), border: UnderlineInputBorder())' + ]), + ); }); - testWidgets('DatePickerDialog uses ThemeData datePicker theme', (WidgetTester tester) async { + testWidgets('DatePickerDialog uses ThemeData datePicker theme (calendar mode)', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: true).copyWith( + theme: ThemeData( datePickerTheme: datePickerTheme, + useMaterial3: true, ), home: Directionality( textDirection: TextDirection.ltr, @@ -398,11 +411,53 @@ void main() { expect(year2023Decoration.border?.bottom.color, datePickerTheme.todayForegroundColor?.resolve({})); }); + testWidgets('DatePickerDialog uses ThemeData datePicker theme (input mode)', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + datePickerTheme: datePickerTheme, + useMaterial3: true, + ), + home: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: DatePickerDialog( + initialEntryMode: DatePickerEntryMode.input, + initialDate: DateTime(2023, DateTime.january, 25), + firstDate: DateTime(2022), + lastDate: DateTime(2024, DateTime.december, 31), + currentDate: DateTime(2023, DateTime.january, 24), + ), + ), + ), + ), + ), + ); + + final Material material = findDialogMaterial(tester); + expect(material.color, datePickerTheme.backgroundColor); + expect(material.elevation, datePickerTheme.elevation); + expect(material.shadowColor, datePickerTheme.shadowColor); + expect(material.surfaceTintColor, datePickerTheme.surfaceTintColor); + expect(material.shape, datePickerTheme.shape); + + final Text selectDate = tester.widget(find.text('Select date')); + final Material headerMaterial = findHeaderMaterial(tester, 'Select date'); + expect(selectDate.style?.color, datePickerTheme.headerForegroundColor); + expect(selectDate.style?.fontSize, datePickerTheme.headerHelpStyle?.fontSize); + expect(headerMaterial.color, datePickerTheme.headerBackgroundColor); + + final InputDecoration inputDecoration = tester.widget(find.byType(TextField)).decoration!; + expect(inputDecoration.fillColor, datePickerTheme.inputDecorationTheme?.fillColor); + }); + testWidgets('DateRangePickerDialog uses ThemeData datePicker theme', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: true).copyWith( + theme: ThemeData( datePickerTheme: datePickerTheme, + useMaterial3: true, ), home: Directionality( textDirection: TextDirection.ltr, @@ -450,8 +505,9 @@ void main() { addTearDown(tester.view.reset); await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: true).copyWith( + theme: ThemeData( datePickerTheme: datePickerTheme, + useMaterial3: true, ), home: Directionality( textDirection: TextDirection.ltr, @@ -482,4 +538,60 @@ void main() { final Divider horizontalDivider = tester.widget(find.byType(Divider)); expect(horizontalDivider.color, datePickerTheme.dividerColor); }); + + testWidgets( + 'DatePicker uses ThemeData.inputDecorationTheme properties ' + 'which are null in DatePickerThemeData.inputDecorationTheme', + (WidgetTester tester) async { + + Widget buildWidget({ + InputDecorationTheme? inputDecorationTheme, + DatePickerThemeData? datePickerTheme, + }) { + return MaterialApp( + theme: ThemeData( + useMaterial3: true, + inputDecorationTheme: inputDecorationTheme, + datePickerTheme: datePickerTheme, + ), + home: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: DatePickerDialog( + initialEntryMode: DatePickerEntryMode.input, + initialDate: DateTime(2023, DateTime.january, 25), + firstDate: DateTime(2022), + lastDate: DateTime(2024, DateTime.december, 31), + currentDate: DateTime(2023, DateTime.january, 24), + ), + ), + ), + ), + ); + } + + // Test DatePicker with DatePickerThemeData.inputDecorationTheme. + await tester.pumpWidget(buildWidget( + inputDecorationTheme: const InputDecorationTheme(filled: true), + datePickerTheme: datePickerTheme, + )); + InputDecoration inputDecoration = tester.widget(find.byType(TextField)).decoration!; + expect(inputDecoration.fillColor, datePickerTheme.inputDecorationTheme!.fillColor); + expect(inputDecoration.border , datePickerTheme.inputDecorationTheme!.border); + + // Test DatePicker with ThemeData.inputDecorationTheme. + await tester.pumpWidget(buildWidget( + inputDecorationTheme: const InputDecorationTheme( + filled: true, + fillColor: Color(0xFF00FF00), + border: OutlineInputBorder(), + ), + )); + await tester.pumpAndSettle(); + + inputDecoration = tester.widget(find.byType(TextField)).decoration!; + expect(inputDecoration.fillColor, const Color(0xFF00FF00)); + expect(inputDecoration.border , const OutlineInputBorder()); + }); }