From 97f7ec2196e5c63d351dbc51bc7c79810ed5988b Mon Sep 17 00:00:00 2001 From: Vasiliy Ditsyak Date: Tue, 12 Mar 2024 21:38:34 +0100 Subject: [PATCH] reactive_dropdown_button2 1.0.0-beta.10 --- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 13 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../example/ios/Runner/Info.plist | 2 + .../reactive_dropdown_button2/CHANGELOG.md | 13 ++ .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- .../example/lib/main.dart | 215 +++++++++++++++++- .../example/pubspec.lock | 74 ++++-- .../example/pubspec.yaml | 1 + .../lib/src/reactive_dropdown_button2.dart | 125 +++++++--- .../reactive_dropdown_button2/pubspec.yaml | 4 +- .../reactive_input_decorator/CHANGELOG.md | 2 + .../src/reactive_input_decorator_stack.dart | 118 ++++++++++ .../reactive_input_decorator/pubspec.yaml | 2 +- .../lib/src/reactive_phone_form_field.dart | 7 +- .../src/validators/validation_message.dart | 2 - .../validators/validator/phone_validator.dart | 127 +++++++++++ .../validator/required_validator.dart | 15 +- .../validator/valid_fixed_line_validator.dart | 47 ++-- .../reactive_phone_form_field/pubspec.yaml | 2 +- 21 files changed, 673 insertions(+), 108 deletions(-) create mode 100644 packages/reactive_input_decorator/lib/src/reactive_input_decorator_stack.dart create mode 100644 packages/reactive_phone_form_field/lib/src/validators/validator/phone_validator.dart diff --git a/packages/reactive_color_picker/example/ios/Flutter/AppFrameworkInfo.plist b/packages/reactive_color_picker/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f9..9625e105 100644 --- a/packages/reactive_color_picker/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/reactive_color_picker/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/packages/reactive_color_picker/example/ios/Runner.xcodeproj/project.pbxproj b/packages/reactive_color_picker/example/ios/Runner.xcodeproj/project.pbxproj index deb66dee..6b8f839a 100644 --- a/packages/reactive_color_picker/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/reactive_color_picker/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -127,7 +127,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -171,10 +171,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -185,6 +187,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -272,7 +275,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -346,7 +349,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -395,7 +398,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/packages/reactive_color_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/reactive_color_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6e..b52b2e69 100644 --- a/packages/reactive_color_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/reactive_color_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ CADisableMinimumFrameDurationOnPhone + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/reactive_dropdown_button2/CHANGELOG.md b/packages/reactive_dropdown_button2/CHANGELOG.md index ae839771..5abc8c84 100644 --- a/packages/reactive_dropdown_button2/CHANGELOG.md +++ b/packages/reactive_dropdown_button2/CHANGELOG.md @@ -1,3 +1,16 @@ +## 1.0.0-beta.10 + +* upgrade control value accessor to have ability to map against `items` + +## 0.0.4-beta.10 + +* more styling options + +## 0.0.3-beta.10 + +* wrapper for v3 beta.10 +* fix for disabled property + ## 0.0.3-beta.6 * wrapper for v3 beta.6 diff --git a/packages/reactive_dropdown_button2/example/ios/Flutter/AppFrameworkInfo.plist b/packages/reactive_dropdown_button2/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105..7c569640 100644 --- a/packages/reactive_dropdown_button2/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/reactive_dropdown_button2/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/packages/reactive_dropdown_button2/example/ios/Runner.xcodeproj/project.pbxproj b/packages/reactive_dropdown_button2/example/ios/Runner.xcodeproj/project.pbxproj index 88db9c3c..835c84d9 100644 --- a/packages/reactive_dropdown_button2/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/reactive_dropdown_button2/example/ios/Runner.xcodeproj/project.pbxproj @@ -345,7 +345,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -473,7 +473,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -522,7 +522,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/packages/reactive_dropdown_button2/example/lib/main.dart b/packages/reactive_dropdown_button2/example/lib/main.dart index 3c624cfc..2a012eb1 100644 --- a/packages/reactive_dropdown_button2/example/lib/main.dart +++ b/packages/reactive_dropdown_button2/example/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; import 'package:reactive_dropdown_button2/reactive_dropdown_button2.dart'; import 'package:reactive_forms/reactive_forms.dart'; @@ -17,17 +18,53 @@ final List items = [ 'Item8', ]; +final List<({int id, String value})> itemsRecord = [ + (id: 1, value: 'Item1'), + (id: 2, value: 'Item2'), + (id: 3, value: 'Item3'), + (id: 4, value: 'Item4'), + (id: 5, value: 'Item5'), + (id: 6, value: 'Item6'), + (id: 7, value: 'Item7'), + (id: 8, value: 'Item8'), +]; + +class Drop extends DropDownValueAccessor { + @override + ({int id, String value})? modelToViewValue(List> items, int? modelValue) { + return items.firstWhereOrNull((e) => e.value?.id == modelValue)?.value; + } + + @override + int? viewToModelValue(List> items, ({int id, String value})? modelValue) { + return modelValue?.id; + } + +} + +const icon = Icon( + Icons.access_alarms, + size: 24, +); +const hint = Text('Hint text'); + class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); FormGroup buildForm() => fb.group({ - 'input': FormControl( - value: null, - validators: [ - const RequiredValidator(), - ], - ), - }); + 'input': FormControl( + value: null, + validators: [ + const RequiredValidator(), + ], + ), + 'input2': FormControl( + value: null, + validators: [ + const RequiredValidator(), + ], + ), + }); @override Widget build(BuildContext context) { @@ -50,7 +87,6 @@ class MyApp extends StatelessWidget { builder: (context, form, child) { return Column( children: [ - const Text('Test'), const SizedBox(height: 16), DropdownButtonHideUnderline( child: ReactiveDropdownButton2( @@ -77,7 +113,6 @@ class MyApp extends StatelessWidget { ), elevation: 2, ), - iconStyleData: const IconStyleData( icon: Icon( Icons.arrow_forward_ios_outlined, @@ -105,9 +140,171 @@ class MyApp extends StatelessWidget { ), ), ), + const SizedBox(height: 16), + ReactiveDropdownButton2( + formControlName: 'input2', + valueAccessor: Drop(), + isExpanded: true, + inputDecoration: const InputDecoration( + border: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + contentPadding: EdgeInsets.zero, + helperText: "", + helperStyle: TextStyle(height: 0.8), + errorStyle: TextStyle(height: 0.8), + ), + items: itemsRecord + .map( + (({int id, String value}) item) => DropdownItem<({int id, String value})>( + value: item, + child: Row( + children: [ + const SizedBox.square(dimension: 40,), + Text(item.value), + ], + ), + ), + ) + .toList(), + hint: Row( + children: [ + const SizedBox.square( + dimension: 40, + child: icon, + ), + const SizedBox(width: 12), + Expanded( + child: DefaultTextStyle.merge( + style: const TextStyle( + fontSize: 14.0, + height: 1.8, + ), + overflow: TextOverflow.ellipsis, + child: hint, + ), + ), + ], + ), + underline: const SizedBox.shrink(), + errorButtonStyleData: ButtonStyleData( + height: 48, + padding: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all( + color: Theme.of(context).colorScheme.error), + borderRadius: const BorderRadius.all( + Radius.circular(12), + ), + ), + ), + buttonStyleData: ButtonStyleData( + height: 48, + padding: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: const Color(0xFFBCC2CE)), + borderRadius: const BorderRadius.all( + Radius.circular(12), + ), + ), + ), + iconStyleData: const IconStyleData( + icon: Icon(Icons.arrow_drop_down), + iconEnabledColor: Colors.black, + iconDisabledColor: Colors.grey, + openMenuIcon: Icon(Icons.arrow_drop_up), + ), + errorBuilder: (_, error) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + error, + style: const TextStyle(height: 0.8), + ), + ), + disabledHint: Row( + children: [ + SizedBox.square( + dimension: 24, + child: Builder( + builder: (context) { + final color = + DefaultTextStyle.of(context).style.color; + + + return IconTheme( + data: IconTheme.of(context).copyWith( + color: color, + size: 16, + ), + child: icon, + ); + }, + ), + ), + const SizedBox(width: 12), + const Text(''), + ], + ), + selectedItemBuilder: (_) { + return items.map( + (value) { + return DropdownMenuItem( + alignment: Alignment.centerLeft, + value: value, + child: Row( + children: [ + SizedBox.square( + dimension: 40, + child: Center( + child: Builder( + builder: (context) { + final color = + DefaultTextStyle.of(context) + .style + .color; + + return IconTheme( + data: + IconTheme.of(context).copyWith( + color: color, + size: 24, + ), + child: icon, + ); + }, + ), + ), + ), + Expanded( + child: Text( + value, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }, + ).toList(); + }, + dropdownStyleData: DropdownStyleData( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + padding: EdgeInsets.zero, + maxHeight: 300, + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 0), + ), + ), + const SizedBox(height: 16), ElevatedButton( child: const Text('Submit'), onPressed: () { + form.markAllAsTouched(); if (form.valid) { debugPrint(form.value.toString()); } diff --git a/packages/reactive_dropdown_button2/example/pubspec.lock b/packages/reactive_dropdown_button2/example/pubspec.lock index 2c56c7c1..c3e1b204 100644 --- a/packages/reactive_dropdown_button2/example/pubspec.lock +++ b/packages/reactive_dropdown_button2/example/pubspec.lock @@ -34,13 +34,13 @@ packages: source: hosted version: "1.1.1" collection: - dependency: transitive + dependency: "direct main" description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" cupertino_icons: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: dropdown_button2 - sha256: b53a583649d1ceaeec74a759b5797db0a17e8790f1ac451086b5e40733941912 + sha256: "4a2dea6d2e1c9288b47903bb5c5127c723f1088fd389f525ada01f1e3f5ac8e7" url: "https://pub.dev" source: hosted - version: "3.0.0-beta.5" + version: "3.0.0-beta.10" fake_async: dependency: transitive description: @@ -91,6 +91,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: @@ -103,41 +127,41 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.11.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" reactive_dropdown_button2: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.0.3-beta" + version: "1.0.0-beta.10" reactive_forms: dependency: "direct main" description: @@ -163,18 +187,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -195,10 +219,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" vector_math: dependency: transitive description: @@ -207,14 +231,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - web: + vm_service: dependency: transitive description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "13.0.0" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.2.0-0 <4.0.0" flutter: ">=3.10.0" diff --git a/packages/reactive_dropdown_button2/example/pubspec.yaml b/packages/reactive_dropdown_button2/example/pubspec.yaml index fd4f0914..ccfc6930 100644 --- a/packages/reactive_dropdown_button2/example/pubspec.yaml +++ b/packages/reactive_dropdown_button2/example/pubspec.yaml @@ -29,6 +29,7 @@ environment: dependencies: flutter: sdk: flutter + collection: ^1.18.0 reactive_dropdown_button2: path: ../ reactive_forms: ^16.0.0 diff --git a/packages/reactive_dropdown_button2/lib/src/reactive_dropdown_button2.dart b/packages/reactive_dropdown_button2/lib/src/reactive_dropdown_button2.dart index 0fd3f386..efeac7b7 100644 --- a/packages/reactive_dropdown_button2/lib/src/reactive_dropdown_button2.dart +++ b/packages/reactive_dropdown_button2/lib/src/reactive_dropdown_button2.dart @@ -2,6 +2,36 @@ import 'package:flutter/material.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:reactive_forms/reactive_forms.dart'; +abstract class DropDownValueAccessor { + // + DropDownValueAccessor(); + + V? modelToViewValue(List> items, T? modelValue); + + T? viewToModelValue(List> items, V? modelValue); +} + +class _DropDownValueAccessor extends ControlValueAccessor { + final List> items; + + final DropDownValueAccessor dropDownValueAccessor; + + _DropDownValueAccessor({ + this.items = const [], + required this.dropDownValueAccessor, + }); + + @override + V? modelToViewValue(T? modelValue) { + return dropDownValueAccessor.modelToViewValue(items, modelValue); + } + + @override + T? viewToModelValue(V? viewValue) { + return dropDownValueAccessor.viewToModelValue(items, viewValue); + } +} + /// A [ReactiveDropdownButton2] that contains a [DropdownButton2]. /// /// This is a convenience widget that wraps a [DropdownButton2] widget in a @@ -79,7 +109,7 @@ class ReactiveDropdownButton2 extends ReactiveFocusableFormField { String? formControlName, FormControl? formControl, Map? validationMessages, - ControlValueAccessor? valueAccessor, + DropDownValueAccessor? valueAccessor, ShowErrorsFunction? showErrors, FocusNode? focusNode, @@ -103,10 +133,13 @@ class ReactiveDropdownButton2 extends ReactiveFocusableFormField { bool autofocus = false, bool? enableFeedback, AlignmentGeometry alignment = AlignmentDirectional.centerStart, + ButtonStyleData? errorButtonStyleData, ButtonStyleData? buttonStyleData, IconStyleData iconStyleData = const IconStyleData(), + IconStyleData errorIconStyleData = const IconStyleData(), DropdownStyleData dropdownStyleData = const DropdownStyleData(), MenuItemStyleData menuItemStyleData = const MenuItemStyleData(), + Widget Function(BuildContext context, String error)? errorBuilder, DropdownSearchData? dropdownSearchData, Widget? customButton, bool openWithLongPress = false, @@ -114,11 +147,18 @@ class ReactiveDropdownButton2 extends ReactiveFocusableFormField { Color? barrierColor, String? barrierLabel, DropdownSeparator? dropdownSeparator, + bool barrierCoversButton = true, + EdgeInsets padding = EdgeInsets.zero, }) : super( key: key, formControl: formControl, formControlName: formControlName, - valueAccessor: valueAccessor, + valueAccessor: valueAccessor != null + ? _DropDownValueAccessor( + items: items ?? [], + dropDownValueAccessor: valueAccessor, + ) + : null, validationMessages: validationMessages, showErrors: showErrors, focusNode: focusNode, @@ -126,39 +166,62 @@ class ReactiveDropdownButton2 extends ReactiveFocusableFormField { final effectiveDecoration = inputDecoration .applyDefaults(Theme.of(field.context).inputDecorationTheme); + final errorText = field.errorText; + return InputDecorator( decoration: effectiveDecoration.copyWith( - errorText: field.errorText, + errorText: errorBuilder == null ? field.errorText : null, enabled: field.control.enabled, + error: errorBuilder != null && errorText != null + ? DefaultTextStyle( + style: Theme.of(field.context) + .textTheme + .bodySmall + ?.copyWith( + color: + Theme.of(field.context).colorScheme.error, + ) ?? + const TextStyle(), + child: errorBuilder.call(field.context, errorText), + ) + : null, ), - child: DropdownButton2( - key: widgetKey, - items: items ?? [], - selectedItemBuilder: selectedItemBuilder, - value: field.value, - hint: hint, - disabledHint: disabledHint, - onChanged: field.didChange, - onMenuStateChange: onMenuStateChange, - style: style, - underline: underline, - isDense: isDense, - isExpanded: isExpanded, - focusNode: field.focusNode, - autofocus: autofocus, - enableFeedback: enableFeedback, - alignment: alignment, - buttonStyleData: buttonStyleData, - iconStyleData: iconStyleData, - dropdownStyleData: dropdownStyleData, - menuItemStyleData: menuItemStyleData, - dropdownSearchData: dropdownSearchData, - customButton: customButton, - openWithLongPress: openWithLongPress, - barrierDismissible: barrierDismissible, - barrierColor: barrierColor, - barrierLabel: barrierLabel, - dropdownSeparator: dropdownSeparator, + child: Padding( + padding: padding, + child: DropdownButton2( + key: widgetKey, + items: items ?? [], + selectedItemBuilder: selectedItemBuilder, + value: field.value, + hint: hint, + disabledHint: disabledHint, + onChanged: field.control.disabled ? null : field.didChange, + onMenuStateChange: onMenuStateChange, + style: style, + underline: underline, + isDense: isDense, + isExpanded: isExpanded, + focusNode: field.focusNode, + autofocus: autofocus, + enableFeedback: enableFeedback, + alignment: alignment, + buttonStyleData: field.errorText != null + ? errorButtonStyleData ?? buttonStyleData + : buttonStyleData, + iconStyleData: field.errorText != null + ? errorIconStyleData + : iconStyleData, + dropdownStyleData: dropdownStyleData, + menuItemStyleData: menuItemStyleData, + dropdownSearchData: dropdownSearchData, + customButton: customButton, + openWithLongPress: openWithLongPress, + barrierDismissible: barrierDismissible, + barrierColor: barrierColor, + barrierLabel: barrierLabel, + dropdownSeparator: dropdownSeparator, + barrierCoversButton: barrierCoversButton, + ), ), ); }, diff --git a/packages/reactive_dropdown_button2/pubspec.yaml b/packages/reactive_dropdown_button2/pubspec.yaml index f17add8e..ae1c4471 100644 --- a/packages/reactive_dropdown_button2/pubspec.yaml +++ b/packages/reactive_dropdown_button2/pubspec.yaml @@ -1,6 +1,6 @@ name: reactive_dropdown_button2 description: Wrapper around dropdown_button2 to use with reactive_forms. -version: 0.0.3-beta.6 +version: 1.0.0-beta.10 repository: https://github.com/artflutter/reactive_forms_widgets/tree/master/packages/reactive_dropdown_button2 issue_tracker: https://github.com/artflutter/reactive_forms_widgets/issues @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - dropdown_button2: ^3.0.0-beta.6 + dropdown_button2: 3.0.0-beta.10 reactive_forms: ^16.0.0 dev_dependencies: diff --git a/packages/reactive_input_decorator/CHANGELOG.md b/packages/reactive_input_decorator/CHANGELOG.md index bbcc1b94..f54f2ea9 100644 --- a/packages/reactive_input_decorator/CHANGELOG.md +++ b/packages/reactive_input_decorator/CHANGELOG.md @@ -1,2 +1,4 @@ +## 0.0.2 +* ReactiveInputDecoratorStack ## 0.0.1 * initial release diff --git a/packages/reactive_input_decorator/lib/src/reactive_input_decorator_stack.dart b/packages/reactive_input_decorator/lib/src/reactive_input_decorator_stack.dart new file mode 100644 index 00000000..7411a932 --- /dev/null +++ b/packages/reactive_input_decorator/lib/src/reactive_input_decorator_stack.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:reactive_forms/reactive_forms.dart'; + +/// A [ReactiveInputDecorator] that contains a [InputDecorator]. +/// +/// This is a convenience widget that wraps a [InputDecorator] widget in a +/// [ReactiveInputDecorator]. +/// +/// A [ReactiveForm] ancestor is required. +/// +class ReactiveInputDecoratorStack extends ReactiveFormField { + /// Creates a [ReactiveInputDecoratorStack] that contains a [InputDecorator]. + /// + /// 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. + /// + /// Can optionally provide a [validationMessages] argument to customize a + /// message for different kinds of validation errors. + /// + /// Can optionally provide a [valueAccessor] to set a custom value accessors. + /// See [ControlValueAccessor]. + /// + /// Can optionally provide a [showErrors] function to customize when to show + /// validation messages. Reactive Widgets make validation messages visible + /// when the control is INVALID and TOUCHED, this behavior can be customized + /// in the [showErrors] function. + /// + /// ### Example: + /// Binds a text field. + /// ``` + /// final form = fb.group({'email': Validators.required}); + /// + /// ReactiveInputDecoratorStack( + /// formControlName: 'email', + /// ), + /// + /// ``` + /// + /// Binds a text field directly with a *FormControl*. + /// ``` + /// final form = fb.group({'email': Validators.required}); + /// + /// ReactiveInputDecoratorStack( + /// formControl: form.control('email'), + /// ), + /// + /// ``` + /// + /// Customize validation messages + /// ```dart + /// ReactiveInputDecoratorStack( + /// formControlName: 'email', + /// validationMessages: { + /// ValidationMessage.required: 'The email must not be empty', + /// ValidationMessage.email: 'The email must be a valid email', + /// } + /// ), + /// ``` + /// + /// Customize when to show up validation messages. + /// ```dart + /// ReactiveInputDecoratorStack( + /// formControlName: 'email', + /// showErrors: (control) => control.invalid && control.touched && control.dirty, + /// ), + /// ``` + /// + /// For documentation about the various parameters, see the [InputDecorator] class + /// and [InputDecorator], the constructor. + ReactiveInputDecoratorStack({ + Key? key, + String? formControlName, + FormControl? formControl, + Map? validationMessages, + ControlValueAccessor? valueAccessor, + ShowErrorsFunction? showErrors, + + ////////////////////////////////////////////////////////////////////////// + required List children, + required Widget? child, + InputDecoration? decoration, + }) : super( + key: key, + formControl: formControl, + formControlName: formControlName, + valueAccessor: valueAccessor, + validationMessages: validationMessages, + showErrors: showErrors, + builder: (field) { + final effectiveDecoration = (decoration ?? const InputDecoration()) + .applyDefaults(Theme.of(field.context).inputDecorationTheme); + + return IgnorePointer( + ignoring: !field.control.enabled, + child: Listener( + onPointerDown: (_) => field.control.markAsTouched(), + child: Stack( + children: [ + InputDecorator( + decoration: effectiveDecoration.copyWith( + errorText: field.errorText, + enabled: field.control.enabled, + ), + child: child, + ), + ...children + ], + ), + ), + ); + }, + ); +} diff --git a/packages/reactive_input_decorator/pubspec.yaml b/packages/reactive_input_decorator/pubspec.yaml index ff991dbc..3de4610b 100644 --- a/packages/reactive_input_decorator/pubspec.yaml +++ b/packages/reactive_input_decorator/pubspec.yaml @@ -1,6 +1,6 @@ name: reactive_input_decorator description: Wrapper around input_decorator to use with reactive_forms. -version: 0.0.1 +version: 0.0.2 repository: https://github.com/artflutter/reactive_forms_widgets/tree/master/packages/reactive_input_decorator issue_tracker: https://github.com/artflutter/reactive_forms_widgets/issues diff --git a/packages/reactive_phone_form_field/lib/src/reactive_phone_form_field.dart b/packages/reactive_phone_form_field/lib/src/reactive_phone_form_field.dart index d81a473b..1702a0c2 100644 --- a/packages/reactive_phone_form_field/lib/src/reactive_phone_form_field.dart +++ b/packages/reactive_phone_form_field/lib/src/reactive_phone_form_field.dart @@ -100,7 +100,6 @@ class ReactivePhoneFormField extends ReactiveFormField { const CountrySelectorNavigator.searchDelegate(), Function(PhoneNumber?)? onSaved, IsoCode defaultCountry = IsoCode.US, - // AutovalidateMode autovalidateMode = AutovalidateMode.onUserInteraction, PhoneNumber? initialValue, double flagSize = 16, InputDecoration decoration = const InputDecoration(), @@ -151,7 +150,6 @@ class ReactivePhoneFormField extends ReactiveFormField { bool enableIMEPersonalizedLearning = true, bool isCountrySelectionEnabled = true, bool isCountryChipPersistent = false, - ReactiveFormFieldCallback? onChanged, }) : super( key: key, formControl: formControl, @@ -171,10 +169,7 @@ class ReactivePhoneFormField extends ReactiveFormField { focusNode: state.focusNode, controller: state._textController, shouldFormat: shouldFormat, - onChanged: (value) { - field.didChange(value); - onChanged?.call(field.control); - }, + onChanged: field.didChange, autofillHints: autofillHints, autofocus: autofocus, enabled: field.control.enabled, diff --git a/packages/reactive_phone_form_field/lib/src/validators/validation_message.dart b/packages/reactive_phone_form_field/lib/src/validators/validation_message.dart index 34f1bfdb..4004d639 100644 --- a/packages/reactive_phone_form_field/lib/src/validators/validation_message.dart +++ b/packages/reactive_phone_form_field/lib/src/validators/validation_message.dart @@ -1,6 +1,4 @@ class PhoneValidationMessage { - static const String required = 'phone.required'; static const String valid = 'phone.valid'; static const String validMobile = 'phone.validMobile'; - static const String validFixedLine = 'phone.validFixedLine'; } diff --git a/packages/reactive_phone_form_field/lib/src/validators/validator/phone_validator.dart b/packages/reactive_phone_form_field/lib/src/validators/validator/phone_validator.dart new file mode 100644 index 00000000..84ea40f8 --- /dev/null +++ b/packages/reactive_phone_form_field/lib/src/validators/validator/phone_validator.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:phone_form_field/phone_form_field.dart'; + +typedef PhoneNumberInputValidator = String? Function(PhoneNumber? phoneNumber); + +abstract class PhoneValidator { + /// allow to compose several validators + /// Note that validator list order is important as first + /// validator failing will return according message. + static PhoneNumberInputValidator compose( + List validators, + ) { + return (valueCandidate) { + for (var validator in validators) { + final validatorResult = validator.call(valueCandidate); + if (validatorResult != null) { + return validatorResult; + } + } + return null; + }; + } + + static PhoneNumberInputValidator required( + { + /// custom error message + String? errorText, + }) { + return (PhoneNumber? valueCandidate) { + if (valueCandidate == null || (valueCandidate.nsn.trim().isEmpty)) { + return errorText; + } + return null; + }; + } + + static PhoneNumberInputValidator valid( + BuildContext context, { + /// custom error message + String? errorText, + }) { + return (PhoneNumber? valueCandidate) { + if (valueCandidate != null && + valueCandidate.nsn.isNotEmpty && + !valueCandidate.isValid()) { + return errorText ?? + PhoneFieldLocalization.of(context).invalidPhoneNumber; + } + return null; + }; + } + + static PhoneNumberInputValidator validType( + BuildContext context, + + /// expected phonetype + PhoneNumberType expectedType, { + /// custom error message + String? errorText, + }) { + return (PhoneNumber? valueCandidate) { + if (valueCandidate != null && + valueCandidate.nsn.isNotEmpty && + !valueCandidate.isValid(type: expectedType)) { + if (expectedType == PhoneNumberType.mobile) { + return errorText ?? + PhoneFieldLocalization.of(context).invalidMobilePhoneNumber; + } else if (expectedType == PhoneNumberType.fixedLine) { + return errorText ?? + PhoneFieldLocalization.of(context).invalidFixedLinePhoneNumber; + } + return errorText ?? + PhoneFieldLocalization.of(context).invalidPhoneNumber; + } + return null; + }; + } + + /// convenience shortcut method for + /// invalidType(context, PhoneNumberType.fixedLine, ...) + static PhoneNumberInputValidator validFixedLine( + BuildContext context, { + /// custom error message + String? errorText, + }) => + validType( + context, + PhoneNumberType.fixedLine, + errorText: errorText, + ); + + /// convenience shortcut method for + /// invalidType(context, PhoneNumberType.mobile, ...) + static PhoneNumberInputValidator validMobile( + BuildContext context, { + /// custom error message + String? errorText, + }) => + validType( + context, + PhoneNumberType.mobile, + errorText: errorText, + ); + + static PhoneNumberInputValidator validCountry( + BuildContext context, + + /// list of valid country isocode + List expectedCountries, { + /// custom error message + String? errorText, + }) { + return (PhoneNumber? valueCandidate) { + if (valueCandidate != null && + (valueCandidate.nsn.isNotEmpty) && + !expectedCountries.contains(valueCandidate.isoCode)) { + return errorText ?? PhoneFieldLocalization.of(context).invalidCountry; + } + return null; + }; + } + + @Deprecated('Use null instead') + static PhoneNumberInputValidator get none => (PhoneNumber? valueCandidate) { + return null; + }; +} diff --git a/packages/reactive_phone_form_field/lib/src/validators/validator/required_validator.dart b/packages/reactive_phone_form_field/lib/src/validators/validator/required_validator.dart index 506adad6..7cb62c2e 100644 --- a/packages/reactive_phone_form_field/lib/src/validators/validator/required_validator.dart +++ b/packages/reactive_phone_form_field/lib/src/validators/validator/required_validator.dart @@ -5,20 +5,21 @@ import 'package:reactive_phone_form_field/src/validators/validation_message.dart class RequiredPhoneValidator extends Validator { const RequiredPhoneValidator(); + static const message = 'phone.required'; + @override Map? validate(AbstractControl control) { - final error = {PhoneValidationMessage.required: true}; + final error = {message: true}; + final value = control.value; - if (control.value == null) { + if(value is! PhoneNumber?) { return error; } - PhoneNumber? valueCandidate = control.value as PhoneNumber; - - if (PhoneValidator.required().call(valueCandidate) == null) { - return null; - } else { + if (value == null || (value.nsn.trim().isEmpty)) { return error; } + + return null; } } diff --git a/packages/reactive_phone_form_field/lib/src/validators/validator/valid_fixed_line_validator.dart b/packages/reactive_phone_form_field/lib/src/validators/validator/valid_fixed_line_validator.dart index e7baded9..c1e36e57 100644 --- a/packages/reactive_phone_form_field/lib/src/validators/validator/valid_fixed_line_validator.dart +++ b/packages/reactive_phone_form_field/lib/src/validators/validator/valid_fixed_line_validator.dart @@ -5,23 +5,44 @@ import 'package:reactive_phone_form_field/src/validators/validation_message.dart class ValidFixedLinePhoneValidator extends Validator { const ValidFixedLinePhoneValidator(); + static const message = 'phone.validFixedLine'; + @override Map? validate(AbstractControl control) { - final error = { - PhoneValidationMessage.validFixedLine: true - }; - - if (control.value == null) { - return null; - } else if (control.value is PhoneNumber) { - PhoneNumber? valueCandidate = control.value as PhoneNumber; - - if (PhoneValidator.validFixedLine().call(valueCandidate) == null) { - return null; - } else { - return error; + final error = { message: true }; + + final value = control.value; + + if(value is! PhoneNumber?) { + return error; + } + + if ( + value.nsn.isNotEmpty && + !valueCandidate.isValid(type: expectedType)) { + if (expectedType == PhoneNumberType.mobile) { + return errorText ?? + PhoneFieldLocalization.of(context).invalidMobilePhoneNumber; + } else if (expectedType == PhoneNumberType.fixedLine) { + return errorText ?? + PhoneFieldLocalization.of(context).invalidFixedLinePhoneNumber; } + return errorText ?? + PhoneFieldLocalization.of(context).invalidPhoneNumber; } + return null; + + // if (value == null) { + // return null; + // } else if (control.value is PhoneNumber) { + // PhoneNumber? valueCandidate = control.value as PhoneNumber; + // + // if (PhoneValidator.validFixedLine().call(valueCandidate) == null) { + // return null; + // } else { + // return error; + // } + // } return null; } diff --git a/packages/reactive_phone_form_field/pubspec.yaml b/packages/reactive_phone_form_field/pubspec.yaml index 612a90b2..d577c502 100644 --- a/packages/reactive_phone_form_field/pubspec.yaml +++ b/packages/reactive_phone_form_field/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter reactive_forms: ^16.0.0 - phone_form_field: ^8.1.0 + phone_form_field: ^9.0.4 dev_dependencies: flutter_test: