From d1ad003c229f84afb3bdfe27552aa4baccb80593 Mon Sep 17 00:00:00 2001 From: Jacques Date: Thu, 1 Feb 2024 14:40:25 +0100 Subject: [PATCH 01/15] feat(buttons): Added the possiblity to only have a next button by return zero on the previous button builder --- lib/src/auth_screen.dart | 53 ++++++++++++++---------- lib/src/config/registration_options.dart | 2 +- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index d007268..bc18d18 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -34,7 +34,7 @@ class AuthScreen extends StatefulWidget { final AppBar? customAppBar; final Color? customBackgroundColor; final Widget Function(Future Function(), String)? nextButtonBuilder; - final Widget Function(VoidCallback, String)? previousButtonBuilder; + final Widget? Function(VoidCallback, String)? previousButtonBuilder; @override State createState() => _AuthScreenState(); @@ -98,6 +98,11 @@ class _AuthScreenState extends State { @override Widget build(BuildContext context) { + var previousButton = widget.previousButtonBuilder?.call( + onPrevious, + widget.previousBtnTitle, + ); + return Scaffold( backgroundColor: widget.customBackgroundColor ?? Colors.white, appBar: _appBar, @@ -145,30 +150,34 @@ class _AuthScreenState extends State { right: 30.0, ), child: Row( - mainAxisAlignment: widget.steps.first != step - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.end, + mainAxisAlignment: + (widget.previousButtonBuilder != null && + previousButton == null) + ? MainAxisAlignment.spaceAround + : widget.steps.first != step + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.end, children: [ if (widget.steps.first != step) - widget.previousButtonBuilder?.call( - onPrevious, - widget.previousBtnTitle, - ) ?? - ElevatedButton( - onPressed: onPrevious, - child: Row( - children: [ - const Icon( - Icons.arrow_back, - size: 18, - ), - Padding( - padding: const EdgeInsets.only(left: 4.0), - child: Text(widget.previousBtnTitle), - ), - ], - ), + if (widget.previousButtonBuilder == null) ...[ + ElevatedButton( + onPressed: onPrevious, + child: Row( + children: [ + const Icon( + Icons.arrow_back, + size: 18, + ), + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text(widget.previousBtnTitle), + ), + ], ), + ), + ] else if (previousButton != null) ...[ + previousButton + ], widget.nextButtonBuilder?.call( () async { await onNext(step); diff --git a/lib/src/config/registration_options.dart b/lib/src/config/registration_options.dart index 8ed741b..d45c9c6 100644 --- a/lib/src/config/registration_options.dart +++ b/lib/src/config/registration_options.dart @@ -28,7 +28,7 @@ class RegistrationOptions { final AppBar Function(String title)? customAppbarBuilder; final Widget Function(Future Function() onPressed, String label)? nextButtonBuilder; - final Widget Function(VoidCallback onPressed, String label)? + final Widget? Function(VoidCallback onPressed, String label)? previousButtonBuilder; final Color? backgroundColor; From f1663afa1b9535e7a24ce9e95dfcd3fa9525be24 Mon Sep 17 00:00:00 2001 From: FahadFahim71 <45163265+FahadFahim71@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:19:21 +0100 Subject: [PATCH 02/15] feat: expose input decoration in authtextfield --- lib/src/model/auth_text_field.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/src/model/auth_text_field.dart b/lib/src/model/auth_text_field.dart index 1f5ac89..51350a2 100644 --- a/lib/src/model/auth_text_field.dart +++ b/lib/src/model/auth_text_field.dart @@ -19,6 +19,7 @@ class AuthTextField extends AuthField { this.onChange, this.hidden, this.onPassChanged, + this.textFieldDecoration, }) { textController = textEditingController ?? TextEditingController(text: value); @@ -32,6 +33,7 @@ class AuthTextField extends AuthField { final Function(String value)? onChange; final bool? hidden; final Function(bool value)? onPassChanged; + final InputDecoration? textFieldDecoration; @override Widget build() { @@ -57,11 +59,12 @@ class AuthTextField extends AuthField { return TextFormField( style: textStyle, - decoration: InputDecoration( - label: label, - hintText: hintText, - suffix: suffix, - ), + decoration: textFieldDecoration ?? + InputDecoration( + label: label, + hintText: hintText, + suffix: suffix, + ), controller: textController, obscureText: hidden ?? obscureText, onChanged: (v) { From 7012942ce50a2de8c8a481595d8bca6e7a02c4de Mon Sep 17 00:00:00 2001 From: FahadFahim71 <45163265+FahadFahim71@users.noreply.github.com> Date: Fri, 2 Feb 2024 22:18:03 +0100 Subject: [PATCH 03/15] feat: add title widget and login button builder --- lib/src/auth_screen.dart | 61 ++++++++++++++---------- lib/src/config/registration_options.dart | 4 ++ lib/src/model/auth_text_field.dart | 47 +++++++++--------- lib/src/registration_screen.dart | 2 + 4 files changed, 67 insertions(+), 47 deletions(-) diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index bc18d18..5ccfdfe 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -19,6 +19,8 @@ class AuthScreen extends StatefulWidget { this.customBackgroundColor, this.nextButtonBuilder, this.previousButtonBuilder, + this.titleWidget, + this.loginButton, super.key, }) : assert(steps.length > 0, 'At least one step is required'); @@ -35,6 +37,8 @@ class AuthScreen extends StatefulWidget { final Color? customBackgroundColor; final Widget Function(Future Function(), String)? nextButtonBuilder; final Widget? Function(VoidCallback, String)? previousButtonBuilder; + final Widget? titleWidget; + final Widget? loginButton; @override State createState() => _AuthScreenState(); @@ -114,38 +118,40 @@ class _AuthScreenState extends State { children: [ for (AuthStep step in widget.steps) Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + // Text(widget.title), + if (widget.titleWidget != null) widget.titleWidget!, + const SizedBox(height: 40), Flexible( - child: Center( - child: ListView( - physics: const ClampingScrollPhysics(), - shrinkWrap: true, - padding: const EdgeInsets.symmetric( - vertical: 8.0, - horizontal: 30.0, - ), - children: [ - for (AuthField field in step.fields) - Align( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (field.title != null) ...[ - field.title!, - ], - field.build(), - ], - ), - ) - ], + child: ListView( + physics: const ClampingScrollPhysics(), + shrinkWrap: true, + padding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 30.0, ), + children: [ + for (AuthField field in step.fields) + Align( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (field.title != null) ...[ + field.title!, + ], + field.build(), + ], + ), + ) + ], ), ), + const Spacer(), Padding( padding: const EdgeInsets.only( top: 15.0, - bottom: 30.0, + // bottom: 30.0, left: 30.0, right: 30.0, ), @@ -209,7 +215,12 @@ class _AuthScreenState extends State { ), ], ), - ) + ), + if (widget.loginButton != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: widget.loginButton!, + ), ], ), ], diff --git a/lib/src/config/registration_options.dart b/lib/src/config/registration_options.dart index d45c9c6..52e0e06 100644 --- a/lib/src/config/registration_options.dart +++ b/lib/src/config/registration_options.dart @@ -18,6 +18,8 @@ class RegistrationOptions { this.nextButtonBuilder, this.previousButtonBuilder, this.backgroundColor, + this.titleWidget, + this.loginButton, }); final RegistrationTranslations registrationTranslations; @@ -31,6 +33,8 @@ class RegistrationOptions { final Widget? Function(VoidCallback onPressed, String label)? previousButtonBuilder; final Color? backgroundColor; + Widget? titleWidget; + Widget? loginButton; static List getDefaultSteps({ TextEditingController? emailController, diff --git a/lib/src/model/auth_text_field.dart b/lib/src/model/auth_text_field.dart index 51350a2..1a69293 100644 --- a/lib/src/model/auth_text_field.dart +++ b/lib/src/model/auth_text_field.dart @@ -57,30 +57,33 @@ class AuthTextField extends AuthField { } } - return TextFormField( - style: textStyle, - decoration: textFieldDecoration ?? - InputDecoration( - label: label, - hintText: hintText, - suffix: suffix, - ), - controller: textController, - obscureText: hidden ?? obscureText, - onChanged: (v) { - value = v; - onChange?.call(value); - }, - validator: (value) { - for (var validator in validators) { - var output = validator(value); - if (output != null) { - return output; + return Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: textStyle, + decoration: textFieldDecoration ?? + InputDecoration( + label: label, + hintText: hintText, + suffix: suffix, + ), + controller: textController, + obscureText: hidden ?? obscureText, + onChanged: (v) { + value = v; + onChange?.call(value); + }, + validator: (value) { + for (var validator in validators) { + var output = validator(value); + if (output != null) { + return output; + } } - } - return null; - }, + return null; + }, + ), ); } } diff --git a/lib/src/registration_screen.dart b/lib/src/registration_screen.dart index 83328dd..4e8f3de 100644 --- a/lib/src/registration_screen.dart +++ b/lib/src/registration_screen.dart @@ -53,6 +53,8 @@ class RegistrationScreen extends StatelessWidget { nextButtonBuilder: registrationOptions.nextButtonBuilder, previousButtonBuilder: registrationOptions.previousButtonBuilder, customBackgroundColor: registrationOptions.backgroundColor, + titleWidget: registrationOptions.titleWidget, + loginButton: registrationOptions.loginButton, ); } } From 46e2d960afe00c845089be7d0032ee7a3da66072 Mon Sep 17 00:00:00 2001 From: Jacques Date: Mon, 5 Feb 2024 13:00:21 +0100 Subject: [PATCH 04/15] feat(bool): Add a boolean field. Can be used for accepting terms and conditions --- example/android/build.gradle | 2 +- example/lib/main.dart | 64 ++++++++++++++++++++++++++++-- example/pubspec.lock | 35 +++++++++------- lib/flutter_registration.dart | 3 ++ lib/src/auth_screen.dart | 7 ++-- lib/src/model/auth_bool_field.dart | 47 ++++++++++++++++++++++ lib/src/model/auth_field.dart | 8 ++-- lib/src/registration_screen.dart | 2 +- pubspec.yaml | 8 ++++ 9 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 lib/src/model/auth_bool_field.dart diff --git a/example/android/build.gradle b/example/android/build.gradle index 83ae220..3cdaac9 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/lib/main.dart b/example/lib/main.dart index b5d639a..e11eec8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,27 +2,83 @@ // // SPDX-License-Identifier: BSD-3-Clause +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_registration/flutter_registration.dart'; import 'example_registration_repository.dart'; void main() { runApp( - const MaterialApp( - home: FlutterRegistrationDemo(), + MaterialApp( + theme: ThemeData( + inputDecorationTheme: const InputDecorationTheme( + errorStyle: TextStyle(color: Colors.red), + ), + ), + home: const FlutterRegistrationDemo(), ), ); } -class FlutterRegistrationDemo extends StatelessWidget { +class FlutterRegistrationDemo extends StatefulWidget { const FlutterRegistrationDemo({Key? key}) : super(key: key); + @override + State createState() => + _FlutterRegistrationDemoState(); +} + +class _FlutterRegistrationDemoState extends State { + late List steps; + + @override + void initState() { + super.initState(); + + steps = RegistrationOptions.getDefaultSteps(); + + steps[1].fields.add( + AuthBoolField( + name: 'termsConditions', + widgetType: BoolWidgetType.checkbox, + validators: [ + (value) { + if (value == null || !value) { + return 'Required'; + } + + return null; + }, + ], + rightWidget: Text.rich( + TextSpan( + text: 'I agree with the ', + // style: const TextStyle(fontSize: 16, color: Colors.black), + children: [ + TextSpan( + text: 'terms & conditions', + style: const TextStyle( + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + debugPrint('Open terms and conditions'); + }, + ), + ], + ), + ), + ), + ); + } + @override Widget build(BuildContext context) { return RegistrationScreen( registrationOptions: RegistrationOptions( + previousButtonBuilder: (onPressed, label) => null, registrationRepository: ExampleRegistrationRepository(), - registrationSteps: RegistrationOptions.getDefaultSteps(), + registrationSteps: steps, afterRegistration: () { debugPrint('Registered!'); }, diff --git a/example/pubspec.lock b/example/pubspec.lock index d9807c5..1018722 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive 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: @@ -62,6 +62,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_input_library: + dependency: transitive + description: + path: "../../flutter_input_library" + relative: true + source: path + version: "2.7.0" flutter_lints: dependency: "direct dev" description: @@ -81,7 +88,7 @@ packages: path: ".." relative: true source: path - version: "0.5.0" + version: "1.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -123,10 +130,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" path: dependency: transitive description: @@ -152,18 +159,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: @@ -184,10 +191,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: @@ -200,10 +207,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=1.17.0" diff --git a/lib/flutter_registration.dart b/lib/flutter_registration.dart index eac231f..8719e4f 100644 --- a/lib/flutter_registration.dart +++ b/lib/flutter_registration.dart @@ -10,5 +10,8 @@ export 'src/model/auth_exception.dart'; export 'src/model/auth_field.dart'; export 'src/model/auth_step.dart'; export 'src/model/auth_text_field.dart'; +export 'src/model/auth_bool_field.dart'; export 'src/registration_screen.dart'; export 'src/service/registration_repository.dart'; +export 'package:flutter_input_library/flutter_input_library.dart' + show BoolWidgetType; diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index bc18d18..ab0eca8 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -24,7 +24,7 @@ class AuthScreen extends StatefulWidget { final String title; final Future Function({ - required HashMap values, + required HashMap values, required void Function(int? pageToReturn) onError, }) onFinish; final List steps; @@ -69,12 +69,11 @@ class _AuthScreenState extends State { FocusScope.of(context).unfocus(); if (widget.steps.last == step) { - var values = HashMap(); + var values = HashMap(); for (var step in widget.steps) { for (var field in step.fields) { - values[field.name] = - (field as AuthTextField).textController.value.text; + values[field.name] = field.value; } } diff --git a/lib/src/model/auth_bool_field.dart b/lib/src/model/auth_bool_field.dart new file mode 100644 index 0000000..d5ae36c --- /dev/null +++ b/lib/src/model/auth_bool_field.dart @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter/material.dart'; +import 'package:flutter_input_library/flutter_input_library.dart'; +import 'package:flutter_registration/flutter_registration.dart'; + +class AuthBoolField extends AuthField { + AuthBoolField({ + required super.name, + required this.widgetType, + super.title, + super.validators = const [], + super.value = '', + this.leftWidget, + this.rightWidget, + this.onChange, + }); + + final Widget? leftWidget; + final Widget? rightWidget; + final BoolWidgetType widgetType; + final Function(String value)? onChange; + + @override + Widget build() { + return FlutterFormInputBool( + widgetType: widgetType, + onChanged: (v) { + value = v; + onChange?.call(value); + }, + validator: (value) { + for (var validator in validators) { + var output = validator(value); + if (output != null) { + return output; + } + } + return null; + }, + leftWidget: leftWidget, + rightWidget: rightWidget, + ); + } +} diff --git a/lib/src/model/auth_field.dart b/lib/src/model/auth_field.dart index 23ad82b..3690139 100644 --- a/lib/src/model/auth_field.dart +++ b/lib/src/model/auth_field.dart @@ -4,18 +4,18 @@ import 'package:flutter/material.dart'; -abstract class AuthField { +abstract class AuthField { AuthField({ required this.name, this.title, this.validators = const [], - this.value = '', + required this.value, }); final String name; final Widget? title; - List validators; - String value; + List validators; + T value; Widget build(); } diff --git a/lib/src/registration_screen.dart b/lib/src/registration_screen.dart index 83328dd..46d7eef 100644 --- a/lib/src/registration_screen.dart +++ b/lib/src/registration_screen.dart @@ -17,7 +17,7 @@ class RegistrationScreen extends StatelessWidget { final RegistrationOptions registrationOptions; Future register({ - required HashMap values, + required HashMap values, required void Function(int? pageToReturn) onError, }) async { try { diff --git a/pubspec.yaml b/pubspec.yaml index d318f16..9545d98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,11 +7,19 @@ description: A Flutter Registration package version: 1.2.0 repository: https://github.com/Iconica-Development/flutter_registration +publish_to: none + environment: sdk: ">=2.18.0 <3.0.0" flutter: ">=1.17.0" dependencies: + flutter_input_library: + path: ../flutter_input_library + # git: + # url: https://github.com/Iconica-Development/flutter_input_library + # ref: 2.7.0 + flutter: sdk: flutter flutter_localizations: From 79fe400842f7b418bd5dc30bc13791c3255c6499 Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 6 Feb 2024 09:40:43 +0100 Subject: [PATCH 05/15] feat(pass): Add dedicated password screen that manages state internally --- example/lib/main.dart | 1 - lib/src/config/registration_options.dart | 33 ++++++++-------- lib/src/model/auth_pass_field.dart | 49 ++++++++++++++++++++++++ lib/src/model/auth_text_field.dart | 38 +----------------- 4 files changed, 65 insertions(+), 56 deletions(-) create mode 100644 lib/src/model/auth_pass_field.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index e11eec8..438e372 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -53,7 +53,6 @@ class _FlutterRegistrationDemoState extends State { rightWidget: Text.rich( TextSpan( text: 'I agree with the ', - // style: const TextStyle(fontSize: 16, color: Colors.black), children: [ TextSpan( text: 'terms & conditions', diff --git a/lib/src/config/registration_options.dart b/lib/src/config/registration_options.dart index 52e0e06..affa3a5 100644 --- a/lib/src/config/registration_options.dart +++ b/lib/src/config/registration_options.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_registration/flutter_registration.dart'; +import 'package:flutter_registration/src/model/auth_pass_field.dart'; class RegistrationOptions { RegistrationOptions({ @@ -73,8 +74,10 @@ class RegistrationOptions { ), ), ), - label: labelBuilder?.call(translations.defaultEmailLabel), - hintText: translations.defaultEmailHint, + textFieldDecoration: InputDecoration( + label: labelBuilder?.call(translations.defaultEmailLabel), + hintText: translations.defaultEmailHint, + ), textStyle: textStyle, validators: [ (email) => (email == null || email.isEmpty) @@ -91,7 +94,7 @@ class RegistrationOptions { ), AuthStep( fields: [ - AuthTextField( + AuthPassField( name: 'password1', textEditingController: pass1Controller, title: titleBuilder?.call( @@ -109,10 +112,11 @@ class RegistrationOptions { ), ), ), - label: labelBuilder?.call(translations.defaultPassword1Label), - hintText: translations.defaultPassword1Hint, + textFieldDecoration: InputDecoration( + label: labelBuilder?.call(translations.defaultPassword1Label), + hintText: translations.defaultPassword1Hint, + ), textStyle: textStyle, - obscureText: true, validators: [ (value) => (value == null || value.isEmpty) ? translations.defaultPassword1ValidatorMessage @@ -121,12 +125,8 @@ class RegistrationOptions { onChange: (value) { password1 = value; }, - hidden: pass1Hidden, - onPassChanged: (value) { - passHideOnChange?.call(true, value); - }, ), - AuthTextField( + AuthPassField( name: 'password2', textEditingController: pass2Controller, title: titleBuilder?.call( @@ -144,10 +144,11 @@ class RegistrationOptions { ), ), ), - label: labelBuilder?.call(translations.defaultPassword2Label), - hintText: translations.defaultPassword2Hint, + textFieldDecoration: InputDecoration( + label: labelBuilder?.call(translations.defaultPassword2Label), + hintText: translations.defaultPassword2Hint, + ), textStyle: textStyle, - obscureText: true, validators: [ (value) { if (pass1Controller != null) { @@ -162,10 +163,6 @@ class RegistrationOptions { return null; } ], - hidden: pass2Hidden, - onPassChanged: (value) { - passHideOnChange?.call(false, value); - }, ), ], ), diff --git a/lib/src/model/auth_pass_field.dart b/lib/src/model/auth_pass_field.dart new file mode 100644 index 0000000..29d6cea --- /dev/null +++ b/lib/src/model/auth_pass_field.dart @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter/material.dart'; +import 'package:flutter_input_library/flutter_input_library.dart'; +import 'package:flutter_registration/flutter_registration.dart'; + +class AuthPassField extends AuthField { + AuthPassField({ + required super.name, + TextEditingController? textEditingController, + super.title, + super.validators = const [], + super.value = '', + this.textStyle, + this.onChange, + this.textFieldDecoration, + }); + + final TextStyle? textStyle; + final Function(String value)? onChange; + final InputDecoration? textFieldDecoration; + + @override + Widget build() { + return Padding( + padding: const EdgeInsets.all(8.0), + child: FlutterFormInputPassword( + style: textStyle, + decoration: textFieldDecoration, + onChanged: (v) { + value = v; + onChange?.call(value); + }, + validator: (value) { + for (var validator in validators) { + var output = validator(value); + if (output != null) { + return output; + } + } + + return null; + }, + ), + ); + } +} diff --git a/lib/src/model/auth_text_field.dart b/lib/src/model/auth_text_field.dart index 1a69293..0a02e10 100644 --- a/lib/src/model/auth_text_field.dart +++ b/lib/src/model/auth_text_field.dart @@ -12,13 +12,8 @@ class AuthTextField extends AuthField { super.title, super.validators = const [], super.value = '', - this.obscureText = false, - this.hintText, - this.label, this.textStyle, this.onChange, - this.hidden, - this.onPassChanged, this.textFieldDecoration, }) { textController = @@ -26,49 +21,18 @@ class AuthTextField extends AuthField { } late TextEditingController textController; - final bool obscureText; - final String? hintText; - final Widget? label; final TextStyle? textStyle; final Function(String value)? onChange; - final bool? hidden; - final Function(bool value)? onPassChanged; final InputDecoration? textFieldDecoration; @override Widget build() { - Widget? suffix; - - if (hidden != null) { - if (hidden!) { - suffix = GestureDetector( - onTap: () { - onPassChanged?.call(!hidden!); - }, - child: const Icon(Icons.visibility), - ); - } else { - suffix = GestureDetector( - onTap: () { - onPassChanged?.call(!hidden!); - }, - child: const Icon(Icons.visibility_off), - ); - } - } - return Padding( padding: const EdgeInsets.all(8.0), child: TextFormField( style: textStyle, - decoration: textFieldDecoration ?? - InputDecoration( - label: label, - hintText: hintText, - suffix: suffix, - ), + decoration: textFieldDecoration, controller: textController, - obscureText: hidden ?? obscureText, onChanged: (v) { value = v; onChange?.call(value); From c86fa1ca5f127ec003e52dfd1854aacf42076135 Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 6 Feb 2024 09:51:25 +0100 Subject: [PATCH 06/15] fix: Small refactor and brought back the normal alignment for the screens --- example/pubspec.lock | 10 ++++++---- lib/src/auth_screen.dart | 17 ++++++----------- lib/src/model/auth_pass_field.dart | 4 +++- lib/src/model/auth_text_field.dart | 4 +++- lib/src/registration_screen.dart | 2 +- pubspec.yaml | 7 +++---- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 1018722..1a7fe01 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -65,10 +65,12 @@ packages: flutter_input_library: dependency: transitive description: - path: "../../flutter_input_library" - relative: true - source: path - version: "2.7.0" + path: "." + ref: "3.0.0" + resolved-ref: "7024fb7e404fbeae0331bfe8f7c115283d0951ce" + url: "https://github.com/Iconica-Development/flutter_input_library" + source: git + version: "3.0.0" flutter_lints: dependency: "direct dev" description: diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index b4b5337..c818a79 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -9,7 +9,7 @@ import 'package:flutter_registration/flutter_registration.dart'; class AuthScreen extends StatefulWidget { const AuthScreen({ - required this.title, + required this.appBarTitle, required this.steps, required this.submitBtnTitle, required this.nextBtnTitle, @@ -24,7 +24,7 @@ class AuthScreen extends StatefulWidget { super.key, }) : assert(steps.length > 0, 'At least one step is required'); - final String title; + final String appBarTitle; final Future Function({ required HashMap values, required void Function(int? pageToReturn) onError, @@ -53,7 +53,7 @@ class _AuthScreenState extends State { AppBar get _appBar => widget.customAppBar ?? AppBar( - title: Text(widget.title), + title: Text(widget.appBarTitle), ); void onPrevious() { @@ -117,9 +117,8 @@ class _AuthScreenState extends State { children: [ for (AuthStep step in widget.steps) Column( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // Text(widget.title), if (widget.titleWidget != null) widget.titleWidget!, const SizedBox(height: 40), Flexible( @@ -150,7 +149,7 @@ class _AuthScreenState extends State { Padding( padding: const EdgeInsets.only( top: 15.0, - // bottom: 30.0, + bottom: 30.0, left: 30.0, right: 30.0, ), @@ -215,11 +214,7 @@ class _AuthScreenState extends State { ], ), ), - if (widget.loginButton != null) - Padding( - padding: const EdgeInsets.all(8.0), - child: widget.loginButton!, - ), + if (widget.loginButton != null) widget.loginButton!, ], ), ], diff --git a/lib/src/model/auth_pass_field.dart b/lib/src/model/auth_pass_field.dart index 29d6cea..2306199 100644 --- a/lib/src/model/auth_pass_field.dart +++ b/lib/src/model/auth_pass_field.dart @@ -16,16 +16,18 @@ class AuthPassField extends AuthField { this.textStyle, this.onChange, this.textFieldDecoration, + this.padding = const EdgeInsets.all(8.0), }); final TextStyle? textStyle; final Function(String value)? onChange; final InputDecoration? textFieldDecoration; + final EdgeInsets padding; @override Widget build() { return Padding( - padding: const EdgeInsets.all(8.0), + padding: padding, child: FlutterFormInputPassword( style: textStyle, decoration: textFieldDecoration, diff --git a/lib/src/model/auth_text_field.dart b/lib/src/model/auth_text_field.dart index 0a02e10..cac464b 100644 --- a/lib/src/model/auth_text_field.dart +++ b/lib/src/model/auth_text_field.dart @@ -15,6 +15,7 @@ class AuthTextField extends AuthField { this.textStyle, this.onChange, this.textFieldDecoration, + this.padding = const EdgeInsets.all(8.0), }) { textController = textEditingController ?? TextEditingController(text: value); @@ -24,11 +25,12 @@ class AuthTextField extends AuthField { final TextStyle? textStyle; final Function(String value)? onChange; final InputDecoration? textFieldDecoration; + final EdgeInsets padding; @override Widget build() { return Padding( - padding: const EdgeInsets.all(8.0), + padding: padding, child: TextFormField( style: textStyle, decoration: textFieldDecoration, diff --git a/lib/src/registration_screen.dart b/lib/src/registration_screen.dart index bb53a56..eb3dece 100644 --- a/lib/src/registration_screen.dart +++ b/lib/src/registration_screen.dart @@ -46,7 +46,7 @@ class RegistrationScreen extends StatelessWidget { translations.title, ), onFinish: register, - title: translations.title, + appBarTitle: translations.title, submitBtnTitle: translations.registerBtn, nextBtnTitle: translations.nextStepBtn, previousBtnTitle: translations.previousStepBtn, diff --git a/pubspec.yaml b/pubspec.yaml index 9545d98..4af66a0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,10 +15,9 @@ environment: dependencies: flutter_input_library: - path: ../flutter_input_library - # git: - # url: https://github.com/Iconica-Development/flutter_input_library - # ref: 2.7.0 + git: + url: https://github.com/Iconica-Development/flutter_input_library + ref: 3.0.0 flutter: sdk: flutter From f328df84dd2be5ba71d80441d4a8d7d04e859ec1 Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 6 Feb 2024 12:54:08 +0100 Subject: [PATCH 07/15] fix: Fixed alignment and spacing when opening keyboard --- lib/src/auth_screen.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index c818a79..7b93d54 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -120,8 +120,7 @@ class _AuthScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (widget.titleWidget != null) widget.titleWidget!, - const SizedBox(height: 40), - Flexible( + Expanded( child: ListView( physics: const ClampingScrollPhysics(), shrinkWrap: true, @@ -130,6 +129,7 @@ class _AuthScreenState extends State { horizontal: 30.0, ), children: [ + const SizedBox(height: 40), for (AuthField field in step.fields) Align( child: Column( @@ -145,7 +145,6 @@ class _AuthScreenState extends State { ], ), ), - const Spacer(), Padding( padding: const EdgeInsets.only( top: 15.0, From 7abb060306b567a20c746b292ccfe71fa9b01c86 Mon Sep 17 00:00:00 2001 From: FahadFahim71 <45163265+FahadFahim71@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:28:51 +0100 Subject: [PATCH 08/15] feat: add auth drop down field --- lib/flutter_registration.dart | 1 + lib/src/auth_screen.dart | 113 ++++++++++++++++-------------- lib/src/model/auth_drop_down.dart | 59 ++++++++++++++++ 3 files changed, 120 insertions(+), 53 deletions(-) create mode 100644 lib/src/model/auth_drop_down.dart diff --git a/lib/flutter_registration.dart b/lib/flutter_registration.dart index 8719e4f..9e7f910 100644 --- a/lib/flutter_registration.dart +++ b/lib/flutter_registration.dart @@ -11,6 +11,7 @@ export 'src/model/auth_field.dart'; export 'src/model/auth_step.dart'; export 'src/model/auth_text_field.dart'; export 'src/model/auth_bool_field.dart'; +export 'src/model/auth_drop_down.dart'; export 'src/registration_screen.dart'; export 'src/service/registration_repository.dart'; export 'package:flutter_input_library/flutter_input_library.dart' diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index 7b93d54..bc04cf9 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -149,71 +149,78 @@ class _AuthScreenState extends State { padding: const EdgeInsets.only( top: 15.0, bottom: 30.0, - left: 30.0, - right: 30.0, ), - child: Row( - mainAxisAlignment: - (widget.previousButtonBuilder != null && - previousButton == null) - ? MainAxisAlignment.spaceAround - : widget.steps.first != step - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.end, + child: Column( children: [ - if (widget.steps.first != step) - if (widget.previousButtonBuilder == null) ...[ - ElevatedButton( - onPressed: onPrevious, - child: Row( - children: [ - const Icon( - Icons.arrow_back, - size: 18, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (widget.steps.first != step) + if (widget.previousButtonBuilder == null) ...[ + ElevatedButton( + onPressed: onPrevious, + child: Row( + children: [ + const Icon( + Icons.arrow_back, + size: 18, + ), + Padding( + padding: + const EdgeInsets.only(left: 4.0), + child: Text(widget.previousBtnTitle), + ), + ], ), - Padding( - padding: const EdgeInsets.only(left: 4.0), - child: Text(widget.previousBtnTitle), - ), - ], - ), - ), - ] else if (previousButton != null) ...[ - previousButton - ], - widget.nextButtonBuilder?.call( - () async { - await onNext(step); - }, - widget.steps.last == step - ? widget.submitBtnTitle - : widget.nextBtnTitle, - ) ?? - ElevatedButton( - onPressed: () async { - await onNext(step); - }, - child: Row( - children: [ - Text( + ), + ] else if (previousButton != null) ...[ + Padding( + padding: const EdgeInsets.all(8.0), + child: previousButton, + ) + ], + Padding( + padding: const EdgeInsets.all(8.0), + child: widget.nextButtonBuilder?.call( + () async { + await onNext(step); + }, widget.steps.last == step ? widget.submitBtnTitle : widget.nextBtnTitle, - ), - const Padding( - padding: EdgeInsets.only(left: 4.0), - child: Icon( - Icons.arrow_forward, - size: 18, + ) ?? + ElevatedButton( + onPressed: () async { + await onNext(step); + }, + child: Row( + children: [ + Text( + widget.steps.last == step + ? widget.submitBtnTitle + : widget.nextBtnTitle, + ), + const Padding( + padding: EdgeInsets.only(left: 4.0), + child: Icon( + Icons.arrow_forward, + size: 18, + ), + ), + ], ), ), - ], - ), ), + ], + ), + if (widget.loginButton != null) + Padding( + padding: const EdgeInsets.only(top: 20.0), + child: widget.loginButton!, + ), ], ), ), - if (widget.loginButton != null) widget.loginButton!, ], ), ], diff --git a/lib/src/model/auth_drop_down.dart b/lib/src/model/auth_drop_down.dart new file mode 100644 index 0000000..80c87da --- /dev/null +++ b/lib/src/model/auth_drop_down.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_registration/flutter_registration.dart'; + +class AuthDropdownField extends AuthField { + AuthDropdownField({ + required super.name, + required this.items, + required this.onChanged, + this.dropdownDecoration, + this.padding = const EdgeInsets.all(8.0), + this.textStyle, + this.icon = const Icon(Icons.keyboard_arrow_down), + required super.value, + }) { + selectedValue = value ?? items.first; + } + + final List items; + final Function(String?) onChanged; + String? selectedValue; + final InputDecoration? dropdownDecoration; + final EdgeInsets padding; + final TextStyle? textStyle; + final Icon icon; + + @override + Widget build() { + return Padding( + padding: padding, + child: DropdownButtonFormField( + icon: icon, + style: textStyle, + value: selectedValue, + decoration: dropdownDecoration, + items: items.map((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (newValue) { + selectedValue = newValue; + onChanged(newValue); + }, + validator: (value) { + if (validators.isNotEmpty) { + for (var validator in validators) { + var output = validator(value); + if (output != null) { + return output; + } + } + } + return null; + }, + ), + ); + } +} From e112c64ee79f1f0b7f713359136b67fb793a4b9c Mon Sep 17 00:00:00 2001 From: FahadFahim71 <45163265+FahadFahim71@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:29:32 +0100 Subject: [PATCH 09/15] fix: correctly align next previous buttons --- lib/src/auth_screen.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index bc04cf9..60b8e15 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -153,7 +153,13 @@ class _AuthScreenState extends State { child: Column( children: [ Row( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: + (widget.previousButtonBuilder != null && + previousButton == null) + ? MainAxisAlignment.spaceAround + : widget.steps.first != step + ? MainAxisAlignment.spaceBetween + : MainAxisAlignment.end, children: [ if (widget.steps.first != step) if (widget.previousButtonBuilder == null) ...[ From 49b9ece2d5704d85d5d4142377b74c63b251de9f Mon Sep 17 00:00:00 2001 From: Jacques Date: Thu, 8 Feb 2024 11:54:07 +0100 Subject: [PATCH 10/15] fix: Fix button placement and added step to button builders --- example/lib/main.dart | 1 - lib/src/auth_screen.dart | 109 ++++++++++++----------- lib/src/config/registration_options.dart | 5 +- 3 files changed, 62 insertions(+), 53 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 438e372..2894316 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -75,7 +75,6 @@ class _FlutterRegistrationDemoState extends State { Widget build(BuildContext context) { return RegistrationScreen( registrationOptions: RegistrationOptions( - previousButtonBuilder: (onPressed, label) => null, registrationRepository: ExampleRegistrationRepository(), registrationSteps: steps, afterRegistration: () { diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index 60b8e15..851befe 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -35,8 +35,11 @@ class AuthScreen extends StatefulWidget { final String previousBtnTitle; final AppBar? customAppBar; final Color? customBackgroundColor; - final Widget Function(Future Function(), String)? nextButtonBuilder; - final Widget? Function(VoidCallback, String)? previousButtonBuilder; + final Widget Function( + Future Function() onPressed, String label, int step)? + nextButtonBuilder; + final Widget? Function(VoidCallback onPressed, String label, int step)? + previousButtonBuilder; final Widget? titleWidget; final Widget? loginButton; @@ -101,11 +104,6 @@ class _AuthScreenState extends State { @override Widget build(BuildContext context) { - var previousButton = widget.previousButtonBuilder?.call( - onPrevious, - widget.previousBtnTitle, - ); - return Scaffold( backgroundColor: widget.customBackgroundColor ?? Colors.white, appBar: _appBar, @@ -115,7 +113,7 @@ class _AuthScreenState extends State { physics: const NeverScrollableScrollPhysics(), controller: _pageController, children: [ - for (AuthStep step in widget.steps) + for (var i = 0; i < widget.steps.length; i++) Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -130,7 +128,7 @@ class _AuthScreenState extends State { ), children: [ const SizedBox(height: 40), - for (AuthField field in step.fields) + for (AuthField field in widget.steps[i].fields) Align( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -146,23 +144,36 @@ class _AuthScreenState extends State { ), ), Padding( - padding: const EdgeInsets.only( + padding: EdgeInsets.only( top: 15.0, bottom: 30.0, + left: widget.previousButtonBuilder == null && + widget.steps.first != widget.steps[i] + ? 30.0 + : 0.0, + right: widget.nextButtonBuilder == null && + widget.previousButtonBuilder == null + ? 30.0 + : 0.0, ), child: Column( children: [ Row( mainAxisAlignment: (widget.previousButtonBuilder != null && - previousButton == null) + widget.previousButtonBuilder?.call( + onPrevious, + widget.previousBtnTitle, + i, + ) == + null) ? MainAxisAlignment.spaceAround - : widget.steps.first != step + : widget.steps.first != widget.steps[i] ? MainAxisAlignment.spaceBetween : MainAxisAlignment.end, children: [ - if (widget.steps.first != step) - if (widget.previousButtonBuilder == null) ...[ + if (widget.previousButtonBuilder == null) ...[ + if (widget.steps.first != widget.steps[i]) ElevatedButton( onPressed: onPrevious, child: Row( @@ -179,44 +190,42 @@ class _AuthScreenState extends State { ], ), ), - ] else if (previousButton != null) ...[ - Padding( - padding: const EdgeInsets.all(8.0), - child: previousButton, - ) - ], - Padding( - padding: const EdgeInsets.all(8.0), - child: widget.nextButtonBuilder?.call( - () async { - await onNext(step); - }, - widget.steps.last == step - ? widget.submitBtnTitle - : widget.nextBtnTitle, - ) ?? - ElevatedButton( - onPressed: () async { - await onNext(step); - }, - child: Row( - children: [ - Text( - widget.steps.last == step - ? widget.submitBtnTitle - : widget.nextBtnTitle, + ] else if (widget.previousButtonBuilder?.call( + onPrevious, widget.previousBtnTitle, i) != + null) ...[ + widget.previousButtonBuilder! + .call(onPrevious, widget.previousBtnTitle, i)! + ], + widget.nextButtonBuilder?.call( + () async { + await onNext(widget.steps[i]); + }, + widget.steps.last == widget.steps[i] + ? widget.submitBtnTitle + : widget.nextBtnTitle, + i, + ) ?? + ElevatedButton( + onPressed: () async { + await onNext(widget.steps[i]); + }, + child: Row( + children: [ + Text( + widget.steps.last == widget.steps[i] + ? widget.submitBtnTitle + : widget.nextBtnTitle, + ), + const Padding( + padding: EdgeInsets.only(left: 4.0), + child: Icon( + Icons.arrow_forward, + size: 18, ), - const Padding( - padding: EdgeInsets.only(left: 4.0), - child: Icon( - Icons.arrow_forward, - size: 18, - ), - ), - ], - ), + ), + ], ), - ), + ), ], ), if (widget.loginButton != null) diff --git a/lib/src/config/registration_options.dart b/lib/src/config/registration_options.dart index affa3a5..51e5eb8 100644 --- a/lib/src/config/registration_options.dart +++ b/lib/src/config/registration_options.dart @@ -29,9 +29,10 @@ class RegistrationOptions { final VoidCallback afterRegistration; final RegistrationRepository registrationRepository; final AppBar Function(String title)? customAppbarBuilder; - final Widget Function(Future Function() onPressed, String label)? + final Widget Function( + Future Function() onPressed, String label, int step)? nextButtonBuilder; - final Widget? Function(VoidCallback onPressed, String label)? + final Widget? Function(VoidCallback onPressed, String label, int step)? previousButtonBuilder; final Color? backgroundColor; Widget? titleWidget; From 15aa74ebdabc8f6abc60f20e02f74613a353e7dc Mon Sep 17 00:00:00 2001 From: FahadFahim71 <45163265+FahadFahim71@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:33:41 +0100 Subject: [PATCH 11/15] fix: export auth_pass_field --- example/pubspec.lock | 14 +++++++------- lib/flutter_registration.dart | 1 + lib/src/config/registration_options.dart | 1 - lib/src/model/auth_pass_field.dart | 3 +++ pubspec.yaml | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 1a7fe01..e221bae 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" fake_async: dependency: transitive description: @@ -67,7 +67,7 @@ packages: description: path: "." ref: "3.0.0" - resolved-ref: "7024fb7e404fbeae0331bfe8f7c115283d0951ce" + resolved-ref: "7d1880b8e348435fc8dc2d3a11f936b224cbd5b7" url: "https://github.com/Iconica-Development/flutter_input_library" source: git version: "3.0.0" @@ -75,10 +75,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.3" flutter_localizations: dependency: transitive description: flutter @@ -108,10 +108,10 @@ packages: dependency: transitive description: name: lints - sha256: "5cfd6509652ff5e7fe149b6df4859e687fca9048437857cb2e65c8d780f396e3" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.1" matcher: dependency: transitive description: diff --git a/lib/flutter_registration.dart b/lib/flutter_registration.dart index 9e7f910..d599a3c 100644 --- a/lib/flutter_registration.dart +++ b/lib/flutter_registration.dart @@ -12,6 +12,7 @@ export 'src/model/auth_step.dart'; export 'src/model/auth_text_field.dart'; export 'src/model/auth_bool_field.dart'; export 'src/model/auth_drop_down.dart'; +export 'src/model/auth_pass_field.dart'; export 'src/registration_screen.dart'; export 'src/service/registration_repository.dart'; export 'package:flutter_input_library/flutter_input_library.dart' diff --git a/lib/src/config/registration_options.dart b/lib/src/config/registration_options.dart index 51e5eb8..2575df6 100644 --- a/lib/src/config/registration_options.dart +++ b/lib/src/config/registration_options.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_registration/flutter_registration.dart'; -import 'package:flutter_registration/src/model/auth_pass_field.dart'; class RegistrationOptions { RegistrationOptions({ diff --git a/lib/src/model/auth_pass_field.dart b/lib/src/model/auth_pass_field.dart index 2306199..be6bacc 100644 --- a/lib/src/model/auth_pass_field.dart +++ b/lib/src/model/auth_pass_field.dart @@ -15,11 +15,13 @@ class AuthPassField extends AuthField { super.value = '', this.textStyle, this.onChange, + this.iconSize, this.textFieldDecoration, this.padding = const EdgeInsets.all(8.0), }); final TextStyle? textStyle; + final double? iconSize; final Function(String value)? onChange; final InputDecoration? textFieldDecoration; final EdgeInsets padding; @@ -30,6 +32,7 @@ class AuthPassField extends AuthField { padding: padding, child: FlutterFormInputPassword( style: textStyle, + iconSize: iconSize ?? 24.0, decoration: textFieldDecoration, onChanged: (v) { value = v; diff --git a/pubspec.yaml b/pubspec.yaml index 4af66a0..ca74f93 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: flutter_input_library: git: url: https://github.com/Iconica-Development/flutter_input_library - ref: 3.0.0 + ref: 3.0.1 flutter: sdk: flutter From 277b38f39e56cb9319794e484d5ecd121ae80d6b Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Fri, 9 Feb 2024 18:36:22 +0100 Subject: [PATCH 12/15] feat: add validation to disable next button --- lib/src/auth_screen.dart | 52 +++++++++++++++++++----- lib/src/config/registration_options.dart | 5 +-- lib/src/model/auth_bool_field.dart | 3 +- lib/src/model/auth_drop_down.dart | 3 +- lib/src/model/auth_field.dart | 7 +++- lib/src/model/auth_pass_field.dart | 3 +- lib/src/model/auth_text_field.dart | 3 +- 7 files changed, 57 insertions(+), 19 deletions(-) diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index 851befe..eac1746 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -35,9 +35,8 @@ class AuthScreen extends StatefulWidget { final String previousBtnTitle; final AppBar? customAppBar; final Color? customBackgroundColor; - final Widget Function( - Future Function() onPressed, String label, int step)? - nextButtonBuilder; + final Widget Function(Future Function()? onPressed, String label, + int step, bool enabled)? nextButtonBuilder; final Widget? Function(VoidCallback onPressed, String label, int step)? previousButtonBuilder; final Widget? titleWidget; @@ -52,6 +51,7 @@ class _AuthScreenState extends State { final _pageController = PageController(); final _animationDuration = const Duration(milliseconds: 300); final _animationCurve = Curves.ease; + bool _formValid = false; AppBar get _appBar => widget.customAppBar ?? @@ -60,6 +60,7 @@ class _AuthScreenState extends State { ); void onPrevious() { + _validate(_pageController.page!.toInt() - 1); _pageController.previousPage( duration: _animationDuration, curve: _animationCurve, @@ -95,6 +96,7 @@ class _AuthScreenState extends State { return; } else { + _validate(_pageController.page!.toInt() + 1); _pageController.nextPage( duration: _animationDuration, curve: _animationCurve, @@ -102,6 +104,29 @@ class _AuthScreenState extends State { } } + void _validate(int currentPage) { + bool isStepValid = true; + + // Loop through each field in the current step + for (var field in widget.steps[currentPage].fields) { + for (var validator in field.validators) { + String? validationResult = validator(field.value); + if (validationResult != null) { + // If any validator returns an error, mark step as invalid and break + isStepValid = false; + break; + } + } + if (!isStepValid) { + break; // No need to check further fields if one is already invalid + } + } + + setState(() { + _formValid = isStepValid; + }); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -136,7 +161,9 @@ class _AuthScreenState extends State { if (field.title != null) ...[ field.title!, ], - field.build(), + field.build(context, () { + _validate(i); + }), ], ), ) @@ -197,18 +224,23 @@ class _AuthScreenState extends State { .call(onPrevious, widget.previousBtnTitle, i)! ], widget.nextButtonBuilder?.call( - () async { - await onNext(widget.steps[i]); - }, + !_formValid + ? null + : () async { + await onNext(widget.steps[i]); + }, widget.steps.last == widget.steps[i] ? widget.submitBtnTitle : widget.nextBtnTitle, i, + _formValid, ) ?? ElevatedButton( - onPressed: () async { - await onNext(widget.steps[i]); - }, + onPressed: !_formValid + ? null + : () async { + await onNext(widget.steps[i]); + }, child: Row( children: [ Text( diff --git a/lib/src/config/registration_options.dart b/lib/src/config/registration_options.dart index 2575df6..1d3e9e3 100644 --- a/lib/src/config/registration_options.dart +++ b/lib/src/config/registration_options.dart @@ -28,9 +28,8 @@ class RegistrationOptions { final VoidCallback afterRegistration; final RegistrationRepository registrationRepository; final AppBar Function(String title)? customAppbarBuilder; - final Widget Function( - Future Function() onPressed, String label, int step)? - nextButtonBuilder; + final Widget Function(Future Function()? onPressed, String label, + int step, bool enabled)? nextButtonBuilder; final Widget? Function(VoidCallback onPressed, String label, int step)? previousButtonBuilder; final Color? backgroundColor; diff --git a/lib/src/model/auth_bool_field.dart b/lib/src/model/auth_bool_field.dart index d5ae36c..3fc243b 100644 --- a/lib/src/model/auth_bool_field.dart +++ b/lib/src/model/auth_bool_field.dart @@ -24,12 +24,13 @@ class AuthBoolField extends AuthField { final Function(String value)? onChange; @override - Widget build() { + Widget build(BuildContext context, Function onValueChanged) { return FlutterFormInputBool( widgetType: widgetType, onChanged: (v) { value = v; onChange?.call(value); + onValueChanged(); }, validator: (value) { for (var validator in validators) { diff --git a/lib/src/model/auth_drop_down.dart b/lib/src/model/auth_drop_down.dart index 80c87da..9a2acba 100644 --- a/lib/src/model/auth_drop_down.dart +++ b/lib/src/model/auth_drop_down.dart @@ -24,7 +24,7 @@ class AuthDropdownField extends AuthField { final Icon icon; @override - Widget build() { + Widget build(BuildContext context, Function onValueChanged) { return Padding( padding: padding, child: DropdownButtonFormField( @@ -41,6 +41,7 @@ class AuthDropdownField extends AuthField { onChanged: (newValue) { selectedValue = newValue; onChanged(newValue); + onValueChanged(); }, validator: (value) { if (validators.isNotEmpty) { diff --git a/lib/src/model/auth_field.dart b/lib/src/model/auth_field.dart index 3690139..1e219a5 100644 --- a/lib/src/model/auth_field.dart +++ b/lib/src/model/auth_field.dart @@ -7,9 +7,10 @@ import 'package:flutter/material.dart'; abstract class AuthField { AuthField({ required this.name, + required this.value, + this.onValueChanged, this.title, this.validators = const [], - required this.value, }); final String name; @@ -17,5 +18,7 @@ abstract class AuthField { List validators; T value; - Widget build(); + final Function(T)? onValueChanged; // Callback for value changes + + Widget build(BuildContext context, Function onValueChanged); } diff --git a/lib/src/model/auth_pass_field.dart b/lib/src/model/auth_pass_field.dart index be6bacc..6cb978c 100644 --- a/lib/src/model/auth_pass_field.dart +++ b/lib/src/model/auth_pass_field.dart @@ -27,7 +27,7 @@ class AuthPassField extends AuthField { final EdgeInsets padding; @override - Widget build() { + Widget build(BuildContext context, Function onValueChanged) { return Padding( padding: padding, child: FlutterFormInputPassword( @@ -37,6 +37,7 @@ class AuthPassField extends AuthField { onChanged: (v) { value = v; onChange?.call(value); + onValueChanged(); }, validator: (value) { for (var validator in validators) { diff --git a/lib/src/model/auth_text_field.dart b/lib/src/model/auth_text_field.dart index cac464b..3c7b7c0 100644 --- a/lib/src/model/auth_text_field.dart +++ b/lib/src/model/auth_text_field.dart @@ -28,7 +28,7 @@ class AuthTextField extends AuthField { final EdgeInsets padding; @override - Widget build() { + Widget build(BuildContext context, Function onValueChanged) { return Padding( padding: padding, child: TextFormField( @@ -38,6 +38,7 @@ class AuthTextField extends AuthField { onChanged: (v) { value = v; onChange?.call(value); + onValueChanged(); }, validator: (value) { for (var validator in validators) { From 855e12f6ae1769fad4625f87a512b6db2f5d0555 Mon Sep 17 00:00:00 2001 From: FahadFahim71 <45163265+FahadFahim71@users.noreply.github.com> Date: Wed, 14 Feb 2024 09:57:29 +0100 Subject: [PATCH 13/15] feat(auth-screen): add flexible spacing between fields --- lib/src/auth_screen.dart | 42 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index eac1746..b0db65c 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -21,6 +21,10 @@ class AuthScreen extends StatefulWidget { this.previousButtonBuilder, this.titleWidget, this.loginButton, + this.titleFlex, + this.formFlex, + this.beforeTitleFlex, + this.afterTitleFlex, super.key, }) : assert(steps.length > 0, 'At least one step is required'); @@ -41,6 +45,10 @@ class AuthScreen extends StatefulWidget { previousButtonBuilder; final Widget? titleWidget; final Widget? loginButton; + final int? titleFlex; + final int? formFlex; + final int? beforeTitleFlex; + final int? afterTitleFlex; @override State createState() => _AuthScreenState(); @@ -140,19 +148,33 @@ class _AuthScreenState extends State { children: [ for (var i = 0; i < widget.steps.length; i++) Column( + mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (widget.titleWidget != null) widget.titleWidget!, - Expanded( - child: ListView( - physics: const ClampingScrollPhysics(), - shrinkWrap: true, - padding: const EdgeInsets.symmetric( - vertical: 8.0, - horizontal: 30.0, + if (widget.titleWidget != null) ...[ + Expanded( + flex: widget.titleFlex ?? 1, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + flex: widget.beforeTitleFlex ?? 3, + child: Container(), + ), + widget.titleWidget!, + Expanded( + flex: widget.afterTitleFlex ?? 2, + child: Container(), + ), + ], ), + ), + ], + Expanded( + flex: widget.formFlex ?? 3, + child: Column( children: [ - const SizedBox(height: 40), for (AuthField field in widget.steps[i].fields) Align( child: Column( @@ -166,7 +188,7 @@ class _AuthScreenState extends State { }), ], ), - ) + ), ], ), ), From 81fab79e81487c7cf16d2c61d90d62bf23744438 Mon Sep 17 00:00:00 2001 From: FahadFahim71 <45163265+FahadFahim71@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:44:14 +0100 Subject: [PATCH 14/15] fix(keyboard-focus): add unfocus for onPrevious --- lib/src/auth_screen.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index b0db65c..12b2bf1 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -68,6 +68,7 @@ class _AuthScreenState extends State { ); void onPrevious() { + FocusScope.of(context).unfocus(); _validate(_pageController.page!.toInt() - 1); _pageController.previousPage( duration: _animationDuration, From 4b855f7488f390f80162f444fa9b3e6b5067a08d Mon Sep 17 00:00:00 2001 From: FahadFahim71 <45163265+FahadFahim71@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:53:02 +0100 Subject: [PATCH 15/15] feat: update changelog.md and version number --- CHANGELOG.md | 15 +++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 901e8ec..501e26b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ SPDX-FileCopyrightText: 2022 Iconica SPDX-License-Identifier: GPL-3.0-or-later --> +# 2.0.0 +- feat(buttons): Added the possiblity to only have a next button by return zero on the previous button builder +- feat: exposed input decoration in AuthTextField +- feat: added title widget and login button builder +- feat(bool): Add a boolean field. Can be used for accepting terms and conditions +- feat(pass): Add dedicated password screen that manages state internally +- fix: Small refactor and brought back the normal alignment for the screens +- fix: Fixed alignment and spacing when opening keyboard +- feat: add auth drop down field +- fix: added step to button builders +- fix: exported auth_pass_field +- feat: added validation to disable next button +- feat(auth-screen): add flexible spacing between fields +- fix(keyboard-focus): add unfocus for onPrevious + # 1.2.0 - feat: Added the ability to have an async register function so you can call it asynchronous. diff --git a/pubspec.yaml b/pubspec.yaml index ca74f93..716570e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ name: flutter_registration description: A Flutter Registration package -version: 1.2.0 +version: 2.0.0 repository: https://github.com/Iconica-Development/flutter_registration publish_to: none