diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb431f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..1ef16b3 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 09126abb222d0f25b03318a1ab4a99d27d9aaa8d + channel: unknown + +project_type: package diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ac07159 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## [0.0.1] - TODO: Add release date. + +* TODO: Describe initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/lib/date_field.dart b/lib/date_field.dart new file mode 100644 index 0000000..aaf8b17 --- /dev/null +++ b/lib/date_field.dart @@ -0,0 +1,245 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class DateFormField extends StatelessWidget { + + /// An optional method to call with the final value when the form is saved via + /// [FormState.save]. + final FormFieldSetter onSaved; + + /// An optional method that validates an input. Returns an error string to + /// display if the input is invalid, or null otherwise. + final FormFieldValidator validator; + + + /// An optional value to initialize the form field to, or null otherwise. + final DateTime initialValue; + + /// If true, this form field will validate and update its error text + /// immediately after every change. Otherwise, you must call + /// [FormFieldState.validate] to validate. If part of a [Form] that + /// auto-validates, this value will be ignored. + final bool autovalidate; + + /// Whether the form is able to receive user input. + /// + /// Defaults to true. If [autovalidate] is true, the field will be validated. + /// Likewise, if this field is false, the widget will not be validated + /// regardless of [autovalidate]. + final bool enabled; + + /// (optional) The first date that the user can select (default is 1900) + final DateTime firstDate; + + /// (optional) The last date that the user can select (default is 2100) + final DateTime lastDate; + + /// (optional) The label to display for the field (default is 'Select date') + final String label; + + /// (optional) Custom [InputDecoration] for the [InputDecorator] widget + final InputDecoration decoration; + + /// (optional) How to display the [DateTime] for the user (default is [DateFormat.yMMMD]) + final DateFormat dateFormat; + + /// (optional) Let you choose the [DatePickerMode] for the date picker! (default is [DatePickerMode.day] + final DatePickerMode initialDatePickerMode; + + const DateFormField( + {Key key, this.onSaved, this.validator, this.initialValue, this.autovalidate=false, this.enabled=true, this.firstDate, this.lastDate, this.label='Select date', this.dateFormat, this.decoration, this.initialDatePickerMode}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return FormField( + onSaved: onSaved, + autovalidate: autovalidate, + validator: validator, + enabled: enabled, + initialValue: initialValue, + builder: (FormFieldState state) { + return DateField( + label: label, + firstDate: firstDate, + lastDate: lastDate, + decoration: decoration, + initialDatePickerMode: initialDatePickerMode, + dateFormat: dateFormat, + errorText: state.errorText, + onDateSelected: (DateTime value) { + state.didChange(value); + }, + selectedDate: state.value, + ); + } + ); + } +} + +/// +/// [DateField] +/// +/// Shows an [_InputDropdown] that'll trigger [DateField._selectDate] whenever the user +/// clicks on it ! +class DateField extends StatelessWidget { + /// Default constructor + DateField({ + @required this.onDateSelected, + @required this.selectedDate, + this.firstDate, + this.lastDate, + this.initialDatePickerMode = DatePickerMode.day, + this.decoration, + this.label = 'Select date', + this.errorText, + this.dateFormat, + }); + + /// Callback for whenever the user selects a [DateTime] + final ValueChanged onDateSelected; + + /// The current selected date to display inside the field + final DateTime selectedDate; + + /// (optional) The first date that the user can select (default is 1900) + final DateTime firstDate; + + /// (optional) 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; + + /// The label to display for the field (default is 'Select date') + final String label; + + /// (optional) The error text that should be displayed under the field + final String errorText; + + /// (optional) Custom [InputDecoration] for the [InputDecorator] widget + final InputDecoration decoration; + + /// (optional) How to display the [DateTime] for the user (default is [DateFormat.yMMMD]) + final DateFormat dateFormat; + + /// Shows a dialog asking the user to pick a date ! + Future _selectDate(BuildContext context) async { + if (!Platform.isIOS) { + showModalBottomSheet( + context: context, + builder: (BuildContext builder) { + return Container( + height: MediaQuery.of(context).size.height / 4, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + onDateTimeChanged:(DateTime dateTime) => onDateSelected(dateTime), + initialDateTime: selectedDate ?? lastDate ?? DateTime.now(), + minimumDate: firstDate, + maximumDate: lastDate, + ), + ); + }, + ); + } + else { + DateTime _selectedDate = await showDatePicker( + context: context, + initialDatePickerMode: initialDatePickerMode, + initialDate: selectedDate ?? lastDate ?? DateTime.now(), + firstDate: firstDate ?? DateTime(1900), + lastDate: lastDate ?? DateTime(2100)); + + + if (_selectedDate != null) { + onDateSelected(_selectedDate); + } + } + } + + + @override + Widget build(BuildContext context) { + String text; + + if (selectedDate != null) + text = (dateFormat ?? DateFormat.yMMMd()).format(selectedDate); + + return _InputDropdown( + text: text ?? label, + label: text == null ? null : label, + errorText: errorText, + decoration: decoration, + onPressed: () { + _selectDate(context); + }, + ); + } +} + +/// +/// [_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, this.label, this.text, this.decoration, this.textStyle, this.onPressed, this.errorText}) + : super(key: key); + + /// The label to display for the field (default is 'Select date') + final String label; + + /// The text that should be displayed inside the field + final String text; + + /// (optional) The error text that should be displayed under the field + final String errorText; + + /// (optional) 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) { + BorderRadius inkwellBorderRadius; + + if (decoration?.border?.runtimeType == OutlineInputBorder) { + inkwellBorderRadius = BorderRadius.circular(8); + } + + return Material( + color: Colors.transparent, + child: InkWell( + borderRadius: inkwellBorderRadius, + onTap: onPressed, + child: InputDecorator( + decoration: decoration ?? InputDecoration( + labelText: label, + errorText: errorText, + border: UnderlineInputBorder(borderSide: BorderSide()), + contentPadding: EdgeInsets.only(bottom: 2.0) + ), + baseStyle: textStyle, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Text(text, style: textStyle), + Icon(Icons.arrow_drop_down), + ], + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..884569d --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,195 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.6" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.8" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0+1" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.11" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" +sdks: + dart: ">=2.5.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..8fa9e43 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,53 @@ +name: date_field +description: Contains DateField and DateFormField +version: 0.0.1 + + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + intl: ^0.16.1 + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages