diff --git a/lib/src/auth_screen.dart b/lib/src/auth_screen.dart index 8c5a51b..64d7a62 100644 --- a/lib/src/auth_screen.dart +++ b/lib/src/auth_screen.dart @@ -7,7 +7,45 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:flutter_registration/flutter_registration.dart'; +/// A widget for handling multi-step authentication processes. class AuthScreen extends StatefulWidget { + /// Constructs an [AuthScreen] object. + /// + /// [appBarTitle] specifies the title of the app bar. + /// + /// [onFinish] is a function called upon completion of the authentication process. + /// + /// [steps] is a list of authentication steps to be completed. + /// + /// [submitBtnTitle] specifies the title of the submit button. + /// + /// [nextBtnTitle] specifies the title of the next button. + /// + /// [previousBtnTitle] specifies the title of the previous button. + /// + /// [customAppBar] allows customization of the app bar. + /// + /// [buttonMainAxisAlignment] specifies the alignment of the buttons. + /// + /// [customBackgroundColor] allows customization of the background color. + /// + /// [nextButtonBuilder] allows customization of the next button. + /// + /// [previousButtonBuilder] allows customization of the previous button. + /// + /// [titleWidget] specifies a custom widget to be displayed at the top of the screen. + /// + /// [loginButton] specifies a custom login button widget. + /// + /// [titleFlex] specifies the flex value for the title widget. + /// + /// [formFlex] specifies the flex value for the form widget. + /// + /// [beforeTitleFlex] specifies the flex value before the title widget. + /// + /// [afterTitleFlex] specifies the flex value after the title widget. + /// + /// [isLoading] indicates whether the screen is in a loading state. const AuthScreen({ required this.appBarTitle, required this.steps, @@ -27,8 +65,9 @@ class AuthScreen extends StatefulWidget { this.beforeTitleFlex, this.afterTitleFlex, this.isLoading = false, - super.key, - }) : assert(steps.length > 0, 'At least one step is required'); + Key? key, + }) : assert(steps.length > 0, 'At least one step is required'), + super(key: key); final String appBarTitle; final Future Function({ @@ -58,6 +97,7 @@ class AuthScreen extends StatefulWidget { State createState() => _AuthScreenState(); } +/// The state for [AuthScreen]. class _AuthScreenState extends State { final _formKey = GlobalKey(); final _pageController = PageController(); @@ -65,12 +105,14 @@ class _AuthScreenState extends State { final _animationCurve = Curves.ease; bool _formValid = false; + /// Gets the app bar. AppBar get _appBar => widget.customAppBar ?? AppBar( title: Text(widget.appBarTitle), ); + /// Handles previous button press. void onPrevious() { FocusScope.of(context).unfocus(); _validate(_pageController.page!.toInt() - 1); @@ -80,6 +122,7 @@ class _AuthScreenState extends State { ); } + /// Handles next button press. Future onNext(AuthStep step) async { if (!_formKey.currentState!.validate()) { return; @@ -117,6 +160,7 @@ class _AuthScreenState extends State { } } + /// Validates the current step. void _validate(int currentPage) { bool isStepValid = true; @@ -156,137 +200,131 @@ class _AuthScreenState extends State { body: Form( key: _formKey, child: PageView( - physics: const NeverScrollableScrollPhysics(), - controller: _pageController, - children: [ - for (var i = 0; i < widget.steps.length; i++) - Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - 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(), - ), - ], + physics: const NeverScrollableScrollPhysics(), + controller: _pageController, + children: [ + for (var i = 0; i < widget.steps.length; i++) + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 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(), ), - ), - Column( + widget.titleWidget!, + Expanded( + flex: widget.afterTitleFlex ?? 2, + child: Container(), + ), + ], + ), + ), + Column( + children: [ + Row( + mainAxisAlignment: widget + .buttonMainAxisAlignment != + null + ? widget.buttonMainAxisAlignment! + : (widget.previousButtonBuilder != null && + widget.previousButtonBuilder?.call( + onPrevious, + widget.previousBtnTitle, + i, + ) == + null) + ? MainAxisAlignment.start + : widget.steps.first != widget.steps[i] + ? MainAxisAlignment.center + : MainAxisAlignment.end, children: [ - Row( - mainAxisAlignment: - widget.buttonMainAxisAlignment != null - ? widget.buttonMainAxisAlignment! - : (widget.previousButtonBuilder != - null && - widget.previousButtonBuilder - ?.call( - onPrevious, - widget - .previousBtnTitle, - i, - ) == - null) - ? MainAxisAlignment.start - : widget.steps.first != - widget.steps[i] - ? MainAxisAlignment.center - : MainAxisAlignment.end, - children: [ - if (widget.previousButtonBuilder == - null) ...[ - if (widget.steps.first != - widget.steps[i]) - 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) ...[ + if (widget.steps.first != widget.steps[i]) + ElevatedButton( + onPressed: onPrevious, + child: Row( + children: [ + const Icon( + Icons.arrow_back, + size: 18, ), - ), - ] else if (widget.previousButtonBuilder - ?.call(onPrevious, - widget.previousBtnTitle, i) != - null) ...[ - widget.previousButtonBuilder!.call( - onPrevious, - widget.previousBtnTitle, - i)! - ], - widget.nextButtonBuilder?.call( - !_formValid - ? null - : () async { - await onNext( - widget.steps[i]); - }, - widget.steps.last == widget.steps[i] - ? widget.submitBtnTitle - : widget.nextBtnTitle, - i, - _formValid, - ) ?? - ElevatedButton( - onPressed: !_formValid - ? null - : () 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, - ), - ), - ], + Padding( + padding: const EdgeInsets.only( + left: 4.0), + child: + Text(widget.previousBtnTitle), ), - ), - ], - ), - if (widget.loginButton != null) - Padding( - padding: const EdgeInsets.only(top: 20.0), - child: widget.loginButton!, - ), + ], + ), + ), + ] else if (widget.previousButtonBuilder?.call( + onPrevious, + widget.previousBtnTitle, + i) != + null) ...[ + widget.previousButtonBuilder!.call( + onPrevious, widget.previousBtnTitle, i)! + ], + widget.nextButtonBuilder?.call( + !_formValid + ? null + : () async { + await onNext(widget.steps[i]); + }, + widget.steps.last == widget.steps[i] + ? widget.submitBtnTitle + : widget.nextBtnTitle, + i, + _formValid, + ) ?? + ElevatedButton( + onPressed: !_formValid + ? null + : () 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, + ), + ), + ], + ), + ), ], ), + if (widget.loginButton != null) + Padding( + padding: const EdgeInsets.only(top: 20.0), + child: widget.loginButton!, + ), ], - ]), - ]), - )); + ), + ], + ], + ), + ], + ), + ), + ); } } diff --git a/lib/src/config/registration_options.dart b/lib/src/config/registration_options.dart index 01abe48..7598581 100644 --- a/lib/src/config/registration_options.dart +++ b/lib/src/config/registration_options.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_registration/flutter_registration.dart'; +/// A set of options for configuring the registration process in a Flutter application. class RegistrationOptions { RegistrationOptions({ required this.registrationRepository, @@ -23,21 +24,67 @@ class RegistrationOptions { this.loginButton, }); + /// Translations for registration-related messages and prompts. final RegistrationTranslations registrationTranslations; + + /// The steps involved in the registration process. final List registrationSteps; + + /// A function that handles errors during registration. final int? Function(String error)? onError; + + /// A callback function executed after successful registration. final VoidCallback afterRegistration; + + /// The repository responsible for registration. final RegistrationRepository registrationRepository; + + /// A function for customizing the app bar displayed during registration. final AppBar Function(String title)? customAppbarBuilder; + + /// A function for customizing the "Next" button. final Widget Function(Future Function()? onPressed, String label, int step, bool enabled)? nextButtonBuilder; + + /// A function for customizing the "Previous" button. final Widget? Function(VoidCallback onPressed, String label, int step)? previousButtonBuilder; + + /// Specifies the alignment of buttons. final MainAxisAlignment? buttonMainAxisAlignment; + + /// The background color of the registration screen. final Color? backgroundColor; + + /// A custom widget for displaying the registration title. Widget? titleWidget; + + /// A custom widget for displaying a login button. Widget? loginButton; + /// Generates default registration steps. + /// + /// [emailController] controller for email input. + /// + /// [pass1Controller] controller for first password input. + /// + /// [pass1Hidden] whether the first password field is initially hidden. + /// + /// [pass2Controller] controller for second password input. + /// + /// [pass2Hidden] whether the second password field is initially hidden. + /// + /// [passHideOnChange] function triggered when password visibility changes. + /// + /// [translations] translations for default registration messages and prompts. + /// + /// [titleBuilder] function for customizing step titles. + /// + /// [labelBuilder] function for customizing field labels. + /// + /// [textStyle] text style for input fields. + /// + /// [initialEmail] initial value for email input. static List getDefaultSteps({ TextEditingController? emailController, TextEditingController? pass1Controller, @@ -61,19 +108,12 @@ class RegistrationOptions { name: 'email', textEditingController: emailController, value: initialEmail ?? '', - title: titleBuilder?.call( - translations.defaultEmailTitle, - ) ?? + title: titleBuilder?.call(translations.defaultEmailTitle) ?? Padding( - padding: const EdgeInsets.only( - top: 24.0, - bottom: 12.0, - ), + padding: const EdgeInsets.only(top: 24.0, bottom: 12.0), child: Text( translations.defaultEmailTitle, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + style: const TextStyle(fontWeight: FontWeight.bold), ), ), textFieldDecoration: InputDecoration( @@ -99,19 +139,12 @@ class RegistrationOptions { AuthPassField( name: 'password1', textEditingController: pass1Controller, - title: titleBuilder?.call( - translations.defaultPassword1Title, - ) ?? + title: titleBuilder?.call(translations.defaultPassword1Title) ?? Padding( - padding: const EdgeInsets.only( - top: 24.0, - bottom: 12.0, - ), + padding: const EdgeInsets.only(top: 24.0, bottom: 12.0), child: Text( translations.defaultPassword1Title, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + style: const TextStyle(fontWeight: FontWeight.bold), ), ), textFieldDecoration: InputDecoration( @@ -131,19 +164,12 @@ class RegistrationOptions { AuthPassField( name: 'password2', textEditingController: pass2Controller, - title: titleBuilder?.call( - translations.defaultPassword2Title, - ) ?? + title: titleBuilder?.call(translations.defaultPassword2Title) ?? Padding( - padding: const EdgeInsets.only( - top: 24.0, - bottom: 12.0, - ), + padding: const EdgeInsets.only(top: 24.0, bottom: 12.0), child: Text( translations.defaultPassword2Title, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + style: const TextStyle(fontWeight: FontWeight.bold), ), ), textFieldDecoration: InputDecoration( diff --git a/lib/src/model/auth_bool_field.dart b/lib/src/model/auth_bool_field.dart index 503c4f4..fd2cbd4 100644 --- a/lib/src/model/auth_bool_field.dart +++ b/lib/src/model/auth_bool_field.dart @@ -6,7 +6,27 @@ import 'package:flutter/material.dart'; import 'package:flutter_input_library/flutter_input_library.dart'; import 'package:flutter_registration/flutter_registration.dart'; +/// A field for capturing boolean values in a Flutter form. +/// +/// Extends [AuthField]. class AuthBoolField extends AuthField { + /// Constructs an [AuthBoolField] object. + /// + /// [name] specifies the name of the field. + /// + /// [widgetType] defines the type of boolean widget to use. + /// + /// [title] specifies the title of the field (optional). + /// + /// [validators] defines a list of validation functions for the field. + /// + /// [value] specifies the initial value of the field (default is false). + /// + /// [leftWidget] is a widget to be displayed on the left side of the boolean widget. + /// + /// [rightWidget] is a widget to be displayed on the right side of the boolean widget. + /// + /// [onChange] is a callback function triggered when the value of the field changes. AuthBoolField({ required super.name, required this.widgetType, diff --git a/lib/src/model/auth_drop_down.dart b/lib/src/model/auth_drop_down.dart index 9a2acba..14bb405 100644 --- a/lib/src/model/auth_drop_down.dart +++ b/lib/src/model/auth_drop_down.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_registration/flutter_registration.dart'; +/// A field for capturing dropdown selections in a Flutter form. +/// +/// Extends [AuthField]. class AuthDropdownField extends AuthField { + /// Constructs an [AuthDropdownField] object. AuthDropdownField({ required super.name, required this.items, @@ -15,12 +19,25 @@ class AuthDropdownField extends AuthField { selectedValue = value ?? items.first; } + /// The list of items for the dropdown. final List items; + + /// A callback function triggered when the dropdown value changes. final Function(String?) onChanged; + + /// The currently selected value in the dropdown. String? selectedValue; + + /// The decoration for the dropdown. final InputDecoration? dropdownDecoration; + + /// The padding around the dropdown. final EdgeInsets padding; + + /// The text style for the dropdown. final TextStyle? textStyle; + + /// The icon to be displayed with the dropdown. final Icon icon; @override diff --git a/lib/src/model/auth_field.dart b/lib/src/model/auth_field.dart index 1e219a5..105a848 100644 --- a/lib/src/model/auth_field.dart +++ b/lib/src/model/auth_field.dart @@ -4,7 +4,21 @@ import 'package:flutter/material.dart'; +/// An abstract class representing a field in a Flutter form. +/// +/// [T] represents the type of value stored in the field. abstract class AuthField { + /// Constructs an [AuthField] object. + /// + /// [name] specifies the name of the field. + /// + /// [value] specifies the initial value of the field. + /// + /// [onValueChanged] is a callback function triggered when the value of the field changes (optional). + /// + /// [title] specifies the title widget of the field (optional). + /// + /// [validators] defines a list of validation functions for the field (optional). AuthField({ required this.name, required this.value, @@ -13,12 +27,25 @@ abstract class AuthField { this.validators = const [], }); + /// The name of the field. final String name; - final Widget? title; - List validators; + + /// The initial value of the field. T value; - final Function(T)? onValueChanged; // Callback for value changes + /// A callback function triggered when the value of the field changes. + final Function(T)? onValueChanged; + /// The title widget of the field. + final Widget? title; + + /// A list of validation functions for the field. + List validators; + + /// Builds the widget representing the field. + /// + /// [context] The build context. + /// + /// [onValueChanged] A function to be called when the value of the field 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 6cb978c..ea52b58 100644 --- a/lib/src/model/auth_pass_field.dart +++ b/lib/src/model/auth_pass_field.dart @@ -6,7 +6,31 @@ import 'package:flutter/material.dart'; import 'package:flutter_input_library/flutter_input_library.dart'; import 'package:flutter_registration/flutter_registration.dart'; +/// A field for capturing password inputs in a Flutter form. +/// +/// Extends [AuthField]. class AuthPassField extends AuthField { + /// Constructs an [AuthPassField] object. + /// + /// [name] specifies the name of the field. + /// + /// [textEditingController] controller for the password input (optional). + /// + /// [title] specifies the title widget of the field (optional). + /// + /// [validators] defines a list of validation functions for the field (optional). + /// + /// [value] specifies the initial value of the field (default is an empty string). + /// + /// [textStyle] defines the text style for the password input. + /// + /// [onChange] is a callback function triggered when the value of the field changes. + /// + /// [iconSize] specifies the size of the icon displayed with the password input (optional). + /// + /// [textFieldDecoration] defines the decoration for the password input field (optional). + /// + /// [padding] defines the padding around the password input field (default is EdgeInsets.all(8.0)). AuthPassField({ required super.name, TextEditingController? textEditingController, diff --git a/lib/src/model/auth_text_field.dart b/lib/src/model/auth_text_field.dart index 3c7b7c0..f4a2248 100644 --- a/lib/src/model/auth_text_field.dart +++ b/lib/src/model/auth_text_field.dart @@ -5,7 +5,29 @@ import 'package:flutter/material.dart'; import 'package:flutter_registration/flutter_registration.dart'; +/// A field for capturing text inputs in a Flutter form. +/// +/// Extends [AuthField]. class AuthTextField extends AuthField { + /// Constructs an [AuthTextField] object. + /// + /// [name] specifies the name of the field. + /// + /// [textEditingController] controller for the text input (optional). + /// + /// [title] specifies the title widget of the field (optional). + /// + /// [validators] defines a list of validation functions for the field (optional). + /// + /// [value] specifies the initial value of the field (default is an empty string). + /// + /// [textStyle] defines the text style for the text input. + /// + /// [onChange] is a callback function triggered when the value of the field changes. + /// + /// [textFieldDecoration] defines the decoration for the text input field (optional). + /// + /// [padding] defines the padding around the text input field (default is EdgeInsets.all(8.0)). AuthTextField({ required super.name, TextEditingController? textEditingController, diff --git a/lib/src/registration_screen.dart b/lib/src/registration_screen.dart index 33695de..0e12f53 100644 --- a/lib/src/registration_screen.dart +++ b/lib/src/registration_screen.dart @@ -4,21 +4,28 @@ import 'package:flutter/material.dart'; import 'package:flutter_registration/flutter_registration.dart'; import 'package:flutter_registration/src/auth_screen.dart'; +/// A screen for user registration. class RegistrationScreen extends StatefulWidget { + /// Constructs a [RegistrationScreen] object. + /// + /// [registrationOptions] specifies the registration options. const RegistrationScreen({ required this.registrationOptions, Key? key, }) : super(key: key); + /// The registration options. final RegistrationOptions registrationOptions; @override RegistrationScreenState createState() => RegistrationScreenState(); } +/// The state for [RegistrationScreen]. class RegistrationScreenState extends State { bool _isLoading = false; + /// Registers the user. Future _register({ required HashMap values, required void Function(int? pageToReturn) onError,