feat: two step login

This commit is contained in:
DirkIconica 2024-03-07 14:21:12 +01:00 committed by Kiril Tijsma
parent f1b11ae79a
commit 54afe8c4bf
3 changed files with 193 additions and 37 deletions

View file

@ -68,7 +68,11 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
<<<<<<< HEAD
version: "6.0.0" version: "6.0.0"
=======
version: "5.2.0"
>>>>>>> 60112c8 (feat: two step login)
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -79,30 +83,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -115,34 +95,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16+1" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.0" version: "0.5.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.10.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.8.3"
pinput: pinput:
dependency: transitive dependency: transitive
description: description:
@ -228,14 +208,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
vm_service: web:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: web
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.0.0" version: "0.3.0"
sdks: sdks:
dart: ">=3.2.0-0 <4.0.0" dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.7.0"

View file

@ -9,3 +9,4 @@ export 'src/service/validation.dart';
export 'src/widgets/email_password_login.dart'; export 'src/widgets/email_password_login.dart';
export 'src/widgets/forgot_password_form.dart'; export 'src/widgets/forgot_password_form.dart';
export 'src/widgets/mfa_widget.dart'; export 'src/widgets/mfa_widget.dart';
export 'src/widgets/two_step_login.dart';

View file

@ -0,0 +1,175 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_login/flutter_login.dart';
class TwoStepLoginForm extends StatefulWidget {
/// Constructs an [TwoStepLoginForm] widget.
///
/// [onCheckEmail]: Callback function for when a user has filled in its email
/// [options]: The options for configuring the login form.
const TwoStepLoginForm({
required this.onCheckEmail,
super.key,
this.options = const LoginOptions(),
});
final LoginOptions options;
final FutureOr<void> Function(String email) onCheckEmail;
@override
State<TwoStepLoginForm> createState() => _TwoStepLoginFormState();
}
class _TwoStepLoginFormState extends State<TwoStepLoginForm> {
final _formKey = GlobalKey<FormState>();
final ValueNotifier<bool> _formValid = ValueNotifier(false);
bool _obscurePassword = true;
String _currentEmail = '';
void _updateCurrentEmail(String email) {
_currentEmail = email;
_validate();
}
void _validate() {
late var isValid =
widget.options.validations.validateEmail(_currentEmail) == null;
if (isValid != _formValid.value) {
_formValid.value = isValid;
}
}
Future<void> _handleLogin() async {
if (mounted) {
var form = _formKey.currentState!;
if (form.validate()) {
await widget.onCheckEmail(
_currentEmail,
);
}
}
}
@override
void initState() {
super.initState();
_currentEmail = widget.options.initialEmail;
_validate();
}
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var options = widget.options;
return CustomScrollView(
physics: const ScrollPhysics(),
slivers: [
SliverFillRemaining(
hasScrollBody: false,
fillOverscroll: true,
child: Column(
children: [
Expanded(
flex: options.spacers.titleSpacer,
child: Column(
children: [
if (options.spacers.spacerBeforeTitle != null) ...[
Spacer(flex: options.spacers.spacerBeforeTitle!),
],
if (options.title != null) ...[
Align(
alignment: Alignment.topCenter,
child: _wrapWithDefaultStyle(
options.title,
theme.textTheme.headlineSmall,
),
),
],
if (options.spacers.spacerAfterTitle != null) ...[
Spacer(flex: options.spacers.spacerAfterTitle!),
],
if (options.subtitle != null) ...[
Align(
alignment: Alignment.topCenter,
child: _wrapWithDefaultStyle(
options.subtitle,
theme.textTheme.titleSmall,
),
),
],
if (options.spacers.spacerAfterSubtitle != null) ...[
Spacer(flex: options.spacers.spacerAfterSubtitle!),
],
if (options.image != null) ...[
Padding(
padding: const EdgeInsets.all(16),
child: options.image,
),
],
if (options.spacers.spacerAfterImage != null) ...[
Spacer(flex: options.spacers.spacerAfterImage!),
],
],
),
),
Expanded(
flex: options.spacers.formFlexValue,
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: options.maxFormWidth ?? 300,
),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
options.emailInputContainerBuilder(
TextFormField(
onChanged: _updateCurrentEmail,
validator: widget.options.validations.validateEmail,
initialValue: options.initialEmail,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
style: options.emailTextStyle,
decoration: options.emailDecoration,
),
),
if (options.spacers.spacerAfterForm != null) ...[
Spacer(flex: options.spacers.spacerAfterForm!),
],
AnimatedBuilder(
animation: _formValid,
builder: (context, _) => options.loginButtonBuilder(
context,
_handleLogin,
!_formValid.value,
() {
_formKey.currentState?.validate();
},
options,
),
),
if (options.spacers.spacerAfterButton != null) ...[
Spacer(flex: options.spacers.spacerAfterButton!),
],
],
),
),
),
),
],
),
),
],
);
}
Widget? _wrapWithDefaultStyle(Widget? widget, TextStyle? style) {
if (style == null || widget == null) {
return widget;
} else {
return DefaultTextStyle(style: style, child: widget);
}
}
}