"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/material/pickers/date_picker_dialog.dart" (13 Nov 2020, 18161 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 // @dart = 2.8
    6 
    7 import 'dart:math' as math;
    8 
    9 import 'package:flutter/services.dart';
   10 import 'package:flutter/widgets.dart';
   11 
   12 import '../color_scheme.dart';
   13 import '../debug.dart';
   14 import '../dialog.dart';
   15 import '../icons.dart';
   16 import '../material_localizations.dart';
   17 import '../text_button.dart';
   18 import '../text_theme.dart';
   19 import '../theme.dart';
   20 
   21 import 'calendar_date_picker.dart';
   22 import 'date_picker_common.dart';
   23 import 'date_picker_header.dart';
   24 import 'date_utils.dart' as utils;
   25 import 'input_date_picker.dart';
   26 
   27 const Size _calendarPortraitDialogSize = Size(330.0, 518.0);
   28 const Size _calendarLandscapeDialogSize = Size(496.0, 346.0);
   29 const Size _inputPortraitDialogSize = Size(330.0, 270.0);
   30 const Size _inputLandscapeDialogSize = Size(496, 160.0);
   31 const Duration _dialogSizeAnimationDuration = Duration(milliseconds: 200);
   32 const double _inputFormPortraitHeight = 98.0;
   33 const double _inputFormLandscapeHeight = 108.0;
   34 
   35 
   36 /// Shows a dialog containing a Material Design date picker.
   37 ///
   38 /// The returned [Future] resolves to the date selected by the user when the
   39 /// user confirms the dialog. If the user cancels the dialog, null is returned.
   40 ///
   41 /// When the date picker is first displayed, it will show the month of
   42 /// [initialDate], with [initialDate] selected.
   43 ///
   44 /// The [firstDate] is the earliest allowable date. The [lastDate] is the latest
   45 /// allowable date. [initialDate] must either fall between these dates,
   46 /// or be equal to one of them. For each of these [DateTime] parameters, only
   47 /// their dates are considered. Their time fields are ignored. They must all
   48 /// be non-null.
   49 ///
   50 /// The [currentDate] represents the current day (i.e. today). This
   51 /// date will be highlighted in the day grid. If null, the date of
   52 /// `DateTime.now()` will be used.
   53 ///
   54 /// An optional [initialEntryMode] argument can be used to display the date
   55 /// picker in the [DatePickerEntryMode.calendar] (a calendar month grid)
   56 /// or [DatePickerEntryMode.input] (a text input field) mode.
   57 /// It defaults to [DatePickerEntryMode.calendar] and must be non-null.
   58 ///
   59 /// An optional [selectableDayPredicate] function can be passed in to only allow
   60 /// certain days for selection. If provided, only the days that
   61 /// [selectableDayPredicate] returns true for will be selectable. For example,
   62 /// this can be used to only allow weekdays for selection. If provided, it must
   63 /// return true for [initialDate].
   64 ///
   65 /// The following optional string parameters allow you to override the default
   66 /// text used for various parts of the dialog:
   67 ///
   68 ///   * [helpText], label displayed at the top of the dialog.
   69 ///   * [cancelText], label on the cancel button.
   70 ///   * [confirmText], label on the ok button.
   71 ///   * [errorFormatText], message used when the input text isn't in a proper date format.
   72 ///   * [errorInvalidText], message used when the input text isn't a selectable date.
   73 ///   * [fieldHintText], text used to prompt the user when no text has been entered in the field.
   74 ///   * [fieldLabelText], label for the date text input field.
   75 ///
   76 /// An optional [locale] argument can be used to set the locale for the date
   77 /// picker. It defaults to the ambient locale provided by [Localizations].
   78 ///
   79 /// An optional [textDirection] argument can be used to set the text direction
   80 /// ([TextDirection.ltr] or [TextDirection.rtl]) for the date picker. It
   81 /// defaults to the ambient text direction provided by [Directionality]. If both
   82 /// [locale] and [textDirection] are non-null, [textDirection] overrides the
   83 /// direction chosen for the [locale].
   84 ///
   85 /// The [context], [useRootNavigator] and [routeSettings] arguments are passed to
   86 /// [showDialog], the documentation for which discusses how it is used. [context]
   87 /// and [useRootNavigator] must be non-null.
   88 ///
   89 /// The [builder] parameter can be used to wrap the dialog widget
   90 /// to add inherited widgets like [Theme].
   91 ///
   92 /// An optional [initialDatePickerMode] argument can be used to have the
   93 /// calendar date picker initially appear in the [DatePickerMode.year] or
   94 /// [DatePickerMode.day] mode. It defaults to [DatePickerMode.day], and
   95 /// must be non-null.
   96 ///
   97 /// See also:
   98 ///
   99 ///  * [showDateRangePicker], which shows a material design date range picker
  100 ///    used to select a range of dates.
  101 ///  * [CalendarDatePicker], which provides the calendar grid used by the date picker dialog.
  102 ///  * [InputDatePickerFormField], which provides a text input field for entering dates.
  103 ///
  104 Future<DateTime> showDatePicker({
  105   @required BuildContext context,
  106   @required DateTime initialDate,
  107   @required DateTime firstDate,
  108   @required DateTime lastDate,
  109   DateTime currentDate,
  110   DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar,
  111   SelectableDayPredicate selectableDayPredicate,
  112   String helpText,
  113   String cancelText,
  114   String confirmText,
  115   Locale locale,
  116   bool useRootNavigator = true,
  117   RouteSettings routeSettings,
  118   TextDirection textDirection,
  119   TransitionBuilder builder,
  120   DatePickerMode initialDatePickerMode = DatePickerMode.day,
  121   String errorFormatText,
  122   String errorInvalidText,
  123   String fieldHintText,
  124   String fieldLabelText,
  125 }) async {
  126   assert(context != null);
  127   assert(initialDate != null);
  128   assert(firstDate != null);
  129   assert(lastDate != null);
  130   initialDate = utils.dateOnly(initialDate);
  131   firstDate = utils.dateOnly(firstDate);
  132   lastDate = utils.dateOnly(lastDate);
  133   assert(
  134     !lastDate.isBefore(firstDate),
  135     'lastDate $lastDate must be on or after firstDate $firstDate.'
  136   );
  137   assert(
  138     !initialDate.isBefore(firstDate),
  139     'initialDate $initialDate must be on or after firstDate $firstDate.'
  140   );
  141   assert(
  142     !initialDate.isAfter(lastDate),
  143     'initialDate $initialDate must be on or before lastDate $lastDate.'
  144   );
  145   assert(
  146     selectableDayPredicate == null || selectableDayPredicate(initialDate),
  147     'Provided initialDate $initialDate must satisfy provided selectableDayPredicate.'
  148   );
  149   assert(initialEntryMode != null);
  150   assert(useRootNavigator != null);
  151   assert(initialDatePickerMode != null);
  152   assert(debugCheckHasMaterialLocalizations(context));
  153 
  154   Widget dialog = _DatePickerDialog(
  155     initialDate: initialDate,
  156     firstDate: firstDate,
  157     lastDate: lastDate,
  158     currentDate: currentDate,
  159     initialEntryMode: initialEntryMode,
  160     selectableDayPredicate: selectableDayPredicate,
  161     helpText: helpText,
  162     cancelText: cancelText,
  163     confirmText: confirmText,
  164     initialCalendarMode: initialDatePickerMode,
  165     errorFormatText: errorFormatText,
  166     errorInvalidText: errorInvalidText,
  167     fieldHintText: fieldHintText,
  168     fieldLabelText: fieldLabelText,
  169   );
  170 
  171   if (textDirection != null) {
  172     dialog = Directionality(
  173       textDirection: textDirection,
  174       child: dialog,
  175     );
  176   }
  177 
  178   if (locale != null) {
  179     dialog = Localizations.override(
  180       context: context,
  181       locale: locale,
  182       child: dialog,
  183     );
  184   }
  185 
  186   return showDialog<DateTime>(
  187     context: context,
  188     useRootNavigator: useRootNavigator,
  189     routeSettings: routeSettings,
  190     builder: (BuildContext context) {
  191       return builder == null ? dialog : builder(context, dialog);
  192     },
  193   );
  194 }
  195 
  196 class _DatePickerDialog extends StatefulWidget {
  197   _DatePickerDialog({
  198     Key key,
  199     @required DateTime initialDate,
  200     @required DateTime firstDate,
  201     @required DateTime lastDate,
  202     DateTime currentDate,
  203     this.initialEntryMode = DatePickerEntryMode.calendar,
  204     this.selectableDayPredicate,
  205     this.cancelText,
  206     this.confirmText,
  207     this.helpText,
  208     this.initialCalendarMode = DatePickerMode.day,
  209     this.errorFormatText,
  210     this.errorInvalidText,
  211     this.fieldHintText,
  212     this.fieldLabelText,
  213   }) : assert(initialDate != null),
  214        assert(firstDate != null),
  215        assert(lastDate != null),
  216        initialDate = utils.dateOnly(initialDate),
  217        firstDate = utils.dateOnly(firstDate),
  218        lastDate = utils.dateOnly(lastDate),
  219        currentDate = utils.dateOnly(currentDate ?? DateTime.now()),
  220        assert(initialEntryMode != null),
  221        assert(initialCalendarMode != null),
  222        super(key: key) {
  223     assert(
  224       !this.lastDate.isBefore(this.firstDate),
  225       'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.'
  226     );
  227     assert(
  228       !this.initialDate.isBefore(this.firstDate),
  229       'initialDate ${this.initialDate} must be on or after firstDate ${this.firstDate}.'
  230     );
  231     assert(
  232       !this.initialDate.isAfter(this.lastDate),
  233       'initialDate ${this.initialDate} must be on or before lastDate ${this.lastDate}.'
  234     );
  235     assert(
  236       selectableDayPredicate == null || selectableDayPredicate(this.initialDate),
  237       'Provided initialDate ${this.initialDate} must satisfy provided selectableDayPredicate'
  238     );
  239   }
  240 
  241   /// The initially selected [DateTime] that the picker should display.
  242   final DateTime initialDate;
  243 
  244   /// The earliest allowable [DateTime] that the user can select.
  245   final DateTime firstDate;
  246 
  247   /// The latest allowable [DateTime] that the user can select.
  248   final DateTime lastDate;
  249 
  250   /// The [DateTime] representing today. It will be highlighted in the day grid.
  251   final DateTime currentDate;
  252 
  253   final DatePickerEntryMode initialEntryMode;
  254 
  255   /// Function to provide full control over which [DateTime] can be selected.
  256   final SelectableDayPredicate selectableDayPredicate;
  257 
  258   /// The text that is displayed on the cancel button.
  259   final String cancelText;
  260 
  261   /// The text that is displayed on the confirm button.
  262   final String confirmText;
  263 
  264   /// The text that is displayed at the top of the header.
  265   ///
  266   /// This is used to indicate to the user what they are selecting a date for.
  267   final String helpText;
  268 
  269   /// The initial display of the calendar picker.
  270   final DatePickerMode initialCalendarMode;
  271 
  272   final String errorFormatText;
  273 
  274   final String errorInvalidText;
  275 
  276   final String fieldHintText;
  277 
  278   final String fieldLabelText;
  279 
  280   @override
  281   _DatePickerDialogState createState() => _DatePickerDialogState();
  282 }
  283 
  284 class _DatePickerDialogState extends State<_DatePickerDialog> {
  285 
  286   DatePickerEntryMode _entryMode;
  287   DateTime _selectedDate;
  288   bool _autoValidate;
  289   final GlobalKey _calendarPickerKey = GlobalKey();
  290   final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  291 
  292   @override
  293   void initState() {
  294     super.initState();
  295     _entryMode = widget.initialEntryMode;
  296     _selectedDate = widget.initialDate;
  297     _autoValidate = false;
  298   }
  299 
  300   void _handleOk() {
  301     if (_entryMode == DatePickerEntryMode.input) {
  302       final FormState form = _formKey.currentState;
  303       if (!form.validate()) {
  304         setState(() => _autoValidate = true);
  305         return;
  306       }
  307       form.save();
  308     }
  309     Navigator.pop(context, _selectedDate);
  310   }
  311 
  312   void _handleCancel() {
  313     Navigator.pop(context);
  314   }
  315 
  316   void _handleEntryModeToggle() {
  317     setState(() {
  318       switch (_entryMode) {
  319         case DatePickerEntryMode.calendar:
  320           _autoValidate = false;
  321           _entryMode = DatePickerEntryMode.input;
  322           break;
  323         case DatePickerEntryMode.input:
  324           _formKey.currentState.save();
  325           _entryMode = DatePickerEntryMode.calendar;
  326           break;
  327       }
  328     });
  329   }
  330 
  331   void _handleDateChanged(DateTime date) {
  332     setState(() => _selectedDate = date);
  333   }
  334 
  335   Size _dialogSize(BuildContext context) {
  336     final Orientation orientation = MediaQuery.of(context).orientation;
  337     switch (_entryMode) {
  338       case DatePickerEntryMode.calendar:
  339         switch (orientation) {
  340           case Orientation.portrait:
  341             return _calendarPortraitDialogSize;
  342           case Orientation.landscape:
  343             return _calendarLandscapeDialogSize;
  344         }
  345         break;
  346       case DatePickerEntryMode.input:
  347         switch (orientation) {
  348           case Orientation.portrait:
  349             return _inputPortraitDialogSize;
  350           case Orientation.landscape:
  351             return _inputLandscapeDialogSize;
  352         }
  353         break;
  354     }
  355     return null;
  356   }
  357 
  358   static final Map<LogicalKeySet, Intent> _formShortcutMap = <LogicalKeySet, Intent>{
  359     // Pressing enter on the field will move focus to the next field or control.
  360     LogicalKeySet(LogicalKeyboardKey.enter): const NextFocusIntent(),
  361   };
  362 
  363   @override
  364   Widget build(BuildContext context) {
  365     final ThemeData theme = Theme.of(context);
  366     final ColorScheme colorScheme = theme.colorScheme;
  367     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
  368     final Orientation orientation = MediaQuery.of(context).orientation;
  369     final TextTheme textTheme = theme.textTheme;
  370     // Constrain the textScaleFactor to the largest supported value to prevent
  371     // layout issues.
  372     final double textScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 1.3);
  373 
  374     final String dateText = _selectedDate != null
  375       ? localizations.formatMediumDate(_selectedDate)
  376       : localizations.unspecifiedDate;
  377     final Color dateColor = colorScheme.brightness == Brightness.light
  378       ? colorScheme.onPrimary
  379       : colorScheme.onSurface;
  380     final TextStyle dateStyle = orientation == Orientation.landscape
  381       ? textTheme.headline5?.copyWith(color: dateColor)
  382       : textTheme.headline4?.copyWith(color: dateColor);
  383 
  384     final Widget actions = Container(
  385       alignment: AlignmentDirectional.centerEnd,
  386       constraints: const BoxConstraints(minHeight: 52.0),
  387       padding: const EdgeInsets.symmetric(horizontal: 8),
  388       child: OverflowBar(
  389         spacing: 8,
  390         children: <Widget>[
  391           TextButton(
  392             child: Text(widget.cancelText ?? localizations.cancelButtonLabel),
  393             onPressed: _handleCancel,
  394           ),
  395           TextButton(
  396             child: Text(widget.confirmText ?? localizations.okButtonLabel),
  397             onPressed: _handleOk,
  398           ),
  399         ],
  400       ),
  401     );
  402 
  403     Widget picker;
  404     IconData entryModeIcon;
  405     String entryModeTooltip;
  406     switch (_entryMode) {
  407       case DatePickerEntryMode.calendar:
  408         picker = CalendarDatePicker(
  409           key: _calendarPickerKey,
  410           initialDate: _selectedDate,
  411           firstDate: widget.firstDate,
  412           lastDate: widget.lastDate,
  413           currentDate: widget.currentDate,
  414           onDateChanged: _handleDateChanged,
  415           selectableDayPredicate: widget.selectableDayPredicate,
  416           initialCalendarMode: widget.initialCalendarMode,
  417         );
  418         entryModeIcon = Icons.edit;
  419         entryModeTooltip = localizations.inputDateModeButtonLabel;
  420         break;
  421 
  422       case DatePickerEntryMode.input:
  423         picker = Form(
  424           key: _formKey,
  425           autovalidate: _autoValidate,
  426           child: Container(
  427             padding: const EdgeInsets.symmetric(horizontal: 24),
  428             height: orientation == Orientation.portrait ? _inputFormPortraitHeight : _inputFormLandscapeHeight,
  429             child: Shortcuts(
  430               shortcuts: _formShortcutMap,
  431               child: Column(
  432                 children: <Widget>[
  433                   const Spacer(),
  434                   InputDatePickerFormField(
  435                     initialDate: _selectedDate,
  436                     firstDate: widget.firstDate,
  437                     lastDate: widget.lastDate,
  438                     onDateSubmitted: _handleDateChanged,
  439                     onDateSaved: _handleDateChanged,
  440                     selectableDayPredicate: widget.selectableDayPredicate,
  441                     errorFormatText: widget.errorFormatText,
  442                     errorInvalidText: widget.errorInvalidText,
  443                     fieldHintText: widget.fieldHintText,
  444                     fieldLabelText: widget.fieldLabelText,
  445                     autofocus: true,
  446                   ),
  447                   const Spacer(),
  448                 ],
  449               ),
  450             ),
  451           ),
  452         );
  453         entryModeIcon = Icons.calendar_today;
  454         entryModeTooltip = localizations.calendarModeButtonLabel;
  455         break;
  456     }
  457 
  458     final Widget header = DatePickerHeader(
  459       helpText: widget.helpText ?? localizations.datePickerHelpText,
  460       titleText: dateText,
  461       titleStyle: dateStyle,
  462       orientation: orientation,
  463       isShort: orientation == Orientation.landscape,
  464       icon: entryModeIcon,
  465       iconTooltip: entryModeTooltip,
  466       onIconPressed: _handleEntryModeToggle,
  467     );
  468 
  469     final Size dialogSize = _dialogSize(context) * textScaleFactor;
  470     return Dialog(
  471       child: AnimatedContainer(
  472         width: dialogSize.width,
  473         height: dialogSize.height,
  474         duration: _dialogSizeAnimationDuration,
  475         curve: Curves.easeIn,
  476         child: MediaQuery(
  477           data: MediaQuery.of(context).copyWith(
  478             textScaleFactor: textScaleFactor,
  479           ),
  480           child: Builder(builder: (BuildContext context) {
  481             switch (orientation) {
  482               case Orientation.portrait:
  483                 return Column(
  484                   mainAxisSize: MainAxisSize.min,
  485                   crossAxisAlignment: CrossAxisAlignment.stretch,
  486                   children: <Widget>[
  487                     header,
  488                     Expanded(child: picker),
  489                     actions,
  490                   ],
  491                 );
  492               case Orientation.landscape:
  493                 return Row(
  494                   mainAxisSize: MainAxisSize.min,
  495                   crossAxisAlignment: CrossAxisAlignment.stretch,
  496                   children: <Widget>[
  497                     header,
  498                     Flexible(
  499                       child: Column(
  500                         mainAxisSize: MainAxisSize.min,
  501                         crossAxisAlignment: CrossAxisAlignment.stretch,
  502                         children: <Widget>[
  503                           Expanded(child: picker),
  504                           actions,
  505                         ],
  506                       ),
  507                     ),
  508                   ],
  509                 );
  510             }
  511             return null;
  512           }),
  513         ),
  514       ),
  515       insetPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
  516       clipBehavior: Clip.antiAlias,
  517     );
  518   }
  519 }