mirror of
https://github.com/Iconica-Development/flutter_registration.git
synced 2025-05-18 13:13:44 +02:00
feat: default styling
This commit is contained in:
parent
cf6e30abec
commit
4bad032588
21 changed files with 671 additions and 521 deletions
|
@ -3,7 +3,10 @@ SPDX-FileCopyrightText: 2022 Iconica
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
||||
|
|
|
@ -21,7 +21,7 @@ void main() {
|
|||
}
|
||||
|
||||
class FlutterRegistrationDemo extends StatefulWidget {
|
||||
const FlutterRegistrationDemo({Key? key}) : super(key: key);
|
||||
const FlutterRegistrationDemo({super.key});
|
||||
|
||||
@override
|
||||
State<FlutterRegistrationDemo> createState() =>
|
||||
|
@ -86,8 +86,7 @@ class _FlutterRegistrationDemoState extends State<FlutterRegistrationDemo> {
|
|||
}
|
||||
|
||||
class ProtectedScreen extends StatelessWidget {
|
||||
const ProtectedScreen({Key? key}) : super(key: key);
|
||||
|
||||
const ProtectedScreen({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
|
|
|
@ -66,10 +66,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: flutter_input_library
|
||||
sha256: "346823caa633d47ad21d8ce92f3c2ed460619fd5447999c35cb171da5e86af06"
|
||||
sha256: db8d9d57c31f166ed7c4ec3d060d18891533c57877a30c6c2668231efa1a44f8
|
||||
url: "https://forgejo.internal.iconica.nl/api/packages/internal/pub/"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
version: "3.6.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// 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;
|
||||
|
||||
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'
|
||||
export "package:flutter_input_library/flutter_input_library.dart"
|
||||
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";
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_registration/flutter_registration.dart';
|
||||
import "dart:async";
|
||||
import "dart:collection";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_registration/flutter_registration.dart";
|
||||
|
||||
/// A widget for handling multi-step authentication processes.
|
||||
class AuthScreen extends StatefulWidget {
|
||||
|
@ -13,7 +13,8 @@ class AuthScreen extends StatefulWidget {
|
|||
///
|
||||
/// [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.
|
||||
///
|
||||
|
@ -33,7 +34,8 @@ class AuthScreen extends StatefulWidget {
|
|||
///
|
||||
/// [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.
|
||||
///
|
||||
|
@ -44,8 +46,7 @@ class AuthScreen extends StatefulWidget {
|
|||
/// [beforeTitleFlex] specifies the flex value before the title widget.
|
||||
///
|
||||
/// [afterTitleFlex] specifies the flex value after the title widget.
|
||||
///
|
||||
/// [isLoading] indicates whether the screen is in a loading state.
|
||||
|
||||
const AuthScreen({
|
||||
required this.appBarTitle,
|
||||
required this.steps,
|
||||
|
@ -64,35 +65,72 @@ class AuthScreen extends StatefulWidget {
|
|||
this.formFlex,
|
||||
this.beforeTitleFlex,
|
||||
this.afterTitleFlex,
|
||||
this.isLoading = false,
|
||||
this.maxFormWidth,
|
||||
Key? key,
|
||||
}) : assert(steps.length > 0, 'At least one step is required'),
|
||||
super(key: key);
|
||||
super.key,
|
||||
}) : assert(steps.length > 0, "At least one step is required");
|
||||
|
||||
/// The title of the app bar.
|
||||
final String appBarTitle;
|
||||
|
||||
/// A function called upon completion of the authentication process.
|
||||
final Future<void> Function({
|
||||
required HashMap<String, dynamic> values,
|
||||
required void Function(int? pageToReturn) onError,
|
||||
}) onFinish;
|
||||
|
||||
/// The authentication steps to be completed.
|
||||
final List<AuthStep> steps;
|
||||
|
||||
/// The title of the submit button.
|
||||
final String submitBtnTitle;
|
||||
|
||||
/// The title of the next button.
|
||||
final String nextBtnTitle;
|
||||
|
||||
/// The title of the previous button.
|
||||
final String previousBtnTitle;
|
||||
|
||||
/// A custom app bar widget.
|
||||
final AppBar? customAppBar;
|
||||
|
||||
/// The alignment of the buttons.
|
||||
final MainAxisAlignment? buttonMainAxisAlignment;
|
||||
|
||||
/// The background color of the screen.
|
||||
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)?
|
||||
previousButtonBuilder;
|
||||
|
||||
/// A custom widget for the title.
|
||||
final Widget? titleWidget;
|
||||
|
||||
/// A custom widget for the login button.
|
||||
final Widget? loginButton;
|
||||
|
||||
/// The flex value for the title widget.
|
||||
final int? titleFlex;
|
||||
|
||||
/// The flex value for the form widget.
|
||||
final int? formFlex;
|
||||
|
||||
/// The flex value before the title widget.
|
||||
final int? beforeTitleFlex;
|
||||
|
||||
/// The flex value after the title widget.
|
||||
final int? afterTitleFlex;
|
||||
final bool isLoading;
|
||||
|
||||
/// The maximum width of the form.
|
||||
final double? maxFormWidth;
|
||||
|
||||
@override
|
||||
|
@ -119,9 +157,11 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||
void onPrevious() {
|
||||
FocusScope.of(context).unfocus();
|
||||
_validate(_pageController.page!.toInt() - 1);
|
||||
_pageController.previousPage(
|
||||
duration: _animationDuration,
|
||||
curve: _animationCurve,
|
||||
unawaited(
|
||||
_pageController.previousPage(
|
||||
duration: _animationDuration,
|
||||
curve: _animationCurve,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -146,31 +186,38 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||
|
||||
await widget.onFinish(
|
||||
values: values,
|
||||
onError: (int? pageToReturn) => _pageController.animateToPage(
|
||||
pageToReturn ?? 0,
|
||||
duration: _animationDuration,
|
||||
curve: _animationCurve,
|
||||
),
|
||||
onError: (int? pageToReturn) {
|
||||
if (pageToReturn == null) {
|
||||
return;
|
||||
}
|
||||
_pageController.animateToPage(
|
||||
pageToReturn,
|
||||
duration: _animationDuration,
|
||||
curve: _animationCurve,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
} else {
|
||||
_validate(_pageController.page!.toInt() + 1);
|
||||
_pageController.nextPage(
|
||||
duration: _animationDuration,
|
||||
curve: _animationCurve,
|
||||
unawaited(
|
||||
_pageController.nextPage(
|
||||
duration: _animationDuration,
|
||||
curve: _animationCurve,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates the current step.
|
||||
void _validate(int currentPage) {
|
||||
bool isStepValid = true;
|
||||
var isStepValid = true;
|
||||
|
||||
// Loop through each field in the current step
|
||||
for (var field in widget.steps[currentPage].fields) {
|
||||
for (var validator in field.validators) {
|
||||
String? validationResult = validator(field.value);
|
||||
var validationResult = validator(field.value);
|
||||
if (validationResult != null) {
|
||||
// If any validator returns an error, mark step as invalid and break
|
||||
isStepValid = false;
|
||||
|
@ -189,203 +236,186 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.isLoading
|
||||
? const Center(
|
||||
child: SizedBox(
|
||||
height: 120,
|
||||
width: 120,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
: Scaffold(
|
||||
backgroundColor:
|
||||
widget.customBackgroundColor ?? const Color(0xffFAF9F6),
|
||||
appBar: _appBar,
|
||||
body: SafeArea(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _pageController,
|
||||
children: <Widget>[
|
||||
for (var i = 0; i < widget.steps.length; i++)
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (widget.titleWidget != null) ...[
|
||||
var theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: widget.customBackgroundColor ?? const Color(0xffFAF9F6),
|
||||
appBar: _appBar,
|
||||
body: SafeArea(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _pageController,
|
||||
children: <Widget>[
|
||||
for (var currentStep = 0;
|
||||
currentStep < widget.steps.length;
|
||||
currentStep++)
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (widget.titleWidget != null) ...[
|
||||
Expanded(
|
||||
flex: widget.titleFlex ?? 1,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: widget.titleFlex ?? 1,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: widget.beforeTitleFlex ?? 2,
|
||||
child: Container(),
|
||||
),
|
||||
widget.titleWidget!,
|
||||
Expanded(
|
||||
flex: widget.afterTitleFlex ?? 2,
|
||||
child: Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
flex: widget.beforeTitleFlex ?? 2,
|
||||
child: Container(),
|
||||
),
|
||||
widget.titleWidget!,
|
||||
Expanded(
|
||||
flex: widget.afterTitleFlex ?? 2,
|
||||
child: Container(),
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
flex: widget.formFlex ?? 3,
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: widget.maxFormWidth ?? 300,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
for (AuthField field
|
||||
in widget.steps[i].fields) ...[
|
||||
if (field.title != null) ...[
|
||||
field.title!,
|
||||
],
|
||||
field.build(context, () {
|
||||
_validate(i);
|
||||
})
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
flex: widget.formFlex ?? 3,
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: widget.maxFormWidth ?? 300,
|
||||
),
|
||||
Column(
|
||||
child: 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
for (AuthField field
|
||||
in widget.steps[currentStep].fields) ...[
|
||||
if (field.title != null) ...[
|
||||
wrapWithDefaultStyle(
|
||||
style: theme.textTheme.headlineLarge!,
|
||||
widget: field.title!,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.loginButton != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: widget.loginButton!,
|
||||
),
|
||||
field.build(context, () {
|
||||
_validate(currentStep);
|
||||
}),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import 'dart:collection';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_registration/flutter_registration.dart';
|
||||
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');
|
||||
debugPrint("register $values");
|
||||
return Future.value(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,18 +2,20 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'dart:async';
|
||||
import "dart:async";
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_registration/flutter_registration.dart';
|
||||
import 'package:flutter_registration/src/config/example_registration_repository.dart';
|
||||
import "package:flutter/material.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 {
|
||||
/// Registration options Constructor
|
||||
RegistrationOptions({
|
||||
required this.afterRegistration,
|
||||
this.registrationRepository,
|
||||
this.registrationSteps,
|
||||
required this.afterRegistration,
|
||||
this.titleFlex,
|
||||
this.formFlex,
|
||||
this.beforeTitleFlex,
|
||||
|
@ -28,6 +30,7 @@ class RegistrationOptions {
|
|||
this.titleWidget,
|
||||
this.loginButton,
|
||||
this.maxFormWidth,
|
||||
this.beforeRegistration,
|
||||
}) {
|
||||
if (registrationSteps == null || registrationSteps!.isEmpty) {
|
||||
steps = RegistrationOptions.getDefaultSteps();
|
||||
|
@ -43,6 +46,7 @@ class RegistrationOptions {
|
|||
/// The steps involved in the registration process.
|
||||
final List<AuthStep>? registrationSteps;
|
||||
|
||||
/// The steps involved in the registration process.
|
||||
List<AuthStep> steps = [];
|
||||
|
||||
/// A function that handles errors during registration.
|
||||
|
@ -58,8 +62,13 @@ class RegistrationOptions {
|
|||
final AppBar Function(String title)? customAppbarBuilder;
|
||||
|
||||
/// A function for customizing the "Next" button.
|
||||
final Widget Function(Future<void> Function()? onPressed, String label,
|
||||
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 function for customizing the "Previous" button.
|
||||
final Widget? Function(VoidCallback onPressed, String label, int step)?
|
||||
|
@ -92,6 +101,9 @@ class RegistrationOptions {
|
|||
/// 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.
|
||||
///
|
||||
/// [emailController] controller for email input.
|
||||
|
@ -119,94 +131,98 @@ class RegistrationOptions {
|
|||
TextEditingController? emailController,
|
||||
TextEditingController? passController,
|
||||
bool passHidden = true,
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
Function(bool mainPass, bool value)? passHideOnChange,
|
||||
RegistrationTranslations translations =
|
||||
const RegistrationTranslations.empty(),
|
||||
Function(String title)? titleBuilder,
|
||||
Function(String label)? labelBuilder,
|
||||
TextStyle? textStyle,
|
||||
TextStyle? hintStyle,
|
||||
String? initialEmail,
|
||||
}) {
|
||||
return [
|
||||
AuthStep(
|
||||
fields: [
|
||||
AuthTextField(
|
||||
name: 'email',
|
||||
textEditingController: emailController,
|
||||
value: initialEmail ?? '',
|
||||
title: titleBuilder?.call(translations.defaultEmailTitle) ??
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 180),
|
||||
child: Text(
|
||||
'Enter your e-mail',
|
||||
style: TextStyle(
|
||||
color: Color(0xff71C6D1),
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 24,
|
||||
}) =>
|
||||
[
|
||||
AuthStep(
|
||||
fields: [
|
||||
AuthTextField(
|
||||
name: "email",
|
||||
textEditingController: emailController,
|
||||
value: initialEmail ?? "",
|
||||
title: titleBuilder?.call(translations.defaultEmailTitle) ??
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 180),
|
||||
child: Text(
|
||||
translations.defaultEmailTitle,
|
||||
),
|
||||
),
|
||||
),
|
||||
textFieldDecoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
label: labelBuilder?.call(translations.defaultEmailLabel),
|
||||
hintText: translations.defaultEmailHint,
|
||||
border: const OutlineInputBorder(),
|
||||
textInputType: TextInputType.emailAddress,
|
||||
textFieldDecoration: InputDecoration(
|
||||
hintStyle: hintStyle,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
label: labelBuilder?.call(translations.defaultEmailLabel),
|
||||
hintText: translations.defaultEmailHint,
|
||||
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(vertical: 20),
|
||||
validators: [
|
||||
(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,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
],
|
||||
),
|
||||
AuthStep(
|
||||
fields: [
|
||||
AuthPassField(
|
||||
name: "password",
|
||||
textEditingController: passController,
|
||||
title: titleBuilder?.call(translations.defaultPasswordTitle) ??
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 180),
|
||||
child: Text(
|
||||
translations.defaultPasswordTitle,
|
||||
),
|
||||
),
|
||||
),
|
||||
textFieldDecoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
label: labelBuilder?.call(translations.defaultPasswordLabel),
|
||||
hintText: translations.defaultPasswordHint,
|
||||
border: const OutlineInputBorder(),
|
||||
textFieldDecoration: InputDecoration(
|
||||
hintStyle: hintStyle,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
label: labelBuilder?.call(translations.defaultPasswordLabel),
|
||||
hintText: translations.defaultPasswordHint,
|
||||
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(vertical: 20),
|
||||
textStyle: textStyle,
|
||||
validators: [
|
||||
(value) => (value == null || value.isEmpty)
|
||||
? translations.defaultPasswordValidatorMessage
|
||||
: null,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
AppBar _createCustomAppBar(String title) {
|
||||
return AppBar(
|
||||
title: Text(title),
|
||||
backgroundColor: const Color(0xffFAF9F6),
|
||||
);
|
||||
}
|
||||
AppBar _createCustomAppBar(String title) => AppBar(
|
||||
iconTheme: const IconThemeData(color: Colors.black, size: 16),
|
||||
title: Text(title),
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
//
|
||||
// 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 {
|
||||
/// Constructs a [RegistrationTranslations] object.
|
||||
const RegistrationTranslations({
|
||||
required this.title,
|
||||
required this.registerBtn,
|
||||
|
@ -19,40 +21,74 @@ class RegistrationTranslations {
|
|||
required this.defaultPasswordLabel,
|
||||
required this.defaultPasswordHint,
|
||||
required this.defaultPasswordValidatorMessage,
|
||||
required this.defaultPasswordToShortValidatorMessage,
|
||||
});
|
||||
|
||||
/// Constructs a [RegistrationTranslations] object with empty strings.
|
||||
const RegistrationTranslations.empty()
|
||||
: title = '',
|
||||
registerBtn = 'Register',
|
||||
previousStepBtn = 'Previous',
|
||||
nextStepBtn = 'Next',
|
||||
closeBtn = 'Close',
|
||||
defaultEmailTitle = 'What is your email?',
|
||||
defaultEmailLabel = '',
|
||||
defaultEmailHint = 'Email address',
|
||||
defaultEmailEmpty = 'Please enter your email address.',
|
||||
defaultEmailValidatorMessage = 'Please enter a valid email address.',
|
||||
defaultPasswordTitle = 'Choose a password',
|
||||
defaultPasswordLabel = 'password',
|
||||
defaultPasswordHint = '',
|
||||
defaultPasswordValidatorMessage = 'Enter a valid password';
|
||||
: title = "",
|
||||
registerBtn = "Register",
|
||||
previousStepBtn = "Previous",
|
||||
nextStepBtn = "Next",
|
||||
closeBtn = "Close",
|
||||
defaultEmailTitle = "enter your email address",
|
||||
defaultEmailLabel = "",
|
||||
defaultEmailHint = "Email address",
|
||||
defaultEmailEmpty = "Please enter your email address.",
|
||||
defaultEmailValidatorMessage = "Please enter a valid email address.",
|
||||
defaultPasswordTitle = "choose a password",
|
||||
defaultPasswordLabel = "",
|
||||
defaultPasswordHint = "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;
|
||||
|
||||
/// 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;
|
||||
|
||||
// create a copywith
|
||||
/// The message for a default password that is too short.
|
||||
final String defaultPasswordToShortValidatorMessage;
|
||||
|
||||
/// create a copywith
|
||||
RegistrationTranslations copyWith({
|
||||
String? title,
|
||||
String? registerBtn,
|
||||
|
@ -68,24 +104,27 @@ class RegistrationTranslations {
|
|||
String? defaultPasswordLabel,
|
||||
String? defaultPasswordHint,
|
||||
String? defaultPasswordValidatorMessage,
|
||||
}) {
|
||||
return RegistrationTranslations(
|
||||
title: title ?? this.title,
|
||||
registerBtn: registerBtn ?? this.registerBtn,
|
||||
previousStepBtn: previousStepBtn ?? this.previousStepBtn,
|
||||
nextStepBtn: nextStepBtn ?? this.nextStepBtn,
|
||||
closeBtn: closeBtn ?? this.closeBtn,
|
||||
defaultEmailTitle: defaultEmailTitle ?? this.defaultEmailTitle,
|
||||
defaultEmailLabel: defaultEmailLabel ?? this.defaultEmailLabel,
|
||||
defaultEmailHint: defaultEmailHint ?? this.defaultEmailHint,
|
||||
defaultEmailEmpty: defaultEmailEmpty ?? this.defaultEmailEmpty,
|
||||
defaultEmailValidatorMessage:
|
||||
defaultEmailValidatorMessage ?? this.defaultEmailValidatorMessage,
|
||||
defaultPasswordTitle: defaultPasswordTitle ?? this.defaultPasswordTitle,
|
||||
defaultPasswordLabel: defaultPasswordLabel ?? this.defaultPasswordLabel,
|
||||
defaultPasswordHint: defaultPasswordHint ?? this.defaultPasswordHint,
|
||||
defaultPasswordValidatorMessage: defaultPasswordValidatorMessage ??
|
||||
this.defaultPasswordValidatorMessage,
|
||||
);
|
||||
}
|
||||
String? defaultPasswordToShortValidatorMessage,
|
||||
}) =>
|
||||
RegistrationTranslations(
|
||||
title: title ?? this.title,
|
||||
registerBtn: registerBtn ?? this.registerBtn,
|
||||
previousStepBtn: previousStepBtn ?? this.previousStepBtn,
|
||||
nextStepBtn: nextStepBtn ?? this.nextStepBtn,
|
||||
closeBtn: closeBtn ?? this.closeBtn,
|
||||
defaultEmailTitle: defaultEmailTitle ?? this.defaultEmailTitle,
|
||||
defaultEmailLabel: defaultEmailLabel ?? this.defaultEmailLabel,
|
||||
defaultEmailHint: defaultEmailHint ?? this.defaultEmailHint,
|
||||
defaultEmailEmpty: defaultEmailEmpty ?? this.defaultEmailEmpty,
|
||||
defaultEmailValidatorMessage:
|
||||
defaultEmailValidatorMessage ?? this.defaultEmailValidatorMessage,
|
||||
defaultPasswordTitle: defaultPasswordTitle ?? this.defaultPasswordTitle,
|
||||
defaultPasswordLabel: defaultPasswordLabel ?? this.defaultPasswordLabel,
|
||||
defaultPasswordHint: defaultPasswordHint ?? this.defaultPasswordHint,
|
||||
defaultPasswordValidatorMessage: defaultPasswordValidatorMessage ??
|
||||
this.defaultPasswordValidatorMessage,
|
||||
defaultPasswordToShortValidatorMessage:
|
||||
defaultPasswordToShortValidatorMessage ??
|
||||
this.defaultPasswordToShortValidatorMessage,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,14 +2,19 @@
|
|||
//
|
||||
// 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 {
|
||||
/// Constructs an [AuthAction] object.
|
||||
AuthAction({
|
||||
required this.title,
|
||||
required this.onPress,
|
||||
});
|
||||
|
||||
/// The title of the action.
|
||||
final String title;
|
||||
|
||||
/// A callback function triggered when the action is pressed.
|
||||
final VoidCallback onPress;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_input_library/flutter_input_library.dart';
|
||||
import 'package:flutter_registration/flutter_registration.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_input_library/flutter_input_library.dart";
|
||||
import "package:flutter_registration/flutter_registration.dart";
|
||||
|
||||
/// A field for capturing boolean values in a Flutter form.
|
||||
///
|
||||
|
@ -22,11 +22,14 @@ class AuthBoolField extends AuthField {
|
|||
///
|
||||
/// [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({
|
||||
required super.name,
|
||||
required this.widgetType,
|
||||
|
@ -38,31 +41,38 @@ class AuthBoolField extends AuthField {
|
|||
this.onChange,
|
||||
});
|
||||
|
||||
/// A widget to be displayed on the left side of the boolean widget.
|
||||
final Widget? leftWidget;
|
||||
|
||||
/// A widget to be displayed on the right side of the boolean widget.
|
||||
final Widget? rightWidget;
|
||||
|
||||
/// The type of boolean widget to use.
|
||||
final BoolWidgetType widgetType;
|
||||
|
||||
/// A callback function triggered when the value of the field changes.
|
||||
final Function(String value)? onChange;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, Function onValueChanged) {
|
||||
return FlutterFormInputBool(
|
||||
widgetType: widgetType,
|
||||
onChanged: (v) {
|
||||
value = v;
|
||||
onChange?.call(value);
|
||||
onValueChanged();
|
||||
},
|
||||
validator: (value) {
|
||||
for (var validator in validators) {
|
||||
var output = validator(value);
|
||||
if (output != null) {
|
||||
return output;
|
||||
Widget build(BuildContext context, Function onValueChanged) =>
|
||||
FlutterFormInputBool(
|
||||
widgetType: widgetType,
|
||||
onChanged: (v) {
|
||||
value = v;
|
||||
onChange?.call(value);
|
||||
// ignore: avoid_dynamic_calls
|
||||
onValueChanged();
|
||||
},
|
||||
validator: (value) {
|
||||
for (var validator in validators) {
|
||||
var output = validator(value);
|
||||
if (output != null) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
leftWidget: leftWidget,
|
||||
rightWidget: rightWidget,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
leftWidget: leftWidget,
|
||||
rightWidget: rightWidget,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_registration/flutter_registration.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_registration/flutter_registration.dart";
|
||||
|
||||
/// A field for capturing dropdown selections in a Flutter form.
|
||||
///
|
||||
|
@ -10,11 +10,11 @@ class AuthDropdownField extends AuthField {
|
|||
required super.name,
|
||||
required this.items,
|
||||
required this.onChanged,
|
||||
required super.value,
|
||||
this.dropdownDecoration,
|
||||
this.padding = const EdgeInsets.all(8.0),
|
||||
this.textStyle,
|
||||
this.icon = const Icon(Icons.keyboard_arrow_down),
|
||||
required super.value,
|
||||
}) {
|
||||
selectedValue = value ?? items.first;
|
||||
}
|
||||
|
@ -41,37 +41,38 @@ class AuthDropdownField extends AuthField {
|
|||
final Icon icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, Function onValueChanged) {
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: DropdownButtonFormField<String>(
|
||||
icon: icon,
|
||||
style: textStyle,
|
||||
value: selectedValue,
|
||||
decoration: dropdownDecoration,
|
||||
items: items.map((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (newValue) {
|
||||
selectedValue = newValue;
|
||||
onChanged(newValue);
|
||||
onValueChanged();
|
||||
},
|
||||
validator: (value) {
|
||||
if (validators.isNotEmpty) {
|
||||
for (var validator in validators) {
|
||||
var output = validator(value);
|
||||
if (output != null) {
|
||||
return output;
|
||||
Widget build(BuildContext context, Function onValueChanged) => Padding(
|
||||
padding: padding,
|
||||
child: DropdownButtonFormField<String>(
|
||||
icon: icon,
|
||||
style: textStyle,
|
||||
value: selectedValue,
|
||||
decoration: dropdownDecoration,
|
||||
items: items
|
||||
.map(
|
||||
(String value) => DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (newValue) {
|
||||
selectedValue = newValue;
|
||||
onChanged(newValue);
|
||||
// ignore: avoid_dynamic_calls
|
||||
onValueChanged();
|
||||
},
|
||||
validator: (value) {
|
||||
if (validators.isNotEmpty) {
|
||||
for (var validator in validators) {
|
||||
var output = validator(value);
|
||||
if (output != null) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
/// An exception thrown when an authentication error occurs.
|
||||
class AuthException implements Exception {
|
||||
/// Constructs an [AuthException] object.
|
||||
AuthException(this.message);
|
||||
|
||||
/// The error message.
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return message;
|
||||
}
|
||||
String toString() => message;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// 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.
|
||||
///
|
||||
|
@ -14,11 +14,13 @@ abstract class AuthField<T> {
|
|||
///
|
||||
/// [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).
|
||||
///
|
||||
/// [validators] defines a list of validation functions for the field (optional).
|
||||
/// [validators] defines a list of validation
|
||||
/// functions for the field (optional).
|
||||
AuthField({
|
||||
required this.name,
|
||||
required this.value,
|
||||
|
@ -46,6 +48,7 @@ abstract class AuthField<T> {
|
|||
///
|
||||
/// [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);
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_input_library/flutter_input_library.dart';
|
||||
import 'package:flutter_registration/flutter_registration.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_input_library/flutter_input_library.dart";
|
||||
import "package:flutter_registration/flutter_registration.dart";
|
||||
|
||||
/// A field for capturing password inputs in a Flutter form.
|
||||
///
|
||||
|
@ -18,25 +18,31 @@ class AuthPassField extends AuthField {
|
|||
///
|
||||
/// [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.
|
||||
///
|
||||
/// [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({
|
||||
required super.name,
|
||||
TextEditingController? textEditingController,
|
||||
this.textEditingController,
|
||||
super.title,
|
||||
super.validators = const [],
|
||||
super.value = '',
|
||||
super.value = "",
|
||||
this.textStyle,
|
||||
this.onChange,
|
||||
this.iconSize,
|
||||
|
@ -44,36 +50,47 @@ class AuthPassField extends AuthField {
|
|||
this.padding = const EdgeInsets.all(8.0),
|
||||
});
|
||||
|
||||
/// The text style for the password input.
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// The size of the icon displayed with the password input.
|
||||
final double? iconSize;
|
||||
|
||||
/// A callback function triggered when the value of the field changes.
|
||||
final Function(String value)? onChange;
|
||||
|
||||
/// The decoration for the password input field.
|
||||
final InputDecoration? textFieldDecoration;
|
||||
|
||||
/// The padding around the password input field.
|
||||
final EdgeInsets padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, Function onValueChanged) {
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: FlutterFormInputPassword(
|
||||
style: textStyle,
|
||||
iconSize: iconSize ?? 24.0,
|
||||
decoration: textFieldDecoration,
|
||||
onChanged: (v) {
|
||||
value = v;
|
||||
onChange?.call(value);
|
||||
onValueChanged();
|
||||
},
|
||||
validator: (value) {
|
||||
for (var validator in validators) {
|
||||
var output = validator(value);
|
||||
if (output != null) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
/// The controller for the password input.
|
||||
final TextEditingController? textEditingController;
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Widget build(BuildContext context, Function onValueChanged) => Padding(
|
||||
padding: padding,
|
||||
child: FlutterFormInputPassword(
|
||||
style: textStyle,
|
||||
iconSize: iconSize ?? 24.0,
|
||||
decoration: textFieldDecoration,
|
||||
onChanged: (v) {
|
||||
value = v;
|
||||
onChange?.call(value);
|
||||
// ignore: avoid_dynamic_calls
|
||||
onValueChanged();
|
||||
},
|
||||
validator: (value) {
|
||||
for (var validator in validators) {
|
||||
var output = validator(value);
|
||||
if (output != null) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
//
|
||||
// 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 {
|
||||
/// Constructs an [AuthStep] object.
|
||||
AuthStep({
|
||||
required this.fields,
|
||||
});
|
||||
|
||||
/// The fields in the step.
|
||||
List<AuthField> fields;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_registration/flutter_registration.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_registration/flutter_registration.dart";
|
||||
|
||||
/// A field for capturing text inputs in a Flutter form.
|
||||
///
|
||||
|
@ -17,62 +17,80 @@ class AuthTextField extends AuthField {
|
|||
///
|
||||
/// [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.
|
||||
///
|
||||
/// [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({
|
||||
required super.name,
|
||||
TextEditingController? textEditingController,
|
||||
super.title,
|
||||
super.validators = const [],
|
||||
super.value = '',
|
||||
super.value = "",
|
||||
this.textStyle,
|
||||
this.onChange,
|
||||
this.textFieldDecoration,
|
||||
this.padding = const EdgeInsets.all(8.0),
|
||||
this.textInputType,
|
||||
}) {
|
||||
textController =
|
||||
textEditingController ?? TextEditingController(text: value);
|
||||
}
|
||||
|
||||
/// The controller for the text input.
|
||||
late TextEditingController textController;
|
||||
|
||||
/// The text style for the text input.
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// A callback function triggered when the value of the field changes.
|
||||
final Function(String value)? onChange;
|
||||
|
||||
/// The decoration for the text input field.
|
||||
final InputDecoration? textFieldDecoration;
|
||||
|
||||
/// The padding around the text input field.
|
||||
final EdgeInsets padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, Function onValueChanged) {
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: TextFormField(
|
||||
style: textStyle,
|
||||
decoration: textFieldDecoration,
|
||||
controller: textController,
|
||||
onChanged: (v) {
|
||||
value = v;
|
||||
onChange?.call(value);
|
||||
onValueChanged();
|
||||
},
|
||||
validator: (value) {
|
||||
for (var validator in validators) {
|
||||
var output = validator(value);
|
||||
if (output != null) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
/// The type of text input.
|
||||
final TextInputType? textInputType;
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Widget build(BuildContext context, Function onValueChanged) => Padding(
|
||||
padding: padding,
|
||||
child: TextFormField(
|
||||
keyboardType: textInputType,
|
||||
style: textStyle,
|
||||
decoration: textFieldDecoration,
|
||||
controller: textController,
|
||||
onChanged: (v) {
|
||||
value = v;
|
||||
onChange?.call(value);
|
||||
// ignore: avoid_dynamic_calls
|
||||
onValueChanged();
|
||||
},
|
||||
validator: (value) {
|
||||
for (var validator in validators) {
|
||||
var output = validator(value);
|
||||
if (output != null) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'dart:collection';
|
||||
import "dart:collection";
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_registration/flutter_registration.dart';
|
||||
import 'package:flutter_registration/src/auth_screen.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_registration/flutter_registration.dart";
|
||||
import "package:flutter_registration/src/auth_screen.dart";
|
||||
|
||||
/// A screen for user registration.
|
||||
class RegistrationScreen extends StatefulWidget {
|
||||
|
@ -11,8 +11,8 @@ class RegistrationScreen extends StatefulWidget {
|
|||
/// [registrationOptions] specifies the registration options.
|
||||
const RegistrationScreen({
|
||||
required this.registrationOptions,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The registration options.
|
||||
final RegistrationOptions registrationOptions;
|
||||
|
@ -23,17 +23,13 @@ class RegistrationScreen extends StatefulWidget {
|
|||
|
||||
/// The state for [RegistrationScreen].
|
||||
class RegistrationScreenState extends State<RegistrationScreen> {
|
||||
bool _isLoading = false;
|
||||
|
||||
/// Registers the user.
|
||||
Future<void> _register({
|
||||
required HashMap<String, dynamic> values,
|
||||
required void Function(int? pageToReturn) onError,
|
||||
}) async {
|
||||
try {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
await widget.registrationOptions.beforeRegistration?.call();
|
||||
|
||||
var registered = await widget.registrationOptions.registrationRepository!
|
||||
.register(values);
|
||||
|
@ -45,12 +41,8 @@ class RegistrationScreenState extends State<RegistrationScreen> {
|
|||
|
||||
onError(pageToReturn);
|
||||
}
|
||||
} catch (e) {
|
||||
onError(0);
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
} on Exception catch (_) {
|
||||
onError(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +67,6 @@ class RegistrationScreenState extends State<RegistrationScreen> {
|
|||
customBackgroundColor: widget.registrationOptions.backgroundColor,
|
||||
titleWidget: widget.registrationOptions.titleWidget,
|
||||
loginButton: widget.registrationOptions.loginButton,
|
||||
isLoading: _isLoading,
|
||||
titleFlex: widget.registrationOptions.titleFlex,
|
||||
formFlex: widget.registrationOptions.formFlex,
|
||||
beforeTitleFlex: widget.registrationOptions.beforeTitleFlex,
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'dart:collection';
|
||||
import "dart:collection";
|
||||
|
||||
/// A mixin for a registration repository.
|
||||
mixin RegistrationRepository {
|
||||
/// Registers a user with the given values.
|
||||
Future<String?> register(HashMap values);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
name: flutter_registration
|
||||
description: A Flutter Registration package
|
||||
version: 2.0.4
|
||||
version: 3.0.0
|
||||
repository: https://github.com/Iconica-Development/flutter_registration
|
||||
|
||||
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
|
@ -16,7 +16,7 @@ environment:
|
|||
dependencies:
|
||||
flutter_input_library:
|
||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
version: ">=3.1.0 <4.0.0"
|
||||
version: ^3.6.0
|
||||
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
@ -26,6 +26,9 @@ dependencies:
|
|||
dev_dependencies:
|
||||
flutter_test:
|
||||
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:
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
void main() {
|
||||
test('test', () {
|
||||
test("test", () {
|
||||
expect(true, true);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue