Compare commits

...

11 commits

Author SHA1 Message Date
Gorter-dev
ee29893bd9
Merge pull request #31 from Iconica-Development/feature/default_style
feat: default styling
2024-08-09 14:32:34 +02:00
mike doornenbal
4bad032588 feat: default styling 2024-08-09 14:22:25 +02:00
Gorter-dev
cf6e30abec
Merge pull request #29 from Iconica-Development/chore/deploy
chore: ready the package for deployment to the pub server
2024-07-22 14:55:50 +02:00
Bart Ribbers
ce80a96958 chore: ready the package for deployment to the pub server 2024-07-11 18:06:46 +02:00
Bart Ribbers
71259c7f78 chore: add fvm configuration to gitignore 2024-07-11 18:03:05 +02:00
Gorter-dev
d82df68989
Merge pull request #27 from Iconica-Development/bugfix/registration_field_size
fix: add default field size
2024-04-22 13:07:04 +02:00
mike doornenbal
c91a1c0856 fix: add default field size 2024-04-22 13:01:35 +02:00
Gorter-dev
444b11a44f
Merge pull request #26 from Iconica-Development/feature/default_styling
feat: add default styling
2024-04-19 11:27:47 +02:00
mike doornenbal
1be83c7013 feat: add default styling 2024-04-19 11:25:09 +02:00
Gorter-dev
990259dabc
Merge pull request #25 from Iconica-Development/2.0.3
feat: add default registrationOptions
2024-04-18 09:39:32 +02:00
mike doornenbal
d849a1834a feat: add default registrationOptions 2024-04-17 15:26:08 +02:00
22 changed files with 775 additions and 564 deletions

4
.gitignore vendored
View file

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

View file

@ -3,6 +3,15 @@ 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
- feat: added default registrationOptions
# 2.0.2 # 2.0.2
- fix: fixed the issue with values not being saved when calling nextStep. - fix: fixed the issue with values not being saved when calling nextStep.

View file

@ -1,4 +1,9 @@
include: package:flutter_lints/flutter.yaml include: package:flutter_iconica_analysis/components_options.yaml
# Additional information about this file can be found at # Possible to overwrite the rules from the package
# 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({Key? key}) : super(key: key); const FlutterRegistrationDemo({super.key});
@override @override
State<FlutterRegistrationDemo> createState() => State<FlutterRegistrationDemo> createState() =>
@ -86,8 +86,7 @@ class _FlutterRegistrationDemoState extends State<FlutterRegistrationDemo> {
} }
class ProtectedScreen extends StatelessWidget { class ProtectedScreen extends StatelessWidget {
const ProtectedScreen({Key? key}) : super(key: key); const ProtectedScreen({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Scaffold( return const Scaffold(

View file

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

View file

@ -1,19 +1,21 @@
// 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 screen with multiple registration steps. /// Flutter registration component that provides a registration
/// screen with multiple registration steps.
library flutter_registration; library flutter_registration;
export 'src/config/registration_options.dart'; export "package:flutter_input_library/flutter_input_library.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,7 +13,8 @@ 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 completion of the authentication process. /// [onFinish] is a function called upon
/// 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.
/// ///
@ -33,7 +34,8 @@ class AuthScreen extends StatefulWidget {
/// ///
/// [previousButtonBuilder] allows customization of the previous button. /// [previousButtonBuilder] allows customization of the previous button.
/// ///
/// [titleWidget] specifies a custom widget to be displayed at the top of the screen. /// [titleWidget] specifies a custom widget
/// to be displayed at the top of the screen.
/// ///
/// [loginButton] specifies a custom login button widget. /// [loginButton] specifies a custom login button widget.
/// ///
@ -44,8 +46,7 @@ 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,
@ -64,34 +65,73 @@ class AuthScreen extends StatefulWidget {
this.formFlex, this.formFlex,
this.beforeTitleFlex, this.beforeTitleFlex,
this.afterTitleFlex, this.afterTitleFlex,
this.isLoading = false, this.maxFormWidth,
Key? key, super.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,
int step, bool enabled)? nextButtonBuilder; /// A custom widget for the button.
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();
@ -109,6 +149,7 @@ class _AuthScreenState extends State<AuthScreen> {
AppBar get _appBar => AppBar get _appBar =>
widget.customAppBar ?? widget.customAppBar ??
AppBar( AppBar(
backgroundColor: const Color(0xffFAF9F6),
title: Text(widget.appBarTitle), title: Text(widget.appBarTitle),
); );
@ -116,9 +157,11 @@ 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,
),
); );
} }
@ -143,31 +186,38 @@ class _AuthScreenState extends State<AuthScreen> {
await widget.onFinish( await widget.onFinish(
values: values, values: values,
onError: (int? pageToReturn) => _pageController.animateToPage( onError: (int? pageToReturn) {
pageToReturn ?? 0, if (pageToReturn == null) {
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) {
bool isStepValid = true; var 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) {
String? validationResult = validator(field.value); var 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;
@ -186,24 +236,20 @@ class _AuthScreenState extends State<AuthScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return widget.isLoading var theme = Theme.of(context);
? const Center( return Scaffold(
child: SizedBox( backgroundColor: widget.customBackgroundColor ?? const Color(0xffFAF9F6),
height: 120,
width: 120,
child: CircularProgressIndicator(),
),
)
: Scaffold(
backgroundColor: widget.customBackgroundColor ?? Colors.white,
appBar: _appBar, appBar: _appBar,
body: Form( body: SafeArea(
child: Form(
key: _formKey, key: _formKey,
child: PageView( child: PageView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
controller: _pageController, controller: _pageController,
children: <Widget>[ children: <Widget>[
for (var i = 0; i < widget.steps.length; i++) for (var currentStep = 0;
currentStep < widget.steps.length;
currentStep++)
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -216,7 +262,7 @@ class _AuthScreenState extends State<AuthScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Expanded( Expanded(
flex: widget.beforeTitleFlex ?? 3, flex: widget.beforeTitleFlex ?? 2,
child: Container(), child: Container(),
), ),
widget.titleWidget!, widget.titleWidget!,
@ -231,108 +277,96 @@ class _AuthScreenState extends State<AuthScreen> {
Expanded( Expanded(
flex: widget.formFlex ?? 3, flex: widget.formFlex ?? 3,
child: Align( child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: widget.maxFormWidth ?? 300,
),
child: Column( child: Column(
children: [ children: [
for (AuthField field for (AuthField field
in widget.steps[i].fields) ...[ in widget.steps[currentStep].fields) ...[
if (field.title != null) ...[ if (field.title != null) ...[
field.title!, wrapWithDefaultStyle(
style: theme.textTheme.headlineLarge!,
widget: field.title!,
),
], ],
field.build(context, () { field.build(context, () {
_validate(i); _validate(currentStep);
}) }),
]
], ],
],
),
), ),
), ),
), ),
Column( Column(
children: [ children: [
Row( SizedBox(
mainAxisAlignment: widget width: MediaQuery.of(context).size.width,
.buttonMainAxisAlignment != child: Padding(
null padding: const EdgeInsets.symmetric(
? widget.buttonMainAxisAlignment! horizontal: 20,
: (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,
i, currentStep,
) == ) ??
null) _stepButton(
? MainAxisAlignment.start buttonText: widget.previousBtnTitle,
: widget.steps.first != widget.steps[i] onTap: () async => onPrevious(),
? MainAxisAlignment.center
: MainAxisAlignment.end,
children: [
if (widget.previousButtonBuilder == null) ...[
if (widget.steps.first != widget.steps[i])
ElevatedButton(
onPressed: onPrevious,
child: Row(
children: [
const Icon(
Icons.arrow_back,
size: 18,
), ),
Padding( const SizedBox(
padding: const EdgeInsets.only( width: 8,
left: 4.0),
child:
Text(widget.previousBtnTitle),
), ),
], ],
),
),
] else if (widget.previousButtonBuilder?.call(
onPrevious,
widget.previousBtnTitle,
i) !=
null) ...[
widget.previousButtonBuilder!.call(
onPrevious, widget.previousBtnTitle, i)!
],
widget.nextButtonBuilder?.call( widget.nextButtonBuilder?.call(
!_formValid !_formValid
? null ? null
: () async { : () async {
await onNext(widget.steps[i]); await onNext(
widget.steps[currentStep],
);
}, },
widget.steps.last == widget.steps[i] widget.steps.last ==
widget.steps[currentStep]
? widget.submitBtnTitle ? widget.submitBtnTitle
: widget.nextBtnTitle, : widget.nextBtnTitle,
i, currentStep,
_formValid, _formValid,
) ?? ) ??
ElevatedButton( _stepButton(
onPressed: !_formValid buttonText: widget.steps.last ==
? null widget.steps[currentStep]
: () async {
await onNext(widget.steps[i]);
},
child: Row(
children: [
Text(
widget.steps.last == widget.steps[i]
? widget.submitBtnTitle ? widget.submitBtnTitle
: widget.nextBtnTitle, : widget.nextBtnTitle,
), onTap: () async {
const Padding( await onNext(
padding: EdgeInsets.only(left: 4.0), widget.steps[currentStep],
child: Icon( );
Icons.arrow_forward, },
size: 18,
),
), ),
], ],
), ),
), ),
], ),
const SizedBox(
height: 8,
), ),
if (widget.loginButton != null) if (widget.loginButton != null)
Padding( Padding(
padding: const EdgeInsets.only(top: 20.0), padding: const EdgeInsets.only(top: 20.0),
child: widget.loginButton!, child: widget.loginButton,
), ),
], ],
), ),
@ -341,6 +375,47 @@ class _AuthScreenState extends State<AuthScreen> {
], ],
), ),
), ),
),
); );
} }
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

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

View file

@ -2,37 +2,52 @@
// //
// 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";
/// A set of options for configuring the registration process in a Flutter application. /// A set of options for configuring the
/// registration process in a Flutter application.
class RegistrationOptions { class RegistrationOptions {
/// Registration options Constructor
RegistrationOptions({ RegistrationOptions({
required this.registrationRepository,
required this.registrationSteps,
required this.afterRegistration, required this.afterRegistration,
this.registrationRepository,
this.registrationSteps,
this.titleFlex, this.titleFlex,
this.formFlex, this.formFlex,
this.beforeTitleFlex, this.beforeTitleFlex,
this.afterTitleFlex, this.afterTitleFlex,
this.registrationTranslations = const RegistrationTranslations.empty(), this.registrationTranslations = const RegistrationTranslations.empty(),
this.onError, this.onError,
this.customAppbarBuilder, this.customAppbarBuilder = _createCustomAppBar,
this.nextButtonBuilder, this.nextButtonBuilder,
this.previousButtonBuilder, this.previousButtonBuilder,
this.buttonMainAxisAlignment, this.buttonMainAxisAlignment,
this.backgroundColor, this.backgroundColor,
this.titleWidget, this.titleWidget,
this.loginButton, this.loginButton,
}); this.maxFormWidth,
this.beforeRegistration,
}) {
if (registrationSteps == null || registrationSteps!.isEmpty) {
steps = RegistrationOptions.getDefaultSteps();
} else {
steps = registrationSteps!;
}
registrationRepository ??= ExampleRegistrationRepository();
}
/// Translations for registration-related messages and prompts. /// Translations for registration-related messages and prompts.
final RegistrationTranslations registrationTranslations; final RegistrationTranslations registrationTranslations;
/// 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 = [];
/// A function that handles errors during registration. /// A function that handles errors during registration.
final int? Function(String error)? onError; final int? Function(String error)? onError;
@ -41,14 +56,19 @@ class RegistrationOptions {
final VoidCallback afterRegistration; final VoidCallback afterRegistration;
/// The repository responsible for registration. /// The repository responsible for registration.
final RegistrationRepository registrationRepository; RegistrationRepository? registrationRepository;
/// A function for customizing the app bar displayed during registration. /// A function for customizing the app bar displayed during registration.
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(Future<void> Function()? onPressed, String label, final Widget Function(
int step, bool enabled)? nextButtonBuilder; Future<void> Function()? onPressed,
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)?
@ -61,10 +81,10 @@ class RegistrationOptions {
final Color? backgroundColor; final Color? backgroundColor;
/// A custom widget for displaying the registration title. /// A custom widget for displaying the registration title.
Widget? titleWidget; final Widget? titleWidget;
/// A custom widget for displaying a login button. /// A custom widget for displaying a login button.
Widget? loginButton; final Widget? loginButton;
/// The number of flex units for the title. /// The number of flex units for the title.
final int? titleFlex; final int? titleFlex;
@ -78,6 +98,12 @@ 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.
@ -103,41 +129,45 @@ class RegistrationOptions {
/// [initialEmail] initial value for email input. /// [initialEmail] initial value for email input.
static List<AuthStep> getDefaultSteps({ static List<AuthStep> getDefaultSteps({
TextEditingController? emailController, TextEditingController? emailController,
TextEditingController? pass1Controller, TextEditingController? passController,
bool pass1Hidden = true, bool passHidden = true,
TextEditingController? pass2Controller, // ignore: avoid_positional_boolean_parameters
bool pass2Hidden = true,
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,
}) { }) =>
var password1 = ''; [
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( Padding(
padding: const EdgeInsets.only(top: 24.0, bottom: 12.0), padding: const EdgeInsets.only(top: 180),
child: Text( child: Text(
translations.defaultEmailTitle, translations.defaultEmailTitle,
style: const TextStyle(fontWeight: FontWeight.bold),
), ),
), ),
textInputType: TextInputType.emailAddress,
textFieldDecoration: InputDecoration( textFieldDecoration: InputDecoration(
hintStyle: hintStyle,
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(),
focusedBorder: const OutlineInputBorder(),
), ),
textStyle: textStyle, textStyle: textStyle,
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,
@ -147,69 +177,52 @@ class RegistrationOptions {
? null ? null
: translations.defaultEmailValidatorMessage, : translations.defaultEmailValidatorMessage,
], ],
) ),
], ],
), ),
AuthStep( AuthStep(
fields: [ fields: [
AuthPassField( AuthPassField(
name: 'password1', name: "password",
textEditingController: pass1Controller, textEditingController: passController,
title: titleBuilder?.call(translations.defaultPassword1Title) ?? title: titleBuilder?.call(translations.defaultPasswordTitle) ??
Padding( Padding(
padding: const EdgeInsets.only(top: 24.0, bottom: 12.0), padding: const EdgeInsets.only(top: 180),
child: Text( child: Text(
translations.defaultPassword1Title, translations.defaultPasswordTitle,
style: const TextStyle(fontWeight: FontWeight.bold),
), ),
), ),
textFieldDecoration: InputDecoration( textFieldDecoration: InputDecoration(
label: labelBuilder?.call(translations.defaultPassword1Label), hintStyle: hintStyle,
hintText: translations.defaultPassword1Hint, contentPadding: const EdgeInsets.symmetric(horizontal: 8),
), label: labelBuilder?.call(translations.defaultPasswordLabel),
textStyle: textStyle, hintText: translations.defaultPasswordHint,
validators: [ border: const OutlineInputBorder(),
(value) => (value == null || value.isEmpty) focusedBorder: const OutlineInputBorder(),
? translations.defaultPassword1ValidatorMessage
: null,
],
onChange: (value) {
password1 = value;
},
),
AuthPassField(
name: 'password2',
textEditingController: pass2Controller,
title: titleBuilder?.call(translations.defaultPassword2Title) ??
Padding(
padding: const EdgeInsets.only(top: 24.0, bottom: 12.0),
child: Text(
translations.defaultPassword2Title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
textFieldDecoration: InputDecoration(
label: labelBuilder?.call(translations.defaultPassword2Label),
hintText: translations.defaultPassword2Hint,
), ),
padding: const EdgeInsets.symmetric(vertical: 20),
textStyle: textStyle, textStyle: textStyle,
validators: [ validators: [
(value) { (value) {
if (pass1Controller != null) { // ignore: avoid_dynamic_calls
if (value != pass1Controller.value.text) { if (value == null || value.isEmpty) {
return translations.defaultPassword2ValidatorMessage; return translations.defaultPasswordValidatorMessage;
}
} else {
if (value != password1) {
return translations.defaultPassword2ValidatorMessage;
} }
// ignore: avoid_dynamic_calls
if (value.length < 6) {
return translations.defaultPasswordToShortValidatorMessage;
} }
return null; return null;
} },
], ],
), ),
], ],
), ),
]; ];
}
} }
AppBar _createCustomAppBar(String title) => AppBar(
iconTheme: const IconThemeData(color: Colors.black, size: 16),
title: Text(title),
backgroundColor: Colors.transparent,
);

View file

@ -2,8 +2,10 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
/// Holds all the translations for the standard elements on the registration screen. /// Holds all the translations for the standard elements
/// 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,
@ -15,56 +17,78 @@ class RegistrationTranslations {
required this.defaultEmailHint, required this.defaultEmailHint,
required this.defaultEmailEmpty, required this.defaultEmailEmpty,
required this.defaultEmailValidatorMessage, required this.defaultEmailValidatorMessage,
required this.defaultPassword1Title, required this.defaultPasswordTitle,
required this.defaultPassword1Label, required this.defaultPasswordLabel,
required this.defaultPassword1Hint, required this.defaultPasswordHint,
required this.defaultPassword1ValidatorMessage, required this.defaultPasswordValidatorMessage,
required this.defaultPassword2Title, required this.defaultPasswordToShortValidatorMessage,
required this.defaultPassword2Label,
required this.defaultPassword2Hint,
required this.defaultPassword2ValidatorMessage,
}); });
/// Constructs a [RegistrationTranslations] object with empty strings.
const RegistrationTranslations.empty() const RegistrationTranslations.empty()
: title = 'Register', : title = "",
registerBtn = 'Register', registerBtn = "Register",
previousStepBtn = 'Previous', previousStepBtn = "Previous",
nextStepBtn = 'Next', nextStepBtn = "Next",
closeBtn = 'Close', closeBtn = "Close",
defaultEmailTitle = 'What is your email?', defaultEmailTitle = "enter your email address",
defaultEmailLabel = '', defaultEmailLabel = "",
defaultEmailHint = 'john.doe@domain.com', defaultEmailHint = "Email address",
defaultEmailEmpty = 'Enter your email', defaultEmailEmpty = "Please enter your email address.",
defaultEmailValidatorMessage = 'Enter a valid email address', defaultEmailValidatorMessage = "Please enter a valid email address.",
defaultPassword1Title = 'Enter a password', defaultPasswordTitle = "choose a password",
defaultPassword1Label = '', defaultPasswordLabel = "",
defaultPassword1Hint = '', defaultPasswordHint = "Password",
defaultPassword1ValidatorMessage = 'Enter a valid password', defaultPasswordValidatorMessage = "Enter a valid password",
defaultPassword2Title = 'Re-enter password', defaultPasswordToShortValidatorMessage =
defaultPassword2Label = '', "Password needs to be at least 6 characters long";
defaultPassword2Hint = '',
defaultPassword2ValidatorMessage = 'Passwords have to be equal';
/// The title of the registration screen.
final String title; final String title;
final String registerBtn;
final String previousStepBtn;
final String nextStepBtn;
final String closeBtn;
final String defaultEmailTitle;
final String defaultEmailLabel;
final String defaultEmailHint;
final String defaultEmailEmpty;
final String defaultEmailValidatorMessage;
final String defaultPassword1Title;
final String defaultPassword1Label;
final String defaultPassword1Hint;
final String defaultPassword1ValidatorMessage;
final String defaultPassword2Title;
final String defaultPassword2Label;
final String defaultPassword2Hint;
final String defaultPassword2ValidatorMessage;
// create a copywith /// The text for the registration button.
final String registerBtn;
/// The text for the previous step button.
final String previousStepBtn;
/// The text for the next step button.
final String nextStepBtn;
/// The text for the close button.
final String closeBtn;
/// The title for the default email field.
final String defaultEmailTitle;
/// The label for the default email field.
final String defaultEmailLabel;
/// The hint for the default email field.
final String defaultEmailHint;
/// The message for an empty default email field.
final String defaultEmailEmpty;
/// The message for an invalid default email field.
final String defaultEmailValidatorMessage;
/// The title for the default password field.
final String defaultPasswordTitle;
/// The label for the default password field.
final String defaultPasswordLabel;
/// The hint for the default password field.
final String defaultPasswordHint;
/// The message for an invalid default password field.
final String defaultPasswordValidatorMessage;
/// The message for a default password that is too short.
final String defaultPasswordToShortValidatorMessage;
/// create a copywith
RegistrationTranslations copyWith({ RegistrationTranslations copyWith({
String? title, String? title,
String? registerBtn, String? registerBtn,
@ -76,16 +100,13 @@ class RegistrationTranslations {
String? defaultEmailHint, String? defaultEmailHint,
String? defaultEmailEmpty, String? defaultEmailEmpty,
String? defaultEmailValidatorMessage, String? defaultEmailValidatorMessage,
String? defaultPassword1Title, String? defaultPasswordTitle,
String? defaultPassword1Label, String? defaultPasswordLabel,
String? defaultPassword1Hint, String? defaultPasswordHint,
String? defaultPassword1ValidatorMessage, String? defaultPasswordValidatorMessage,
String? defaultPassword2Title, String? defaultPasswordToShortValidatorMessage,
String? defaultPassword2Label, }) =>
String? defaultPassword2Hint, RegistrationTranslations(
String? defaultPassword2ValidatorMessage,
}) {
return 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,
@ -97,20 +118,13 @@ class RegistrationTranslations {
defaultEmailEmpty: defaultEmailEmpty ?? this.defaultEmailEmpty, defaultEmailEmpty: defaultEmailEmpty ?? this.defaultEmailEmpty,
defaultEmailValidatorMessage: defaultEmailValidatorMessage:
defaultEmailValidatorMessage ?? this.defaultEmailValidatorMessage, defaultEmailValidatorMessage ?? this.defaultEmailValidatorMessage,
defaultPassword1Title: defaultPasswordTitle: defaultPasswordTitle ?? this.defaultPasswordTitle,
defaultPassword1Title ?? this.defaultPassword1Title, defaultPasswordLabel: defaultPasswordLabel ?? this.defaultPasswordLabel,
defaultPassword1Label: defaultPasswordHint: defaultPasswordHint ?? this.defaultPasswordHint,
defaultPassword1Label ?? this.defaultPassword1Label, defaultPasswordValidatorMessage: defaultPasswordValidatorMessage ??
defaultPassword1Hint: defaultPassword1Hint ?? this.defaultPassword1Hint, this.defaultPasswordValidatorMessage,
defaultPassword1ValidatorMessage: defaultPassword1ValidatorMessage ?? defaultPasswordToShortValidatorMessage:
this.defaultPassword1ValidatorMessage, defaultPasswordToShortValidatorMessage ??
defaultPassword2Title: this.defaultPasswordToShortValidatorMessage,
defaultPassword2Title ?? this.defaultPassword2Title,
defaultPassword2Label:
defaultPassword2Label ?? this.defaultPassword2Label,
defaultPassword2Hint: defaultPassword2Hint ?? this.defaultPassword2Hint,
defaultPassword2ValidatorMessage: defaultPassword2ValidatorMessage ??
this.defaultPassword2ValidatorMessage,
); );
}
} }

View file

@ -2,14 +2,19 @@
// //
// 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,11 +22,14 @@ 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 left side of the boolean widget. /// [leftWidget] is a widget to be displayed on the
/// left side of the boolean widget.
/// ///
/// [rightWidget] is a widget to be displayed on the right side of the boolean widget. /// [rightWidget] is a widget to be displayed on the
/// right side of the boolean widget.
/// ///
/// [onChange] is a callback function triggered when the value of the field changes. /// [onChange] is a callback function triggered when
/// the value of the field changes.
AuthBoolField({ AuthBoolField({
required super.name, required super.name,
required this.widgetType, required this.widgetType,
@ -38,18 +41,26 @@ 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) =>
return FlutterFormInputBool( 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) {
@ -64,5 +75,4 @@ class AuthBoolField extends AuthField {
leftWidget: leftWidget, leftWidget: leftWidget,
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,23 +41,25 @@ class AuthDropdownField extends AuthField {
final Icon icon; final Icon icon;
@override @override
Widget build(BuildContext context, Function onValueChanged) { Widget build(BuildContext context, Function onValueChanged) => Padding(
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.map((String value) { items: items
return DropdownMenuItem<String>( .map(
(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) {
@ -73,5 +75,4 @@ class AuthDropdownField extends AuthField {
}, },
), ),
); );
}
} }

View file

@ -2,12 +2,14 @@
// //
// 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() { String toString() => message;
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,11 +14,13 @@ 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 value of the field changes (optional). /// [onValueChanged] is a callback function triggered when the
/// 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 functions for the field (optional). /// [validators] defines a list of validation
/// functions for the field (optional).
AuthField({ AuthField({
required this.name, required this.name,
required this.value, required this.value,
@ -46,6 +48,7 @@ abstract class AuthField<T> {
/// ///
/// [context] The build context. /// [context] The build context.
/// ///
/// [onValueChanged] A function to be called when the value of the field changes. /// [onValueChanged] A function to be called when
/// 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,25 +18,31 @@ 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 functions for the field (optional). /// [validators] defines a list of validation
/// functions for the field (optional).
/// ///
/// [value] specifies the initial value of the field (default is an empty string). /// [value] specifies the initial value of the
/// 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 the value of the field changes. /// [onChange] is a callback function triggered when
/// the value of the field changes.
/// ///
/// [iconSize] specifies the size of the icon displayed with the password input (optional). /// [iconSize] specifies the size of the icon displayed
/// with the password input (optional).
/// ///
/// [textFieldDecoration] defines the decoration for the password input field (optional). /// [textFieldDecoration] defines the decoration for the
/// password input field (optional).
/// ///
/// [padding] defines the padding around the password input field (default is EdgeInsets.all(8.0)). /// [padding] defines the padding around the password input
/// field (default is EdgeInsets.all(8.0)).
AuthPassField({ AuthPassField({
required super.name, required super.name,
TextEditingController? textEditingController, this.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,
@ -44,15 +50,26 @@ 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) { Widget build(BuildContext context, Function onValueChanged) => Padding(
return Padding(
padding: padding, padding: padding,
child: FlutterFormInputPassword( child: FlutterFormInputPassword(
style: textStyle, style: textStyle,
@ -61,6 +78,7 @@ 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) {
@ -75,5 +93,4 @@ class AuthPassField extends AuthField {
}, },
), ),
); );
}
} }

View file

@ -2,12 +2,15 @@
// //
// 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,49 +17,68 @@ 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 functions for the field (optional). /// [validators] defines a list of validation
/// functions for the field (optional).
/// ///
/// [value] specifies the initial value of the field (default is an empty string). /// [value] specifies the initial value of the
/// 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 when the value of the field changes. /// [onChange] is a callback function triggered
/// when the value of the field changes.
/// ///
/// [textFieldDecoration] defines the decoration for the text input field (optional). /// [textFieldDecoration] defines the decoration
/// for the text input field (optional).
/// ///
/// [padding] defines the padding around the text input field (default is EdgeInsets.all(8.0)). /// [padding] defines the padding around the text
/// 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) { Widget build(BuildContext context, Function onValueChanged) => Padding(
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) {
@ -74,5 +93,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,
Key? key, super.key,
}) : super(key: key); });
/// The registration options. /// The registration options.
final RegistrationOptions registrationOptions; final RegistrationOptions registrationOptions;
@ -23,19 +23,15 @@ 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 {
setState(() { await widget.registrationOptions.beforeRegistration?.call();
_isLoading = true;
});
var registered = await widget.registrationOptions.registrationRepository var registered = await widget.registrationOptions.registrationRepository!
.register(values); .register(values);
if (registered == null) { if (registered == null) {
@ -45,12 +41,8 @@ class RegistrationScreenState extends State<RegistrationScreen> {
onError(pageToReturn); onError(pageToReturn);
} }
} catch (e) { } on Exception catch (_) {
onError(0); onError(null);
} finally {
setState(() {
_isLoading = false;
});
} }
} }
@ -59,7 +51,7 @@ class RegistrationScreenState extends State<RegistrationScreen> {
var translations = widget.registrationOptions.registrationTranslations; var translations = widget.registrationOptions.registrationTranslations;
return AuthScreen( return AuthScreen(
steps: widget.registrationOptions.registrationSteps, steps: widget.registrationOptions.steps,
customAppBar: widget.registrationOptions.customAppbarBuilder?.call( customAppBar: widget.registrationOptions.customAppbarBuilder?.call(
translations.title, translations.title,
), ),
@ -75,11 +67,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,8 +2,10 @@
// //
// 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,20 +4,19 @@
name: flutter_registration name: flutter_registration
description: A Flutter Registration package description: A Flutter Registration package
version: 2.0.2 version: 3.0.0
repository: https://github.com/Iconica-Development/flutter_registration repository: https://github.com/Iconica-Development/flutter_registration
publish_to: none publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment: environment:
sdk: ">=2.18.0 <3.0.0" sdk: ">=3.0.0 <4.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
flutter_input_library: flutter_input_library:
git: hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
url: https://github.com/Iconica-Development/flutter_input_library version: ^3.6.0
ref: 3.1.0
flutter: flutter:
sdk: flutter sdk: flutter
@ -27,6 +26,9 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 flutter_iconica_analysis:
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);
}); });
} }