Skip to content

Commit

Permalink
change datetime value. close goxiaoy#92
Browse files Browse the repository at this point in the history
  • Loading branch information
goxiaoy committed Jun 9, 2023
1 parent 736b0e5 commit 698c983
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 12 deletions.
14 changes: 7 additions & 7 deletions lib/ui/elements/text.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_survey_js/ui/reactive/reactive.dart';
import 'package:flutter_survey_js/ui/reactive/reactive_color_picker.dart';
import 'package:flutter_survey_js/ui/reactive/reactive_date_time_picker.dart';
import 'package:flutter_survey_js/ui/survey_configuration.dart';
import 'package:flutter_survey_js_model/flutter_survey_js_model.dart' as s;
import 'package:reactive_date_time_picker/reactive_date_time_picker.dart';
import 'package:reactive_forms/reactive_forms.dart';

Widget textBuilder(BuildContext context, s.Elementbase element,
Expand Down Expand Up @@ -52,7 +52,7 @@ Widget textBuilder(BuildContext context, s.Elementbase element,
);
}
if (e.inputType == s.TextInputType.range) {
//TODO
//TODO
}
if (e.inputType == s.TextInputType.tel) {
widget = ReactiveTextField(
Expand All @@ -62,13 +62,13 @@ Widget textBuilder(BuildContext context, s.Elementbase element,
);
}
if (e.inputType == s.TextInputType.time) {
//TODO
//TODO
}
if (e.inputType == s.TextInputType.url) {
//TODO
//TODO
}
if (e.inputType == s.TextInputType.week) {
//TODO
//TODO
}
if (e.inputType == s.TextInputType.number) {
widget = ReactiveTextField(
Expand All @@ -87,9 +87,9 @@ AbstractControl textControlBuilder(BuildContext context, s.Elementbase element,
final e = element as s.Text;
if (e.inputType == s.TextInputType.date ||
e.inputType == s.TextInputType.datetimeLocal) {
return FormControl<DateTime>(
return FormControl<String>(
validators: validators,
value: e.defaultValue.tryCastToDateTime() ?? value.tryCastToDateTime());
value: e.defaultValue.tryCastToString() ?? value.tryCastToString());
}
if (e.inputType == s.TextInputType.color) {
return FormControl<String>(
Expand Down
291 changes: 291 additions & 0 deletions lib/ui/reactive/reactive_date_time_picker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import 'package:flutter/material.dart';
import 'package:flutter_survey_js/survey.dart' hide Text, TextInputType;

import 'package:intl/intl.dart' hide TextDirection;
import 'package:reactive_forms/reactive_forms.dart';

enum ReactiveDatePickerFieldType {
date,
time,
dateTime,
}

typedef GetInitialDate = DateTime Function(
DateTime? fieldValue, DateTime lastDate);

typedef GetInitialTime = TimeOfDay Function(DateTime? fieldValue);

/// A builder that builds a widget responsible to decide when to show
/// the picker dialog.
///
/// It has a property to access the [FormControl]
/// that is bound to [ReactiveDatePickerField].
/// This is a convenience widget that wraps the function
/// [showDatePicker] and [showTimePicker] in a [ReactiveDatePickerField].
///
/// The [formControlName] is required to bind this [ReactiveDatePickerField]
/// to a [FormControl].
///
/// For documentation about the various parameters, see the [showDatePicker]
/// and [showTimePicker] function parameters.
///
/// ## Example:
///
/// ```dart
/// ReactiveDatePickerField(
/// formControlName: 'birthday',
/// )
/// ```
class ReactiveDateTimePicker extends ReactiveFormField<String, String> {
/// Creates a [ReactiveDatePickerField] that wraps the function [showDatePicker].
///
/// Can optionally provide a [formControl] to bind this widget to a control.
///
/// Can optionally provide a [formControlName] to bind this ReactiveFormField
/// to a [FormControl].
///
/// Must provide one of the arguments [formControl] or a [formControlName],
/// but not both at the same time.
///
/// The parameter [transitionBuilder] is the equivalent of [builder]
/// parameter in the [showTimePicker].
///
/// For documentation about the various parameters, see the [showTimePicker]
/// function parameters.
ReactiveDateTimePicker({
Key? key,
String? formControlName,
FormControl<String>? formControl,
ControlValueAccessor<String, String>? valueAccessor,
Map<String, ValidationMessageFunction>? validationMessages,
ShowErrorsFunction? showErrors,

////////////////////////////////////////////////////////////////////////////
TextStyle? style,
ReactiveDatePickerFieldType type = ReactiveDatePickerFieldType.date,
InputDecoration? decoration,
bool showClearIcon = true,
Widget clearIcon = const Icon(Icons.clear),

// common params
TransitionBuilder? builder,
bool useRootNavigator = true,
String? cancelText,
String? confirmText,
String? helpText,
GetInitialDate? getInitialDate,
GetInitialTime? getInitialTime,
DateFormat? dateFormat,
double disabledOpacity = 0.5,

// date picker params
DateTime? firstDate,
DateTime? lastDate,
DatePickerEntryMode datePickerEntryMode = DatePickerEntryMode.calendar,
SelectableDayPredicate? selectableDayPredicate,
Locale? locale,
TextDirection? textDirection,
DatePickerMode initialDatePickerMode = DatePickerMode.day,
String? errorFormatText,
String? errorInvalidText,
String? fieldHintText,
String? fieldLabelText,
RouteSettings? datePickerRouteSettings,
TextInputType? keyboardType,
Offset? anchorPoint,

// time picker params
TimePickerEntryMode timePickerEntryMode = TimePickerEntryMode.dial,
RouteSettings? timePickerRouteSettings,
}) : super(
key: key,
formControl: formControl,
formControlName: formControlName,
validationMessages: validationMessages,
valueAccessor: valueAccessor,
showErrors: showErrors,
builder: (field) {
Widget? suffixIcon = decoration?.suffixIcon;
final isEmptyValue =
field.value == null || field.value?.isEmpty == true;

if (showClearIcon && !isEmptyValue) {
suffixIcon = InkWell(
borderRadius: BorderRadius.circular(25),
child: clearIcon,
onTap: () {
field.control.markAsTouched();
field.didChange(null);
},
);
}

final InputDecoration effectiveDecoration =
(decoration ?? const InputDecoration())
.applyDefaults(Theme.of(field.context).inputDecorationTheme)
.copyWith(suffixIcon: suffixIcon);
final effectiveValueAccessor =
_effectiveValueAccessor(type, dateFormat);
final effectiveLastDate = lastDate ?? DateTime(2100);

return IgnorePointer(
ignoring: !field.control.enabled,
child: Opacity(
opacity: field.control.enabled ? 1 : disabledOpacity,
child: GestureDetector(
onTap: () async {
DateTime? date;
TimeOfDay? time;
field.control.focus();
field.control.updateValueAndValidity();

final fieldDatetimeValue =
field.control.value.tryCastToDateTime();

if (type == ReactiveDatePickerFieldType.date ||
type == ReactiveDatePickerFieldType.dateTime) {
date = await showDatePicker(
context: field.context,
initialDate: (getInitialDate ?? _getInitialDate)(
fieldDatetimeValue,
effectiveLastDate,
),
firstDate: firstDate ?? DateTime(1900),
lastDate: effectiveLastDate,
initialEntryMode: datePickerEntryMode,
selectableDayPredicate: selectableDayPredicate,
helpText: helpText,
cancelText: cancelText,
confirmText: confirmText,
locale: locale,
useRootNavigator: useRootNavigator,
routeSettings: datePickerRouteSettings,
textDirection: textDirection,
builder: builder,
initialDatePickerMode: initialDatePickerMode,
errorFormatText: errorFormatText,
errorInvalidText: errorInvalidText,
fieldHintText: fieldHintText,
fieldLabelText: fieldLabelText,
keyboardType: keyboardType,
anchorPoint: anchorPoint,
);
}

if (type == ReactiveDatePickerFieldType.time ||
(type == ReactiveDatePickerFieldType.dateTime &&
// there is no need to show timepicker if cancel was pressed on datepicker
date != null)) {
time = await showTimePicker(
context: field.context,
initialTime: (getInitialTime ??
_getInitialTime)(fieldDatetimeValue),
builder: builder,
useRootNavigator: useRootNavigator,
initialEntryMode: timePickerEntryMode,
cancelText: cancelText,
confirmText: confirmText,
helpText: helpText,
routeSettings: timePickerRouteSettings,
);
}

if (
// if `date` and `time` in `dateTime` mode is not empty...
(type == ReactiveDatePickerFieldType.dateTime &&
(date != null && time != null)) ||
// ... or if `date` in `date` mode is not empty ...
(type == ReactiveDatePickerFieldType.date &&
date != null) ||
// ... or if `time` in `time` mode is not empty ...
(type == ReactiveDatePickerFieldType.time &&
time != null)) {
final dateTime = _combine(date, time);

final value = field.control.value.tryCastToDateTime();
// ... and new value is not the same as was before...
if (value == null || dateTime.compareTo(value) != 0) {
// ... this means that cancel was not pressed at any moment
// so we can update the field
field.didChange(
effectiveValueAccessor.modelToViewValue(
dateTime,
),
);
}
}
field.control.unfocus();
field.control.updateValueAndValidity();
field.control.markAsTouched();
},
child: InputDecorator(
decoration: effectiveDecoration.copyWith(
errorText: field.errorText,
enabled: field.control.enabled,
),
isFocused: field.control.hasFocus,
isEmpty: isEmptyValue,
child: Text(
field.value ?? '',
style: Theme.of(field.context)
.textTheme
.subtitle1
?.merge(style),
),
),
),
),
);
},
);

static DateTimeValueAccessor _effectiveValueAccessor(
ReactiveDatePickerFieldType fieldType, DateFormat? dateFormat) {
switch (fieldType) {
case ReactiveDatePickerFieldType.date:
return DateTimeValueAccessor(
dateTimeFormat: dateFormat ?? DateFormat('yyyy-MM-dd'),
);
case ReactiveDatePickerFieldType.time:
return DateTimeValueAccessor(
dateTimeFormat: dateFormat ?? DateFormat('HH:mm'),
);
case ReactiveDatePickerFieldType.dateTime:
return DateTimeValueAccessor(
dateTimeFormat: dateFormat ?? DateFormat('yyyy-MM-dd HH:mm'),
);
}
}

static DateTime _combine(DateTime? date, TimeOfDay? time) {
DateTime dateTime = DateTime(0);

if (date != null) {
dateTime = dateTime.add(date.difference(dateTime));
}

if (time != null) {
dateTime = dateTime.add(Duration(hours: time.hour, minutes: time.minute));
}

return dateTime;
}

static DateTime _getInitialDate(DateTime? fieldValue, DateTime lastDate) {
if (fieldValue != null) {
return fieldValue;
}

final now = DateTime.now();
return now.compareTo(lastDate) > 0 ? lastDate : now;
}

static TimeOfDay _getInitialTime(dynamic fieldValue) {
if (fieldValue != null && fieldValue is DateTime) {
return TimeOfDay(hour: fieldValue.hour, minute: fieldValue.minute);
}

return TimeOfDay.now();
}
}
1 change: 0 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ dependencies:
multiple_localization: ^0.4.0

reactive_forms: ^14.3.0
reactive_date_time_picker: ^0.6.1
reactive_date_range_picker: ^0.6.0
reactive_segmented_control: ^0.6.0
reactive_advanced_switch: ^0.6.1
Expand Down
8 changes: 4 additions & 4 deletions schema/flutter_survey_js_model/lib/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ extension ObjectExtension on Object? {
if (this is bool) {
return this as bool;
}
return this.toString().toBoolean();
return this.toString().trim().toBoolean();
}

String? tryCastToString() {
Expand All @@ -202,14 +202,14 @@ extension ObjectExtension on Object? {
if (this is int) {
return this as int;
}
return int.tryParse(this.toString());
return int.tryParse(this.toString().trim());
}

DateTime? tryCastToDateTime() {
if (this == null) {
return null;
}
return DateTime.tryParse(this.toString());
return DateTime.tryParse(this.toString().trim());
}

num? tryCastToNum() {
Expand All @@ -219,7 +219,7 @@ extension ObjectExtension on Object? {
if (this is num) {
return this as num;
}
return num.tryParse(this.toString());
return num.tryParse(this.toString().trim());
}
}

Expand Down

0 comments on commit 698c983

Please sign in to comment.