Fixing label & hint style issues

Moving to a more generic architecture
Updating the analysis_options.yaml file
This commit is contained in:
Gaspard Merten 2021-09-22 20:06:57 +02:00
parent ddcb665d2f
commit 13eaa0df3a
10 changed files with 431 additions and 427 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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);
},

View file

@ -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:

View file

@ -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);
});
}

View file

@ -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
View 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
View 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> {}

View file

@ -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: