Compare commits

..

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

22 changed files with 540 additions and 733 deletions

6
.gitignore vendored
View file

@ -34,8 +34,4 @@ migrate_working_dir/
build/ build/
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies
.metadata .metadata
# FVM Version Cache
.fvm/
.fvmrc

View file

@ -3,13 +3,6 @@ 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
- feat: added maxFormWidth to AuthScreen
# 2.0.3 # 2.0.3
- feat: added default registrationOptions - feat: added default registrationOptions

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,73 +64,34 @@ class AuthScreen extends StatefulWidget {
this.formFlex, this.formFlex,
this.beforeTitleFlex, this.beforeTitleFlex,
this.afterTitleFlex, this.afterTitleFlex,
this.maxFormWidth, this.isLoading = false,
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;
@override @override
State<AuthScreen> createState() => _AuthScreenState(); State<AuthScreen> createState() => _AuthScreenState();
@ -157,11 +117,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 +144,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; duration: _animationDuration,
} curve: _animationCurve,
_pageController.animateToPage( ),
pageToReturn,
duration: _animationDuration,
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,186 +187,197 @@ 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(
appBar: _appBar, height: 120,
body: SafeArea( width: 120,
child: Form( child: CircularProgressIndicator(),
key: _formKey, ),
child: PageView( )
physics: const NeverScrollableScrollPhysics(), : Scaffold(
controller: _pageController, backgroundColor:
children: <Widget>[ widget.customBackgroundColor ?? const Color(0xffFAF9F6),
for (var currentStep = 0; appBar: _appBar,
currentStep < widget.steps.length; body: SafeArea(
currentStep++) child: Form(
Column( key: _formKey,
mainAxisSize: MainAxisSize.min, child: PageView(
mainAxisAlignment: MainAxisAlignment.spaceBetween, physics: const NeverScrollableScrollPhysics(),
children: [ controller: _pageController,
if (widget.titleWidget != null) ...[ children: <Widget>[
Expanded( for (var i = 0; i < widget.steps.length; i++)
flex: widget.titleFlex ?? 1, Column(
child: Column( mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min, children: [
children: [ if (widget.titleWidget != null) ...[
Expanded( Expanded(
flex: widget.beforeTitleFlex ?? 2, flex: widget.titleFlex ?? 1,
child: Container(), child: Column(
), mainAxisAlignment: MainAxisAlignment.start,
widget.titleWidget!, mainAxisSize: MainAxisSize.min,
Expanded( children: [
flex: widget.afterTitleFlex ?? 2, Expanded(
child: Container(), flex: widget.beforeTitleFlex ?? 2,
child: Container(),
),
widget.titleWidget!,
Expanded(
flex: widget.afterTitleFlex ?? 2,
child: Container(),
),
],
),
), ),
], ],
), Expanded(
), flex: widget.formFlex ?? 3,
], child: Align(
Expanded( child: Column(
flex: widget.formFlex ?? 3, children: [
child: Align( for (AuthField field
alignment: Alignment.topCenter, in widget.steps[i].fields) ...[
child: ConstrainedBox( if (field.title != null) ...[
constraints: BoxConstraints( field.title!,
maxWidth: widget.maxFormWidth ?? 300, ],
), field.build(context, () {
child: Column( _validate(i);
children: [ })
for (AuthField field ]
in widget.steps[currentStep].fields) ...[
if (field.title != null) ...[
wrapWithDefaultStyle(
style: theme.textTheme.headlineLarge!,
widget: field.title!,
),
], ],
field.build(context, () { ),
_validate(currentStep); ),
}), ),
], 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: [
if (widget.previousButtonBuilder == null) ...[
if (widget.steps.first != widget.steps[i])
Padding(
padding: const EdgeInsets.only(
left: 16, bottom: 10, right: 8),
child: InkWell(
onTap: onPrevious,
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.previousBtnTitle,
style: const TextStyle(
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!,
),
], ],
), ),
), ],
), ),
),
Column(
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
),
child: Row(
mainAxisAlignment: widget.steps.first !=
widget.steps[currentStep]
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.end,
children: [
if (widget.steps.first !=
widget.steps[currentStep]) ...[
widget.previousButtonBuilder?.call(
onPrevious,
widget.previousBtnTitle,
currentStep,
) ??
_stepButton(
buttonText: widget.previousBtnTitle,
onTap: () async => onPrevious(),
),
const SizedBox(
width: 8,
),
],
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: const EdgeInsets.only(top: 20.0),
child: widget.loginButton,
),
],
),
], ],
), ),
],
),
),
),
);
}
Widget _stepButton({
required String buttonText,
required Future Function()? onTap,
}) {
var theme = Theme.of(context);
return Flexible(
child: InkWell(
onTap: onTap,
child: Container(
width: double.infinity,
constraints: const BoxConstraints(
maxWidth: 180,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: const Color(
0xff979797,
), ),
), ),
), );
child: Padding(
padding: const EdgeInsets.all(4),
child: Text(
buttonText,
style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center,
),
),
),
),
);
} }
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,
@ -29,8 +27,6 @@ class RegistrationOptions {
this.backgroundColor, this.backgroundColor,
this.titleWidget, this.titleWidget,
this.loginButton, this.loginButton,
this.maxFormWidth,
this.beforeRegistration,
}) { }) {
if (registrationSteps == null || registrationSteps!.isEmpty) { if (registrationSteps == null || registrationSteps!.isEmpty) {
steps = RegistrationOptions.getDefaultSteps(); steps = RegistrationOptions.getDefaultSteps();
@ -46,7 +42,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 +57,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)?
@ -98,12 +88,6 @@ class RegistrationOptions {
/// The number of flex units for the buttons. /// The number of flex units for the buttons.
final int? afterTitleFlex; final int? afterTitleFlex;
/// The maximum width of the form. Defaults to 300.
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,98 +115,94 @@ 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,
padding: const EdgeInsets.symmetric(vertical: 20),
validators: [
// ignore: avoid_dynamic_calls
(email) => (email == null || email.isEmpty)
? translations.defaultEmailEmpty
: null,
(email) =>
RegExp(r"""(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])""")
.hasMatch(email!)
? null
: translations.defaultEmailValidatorMessage,
],
), ),
], textStyle: textStyle,
), padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 20),
AuthStep( validators: [
fields: [ (email) => (email == null || email.isEmpty)
AuthPassField( ? translations.defaultEmailEmpty
name: "password", : null,
textEditingController: passController, (email) =>
title: titleBuilder?.call(translations.defaultPasswordTitle) ?? RegExp(r"""(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])""")
Padding( .hasMatch(email!)
padding: const EdgeInsets.only(top: 180), ? null
child: Text( : translations.defaultEmailValidatorMessage,
translations.defaultPasswordTitle, ],
),
],
),
AuthStep(
fields: [
AuthPassField(
name: 'password',
textEditingController: passController,
title: titleBuilder?.call(translations.defaultPasswordTitle) ??
Padding(
padding: const EdgeInsets.only(top: 180),
child: Text(
translations.defaultPasswordTitle,
style: const TextStyle(
color: Color(0xff71C6D1),
fontWeight: FontWeight.w800,
fontSize: 24,
), ),
), ),
textFieldDecoration: InputDecoration( ),
hintStyle: hintStyle, textFieldDecoration: InputDecoration(
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),
textStyle: textStyle,
validators: [
(value) {
// ignore: avoid_dynamic_calls
if (value == null || value.isEmpty) {
return translations.defaultPasswordValidatorMessage;
}
// ignore: avoid_dynamic_calls
if (value.length < 6) {
return translations.defaultPasswordToShortValidatorMessage;
}
return null;
},
],
), ),
], padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 20),
), textStyle: textStyle,
]; validators: [
(value) => (value == null || value.isEmpty)
? translations.defaultPasswordValidatorMessage
: 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,27 +68,24 @@ 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, nextStepBtn: nextStepBtn ?? this.nextStepBtn,
nextStepBtn: nextStepBtn ?? this.nextStepBtn, closeBtn: closeBtn ?? this.closeBtn,
closeBtn: closeBtn ?? this.closeBtn, defaultEmailTitle: defaultEmailTitle ?? this.defaultEmailTitle,
defaultEmailTitle: defaultEmailTitle ?? this.defaultEmailTitle, defaultEmailLabel: defaultEmailLabel ?? this.defaultEmailLabel,
defaultEmailLabel: defaultEmailLabel ?? this.defaultEmailLabel, defaultEmailHint: defaultEmailHint ?? this.defaultEmailHint,
defaultEmailHint: defaultEmailHint ?? this.defaultEmailHint, defaultEmailEmpty: defaultEmailEmpty ?? this.defaultEmailEmpty,
defaultEmailEmpty: defaultEmailEmpty ?? this.defaultEmailEmpty, defaultEmailValidatorMessage:
defaultEmailValidatorMessage: defaultEmailValidatorMessage ?? this.defaultEmailValidatorMessage,
defaultEmailValidatorMessage ?? this.defaultEmailValidatorMessage, defaultPasswordTitle: defaultPasswordTitle ?? this.defaultPasswordTitle,
defaultPasswordTitle: defaultPasswordTitle ?? this.defaultPasswordTitle, defaultPasswordLabel: defaultPasswordLabel ?? this.defaultPasswordLabel,
defaultPasswordLabel: defaultPasswordLabel ?? this.defaultPasswordLabel, 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,38 +38,31 @@ 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) { for (var validator in validators) {
for (var validator in validators) { var output = validator(value);
var output = validator(value); if (output != null) {
if (output != null) { return output;
return output;
}
} }
return null; }
}, return null;
leftWidget: leftWidget, },
rightWidget: rightWidget, leftWidget: leftWidget,
); 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,38 +41,37 @@ 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) {
padding: padding, return Padding(
child: DropdownButtonFormField<String>( padding: padding,
icon: icon, child: DropdownButtonFormField<String>(
style: textStyle, icon: icon,
value: selectedValue, style: textStyle,
decoration: dropdownDecoration, value: selectedValue,
items: items decoration: dropdownDecoration,
.map( items: items.map((String value) {
(String value) => DropdownMenuItem<String>( return 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); onValueChanged();
// ignore: avoid_dynamic_calls },
onValueChanged(); validator: (value) {
}, if (validators.isNotEmpty) {
validator: (value) { for (var validator in validators) {
if (validators.isNotEmpty) { var output = validator(value);
for (var validator in validators) { if (output != null) {
var output = validator(value); return output;
if (output != null) {
return output;
}
} }
} }
return null; }
}, return null;
), },
); ),
);
}
} }

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,47 +44,36 @@ 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) {
padding: padding, return Padding(
child: FlutterFormInputPassword( padding: padding,
style: textStyle, child: FlutterFormInputPassword(
iconSize: iconSize ?? 24.0, style: textStyle,
decoration: textFieldDecoration, iconSize: iconSize ?? 24.0,
onChanged: (v) { decoration: textFieldDecoration,
value = v; onChanged: (v) {
onChange?.call(value); value = v;
// ignore: avoid_dynamic_calls onChange?.call(value);
onValueChanged(); onValueChanged();
}, },
validator: (value) { validator: (value) {
for (var validator in validators) { for (var validator in validators) {
var output = validator(value); var output = validator(value);
if (output != null) { if (output != null) {
return output; return output;
}
} }
}
return null; return null;
}, },
), ),
); );
}
} }

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,80 +17,62 @@ 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) {
padding: padding, return Padding(
child: TextFormField( padding: padding,
keyboardType: textInputType, child: TextFormField(
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) { for (var validator in validators) {
for (var validator in validators) { var output = validator(value);
var output = validator(value); if (output != null) {
if (output != null) { return output;
return output;
}
} }
}
return null; return null;
}, },
), ),
); );
}
} }

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,11 +75,11 @@ 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,
afterTitleFlex: widget.registrationOptions.afterTitleFlex, afterTitleFlex: widget.registrationOptions.afterTitleFlex,
maxFormWidth: widget.registrationOptions.maxFormWidth,
); );
} }
} }

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.3
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);
}); });
} }