Compare commits

..

No commits in common. "master" and "2.0.4" have entirely different histories.

22 changed files with 545 additions and 721 deletions

4
.gitignore vendored
View file

@ -35,7 +35,3 @@ build/
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies
.metadata .metadata
# FVM Version Cache
.fvm/
.fvmrc

View file

@ -3,10 +3,7 @@ SPDX-FileCopyrightText: 2022 Iconica
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
--> -->
# 3.0.0
- fix: fixed the issue with the scrollController when the `pageToReturnTo` is null.
- feat: Added default styling and theme.
- feat: Added Iconica linter.
# 2.0.4 # 2.0.4
- feat: added maxFormWidth to AuthScreen - feat: added maxFormWidth to AuthScreen

View file

@ -1,9 +1,4 @@
include: package:flutter_iconica_analysis/components_options.yaml include: package:flutter_lints/flutter.yaml
# Possible to overwrite the rules from the package # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
analyzer:
exclude:
linter:
rules:

View file

@ -21,7 +21,7 @@ void main() {
} }
class FlutterRegistrationDemo extends StatefulWidget { class FlutterRegistrationDemo extends StatefulWidget {
const FlutterRegistrationDemo({super.key}); const FlutterRegistrationDemo({Key? key}) : super(key: key);
@override @override
State<FlutterRegistrationDemo> createState() => State<FlutterRegistrationDemo> createState() =>
@ -86,7 +86,8 @@ class _FlutterRegistrationDemoState extends State<FlutterRegistrationDemo> {
} }
class ProtectedScreen extends StatelessWidget { class ProtectedScreen extends StatelessWidget {
const ProtectedScreen({super.key}); const ProtectedScreen({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Scaffold( return const Scaffold(

View file

@ -65,11 +65,12 @@ packages:
flutter_input_library: flutter_input_library:
dependency: transitive dependency: transitive
description: description:
name: flutter_input_library path: "."
sha256: db8d9d57c31f166ed7c4ec3d060d18891533c57877a30c6c2668231efa1a44f8 ref: "3.0.0"
url: "https://forgejo.internal.iconica.nl/api/packages/internal/pub/" resolved-ref: "7d1880b8e348435fc8dc2d3a11f936b224cbd5b7"
source: hosted url: "https://github.com/Iconica-Development/flutter_input_library"
version: "3.6.0" source: git
version: "3.0.0"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -89,7 +90,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "2.0.4" version: "1.2.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -99,34 +100,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0" version: "0.18.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -139,34 +116,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16+1" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.0" version: "0.5.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.10.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.8.3"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -216,10 +193,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.0" version: "0.6.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -228,14 +205,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
vm_service: web:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: web
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.1" version: "0.3.0"
sdks: sdks:
dart: ">=3.3.0 <4.0.0" dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=1.17.0"

View file

@ -1,21 +1,19 @@
// SPDX-FileCopyrightText: 2022 Iconica // SPDX-FileCopyrightText: 2022 Iconica
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
/// Flutter registration component that provides a registration /// Flutter registration component that provides a registration screen with multiple registration steps.
/// screen with multiple registration steps.
library flutter_registration; library flutter_registration;
export "package:flutter_input_library/flutter_input_library.dart" export 'src/config/registration_options.dart';
export 'src/config/registration_translations.dart';
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/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'
show BoolWidgetType; show BoolWidgetType;
export "src/config/registration_options.dart";
export "src/config/registration_translations.dart";
export "src/model/auth_bool_field.dart";
export "src/model/auth_drop_down.dart";
export "src/model/auth_exception.dart";
export "src/model/auth_field.dart";
export "src/model/auth_pass_field.dart";
export "src/model/auth_step.dart";
export "src/model/auth_text_field.dart";
export "src/registration_screen.dart";
export "src/service/registration_repository.dart";

View file

@ -2,10 +2,10 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import "dart:async"; import 'dart:async';
import "dart:collection"; import 'dart:collection';
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
import "package:flutter_registration/flutter_registration.dart"; import 'package:flutter_registration/flutter_registration.dart';
/// A widget for handling multi-step authentication processes. /// A widget for handling multi-step authentication processes.
class AuthScreen extends StatefulWidget { class AuthScreen extends StatefulWidget {
@ -13,8 +13,7 @@ class AuthScreen extends StatefulWidget {
/// ///
/// [appBarTitle] specifies the title of the app bar. /// [appBarTitle] specifies the title of the app bar.
/// ///
/// [onFinish] is a function called upon /// [onFinish] is a function called upon completion of the authentication process.
/// completion of the authentication process.
/// ///
/// [steps] is a list of authentication steps to be completed. /// [steps] is a list of authentication steps to be completed.
/// ///
@ -34,8 +33,7 @@ class AuthScreen extends StatefulWidget {
/// ///
/// [previousButtonBuilder] allows customization of the previous button. /// [previousButtonBuilder] allows customization of the previous button.
/// ///
/// [titleWidget] specifies a custom widget /// [titleWidget] specifies a custom widget to be displayed at the top of the screen.
/// to be displayed at the top of the screen.
/// ///
/// [loginButton] specifies a custom login button widget. /// [loginButton] specifies a custom login button widget.
/// ///
@ -46,7 +44,8 @@ class AuthScreen extends StatefulWidget {
/// [beforeTitleFlex] specifies the flex value before the title widget. /// [beforeTitleFlex] specifies the flex value before the title widget.
/// ///
/// [afterTitleFlex] specifies the flex value after the title widget. /// [afterTitleFlex] specifies the flex value after the title widget.
///
/// [isLoading] indicates whether the screen is in a loading state.
const AuthScreen({ const AuthScreen({
required this.appBarTitle, required this.appBarTitle,
required this.steps, required this.steps,
@ -65,72 +64,35 @@ class AuthScreen extends StatefulWidget {
this.formFlex, this.formFlex,
this.beforeTitleFlex, this.beforeTitleFlex,
this.afterTitleFlex, this.afterTitleFlex,
this.isLoading = false,
this.maxFormWidth, this.maxFormWidth,
super.key, Key? key,
}) : assert(steps.length > 0, "At least one step is required"); }) : assert(steps.length > 0, 'At least one step is required'),
super(key: key);
/// The title of the app bar.
final String appBarTitle; final String appBarTitle;
/// A function called upon completion of the authentication process.
final Future<void> Function({ final Future<void> Function({
required HashMap<String, dynamic> values, required HashMap<String, dynamic> values,
required void Function(int? pageToReturn) onError, required void Function(int? pageToReturn) onError,
}) onFinish; }) onFinish;
/// The authentication steps to be completed.
final List<AuthStep> steps; final List<AuthStep> steps;
/// The title of the submit button.
final String submitBtnTitle; final String submitBtnTitle;
/// The title of the next button.
final String nextBtnTitle; final String nextBtnTitle;
/// The title of the previous button.
final String previousBtnTitle; final String previousBtnTitle;
/// A custom app bar widget.
final AppBar? customAppBar; final AppBar? customAppBar;
/// The alignment of the buttons.
final MainAxisAlignment? buttonMainAxisAlignment; final MainAxisAlignment? buttonMainAxisAlignment;
/// The background color of the screen.
final Color? customBackgroundColor; final Color? customBackgroundColor;
final Widget Function(Future<void> Function()? onPressed, String label,
/// A custom widget for the button. int step, bool enabled)? nextButtonBuilder;
final Widget Function(
Future<void> Function()? onPressed,
String label,
int step,
// ignore: avoid_positional_boolean_parameters
bool enabled,
)? nextButtonBuilder;
/// A custom widget for the button.
final Widget? Function(VoidCallback onPressed, String label, int step)? final Widget? Function(VoidCallback onPressed, String label, int step)?
previousButtonBuilder; previousButtonBuilder;
/// A custom widget for the title.
final Widget? titleWidget; final Widget? titleWidget;
/// A custom widget for the login button.
final Widget? loginButton; final Widget? loginButton;
/// The flex value for the title widget.
final int? titleFlex; final int? titleFlex;
/// The flex value for the form widget.
final int? formFlex; final int? formFlex;
/// The flex value before the title widget.
final int? beforeTitleFlex; final int? beforeTitleFlex;
/// The flex value after the title widget.
final int? afterTitleFlex; final int? afterTitleFlex;
final bool isLoading;
/// The maximum width of the form.
final double? maxFormWidth; final double? maxFormWidth;
@override @override
@ -157,11 +119,9 @@ class _AuthScreenState extends State<AuthScreen> {
void onPrevious() { void onPrevious() {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
_validate(_pageController.page!.toInt() - 1); _validate(_pageController.page!.toInt() - 1);
unawaited(
_pageController.previousPage( _pageController.previousPage(
duration: _animationDuration, duration: _animationDuration,
curve: _animationCurve, curve: _animationCurve,
),
); );
} }
@ -186,38 +146,31 @@ class _AuthScreenState extends State<AuthScreen> {
await widget.onFinish( await widget.onFinish(
values: values, values: values,
onError: (int? pageToReturn) { onError: (int? pageToReturn) => _pageController.animateToPage(
if (pageToReturn == null) { pageToReturn ?? 0,
return;
}
_pageController.animateToPage(
pageToReturn,
duration: _animationDuration, duration: _animationDuration,
curve: _animationCurve, curve: _animationCurve,
); ),
},
); );
return; return;
} else { } else {
_validate(_pageController.page!.toInt() + 1); _validate(_pageController.page!.toInt() + 1);
unawaited(
_pageController.nextPage( _pageController.nextPage(
duration: _animationDuration, duration: _animationDuration,
curve: _animationCurve, curve: _animationCurve,
),
); );
} }
} }
/// Validates the current step. /// Validates the current step.
void _validate(int currentPage) { void _validate(int currentPage) {
var isStepValid = true; bool isStepValid = true;
// Loop through each field in the current step // Loop through each field in the current step
for (var field in widget.steps[currentPage].fields) { for (var field in widget.steps[currentPage].fields) {
for (var validator in field.validators) { for (var validator in field.validators) {
var validationResult = validator(field.value); String? validationResult = validator(field.value);
if (validationResult != null) { if (validationResult != null) {
// If any validator returns an error, mark step as invalid and break // If any validator returns an error, mark step as invalid and break
isStepValid = false; isStepValid = false;
@ -236,9 +189,17 @@ class _AuthScreenState extends State<AuthScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); return widget.isLoading
return Scaffold( ? const Center(
backgroundColor: widget.customBackgroundColor ?? const Color(0xffFAF9F6), child: SizedBox(
height: 120,
width: 120,
child: CircularProgressIndicator(),
),
)
: Scaffold(
backgroundColor:
widget.customBackgroundColor ?? const Color(0xffFAF9F6),
appBar: _appBar, appBar: _appBar,
body: SafeArea( body: SafeArea(
child: Form( child: Form(
@ -247,9 +208,7 @@ class _AuthScreenState extends State<AuthScreen> {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
controller: _pageController, controller: _pageController,
children: <Widget>[ children: <Widget>[
for (var currentStep = 0; for (var i = 0; i < widget.steps.length; i++)
currentStep < widget.steps.length;
currentStep++)
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -285,17 +244,14 @@ class _AuthScreenState extends State<AuthScreen> {
child: Column( child: Column(
children: [ children: [
for (AuthField field for (AuthField field
in widget.steps[currentStep].fields) ...[ in widget.steps[i].fields) ...[
if (field.title != null) ...[ if (field.title != null) ...[
wrapWithDefaultStyle( field.title!,
style: theme.textTheme.headlineLarge!,
widget: field.title!,
),
], ],
field.build(context, () { field.build(context, () {
_validate(currentStep); _validate(i);
}), })
], ]
], ],
), ),
), ),
@ -303,119 +259,133 @@ class _AuthScreenState extends State<AuthScreen> {
), ),
Column( Column(
children: [ children: [
SizedBox( Row(
width: MediaQuery.of(context).size.width, mainAxisAlignment: widget
child: Padding( .buttonMainAxisAlignment !=
padding: const EdgeInsets.symmetric( null
horizontal: 20, ? widget.buttonMainAxisAlignment!
), : (widget.previousButtonBuilder != null &&
child: Row(
mainAxisAlignment: widget.steps.first !=
widget.steps[currentStep]
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.end,
children: [
if (widget.steps.first !=
widget.steps[currentStep]) ...[
widget.previousButtonBuilder?.call( widget.previousButtonBuilder?.call(
onPrevious, onPrevious,
widget.previousBtnTitle, widget.previousBtnTitle,
currentStep, i,
) ?? ) ==
_stepButton( null)
buttonText: widget.previousBtnTitle, ? MainAxisAlignment.start
onTap: () async => onPrevious(), : widget.steps.first != widget.steps[i]
), ? MainAxisAlignment.center
const SizedBox( : MainAxisAlignment.end,
width: 8, children: [
), if (widget.previousButtonBuilder == null) ...[
], if (widget.steps.first != widget.steps[i])
widget.nextButtonBuilder?.call(
!_formValid
? null
: () async {
await onNext(
widget.steps[currentStep],
);
},
widget.steps.last ==
widget.steps[currentStep]
? widget.submitBtnTitle
: widget.nextBtnTitle,
currentStep,
_formValid,
) ??
_stepButton(
buttonText: widget.steps.last ==
widget.steps[currentStep]
? widget.submitBtnTitle
: widget.nextBtnTitle,
onTap: () async {
await onNext(
widget.steps[currentStep],
);
},
),
],
),
),
),
const SizedBox(
height: 8,
),
if (widget.loginButton != null)
Padding( Padding(
padding: const EdgeInsets.only(top: 20.0), padding: const EdgeInsets.only(
child: widget.loginButton, left: 16, bottom: 10, right: 8),
),
],
),
],
),
],
),
),
),
);
}
Widget _stepButton({
required String buttonText,
required Future Function()? onTap,
}) {
var theme = Theme.of(context);
return Flexible(
child: InkWell( child: InkWell(
onTap: onTap, onTap: onPrevious,
child: Container( child: Container(
width: double.infinity, width: 180,
constraints: const BoxConstraints(
maxWidth: 180,
),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20), borderRadius:
BorderRadius.circular(20),
border: Border.all( border: Border.all(
color: const Color( color: const Color(
0xff979797, 0xff979797,
), ),
), ),
), ),
child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(4), padding:
const EdgeInsets.symmetric(
vertical: 2.0),
child: Text( child: Text(
buttonText, widget.previousBtnTitle,
style: theme.textTheme.bodyMedium, style: const TextStyle(
textAlign: TextAlign.center, fontSize: 16,
fontWeight: FontWeight.w500,
), ),
), ),
), ),
), ),
),
),
),
] 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,
) ??
Padding(
padding: const EdgeInsets.only(
right: 16, bottom: 10, left: 8),
child: InkWell(
onTap: !_formValid
? null
: () async {
await onNext(widget.steps[i]);
},
child: Container(
width: 180,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(20),
border: Border.all(
color: const Color(
0xff979797,
),
),
),
child: Center(
child: Padding(
padding:
const EdgeInsets.symmetric(
vertical: 2.0),
child: Text(
widget.steps.last ==
widget.steps[i]
? widget.submitBtnTitle
: widget.nextBtnTitle,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
),
),
),
],
),
if (widget.loginButton != null)
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: widget.loginButton!,
),
],
),
],
),
],
),
),
),
); );
} }
Widget wrapWithDefaultStyle({
required Widget widget,
required TextStyle style,
}) =>
DefaultTextStyle(style: style, child: widget);
} }

View file

@ -1,12 +1,11 @@
import "dart:collection"; import 'dart:collection';
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
import "package:flutter_registration/flutter_registration.dart"; import 'package:flutter_registration/flutter_registration.dart';
/// A registration repository that does nothing.
class ExampleRegistrationRepository with RegistrationRepository { class ExampleRegistrationRepository with RegistrationRepository {
@override @override
Future<String?> register(HashMap values) { Future<String?> register(HashMap values) {
debugPrint("register $values"); debugPrint('register $values');
return Future.value(null); return Future.value(null);
} }
} }

View file

@ -2,20 +2,18 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import "dart:async"; import 'dart:async';
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
import "package:flutter_registration/flutter_registration.dart"; import 'package:flutter_registration/flutter_registration.dart';
import "package:flutter_registration/src/config/example_registration_repository.dart"; import 'package:flutter_registration/src/config/example_registration_repository.dart';
/// A set of options for configuring the /// A set of options for configuring the registration process in a Flutter application.
/// registration process in a Flutter application.
class RegistrationOptions { class RegistrationOptions {
/// Registration options Constructor
RegistrationOptions({ RegistrationOptions({
required this.afterRegistration,
this.registrationRepository, this.registrationRepository,
this.registrationSteps, this.registrationSteps,
required this.afterRegistration,
this.titleFlex, this.titleFlex,
this.formFlex, this.formFlex,
this.beforeTitleFlex, this.beforeTitleFlex,
@ -30,7 +28,6 @@ class RegistrationOptions {
this.titleWidget, this.titleWidget,
this.loginButton, this.loginButton,
this.maxFormWidth, this.maxFormWidth,
this.beforeRegistration,
}) { }) {
if (registrationSteps == null || registrationSteps!.isEmpty) { if (registrationSteps == null || registrationSteps!.isEmpty) {
steps = RegistrationOptions.getDefaultSteps(); steps = RegistrationOptions.getDefaultSteps();
@ -46,7 +43,6 @@ class RegistrationOptions {
/// The steps involved in the registration process. /// The steps involved in the registration process.
final List<AuthStep>? registrationSteps; final List<AuthStep>? registrationSteps;
/// The steps involved in the registration process.
List<AuthStep> steps = []; List<AuthStep> steps = [];
/// A function that handles errors during registration. /// A function that handles errors during registration.
@ -62,13 +58,8 @@ class RegistrationOptions {
final AppBar Function(String title)? customAppbarBuilder; final AppBar Function(String title)? customAppbarBuilder;
/// A function for customizing the "Next" button. /// A function for customizing the "Next" button.
final Widget Function( final Widget Function(Future<void> Function()? onPressed, String label,
Future<void> Function()? onPressed, int step, bool enabled)? nextButtonBuilder;
String label,
int step,
// ignore: avoid_positional_boolean_parameters
bool enabled,
)? nextButtonBuilder;
/// A function for customizing the "Previous" button. /// A function for customizing the "Previous" button.
final Widget? Function(VoidCallback onPressed, String label, int step)? final Widget? Function(VoidCallback onPressed, String label, int step)?
@ -101,9 +92,6 @@ class RegistrationOptions {
/// The maximum width of the form. Defaults to 300. /// The maximum width of the form. Defaults to 300.
final double? maxFormWidth; final double? maxFormWidth;
/// This function gets called before the user gets registered.
final Future<void> Function()? beforeRegistration;
/// Generates default registration steps. /// Generates default registration steps.
/// ///
/// [emailController] controller for email input. /// [emailController] controller for email input.
@ -131,43 +119,42 @@ class RegistrationOptions {
TextEditingController? emailController, TextEditingController? emailController,
TextEditingController? passController, TextEditingController? passController,
bool passHidden = true, bool passHidden = true,
// ignore: avoid_positional_boolean_parameters
Function(bool mainPass, bool value)? passHideOnChange, Function(bool mainPass, bool value)? passHideOnChange,
RegistrationTranslations translations = RegistrationTranslations translations =
const RegistrationTranslations.empty(), const RegistrationTranslations.empty(),
Function(String title)? titleBuilder, Function(String title)? titleBuilder,
Function(String label)? labelBuilder, Function(String label)? labelBuilder,
TextStyle? textStyle, TextStyle? textStyle,
TextStyle? hintStyle,
String? initialEmail, String? initialEmail,
}) => }) {
[ return [
AuthStep( AuthStep(
fields: [ fields: [
AuthTextField( AuthTextField(
name: "email", name: 'email',
textEditingController: emailController, textEditingController: emailController,
value: initialEmail ?? "", value: initialEmail ?? '',
title: titleBuilder?.call(translations.defaultEmailTitle) ?? title: titleBuilder?.call(translations.defaultEmailTitle) ??
Padding( const Padding(
padding: const EdgeInsets.only(top: 180), padding: EdgeInsets.only(top: 180),
child: Text( child: Text(
translations.defaultEmailTitle, 'Enter your e-mail',
style: TextStyle(
color: Color(0xff71C6D1),
fontWeight: FontWeight.w800,
fontSize: 24,
),
), ),
), ),
textInputType: TextInputType.emailAddress,
textFieldDecoration: InputDecoration( textFieldDecoration: InputDecoration(
hintStyle: hintStyle,
contentPadding: const EdgeInsets.symmetric(horizontal: 8), contentPadding: const EdgeInsets.symmetric(horizontal: 8),
label: labelBuilder?.call(translations.defaultEmailLabel), label: labelBuilder?.call(translations.defaultEmailLabel),
hintText: translations.defaultEmailHint, hintText: translations.defaultEmailHint,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
focusedBorder: const OutlineInputBorder(),
), ),
textStyle: textStyle, textStyle: textStyle,
padding: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.symmetric(vertical: 20),
validators: [ validators: [
// ignore: avoid_dynamic_calls
(email) => (email == null || email.isEmpty) (email) => (email == null || email.isEmpty)
? translations.defaultEmailEmpty ? translations.defaultEmailEmpty
: null, : null,
@ -183,46 +170,43 @@ class RegistrationOptions {
AuthStep( AuthStep(
fields: [ fields: [
AuthPassField( AuthPassField(
name: "password", name: 'password',
textEditingController: passController, textEditingController: passController,
title: titleBuilder?.call(translations.defaultPasswordTitle) ?? title: titleBuilder?.call(translations.defaultPasswordTitle) ??
Padding( Padding(
padding: const EdgeInsets.only(top: 180), padding: const EdgeInsets.only(top: 180),
child: Text( child: Text(
translations.defaultPasswordTitle, translations.defaultPasswordTitle,
style: const TextStyle(
color: Color(0xff71C6D1),
fontWeight: FontWeight.w800,
fontSize: 24,
),
), ),
), ),
textFieldDecoration: InputDecoration( textFieldDecoration: InputDecoration(
hintStyle: hintStyle,
contentPadding: const EdgeInsets.symmetric(horizontal: 8), contentPadding: const EdgeInsets.symmetric(horizontal: 8),
label: labelBuilder?.call(translations.defaultPasswordLabel), label: labelBuilder?.call(translations.defaultPasswordLabel),
hintText: translations.defaultPasswordHint, hintText: translations.defaultPasswordHint,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
focusedBorder: const OutlineInputBorder(),
), ),
padding: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.symmetric(vertical: 20),
textStyle: textStyle, textStyle: textStyle,
validators: [ validators: [
(value) { (value) => (value == null || value.isEmpty)
// ignore: avoid_dynamic_calls ? translations.defaultPasswordValidatorMessage
if (value == null || value.isEmpty) { : null,
return translations.defaultPasswordValidatorMessage;
}
// ignore: avoid_dynamic_calls
if (value.length < 6) {
return translations.defaultPasswordToShortValidatorMessage;
}
return null;
},
], ],
), ),
], ],
), ),
]; ];
} }
}
AppBar _createCustomAppBar(String title) => AppBar( AppBar _createCustomAppBar(String title) {
iconTheme: const IconThemeData(color: Colors.black, size: 16), return AppBar(
title: Text(title), title: Text(title),
backgroundColor: Colors.transparent, backgroundColor: const Color(0xffFAF9F6),
); );
}

View file

@ -2,10 +2,8 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
/// Holds all the translations for the standard elements /// Holds all the translations for the standard elements on the registration screen.
/// on the registration screen.
class RegistrationTranslations { class RegistrationTranslations {
/// Constructs a [RegistrationTranslations] object.
const RegistrationTranslations({ const RegistrationTranslations({
required this.title, required this.title,
required this.registerBtn, required this.registerBtn,
@ -21,74 +19,40 @@ class RegistrationTranslations {
required this.defaultPasswordLabel, required this.defaultPasswordLabel,
required this.defaultPasswordHint, required this.defaultPasswordHint,
required this.defaultPasswordValidatorMessage, required this.defaultPasswordValidatorMessage,
required this.defaultPasswordToShortValidatorMessage,
}); });
/// Constructs a [RegistrationTranslations] object with empty strings.
const RegistrationTranslations.empty() const RegistrationTranslations.empty()
: title = "", : title = '',
registerBtn = "Register", registerBtn = 'Register',
previousStepBtn = "Previous", previousStepBtn = 'Previous',
nextStepBtn = "Next", nextStepBtn = 'Next',
closeBtn = "Close", closeBtn = 'Close',
defaultEmailTitle = "enter your email address", defaultEmailTitle = 'What is your email?',
defaultEmailLabel = "", defaultEmailLabel = '',
defaultEmailHint = "Email address", defaultEmailHint = 'Email address',
defaultEmailEmpty = "Please enter your email address.", defaultEmailEmpty = 'Please enter your email address.',
defaultEmailValidatorMessage = "Please enter a valid email address.", defaultEmailValidatorMessage = 'Please enter a valid email address.',
defaultPasswordTitle = "choose a password", defaultPasswordTitle = 'Choose a password',
defaultPasswordLabel = "", defaultPasswordLabel = 'password',
defaultPasswordHint = "Password", defaultPasswordHint = '',
defaultPasswordValidatorMessage = "Enter a valid password", defaultPasswordValidatorMessage = 'Enter a valid password';
defaultPasswordToShortValidatorMessage =
"Password needs to be at least 6 characters long";
/// The title of the registration screen.
final String title; final String title;
/// The text for the registration button.
final String registerBtn; final String registerBtn;
/// The text for the previous step button.
final String previousStepBtn; final String previousStepBtn;
/// The text for the next step button.
final String nextStepBtn; final String nextStepBtn;
/// The text for the close button.
final String closeBtn; final String closeBtn;
/// The title for the default email field.
final String defaultEmailTitle; final String defaultEmailTitle;
/// The label for the default email field.
final String defaultEmailLabel; final String defaultEmailLabel;
/// The hint for the default email field.
final String defaultEmailHint; final String defaultEmailHint;
/// The message for an empty default email field.
final String defaultEmailEmpty; final String defaultEmailEmpty;
/// The message for an invalid default email field.
final String defaultEmailValidatorMessage; final String defaultEmailValidatorMessage;
/// The title for the default password field.
final String defaultPasswordTitle; final String defaultPasswordTitle;
/// The label for the default password field.
final String defaultPasswordLabel; final String defaultPasswordLabel;
/// The hint for the default password field.
final String defaultPasswordHint; final String defaultPasswordHint;
/// The message for an invalid default password field.
final String defaultPasswordValidatorMessage; final String defaultPasswordValidatorMessage;
/// The message for a default password that is too short. // create a copywith
final String defaultPasswordToShortValidatorMessage;
/// create a copywith
RegistrationTranslations copyWith({ RegistrationTranslations copyWith({
String? title, String? title,
String? registerBtn, String? registerBtn,
@ -104,9 +68,8 @@ class RegistrationTranslations {
String? defaultPasswordLabel, String? defaultPasswordLabel,
String? defaultPasswordHint, String? defaultPasswordHint,
String? defaultPasswordValidatorMessage, String? defaultPasswordValidatorMessage,
String? defaultPasswordToShortValidatorMessage, }) {
}) => return RegistrationTranslations(
RegistrationTranslations(
title: title ?? this.title, title: title ?? this.title,
registerBtn: registerBtn ?? this.registerBtn, registerBtn: registerBtn ?? this.registerBtn,
previousStepBtn: previousStepBtn ?? this.previousStepBtn, previousStepBtn: previousStepBtn ?? this.previousStepBtn,
@ -123,8 +86,6 @@ class RegistrationTranslations {
defaultPasswordHint: defaultPasswordHint ?? this.defaultPasswordHint, defaultPasswordHint: defaultPasswordHint ?? this.defaultPasswordHint,
defaultPasswordValidatorMessage: defaultPasswordValidatorMessage ?? defaultPasswordValidatorMessage: defaultPasswordValidatorMessage ??
this.defaultPasswordValidatorMessage, this.defaultPasswordValidatorMessage,
defaultPasswordToShortValidatorMessage:
defaultPasswordToShortValidatorMessage ??
this.defaultPasswordToShortValidatorMessage,
); );
} }
}

View file

@ -2,19 +2,14 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
/// An action that can be performed during authentication.
class AuthAction { class AuthAction {
/// Constructs an [AuthAction] object.
AuthAction({ AuthAction({
required this.title, required this.title,
required this.onPress, required this.onPress,
}); });
/// The title of the action.
final String title; final String title;
/// A callback function triggered when the action is pressed.
final VoidCallback onPress; final VoidCallback onPress;
} }

View file

@ -2,9 +2,9 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
import "package:flutter_input_library/flutter_input_library.dart"; import 'package:flutter_input_library/flutter_input_library.dart';
import "package:flutter_registration/flutter_registration.dart"; import 'package:flutter_registration/flutter_registration.dart';
/// A field for capturing boolean values in a Flutter form. /// A field for capturing boolean values in a Flutter form.
/// ///
@ -22,14 +22,11 @@ class AuthBoolField extends AuthField {
/// ///
/// [value] specifies the initial value of the field (default is false). /// [value] specifies the initial value of the field (default is false).
/// ///
/// [leftWidget] is a widget to be displayed on the /// [leftWidget] is a widget to be displayed on the left side of the boolean widget.
/// left side of the boolean widget.
/// ///
/// [rightWidget] is a widget to be displayed on the /// [rightWidget] is a widget to be displayed on the right side of the boolean widget.
/// right side of the boolean widget.
/// ///
/// [onChange] is a callback function triggered when /// [onChange] is a callback function triggered when the value of the field changes.
/// the value of the field changes.
AuthBoolField({ AuthBoolField({
required super.name, required super.name,
required this.widgetType, required this.widgetType,
@ -41,26 +38,18 @@ class AuthBoolField extends AuthField {
this.onChange, this.onChange,
}); });
/// A widget to be displayed on the left side of the boolean widget.
final Widget? leftWidget; final Widget? leftWidget;
/// A widget to be displayed on the right side of the boolean widget.
final Widget? rightWidget; final Widget? rightWidget;
/// The type of boolean widget to use.
final BoolWidgetType widgetType; final BoolWidgetType widgetType;
/// A callback function triggered when the value of the field changes.
final Function(String value)? onChange; final Function(String value)? onChange;
@override @override
Widget build(BuildContext context, Function onValueChanged) => Widget build(BuildContext context, Function onValueChanged) {
FlutterFormInputBool( return FlutterFormInputBool(
widgetType: widgetType, widgetType: widgetType,
onChanged: (v) { onChanged: (v) {
value = v; value = v;
onChange?.call(value); onChange?.call(value);
// ignore: avoid_dynamic_calls
onValueChanged(); onValueChanged();
}, },
validator: (value) { validator: (value) {
@ -76,3 +65,4 @@ class AuthBoolField extends AuthField {
rightWidget: rightWidget, rightWidget: rightWidget,
); );
} }
}

View file

@ -1,5 +1,5 @@
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
import "package:flutter_registration/flutter_registration.dart"; import 'package:flutter_registration/flutter_registration.dart';
/// A field for capturing dropdown selections in a Flutter form. /// A field for capturing dropdown selections in a Flutter form.
/// ///
@ -10,11 +10,11 @@ class AuthDropdownField extends AuthField {
required super.name, required super.name,
required this.items, required this.items,
required this.onChanged, required this.onChanged,
required super.value,
this.dropdownDecoration, this.dropdownDecoration,
this.padding = const EdgeInsets.all(8.0), this.padding = const EdgeInsets.all(8.0),
this.textStyle, this.textStyle,
this.icon = const Icon(Icons.keyboard_arrow_down), this.icon = const Icon(Icons.keyboard_arrow_down),
required super.value,
}) { }) {
selectedValue = value ?? items.first; selectedValue = value ?? items.first;
} }
@ -41,25 +41,23 @@ class AuthDropdownField extends AuthField {
final Icon icon; final Icon icon;
@override @override
Widget build(BuildContext context, Function onValueChanged) => Padding( Widget build(BuildContext context, Function onValueChanged) {
return Padding(
padding: padding, padding: padding,
child: DropdownButtonFormField<String>( child: DropdownButtonFormField<String>(
icon: icon, icon: icon,
style: textStyle, style: textStyle,
value: selectedValue, value: selectedValue,
decoration: dropdownDecoration, decoration: dropdownDecoration,
items: items items: items.map((String value) {
.map( return DropdownMenuItem<String>(
(String value) => DropdownMenuItem<String>(
value: value, value: value,
child: Text(value), child: Text(value),
), );
) }).toList(),
.toList(),
onChanged: (newValue) { onChanged: (newValue) {
selectedValue = newValue; selectedValue = newValue;
onChanged(newValue); onChanged(newValue);
// ignore: avoid_dynamic_calls
onValueChanged(); onValueChanged();
}, },
validator: (value) { validator: (value) {
@ -76,3 +74,4 @@ class AuthDropdownField extends AuthField {
), ),
); );
} }
}

View file

@ -2,14 +2,12 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
/// An exception thrown when an authentication error occurs.
class AuthException implements Exception { class AuthException implements Exception {
/// Constructs an [AuthException] object.
AuthException(this.message); AuthException(this.message);
/// The error message.
final String message; final String message;
@override @override
String toString() => message; String toString() {
return message;
}
} }

View file

@ -2,7 +2,7 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
/// An abstract class representing a field in a Flutter form. /// An abstract class representing a field in a Flutter form.
/// ///
@ -14,13 +14,11 @@ abstract class AuthField<T> {
/// ///
/// [value] specifies the initial value of the field. /// [value] specifies the initial value of the field.
/// ///
/// [onValueChanged] is a callback function triggered when the /// [onValueChanged] is a callback function triggered when the value of the field changes (optional).
/// value of the field changes (optional).
/// ///
/// [title] specifies the title widget of the field (optional). /// [title] specifies the title widget of the field (optional).
/// ///
/// [validators] defines a list of validation /// [validators] defines a list of validation functions for the field (optional).
/// functions for the field (optional).
AuthField({ AuthField({
required this.name, required this.name,
required this.value, required this.value,
@ -48,7 +46,6 @@ abstract class AuthField<T> {
/// ///
/// [context] The build context. /// [context] The build context.
/// ///
/// [onValueChanged] A function to be called when /// [onValueChanged] A function to be called when the value of the field changes.
/// the value of the field changes.
Widget build(BuildContext context, Function onValueChanged); Widget build(BuildContext context, Function onValueChanged);
} }

View file

@ -2,9 +2,9 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
import "package:flutter_input_library/flutter_input_library.dart"; import 'package:flutter_input_library/flutter_input_library.dart';
import "package:flutter_registration/flutter_registration.dart"; import 'package:flutter_registration/flutter_registration.dart';
/// A field for capturing password inputs in a Flutter form. /// A field for capturing password inputs in a Flutter form.
/// ///
@ -18,31 +18,25 @@ class AuthPassField extends AuthField {
/// ///
/// [title] specifies the title widget of the field (optional). /// [title] specifies the title widget of the field (optional).
/// ///
/// [validators] defines a list of validation /// [validators] defines a list of validation functions for the field (optional).
/// functions for the field (optional).
/// ///
/// [value] specifies the initial value of the /// [value] specifies the initial value of the field (default is an empty string).
/// field (default is an empty string).
/// ///
/// [textStyle] defines the text style for the password input. /// [textStyle] defines the text style for the password input.
/// ///
/// [onChange] is a callback function triggered when /// [onChange] is a callback function triggered when the value of the field changes.
/// the value of the field changes.
/// ///
/// [iconSize] specifies the size of the icon displayed /// [iconSize] specifies the size of the icon displayed with the password input (optional).
/// with the password input (optional).
/// ///
/// [textFieldDecoration] defines the decoration for the /// [textFieldDecoration] defines the decoration for the password input field (optional).
/// password input field (optional).
/// ///
/// [padding] defines the padding around the password input /// [padding] defines the padding around the password input field (default is EdgeInsets.all(8.0)).
/// field (default is EdgeInsets.all(8.0)).
AuthPassField({ AuthPassField({
required super.name, required super.name,
this.textEditingController, TextEditingController? textEditingController,
super.title, super.title,
super.validators = const [], super.validators = const [],
super.value = "", super.value = '',
this.textStyle, this.textStyle,
this.onChange, this.onChange,
this.iconSize, this.iconSize,
@ -50,26 +44,15 @@ class AuthPassField extends AuthField {
this.padding = const EdgeInsets.all(8.0), this.padding = const EdgeInsets.all(8.0),
}); });
/// The text style for the password input.
final TextStyle? textStyle; final TextStyle? textStyle;
/// The size of the icon displayed with the password input.
final double? iconSize; final double? iconSize;
/// A callback function triggered when the value of the field changes.
final Function(String value)? onChange; final Function(String value)? onChange;
/// The decoration for the password input field.
final InputDecoration? textFieldDecoration; final InputDecoration? textFieldDecoration;
/// The padding around the password input field.
final EdgeInsets padding; final EdgeInsets padding;
/// The controller for the password input.
final TextEditingController? textEditingController;
@override @override
Widget build(BuildContext context, Function onValueChanged) => Padding( Widget build(BuildContext context, Function onValueChanged) {
return Padding(
padding: padding, padding: padding,
child: FlutterFormInputPassword( child: FlutterFormInputPassword(
style: textStyle, style: textStyle,
@ -78,7 +61,6 @@ class AuthPassField extends AuthField {
onChanged: (v) { onChanged: (v) {
value = v; value = v;
onChange?.call(value); onChange?.call(value);
// ignore: avoid_dynamic_calls
onValueChanged(); onValueChanged();
}, },
validator: (value) { validator: (value) {
@ -94,3 +76,4 @@ class AuthPassField extends AuthField {
), ),
); );
} }
}

View file

@ -2,15 +2,12 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import "package:flutter_registration/src/model/auth_field.dart"; import 'package:flutter_registration/src/model/auth_field.dart';
/// A step in the authentication process.
class AuthStep { class AuthStep {
/// Constructs an [AuthStep] object.
AuthStep({ AuthStep({
required this.fields, required this.fields,
}); });
/// The fields in the step.
List<AuthField> fields; List<AuthField> fields;
} }

View file

@ -2,8 +2,8 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
import "package:flutter_registration/flutter_registration.dart"; import 'package:flutter_registration/flutter_registration.dart';
/// A field for capturing text inputs in a Flutter form. /// A field for capturing text inputs in a Flutter form.
/// ///
@ -17,68 +17,49 @@ class AuthTextField extends AuthField {
/// ///
/// [title] specifies the title widget of the field (optional). /// [title] specifies the title widget of the field (optional).
/// ///
/// [validators] defines a list of validation /// [validators] defines a list of validation functions for the field (optional).
/// functions for the field (optional).
/// ///
/// [value] specifies the initial value of the /// [value] specifies the initial value of the field (default is an empty string).
/// field (default is an empty string).
/// ///
/// [textStyle] defines the text style for the text input. /// [textStyle] defines the text style for the text input.
/// ///
/// [onChange] is a callback function triggered /// [onChange] is a callback function triggered when the value of the field changes.
/// when the value of the field changes.
/// ///
/// [textFieldDecoration] defines the decoration /// [textFieldDecoration] defines the decoration for the text input field (optional).
/// for the text input field (optional).
/// ///
/// [padding] defines the padding around the text /// [padding] defines the padding around the text input field (default is EdgeInsets.all(8.0)).
/// input field (default is EdgeInsets.all(8.0)).
AuthTextField({ AuthTextField({
required super.name, required super.name,
TextEditingController? textEditingController, TextEditingController? textEditingController,
super.title, super.title,
super.validators = const [], super.validators = const [],
super.value = "", super.value = '',
this.textStyle, this.textStyle,
this.onChange, this.onChange,
this.textFieldDecoration, this.textFieldDecoration,
this.padding = const EdgeInsets.all(8.0), this.padding = const EdgeInsets.all(8.0),
this.textInputType,
}) { }) {
textController = textController =
textEditingController ?? TextEditingController(text: value); textEditingController ?? TextEditingController(text: value);
} }
/// The controller for the text input.
late TextEditingController textController; late TextEditingController textController;
/// The text style for the text input.
final TextStyle? textStyle; final TextStyle? textStyle;
/// A callback function triggered when the value of the field changes.
final Function(String value)? onChange; final Function(String value)? onChange;
/// The decoration for the text input field.
final InputDecoration? textFieldDecoration; final InputDecoration? textFieldDecoration;
/// The padding around the text input field.
final EdgeInsets padding; final EdgeInsets padding;
/// The type of text input.
final TextInputType? textInputType;
@override @override
Widget build(BuildContext context, Function onValueChanged) => Padding( Widget build(BuildContext context, Function onValueChanged) {
return Padding(
padding: padding, padding: padding,
child: TextFormField( child: TextFormField(
keyboardType: textInputType,
style: textStyle, style: textStyle,
decoration: textFieldDecoration, decoration: textFieldDecoration,
controller: textController, controller: textController,
onChanged: (v) { onChanged: (v) {
value = v; value = v;
onChange?.call(value); onChange?.call(value);
// ignore: avoid_dynamic_calls
onValueChanged(); onValueChanged();
}, },
validator: (value) { validator: (value) {
@ -94,3 +75,4 @@ class AuthTextField extends AuthField {
), ),
); );
} }
}

View file

@ -1,8 +1,8 @@
import "dart:collection"; import 'dart:collection';
import "package:flutter/material.dart"; import 'package:flutter/material.dart';
import "package:flutter_registration/flutter_registration.dart"; import 'package:flutter_registration/flutter_registration.dart';
import "package:flutter_registration/src/auth_screen.dart"; import 'package:flutter_registration/src/auth_screen.dart';
/// A screen for user registration. /// A screen for user registration.
class RegistrationScreen extends StatefulWidget { class RegistrationScreen extends StatefulWidget {
@ -11,8 +11,8 @@ class RegistrationScreen extends StatefulWidget {
/// [registrationOptions] specifies the registration options. /// [registrationOptions] specifies the registration options.
const RegistrationScreen({ const RegistrationScreen({
required this.registrationOptions, required this.registrationOptions,
super.key, Key? key,
}); }) : super(key: key);
/// The registration options. /// The registration options.
final RegistrationOptions registrationOptions; final RegistrationOptions registrationOptions;
@ -23,13 +23,17 @@ class RegistrationScreen extends StatefulWidget {
/// The state for [RegistrationScreen]. /// The state for [RegistrationScreen].
class RegistrationScreenState extends State<RegistrationScreen> { class RegistrationScreenState extends State<RegistrationScreen> {
bool _isLoading = false;
/// Registers the user. /// Registers the user.
Future<void> _register({ Future<void> _register({
required HashMap<String, dynamic> values, required HashMap<String, dynamic> values,
required void Function(int? pageToReturn) onError, required void Function(int? pageToReturn) onError,
}) async { }) async {
try { try {
await widget.registrationOptions.beforeRegistration?.call(); setState(() {
_isLoading = true;
});
var registered = await widget.registrationOptions.registrationRepository! var registered = await widget.registrationOptions.registrationRepository!
.register(values); .register(values);
@ -41,8 +45,12 @@ class RegistrationScreenState extends State<RegistrationScreen> {
onError(pageToReturn); onError(pageToReturn);
} }
} on Exception catch (_) { } catch (e) {
onError(null); onError(0);
} finally {
setState(() {
_isLoading = false;
});
} }
} }
@ -67,6 +75,7 @@ class RegistrationScreenState extends State<RegistrationScreen> {
customBackgroundColor: widget.registrationOptions.backgroundColor, customBackgroundColor: widget.registrationOptions.backgroundColor,
titleWidget: widget.registrationOptions.titleWidget, titleWidget: widget.registrationOptions.titleWidget,
loginButton: widget.registrationOptions.loginButton, loginButton: widget.registrationOptions.loginButton,
isLoading: _isLoading,
titleFlex: widget.registrationOptions.titleFlex, titleFlex: widget.registrationOptions.titleFlex,
formFlex: widget.registrationOptions.formFlex, formFlex: widget.registrationOptions.formFlex,
beforeTitleFlex: widget.registrationOptions.beforeTitleFlex, beforeTitleFlex: widget.registrationOptions.beforeTitleFlex,

View file

@ -2,10 +2,8 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import "dart:collection"; import 'dart:collection';
/// A mixin for a registration repository.
mixin RegistrationRepository { mixin RegistrationRepository {
/// Registers a user with the given values.
Future<String?> register(HashMap values); Future<String?> register(HashMap values);
} }

View file

@ -4,19 +4,20 @@
name: flutter_registration name: flutter_registration
description: A Flutter Registration package description: A Flutter Registration package
version: 3.0.0 version: 2.0.4
repository: https://github.com/Iconica-Development/flutter_registration repository: https://github.com/Iconica-Development/flutter_registration
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub publish_to: none
environment: environment:
sdk: ">=3.0.0 <4.0.0" sdk: ">=2.18.0 <3.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
flutter_input_library: flutter_input_library:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub git:
version: ^3.6.0 url: https://github.com/Iconica-Development/flutter_input_library
ref: 3.1.0
flutter: flutter:
sdk: flutter sdk: flutter
@ -26,9 +27,6 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_iconica_analysis: flutter_lints: ^2.0.0
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
flutter: flutter:

View file

@ -3,10 +3,10 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "package:flutter_test/flutter_test.dart"; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
test("test", () { test('test', () {
expect(true, true); expect(true, true);
}); });
} }