diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c40ade..bb84aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,4 +18,9 @@ ## 0.1.2 -* Fixing an incorrect boolean (iOS picker was inverted with the Android one) \ No newline at end of file +* Fixing an incorrect boolean (iOS picker was inverted with the Android one) + +## 2.0.0 + +* DateFormField now extends FormField. All issues related to this are now fiex +* The style of the DateField (and by extension the one of DateFormField) is now rigorously applying the theme or any customization. \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..17bc5a5 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,190 @@ +# Specify analysis options. +# +# Until there are meta linter rules, each desired lint must be explicitly enabled. +# See: https://github.com/dart-lang/linter/issues/288 +# +# For a list of lints, see: http://dart-lang.github.io/linter/lints/ +# See the configuration guide for more +# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer +# +# There are other similar analysis options files in the flutter repos, +# which should be kept in sync with this file: +# +# - analysis_options.yaml (this file) +# - packages/flutter/lib/analysis_options_user.yaml +# - https://github.com/flutter/plugins/blob/master/analysis_options.yaml +# - https://github.com/flutter/engine/blob/master/analysis_options.yaml +# +# This file contains the analysis options used by Flutter tools, such as IntelliJ, +# Android Studio, and the `flutter analyze` command. + +analyzer: + strong-mode: + implicit-dynamic: false + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: warning + # treat missing returns as a warning (not a hint) + missing_return: warning + # allow having TODOs in the code + todo: ignore + # Ignore analyzer hints for updating pubspecs when using Future or + # Stream and not importing dart:async + # Please see https://github.com/flutter/flutter/pull/24528 for details. + sdk_version_async_exported_from_core: ignore + exclude: + - "bin/cache/**" + # the following two are relative to the stocks example and the flutter package respectively + # see https://github.com/dart-lang/sdk/issues/28463 + - "lib/i18n/stock_messages_*.dart" + - "lib/src/http/**" + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + - always_require_non_null_named_parameters + # - always_specify_types + - annotate_overrides + # - avoid_annotating_with_dynamic # conflicts with always_specify_types + - avoid_as + - avoid_bool_literals_in_conditional_expressions + # - avoid_catches_without_on_clauses # we do this commonly + # - avoid_catching_errors # we do this commonly + - avoid_classes_with_only_static_members + # - avoid_double_and_int_checks # only useful when targeting JS runtime + - avoid_empty_else + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + # - avoid_implementing_value_types # not yet tested + - avoid_init_to_null + # - avoid_js_rounded_ints # only useful when targeting JS runtime + - avoid_null_checks_in_equality_operators + # - avoid_positional_boolean_parameters # not yet tested + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + # - avoid_returning_null # there are plenty of valid reasons to return null + # - avoid_returning_null_for_future # not yet tested + - avoid_returning_null_for_void + # - avoid_returning_this # there are plenty of valid reasons to return this + # - avoid_setters_without_getters # not yet tested + # - avoid_shadowing_type_parameters # not yet tested + # - avoid_single_cascade_in_expression_statements # not yet tested + - avoid_slow_async_io + - avoid_types_as_parameter_names + # - avoid_types_on_closure_parameters # conflicts with always_specify_types + - avoid_unused_constructor_parameters + - avoid_void_async + - await_only_futures + - camel_case_types + - cancel_subscriptions + # - cascade_invocations # not yet tested + # - close_sinks # not reliable enough + # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 + # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + # - curly_braces_in_flow_control_structures # not yet tested + # - diagnostic_describe_all_properties # not yet tested + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + # - file_names # not yet tested + - hash_and_equals + - implementation_imports + # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 + - iterable_contains_unrelated_type + # - join_return_with_assignment # not yet tested + - library_names + - library_prefixes + # - lines_longer_than_80_chars # not yet tested + - list_remove_unrelated_type + # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 + - no_adjacent_strings_in_list + - no_duplicate_case_values + - non_constant_identifier_names + # - null_closures # not yet tested + # - omit_local_variable_types # opposite of always_specify_types + # - one_member_abstracts # too many false positives + # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + # - prefer_asserts_with_message # not yet tested + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + # - prefer_constructors_over_static_methods # not yet tested + - prefer_contains + # - prefer_double_quotes # opposite of prefer_single_quotes + - prefer_equal_for_default_values + # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + # - prefer_for_elements_to_map_fromIterable # not yet tested + - prefer_foreach + # - prefer_function_declarations_over_variables # not yet tested + # - prefer_generic_function_type_aliases + # - prefer_if_elements_to_conditional_expressions # not yet tested + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + # - prefer_int_literals # not yet tested + # - prefer_interpolation_to_compose_strings # not yet tested + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + # - prefer_mixin # https://github.com/dart-lang/language/issues/32 + # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + # - provide_deprecation_message # not yet tested + # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - recursive_getters + - slash_for_doc_comments + # - sort_child_properties_last # not yet tested + - sort_constructors_first + - sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures # too many false positives + # - unnecessary_await_in_return # not yet tested + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_this + - unrelated_type_equality_checks + # - unsafe_html # not yet tested + - use_full_hex_values_for_flutter_colors + # - use_function_type_syntax_for_parameters # not yet tested + - use_rethrow_when_possible + # - use_setters_to_change_properties # not yet tested + # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + - valid_regexps + # - void_checks # not yet tested \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index eae443e..a62eb65 100644 --- a/example/main.dart +++ b/example/main.dart @@ -30,6 +30,7 @@ class _HomeWidgetState extends State { Column( children: [ DateField( + selectedDate: selectedDate, onDateSelected: (DateTime date) { setState(() { diff --git a/lib/date_field.dart b/lib/date_field.dart index 86a895e..9406487 100644 --- a/lib/date_field.dart +++ b/lib/date_field.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; - import 'package:intl/intl.dart'; /// A [FormField] that contains a [DateField]. @@ -14,30 +13,56 @@ import 'package:intl/intl.dart'; /// save, reset, or validate multiple fields at once. To use without a [Form], /// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to /// save or reset the form field. -class DateFormField extends StatelessWidget { - /// An optional method to call with the final value when the form is saved via - /// [FormState.save]. - final FormFieldSetter onSaved; +class DateFormField extends FormField { - /// An optional method that validates an input. Returns an error string to - /// display if the input is invalid, or null otherwise. - final FormFieldValidator validator; + DateFormField({ + Key key, + FormFieldSetter onSaved, + FormFieldValidator validator, + DateTime initialValue, + bool autovalidate = false, + bool enabled = true, + this.onDateSelected, + this.firstDate, + this.lastDate, + this.label = 'Select date', + this.dateFormat, + this.decoration, + this.initialDatePickerMode = DatePickerMode.day + }) : super( + key: key, + initialValue: initialValue, + onSaved: onSaved, + validator: validator, + autovalidate: autovalidate, + enabled: enabled, + builder: (FormFieldState field) { + final _DateFormFieldState state = field; - /// An optional value to initialize the form field to, or null otherwise. - final DateTime initialValue; + void onChangedHandler(DateTime value) { + if (onDateSelected != null) { + onDateSelected(value); + } + field.didChange(value); + } + return DateField( + label: label, + firstDate: firstDate, + lastDate: lastDate, + decoration: decoration, + initialDatePickerMode: initialDatePickerMode, + dateFormat: dateFormat, + errorText: state.errorText, + onDateSelected: onChangedHandler, + selectedDate: state.value, + enabled: enabled, + ); + }, + ); - /// If true, this form field will validate and update its error text - /// immediately after every change. Otherwise, you must call - /// [FormFieldState.validate] to validate. If part of a [Form] that - /// auto-validates, this value will be ignored. - final bool autovalidate; - - /// Whether the form is able to receive user input. - /// - /// Defaults to true. If [autovalidate] is true, the field will be validated. - /// Likewise, if this field is false, the widget will not be validated - /// regardless of [autovalidate]. - final bool enabled; + /// (optional) A callback that will be triggered whenever a new + /// DateTime is selected + final ValueChanged onDateSelected; /// (optional) The first date that the user can select (default is 1900) final DateTime firstDate; @@ -57,64 +82,31 @@ class DateFormField extends StatelessWidget { /// (optional) Let you choose the [DatePickerMode] for the date picker! (default is [DatePickerMode.day] final DatePickerMode initialDatePickerMode; - const DateFormField( - {Key key, - this.onSaved, - this.validator, - this.initialValue, - this.autovalidate = false, - this.enabled = true, - this.firstDate, - this.lastDate, - this.label = 'Select date', - this.dateFormat, - this.decoration, - this.initialDatePickerMode}) - : super(key: key); @override - Widget build(BuildContext context) { - return FormField( - onSaved: onSaved, - autovalidate: autovalidate, - validator: validator, - enabled: enabled, - initialValue: initialValue, - builder: (FormFieldState state) { - return DateField( - label: label, - firstDate: firstDate, - lastDate: lastDate, - decoration: decoration, - initialDatePickerMode: initialDatePickerMode, - dateFormat: dateFormat, - errorText: state.errorText, - onDateSelected: (DateTime value) { - state.didChange(value); - }, - selectedDate: state.value, - ); - }); - } + _DateFormFieldState createState() => _DateFormFieldState(); } -/// +class _DateFormFieldState extends FormFieldState {} + /// [DateField] /// /// Shows an [_InputDropdown] that'll trigger [DateField._selectDate] whenever the user /// clicks on it ! The date picker is **platform responsive** (ios date picker style for ios, ...) class DateField extends StatelessWidget { + /// Default constructor - DateField({ + const DateField({ @required this.onDateSelected, @required this.selectedDate, this.firstDate, this.lastDate, this.initialDatePickerMode = DatePickerMode.day, this.decoration, - this.label = 'Select date', this.errorText, this.dateFormat, + this.label = 'Select date', + this.enabled = true, }); /// Callback for whenever the user selects a [DateTime] @@ -144,19 +136,20 @@ class DateField extends StatelessWidget { /// (optional) How to display the [DateTime] for the user (default is [DateFormat.yMMMD]) final DateFormat dateFormat; + /// (optional) Whether the field is usable. If false the user won't be able to select any date + final bool enabled; + /// Shows a dialog asking the user to pick a date ! Future _selectDate(BuildContext context) async { - TextFormField(); if (Platform.isIOS) { - showModalBottomSheet( + showModalBottomSheet( context: context, builder: (BuildContext builder) { return Container( height: MediaQuery.of(context).size.height / 4, child: CupertinoDatePicker( mode: CupertinoDatePickerMode.date, - onDateTimeChanged: (DateTime dateTime) => - onDateSelected(dateTime), + onDateTimeChanged: onDateSelected, initialDateTime: selectedDate ?? lastDate ?? DateTime.now(), minimumDate: firstDate, maximumDate: lastDate, @@ -164,8 +157,10 @@ class DateField extends StatelessWidget { ); }, ); - } else { - DateTime _selectedDate = await showDatePicker( + } + else { + + final DateTime _selectedDate = await showDatePicker( context: context, initialDatePickerMode: initialDatePickerMode, initialDate: selectedDate ?? lastDate ?? DateTime.now(), @@ -190,9 +185,9 @@ class DateField extends StatelessWidget { label: text == null ? null : label, errorText: errorText, decoration: decoration, - onPressed: () { + onPressed: enabled ? () { _selectDate(context); - }, + } : null, ); } } @@ -206,13 +201,15 @@ class DateField extends StatelessWidget { class _InputDropdown extends StatelessWidget { const _InputDropdown( {Key key, - this.label, - this.text, - this.decoration, - this.textStyle, - this.onPressed, - this.errorText}) - : super(key: key); + @required this.text, + this.label, + this.decoration, + this.textStyle, + this.onPressed, + this.errorText, + }) : + assert(text != null), + super(key: key); /// The label to display for the field (default is 'Select date') final String label; @@ -234,33 +231,33 @@ class _InputDropdown extends StatelessWidget { @override Widget build(BuildContext context) { - assert(text != null); - BorderRadius inkwellBorderRadius; if (decoration?.border?.runtimeType == OutlineInputBorder) { inkwellBorderRadius = BorderRadius.circular(8); } + final InputDecoration effectiveDecoration = decoration?.copyWith( + errorText: errorText + ) ?? InputDecoration( + labelText: label, + errorText: errorText, + suffixIcon: Icon(Icons.arrow_drop_down), + ).applyDefaults(Theme.of(context).inputDecorationTheme); + return Material( color: Colors.transparent, child: InkWell( borderRadius: inkwellBorderRadius, onTap: onPressed, child: InputDecorator( - decoration: decoration ?? - InputDecoration( - labelText: label, - errorText: errorText, - border: UnderlineInputBorder(borderSide: BorderSide()), - contentPadding: EdgeInsets.only(bottom: 2.0)), + decoration: effectiveDecoration, baseStyle: textStyle, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: [ Text(text, style: textStyle), - Icon(Icons.arrow_drop_down), ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index a81d81e..b57aefa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: date_field description: Contains DateField and DateFormField which allows the user to pick a DateTime from an input field! -version: 0.1.2 +version: 0.2.0 homepage: 'https://github.com/GaspardMerten/date_field' environment: