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
|
||||
|
||||
* 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
|
||||
dependencies:
|
||||
...
|
||||
date_field: ^2.0.1
|
||||
date_field: ^2.1.0
|
||||
```
|
||||
|
||||
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:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
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/**"
|
||||
# Ignore protoc generated files
|
||||
- "dev/conductor/lib/proto/*"
|
||||
|
||||
linter:
|
||||
rules:
|
||||
|
@ -45,18 +16,22 @@ linter:
|
|||
# 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_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_require_non_null_named_parameters
|
||||
# - always_specify_types
|
||||
- always_specify_types
|
||||
# - always_use_package_imports # we do this commonly
|
||||
- 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_dynamic_calls
|
||||
- avoid_empty_else
|
||||
- avoid_equals_and_hash_code_on_mutable_classes
|
||||
- avoid_escaping_inner_quotes
|
||||
- avoid_field_initializers_in_const_classes
|
||||
- avoid_function_literals_in_foreach_calls
|
||||
# - avoid_implementing_value_types # not yet tested
|
||||
|
@ -64,7 +39,9 @@ linter:
|
|||
# - avoid_js_rounded_ints # only useful when targeting JS runtime
|
||||
- avoid_null_checks_in_equality_operators
|
||||
# - 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_redundant_argument_values # not yet tested
|
||||
- avoid_relative_lib_imports
|
||||
- avoid_renaming_method_parameters
|
||||
- avoid_return_types_on_setters
|
||||
|
@ -73,42 +50,56 @@ linter:
|
|||
- 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_shadowing_type_parameters
|
||||
- avoid_single_cascade_in_expression_statements
|
||||
- avoid_slow_async_io
|
||||
- avoid_type_to_string
|
||||
- avoid_types_as_parameter_names
|
||||
# - avoid_types_on_closure_parameters # conflicts with always_specify_types
|
||||
- avoid_unnecessary_containers
|
||||
- avoid_unused_constructor_parameters
|
||||
- avoid_void_async
|
||||
# - avoid_web_libraries_in_flutter # not yet tested
|
||||
- await_only_futures
|
||||
- camel_case_extensions
|
||||
- camel_case_types
|
||||
- cancel_subscriptions
|
||||
# - cascade_invocations # not yet tested
|
||||
- cast_nullable_to_non_nullable
|
||||
# - 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
|
||||
- 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
|
||||
- directives_ordering
|
||||
# - do_not_use_environment # we do this commonly
|
||||
- empty_catches
|
||||
- empty_constructor_bodies
|
||||
- empty_statements
|
||||
# - file_names # not yet tested
|
||||
- exhaustive_cases
|
||||
- file_names
|
||||
- flutter_style_todos
|
||||
- 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
|
||||
# - join_return_with_assignment # not required by flutter style
|
||||
- leading_newlines_in_multiline_strings
|
||||
- library_names
|
||||
- library_prefixes
|
||||
# - lines_longer_than_80_chars # not yet tested
|
||||
# - lines_longer_than_80_chars # not required by flutter style
|
||||
- list_remove_unrelated_type
|
||||
# - 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_default_cases # too many false positives
|
||||
- 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
|
||||
# - null_closures # not yet tested
|
||||
- null_check_on_nullable_type_parameter
|
||||
- null_closures
|
||||
# - 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
|
||||
|
@ -119,14 +110,14 @@ linter:
|
|||
# - parameter_assignments # we do this commonly
|
||||
- prefer_adjacent_string_concatenation
|
||||
- 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_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_constructors_over_static_methods # far too many false positives
|
||||
- prefer_contains
|
||||
# - prefer_double_quotes # opposite of prefer_single_quotes
|
||||
- prefer_equal_for_default_values
|
||||
|
@ -134,57 +125,72 @@ linter:
|
|||
- prefer_final_fields
|
||||
- prefer_final_in_for_each
|
||||
- prefer_final_locals
|
||||
# - prefer_for_elements_to_map_fromIterable # not yet tested
|
||||
- prefer_for_elements_to_map_fromIterable
|
||||
- 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_function_declarations_over_variables
|
||||
- prefer_generic_function_type_aliases
|
||||
- prefer_if_elements_to_conditional_expressions
|
||||
- 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_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
|
||||
- prefer_is_empty
|
||||
- prefer_is_not_empty
|
||||
- prefer_is_not_operator
|
||||
- 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_null_aware_operators
|
||||
# - prefer_relative_imports # incompatible with sub-package imports
|
||||
- prefer_single_quotes
|
||||
- prefer_spread_collections
|
||||
- prefer_typing_uninitialized_variables
|
||||
- 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
|
||||
- recursive_getters
|
||||
- sized_box_for_whitespace
|
||||
- slash_for_doc_comments
|
||||
# - sort_child_properties_last # not yet tested
|
||||
- sort_constructors_first
|
||||
- sort_pub_dependencies
|
||||
# - sort_pub_dependencies # prevents separating pinned transitive dependencies
|
||||
- sort_unnamed_constructors_first
|
||||
- test_types_in_equals
|
||||
- throw_in_finally
|
||||
- tighten_type_of_initializing_formals
|
||||
# - 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_await_in_return
|
||||
- unnecessary_brace_in_string_interps
|
||||
- unnecessary_const
|
||||
# - unnecessary_final # conflicts with prefer_final_locals
|
||||
- unnecessary_getters_setters
|
||||
# - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498
|
||||
- unnecessary_new
|
||||
- unnecessary_null_aware_assignments
|
||||
- unnecessary_null_checks
|
||||
- unnecessary_null_in_if_null_operators
|
||||
- unnecessary_nullable_for_final_variable_declarations
|
||||
- unnecessary_overrides
|
||||
- unnecessary_parenthesis
|
||||
# - unnecessary_raw_strings # not yet tested
|
||||
- unnecessary_statements
|
||||
- unnecessary_string_escapes
|
||||
- unnecessary_string_interpolations
|
||||
- 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_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_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
|
||||
- void_checks
|
||||
|
|
|
@ -1,33 +1,38 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:date_field/date_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
inputDecorationTheme:
|
||||
const InputDecorationTheme(border: OutlineInputBorder()),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: MyHomePage(),
|
||||
home: const MyHomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_MyHomePageState createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
DateTime selectedDate;
|
||||
DateTime? selectedDate;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -36,7 +41,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const FlutterLogo(size: 100),
|
||||
const SizedBox(height: 20),
|
||||
const Text('DateField package showcase'),
|
||||
|
@ -59,7 +64,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
),
|
||||
Form(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
DateTimeFormField(
|
||||
decoration: const InputDecoration(
|
||||
hintStyle: TextStyle(color: Colors.black45),
|
||||
|
@ -69,7 +74,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
labelText: 'My Super Date Time Field',
|
||||
),
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
validator: (e) =>
|
||||
validator: (DateTime? e) =>
|
||||
(e?.day ?? 0) == 1 ? 'Please not the first day' : null,
|
||||
onDateSelected: (DateTime value) {
|
||||
print(value);
|
||||
|
@ -86,8 +91,11 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
),
|
||||
mode: DateTimeFieldPickerMode.time,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
validator: (e) =>
|
||||
(e?.day ?? 0) == 1 ? 'Please not the first day' : null,
|
||||
validator: (DateTime? e) {
|
||||
return (e?.day ?? 0) == 1
|
||||
? 'Please not the first day'
|
||||
: null;
|
||||
},
|
||||
onDateSelected: (DateTime 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
|
||||
|
||||
environment:
|
||||
sdk: ">=2.7.0 <3.0.0"
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -21,7 +21,7 @@ dependencies:
|
|||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
cupertino_icons: ^1.0.3
|
||||
|
||||
|
||||
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';
|
||||
import 'package:flutter/material.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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
export 'package:date_field/src/field.dart';
|
||||
export 'package:date_field/src/form_field.dart';
|
||||
|
|
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
|
||||
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'
|
||||
|
||||
environment:
|
||||
|
|
Loading…
Reference in a new issue