feat: default styling

This commit is contained in:
mike doornenbal 2024-08-08 10:57:20 +02:00
parent cf6e30abec
commit 4bad032588
21 changed files with 671 additions and 521 deletions

View file

@ -3,7 +3,10 @@ SPDX-FileCopyrightText: 2022 Iconica
SPDX-License-Identifier: GPL-3.0-or-later
-->
# 3.0.0
- fix: fixed the issue with the scrollController when the `pageToReturnTo` is null.
- feat: Added default styling and theme.
- feat: Added Iconica linter.
# 2.0.4
- feat: added maxFormWidth to AuthScreen

View file

@ -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:

View file

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

View file

@ -66,10 +66,10 @@ packages:
dependency: transitive
description:
name: flutter_input_library
sha256: "346823caa633d47ad21d8ce92f3c2ed460619fd5447999c35cb171da5e86af06"
sha256: db8d9d57c31f166ed7c4ec3d060d18891533c57877a30c6c2668231efa1a44f8
url: "https://forgejo.internal.iconica.nl/api/packages/internal/pub/"
source: hosted
version: "3.3.1"
version: "3.6.0"
flutter_lints:
dependency: "direct dev"
description:

View file

@ -1,19 +1,21 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
/// Flutter registration component that provides a registration screen with multiple registration steps.
/// Flutter registration component that provides a registration
/// screen with multiple registration steps.
library flutter_registration;
export 'src/config/registration_options.dart';
export 'src/config/registration_translations.dart';
export 'src/model/auth_exception.dart';
export 'src/model/auth_field.dart';
export 'src/model/auth_step.dart';
export 'src/model/auth_text_field.dart';
export 'src/model/auth_bool_field.dart';
export 'src/model/auth_drop_down.dart';
export 'src/model/auth_pass_field.dart';
export 'src/registration_screen.dart';
export 'src/service/registration_repository.dart';
export 'package:flutter_input_library/flutter_input_library.dart'
export "package:flutter_input_library/flutter_input_library.dart"
show BoolWidgetType;
export "src/config/registration_options.dart";
export "src/config/registration_translations.dart";
export "src/model/auth_bool_field.dart";
export "src/model/auth_drop_down.dart";
export "src/model/auth_exception.dart";
export "src/model/auth_field.dart";
export "src/model/auth_pass_field.dart";
export "src/model/auth_step.dart";
export "src/model/auth_text_field.dart";
export "src/registration_screen.dart";
export "src/service/registration_repository.dart";

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,7 +13,8 @@ class AuthScreen extends StatefulWidget {
///
/// [appBarTitle] specifies the title of the app bar.
///
/// [onFinish] is a function called upon completion of the authentication process.
/// [onFinish] is a function called upon
/// completion of the authentication process.
///
/// [steps] is a list of authentication steps to be completed.
///
@ -33,7 +34,8 @@ class AuthScreen extends StatefulWidget {
///
/// [previousButtonBuilder] allows customization of the previous button.
///
/// [titleWidget] specifies a custom widget to be displayed at the top of the screen.
/// [titleWidget] specifies a custom widget
/// to be displayed at the top of the screen.
///
/// [loginButton] specifies a custom login button widget.
///
@ -44,8 +46,7 @@ class AuthScreen extends StatefulWidget {
/// [beforeTitleFlex] specifies the flex value before the title widget.
///
/// [afterTitleFlex] specifies the flex value after the title widget.
///
/// [isLoading] indicates whether the screen is in a loading state.
const AuthScreen({
required this.appBarTitle,
required this.steps,
@ -64,35 +65,72 @@ class AuthScreen extends StatefulWidget {
this.formFlex,
this.beforeTitleFlex,
this.afterTitleFlex,
this.isLoading = false,
this.maxFormWidth,
Key? key,
}) : assert(steps.length > 0, 'At least one step is required'),
super(key: key);
super.key,
}) : assert(steps.length > 0, "At least one step is required");
/// The title of the app bar.
final String appBarTitle;
/// A function called upon completion of the authentication process.
final Future<void> Function({
required HashMap<String, dynamic> values,
required void Function(int? pageToReturn) onError,
}) onFinish;
/// The authentication steps to be completed.
final List<AuthStep> steps;
/// The title of the submit button.
final String submitBtnTitle;
/// The title of the next button.
final String nextBtnTitle;
/// The title of the previous button.
final String previousBtnTitle;
/// A custom app bar widget.
final AppBar? customAppBar;
/// The alignment of the buttons.
final MainAxisAlignment? buttonMainAxisAlignment;
/// The background color of the screen.
final Color? customBackgroundColor;
final Widget Function(Future<void> Function()? onPressed, String label,
int step, bool enabled)? nextButtonBuilder;
/// A custom widget for the button.
final Widget Function(
Future<void> Function()? onPressed,
String label,
int step,
// ignore: avoid_positional_boolean_parameters
bool enabled,
)? nextButtonBuilder;
/// A custom widget for the button.
final Widget? Function(VoidCallback onPressed, String label, int step)?
previousButtonBuilder;
/// A custom widget for the title.
final Widget? titleWidget;
/// A custom widget for the login button.
final Widget? loginButton;
/// The flex value for the title widget.
final int? titleFlex;
/// The flex value for the form widget.
final int? formFlex;
/// The flex value before the title widget.
final int? beforeTitleFlex;
/// The flex value after the title widget.
final int? afterTitleFlex;
final bool isLoading;
/// The maximum width of the form.
final double? maxFormWidth;
@override
@ -119,9 +157,11 @@ class _AuthScreenState extends State<AuthScreen> {
void onPrevious() {
FocusScope.of(context).unfocus();
_validate(_pageController.page!.toInt() - 1);
unawaited(
_pageController.previousPage(
duration: _animationDuration,
curve: _animationCurve,
),
);
}
@ -146,31 +186,38 @@ class _AuthScreenState extends State<AuthScreen> {
await widget.onFinish(
values: values,
onError: (int? pageToReturn) => _pageController.animateToPage(
pageToReturn ?? 0,
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) {
bool isStepValid = true;
var isStepValid = true;
// Loop through each field in the current step
for (var field in widget.steps[currentPage].fields) {
for (var validator in field.validators) {
String? validationResult = validator(field.value);
var validationResult = validator(field.value);
if (validationResult != null) {
// If any validator returns an error, mark step as invalid and break
isStepValid = false;
@ -189,17 +236,9 @@ class _AuthScreenState extends State<AuthScreen> {
@override
Widget build(BuildContext context) {
return widget.isLoading
? const Center(
child: SizedBox(
height: 120,
width: 120,
child: CircularProgressIndicator(),
),
)
: Scaffold(
backgroundColor:
widget.customBackgroundColor ?? const Color(0xffFAF9F6),
var theme = Theme.of(context);
return Scaffold(
backgroundColor: widget.customBackgroundColor ?? const Color(0xffFAF9F6),
appBar: _appBar,
body: SafeArea(
child: Form(
@ -208,7 +247,9 @@ class _AuthScreenState extends State<AuthScreen> {
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
children: <Widget>[
for (var i = 0; i < widget.steps.length; i++)
for (var currentStep = 0;
currentStep < widget.steps.length;
currentStep++)
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -244,14 +285,17 @@ class _AuthScreenState extends State<AuthScreen> {
child: Column(
children: [
for (AuthField field
in widget.steps[i].fields) ...[
in widget.steps[currentStep].fields) ...[
if (field.title != null) ...[
field.title!,
wrapWithDefaultStyle(
style: theme.textTheme.headlineLarge!,
widget: field.title!,
),
],
field.build(context, () {
_validate(i);
})
]
_validate(currentStep);
}),
],
],
),
),
@ -259,124 +303,70 @@ class _AuthScreenState extends State<AuthScreen> {
),
Column(
children: [
Row(
mainAxisAlignment: widget
.buttonMainAxisAlignment !=
null
? widget.buttonMainAxisAlignment!
: (widget.previousButtonBuilder != null &&
SizedBox(
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
),
child: Row(
mainAxisAlignment: widget.steps.first !=
widget.steps[currentStep]
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.end,
children: [
if (widget.steps.first !=
widget.steps[currentStep]) ...[
widget.previousButtonBuilder?.call(
onPrevious,
widget.previousBtnTitle,
i,
) ==
null)
? MainAxisAlignment.start
: widget.steps.first != widget.steps[i]
? MainAxisAlignment.center
: MainAxisAlignment.end,
children: [
if (widget.previousButtonBuilder == null) ...[
if (widget.steps.first != widget.steps[i])
Padding(
padding: const EdgeInsets.only(
left: 16, bottom: 10, right: 8),
child: InkWell(
onTap: onPrevious,
child: Container(
width: 180,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(20),
border: Border.all(
color: const Color(
0xff979797,
currentStep,
) ??
_stepButton(
buttonText: widget.previousBtnTitle,
onTap: () async => onPrevious(),
),
const SizedBox(
width: 8,
),
),
child: Center(
child: Padding(
padding:
const EdgeInsets.symmetric(
vertical: 2.0),
child: Text(
widget.previousBtnTitle,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
),
),
),
] else if (widget.previousButtonBuilder?.call(
onPrevious,
widget.previousBtnTitle,
i) !=
null) ...[
widget.previousButtonBuilder!.call(
onPrevious, widget.previousBtnTitle, i)!
],
widget.nextButtonBuilder?.call(
!_formValid
? null
: () async {
await onNext(widget.steps[i]);
await onNext(
widget.steps[currentStep],
);
},
widget.steps.last == widget.steps[i]
widget.steps.last ==
widget.steps[currentStep]
? widget.submitBtnTitle
: widget.nextBtnTitle,
i,
currentStep,
_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]
_stepButton(
buttonText: widget.steps.last ==
widget.steps[currentStep]
? widget.submitBtnTitle
: widget.nextBtnTitle,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
),
),
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!,
child: widget.loginButton,
),
],
),
@ -388,4 +378,44 @@ class _AuthScreenState extends State<AuthScreen> {
),
);
}
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);
}

View file

@ -1,11 +1,12 @@
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:flutter_registration/flutter_registration.dart';
import "dart:collection";
import "package:flutter/material.dart";
import "package:flutter_registration/flutter_registration.dart";
/// A registration repository that does nothing.
class ExampleRegistrationRepository with RegistrationRepository {
@override
Future<String?> register(HashMap values) {
debugPrint('register $values');
debugPrint("register $values");
return Future.value(null);
}
}

View file

@ -2,18 +2,20 @@
//
// SPDX-License-Identifier: BSD-3-Clause
import 'dart:async';
import "dart:async";
import 'package:flutter/material.dart';
import 'package:flutter_registration/flutter_registration.dart';
import 'package:flutter_registration/src/config/example_registration_repository.dart';
import "package:flutter/material.dart";
import "package:flutter_registration/flutter_registration.dart";
import "package:flutter_registration/src/config/example_registration_repository.dart";
/// A set of options for configuring the registration process in a Flutter application.
/// A set of options for configuring the
/// registration process in a Flutter application.
class RegistrationOptions {
/// Registration options Constructor
RegistrationOptions({
required this.afterRegistration,
this.registrationRepository,
this.registrationSteps,
required this.afterRegistration,
this.titleFlex,
this.formFlex,
this.beforeTitleFlex,
@ -28,6 +30,7 @@ class RegistrationOptions {
this.titleWidget,
this.loginButton,
this.maxFormWidth,
this.beforeRegistration,
}) {
if (registrationSteps == null || registrationSteps!.isEmpty) {
steps = RegistrationOptions.getDefaultSteps();
@ -43,6 +46,7 @@ class RegistrationOptions {
/// The steps involved in the registration process.
final List<AuthStep>? registrationSteps;
/// The steps involved in the registration process.
List<AuthStep> steps = [];
/// A function that handles errors during registration.
@ -58,8 +62,13 @@ class RegistrationOptions {
final AppBar Function(String title)? customAppbarBuilder;
/// A function for customizing the "Next" button.
final Widget Function(Future<void> Function()? onPressed, String label,
int step, bool enabled)? nextButtonBuilder;
final Widget Function(
Future<void> Function()? onPressed,
String label,
int step,
// ignore: avoid_positional_boolean_parameters
bool enabled,
)? nextButtonBuilder;
/// A function for customizing the "Previous" button.
final Widget? Function(VoidCallback onPressed, String label, int step)?
@ -92,6 +101,9 @@ class RegistrationOptions {
/// The maximum width of the form. Defaults to 300.
final double? maxFormWidth;
/// This function gets called before the user gets registered.
final Future<void> Function()? beforeRegistration;
/// Generates default registration steps.
///
/// [emailController] controller for email input.
@ -119,42 +131,43 @@ 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) ??
const Padding(
padding: EdgeInsets.only(top: 180),
Padding(
padding: const EdgeInsets.only(top: 180),
child: Text(
'Enter your e-mail',
style: TextStyle(
color: Color(0xff71C6D1),
fontWeight: FontWeight.w800,
fontSize: 24,
),
translations.defaultEmailTitle,
),
),
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,
@ -170,43 +183,46 @@ 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),
textStyle: textStyle,
validators: [
(value) => (value == null || value.isEmpty)
? translations.defaultPasswordValidatorMessage
: null,
(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;
},
],
),
],
),
];
}
}
AppBar _createCustomAppBar(String title) {
return AppBar(
AppBar _createCustomAppBar(String title) => AppBar(
iconTheme: const IconThemeData(color: Colors.black, size: 16),
title: Text(title),
backgroundColor: const Color(0xffFAF9F6),
backgroundColor: Colors.transparent,
);
}

View file

@ -2,8 +2,10 @@
//
// SPDX-License-Identifier: BSD-3-Clause
/// Holds all the translations for the standard elements on the registration screen.
/// Holds all the translations for the standard elements
/// on the registration screen.
class RegistrationTranslations {
/// Constructs a [RegistrationTranslations] object.
const RegistrationTranslations({
required this.title,
required this.registerBtn,
@ -19,40 +21,74 @@ class RegistrationTranslations {
required this.defaultPasswordLabel,
required this.defaultPasswordHint,
required this.defaultPasswordValidatorMessage,
required this.defaultPasswordToShortValidatorMessage,
});
/// Constructs a [RegistrationTranslations] object with empty strings.
const RegistrationTranslations.empty()
: title = '',
registerBtn = 'Register',
previousStepBtn = 'Previous',
nextStepBtn = 'Next',
closeBtn = 'Close',
defaultEmailTitle = 'What is your email?',
defaultEmailLabel = '',
defaultEmailHint = 'Email address',
defaultEmailEmpty = 'Please enter your email address.',
defaultEmailValidatorMessage = 'Please enter a valid email address.',
defaultPasswordTitle = 'Choose a password',
defaultPasswordLabel = 'password',
defaultPasswordHint = '',
defaultPasswordValidatorMessage = 'Enter a valid password';
: title = "",
registerBtn = "Register",
previousStepBtn = "Previous",
nextStepBtn = "Next",
closeBtn = "Close",
defaultEmailTitle = "enter your email address",
defaultEmailLabel = "",
defaultEmailHint = "Email address",
defaultEmailEmpty = "Please enter your email address.",
defaultEmailValidatorMessage = "Please enter a valid email address.",
defaultPasswordTitle = "choose a password",
defaultPasswordLabel = "",
defaultPasswordHint = "Password",
defaultPasswordValidatorMessage = "Enter a valid password",
defaultPasswordToShortValidatorMessage =
"Password needs to be at least 6 characters long";
/// The title of the registration screen.
final String title;
/// The text for the registration button.
final String registerBtn;
/// The text for the previous step button.
final String previousStepBtn;
/// The text for the next step button.
final String nextStepBtn;
/// The text for the close button.
final String closeBtn;
/// The title for the default email field.
final String defaultEmailTitle;
/// The label for the default email field.
final String defaultEmailLabel;
/// The hint for the default email field.
final String defaultEmailHint;
/// The message for an empty default email field.
final String defaultEmailEmpty;
/// The message for an invalid default email field.
final String defaultEmailValidatorMessage;
/// The title for the default password field.
final String defaultPasswordTitle;
/// The label for the default password field.
final String defaultPasswordLabel;
/// The hint for the default password field.
final String defaultPasswordHint;
/// The message for an invalid default password field.
final String defaultPasswordValidatorMessage;
// create a copywith
/// The message for a default password that is too short.
final String defaultPasswordToShortValidatorMessage;
/// create a copywith
RegistrationTranslations copyWith({
String? title,
String? registerBtn,
@ -68,8 +104,9 @@ class RegistrationTranslations {
String? defaultPasswordLabel,
String? defaultPasswordHint,
String? defaultPasswordValidatorMessage,
}) {
return RegistrationTranslations(
String? defaultPasswordToShortValidatorMessage,
}) =>
RegistrationTranslations(
title: title ?? this.title,
registerBtn: registerBtn ?? this.registerBtn,
previousStepBtn: previousStepBtn ?? this.previousStepBtn,
@ -86,6 +123,8 @@ class RegistrationTranslations {
defaultPasswordHint: defaultPasswordHint ?? this.defaultPasswordHint,
defaultPasswordValidatorMessage: defaultPasswordValidatorMessage ??
this.defaultPasswordValidatorMessage,
defaultPasswordToShortValidatorMessage:
defaultPasswordToShortValidatorMessage ??
this.defaultPasswordToShortValidatorMessage,
);
}
}

View file

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

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,11 +22,14 @@ class AuthBoolField extends AuthField {
///
/// [value] specifies the initial value of the field (default is false).
///
/// [leftWidget] is a widget to be displayed on the left side of the boolean widget.
/// [leftWidget] is a widget to be displayed on the
/// left side of the boolean widget.
///
/// [rightWidget] is a widget to be displayed on the right side of the boolean widget.
/// [rightWidget] is a widget to be displayed on the
/// right side of the boolean widget.
///
/// [onChange] is a callback function triggered when the value of the field changes.
/// [onChange] is a callback function triggered when
/// the value of the field changes.
AuthBoolField({
required super.name,
required this.widgetType,
@ -38,18 +41,26 @@ class AuthBoolField extends AuthField {
this.onChange,
});
/// A widget to be displayed on the left side of the boolean widget.
final Widget? leftWidget;
/// A widget to be displayed on the right side of the boolean widget.
final Widget? rightWidget;
/// The type of boolean widget to use.
final BoolWidgetType widgetType;
/// A callback function triggered when the value of the field changes.
final Function(String value)? onChange;
@override
Widget build(BuildContext context, Function onValueChanged) {
return FlutterFormInputBool(
Widget build(BuildContext context, Function onValueChanged) =>
FlutterFormInputBool(
widgetType: widgetType,
onChanged: (v) {
value = v;
onChange?.call(value);
// ignore: avoid_dynamic_calls
onValueChanged();
},
validator: (value) {
@ -65,4 +76,3 @@ 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,23 +41,25 @@ class AuthDropdownField extends AuthField {
final Icon icon;
@override
Widget build(BuildContext context, Function onValueChanged) {
return Padding(
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) {
return DropdownMenuItem<String>(
items: items
.map(
(String value) => DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
)
.toList(),
onChanged: (newValue) {
selectedValue = newValue;
onChanged(newValue);
// ignore: avoid_dynamic_calls
onValueChanged();
},
validator: (value) {
@ -74,4 +76,3 @@ class AuthDropdownField extends AuthField {
),
);
}
}

View file

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

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,11 +14,13 @@ abstract class AuthField<T> {
///
/// [value] specifies the initial value of the field.
///
/// [onValueChanged] is a callback function triggered when the value of the field changes (optional).
/// [onValueChanged] is a callback function triggered when the
/// value of the field changes (optional).
///
/// [title] specifies the title widget of the field (optional).
///
/// [validators] defines a list of validation functions for the field (optional).
/// [validators] defines a list of validation
/// functions for the field (optional).
AuthField({
required this.name,
required this.value,
@ -46,6 +48,7 @@ abstract class AuthField<T> {
///
/// [context] The build context.
///
/// [onValueChanged] A function to be called when the value of the field changes.
/// [onValueChanged] A function to be called when
/// the value of the field changes.
Widget build(BuildContext context, Function onValueChanged);
}

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,25 +18,31 @@ class AuthPassField extends AuthField {
///
/// [title] specifies the title widget of the field (optional).
///
/// [validators] defines a list of validation functions for the field (optional).
/// [validators] defines a list of validation
/// functions for the field (optional).
///
/// [value] specifies the initial value of the field (default is an empty string).
/// [value] specifies the initial value of the
/// field (default is an empty string).
///
/// [textStyle] defines the text style for the password input.
///
/// [onChange] is a callback function triggered when the value of the field changes.
/// [onChange] is a callback function triggered when
/// the value of the field changes.
///
/// [iconSize] specifies the size of the icon displayed with the password input (optional).
/// [iconSize] specifies the size of the icon displayed
/// with the password input (optional).
///
/// [textFieldDecoration] defines the decoration for the password input field (optional).
/// [textFieldDecoration] defines the decoration for the
/// password input field (optional).
///
/// [padding] defines the padding around the password input field (default is EdgeInsets.all(8.0)).
/// [padding] defines the padding around the password input
/// field (default is EdgeInsets.all(8.0)).
AuthPassField({
required super.name,
TextEditingController? textEditingController,
this.textEditingController,
super.title,
super.validators = const [],
super.value = '',
super.value = "",
this.textStyle,
this.onChange,
this.iconSize,
@ -44,15 +50,26 @@ 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) {
return Padding(
Widget build(BuildContext context, Function onValueChanged) => Padding(
padding: padding,
child: FlutterFormInputPassword(
style: textStyle,
@ -61,6 +78,7 @@ class AuthPassField extends AuthField {
onChanged: (v) {
value = v;
onChange?.call(value);
// ignore: avoid_dynamic_calls
onValueChanged();
},
validator: (value) {
@ -76,4 +94,3 @@ class AuthPassField extends AuthField {
),
);
}
}

View file

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

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,49 +17,68 @@ 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) {
return Padding(
Widget build(BuildContext context, Function onValueChanged) => Padding(
padding: padding,
child: TextFormField(
keyboardType: textInputType,
style: textStyle,
decoration: textFieldDecoration,
controller: textController,
onChanged: (v) {
value = v;
onChange?.call(value);
// ignore: avoid_dynamic_calls
onValueChanged();
},
validator: (value) {
@ -75,4 +94,3 @@ 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,
Key? key,
}) : super(key: key);
super.key,
});
/// The registration options.
final RegistrationOptions registrationOptions;
@ -23,17 +23,13 @@ class RegistrationScreen extends StatefulWidget {
/// The state for [RegistrationScreen].
class RegistrationScreenState extends State<RegistrationScreen> {
bool _isLoading = false;
/// Registers the user.
Future<void> _register({
required HashMap<String, dynamic> values,
required void Function(int? pageToReturn) onError,
}) async {
try {
setState(() {
_isLoading = true;
});
await widget.registrationOptions.beforeRegistration?.call();
var registered = await widget.registrationOptions.registrationRepository!
.register(values);
@ -45,12 +41,8 @@ class RegistrationScreenState extends State<RegistrationScreen> {
onError(pageToReturn);
}
} catch (e) {
onError(0);
} finally {
setState(() {
_isLoading = false;
});
} on Exception catch (_) {
onError(null);
}
}
@ -75,7 +67,6 @@ class RegistrationScreenState extends State<RegistrationScreen> {
customBackgroundColor: widget.registrationOptions.backgroundColor,
titleWidget: widget.registrationOptions.titleWidget,
loginButton: widget.registrationOptions.loginButton,
isLoading: _isLoading,
titleFlex: widget.registrationOptions.titleFlex,
formFlex: widget.registrationOptions.formFlex,
beforeTitleFlex: widget.registrationOptions.beforeTitleFlex,

View file

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

View file

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

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