Compare commits

..

No commits in common. "master" and "2.0.3" have entirely different histories.

22 changed files with 540 additions and 733 deletions

4
.gitignore vendored
View file

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

View file

@ -3,13 +3,6 @@ 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

View file

@ -1,9 +1,4 @@
include: package:flutter_iconica_analysis/components_options.yaml
include: package:flutter_lints/flutter.yaml
# Possible to overwrite the rules from the package
analyzer:
exclude:
linter:
rules:
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -21,7 +21,7 @@ void main() {
}
class FlutterRegistrationDemo extends StatefulWidget {
const FlutterRegistrationDemo({super.key});
const FlutterRegistrationDemo({Key? key}) : super(key: key);
@override
State<FlutterRegistrationDemo> createState() =>
@ -86,7 +86,8 @@ class _FlutterRegistrationDemoState extends State<FlutterRegistrationDemo> {
}
class ProtectedScreen extends StatelessWidget {
const ProtectedScreen({super.key});
const ProtectedScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Scaffold(

View file

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

View file

@ -1,21 +1,19 @@
// 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 "package:flutter_input_library/flutter_input_library.dart"
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'
show BoolWidgetType;
export "src/config/registration_options.dart";
export "src/config/registration_translations.dart";
export "src/model/auth_bool_field.dart";
export "src/model/auth_drop_down.dart";
export "src/model/auth_exception.dart";
export "src/model/auth_field.dart";
export "src/model/auth_pass_field.dart";
export "src/model/auth_step.dart";
export "src/model/auth_text_field.dart";
export "src/registration_screen.dart";
export "src/service/registration_repository.dart";

View file

@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: BSD-3-Clause
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,8 +13,7 @@ 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.
///
@ -34,8 +33,7 @@ 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.
///
@ -46,7 +44,8 @@ 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,
@ -65,73 +64,34 @@ class AuthScreen extends StatefulWidget {
this.formFlex,
this.beforeTitleFlex,
this.afterTitleFlex,
this.maxFormWidth,
super.key,
}) : assert(steps.length > 0, "At least one step is required");
this.isLoading = false,
Key? key,
}) : assert(steps.length > 0, 'At least one step is required'),
super(key: key);
/// 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;
/// 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(Future<void> Function()? onPressed, String label,
int step, bool enabled)? nextButtonBuilder;
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;
final bool isLoading;
@override
State<AuthScreen> createState() => _AuthScreenState();
@ -157,11 +117,9 @@ class _AuthScreenState extends State<AuthScreen> {
void onPrevious() {
FocusScope.of(context).unfocus();
_validate(_pageController.page!.toInt() - 1);
unawaited(
_pageController.previousPage(
duration: _animationDuration,
curve: _animationCurve,
),
);
}
@ -186,38 +144,31 @@ class _AuthScreenState extends State<AuthScreen> {
await widget.onFinish(
values: values,
onError: (int? pageToReturn) {
if (pageToReturn == null) {
return;
}
_pageController.animateToPage(
pageToReturn,
onError: (int? pageToReturn) => _pageController.animateToPage(
pageToReturn ?? 0,
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;
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) {
var validationResult = validator(field.value);
String? validationResult = validator(field.value);
if (validationResult != null) {
// If any validator returns an error, mark step as invalid and break
isStepValid = false;
@ -236,9 +187,17 @@ class _AuthScreenState extends State<AuthScreen> {
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
return Scaffold(
backgroundColor: widget.customBackgroundColor ?? const Color(0xffFAF9F6),
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(
@ -247,9 +206,7 @@ class _AuthScreenState extends State<AuthScreen> {
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
children: <Widget>[
for (var currentStep = 0;
currentStep < widget.steps.length;
currentStep++)
for (var i = 0; i < widget.steps.length; i++)
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -277,145 +234,150 @@ class _AuthScreenState extends State<AuthScreen> {
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) ...[
in widget.steps[i].fields) ...[
if (field.title != null) ...[
wrapWithDefaultStyle(
style: theme.textTheme.headlineLarge!,
widget: field.title!,
),
field.title!,
],
field.build(context, () {
_validate(currentStep);
}),
_validate(i);
})
]
],
],
),
),
),
),
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]) ...[
Row(
mainAxisAlignment: widget
.buttonMainAxisAlignment !=
null
? widget.buttonMainAxisAlignment!
: (widget.previousButtonBuilder != null &&
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)
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(top: 20.0),
child: widget.loginButton,
),
],
),
],
),
],
),
),
),
);
}
Widget _stepButton({
required String buttonText,
required Future Function()? onTap,
}) {
var theme = Theme.of(context);
return Flexible(
padding: const EdgeInsets.only(
left: 16, bottom: 10, right: 8),
child: InkWell(
onTap: onTap,
onTap: onPrevious,
child: Container(
width: double.infinity,
constraints: const BoxConstraints(
maxWidth: 180,
),
width: 180,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
borderRadius:
BorderRadius.circular(20),
border: Border.all(
color: const Color(
0xff979797,
),
),
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(4),
padding:
const EdgeInsets.symmetric(
vertical: 2.0),
child: Text(
buttonText,
style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center,
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,
),
),
),
),
),
),
),
],
),
if (widget.loginButton != null)
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: widget.loginButton!,
),
],
),
],
),
],
),
),
),
);
}
Widget wrapWithDefaultStyle({
required Widget widget,
required TextStyle style,
}) =>
DefaultTextStyle(style: style, child: widget);
}

View file

@ -1,12 +1,11 @@
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);
}
}

View file

@ -2,20 +2,18 @@
//
// 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,
@ -29,8 +27,6 @@ class RegistrationOptions {
this.backgroundColor,
this.titleWidget,
this.loginButton,
this.maxFormWidth,
this.beforeRegistration,
}) {
if (registrationSteps == null || registrationSteps!.isEmpty) {
steps = RegistrationOptions.getDefaultSteps();
@ -46,7 +42,6 @@ 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.
@ -62,13 +57,8 @@ 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,
// ignore: avoid_positional_boolean_parameters
bool enabled,
)? nextButtonBuilder;
final Widget Function(Future<void> Function()? onPressed, String label,
int step, bool enabled)? nextButtonBuilder;
/// A function for customizing the "Previous" button.
final Widget? Function(VoidCallback onPressed, String label, int step)?
@ -98,12 +88,6 @@ class RegistrationOptions {
/// 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.
@ -131,43 +115,42 @@ 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",
name: 'email',
textEditingController: emailController,
value: initialEmail ?? "",
value: initialEmail ?? '',
title: titleBuilder?.call(translations.defaultEmailTitle) ??
Padding(
padding: const EdgeInsets.only(top: 180),
const Padding(
padding: EdgeInsets.only(top: 180),
child: Text(
translations.defaultEmailTitle,
'Enter your e-mail',
style: TextStyle(
color: Color(0xff71C6D1),
fontWeight: FontWeight.w800,
fontSize: 24,
),
),
),
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),
padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 20),
validators: [
// ignore: avoid_dynamic_calls
(email) => (email == null || email.isEmpty)
? translations.defaultEmailEmpty
: null,
@ -183,46 +166,43 @@ class RegistrationOptions {
AuthStep(
fields: [
AuthPassField(
name: "password",
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,
),
),
),
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),
padding: const EdgeInsets.symmetric(horizontal: 60, 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;
},
(value) => (value == null || value.isEmpty)
? translations.defaultPasswordValidatorMessage
: null,
],
),
],
),
];
}
}
AppBar _createCustomAppBar(String title) => AppBar(
iconTheme: const IconThemeData(color: Colors.black, size: 16),
AppBar _createCustomAppBar(String title) {
return AppBar(
title: Text(title),
backgroundColor: Colors.transparent,
backgroundColor: const Color(0xffFAF9F6),
);
}

View file

@ -2,10 +2,8 @@
//
// 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,
@ -21,74 +19,40 @@ 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 = "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";
: 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';
/// 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;
/// The message for a default password that is too short.
final String defaultPasswordToShortValidatorMessage;
/// create a copywith
// create a copywith
RegistrationTranslations copyWith({
String? title,
String? registerBtn,
@ -104,9 +68,8 @@ class RegistrationTranslations {
String? defaultPasswordLabel,
String? defaultPasswordHint,
String? defaultPasswordValidatorMessage,
String? defaultPasswordToShortValidatorMessage,
}) =>
RegistrationTranslations(
}) {
return RegistrationTranslations(
title: title ?? this.title,
registerBtn: registerBtn ?? this.registerBtn,
previousStepBtn: previousStepBtn ?? this.previousStepBtn,
@ -123,8 +86,6 @@ class RegistrationTranslations {
defaultPasswordHint: defaultPasswordHint ?? this.defaultPasswordHint,
defaultPasswordValidatorMessage: defaultPasswordValidatorMessage ??
this.defaultPasswordValidatorMessage,
defaultPasswordToShortValidatorMessage:
defaultPasswordToShortValidatorMessage ??
this.defaultPasswordToShortValidatorMessage,
);
}
}

View file

@ -2,19 +2,14 @@
//
// 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;
}

View file

@ -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,14 +22,11 @@ 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,
@ -41,26 +38,18 @@ 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) =>
FlutterFormInputBool(
Widget build(BuildContext context, Function onValueChanged) {
return FlutterFormInputBool(
widgetType: widgetType,
onChanged: (v) {
value = v;
onChange?.call(value);
// ignore: avoid_dynamic_calls
onValueChanged();
},
validator: (value) {
@ -76,3 +65,4 @@ class AuthBoolField extends AuthField {
rightWidget: rightWidget,
);
}
}

View file

@ -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,25 +41,23 @@ class AuthDropdownField extends AuthField {
final Icon icon;
@override
Widget build(BuildContext context, Function onValueChanged) => Padding(
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) => DropdownMenuItem<String>(
items: items.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
),
)
.toList(),
);
}).toList(),
onChanged: (newValue) {
selectedValue = newValue;
onChanged(newValue);
// ignore: avoid_dynamic_calls
onValueChanged();
},
validator: (value) {
@ -76,3 +74,4 @@ class AuthDropdownField extends AuthField {
),
);
}
}

View file

@ -2,14 +2,12 @@
//
// 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() => message;
String toString() {
return message;
}
}

View file

@ -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,13 +14,11 @@ 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,
@ -48,7 +46,6 @@ 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);
}

View file

@ -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,31 +18,25 @@ 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,
this.textEditingController,
TextEditingController? textEditingController,
super.title,
super.validators = const [],
super.value = "",
super.value = '',
this.textStyle,
this.onChange,
this.iconSize,
@ -50,26 +44,15 @@ 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;
/// The controller for the password input.
final TextEditingController? textEditingController;
@override
Widget build(BuildContext context, Function onValueChanged) => Padding(
Widget build(BuildContext context, Function onValueChanged) {
return Padding(
padding: padding,
child: FlutterFormInputPassword(
style: textStyle,
@ -78,7 +61,6 @@ class AuthPassField extends AuthField {
onChanged: (v) {
value = v;
onChange?.call(value);
// ignore: avoid_dynamic_calls
onValueChanged();
},
validator: (value) {
@ -94,3 +76,4 @@ class AuthPassField extends AuthField {
),
);
}
}

View file

@ -2,15 +2,12 @@
//
// 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;
}

View file

@ -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,68 +17,49 @@ 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;
/// The type of text input.
final TextInputType? textInputType;
@override
Widget build(BuildContext context, Function onValueChanged) => Padding(
Widget build(BuildContext context, Function onValueChanged) {
return 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) {
@ -94,3 +75,4 @@ class AuthTextField extends AuthField {
),
);
}
}

View file

@ -1,8 +1,8 @@
import "dart:collection";
import 'dart:collection';
import "package:flutter/material.dart";
import "package:flutter_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,
super.key,
});
Key? key,
}) : super(key: key);
/// The registration options.
final RegistrationOptions registrationOptions;
@ -23,13 +23,17 @@ 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 {
await widget.registrationOptions.beforeRegistration?.call();
setState(() {
_isLoading = true;
});
var registered = await widget.registrationOptions.registrationRepository!
.register(values);
@ -41,8 +45,12 @@ class RegistrationScreenState extends State<RegistrationScreen> {
onError(pageToReturn);
}
} on Exception catch (_) {
onError(null);
} catch (e) {
onError(0);
} finally {
setState(() {
_isLoading = false;
});
}
}
@ -67,11 +75,11 @@ 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,
afterTitleFlex: widget.registrationOptions.afterTitleFlex,
maxFormWidth: widget.registrationOptions.maxFormWidth,
);
}
}

View file

@ -2,10 +2,8 @@
//
// 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);
}

View file

@ -4,19 +4,20 @@
name: flutter_registration
description: A Flutter Registration package
version: 3.0.0
version: 2.0.3
repository: https://github.com/Iconica-Development/flutter_registration
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=2.18.0 <3.0.0"
flutter: ">=1.17.0"
dependencies:
flutter_input_library:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: ^3.6.0
git:
url: https://github.com/Iconica-Development/flutter_input_library
ref: 3.1.0
flutter:
sdk: flutter
@ -26,9 +27,6 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
flutter_lints: ^2.0.0
flutter:

View file

@ -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);
});
}