Compare commits

...

71 commits

Author SHA1 Message Date
Gorter-dev
ee29893bd9
Merge pull request #31 from Iconica-Development/feature/default_style
feat: default styling
2024-08-09 14:32:34 +02:00
mike doornenbal
4bad032588 feat: default styling 2024-08-09 14:22:25 +02:00
Gorter-dev
cf6e30abec
Merge pull request #29 from Iconica-Development/chore/deploy
chore: ready the package for deployment to the pub server
2024-07-22 14:55:50 +02:00
Bart Ribbers
ce80a96958 chore: ready the package for deployment to the pub server 2024-07-11 18:06:46 +02:00
Bart Ribbers
71259c7f78 chore: add fvm configuration to gitignore 2024-07-11 18:03:05 +02:00
Gorter-dev
d82df68989
Merge pull request #27 from Iconica-Development/bugfix/registration_field_size
fix: add default field size
2024-04-22 13:07:04 +02:00
mike doornenbal
c91a1c0856 fix: add default field size 2024-04-22 13:01:35 +02:00
Gorter-dev
444b11a44f
Merge pull request #26 from Iconica-Development/feature/default_styling
feat: add default styling
2024-04-19 11:27:47 +02:00
mike doornenbal
1be83c7013 feat: add default styling 2024-04-19 11:25:09 +02:00
Gorter-dev
990259dabc
Merge pull request #25 from Iconica-Development/2.0.3
feat: add default registrationOptions
2024-04-18 09:39:32 +02:00
mike doornenbal
d849a1834a feat: add default registrationOptions 2024-04-17 15:26:08 +02:00
Gorter-dev
05b143a5bd
Merge pull request #24 from Iconica-Development/bugfix/input-values-clearing
fix: input values clearing on next step
2024-04-04 09:51:30 +02:00
mike doornenbal
a44a419bc4 fix: input values clearing on next step 2024-04-04 09:49:44 +02:00
Gorter-dev
7890c17587
Merge pull request #20 from Iconica-Development/doc/improve-documentation
refactor: add documentation to files
2024-04-03 14:56:58 +02:00
Vick Top
3ec92aa68c refactor: add documentation to files 2024-02-20 13:59:43 +01:00
Freek van de Ven
7a4bcd8b3f
Merge pull request #19 from Iconica-Development/feat/registration-progress-indicator
feat(registration): add progress indicator for on finish
2024-02-15 17:45:35 +01:00
FahadFahim71
3884ef08b1 feat(registration): add progress indicator for on finish 2024-02-15 17:16:04 +01:00
Fahad Fahim
dcea299aa6
Merge pull request #18 from CodeThomnics/bugfix/button_alignment
bugfix: Fix flex overflow, add own padding and alignment option for b…
2024-02-15 16:21:48 +01:00
mike doornenbal
957abd2ac2 Merge pull request #17 from Iconica-Development/fix/small-fixes
feat: small fixes
2024-02-15 09:57:57 +01:00
CodeThomnics
0f28c7084e bugfix: Fix flex overflow, add own padding and alignment option for buttons
Closes: #14
2024-02-14 20:04:22 +01:00
mike doornenbal
44871bf44e
Merge pull request #17 from Iconica-Development/fix/small-fixes
feat: small fixes
2024-02-14 11:58:41 +01:00
Freek van de Ven
9ebb917fb8 feat: small fixes 2024-02-14 11:56:36 +01:00
mike doornenbal
0472ebc4c5
Merge pull request #13 from Iconica-Development/feat/buttons_and_fields
FInish flutter_registration for traffic university
2024-02-14 11:36:48 +01:00
FahadFahim71
4b855f7488 feat: update changelog.md and version number 2024-02-14 10:55:35 +01:00
FahadFahim71
81fab79e81 fix(keyboard-focus): add unfocus for onPrevious 2024-02-14 10:44:14 +01:00
FahadFahim71
855e12f6ae feat(auth-screen): add flexible spacing between fields 2024-02-14 09:57:29 +01:00
Freek van de Ven
40c4bfea89
Merge pull request #16 from Iconica-Development/update-component-documentation-workflow-correct
Add component-documentation.yml correct
2024-02-14 08:03:19 +01:00
Vick Top
ced19fe61a feat(documentation): Create component-documentation.yml workflow file 2024-02-13 13:34:32 +01:00
Vick Top
7cfe7d5112 chore: Remove old component-documentation.yml 2024-02-13 13:34:32 +01:00
Freek van de Ven
7bb87acf65
Merge pull request #15 from Iconica-Development/update-component-documentation-workflow
Add component-documentation.yml
2024-02-12 20:11:26 +01:00
Vick Top
3ca9e7bde7 feat(documentation): Create component-documentation.yml workflow file 2024-02-12 19:05:54 +01:00
Freek van de Ven
277b38f39e feat: add validation to disable next button 2024-02-09 18:36:22 +01:00
FahadFahim71
15aa74ebda fix: export auth_pass_field 2024-02-09 14:29:13 +01:00
Jacques
49b9ece2d5 fix: Fix button placement and added step to button builders 2024-02-08 11:54:07 +01:00
FahadFahim71
e112c64ee7 fix: correctly align next previous buttons 2024-02-08 10:29:32 +01:00
FahadFahim71
7abb060306 feat: add auth drop down field 2024-02-06 16:28:51 +01:00
Jacques
f328df84dd fix: Fixed alignment and spacing when opening keyboard 2024-02-06 13:15:48 +01:00
Jacques
c86fa1ca5f fix: Small refactor and brought back the normal alignment for the screens 2024-02-06 10:50:02 +01:00
Jacques
79fe400842 feat(pass): Add dedicated password screen that manages state internally 2024-02-06 09:40:43 +01:00
Jacques
078574c214 Merge commit '7012942ce50a2de8c8a481595d8bca6e7a02c4de' into feat/buttons_and_fields 2024-02-05 13:02:47 +01:00
Jacques
46e2d960af feat(bool): Add a boolean field. Can be used for accepting terms and conditions 2024-02-05 13:00:21 +01:00
FahadFahim71
7012942ce5 feat: add title widget and login button builder 2024-02-02 22:18:03 +01:00
FahadFahim71
f1663afa1b feat: expose input decoration in authtextfield 2024-02-02 11:19:21 +01:00
Jacques
d1ad003c22 feat(buttons): Added the possiblity to only have a next button by return zero on the previous button builder 2024-02-01 14:40:25 +01:00
Gorter-dev
5bc388677b
Merge pull request #11 from Iconica-Development/feature/async_on_next
Feature/async on next
2023-10-16 15:54:44 +02:00
Bugfix Jacques
6666e33391 fix: Changelog 2023-10-16 15:47:39 +02:00
Bugfix Jacques
9886c71fcf feat: ability to wait for the onfinished 2023-10-16 15:46:23 +02:00
Gorter-dev
e2b73bdb7b
Merge pull request #10 from Iconica-Development/feature/return_page_on_error
Feature/return page on error
2023-10-03 14:40:06 +02:00
Bugfix Jacques
059ba3c754 feat: Now returning page 2023-10-03 14:38:52 +02:00
Bugfix Jacques
fffafdffd5 fix: Change log and version 2023-10-03 14:06:36 +02:00
Bugfix Jacques
f8ecce95a0 feat: Added the ability to decide to go back to the first page 2023-10-03 14:05:37 +02:00
Jacques Doeleman
a9380b0eb7
Merge pull request #9 from Iconica-Development/bugfix/change_error
Bugfix/change error
2023-09-20 17:00:01 +02:00
Bugfix Jacques
a4cbbc5e74 fix: Update to dart format 2023-09-20 16:58:08 +02:00
Bugfix Jacques
63d4773034 fix: Changes version and updated changelog 2023-09-20 11:48:34 +02:00
Bugfix Jacques
e715b1192b feat: register function can now return a nullable string
Works like a validator
2023-09-20 11:45:55 +02:00
mike doornenbal
caf68b6d8f fix: change error and background color 2023-09-19 11:16:43 +02:00
Thomas Klein Langenhorst
38624ac637
Merge pull request #7 from Iconica-Development/feature/change_readme
feat: improved README, fixed example, added GIF, added Github Actions…
2023-03-31 11:35:16 +02:00
Thomas Klein Langenhorst
74a65923de feat: improved README, fixed example, added GIF, added Github Actions, added dependabot 2023-03-31 11:30:37 +02:00
Thomas Klein Langenhorst
9da59f3d75 bump version to 0.5.0 2023-03-31 11:21:50 +02:00
Thomas Klein Langenhorst
a4e39977ad
Merge pull request #6 from Iconica-Development/feature/add_custom_background
- feat: add customBackgroundColor to AuthScreen
2023-03-31 11:20:50 +02:00
Thomas Klein Langenhorst
e2f65cdde9 - feat: add customBackgroundColor to AuthScreen
- fix: fix linter
- fix: fix translations to English
2023-03-31 11:20:21 +02:00
Gorter-dev
0ec09155f9
Merge pull request #5 from Iconica-Development/feature/password_visibilty
feat: Added the abilty to show and hide the passwords
2023-03-09 09:31:49 +01:00
Bugfix Jacques
84d7519da6 feat: Added the abilty to show and hide the passwords 2023-03-09 09:12:41 +01:00
Gorter-dev
67d4aa1c45
Merge pull request #4 from Iconica-Development/feature/initial_email 2023-02-24 14:26:12 +01:00
Bugfix Jacques
bcb9328c4a feat: Added the ability to add an initial value to the default email 2023-02-24 14:20:36 +01:00
Gorter-dev
63a3223d0f
Merge pull request #3 from Iconica-Development/feature/added_styling
feat: Added second textfield too password and added more styling options
2023-02-16 16:17:27 +01:00
Bugfix Jacques
1666185920 feat: Added second textfield too password and added more styling options 2023-02-16 15:05:49 +01:00
Gorter-dev
ac3c1e0d48
Merge pull request #1 from Iconica-Development/feature/translate_and_regex
fix: translate defaultsteps and emailregex fix
2022-11-04 11:54:02 +01:00
Freek van de Ven
4ff212ad91 fix: translate defaultsteps and emailregex fix 2022-11-04 11:38:16 +01:00
cd09b2cc56 Add BSD-3-Clause license 2022-11-01 09:19:20 +01:00
Stein Milder
4eaa584f17 Updated documentation 2022-09-28 13:56:29 +02:00
33 changed files with 1562 additions and 828 deletions

10
.github/dependabot.yaml vendored Normal file
View file

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "pub"
directory: "/"
schedule:
interval: "daily"

View 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

32
.github/workflows/flutter.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: CI
on:
push:
branches: [ master ]
pull_request:
branches:
- master
- feature/*
- bugfix/*
- hotfix/*
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.gradle/wrapper
/opt/hostedtoolcache/flutter
key: ${{ runner.OS }}-flutter-install-cache
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
- name: Flutter pub get
run: flutter pub get
- name: Dart format
run: dart format -o none --set-exit-if-changed .
- name: Flutter analyze
run: flutter analyze

7
.gitignore vendored
View file

@ -33,4 +33,9 @@ migrate_working_dir/
.packages .packages
build/ build/
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies
.metadata
# FVM Version Cache
.fvm/
.fvmrc

View file

@ -3,12 +3,76 @@ SPDX-FileCopyrightText: 2022 Iconica
SPDX-License-Identifier: GPL-3.0-or-later 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
## 0.0.1 # 2.0.3
- feat: added default registrationOptions
* Initial version # 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.1.0
- feat: Added the ability to go to specific page on error
# 1.0.0
- feat: Added an onError function.
- feat: Register function in RegistrationRepository can now return a nullable string. Works like a validator.
# 0.5.0
- feat: add customBackgroundColor to AuthScreen
- fix: fix linter
- fix: fix translations to English
# 0.4.0
- feat: Added the abilty to show and hide the passwords
## 0.3.0
- Added the abilty to set an initial value fot the default email field
## 0.2.0
- Added the abilty to add labels
- The default password step now includes two textfields
## 0.0.2 ## 0.0.2
* Firebase integration - Firebase integration
* Registration capabilities - Registration capabilities
## 0.0.1
- Initial version

9
LICENSE Normal file
View file

@ -0,0 +1,9 @@
Copyright (c) 2022 Iconica, All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,33 +1,63 @@
[![pub package](https://img.shields.io/pub/v/[PACKAGE NAME ON PUB].svg)](https://github.com/Iconica-Development) [![Build status](URL TO REPO)](URL TO GITHUB ACTIONS) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://github.com/tenhobi/effective_dart) [![pub package](https://img.shields.io/pub/v/bottom_alert_dialog.svg)](https://github.com/Iconica-Development) [![Build status](https://github.com/Iconica-Development/flutter_registration)](https://github.com/Iconica-Development/flutter_registration/actions/new) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://github.com/tenhobi/effective_dart)
Short description of what your package is, why you created it. What issues it fixes and how it works. Also mention the available platforms # Flutter Registration
Flutter Registration is a package to easily implement
a registration flow in your app.
![Registration GIF](flutter_registration.gif)
## Setup ## Setup
What setup steps are neccesarry and why> To use this package, add `flutter_registration` as a dependency in your pubspec.yaml file.
<details>
<summary>PLATFORM</summary>
specific platform steps
</details>
## How to use ## How to use
How can we use the package descibe the most common ways with examples in To configure the registration plug-in use the ```RegistrationScreen``` widget.
```dart ```dart
codeblocks void main() {
``` runApp(
MaterialApp(
home: RegistrationScreen(
registrationOptions: RegistrationOptions(
registrationRepository: ExampleRegistrationRepository(),
registrationSteps: RegistrationOptions.defaultSteps,
afterRegistration: () {
debugPrint('Registered!');
},
),
),
),
);
}
```
You are required to provide your own RegistrationRepository, this can be done using the parameter ```registrationRepository``` within the RegistrationsOptions which can be assigned to the RegistrationScreen widget.
A RegistrationRepository is responsible for sending the provided user details (email address and password for example) to an API.
An example for creating a RegistrationRepository is specificied below:
```dart
class ExampleRegistrationRepository with RegistrationRepository {
@override
Future<bool> register(HashMap values) {
debugPrint('register: $values');
return Future.value(true);
}
}
```
See the [Example Code](example/lib/main.dart) for an example on how to use this package.
## Issues ## Issues
Please file any issues, bugs or feature request as an issue on our [GitHub](REPO URL) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl). Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_registration) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
## Want to contribute ## Want to contribute
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](URL TO PULL REQUEST TAB IN REPO). If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_registration/pulls).
## Author ## Author
This flutter_registration for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl> This `flutter_registration` for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl>

View file

@ -1,218 +1,9 @@
# SPDX-FileCopyrightText: 2022 Iconica include: package:flutter_iconica_analysis/components_options.yaml
#
# SPDX-License-Identifier: GPL-3.0-or-later # Possible to overwrite the rules from the package
include: package:flutter_lints/flutter.yaml
analyzer: analyzer:
errors: exclude:
todo: ignore
exclude: [lib/generated_plugin_registrant.dart]
linter:
# https://dart.dev/tools/linter-rules#lints
rules:
# error rules
always_use_package_imports: false
avoid_dynamic_calls: true
avoid_empty_else: true
avoid_print: true
avoid_relative_lib_imports: true
avoid_returning_null_for_future: true
avoid_slow_async_io: true
avoid_type_to_string: true
avoid_types_as_parameter_names: true
avoid_web_libraries_in_flutter: true
cancel_subscriptions: true
close_sinks: true
comment_references: false
control_flow_in_finally: true
diagnostic_describe_all_properties: false
empty_statements: true
hash_and_equals: true
invariant_booleans: true
iterable_contains_unrelated_type: true
list_remove_unrelated_type: true
literal_only_boolean_expressions: true
no_adjacent_strings_in_list: true
no_duplicate_case_values: true
no_logic_in_create_state: true
prefer_relative_imports: false
prefer_void_to_null: true
test_types_in_equals: true
throw_in_finally: true
unnecessary_statements: true
unrelated_type_equality_checks: true
unsafe_html: true
use_build_context_synchronously: true
use_key_in_widget_constructors: true
valid_regexps: true
# style rules
always_declare_return_types: true
always_put_control_body_on_new_line: true
always_put_required_named_parameters_first: true
always_require_non_null_named_parameters: true
always_specify_types: false
annotate_overrides: true
avoid_annotating_with_dynamic: false
avoid_bool_literals_in_conditional_expressions: true
avoid_catches_without_on_clauses: false
avoid_catching_errors: false
avoid_classes_with_only_static_members: true
avoid_double_and_int_checks: true
avoid_equals_and_hash_code_on_mutable_classes: false
avoid_escaping_inner_quotes: false
avoid_field_initializers_in_const_classes: true
avoid_final_parameters: true
avoid_function_literals_in_foreach_calls: true
avoid_implementing_value_types: true
avoid_init_to_null: true
avoid_js_rounded_ints: true
avoid_multiple_declarations_per_line: true
avoid_null_checks_in_equality_operators: true
avoid_positional_boolean_parameters: true
avoid_private_typedef_functions: true
avoid_redundant_argument_values: false
avoid_renaming_method_parameters: true
avoid_return_types_on_setters: true
avoid_returning_null: true
avoid_returning_null_for_void: true
avoid_returning_this: true
avoid_setters_without_getters: true
avoid_shadowing_type_parameters: true
avoid_single_cascade_in_expression_statements: true
avoid_types_on_closure_parameters: false
avoid_unnecessary_containers: false
avoid_unused_constructor_parameters: true
avoid_void_async: true
await_only_futures: true
camel_case_extensions: true
camel_case_types: true
cascade_invocations: true
cast_nullable_to_non_nullable: true
conditional_uri_does_not_exist: true
constant_identifier_names: true
curly_braces_in_flow_control_structures: true
deprecated_consistency: true
directives_ordering: true
do_not_use_environment: true
empty_catches: true
empty_constructor_bodies: true
eol_at_end_of_file: true
exhaustive_cases: true
file_names: true
flutter_style_todos: true
implementation_imports: true
join_return_with_assignment: true
leading_newlines_in_multiline_strings: true
library_names: true
library_prefixes: true
library_private_types_in_public_api: true
lines_longer_than_80_chars: true
missing_whitespace_between_adjacent_strings: true
no_default_cases: true
no_leading_underscores_for_library_prefixes: true
no_leading_underscores_for_local_identifiers: true
no_runtimeType_toString: true
non_constant_identifier_names: true
noop_primitive_operations: true
null_check_on_nullable_type_parameter: true
null_closures: true
omit_local_variable_types: true
one_member_abstracts: true
only_throw_errors: true
overridden_fields: true
package_api_docs: true
package_prefixed_library_names: true
parameter_assignments: true
prefer_adjacent_string_concatenation: true
prefer_asserts_in_initializer_lists: true
prefer_asserts_with_message: true
prefer_collection_literals: true
prefer_conditional_assignment: true
prefer_const_constructors: true
prefer_const_constructors_in_immutables: true
prefer_const_declarations: false
prefer_const_literals_to_create_immutables: false
prefer_constructors_over_static_methods: true
prefer_contains: true
prefer_double_quotes: false
prefer_equal_for_default_values: true
prefer_expression_function_bodies: false
prefer_final_fields: true
prefer_final_in_for_each: false
prefer_final_locals: false
prefer_final_parameters: false
prefer_for_elements_to_map_fromIterable: true
prefer_foreach: true
prefer_function_declarations_over_variables: true
prefer_generic_function_type_aliases: true
prefer_if_elements_to_conditional_expressions: true
prefer_if_null_operators: true
prefer_initializing_formals: true
prefer_inlined_adds: true
prefer_int_literals: false
prefer_interpolation_to_compose_strings: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_is_not_operator: true
prefer_iterable_whereType: true
prefer_mixin: true
prefer_null_aware_method_calls: true
prefer_null_aware_operators: true
prefer_single_quotes: true
prefer_spread_collections: true
prefer_typing_uninitialized_variables: true
provide_deprecation_message: true
public_member_api_docs: false
recursive_getters: true
require_trailing_commas: true
sized_box_for_whitespace: true
sized_box_shrink_expand: true
slash_for_doc_comments: true
sort_child_properties_last: true
sort_constructors_first: true
sort_unnamed_constructors_first: true
tighten_type_of_initializing_formals: true
type_annotate_public_apis: true
type_init_formals: true
unawaited_futures: true
unnecessary_await_in_return: true
unnecessary_brace_in_string_interps: true
unnecessary_const: false
unnecessary_constructor_name: true
unnecessary_final: true
unnecessary_getters_setters: true
unnecessary_lambdas: true
unnecessary_late: true
unnecessary_new: true
unnecessary_null_aware_assignments: true
unnecessary_null_checks: true
unnecessary_null_in_if_null_operators: true
unnecessary_nullable_for_final_variable_declarations: true
unnecessary_overrides: true
unnecessary_parenthesis: true
unnecessary_raw_strings: true
unnecessary_string_escapes: true
unnecessary_string_interpolations: true
unnecessary_this: true
use_decorated_box: true
use_full_hex_values_for_flutter_colors: true
use_function_type_syntax_for_parameters: true
use_if_null_to_convert_nulls_to_bools: true
use_is_even_rather_than_modulo: true
use_late_for_private_fields_and_variables: true
use_named_constants: true
use_raw_strings: false
use_rethrow_when_possible: true
use_setters_to_change_properties: true
use_string_buffers: true
use_test_throws_matchers: true
use_to_and_as_if_applicable: true
void_checks: true
# pub rules
depend_on_referenced_packages: true
lowercase_with_underscores: true
secure_pubspec_urls: false
sort_pub_dependencies: false
# Additional information about this file can be found at linter:
# https://dart.dev/guides/language/analysis-options rules:

View file

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

View file

@ -1,218 +1,4 @@
# SPDX-FileCopyrightText: 2022 Iconica
#
# SPDX-License-Identifier: GPL-3.0-or-later
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
analyzer:
errors:
todo: ignore
exclude: [lib/generated_plugin_registrant.dart]
linter:
# https://dart.dev/tools/linter-rules#lints
rules:
# error rules
always_use_package_imports: false
avoid_dynamic_calls: true
avoid_empty_else: true
avoid_print: true
avoid_relative_lib_imports: true
avoid_returning_null_for_future: true
avoid_slow_async_io: true
avoid_type_to_string: true
avoid_types_as_parameter_names: true
avoid_web_libraries_in_flutter: true
cancel_subscriptions: true
close_sinks: true
comment_references: false
control_flow_in_finally: true
diagnostic_describe_all_properties: false
empty_statements: true
hash_and_equals: true
invariant_booleans: true
iterable_contains_unrelated_type: true
list_remove_unrelated_type: true
literal_only_boolean_expressions: true
no_adjacent_strings_in_list: true
no_duplicate_case_values: true
no_logic_in_create_state: true
prefer_relative_imports: false
prefer_void_to_null: true
test_types_in_equals: true
throw_in_finally: true
unnecessary_statements: true
unrelated_type_equality_checks: true
unsafe_html: true
use_build_context_synchronously: true
use_key_in_widget_constructors: true
valid_regexps: true
# style rules
always_declare_return_types: true
always_put_control_body_on_new_line: true
always_put_required_named_parameters_first: true
always_require_non_null_named_parameters: true
always_specify_types: false
annotate_overrides: true
avoid_annotating_with_dynamic: false
avoid_bool_literals_in_conditional_expressions: true
avoid_catches_without_on_clauses: false
avoid_catching_errors: false
avoid_classes_with_only_static_members: true
avoid_double_and_int_checks: true
avoid_equals_and_hash_code_on_mutable_classes: false
avoid_escaping_inner_quotes: false
avoid_field_initializers_in_const_classes: true
avoid_final_parameters: true
avoid_function_literals_in_foreach_calls: true
avoid_implementing_value_types: true
avoid_init_to_null: true
avoid_js_rounded_ints: true
avoid_multiple_declarations_per_line: true
avoid_null_checks_in_equality_operators: true
avoid_positional_boolean_parameters: true
avoid_private_typedef_functions: true
avoid_redundant_argument_values: false
avoid_renaming_method_parameters: true
avoid_return_types_on_setters: true
avoid_returning_null: true
avoid_returning_null_for_void: true
avoid_returning_this: true
avoid_setters_without_getters: true
avoid_shadowing_type_parameters: true
avoid_single_cascade_in_expression_statements: true
avoid_types_on_closure_parameters: false
avoid_unnecessary_containers: false
avoid_unused_constructor_parameters: true
avoid_void_async: true
await_only_futures: true
camel_case_extensions: true
camel_case_types: true
cascade_invocations: true
cast_nullable_to_non_nullable: true
conditional_uri_does_not_exist: true
constant_identifier_names: true
curly_braces_in_flow_control_structures: true
deprecated_consistency: true
directives_ordering: true
do_not_use_environment: true
empty_catches: true
empty_constructor_bodies: true
eol_at_end_of_file: true
exhaustive_cases: true
file_names: true
flutter_style_todos: true
implementation_imports: true
join_return_with_assignment: true
leading_newlines_in_multiline_strings: true
library_names: true
library_prefixes: true
library_private_types_in_public_api: true
lines_longer_than_80_chars: true
missing_whitespace_between_adjacent_strings: true
no_default_cases: true
no_leading_underscores_for_library_prefixes: true
no_leading_underscores_for_local_identifiers: true
no_runtimeType_toString: true
non_constant_identifier_names: true
noop_primitive_operations: true
null_check_on_nullable_type_parameter: true
null_closures: true
omit_local_variable_types: true
one_member_abstracts: true
only_throw_errors: true
overridden_fields: true
package_api_docs: true
package_prefixed_library_names: true
parameter_assignments: true
prefer_adjacent_string_concatenation: true
prefer_asserts_in_initializer_lists: true
prefer_asserts_with_message: true
prefer_collection_literals: true
prefer_conditional_assignment: true
prefer_const_constructors: true
prefer_const_constructors_in_immutables: true
prefer_const_declarations: false
prefer_const_literals_to_create_immutables: false
prefer_constructors_over_static_methods: true
prefer_contains: true
prefer_double_quotes: false
prefer_equal_for_default_values: true
prefer_expression_function_bodies: false
prefer_final_fields: true
prefer_final_in_for_each: false
prefer_final_locals: false
prefer_final_parameters: false
prefer_for_elements_to_map_fromIterable: true
prefer_foreach: true
prefer_function_declarations_over_variables: true
prefer_generic_function_type_aliases: true
prefer_if_elements_to_conditional_expressions: true
prefer_if_null_operators: true
prefer_initializing_formals: true
prefer_inlined_adds: true
prefer_int_literals: false
prefer_interpolation_to_compose_strings: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_is_not_operator: true
prefer_iterable_whereType: true
prefer_mixin: true
prefer_null_aware_method_calls: true
prefer_null_aware_operators: true
prefer_single_quotes: true
prefer_spread_collections: true
prefer_typing_uninitialized_variables: true
provide_deprecation_message: true
public_member_api_docs: false
recursive_getters: true
require_trailing_commas: true
sized_box_for_whitespace: true
sized_box_shrink_expand: true
slash_for_doc_comments: true
sort_child_properties_last: true
sort_constructors_first: true
sort_unnamed_constructors_first: true
tighten_type_of_initializing_formals: true
type_annotate_public_apis: true
type_init_formals: true
unawaited_futures: true
unnecessary_await_in_return: true
unnecessary_brace_in_string_interps: true
unnecessary_const: false
unnecessary_constructor_name: true
unnecessary_final: true
unnecessary_getters_setters: true
unnecessary_lambdas: true
unnecessary_late: true
unnecessary_new: true
unnecessary_null_aware_assignments: true
unnecessary_null_checks: true
unnecessary_null_in_if_null_operators: true
unnecessary_nullable_for_final_variable_declarations: true
unnecessary_overrides: true
unnecessary_parenthesis: true
unnecessary_raw_strings: true
unnecessary_string_escapes: true
unnecessary_string_interpolations: true
unnecessary_this: true
use_decorated_box: true
use_full_hex_values_for_flutter_colors: true
use_function_type_syntax_for_parameters: true
use_if_null_to_convert_nulls_to_bools: true
use_is_even_rather_than_modulo: true
use_late_for_private_fields_and_variables: true
use_named_constants: true
use_raw_strings: false
use_rethrow_when_possible: true
use_setters_to_change_properties: true
use_string_buffers: true
use_test_throws_matchers: true
use_to_and_as_if_applicable: true
void_checks: true
# pub rules
depend_on_referenced_packages: true
lowercase_with_underscores: true
secure_pubspec_urls: false
sort_pub_dependencies: false
# Additional information about this file can be found at # Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options # https://dart.dev/guides/language/analysis-options

View file

@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
task clean(type: Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View file

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '11.0' platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'dart:collection'; import 'dart:collection';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -5,8 +9,8 @@ import 'package:flutter_registration/flutter_registration.dart';
class ExampleRegistrationRepository with RegistrationRepository { class ExampleRegistrationRepository with RegistrationRepository {
@override @override
Future<bool> register(HashMap values) { Future<String?> register(HashMap values) {
debugPrint('register: $values'); debugPrint('register: $values');
return Future.value(true); return Future.value(null);
} }
} }

View file

@ -1,24 +1,82 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_registration/flutter_registration.dart'; import 'package:flutter_registration/flutter_registration.dart';
import 'example_registration_repository.dart'; import 'example_registration_repository.dart';
void main() { void main() {
runApp( runApp(
const MaterialApp( MaterialApp(
home: FlutterRegistrationDemo(), theme: ThemeData(
inputDecorationTheme: const InputDecorationTheme(
errorStyle: TextStyle(color: Colors.red),
),
),
home: const FlutterRegistrationDemo(),
), ),
); );
} }
class FlutterRegistrationDemo extends StatelessWidget { class FlutterRegistrationDemo extends StatefulWidget {
const FlutterRegistrationDemo({Key? key}) : super(key: key); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RegistrationScreen( return RegistrationScreen(
registrationOptions: RegistrationOptions( registrationOptions: RegistrationOptions(
registrationRepository: ExampleRegistrationRepository(), registrationRepository: ExampleRegistrationRepository(),
registrationSteps: RegistrationOptions.defaultSteps, registrationSteps: steps,
afterRegistration: () { afterRegistration: () {
debugPrint('Registered!'); debugPrint('Registered!');
}, },
@ -28,8 +86,7 @@ class FlutterRegistrationDemo extends StatelessWidget {
} }
class ProtectedScreen extends StatelessWidget { class ProtectedScreen extends StatelessWidget {
const ProtectedScreen({Key? key}) : super(key: key); const ProtectedScreen({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Scaffold( return const Scaffold(

View file

@ -5,49 +5,56 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
url: "https://pub.dartlang.org" sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.9.0" version: "2.11.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.3.0"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.18.0"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
url: "https://pub.dartlang.org" sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.6"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
flutter: flutter:
@ -55,20 +62,22 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_hooks: flutter_input_library:
dependency: transitive dependency: transitive
description: description:
name: flutter_hooks name: flutter_input_library
url: "https://pub.dartlang.org" sha256: db8d9d57c31f166ed7c4ec3d060d18891533c57877a30c6c2668231efa1a44f8
url: "https://forgejo.internal.iconica.nl/api/packages/internal/pub/"
source: hosted source: hosted
version: "0.18.5+1" version: "3.6.0"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
url: "https://pub.dartlang.org" sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.3"
flutter_localizations: flutter_localizations:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -80,7 +89,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.0.1" version: "2.0.4"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -90,44 +99,74 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
url: "https://pub.dartlang.org" sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.0" 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: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
url: "https://pub.dartlang.org" sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.1.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.dartlang.org" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.12" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.5" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.0" version: "1.12.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.2" version: "1.9.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -137,51 +176,66 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
url: "https://pub.dartlang.org" sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.10.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.2"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.2.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.12" version: "0.7.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.1"
sdks: sdks:
dart: ">=2.18.0 <3.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.0.0" flutter: ">=3.18.0-18.0.pre.54"

BIN
flutter_registration.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 KiB

View file

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

@ -1,185 +1,421 @@
import 'dart:collection'; // SPDX-FileCopyrightText: 2022 Iconica
import 'package:flutter/material.dart'; //
import 'package:flutter_registration/src/model/auth_field.dart'; // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_registration/src/model/auth_step.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 { 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({ const AuthScreen({
required this.title, required this.appBarTitle,
required this.steps, required this.steps,
required this.submitBtnTitle, required this.submitBtnTitle,
required this.nextBtnTitle, required this.nextBtnTitle,
required this.previousBtnTitle, required this.previousBtnTitle,
required this.onFinish, required this.onFinish,
this.customAppBar, 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, 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 Function({ final String appBarTitle;
required HashMap<String, String> values,
required VoidCallback onError, /// 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; }) onFinish;
/// The authentication steps to be completed.
final List<AuthStep> steps; final List<AuthStep> steps;
/// The title of the submit button.
final String submitBtnTitle; final String submitBtnTitle;
/// The title of the next button.
final String nextBtnTitle; final String nextBtnTitle;
/// The title of the previous button.
final String previousBtnTitle; final String previousBtnTitle;
/// A custom app bar widget.
final AppBar? customAppBar; 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(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 @override
State<AuthScreen> createState() => _AuthScreenState(); State<AuthScreen> createState() => _AuthScreenState();
} }
/// The state for [AuthScreen].
class _AuthScreenState extends State<AuthScreen> { class _AuthScreenState extends State<AuthScreen> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final _pageController = PageController(); final _pageController = PageController();
final _animationDuration = const Duration(milliseconds: 300); final _animationDuration = const Duration(milliseconds: 300);
final _animationCurve = Curves.ease; final _animationCurve = Curves.ease;
bool _formValid = false;
/// Gets the app bar.
AppBar get _appBar => AppBar get _appBar =>
widget.customAppBar ?? widget.customAppBar ??
AppBar( 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;
}
_formKey.currentState!.save();
FocusScope.of(context).unfocus();
if (widget.steps.last == step) {
var values = HashMap<String, dynamic>();
for (var step in widget.steps) {
for (var field in step.fields) {
values[field.name] = field.value;
}
}
await widget.onFinish(
values: values,
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 @override
Widget build(BuildContext context) => Scaffold( Widget build(BuildContext context) {
backgroundColor: Theme.of(context).backgroundColor, var theme = Theme.of(context);
appBar: _appBar, return Scaffold(
body: Form( backgroundColor: widget.customBackgroundColor ?? const Color(0xffFAF9F6),
appBar: _appBar,
body: SafeArea(
child: Form(
key: _formKey, key: _formKey,
child: PageView( child: PageView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
controller: _pageController, controller: _pageController,
children: <Widget>[ children: <Widget>[
for (AuthStep step in widget.steps) for (var currentStep = 0;
currentStep < widget.steps.length;
currentStep++)
Column( Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Flexible( if (widget.titleWidget != null) ...[
child: Center( Expanded(
child: ListView( flex: widget.titleFlex ?? 1,
physics: const ClampingScrollPhysics(), child: Column(
shrinkWrap: true, mainAxisAlignment: MainAxisAlignment.start,
padding: const EdgeInsets.symmetric( mainAxisSize: MainAxisSize.min,
vertical: 8.0,
horizontal: 30.0,
),
children: [ children: [
for (AuthField field in step.fields) Expanded(
Align( flex: widget.beforeTitleFlex ?? 2,
child: Column( child: Container(),
crossAxisAlignment: CrossAxisAlignment.start, ),
children: [ widget.titleWidget!,
Padding( Expanded(
padding: const EdgeInsets.only( flex: widget.afterTitleFlex ?? 2,
top: 24.0, child: Container(),
bottom: 12.0, ),
),
child: Text(
field.title,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
field.build(),
],
),
)
], ],
), ),
), ),
), ],
Padding( Expanded(
padding: const EdgeInsets.only( flex: widget.formFlex ?? 3,
top: 15.0, child: Align(
bottom: 30.0, alignment: Alignment.topCenter,
left: 30.0, child: ConstrainedBox(
right: 30.0, constraints: BoxConstraints(
), maxWidth: widget.maxFormWidth ?? 300,
child: Row( ),
mainAxisAlignment: widget.steps.first != step child: Column(
? MainAxisAlignment.spaceBetween children: [
: MainAxisAlignment.end, for (AuthField field
children: [ in widget.steps[currentStep].fields) ...[
if (widget.steps.first != step) if (field.title != null) ...[
ElevatedButton( wrapWithDefaultStyle(
onPressed: () => _pageController.previousPage( style: theme.textTheme.headlineLarge!,
duration: _animationDuration, widget: field.title!,
curve: _animationCurve,
),
child: Row(
children: [
const Icon(
Icons.arrow_back,
size: 18,
),
Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Text(widget.previousBtnTitle),
), ),
], ],
), field.build(context, () {
_validate(currentStep);
}),
],
],
),
),
),
),
Column(
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
), ),
ElevatedButton(
onPressed: () {
if (!_formKey.currentState!.validate()) {
return;
}
FocusScope.of(context).unfocus();
if (widget.steps.last == step) {
var values = HashMap<String, String>();
for (var step in widget.steps) {
for (var field in step.fields) {
values[field.name] = field.value;
}
}
widget.onFinish(
values: values,
onError: () => _pageController.animateToPage(
0,
duration: _animationDuration,
curve: _animationCurve,
),
);
return;
}
_pageController.nextPage(
duration: _animationDuration,
curve: _animationCurve,
);
},
child: Row( child: Row(
mainAxisAlignment: widget.steps.first !=
widget.steps[currentStep]
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.end,
children: [ children: [
Text( if (widget.steps.first !=
widget.steps.last == step widget.steps[currentStep]) ...[
? widget.submitBtnTitle widget.previousButtonBuilder?.call(
: widget.nextBtnTitle, onPrevious,
), widget.previousBtnTitle,
const Padding( currentStep,
padding: EdgeInsets.only(left: 4.0), ) ??
child: Icon( _stepButton(
Icons.arrow_forward, buttonText: widget.previousBtnTitle,
size: 18, 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)
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);
} }

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

View file

@ -1,54 +1,228 @@
import 'package:flutter/material.dart'; // SPDX-FileCopyrightText: 2022 Iconica
import 'package:flutter_registration/flutter_registration.dart'; //
// SPDX-License-Identifier: BSD-3-Clause
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";
/// A set of options for configuring the
/// registration process in a Flutter application.
class RegistrationOptions { class RegistrationOptions {
/// Registration options Constructor
RegistrationOptions({ RegistrationOptions({
required this.registrationRepository,
required this.registrationSteps,
required this.afterRegistration, required this.afterRegistration,
this.registrationTranslations = const RegistrationTranslations(), this.registrationRepository,
this.customAppbarBuilder, this.registrationSteps,
}); this.titleFlex,
this.formFlex,
this.beforeTitleFlex,
this.afterTitleFlex,
this.registrationTranslations = const RegistrationTranslations.empty(),
this.onError,
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 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 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 AppBar Function(String title)? customAppbarBuilder;
static List<AuthStep> get defaultSteps => [ /// 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? 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,
}) =>
[
AuthStep( AuthStep(
fields: [ fields: [
AuthTextField( AuthTextField(
name: 'email', name: "email",
title: 'Wat is je e-mailadres?', textEditingController: emailController,
hintText: 'iemand@voorbeeld.nl', value: initialEmail ?? "",
title: titleBuilder?.call(translations.defaultEmailTitle) ??
Padding(
padding: const EdgeInsets.only(top: 180),
child: Text(
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: [ validators: [
// ignore: avoid_dynamic_calls
(email) => (email == null || email.isEmpty) (email) => (email == null || email.isEmpty)
? 'Geef uw e-mailadres op' ? translations.defaultEmailEmpty
: null, : null,
(email) => (email) =>
RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") RegExp(r"""(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])""")
.hasMatch(email!) .hasMatch(email!)
? null ? null
: 'Geef een geldig e-mailadres op', : translations.defaultEmailValidatorMessage,
], ],
) ),
], ],
), ),
AuthStep( AuthStep(
fields: [ fields: [
AuthTextField( AuthPassField(
name: 'password', name: "password",
title: 'Kies een wachtwoord', textEditingController: passController,
obscureText: true, title: titleBuilder?.call(translations.defaultPasswordTitle) ??
Padding(
padding: const EdgeInsets.only(top: 180),
child: Text(
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(),
),
padding: const EdgeInsets.symmetric(vertical: 20),
textStyle: textStyle,
validators: [ validators: [
(value) => (value == null || value.isEmpty) (value) {
? 'Geef een wachtwoord op' // ignore: avoid_dynamic_calls
: null, 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) => AppBar(
iconTheme: const IconThemeData(color: Colors.black, size: 16),
title: Text(title),
backgroundColor: Colors.transparent,
);

View file

@ -1,15 +1,130 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
/// Holds all the translations for the standard elements
/// on the registration screen.
class RegistrationTranslations { class RegistrationTranslations {
/// Constructs a [RegistrationTranslations] object.
const RegistrationTranslations({ const RegistrationTranslations({
this.title = 'Registreren', required this.title,
this.registerBtn = 'Registreren', required this.registerBtn,
this.previousStepBtn = 'Vorige stap', required this.previousStepBtn,
this.nextStepBtn = 'Volgende stap', required this.nextStepBtn,
this.closeBtn = 'Sluiten', 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; final String title;
/// The text for the registration button.
final String registerBtn; final String registerBtn;
/// The text for the previous step button.
final String previousStepBtn; final String previousStepBtn;
/// The text for the next step button.
final String nextStepBtn; final String nextStepBtn;
/// The text for the close button.
final String closeBtn; 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
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,
);
} }

View file

@ -1,11 +1,20 @@
import 'package:flutter/material.dart'; // SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import "package:flutter/material.dart";
/// An action that can be performed during authentication.
class AuthAction { class AuthAction {
/// Constructs an [AuthAction] object.
AuthAction({ AuthAction({
required this.title, required this.title,
required this.onPress, required this.onPress,
}); });
/// The title of the action.
final String title; final String title;
/// A callback function triggered when the action is pressed.
final VoidCallback onPress; final VoidCallback onPress;
} }

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

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

View file

@ -1,9 +1,15 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
/// An exception thrown when an authentication error occurs.
class AuthException implements Exception { class AuthException implements Exception {
/// Constructs an [AuthException] object.
AuthException(this.message); AuthException(this.message);
/// The error message.
final String message; final String message;
@override @override
String toString() { String toString() => message;
return message;
}
} }

View file

@ -1,17 +1,54 @@
import 'package:flutter/material.dart'; // SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
abstract class AuthField { import "package:flutter/material.dart";
/// 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({ AuthField({
required this.name, required this.name,
required this.title, required this.value,
this.onValueChanged,
this.title,
this.validators = const [], this.validators = const [],
this.value = '',
}); });
/// The name of the field.
final String name; final String name;
final String 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);
} }

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

View file

@ -1,9 +1,16 @@
import 'package:flutter_registration/src/model/auth_field.dart'; // SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import "package:flutter_registration/src/model/auth_field.dart";
/// A step in the authentication process.
class AuthStep { class AuthStep {
/// Constructs an [AuthStep] object.
AuthStep({ AuthStep({
required this.fields, required this.fields,
}); });
/// The fields in the step.
List<AuthField> fields; List<AuthField> fields;
} }

View file

@ -1,41 +1,96 @@
import 'package:flutter/material.dart'; // SPDX-FileCopyrightText: 2022 Iconica
import 'package:flutter_registration/flutter_registration.dart'; //
// SPDX-License-Identifier: BSD-3-Clause
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 { 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({ AuthTextField({
required super.name, required super.name,
required super.title, TextEditingController? textEditingController,
super.title,
super.validators = const [], super.validators = const [],
super.value = '', super.value = "",
this.obscureText = false, this.textStyle,
this.hintText, this.onChange,
this.textFieldDecoration,
this.padding = const EdgeInsets.all(8.0),
this.textInputType,
}) { }) {
_textEditingController = TextEditingController(); textController =
textEditingController ?? TextEditingController(text: value);
} }
late TextEditingController _textEditingController; /// The controller for the text input.
final bool obscureText; late TextEditingController textController;
final String? hintText;
/// 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 @override
Widget build() => TextFormField( Widget build(BuildContext context, Function onValueChanged) => Padding(
decoration: InputDecoration( padding: padding,
hintText: hintText, child: TextFormField(
), keyboardType: textInputType,
controller: _textEditingController, style: textStyle,
obscureText: obscureText, decoration: textFieldDecoration,
onChanged: (v) { controller: textController,
value = v; onChanged: (v) {
}, value = v;
validator: (value) { onChange?.call(value);
for (var validator in validators) { // ignore: avoid_dynamic_calls
var output = validator(value); onValueChanged();
if (output != null) { },
return output; validator: (value) {
for (var validator in validators) {
var output = validator(value);
if (output != null) {
return output;
}
} }
}
return null; return null;
}, },
),
); );
} }

View file

@ -1,45 +1,77 @@
import 'dart:collection'; import "dart:collection";
import 'package:flutter/material.dart'; import "package:flutter/material.dart";
import 'package:flutter_registration/flutter_registration.dart'; import "package:flutter_registration/flutter_registration.dart";
import 'package:flutter_registration/src/auth_screen.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({ const RegistrationScreen({
required this.registrationOptions, required this.registrationOptions,
super.key, super.key,
}); });
/// The registration options.
final RegistrationOptions registrationOptions; final RegistrationOptions registrationOptions;
@override @override
Widget build(BuildContext context) { RegistrationScreenState createState() => RegistrationScreenState();
var translations = registrationOptions.registrationTranslations; }
void register({ /// The state for [RegistrationScreen].
required HashMap<String, String> values, class RegistrationScreenState extends State<RegistrationScreen> {
required VoidCallback onError, /// Registers the user.
}) => Future<void> _register({
registrationOptions.registrationRepository.register(values).then( required HashMap<String, dynamic> values,
(response) { required void Function(int? pageToReturn) onError,
if (response) { }) async {
registrationOptions.afterRegistration(); try {
} await widget.registrationOptions.beforeRegistration?.call();
},
).catchError((_) { var registered = await widget.registrationOptions.registrationRepository!
onError(); .register(values);
});
if (registered == null) {
widget.registrationOptions.afterRegistration();
} else {
var pageToReturn = widget.registrationOptions.onError?.call(registered);
onError(pageToReturn);
}
} on Exception catch (_) {
onError(null);
}
}
@override
Widget build(BuildContext context) {
var translations = widget.registrationOptions.registrationTranslations;
return AuthScreen( return AuthScreen(
steps: registrationOptions.registrationSteps, steps: widget.registrationOptions.steps,
customAppBar: registrationOptions.customAppbarBuilder?.call( customAppBar: widget.registrationOptions.customAppbarBuilder?.call(
translations.title, translations.title,
), ),
onFinish: register, onFinish: _register,
title: translations.title, appBarTitle: translations.title,
submitBtnTitle: translations.registerBtn, submitBtnTitle: translations.registerBtn,
nextBtnTitle: translations.nextStepBtn, nextBtnTitle: translations.nextStepBtn,
previousBtnTitle: translations.previousStepBtn, previousBtnTitle: translations.previousStepBtn,
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,
); );
} }
} }

View file

@ -1,5 +1,11 @@
import 'dart:collection'; // SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import "dart:collection";
/// A mixin for a registration repository.
mixin RegistrationRepository { mixin RegistrationRepository {
Future<bool> register(HashMap values); /// Registers a user with the given values.
Future<String?> register(HashMap values);
} }

View file

@ -3,59 +3,32 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
name: flutter_registration name: flutter_registration
description: A standard flutter package. description: A Flutter Registration package
version: 0.0.1 version: 3.0.0
homepage: repository: https://github.com/Iconica-Development/flutter_registration
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment: environment:
sdk: '>=2.18.0 <3.0.0' sdk: ">=3.0.0 <4.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
flutter_input_library:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: ^3.6.0
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_hooks: ^0.18.5+1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter: flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages

View file

@ -1,11 +1,12 @@
// SPDX-FileCopyrightText: 2022 Iconica // SPDX-FileCopyrightText: 2022 Iconica
// //
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import 'package:flutter_test/flutter_test.dart'; import "package:flutter_test/flutter_test.dart";
void main() { void main() {
test('test', () { test("test", () {
expect(true, true); expect(true, true);
}); });
} }