mirror of
https://github.com/Iconica-Development/flutter_registration.git
synced 2025-05-19 13:23:45 +02:00
Compare commits
44 commits
Author | SHA1 | Date | |
---|---|---|---|
|
ee29893bd9 | ||
|
4bad032588 | ||
|
cf6e30abec | ||
|
ce80a96958 | ||
|
71259c7f78 | ||
|
d82df68989 | ||
|
c91a1c0856 | ||
|
444b11a44f | ||
|
1be83c7013 | ||
|
990259dabc | ||
|
d849a1834a | ||
|
05b143a5bd | ||
|
a44a419bc4 | ||
|
7890c17587 | ||
|
3ec92aa68c | ||
|
7a4bcd8b3f | ||
|
3884ef08b1 | ||
|
dcea299aa6 | ||
|
957abd2ac2 | ||
|
0f28c7084e | ||
|
44871bf44e | ||
|
9ebb917fb8 | ||
|
0472ebc4c5 | ||
|
4b855f7488 | ||
|
81fab79e81 | ||
|
855e12f6ae | ||
|
40c4bfea89 | ||
|
ced19fe61a | ||
|
7cfe7d5112 | ||
|
7bb87acf65 | ||
|
3ca9e7bde7 | ||
|
277b38f39e | ||
|
15aa74ebda | ||
|
49b9ece2d5 | ||
|
e112c64ee7 | ||
|
7abb060306 | ||
|
f328df84dd | ||
|
c86fa1ca5f | ||
|
79fe400842 | ||
|
078574c214 | ||
|
46e2d960af | ||
|
7012942ce5 | ||
|
f1663afa1b | ||
|
d1ad003c22 |
25 changed files with 1284 additions and 480 deletions
14
.github/workflows/component-documentation.yml
vendored
Normal file
14
.github/workflows/component-documentation.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Iconica Standard Component Documentation Workflow
|
||||
# Workflow Caller version: 1.0.0
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-iconica-component-documentation-workflow:
|
||||
uses: Iconica-Development/.github/.github/workflows/component-documentation.yml@master
|
||||
secrets: inherit
|
||||
permissions: write-all
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -34,3 +34,8 @@ migrate_working_dir/
|
|||
build/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.metadata
|
||||
|
||||
# FVM Version Cache
|
||||
.fvm/
|
||||
.fvmrc
|
||||
|
|
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -3,6 +3,39 @@ 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
|
||||
|
||||
# 2.0.3
|
||||
- feat: added default registrationOptions
|
||||
|
||||
# 2.0.2
|
||||
- fix: fixed the issue with values not being saved when calling nextStep.
|
||||
|
||||
# 2.0.1
|
||||
- feat: added circular progress indicator while awaiting registration of user
|
||||
- feat: added alignment option for buttons
|
||||
|
||||
# 2.0.0
|
||||
- feat(buttons): Added the possiblity to only have a next button by return zero on the previous button builder
|
||||
- feat: exposed input decoration in AuthTextField
|
||||
- feat: added title widget and login button builder
|
||||
- feat(bool): Add a boolean field. Can be used for accepting terms and conditions
|
||||
- feat(pass): Add dedicated password screen that manages state internally
|
||||
- fix: Small refactor and brought back the normal alignment for the screens
|
||||
- fix: Fixed alignment and spacing when opening keyboard
|
||||
- feat: add auth drop down field
|
||||
- fix: added step to button builders
|
||||
- fix: exported auth_pass_field
|
||||
- feat: added validation to disable next button
|
||||
- feat(auth-screen): add flexible spacing between fields
|
||||
- fix(keyboard-focus): add unfocus for onPrevious
|
||||
- fix: add empty and required constructors for the RegistrationTranslations
|
||||
|
||||
# 1.2.0
|
||||
|
||||
- feat: Added the ability to have an async register function so you can call it asynchronous.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2022 Iconica
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
image: cirrusci/flutter
|
||||
|
||||
pipelines:
|
||||
pull-requests:
|
||||
feature/*:
|
||||
- step:
|
||||
caches:
|
||||
- gradle
|
||||
- gradlewrapper
|
||||
- flutter
|
||||
name: Run analyzer & test
|
||||
script:
|
||||
- flutter pub get
|
||||
- flutter format -o none --set-exit-if-changed .
|
||||
- flutter analyze
|
||||
|
||||
develop:
|
||||
- step:
|
||||
caches:
|
||||
- gradle
|
||||
- gradlewrapper
|
||||
- flutter
|
||||
name: Run analyzer & test
|
||||
script:
|
||||
- flutter pub get
|
||||
- flutter format -o none --set-exit-if-changed .
|
||||
- flutter analyze
|
||||
|
||||
hotfix/*:
|
||||
- step:
|
||||
caches:
|
||||
- gradle
|
||||
- gradlewrapper
|
||||
- flutter
|
||||
name: Run analyzer & test
|
||||
script:
|
||||
- flutter pub get
|
||||
- flutter format -o none --set-exit-if-changed .
|
||||
- flutter analyze
|
||||
|
||||
definitions:
|
||||
caches:
|
||||
gradlewrapper: ~/.gradle/wrapper
|
||||
flutter: ~/.pub-cache
|
|
@ -26,6 +26,6 @@ subprojects {
|
|||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
|
|
@ -2,27 +2,81 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_registration/flutter_registration.dart';
|
||||
import 'example_registration_repository.dart';
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
const MaterialApp(
|
||||
home: FlutterRegistrationDemo(),
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
errorStyle: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
home: const FlutterRegistrationDemo(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class FlutterRegistrationDemo extends StatelessWidget {
|
||||
const FlutterRegistrationDemo({Key? key}) : super(key: key);
|
||||
class FlutterRegistrationDemo extends StatefulWidget {
|
||||
const FlutterRegistrationDemo({super.key});
|
||||
|
||||
@override
|
||||
State<FlutterRegistrationDemo> createState() =>
|
||||
_FlutterRegistrationDemoState();
|
||||
}
|
||||
|
||||
class _FlutterRegistrationDemoState extends State<FlutterRegistrationDemo> {
|
||||
late List<AuthStep> steps;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
steps = RegistrationOptions.getDefaultSteps();
|
||||
|
||||
steps[1].fields.add(
|
||||
AuthBoolField(
|
||||
name: 'termsConditions',
|
||||
widgetType: BoolWidgetType.checkbox,
|
||||
validators: [
|
||||
(value) {
|
||||
if (value == null || !value) {
|
||||
return 'Required';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
],
|
||||
rightWidget: Text.rich(
|
||||
TextSpan(
|
||||
text: 'I agree with the ',
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: 'terms & conditions',
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
debugPrint('Open terms and conditions');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RegistrationScreen(
|
||||
registrationOptions: RegistrationOptions(
|
||||
registrationRepository: ExampleRegistrationRepository(),
|
||||
registrationSteps: RegistrationOptions.getDefaultSteps(),
|
||||
registrationSteps: steps,
|
||||
afterRegistration: () {
|
||||
debugPrint('Registered!');
|
||||
},
|
||||
|
@ -32,8 +86,7 @@ class FlutterRegistrationDemo extends StatelessWidget {
|
|||
}
|
||||
|
||||
class ProtectedScreen extends StatelessWidget {
|
||||
const ProtectedScreen({Key? key}) : super(key: key);
|
||||
|
||||
const ProtectedScreen({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
|
|
|
@ -37,18 +37,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.2"
|
||||
version: "1.18.0"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
|
||||
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "1.0.6"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -62,14 +62,22 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_input_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_input_library
|
||||
sha256: db8d9d57c31f166ed7c4ec3d060d18891533c57877a30c6c2668231efa1a44f8
|
||||
url: "https://forgejo.internal.iconica.nl/api/packages/internal/pub/"
|
||||
source: hosted
|
||||
version: "3.6.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
|
||||
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.3"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -81,7 +89,7 @@ packages:
|
|||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.5.0"
|
||||
version: "2.0.4"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -91,50 +99,74 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "5cfd6509652ff5e7fe149b6df4859e687fca9048437857cb2e65c8d780f396e3"
|
||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.1.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16"
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "0.8.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.12.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
version: "1.9.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -152,18 +184,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -184,10 +216,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
version: "0.7.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -196,14 +228,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
web:
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
name: vm_service
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4-beta"
|
||||
version: "14.2.1"
|
||||
sdks:
|
||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
/// 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/registration_screen.dart';
|
||||
export 'src/service/registration_repository.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,63 +2,170 @@
|
|||
//
|
||||
// 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 {
|
||||
/// 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.
|
||||
|
||||
const AuthScreen({
|
||||
required this.title,
|
||||
required this.appBarTitle,
|
||||
required this.steps,
|
||||
required this.submitBtnTitle,
|
||||
required this.nextBtnTitle,
|
||||
required this.previousBtnTitle,
|
||||
required this.onFinish,
|
||||
this.customAppBar,
|
||||
this.buttonMainAxisAlignment,
|
||||
this.customBackgroundColor,
|
||||
this.nextButtonBuilder,
|
||||
this.previousButtonBuilder,
|
||||
this.titleWidget,
|
||||
this.loginButton,
|
||||
this.titleFlex,
|
||||
this.formFlex,
|
||||
this.beforeTitleFlex,
|
||||
this.afterTitleFlex,
|
||||
this.maxFormWidth,
|
||||
super.key,
|
||||
}) : assert(steps.length > 0, 'At least one step is required');
|
||||
}) : assert(steps.length > 0, "At least one step is required");
|
||||
|
||||
final String title;
|
||||
/// 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, String> values,
|
||||
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(), String)? nextButtonBuilder;
|
||||
final Widget Function(VoidCallback, String)? previousButtonBuilder;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// The maximum width of the form.
|
||||
final double? maxFormWidth;
|
||||
|
||||
@override
|
||||
State<AuthScreen> createState() => _AuthScreenState();
|
||||
}
|
||||
|
||||
/// The state for [AuthScreen].
|
||||
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;
|
||||
|
||||
/// Gets the app bar.
|
||||
AppBar get _appBar =>
|
||||
widget.customAppBar ??
|
||||
AppBar(
|
||||
title: Text(widget.title),
|
||||
backgroundColor: const Color(0xffFAF9F6),
|
||||
title: Text(widget.appBarTitle),
|
||||
);
|
||||
|
||||
/// Handles previous button press.
|
||||
void onPrevious() {
|
||||
FocusScope.of(context).unfocus();
|
||||
_validate(_pageController.page!.toInt() - 1);
|
||||
unawaited(
|
||||
_pageController.previousPage(
|
||||
duration: _animationDuration,
|
||||
curve: _animationCurve,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles next button press.
|
||||
Future<void> onNext(AuthStep step) async {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
|
@ -69,143 +176,246 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||
FocusScope.of(context).unfocus();
|
||||
|
||||
if (widget.steps.last == step) {
|
||||
var values = HashMap<String, String>();
|
||||
var values = HashMap<String, dynamic>();
|
||||
|
||||
for (var step in widget.steps) {
|
||||
for (var field in step.fields) {
|
||||
values[field.name] =
|
||||
(field as AuthTextField).textController.value.text;
|
||||
values[field.name] = field.value;
|
||||
}
|
||||
}
|
||||
|
||||
await widget.onFinish(
|
||||
values: values,
|
||||
onError: (int? pageToReturn) => _pageController.animateToPage(
|
||||
pageToReturn ?? 0,
|
||||
onError: (int? pageToReturn) {
|
||||
if (pageToReturn == null) {
|
||||
return;
|
||||
}
|
||||
_pageController.animateToPage(
|
||||
pageToReturn,
|
||||
duration: _animationDuration,
|
||||
curve: _animationCurve,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
} else {
|
||||
_validate(_pageController.page!.toInt() + 1);
|
||||
unawaited(
|
||||
_pageController.nextPage(
|
||||
duration: _animationDuration,
|
||||
curve: _animationCurve,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates the current step.
|
||||
void _validate(int currentPage) {
|
||||
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) {
|
||||
var 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;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: widget.customBackgroundColor ?? Colors.white,
|
||||
backgroundColor: widget.customBackgroundColor ?? const Color(0xffFAF9F6),
|
||||
appBar: _appBar,
|
||||
body: Form(
|
||||
body: SafeArea(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _pageController,
|
||||
children: <Widget>[
|
||||
for (AuthStep step in widget.steps)
|
||||
for (var currentStep = 0;
|
||||
currentStep < widget.steps.length;
|
||||
currentStep++)
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Center(
|
||||
child: ListView(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 30.0,
|
||||
),
|
||||
children: [
|
||||
for (AuthField field in step.fields)
|
||||
Align(
|
||||
if (widget.titleWidget != null) ...[
|
||||
Expanded(
|
||||
flex: widget.titleFlex ?? 1,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
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[currentStep].fields) ...[
|
||||
if (field.title != null) ...[
|
||||
field.title!,
|
||||
],
|
||||
field.build(),
|
||||
],
|
||||
wrapWithDefaultStyle(
|
||||
style: theme.textTheme.headlineLarge!,
|
||||
widget: field.title!,
|
||||
),
|
||||
)
|
||||
],
|
||||
field.build(context, () {
|
||||
_validate(currentStep);
|
||||
}),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 15.0,
|
||||
bottom: 30.0,
|
||||
left: 30.0,
|
||||
right: 30.0,
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: widget.steps.first != step
|
||||
mainAxisAlignment: widget.steps.first !=
|
||||
widget.steps[currentStep]
|
||||
? MainAxisAlignment.spaceBetween
|
||||
: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (widget.steps.first != step)
|
||||
if (widget.steps.first !=
|
||||
widget.steps[currentStep]) ...[
|
||||
widget.previousButtonBuilder?.call(
|
||||
onPrevious,
|
||||
widget.previousBtnTitle,
|
||||
currentStep,
|
||||
) ??
|
||||
ElevatedButton(
|
||||
onPressed: onPrevious,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.arrow_back,
|
||||
size: 18,
|
||||
_stepButton(
|
||||
buttonText: widget.previousBtnTitle,
|
||||
onTap: () async => onPrevious(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4.0),
|
||||
child: Text(widget.previousBtnTitle),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
widget.nextButtonBuilder?.call(
|
||||
() async {
|
||||
await onNext(step);
|
||||
!_formValid
|
||||
? null
|
||||
: () async {
|
||||
await onNext(
|
||||
widget.steps[currentStep],
|
||||
);
|
||||
},
|
||||
widget.steps.last == step
|
||||
widget.steps.last ==
|
||||
widget.steps[currentStep]
|
||||
? widget.submitBtnTitle
|
||||
: widget.nextBtnTitle,
|
||||
currentStep,
|
||||
_formValid,
|
||||
) ??
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await onNext(step);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
widget.steps.last == step
|
||||
_stepButton(
|
||||
buttonText: widget.steps.last ==
|
||||
widget.steps[currentStep]
|
||||
? widget.submitBtnTitle
|
||||
: widget.nextBtnTitle,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 4.0),
|
||||
child: Icon(
|
||||
Icons.arrow_forward,
|
||||
size: 18,
|
||||
),
|
||||
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);
|
||||
}
|
||||
|
|
12
lib/src/config/example_registration_repository.dart
Normal file
12
lib/src/config/example_registration_repository.dart
Normal 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);
|
||||
}
|
||||
}
|
|
@ -2,77 +2,172 @@
|
|||
//
|
||||
// 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/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.
|
||||
class RegistrationOptions {
|
||||
/// Registration options Constructor
|
||||
RegistrationOptions({
|
||||
required this.registrationRepository,
|
||||
required this.registrationSteps,
|
||||
required this.afterRegistration,
|
||||
this.registrationTranslations = const RegistrationTranslations(),
|
||||
this.registrationRepository,
|
||||
this.registrationSteps,
|
||||
this.titleFlex,
|
||||
this.formFlex,
|
||||
this.beforeTitleFlex,
|
||||
this.afterTitleFlex,
|
||||
this.registrationTranslations = const RegistrationTranslations.empty(),
|
||||
this.onError,
|
||||
this.customAppbarBuilder,
|
||||
this.customAppbarBuilder = _createCustomAppBar,
|
||||
this.nextButtonBuilder,
|
||||
this.previousButtonBuilder,
|
||||
this.buttonMainAxisAlignment,
|
||||
this.backgroundColor,
|
||||
});
|
||||
this.titleWidget,
|
||||
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.
|
||||
final RegistrationTranslations registrationTranslations;
|
||||
final List<AuthStep> registrationSteps;
|
||||
|
||||
/// 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.
|
||||
final int? Function(String error)? onError;
|
||||
|
||||
/// A callback function executed after successful registration.
|
||||
final VoidCallback afterRegistration;
|
||||
final RegistrationRepository registrationRepository;
|
||||
|
||||
/// The repository responsible for registration.
|
||||
RegistrationRepository? registrationRepository;
|
||||
|
||||
/// A function for customizing the app bar displayed during registration.
|
||||
final AppBar Function(String title)? customAppbarBuilder;
|
||||
final Widget Function(Future<void> Function() onPressed, String label)?
|
||||
nextButtonBuilder;
|
||||
final Widget Function(VoidCallback onPressed, String label)?
|
||||
|
||||
/// A function for customizing the "Next" button.
|
||||
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)?
|
||||
previousButtonBuilder;
|
||||
|
||||
/// Specifies the alignment of buttons.
|
||||
final MainAxisAlignment? buttonMainAxisAlignment;
|
||||
|
||||
/// The background color of the registration screen.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// A custom widget for displaying the registration title.
|
||||
final Widget? titleWidget;
|
||||
|
||||
/// A custom widget for displaying a login button.
|
||||
final Widget? loginButton;
|
||||
|
||||
/// The number of flex units for the title.
|
||||
final int? titleFlex;
|
||||
|
||||
/// The number of flex units for the form.
|
||||
final int? formFlex;
|
||||
|
||||
/// The number of flex units for the buttons.
|
||||
final int? beforeTitleFlex;
|
||||
|
||||
/// The number of flex units for the buttons.
|
||||
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.
|
||||
///
|
||||
/// [emailController] controller for email input.
|
||||
///
|
||||
/// [pass1Controller] controller for first password input.
|
||||
///
|
||||
/// [pass1Hidden] whether the first password field is initially hidden.
|
||||
///
|
||||
/// [pass2Controller] controller for second password input.
|
||||
///
|
||||
/// [pass2Hidden] whether the second password field is initially hidden.
|
||||
///
|
||||
/// [passHideOnChange] function triggered when password visibility changes.
|
||||
///
|
||||
/// [translations] translations for default registration messages and prompts.
|
||||
///
|
||||
/// [titleBuilder] function for customizing step titles.
|
||||
///
|
||||
/// [labelBuilder] function for customizing field labels.
|
||||
///
|
||||
/// [textStyle] text style for input fields.
|
||||
///
|
||||
/// [initialEmail] initial value for email input.
|
||||
static List<AuthStep> getDefaultSteps({
|
||||
TextEditingController? emailController,
|
||||
TextEditingController? pass1Controller,
|
||||
bool pass1Hidden = true,
|
||||
TextEditingController? pass2Controller,
|
||||
bool pass2Hidden = true,
|
||||
TextEditingController? passController,
|
||||
bool passHidden = true,
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
Function(bool mainPass, bool value)? passHideOnChange,
|
||||
RegistrationTranslations translations = const RegistrationTranslations(),
|
||||
RegistrationTranslations translations =
|
||||
const RegistrationTranslations.empty(),
|
||||
Function(String title)? titleBuilder,
|
||||
Function(String label)? labelBuilder,
|
||||
TextStyle? textStyle,
|
||||
TextStyle? hintStyle,
|
||||
String? initialEmail,
|
||||
}) {
|
||||
var password1 = '';
|
||||
|
||||
return [
|
||||
}) =>
|
||||
[
|
||||
AuthStep(
|
||||
fields: [
|
||||
AuthTextField(
|
||||
name: 'email',
|
||||
name: "email",
|
||||
textEditingController: emailController,
|
||||
value: initialEmail ?? '',
|
||||
title: titleBuilder?.call(
|
||||
translations.defaultEmailTitle,
|
||||
) ??
|
||||
value: initialEmail ?? "",
|
||||
title: titleBuilder?.call(translations.defaultEmailTitle) ??
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24.0,
|
||||
bottom: 12.0,
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 180),
|
||||
child: Text(
|
||||
translations.defaultEmailTitle,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
|
@ -82,89 +177,52 @@ class RegistrationOptions {
|
|||
? null
|
||||
: translations.defaultEmailValidatorMessage,
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
AuthStep(
|
||||
fields: [
|
||||
AuthTextField(
|
||||
name: 'password1',
|
||||
textEditingController: pass1Controller,
|
||||
title: titleBuilder?.call(
|
||||
translations.defaultPassword1Title,
|
||||
) ??
|
||||
AuthPassField(
|
||||
name: "password",
|
||||
textEditingController: passController,
|
||||
title: titleBuilder?.call(translations.defaultPasswordTitle) ??
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24.0,
|
||||
bottom: 12.0,
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 180),
|
||||
child: Text(
|
||||
translations.defaultPassword1Title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
translations.defaultPasswordTitle,
|
||||
),
|
||||
),
|
||||
textFieldDecoration: InputDecoration(
|
||||
hintStyle: hintStyle,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
label: labelBuilder?.call(translations.defaultPasswordLabel),
|
||||
hintText: translations.defaultPasswordHint,
|
||||
border: const OutlineInputBorder(),
|
||||
focusedBorder: const OutlineInputBorder(),
|
||||
),
|
||||
label: labelBuilder?.call(translations.defaultPassword1Label),
|
||||
hintText: translations.defaultPassword1Hint,
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
textStyle: textStyle,
|
||||
obscureText: true,
|
||||
validators: [
|
||||
(value) => (value == null || value.isEmpty)
|
||||
? translations.defaultPassword1ValidatorMessage
|
||||
: null,
|
||||
],
|
||||
onChange: (value) {
|
||||
password1 = value;
|
||||
},
|
||||
hidden: pass1Hidden,
|
||||
onPassChanged: (value) {
|
||||
passHideOnChange?.call(true, value);
|
||||
},
|
||||
),
|
||||
AuthTextField(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
label: labelBuilder?.call(translations.defaultPassword2Label),
|
||||
hintText: translations.defaultPassword2Hint,
|
||||
textStyle: textStyle,
|
||||
obscureText: true,
|
||||
validators: [
|
||||
(value) {
|
||||
if (pass1Controller != null) {
|
||||
if (value != pass1Controller.value.text) {
|
||||
return translations.defaultPassword2ValidatorMessage;
|
||||
}
|
||||
} else {
|
||||
if (value != password1) {
|
||||
return translations.defaultPassword2ValidatorMessage;
|
||||
// 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;
|
||||
}
|
||||
],
|
||||
hidden: pass2Hidden,
|
||||
onPassChanged: (value) {
|
||||
passHideOnChange?.call(false, value);
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
AppBar _createCustomAppBar(String title) => AppBar(
|
||||
iconTheme: const IconThemeData(color: Colors.black, size: 16),
|
||||
title: Text(title),
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
|
|
|
@ -2,44 +2,129 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
/// Holds all the translations for the standard elements
|
||||
/// on the registration screen.
|
||||
class RegistrationTranslations {
|
||||
/// Constructs a [RegistrationTranslations] object.
|
||||
const RegistrationTranslations({
|
||||
this.title = 'Register',
|
||||
this.registerBtn = 'Register',
|
||||
this.previousStepBtn = 'Previous',
|
||||
this.nextStepBtn = 'Next',
|
||||
this.closeBtn = 'Close',
|
||||
this.defaultEmailTitle = 'What is your email?',
|
||||
this.defaultEmailLabel = '',
|
||||
this.defaultEmailHint = 'john.doe@domain.com',
|
||||
this.defaultEmailEmpty = 'Enter your email',
|
||||
this.defaultEmailValidatorMessage = 'Enter a valid email address',
|
||||
this.defaultPassword1Title = 'Enter a password',
|
||||
this.defaultPassword1Label = '',
|
||||
this.defaultPassword1Hint = '',
|
||||
this.defaultPassword1ValidatorMessage = 'Enter a valid password',
|
||||
this.defaultPassword2Title = 'Re-enter password',
|
||||
this.defaultPassword2Label = '',
|
||||
this.defaultPassword2Hint = '',
|
||||
this.defaultPassword2ValidatorMessage = 'Passwords have to be equal',
|
||||
required this.title,
|
||||
required this.registerBtn,
|
||||
required this.previousStepBtn,
|
||||
required this.nextStepBtn,
|
||||
required this.closeBtn,
|
||||
required this.defaultEmailTitle,
|
||||
required this.defaultEmailLabel,
|
||||
required this.defaultEmailHint,
|
||||
required this.defaultEmailEmpty,
|
||||
required this.defaultEmailValidatorMessage,
|
||||
required this.defaultPasswordTitle,
|
||||
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 = "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;
|
||||
final String defaultPassword1Title;
|
||||
final String defaultPassword1Label;
|
||||
final String defaultPassword1Hint;
|
||||
final String defaultPassword1ValidatorMessage;
|
||||
final String defaultPassword2Title;
|
||||
final String defaultPassword2Label;
|
||||
final String defaultPassword2Hint;
|
||||
final String defaultPassword2ValidatorMessage;
|
||||
|
||||
/// 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({
|
||||
String? title,
|
||||
String? registerBtn,
|
||||
String? previousStepBtn,
|
||||
String? nextStepBtn,
|
||||
String? closeBtn,
|
||||
String? defaultEmailTitle,
|
||||
String? defaultEmailLabel,
|
||||
String? defaultEmailHint,
|
||||
String? defaultEmailEmpty,
|
||||
String? defaultEmailValidatorMessage,
|
||||
String? defaultPasswordTitle,
|
||||
String? defaultPasswordLabel,
|
||||
String? defaultPasswordHint,
|
||||
String? 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;
|
||||
}
|
||||
|
|
78
lib/src/model/auth_bool_field.dart
Normal file
78
lib/src/model/auth_bool_field.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// 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";
|
||||
|
||||
/// A field for capturing boolean values in a Flutter form.
|
||||
///
|
||||
/// Extends [AuthField].
|
||||
class AuthBoolField extends AuthField {
|
||||
/// Constructs an [AuthBoolField] object.
|
||||
///
|
||||
/// [name] specifies the name of the field.
|
||||
///
|
||||
/// [widgetType] defines the type of boolean widget to use.
|
||||
///
|
||||
/// [title] specifies the title of the field (optional).
|
||||
///
|
||||
/// [validators] defines a list of validation functions for the field.
|
||||
///
|
||||
/// [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.
|
||||
///
|
||||
/// [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.
|
||||
AuthBoolField({
|
||||
required super.name,
|
||||
required this.widgetType,
|
||||
super.title,
|
||||
super.validators = const [],
|
||||
super.value = false,
|
||||
this.leftWidget,
|
||||
this.rightWidget,
|
||||
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) =>
|
||||
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,
|
||||
);
|
||||
}
|
78
lib/src/model/auth_drop_down.dart
Normal file
78
lib/src/model/auth_drop_down.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_registration/flutter_registration.dart";
|
||||
|
||||
/// A field for capturing dropdown selections in a Flutter form.
|
||||
///
|
||||
/// Extends [AuthField].
|
||||
class AuthDropdownField extends AuthField {
|
||||
/// Constructs an [AuthDropdownField] object.
|
||||
AuthDropdownField({
|
||||
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),
|
||||
}) {
|
||||
selectedValue = value ?? items.first;
|
||||
}
|
||||
|
||||
/// The list of items for the dropdown.
|
||||
final List<String> items;
|
||||
|
||||
/// A callback function triggered when the dropdown value changes.
|
||||
final Function(String?) onChanged;
|
||||
|
||||
/// The currently selected value in the dropdown.
|
||||
String? selectedValue;
|
||||
|
||||
/// The decoration for the dropdown.
|
||||
final InputDecoration? dropdownDecoration;
|
||||
|
||||
/// The padding around the dropdown.
|
||||
final EdgeInsets padding;
|
||||
|
||||
/// The text style for the dropdown.
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// The icon to be displayed with the dropdown.
|
||||
final Icon icon;
|
||||
|
||||
@override
|
||||
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;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
|
@ -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,20 +2,53 @@
|
|||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
abstract class AuthField {
|
||||
/// An abstract class representing a field in a Flutter form.
|
||||
///
|
||||
/// [T] represents the type of value stored in the field.
|
||||
abstract class AuthField<T> {
|
||||
/// Constructs an [AuthField] object.
|
||||
///
|
||||
/// [name] specifies the name 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).
|
||||
///
|
||||
/// [title] specifies the title widget of the field (optional).
|
||||
///
|
||||
/// [validators] defines a list of validation
|
||||
/// functions for the field (optional).
|
||||
AuthField({
|
||||
required this.name,
|
||||
required this.value,
|
||||
this.onValueChanged,
|
||||
this.title,
|
||||
this.validators = const [],
|
||||
this.value = '',
|
||||
});
|
||||
|
||||
/// The name of the field.
|
||||
final String name;
|
||||
final Widget? title;
|
||||
List<String? Function(String?)> validators;
|
||||
String value;
|
||||
|
||||
Widget build();
|
||||
/// The initial value of the field.
|
||||
T value;
|
||||
|
||||
/// A callback function triggered when the value of the field changes.
|
||||
final Function(T)? onValueChanged;
|
||||
|
||||
/// The title widget of the field.
|
||||
final Widget? title;
|
||||
|
||||
/// A list of validation functions for the field.
|
||||
List<String? Function(T?)> validators;
|
||||
|
||||
/// Builds the widget representing the field.
|
||||
///
|
||||
/// [context] The build context.
|
||||
///
|
||||
/// [onValueChanged] A function to be called when
|
||||
/// the value of the field changes.
|
||||
Widget build(BuildContext context, Function onValueChanged);
|
||||
}
|
||||
|
|
96
lib/src/model/auth_pass_field.dart
Normal file
96
lib/src/model/auth_pass_field.dart
Normal file
|
@ -0,0 +1,96 @@
|
|||
// SPDX-FileCopyrightText: 2024 Iconica
|
||||
//
|
||||
// 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";
|
||||
|
||||
/// A field for capturing password inputs in a Flutter form.
|
||||
///
|
||||
/// Extends [AuthField].
|
||||
class AuthPassField extends AuthField {
|
||||
/// Constructs an [AuthPassField] object.
|
||||
///
|
||||
/// [name] specifies the name of the field.
|
||||
///
|
||||
/// [textEditingController] controller for the password input (optional).
|
||||
///
|
||||
/// [title] specifies the title widget of 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).
|
||||
///
|
||||
/// [textStyle] defines the text style for the password input.
|
||||
///
|
||||
/// [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).
|
||||
///
|
||||
/// [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)).
|
||||
AuthPassField({
|
||||
required super.name,
|
||||
this.textEditingController,
|
||||
super.title,
|
||||
super.validators = const [],
|
||||
super.value = "",
|
||||
this.textStyle,
|
||||
this.onChange,
|
||||
this.iconSize,
|
||||
this.textFieldDecoration,
|
||||
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;
|
||||
|
||||
/// The controller for the password input.
|
||||
final TextEditingController? textEditingController;
|
||||
|
||||
@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,71 +2,84 @@
|
|||
//
|
||||
// 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.
|
||||
///
|
||||
/// Extends [AuthField].
|
||||
class AuthTextField extends AuthField {
|
||||
/// Constructs an [AuthTextField] object.
|
||||
///
|
||||
/// [name] specifies the name of the field.
|
||||
///
|
||||
/// [textEditingController] controller for the text input (optional).
|
||||
///
|
||||
/// [title] specifies the title widget of 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).
|
||||
///
|
||||
/// [textStyle] defines the text style for the text input.
|
||||
///
|
||||
/// [onChange] is a callback function triggered
|
||||
/// when the value of the field changes.
|
||||
///
|
||||
/// [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)).
|
||||
AuthTextField({
|
||||
required super.name,
|
||||
TextEditingController? textEditingController,
|
||||
super.title,
|
||||
super.validators = const [],
|
||||
super.value = '',
|
||||
this.obscureText = false,
|
||||
this.hintText,
|
||||
this.label,
|
||||
super.value = "",
|
||||
this.textStyle,
|
||||
this.onChange,
|
||||
this.hidden,
|
||||
this.onPassChanged,
|
||||
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;
|
||||
final bool obscureText;
|
||||
final String? hintText;
|
||||
final Widget? label;
|
||||
|
||||
/// 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;
|
||||
final bool? hidden;
|
||||
final Function(bool value)? onPassChanged;
|
||||
|
||||
/// The decoration for the text input field.
|
||||
final InputDecoration? textFieldDecoration;
|
||||
|
||||
/// The padding around the text input field.
|
||||
final EdgeInsets padding;
|
||||
|
||||
/// The type of text input.
|
||||
final TextInputType? textInputType;
|
||||
|
||||
@override
|
||||
Widget build() {
|
||||
Widget? suffix;
|
||||
|
||||
if (hidden != null) {
|
||||
if (hidden!) {
|
||||
suffix = GestureDetector(
|
||||
onTap: () {
|
||||
onPassChanged?.call(!hidden!);
|
||||
},
|
||||
child: const Icon(Icons.visibility),
|
||||
);
|
||||
} else {
|
||||
suffix = GestureDetector(
|
||||
onTap: () {
|
||||
onPassChanged?.call(!hidden!);
|
||||
},
|
||||
child: const Icon(Icons.visibility_off),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return TextFormField(
|
||||
Widget build(BuildContext context, Function onValueChanged) => Padding(
|
||||
padding: padding,
|
||||
child: TextFormField(
|
||||
keyboardType: textInputType,
|
||||
style: textStyle,
|
||||
decoration: InputDecoration(
|
||||
label: label,
|
||||
hintText: hintText,
|
||||
suffix: suffix,
|
||||
),
|
||||
decoration: textFieldDecoration,
|
||||
controller: textController,
|
||||
obscureText: hidden ?? obscureText,
|
||||
onChanged: (v) {
|
||||
value = v;
|
||||
onChange?.call(value);
|
||||
// ignore: avoid_dynamic_calls
|
||||
onValueChanged();
|
||||
},
|
||||
validator: (value) {
|
||||
for (var validator in validators) {
|
||||
|
@ -78,6 +91,6 @@ class AuthTextField extends AuthField {
|
|||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,58 +1,77 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
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';
|
||||
|
||||
class RegistrationScreen extends StatelessWidget {
|
||||
/// A screen for user registration.
|
||||
class RegistrationScreen extends StatefulWidget {
|
||||
/// Constructs a [RegistrationScreen] object.
|
||||
///
|
||||
/// [registrationOptions] specifies the registration options.
|
||||
const RegistrationScreen({
|
||||
required this.registrationOptions,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The registration options.
|
||||
final RegistrationOptions registrationOptions;
|
||||
|
||||
Future<void> register({
|
||||
required HashMap<String, String> values,
|
||||
@override
|
||||
RegistrationScreenState createState() => RegistrationScreenState();
|
||||
}
|
||||
|
||||
/// The state for [RegistrationScreen].
|
||||
class RegistrationScreenState extends State<RegistrationScreen> {
|
||||
/// Registers the user.
|
||||
Future<void> _register({
|
||||
required HashMap<String, dynamic> values,
|
||||
required void Function(int? pageToReturn) onError,
|
||||
}) async {
|
||||
try {
|
||||
var registered =
|
||||
await registrationOptions.registrationRepository.register(values);
|
||||
await widget.registrationOptions.beforeRegistration?.call();
|
||||
|
||||
var registered = await widget.registrationOptions.registrationRepository!
|
||||
.register(values);
|
||||
|
||||
if (registered == null) {
|
||||
registrationOptions.afterRegistration();
|
||||
widget.registrationOptions.afterRegistration();
|
||||
} else {
|
||||
var pageToReturn = registrationOptions.onError?.call(registered);
|
||||
var pageToReturn = widget.registrationOptions.onError?.call(registered);
|
||||
|
||||
onError(pageToReturn);
|
||||
}
|
||||
} catch (e) {
|
||||
onError(0);
|
||||
} on Exception catch (_) {
|
||||
onError(null);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var translations = registrationOptions.registrationTranslations;
|
||||
var translations = widget.registrationOptions.registrationTranslations;
|
||||
|
||||
return AuthScreen(
|
||||
steps: registrationOptions.registrationSteps,
|
||||
customAppBar: registrationOptions.customAppbarBuilder?.call(
|
||||
steps: widget.registrationOptions.steps,
|
||||
customAppBar: widget.registrationOptions.customAppbarBuilder?.call(
|
||||
translations.title,
|
||||
),
|
||||
onFinish: register,
|
||||
title: translations.title,
|
||||
onFinish: _register,
|
||||
appBarTitle: translations.title,
|
||||
submitBtnTitle: translations.registerBtn,
|
||||
nextBtnTitle: translations.nextStepBtn,
|
||||
previousBtnTitle: translations.previousStepBtn,
|
||||
nextButtonBuilder: registrationOptions.nextButtonBuilder,
|
||||
previousButtonBuilder: registrationOptions.previousButtonBuilder,
|
||||
customBackgroundColor: registrationOptions.backgroundColor,
|
||||
nextButtonBuilder: widget.registrationOptions.nextButtonBuilder,
|
||||
previousButtonBuilder: widget.registrationOptions.previousButtonBuilder,
|
||||
buttonMainAxisAlignment:
|
||||
widget.registrationOptions.buttonMainAxisAlignment,
|
||||
customBackgroundColor: widget.registrationOptions.backgroundColor,
|
||||
titleWidget: widget.registrationOptions.titleWidget,
|
||||
loginButton: widget.registrationOptions.loginButton,
|
||||
titleFlex: widget.registrationOptions.titleFlex,
|
||||
formFlex: widget.registrationOptions.formFlex,
|
||||
beforeTitleFlex: widget.registrationOptions.beforeTitleFlex,
|
||||
afterTitleFlex: widget.registrationOptions.afterTitleFlex,
|
||||
maxFormWidth: widget.registrationOptions.maxFormWidth,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
15
pubspec.yaml
15
pubspec.yaml
|
@ -4,14 +4,20 @@
|
|||
|
||||
name: flutter_registration
|
||||
description: A Flutter Registration package
|
||||
version: 1.2.0
|
||||
version: 3.0.0
|
||||
repository: https://github.com/Iconica-Development/flutter_registration
|
||||
|
||||
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <3.0.0"
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter_input_library:
|
||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
version: ^3.6.0
|
||||
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
|
@ -20,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