Fixing label & hint style issues
Moving to a more generic architecture Updating the analysis_options.yaml file
This commit is contained in:
parent
ddcb665d2f
commit
13eaa0df3a
10 changed files with 431 additions and 427 deletions
|
@ -1,3 +1,9 @@
|
||||||
|
##2.1.0
|
||||||
|
|
||||||
|
* Fixing label & hint style issues
|
||||||
|
* Moving to a more generic architecture
|
||||||
|
* Updating the analysis_options.yaml file
|
||||||
|
|
||||||
##2.0.1
|
##2.0.1
|
||||||
|
|
||||||
* Adding the ability to specify the entry mode for the material date picker.
|
* Adding the ability to specify the entry mode for the material date picker.
|
||||||
|
|
|
@ -15,7 +15,7 @@ In the `pubspec.yaml` of your flutter project, add the following dependency:
|
||||||
```yaml
|
```yaml
|
||||||
dependencies:
|
dependencies:
|
||||||
...
|
...
|
||||||
date_field: ^2.0.1
|
date_field: ^2.1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
In your library add the following import:
|
In your library add the following import:
|
||||||
|
@ -63,4 +63,4 @@ DateTimeFormField(
|
||||||
),
|
),
|
||||||
```
|
```
|
||||||
|
|
||||||
You can check the Github repo for a complete example.
|
You can check the GitHub repo for a complete example.
|
|
@ -1,43 +1,14 @@
|
||||||
# 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:
|
analyzer:
|
||||||
strong-mode:
|
strong-mode:
|
||||||
|
implicit-casts: false
|
||||||
implicit-dynamic: false
|
implicit-dynamic: false
|
||||||
errors:
|
errors:
|
||||||
# treat missing required parameters as a warning (not a hint)
|
|
||||||
missing_required_param: warning
|
missing_required_param: warning
|
||||||
# treat missing returns as a warning (not a hint)
|
|
||||||
missing_return: warning
|
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:
|
exclude:
|
||||||
- "bin/cache/**"
|
- "bin/cache/**"
|
||||||
# the following two are relative to the stocks example and the flutter package respectively
|
# Ignore protoc generated files
|
||||||
# see https://github.com/dart-lang/sdk/issues/28463
|
- "dev/conductor/lib/proto/*"
|
||||||
- "lib/i18n/stock_messages_*.dart"
|
|
||||||
- "lib/src/http/**"
|
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
rules:
|
rules:
|
||||||
|
@ -45,18 +16,22 @@ linter:
|
||||||
# the Dart Lint rules page to make maintenance easier
|
# the Dart Lint rules page to make maintenance easier
|
||||||
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
|
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
|
||||||
- always_declare_return_types
|
- always_declare_return_types
|
||||||
|
- always_put_control_body_on_new_line
|
||||||
# - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
|
# - 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_require_non_null_named_parameters
|
||||||
# - always_specify_types
|
- always_specify_types
|
||||||
|
# - always_use_package_imports # we do this commonly
|
||||||
- annotate_overrides
|
- annotate_overrides
|
||||||
# - avoid_annotating_with_dynamic # conflicts with always_specify_types
|
# - avoid_annotating_with_dynamic # conflicts with always_specify_types
|
||||||
- avoid_as
|
|
||||||
- avoid_bool_literals_in_conditional_expressions
|
- avoid_bool_literals_in_conditional_expressions
|
||||||
# - avoid_catches_without_on_clauses # we do this commonly
|
# - avoid_catches_without_on_clauses # we do this commonly
|
||||||
# - avoid_catching_errors # we do this commonly
|
# - avoid_catching_errors # we do this commonly
|
||||||
- avoid_classes_with_only_static_members
|
- avoid_classes_with_only_static_members
|
||||||
# - avoid_double_and_int_checks # only useful when targeting JS runtime
|
# - avoid_double_and_int_checks # only useful when targeting JS runtime
|
||||||
|
- avoid_dynamic_calls
|
||||||
- avoid_empty_else
|
- avoid_empty_else
|
||||||
|
- avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
- avoid_escaping_inner_quotes
|
||||||
- avoid_field_initializers_in_const_classes
|
- avoid_field_initializers_in_const_classes
|
||||||
- avoid_function_literals_in_foreach_calls
|
- avoid_function_literals_in_foreach_calls
|
||||||
# - avoid_implementing_value_types # not yet tested
|
# - avoid_implementing_value_types # not yet tested
|
||||||
|
@ -64,7 +39,9 @@ linter:
|
||||||
# - avoid_js_rounded_ints # only useful when targeting JS runtime
|
# - avoid_js_rounded_ints # only useful when targeting JS runtime
|
||||||
- avoid_null_checks_in_equality_operators
|
- avoid_null_checks_in_equality_operators
|
||||||
# - avoid_positional_boolean_parameters # not yet tested
|
# - avoid_positional_boolean_parameters # not yet tested
|
||||||
|
# - avoid_print # not yet tested
|
||||||
# - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356)
|
# - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356)
|
||||||
|
# - avoid_redundant_argument_values # not yet tested
|
||||||
- avoid_relative_lib_imports
|
- avoid_relative_lib_imports
|
||||||
- avoid_renaming_method_parameters
|
- avoid_renaming_method_parameters
|
||||||
- avoid_return_types_on_setters
|
- avoid_return_types_on_setters
|
||||||
|
@ -73,42 +50,56 @@ linter:
|
||||||
- avoid_returning_null_for_void
|
- avoid_returning_null_for_void
|
||||||
# - avoid_returning_this # there are plenty of valid reasons to return this
|
# - avoid_returning_this # there are plenty of valid reasons to return this
|
||||||
# - avoid_setters_without_getters # not yet tested
|
# - avoid_setters_without_getters # not yet tested
|
||||||
# - avoid_shadowing_type_parameters # not yet tested
|
- avoid_shadowing_type_parameters
|
||||||
# - avoid_single_cascade_in_expression_statements # not yet tested
|
- avoid_single_cascade_in_expression_statements
|
||||||
- avoid_slow_async_io
|
- avoid_slow_async_io
|
||||||
|
- avoid_type_to_string
|
||||||
- avoid_types_as_parameter_names
|
- avoid_types_as_parameter_names
|
||||||
# - avoid_types_on_closure_parameters # conflicts with always_specify_types
|
# - avoid_types_on_closure_parameters # conflicts with always_specify_types
|
||||||
|
- avoid_unnecessary_containers
|
||||||
- avoid_unused_constructor_parameters
|
- avoid_unused_constructor_parameters
|
||||||
- avoid_void_async
|
- avoid_void_async
|
||||||
|
# - avoid_web_libraries_in_flutter # not yet tested
|
||||||
- await_only_futures
|
- await_only_futures
|
||||||
|
- camel_case_extensions
|
||||||
- camel_case_types
|
- camel_case_types
|
||||||
- cancel_subscriptions
|
- cancel_subscriptions
|
||||||
# - cascade_invocations # not yet tested
|
# - cascade_invocations # not yet tested
|
||||||
|
- cast_nullable_to_non_nullable
|
||||||
# - close_sinks # not reliable enough
|
# - close_sinks # not reliable enough
|
||||||
# - comment_references # blocked on https://github.com/flutter/flutter/issues/20765
|
# - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142
|
||||||
# - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204
|
# - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204
|
||||||
- control_flow_in_finally
|
- control_flow_in_finally
|
||||||
# - curly_braces_in_flow_control_structures # not yet tested
|
# - curly_braces_in_flow_control_structures # not required by flutter style
|
||||||
# - diagnostic_describe_all_properties # not yet tested
|
# - diagnostic_describe_all_properties # not yet tested
|
||||||
- directives_ordering
|
- directives_ordering
|
||||||
|
# - do_not_use_environment # we do this commonly
|
||||||
- empty_catches
|
- empty_catches
|
||||||
- empty_constructor_bodies
|
- empty_constructor_bodies
|
||||||
- empty_statements
|
- empty_statements
|
||||||
# - file_names # not yet tested
|
- exhaustive_cases
|
||||||
|
- file_names
|
||||||
|
- flutter_style_todos
|
||||||
- hash_and_equals
|
- hash_and_equals
|
||||||
- implementation_imports
|
- implementation_imports
|
||||||
# - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811
|
# - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811
|
||||||
- iterable_contains_unrelated_type
|
- iterable_contains_unrelated_type
|
||||||
# - join_return_with_assignment # not yet tested
|
# - join_return_with_assignment # not required by flutter style
|
||||||
|
- leading_newlines_in_multiline_strings
|
||||||
- library_names
|
- library_names
|
||||||
- library_prefixes
|
- library_prefixes
|
||||||
# - lines_longer_than_80_chars # not yet tested
|
# - lines_longer_than_80_chars # not required by flutter style
|
||||||
- list_remove_unrelated_type
|
- list_remove_unrelated_type
|
||||||
# - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181
|
# - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181
|
||||||
|
- missing_whitespace_between_adjacent_strings
|
||||||
- no_adjacent_strings_in_list
|
- no_adjacent_strings_in_list
|
||||||
|
# - no_default_cases # too many false positives
|
||||||
- no_duplicate_case_values
|
- no_duplicate_case_values
|
||||||
|
- no_logic_in_create_state
|
||||||
|
# - no_runtimeType_toString # ok in tests; we enable this only in packages/
|
||||||
- non_constant_identifier_names
|
- non_constant_identifier_names
|
||||||
# - null_closures # not yet tested
|
- null_check_on_nullable_type_parameter
|
||||||
|
- null_closures
|
||||||
# - omit_local_variable_types # opposite of always_specify_types
|
# - omit_local_variable_types # opposite of always_specify_types
|
||||||
# - one_member_abstracts # too many false positives
|
# - one_member_abstracts # too many false positives
|
||||||
# - only_throw_errors # https://github.com/flutter/flutter/issues/5792
|
# - only_throw_errors # https://github.com/flutter/flutter/issues/5792
|
||||||
|
@ -119,14 +110,14 @@ linter:
|
||||||
# - parameter_assignments # we do this commonly
|
# - parameter_assignments # we do this commonly
|
||||||
- prefer_adjacent_string_concatenation
|
- prefer_adjacent_string_concatenation
|
||||||
- prefer_asserts_in_initializer_lists
|
- prefer_asserts_in_initializer_lists
|
||||||
# - prefer_asserts_with_message # not yet tested
|
# - prefer_asserts_with_message # not required by flutter style
|
||||||
- prefer_collection_literals
|
- prefer_collection_literals
|
||||||
- prefer_conditional_assignment
|
- prefer_conditional_assignment
|
||||||
- prefer_const_constructors
|
- prefer_const_constructors
|
||||||
- prefer_const_constructors_in_immutables
|
- prefer_const_constructors_in_immutables
|
||||||
- prefer_const_declarations
|
- prefer_const_declarations
|
||||||
- prefer_const_literals_to_create_immutables
|
- prefer_const_literals_to_create_immutables
|
||||||
# - prefer_constructors_over_static_methods # not yet tested
|
# - prefer_constructors_over_static_methods # far too many false positives
|
||||||
- prefer_contains
|
- prefer_contains
|
||||||
# - prefer_double_quotes # opposite of prefer_single_quotes
|
# - prefer_double_quotes # opposite of prefer_single_quotes
|
||||||
- prefer_equal_for_default_values
|
- prefer_equal_for_default_values
|
||||||
|
@ -134,57 +125,72 @@ linter:
|
||||||
- prefer_final_fields
|
- prefer_final_fields
|
||||||
- prefer_final_in_for_each
|
- prefer_final_in_for_each
|
||||||
- prefer_final_locals
|
- prefer_final_locals
|
||||||
# - prefer_for_elements_to_map_fromIterable # not yet tested
|
- prefer_for_elements_to_map_fromIterable
|
||||||
- prefer_foreach
|
- prefer_foreach
|
||||||
# - prefer_function_declarations_over_variables # not yet tested
|
- prefer_function_declarations_over_variables
|
||||||
# - prefer_generic_function_type_aliases
|
- prefer_generic_function_type_aliases
|
||||||
# - prefer_if_elements_to_conditional_expressions # not yet tested
|
- prefer_if_elements_to_conditional_expressions
|
||||||
- prefer_if_null_operators
|
- prefer_if_null_operators
|
||||||
- prefer_initializing_formals
|
- prefer_initializing_formals
|
||||||
- prefer_inlined_adds
|
- prefer_inlined_adds
|
||||||
# - prefer_int_literals # not yet tested
|
# - prefer_int_literals # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#use-double-literals-for-double-constants
|
||||||
# - prefer_interpolation_to_compose_strings # not yet tested
|
- prefer_interpolation_to_compose_strings
|
||||||
- prefer_is_empty
|
- prefer_is_empty
|
||||||
- prefer_is_not_empty
|
- prefer_is_not_empty
|
||||||
|
- prefer_is_not_operator
|
||||||
- prefer_iterable_whereType
|
- prefer_iterable_whereType
|
||||||
# - prefer_mixin # https://github.com/dart-lang/language/issues/32
|
# - 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_null_aware_operators
|
||||||
|
# - prefer_relative_imports # incompatible with sub-package imports
|
||||||
- prefer_single_quotes
|
- prefer_single_quotes
|
||||||
- prefer_spread_collections
|
- prefer_spread_collections
|
||||||
- prefer_typing_uninitialized_variables
|
- prefer_typing_uninitialized_variables
|
||||||
- prefer_void_to_null
|
- prefer_void_to_null
|
||||||
# - provide_deprecation_message # not yet tested
|
- provide_deprecation_message
|
||||||
# - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml
|
# - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml
|
||||||
- recursive_getters
|
- recursive_getters
|
||||||
|
- sized_box_for_whitespace
|
||||||
- slash_for_doc_comments
|
- slash_for_doc_comments
|
||||||
# - sort_child_properties_last # not yet tested
|
# - sort_child_properties_last # not yet tested
|
||||||
- sort_constructors_first
|
- sort_constructors_first
|
||||||
- sort_pub_dependencies
|
# - sort_pub_dependencies # prevents separating pinned transitive dependencies
|
||||||
- sort_unnamed_constructors_first
|
- sort_unnamed_constructors_first
|
||||||
- test_types_in_equals
|
- test_types_in_equals
|
||||||
- throw_in_finally
|
- throw_in_finally
|
||||||
|
- tighten_type_of_initializing_formals
|
||||||
# - type_annotate_public_apis # subset of always_specify_types
|
# - type_annotate_public_apis # subset of always_specify_types
|
||||||
- type_init_formals
|
- type_init_formals
|
||||||
# - unawaited_futures # too many false positives
|
# - unawaited_futures # too many false positives
|
||||||
# - unnecessary_await_in_return # not yet tested
|
- unnecessary_await_in_return
|
||||||
- unnecessary_brace_in_string_interps
|
- unnecessary_brace_in_string_interps
|
||||||
- unnecessary_const
|
- unnecessary_const
|
||||||
|
# - unnecessary_final # conflicts with prefer_final_locals
|
||||||
- unnecessary_getters_setters
|
- unnecessary_getters_setters
|
||||||
# - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498
|
# - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498
|
||||||
- unnecessary_new
|
- unnecessary_new
|
||||||
- unnecessary_null_aware_assignments
|
- unnecessary_null_aware_assignments
|
||||||
|
- unnecessary_null_checks
|
||||||
- unnecessary_null_in_if_null_operators
|
- unnecessary_null_in_if_null_operators
|
||||||
|
- unnecessary_nullable_for_final_variable_declarations
|
||||||
- unnecessary_overrides
|
- unnecessary_overrides
|
||||||
- unnecessary_parenthesis
|
- unnecessary_parenthesis
|
||||||
|
# - unnecessary_raw_strings # not yet tested
|
||||||
- unnecessary_statements
|
- unnecessary_statements
|
||||||
|
- unnecessary_string_escapes
|
||||||
|
- unnecessary_string_interpolations
|
||||||
- unnecessary_this
|
- unnecessary_this
|
||||||
- unrelated_type_equality_checks
|
- unrelated_type_equality_checks
|
||||||
# - unsafe_html # not yet tested
|
# - unsafe_html # not yet tested
|
||||||
- use_full_hex_values_for_flutter_colors
|
- use_full_hex_values_for_flutter_colors
|
||||||
# - use_function_type_syntax_for_parameters # not yet tested
|
- use_function_type_syntax_for_parameters
|
||||||
|
# - use_if_null_to_convert_nulls_to_bools # not yet tested
|
||||||
|
- use_is_even_rather_than_modulo
|
||||||
|
- use_key_in_widget_constructors
|
||||||
|
- use_late_for_private_fields_and_variables
|
||||||
|
- use_raw_strings
|
||||||
- use_rethrow_when_possible
|
- use_rethrow_when_possible
|
||||||
# - use_setters_to_change_properties # not yet tested
|
# - use_setters_to_change_properties # not yet tested
|
||||||
# - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182
|
# - 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
|
# - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review
|
||||||
- valid_regexps
|
- valid_regexps
|
||||||
# - void_checks # not yet tested
|
- void_checks
|
||||||
|
|
|
@ -1,33 +1,38 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:date_field/date_field.dart';
|
import 'package:date_field/date_field.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
|
const MyApp({Key? key}) : super(key: key);
|
||||||
|
|
||||||
// This widget is the root of your application.
|
// This widget is the root of your application.
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'Flutter Demo',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
inputDecorationTheme:
|
inputDecorationTheme: const InputDecorationTheme(
|
||||||
const InputDecorationTheme(border: OutlineInputBorder()),
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
),
|
),
|
||||||
home: MyHomePage(),
|
home: const MyHomePage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
class MyHomePage extends StatefulWidget {
|
||||||
|
const MyHomePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MyHomePageState createState() => _MyHomePageState();
|
_MyHomePageState createState() => _MyHomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
DateTime selectedDate;
|
DateTime? selectedDate;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -36,7 +41,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: <Widget>[
|
||||||
const FlutterLogo(size: 100),
|
const FlutterLogo(size: 100),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
const Text('DateField package showcase'),
|
const Text('DateField package showcase'),
|
||||||
|
@ -59,7 +64,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
),
|
),
|
||||||
Form(
|
Form(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: <Widget>[
|
||||||
DateTimeFormField(
|
DateTimeFormField(
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
hintStyle: TextStyle(color: Colors.black45),
|
hintStyle: TextStyle(color: Colors.black45),
|
||||||
|
@ -69,7 +74,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
labelText: 'My Super Date Time Field',
|
labelText: 'My Super Date Time Field',
|
||||||
),
|
),
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
validator: (e) =>
|
validator: (DateTime? e) =>
|
||||||
(e?.day ?? 0) == 1 ? 'Please not the first day' : null,
|
(e?.day ?? 0) == 1 ? 'Please not the first day' : null,
|
||||||
onDateSelected: (DateTime value) {
|
onDateSelected: (DateTime value) {
|
||||||
print(value);
|
print(value);
|
||||||
|
@ -86,8 +91,11 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
),
|
),
|
||||||
mode: DateTimeFieldPickerMode.time,
|
mode: DateTimeFieldPickerMode.time,
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
validator: (e) =>
|
validator: (DateTime? e) {
|
||||||
(e?.day ?? 0) == 1 ? 'Please not the first day' : null,
|
return (e?.day ?? 0) == 1
|
||||||
|
? 'Please not the first day'
|
||||||
|
: null;
|
||||||
|
},
|
||||||
onDateSelected: (DateTime value) {
|
onDateSelected: (DateTime value) {
|
||||||
print(value);
|
print(value);
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.7.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -21,7 +21,7 @@ dependencies:
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.3
|
||||||
|
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility that Flutter provides. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'package:example/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
|
||||||
// Build our app and trigger a frame.
|
|
||||||
await tester.pumpWidget(MyApp());
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
|
||||||
expect(find.text('0'), findsOneWidget);
|
|
||||||
expect(find.text('1'), findsNothing);
|
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
|
||||||
expect(find.text('0'), findsNothing);
|
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,325 +1,2 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
export 'package:date_field/src/field.dart';
|
||||||
import 'package:flutter/material.dart';
|
export 'package:date_field/src/form_field.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
final DateTime _kDefaultFirstSelectableDate = DateTime(1900);
|
|
||||||
final DateTime _kDefaultLastSelectableDate = DateTime(2100);
|
|
||||||
|
|
||||||
const double _kCupertinoDatePickerHeight = 216;
|
|
||||||
|
|
||||||
/// A [FormField] that contains a [DateTimeField].
|
|
||||||
///
|
|
||||||
/// This is a convenience widget that wraps a [DateTimeField] widget in a
|
|
||||||
/// [FormField].
|
|
||||||
///
|
|
||||||
/// A [Form] ancestor is not required. The [Form] simply makes it easier to
|
|
||||||
/// 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 DateTimeFormField extends FormField<DateTime> {
|
|
||||||
DateTimeFormField({
|
|
||||||
Key? key,
|
|
||||||
FormFieldSetter<DateTime>? onSaved,
|
|
||||||
FormFieldValidator<DateTime>? validator,
|
|
||||||
DateTime? initialValue,
|
|
||||||
AutovalidateMode? autovalidateMode,
|
|
||||||
bool enabled = true,
|
|
||||||
TextStyle? dateTextStyle,
|
|
||||||
DateFormat? dateFormat,
|
|
||||||
DateTime? firstDate,
|
|
||||||
DateTime? lastDate,
|
|
||||||
ValueChanged<DateTime>? onDateSelected,
|
|
||||||
InputDecoration? decoration,
|
|
||||||
DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar,
|
|
||||||
DatePickerMode initialDatePickerMode = DatePickerMode.day,
|
|
||||||
DateTimeFieldPickerMode mode = DateTimeFieldPickerMode.dateAndTime,
|
|
||||||
}) : super(
|
|
||||||
key: key,
|
|
||||||
initialValue: initialValue,
|
|
||||||
onSaved: onSaved,
|
|
||||||
validator: validator,
|
|
||||||
autovalidateMode: autovalidateMode,
|
|
||||||
enabled: enabled,
|
|
||||||
builder: (FormFieldState<DateTime> field) {
|
|
||||||
// Theme defaults are applied inside the _InputDropdown widget
|
|
||||||
final InputDecoration _decorationWithThemeDefaults =
|
|
||||||
decoration ?? const InputDecoration();
|
|
||||||
|
|
||||||
final InputDecoration effectiveDecoration =
|
|
||||||
_decorationWithThemeDefaults.copyWith(
|
|
||||||
errorText: field.errorText);
|
|
||||||
|
|
||||||
void onChangedHandler(DateTime value) {
|
|
||||||
if (onDateSelected != null) {
|
|
||||||
onDateSelected(value);
|
|
||||||
}
|
|
||||||
field.didChange(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return DateTimeField(
|
|
||||||
firstDate: firstDate,
|
|
||||||
lastDate: lastDate,
|
|
||||||
decoration: effectiveDecoration,
|
|
||||||
initialDatePickerMode: initialDatePickerMode,
|
|
||||||
dateFormat: dateFormat,
|
|
||||||
onDateSelected: onChangedHandler,
|
|
||||||
selectedDate: field.value,
|
|
||||||
enabled: enabled,
|
|
||||||
mode: mode,
|
|
||||||
initialEntryMode: initialEntryMode,
|
|
||||||
dateTextStyle: dateTextStyle,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_DateFormFieldState createState() => _DateFormFieldState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DateFormFieldState extends FormFieldState<DateTime> {}
|
|
||||||
|
|
||||||
/// [DateTimeField]
|
|
||||||
///
|
|
||||||
/// Shows an [_InputDropdown] that'll trigger [DateTimeField._selectDate] whenever the user
|
|
||||||
/// clicks on it ! The date picker is **platform responsive** (ios date picker style for ios, ...)
|
|
||||||
class DateTimeField extends StatelessWidget {
|
|
||||||
DateTimeField({
|
|
||||||
Key? key,
|
|
||||||
required this.onDateSelected,
|
|
||||||
required this.selectedDate,
|
|
||||||
this.initialDatePickerMode = DatePickerMode.day,
|
|
||||||
this.decoration,
|
|
||||||
this.enabled = true,
|
|
||||||
this.mode = DateTimeFieldPickerMode.dateAndTime,
|
|
||||||
this.initialEntryMode = DatePickerEntryMode.calendar,
|
|
||||||
this.dateTextStyle,
|
|
||||||
DateTime? firstDate,
|
|
||||||
DateTime? lastDate,
|
|
||||||
DateFormat? dateFormat,
|
|
||||||
}) : dateFormat = dateFormat ?? getDateFormatFromDateFieldPickerMode(mode),
|
|
||||||
firstDate = firstDate ?? _kDefaultFirstSelectableDate,
|
|
||||||
lastDate = lastDate ?? _kDefaultLastSelectableDate,
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
DateTimeField.time({
|
|
||||||
Key? key,
|
|
||||||
this.onDateSelected,
|
|
||||||
this.selectedDate,
|
|
||||||
this.decoration,
|
|
||||||
this.enabled,
|
|
||||||
this.dateTextStyle,
|
|
||||||
this.initialEntryMode = DatePickerEntryMode.calendar,
|
|
||||||
DateTime? firstDate,
|
|
||||||
DateTime? lastDate,
|
|
||||||
}) : initialDatePickerMode = null,
|
|
||||||
mode = DateTimeFieldPickerMode.time,
|
|
||||||
dateFormat = DateFormat.jm(),
|
|
||||||
firstDate = firstDate ?? DateTime(2000),
|
|
||||||
lastDate = lastDate ?? DateTime(2001),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// Callback for whenever the user selects a [DateTime]
|
|
||||||
final ValueChanged<DateTime>? onDateSelected;
|
|
||||||
|
|
||||||
/// The current selected date to display inside the field
|
|
||||||
final DateTime? selectedDate;
|
|
||||||
|
|
||||||
/// The first date that the user can select (default is 1900)
|
|
||||||
final DateTime firstDate;
|
|
||||||
|
|
||||||
/// The last date that the user can select (default is 2100)
|
|
||||||
final DateTime lastDate;
|
|
||||||
|
|
||||||
/// Let you choose the [DatePickerMode] for the date picker! (default is [DatePickerMode.day]
|
|
||||||
final DatePickerMode? initialDatePickerMode;
|
|
||||||
|
|
||||||
/// Custom [InputDecoration] for the [InputDecorator] widget
|
|
||||||
final InputDecoration? decoration;
|
|
||||||
|
|
||||||
/// How to display the [DateTime] for the user (default is [DateFormat.yMMMD])
|
|
||||||
final DateFormat dateFormat;
|
|
||||||
|
|
||||||
/// Whether the field is usable. If false the user won't be able to select any date
|
|
||||||
final bool? enabled;
|
|
||||||
|
|
||||||
/// Whether to ask the user to pick only the date, the time or both.
|
|
||||||
final DateTimeFieldPickerMode mode;
|
|
||||||
|
|
||||||
/// [TextStyle] of the selected date inside the field.
|
|
||||||
final TextStyle? dateTextStyle;
|
|
||||||
|
|
||||||
/// The initial entry mode for the material date picker dialog
|
|
||||||
final DatePickerEntryMode initialEntryMode;
|
|
||||||
|
|
||||||
/// Shows a dialog asking the user to pick a date !
|
|
||||||
Future<void> _selectDate(BuildContext context) async {
|
|
||||||
final DateTime initialDateTime = selectedDate ?? DateTime.now();
|
|
||||||
|
|
||||||
if (Theme.of(context).platform == TargetPlatform.iOS) {
|
|
||||||
showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext builder) {
|
|
||||||
return Container(
|
|
||||||
height: _kCupertinoDatePickerHeight,
|
|
||||||
child: CupertinoDatePicker(
|
|
||||||
mode: _cupertinoModeFromPickerMode(mode),
|
|
||||||
onDateTimeChanged: onDateSelected!,
|
|
||||||
initialDateTime: initialDateTime,
|
|
||||||
minimumDate: firstDate,
|
|
||||||
maximumDate: lastDate,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
DateTime _selectedDateTime = initialDateTime;
|
|
||||||
|
|
||||||
if ([DateTimeFieldPickerMode.dateAndTime, DateTimeFieldPickerMode.date]
|
|
||||||
.contains(mode)) {
|
|
||||||
final DateTime? _selectedDate = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDatePickerMode: initialDatePickerMode!,
|
|
||||||
initialDate: initialDateTime,
|
|
||||||
initialEntryMode: initialEntryMode,
|
|
||||||
firstDate: firstDate,
|
|
||||||
lastDate: lastDate,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (_selectedDate != null) {
|
|
||||||
_selectedDateTime = _selectedDate;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([DateTimeFieldPickerMode.dateAndTime, DateTimeFieldPickerMode.time]
|
|
||||||
.contains(mode)) {
|
|
||||||
final TimeOfDay? _selectedTime = await showTimePicker(
|
|
||||||
initialTime: TimeOfDay.fromDateTime(initialDateTime),
|
|
||||||
context: context,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (_selectedTime != null) {
|
|
||||||
_selectedDateTime = DateTime(
|
|
||||||
_selectedDateTime.year,
|
|
||||||
_selectedDateTime.month,
|
|
||||||
_selectedDateTime.day,
|
|
||||||
_selectedTime.hour,
|
|
||||||
_selectedTime.minute,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDateSelected!(_selectedDateTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
String? text;
|
|
||||||
|
|
||||||
if (selectedDate != null) text = dateFormat.format(selectedDate!);
|
|
||||||
|
|
||||||
TextStyle? textStyle;
|
|
||||||
|
|
||||||
if (text == null) {
|
|
||||||
textStyle = decoration!.hintStyle ??
|
|
||||||
Theme.of(context).inputDecorationTheme.hintStyle;
|
|
||||||
} else {
|
|
||||||
textStyle = dateTextStyle ?? dateTextStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
final bool shouldDisplayLabelText = (text ?? decoration!.hintText) != null;
|
|
||||||
|
|
||||||
InputDecoration? effectiveDecoration = decoration;
|
|
||||||
|
|
||||||
if (!shouldDisplayLabelText) {
|
|
||||||
effectiveDecoration = effectiveDecoration!.copyWith(labelText: '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return _InputDropdown(
|
|
||||||
text: text ??
|
|
||||||
decoration!.hintText ??
|
|
||||||
decoration!.labelText ??
|
|
||||||
'Select date',
|
|
||||||
textStyle: textStyle,
|
|
||||||
decoration: effectiveDecoration,
|
|
||||||
onPressed: enabled! ? () => _selectDate(context) : null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Those values are used by the [DateTimeField] widget to determine whether to ask
|
|
||||||
/// the user for the time, the date or both.
|
|
||||||
enum DateTimeFieldPickerMode { time, date, dateAndTime }
|
|
||||||
|
|
||||||
/// Returns the [CupertinoDatePickerMode] corresponding to the selected
|
|
||||||
/// [DateTimeFieldPickerMode]. This exists to prevent redundancy in the [DateTimeField]
|
|
||||||
/// widget parameters.
|
|
||||||
CupertinoDatePickerMode _cupertinoModeFromPickerMode(
|
|
||||||
DateTimeFieldPickerMode mode) {
|
|
||||||
switch (mode) {
|
|
||||||
case DateTimeFieldPickerMode.time:
|
|
||||||
return CupertinoDatePickerMode.time;
|
|
||||||
case DateTimeFieldPickerMode.date:
|
|
||||||
return CupertinoDatePickerMode.date;
|
|
||||||
default:
|
|
||||||
return CupertinoDatePickerMode.dateAndTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the corresponding default [DateFormat] for the selected [DateTimeFieldPickerMode]
|
|
||||||
DateFormat getDateFormatFromDateFieldPickerMode(DateTimeFieldPickerMode mode) {
|
|
||||||
switch (mode) {
|
|
||||||
case DateTimeFieldPickerMode.time:
|
|
||||||
return DateFormat.jm();
|
|
||||||
case DateTimeFieldPickerMode.date:
|
|
||||||
return DateFormat.yMMMMd();
|
|
||||||
default:
|
|
||||||
return DateFormat.yMd().add_jm();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// [_InputDropdown]
|
|
||||||
///
|
|
||||||
/// Shows a field with a dropdown arrow !
|
|
||||||
/// It does not show any popup menu, it'll just trigger onPressed whenever the
|
|
||||||
/// user does click on it !
|
|
||||||
class _InputDropdown extends StatelessWidget {
|
|
||||||
const _InputDropdown({
|
|
||||||
Key? key,
|
|
||||||
required this.text,
|
|
||||||
this.decoration,
|
|
||||||
this.textStyle,
|
|
||||||
this.onPressed,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
/// The text that should be displayed inside the field
|
|
||||||
final String text;
|
|
||||||
|
|
||||||
/// Custom [InputDecoration] for the [InputDecorator] widget
|
|
||||||
final InputDecoration? decoration;
|
|
||||||
|
|
||||||
/// TextStyle for the field
|
|
||||||
final TextStyle? textStyle;
|
|
||||||
|
|
||||||
/// Callbacks triggered whenever the user presses on the field!
|
|
||||||
final VoidCallback? onPressed;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final InputDecoration effectiveDecoration = decoration ??
|
|
||||||
const InputDecoration(
|
|
||||||
suffixIcon: Icon(Icons.arrow_drop_down),
|
|
||||||
).applyDefaults(Theme.of(context).inputDecorationTheme);
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: onPressed,
|
|
||||||
child: InputDecorator(
|
|
||||||
decoration: effectiveDecoration,
|
|
||||||
child: Text(text, style: textStyle),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
262
lib/src/field.dart
Normal file
262
lib/src/field.dart
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
final DateTime _kDefaultFirstSelectableDate = DateTime(1900);
|
||||||
|
final DateTime _kDefaultLastSelectableDate = DateTime(2100);
|
||||||
|
|
||||||
|
const double _kCupertinoDatePickerHeight = 216;
|
||||||
|
|
||||||
|
/// [DateTimeField]
|
||||||
|
///
|
||||||
|
/// Shows an [_InputDropdown] that'll trigger [DateTimeField._selectDate] whenever the user
|
||||||
|
/// clicks on it ! The date picker is **platform responsive** (ios date picker style for ios, ...)
|
||||||
|
class DateTimeField extends StatelessWidget {
|
||||||
|
DateTimeField({
|
||||||
|
Key? key,
|
||||||
|
required this.onDateSelected,
|
||||||
|
required this.selectedDate,
|
||||||
|
this.initialDatePickerMode = DatePickerMode.day,
|
||||||
|
this.decoration,
|
||||||
|
this.enabled = true,
|
||||||
|
this.mode = DateTimeFieldPickerMode.dateAndTime,
|
||||||
|
this.initialEntryMode = DatePickerEntryMode.calendar,
|
||||||
|
this.dateTextStyle,
|
||||||
|
DateTime? firstDate,
|
||||||
|
DateTime? lastDate,
|
||||||
|
DateFormat? dateFormat,
|
||||||
|
}) : dateFormat = dateFormat ?? getDateFormatFromDateFieldPickerMode(mode),
|
||||||
|
firstDate = firstDate ?? _kDefaultFirstSelectableDate,
|
||||||
|
lastDate = lastDate ?? _kDefaultLastSelectableDate,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
DateTimeField.time({
|
||||||
|
Key? key,
|
||||||
|
this.onDateSelected,
|
||||||
|
this.selectedDate,
|
||||||
|
this.decoration,
|
||||||
|
this.enabled,
|
||||||
|
this.dateTextStyle,
|
||||||
|
this.initialEntryMode = DatePickerEntryMode.calendar,
|
||||||
|
DateTime? firstDate,
|
||||||
|
DateTime? lastDate,
|
||||||
|
}) : initialDatePickerMode = null,
|
||||||
|
mode = DateTimeFieldPickerMode.time,
|
||||||
|
dateFormat = DateFormat.jm(),
|
||||||
|
firstDate = firstDate ?? DateTime(2000),
|
||||||
|
lastDate = lastDate ?? DateTime(2001),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
/// Callback for whenever the user selects a [DateTime]
|
||||||
|
final ValueChanged<DateTime>? onDateSelected;
|
||||||
|
|
||||||
|
/// The current selected date to display inside the field
|
||||||
|
final DateTime? selectedDate;
|
||||||
|
|
||||||
|
/// The first date that the user can select (default is 1900)
|
||||||
|
final DateTime firstDate;
|
||||||
|
|
||||||
|
/// The last date that the user can select (default is 2100)
|
||||||
|
final DateTime lastDate;
|
||||||
|
|
||||||
|
/// Let you choose the [DatePickerMode] for the date picker! (default is [DatePickerMode.day]
|
||||||
|
final DatePickerMode? initialDatePickerMode;
|
||||||
|
|
||||||
|
/// Custom [InputDecoration] for the [InputDecorator] widget
|
||||||
|
final InputDecoration? decoration;
|
||||||
|
|
||||||
|
/// How to display the [DateTime] for the user (default is [DateFormat.yMMMD])
|
||||||
|
final DateFormat dateFormat;
|
||||||
|
|
||||||
|
/// Whether the field is usable. If false the user won't be able to select any date
|
||||||
|
final bool? enabled;
|
||||||
|
|
||||||
|
/// Whether to ask the user to pick only the date, the time or both.
|
||||||
|
final DateTimeFieldPickerMode mode;
|
||||||
|
|
||||||
|
/// [TextStyle] of the selected date inside the field.
|
||||||
|
final TextStyle? dateTextStyle;
|
||||||
|
|
||||||
|
/// The initial entry mode for the material date picker dialog
|
||||||
|
final DatePickerEntryMode initialEntryMode;
|
||||||
|
|
||||||
|
/// Shows a dialog asking the user to pick a date !
|
||||||
|
Future<void> _selectDate(BuildContext context) async {
|
||||||
|
final DateTime initialDateTime = selectedDate ?? DateTime.now();
|
||||||
|
|
||||||
|
if (Theme.of(context).platform == TargetPlatform.iOS) {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext builder) {
|
||||||
|
return SizedBox(
|
||||||
|
height: _kCupertinoDatePickerHeight,
|
||||||
|
child: CupertinoDatePicker(
|
||||||
|
mode: _cupertinoModeFromPickerMode(mode),
|
||||||
|
onDateTimeChanged: onDateSelected!,
|
||||||
|
initialDateTime: initialDateTime,
|
||||||
|
minimumDate: firstDate,
|
||||||
|
maximumDate: lastDate,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
DateTime _selectedDateTime = initialDateTime;
|
||||||
|
|
||||||
|
const List<DateTimeFieldPickerMode> modesWithDate =
|
||||||
|
<DateTimeFieldPickerMode>[
|
||||||
|
DateTimeFieldPickerMode.dateAndTime,
|
||||||
|
DateTimeFieldPickerMode.date
|
||||||
|
];
|
||||||
|
|
||||||
|
if (modesWithDate.contains(mode)) {
|
||||||
|
final DateTime? _selectedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDatePickerMode: initialDatePickerMode!,
|
||||||
|
initialDate: initialDateTime,
|
||||||
|
initialEntryMode: initialEntryMode,
|
||||||
|
firstDate: firstDate,
|
||||||
|
lastDate: lastDate,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_selectedDate != null) {
|
||||||
|
_selectedDateTime = _selectedDate;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<DateTimeFieldPickerMode> modesWithTime =
|
||||||
|
<DateTimeFieldPickerMode>[
|
||||||
|
DateTimeFieldPickerMode.dateAndTime,
|
||||||
|
DateTimeFieldPickerMode.time
|
||||||
|
];
|
||||||
|
|
||||||
|
if (modesWithTime.contains(mode)) {
|
||||||
|
final TimeOfDay? _selectedTime = await showTimePicker(
|
||||||
|
initialTime: TimeOfDay.fromDateTime(initialDateTime),
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_selectedTime != null) {
|
||||||
|
_selectedDateTime = DateTime(
|
||||||
|
_selectedDateTime.year,
|
||||||
|
_selectedDateTime.month,
|
||||||
|
_selectedDateTime.day,
|
||||||
|
_selectedTime.hour,
|
||||||
|
_selectedTime.minute,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDateSelected!(_selectedDateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
String? text;
|
||||||
|
|
||||||
|
if (selectedDate != null) {
|
||||||
|
text = dateFormat.format(selectedDate!);
|
||||||
|
}
|
||||||
|
TextStyle? textStyle;
|
||||||
|
|
||||||
|
textStyle = dateTextStyle ?? dateTextStyle;
|
||||||
|
|
||||||
|
return _InputDropdown(
|
||||||
|
text: text,
|
||||||
|
textStyle: textStyle,
|
||||||
|
isEmpty: selectedDate == null,
|
||||||
|
decoration: decoration,
|
||||||
|
onPressed: enabled! ? () => _selectDate(context) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Those values are used by the [DateTimeField] widget to determine whether to ask
|
||||||
|
/// the user for the time, the date or both.
|
||||||
|
enum DateTimeFieldPickerMode { time, date, dateAndTime }
|
||||||
|
|
||||||
|
/// Returns the [CupertinoDatePickerMode] corresponding to the selected
|
||||||
|
/// [DateTimeFieldPickerMode]. This exists to prevent redundancy in the [DateTimeField]
|
||||||
|
/// widget parameters.
|
||||||
|
CupertinoDatePickerMode _cupertinoModeFromPickerMode(
|
||||||
|
DateTimeFieldPickerMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case DateTimeFieldPickerMode.time:
|
||||||
|
return CupertinoDatePickerMode.time;
|
||||||
|
case DateTimeFieldPickerMode.date:
|
||||||
|
return CupertinoDatePickerMode.date;
|
||||||
|
default:
|
||||||
|
return CupertinoDatePickerMode.dateAndTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the corresponding default [DateFormat] for the selected [DateTimeFieldPickerMode]
|
||||||
|
DateFormat getDateFormatFromDateFieldPickerMode(DateTimeFieldPickerMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case DateTimeFieldPickerMode.time:
|
||||||
|
return DateFormat.jm();
|
||||||
|
case DateTimeFieldPickerMode.date:
|
||||||
|
return DateFormat.yMMMMd();
|
||||||
|
default:
|
||||||
|
return DateFormat.yMd().add_jm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// [_InputDropdown]
|
||||||
|
///
|
||||||
|
/// Shows a field with a dropdown arrow !
|
||||||
|
/// It does not show any popup menu, it'll just trigger onPressed whenever the
|
||||||
|
/// user does click on it !
|
||||||
|
class _InputDropdown extends StatelessWidget {
|
||||||
|
const _InputDropdown({
|
||||||
|
Key? key,
|
||||||
|
required this.text,
|
||||||
|
this.decoration,
|
||||||
|
this.textStyle,
|
||||||
|
this.onPressed,
|
||||||
|
required this.isEmpty,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// The text that should be displayed inside the field
|
||||||
|
final String? text;
|
||||||
|
|
||||||
|
/// Custom [InputDecoration] for the [InputDecorator] widget
|
||||||
|
final InputDecoration? decoration;
|
||||||
|
|
||||||
|
/// TextStyle for the field
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
|
||||||
|
/// Callbacks triggered whenever the user presses on the field!
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
/// Whether the input field is empty.
|
||||||
|
///
|
||||||
|
/// Determines the position of the label text and whether to display the hint
|
||||||
|
/// text.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool isEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final InputDecoration effectiveDecoration = decoration ??
|
||||||
|
const InputDecoration(
|
||||||
|
suffixIcon: Icon(Icons.arrow_drop_down),
|
||||||
|
);
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onPressed,
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: effectiveDecoration.applyDefaults(
|
||||||
|
Theme.of(context).inputDecorationTheme,
|
||||||
|
),
|
||||||
|
isEmpty: isEmpty,
|
||||||
|
child: text == null ? null : Text(text!, style: textStyle),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
75
lib/src/form_field.dart
Normal file
75
lib/src/form_field.dart
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:date_field/src/field.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
/// A [FormField] that contains a [DateTimeField].
|
||||||
|
///
|
||||||
|
/// This is a convenience widget that wraps a [DateTimeField] widget in a
|
||||||
|
/// [FormField].
|
||||||
|
///
|
||||||
|
/// A [Form] ancestor is not required. The [Form] simply makes it easier to
|
||||||
|
/// 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 DateTimeFormField extends FormField<DateTime> {
|
||||||
|
DateTimeFormField({
|
||||||
|
Key? key,
|
||||||
|
FormFieldSetter<DateTime>? onSaved,
|
||||||
|
FormFieldValidator<DateTime>? validator,
|
||||||
|
DateTime? initialValue,
|
||||||
|
AutovalidateMode? autovalidateMode,
|
||||||
|
bool enabled = true,
|
||||||
|
TextStyle? dateTextStyle,
|
||||||
|
DateFormat? dateFormat,
|
||||||
|
DateTime? firstDate,
|
||||||
|
DateTime? lastDate,
|
||||||
|
ValueChanged<DateTime>? onDateSelected,
|
||||||
|
InputDecoration? decoration,
|
||||||
|
DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar,
|
||||||
|
DatePickerMode initialDatePickerMode = DatePickerMode.day,
|
||||||
|
DateTimeFieldPickerMode mode = DateTimeFieldPickerMode.dateAndTime,
|
||||||
|
}) : super(
|
||||||
|
key: key,
|
||||||
|
initialValue: initialValue,
|
||||||
|
onSaved: onSaved,
|
||||||
|
validator: validator,
|
||||||
|
autovalidateMode: autovalidateMode,
|
||||||
|
enabled: enabled,
|
||||||
|
builder: (FormFieldState<DateTime> field) {
|
||||||
|
// Theme defaults are applied inside the _InputDropdown widget
|
||||||
|
final InputDecoration _decorationWithThemeDefaults =
|
||||||
|
decoration ?? const InputDecoration();
|
||||||
|
|
||||||
|
final InputDecoration effectiveDecoration =
|
||||||
|
_decorationWithThemeDefaults.copyWith(
|
||||||
|
errorText: field.errorText);
|
||||||
|
|
||||||
|
void onChangedHandler(DateTime value) {
|
||||||
|
if (onDateSelected != null) {
|
||||||
|
onDateSelected(value);
|
||||||
|
}
|
||||||
|
field.didChange(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTimeField(
|
||||||
|
firstDate: firstDate,
|
||||||
|
lastDate: lastDate,
|
||||||
|
decoration: effectiveDecoration,
|
||||||
|
initialDatePickerMode: initialDatePickerMode,
|
||||||
|
dateFormat: dateFormat,
|
||||||
|
onDateSelected: onChangedHandler,
|
||||||
|
selectedDate: field.value,
|
||||||
|
enabled: enabled,
|
||||||
|
mode: mode,
|
||||||
|
initialEntryMode: initialEntryMode,
|
||||||
|
dateTextStyle: dateTextStyle,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DateFormFieldState createState() => _DateFormFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DateFormFieldState extends FormFieldState<DateTime> {}
|
|
@ -1,6 +1,6 @@
|
||||||
name: date_field
|
name: date_field
|
||||||
description: A widget in the form of a field that lets people choose a date, a time or both.
|
description: A widget in the form of a field that lets people choose a date, a time or both.
|
||||||
version: 2.0.1
|
version: 2.1.0
|
||||||
homepage: 'https://github.com/GaspardMerten/date_field'
|
homepage: 'https://github.com/GaspardMerten/date_field'
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|
Loading…
Reference in a new issue