flutter_registration/lib/src/auth_screen.dart

331 lines
12 KiB
Dart
Raw Normal View History

2022-11-01 09:19:20 +01:00
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'dart:async';
2022-09-20 15:51:22 +02:00
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:flutter_registration/flutter_registration.dart';
2022-09-20 15:51:22 +02:00
2024-02-19 13:31:12 +01:00
/// A widget for handling multi-step authentication processes.
2022-09-20 15:51:22 +02:00
class AuthScreen extends StatefulWidget {
2024-02-19 13:31:12 +01:00
/// Constructs an [AuthScreen] object.
///
/// [appBarTitle] specifies the title of the app bar.
///
/// [onFinish] is a function called upon completion of the authentication process.
///
/// [steps] is a list of authentication steps to be completed.
///
/// [submitBtnTitle] specifies the title of the submit button.
///
/// [nextBtnTitle] specifies the title of the next button.
///
/// [previousBtnTitle] specifies the title of the previous button.
///
/// [customAppBar] allows customization of the app bar.
///
/// [buttonMainAxisAlignment] specifies the alignment of the buttons.
///
/// [customBackgroundColor] allows customization of the background color.
///
/// [nextButtonBuilder] allows customization of the next button.
///
/// [previousButtonBuilder] allows customization of the previous button.
///
/// [titleWidget] specifies a custom widget to be displayed at the top of the screen.
///
/// [loginButton] specifies a custom login button widget.
///
/// [titleFlex] specifies the flex value for the title widget.
///
/// [formFlex] specifies the flex value for the form widget.
///
/// [beforeTitleFlex] specifies the flex value before the title widget.
///
/// [afterTitleFlex] specifies the flex value after the title widget.
///
/// [isLoading] indicates whether the screen is in a loading state.
2022-09-20 15:51:22 +02:00
const AuthScreen({
required this.appBarTitle,
2022-09-20 15:51:22 +02:00
required this.steps,
required this.submitBtnTitle,
required this.nextBtnTitle,
required this.previousBtnTitle,
2022-09-20 15:51:22 +02:00
required this.onFinish,
2022-09-28 09:23:41 +02:00
this.customAppBar,
this.buttonMainAxisAlignment,
this.customBackgroundColor,
this.nextButtonBuilder,
this.previousButtonBuilder,
this.titleWidget,
this.loginButton,
this.titleFlex,
this.formFlex,
this.beforeTitleFlex,
this.afterTitleFlex,
this.isLoading = false,
2024-02-19 13:31:12 +01:00
Key? key,
}) : assert(steps.length > 0, 'At least one step is required'),
super(key: key);
2022-09-20 15:51:22 +02:00
final String appBarTitle;
final Future<void> Function({
required HashMap<String, dynamic> values,
2023-10-03 14:38:52 +02:00
required void Function(int? pageToReturn) onError,
}) onFinish;
2022-09-20 15:51:22 +02:00
final List<AuthStep> steps;
final String submitBtnTitle;
final String nextBtnTitle;
final String previousBtnTitle;
2022-09-28 09:23:41 +02:00
final AppBar? customAppBar;
final MainAxisAlignment? buttonMainAxisAlignment;
final Color? customBackgroundColor;
final Widget Function(Future<void> Function()? onPressed, String label,
int step, bool enabled)? nextButtonBuilder;
final Widget? Function(VoidCallback onPressed, String label, int step)?
previousButtonBuilder;
final Widget? titleWidget;
final Widget? loginButton;
final int? titleFlex;
final int? formFlex;
final int? beforeTitleFlex;
final int? afterTitleFlex;
final bool isLoading;
2022-09-20 15:51:22 +02:00
@override
State<AuthScreen> createState() => _AuthScreenState();
}
2024-02-19 13:31:12 +01:00
/// The state for [AuthScreen].
2022-09-20 15:51:22 +02:00
class _AuthScreenState extends State<AuthScreen> {
final _formKey = GlobalKey<FormState>();
final _pageController = PageController();
final _animationDuration = const Duration(milliseconds: 300);
final _animationCurve = Curves.ease;
bool _formValid = false;
2022-09-20 15:51:22 +02:00
2024-02-19 13:31:12 +01:00
/// Gets the app bar.
2022-09-28 09:23:41 +02:00
AppBar get _appBar =>
widget.customAppBar ??
AppBar(
title: Text(widget.appBarTitle),
2022-09-28 09:23:41 +02:00
);
2024-02-19 13:31:12 +01:00
/// Handles previous button press.
void onPrevious() {
FocusScope.of(context).unfocus();
_validate(_pageController.page!.toInt() - 1);
_pageController.previousPage(
duration: _animationDuration,
curve: _animationCurve,
);
}
2024-02-19 13:31:12 +01:00
/// Handles next button press.
Future<void> onNext(AuthStep step) async {
if (!_formKey.currentState!.validate()) {
return;
}
_formKey.currentState!.save();
FocusScope.of(context).unfocus();
if (widget.steps.last == step) {
var values = HashMap<String, dynamic>();
for (var step in widget.steps) {
for (var field in step.fields) {
values[field.name] = field.value;
}
}
await widget.onFinish(
values: values,
2023-10-03 14:38:52 +02:00
onError: (int? pageToReturn) => _pageController.animateToPage(
pageToReturn ?? 0,
duration: _animationDuration,
curve: _animationCurve,
),
);
return;
} else {
_validate(_pageController.page!.toInt() + 1);
_pageController.nextPage(
duration: _animationDuration,
curve: _animationCurve,
);
}
}
2024-02-19 13:31:12 +01:00
/// Validates the current step.
void _validate(int currentPage) {
bool 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);
if (validationResult != null) {
// If any validator returns an error, mark step as invalid and break
isStepValid = false;
break;
}
}
if (!isStepValid) {
break; // No need to check further fields if one is already invalid
}
}
setState(() {
_formValid = isStepValid;
});
}
2022-09-20 15:51:22 +02:00
@override
Widget build(BuildContext context) {
return widget.isLoading
? const Center(
child: SizedBox(
height: 120,
width: 120,
child: CircularProgressIndicator(),
),
)
: Scaffold(
backgroundColor: widget.customBackgroundColor ?? Colors.white,
appBar: _appBar,
body: Form(
key: _formKey,
child: PageView(
2024-02-19 13:31:12 +01:00
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) ...[
Expanded(
flex: widget.titleFlex ?? 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
flex: widget.beforeTitleFlex ?? 3,
child: Container(),
2024-02-06 16:28:51 +01:00
),
2024-02-19 13:31:12 +01:00
widget.titleWidget!,
Expanded(
flex: widget.afterTitleFlex ?? 2,
child: Container(),
),
],
),
),
Column(
children: [
Row(
mainAxisAlignment: widget
.buttonMainAxisAlignment !=
null
? widget.buttonMainAxisAlignment!
: (widget.previousButtonBuilder != null &&
widget.previousButtonBuilder?.call(
onPrevious,
widget.previousBtnTitle,
i,
) ==
null)
? MainAxisAlignment.start
: widget.steps.first != widget.steps[i]
? MainAxisAlignment.center
: MainAxisAlignment.end,
children: [
2024-02-19 13:31:12 +01:00
if (widget.previousButtonBuilder == null) ...[
if (widget.steps.first != widget.steps[i])
ElevatedButton(
onPressed: onPrevious,
child: Row(
children: [
const Icon(
Icons.arrow_back,
size: 18,
),
Padding(
padding: const EdgeInsets.only(
left: 4.0),
child:
Text(widget.previousBtnTitle),
),
2024-02-19 13:31:12 +01:00
],
),
),
] else if (widget.previousButtonBuilder?.call(
onPrevious,
widget.previousBtnTitle,
i) !=
null) ...[
widget.previousButtonBuilder!.call(
onPrevious, widget.previousBtnTitle, i)!
],
widget.nextButtonBuilder?.call(
!_formValid
? null
: () async {
await onNext(widget.steps[i]);
},
widget.steps.last == widget.steps[i]
? widget.submitBtnTitle
: widget.nextBtnTitle,
i,
_formValid,
) ??
ElevatedButton(
onPressed: !_formValid
? null
: () async {
await onNext(widget.steps[i]);
},
child: Row(
children: [
Text(
widget.steps.last ==
widget.steps[i]
? widget.submitBtnTitle
: widget.nextBtnTitle,
),
const Padding(
padding:
EdgeInsets.only(left: 4.0),
child: Icon(
Icons.arrow_forward,
size: 18,
),
),
2024-02-19 13:31:12 +01:00
],
),
),
],
),
2024-02-19 13:31:12 +01:00
if (widget.loginButton != null)
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: widget.loginButton!,
),
],
2024-02-19 13:31:12 +01:00
),
],
],
),
],
),
),
);
}
2022-09-20 15:51:22 +02:00
}