feat: registration options, translations

This commit is contained in:
Stein Milder 2022-09-26 10:35:53 +02:00
parent 7f36ab8eca
commit 6f7ec0b0e6
6 changed files with 216 additions and 162 deletions

View file

@ -16,10 +16,13 @@ class FlutterRegistrationDemo extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RegistrationScreen( return RegistrationScreen(
afterRegistration: () { registrationOptions: RegistrationOptions(
debugPrint('Registered!'); registrationRepository: ExampleRegistrationRepository(),
}, registrationSteps: RegistrationOptions.defaultSteps,
repository: ExampleRegistrationRepository(), afterRegistration: () {
debugPrint('Registered!');
},
),
); );
} }
} }

View file

@ -1,5 +1,7 @@
library flutter_registration; library flutter_registration;
export 'src/config/registration_options.dart';
export 'src/config/registration_translations.dart';
export 'src/model/auth_exception.dart'; export 'src/model/auth_exception.dart';
export 'src/model/auth_field.dart'; export 'src/model/auth_field.dart';
export 'src/model/auth_step.dart'; export 'src/model/auth_step.dart';

View file

@ -8,6 +8,8 @@ class AuthScreen extends StatefulWidget {
required this.title, required this.title,
required this.steps, required this.steps,
required this.submitBtnTitle, required this.submitBtnTitle,
required this.nextBtnTitle,
required this.previousBtnTitle,
required this.onFinish, required this.onFinish,
super.key, super.key,
}) : assert(steps.length > 0, 'At least one step is required'); }) : assert(steps.length > 0, 'At least one step is required');
@ -16,6 +18,8 @@ class AuthScreen extends StatefulWidget {
final Function(HashMap<String, String>) onFinish; final Function(HashMap<String, String>) onFinish;
final List<AuthStep> steps; final List<AuthStep> steps;
final String submitBtnTitle; final String submitBtnTitle;
final String nextBtnTitle;
final String previousBtnTitle;
@override @override
State<AuthScreen> createState() => _AuthScreenState(); State<AuthScreen> createState() => _AuthScreenState();
@ -28,128 +32,136 @@ class _AuthScreenState extends State<AuthScreen> {
final _animationCurve = Curves.ease; final _animationCurve = Curves.ease;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Scaffold(
return Scaffold( backgroundColor: Theme.of(context).backgroundColor,
backgroundColor: Theme.of(context).backgroundColor, appBar: AppBar(
appBar: AppBar( title: Text(widget.title),
title: Text(widget.title), ),
), body: Form(
body: 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 (AuthStep step in widget.steps) Column(
Column( mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
children: [ Flexible(
const Spacer(), child: Center(
Padding( child: ListView(
padding: const EdgeInsets.symmetric( physics: const ClampingScrollPhysics(),
vertical: 8.0, shrinkWrap: true,
horizontal: 30.0, padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 30.0,
),
children: [
for (AuthField field in step.fields)
Align(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(
top: 24.0,
bottom: 12.0,
),
child: Text(
field.title,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
field.build(),
],
),
)
],
),
),
), ),
child: Column( Padding(
children: [ padding: const EdgeInsets.only(
for (AuthField field in step.fields) top: 15.0,
Column( bottom: 30.0,
crossAxisAlignment: CrossAxisAlignment.start, left: 30.0,
children: [ right: 30.0,
Padding( ),
padding: const EdgeInsets.only( child: Row(
top: 24.0, mainAxisAlignment: widget.steps.first != step
bottom: 12.0, ? MainAxisAlignment.spaceBetween
), : MainAxisAlignment.end,
child: Text( children: [
field.title, if (widget.steps.first != step)
style: const TextStyle( ElevatedButton(
fontWeight: FontWeight.bold, onPressed: () => _pageController.previousPage(
), duration: _animationDuration,
), 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(),
],
)
],
),
),
const Spacer(),
Padding(
padding: const EdgeInsets.all(30.0),
child: Row(
mainAxisAlignment: widget.steps.first != step
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.end,
children: [
if (widget.steps.first != step)
ElevatedButton(
onPressed: () => _pageController.previousPage(
duration: _animationDuration,
curve: _animationCurve,
), ),
ElevatedButton(
onPressed: () {
if (!_formKey.currentState!.validate()) {
return;
}
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);
return;
}
_pageController.nextPage(
duration: _animationDuration,
curve: _animationCurve,
);
},
child: Row( child: Row(
children: [ children: [
const Icon( Text(
Icons.arrow_back, widget.steps.last == step
size: 18, ? widget.submitBtnTitle
: widget.nextBtnTitle,
), ),
const Padding( const Padding(
padding: EdgeInsets.only(left: 4.0), padding: EdgeInsets.only(left: 4.0),
child: Text('Vorige stap'), child: Icon(
Icons.arrow_forward,
size: 18,
),
), ),
], ],
), ),
), ),
ElevatedButton( ],
onPressed: () { ),
if (!_formKey.currentState!.validate()) { )
return; ],
} ),
],
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);
return;
}
_pageController.nextPage(
duration: _animationDuration,
curve: _animationCurve,
);
},
child: Row(
children: [
Text(
widget.steps.last == step
? widget.submitBtnTitle
: 'Volgende stap',
),
const Padding(
padding: EdgeInsets.only(left: 4.0),
child: Icon(
Icons.arrow_forward,
size: 18,
),
),
],
),
),
],
),
)
],
),
],
), ),
), );
);
}
} }

View file

@ -0,0 +1,51 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_registration/flutter_registration.dart';
class RegistrationOptions {
RegistrationOptions({
required this.registrationRepository,
required this.registrationSteps,
required this.afterRegistration,
this.registrationTranslations = const RegistrationTranslations(),
});
final RegistrationTranslations registrationTranslations;
final List<AuthStep> registrationSteps;
final VoidCallback afterRegistration;
final RegistrationRepository registrationRepository;
static List<AuthStep> get defaultSteps => [
AuthStep(
fields: [
AuthTextField(
name: 'email',
title: 'Wat is je e-mailadres?',
validators: [
(email) => (email == null || email.isEmpty)
? 'Geef uw e-mailadres op'
: null,
(email) =>
RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(email!)
? null
: 'Geef een geldig e-mailadres op',
],
)
],
),
AuthStep(
fields: [
AuthTextField(
name: 'password',
title: 'Kies een wachtwoord',
obscureText: true,
validators: [
(value) => (value == null || value.isEmpty)
? 'Geef een wachtwoord op'
: null,
],
),
],
),
];
}

View file

@ -0,0 +1,15 @@
class RegistrationTranslations {
const RegistrationTranslations({
this.title = 'Registreren',
this.registerBtn = 'Registreren',
this.previousStepBtn = 'Vorige stap',
this.nextStepBtn = 'Volgende stap',
this.closeBtn = 'Sluiten',
});
final String title;
final String registerBtn;
final String previousStepBtn;
final String nextStepBtn;
final String closeBtn;
}

View file

@ -4,35 +4,38 @@ import 'package:flutter_registration/src/auth_screen.dart';
class RegistrationScreen extends StatelessWidget { class RegistrationScreen extends StatelessWidget {
const RegistrationScreen({ const RegistrationScreen({
required this.afterRegistration, required this.registrationOptions,
required this.repository,
this.additionalSteps = const [],
super.key, super.key,
}); });
final VoidCallback afterRegistration; final RegistrationOptions registrationOptions;
final RegistrationRepository repository;
final List<AuthStep> additionalSteps;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var translations = registrationOptions.registrationTranslations;
void showError(String error) => showDialog<String>( void showError(String error) => showDialog<String>(
context: context, context: context,
builder: (BuildContext context) => AlertDialog( builder: (BuildContext context) => AlertDialog(
content: Text(error), content: Text(error),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
onPressed: () => Navigator.pop(context, 'Sluit'), onPressed: () => Navigator.pop(
child: const Text('Sluit'), context,
translations.closeBtn,
),
child: Text(
translations.closeBtn,
),
), ),
], ],
), ),
); );
void register(values) => repository void register(values) => registrationOptions.registrationRepository
.register(values) .register(values)
.then( .then(
(value) => afterRegistration(), (_) => registrationOptions.afterRegistration(),
) )
.catchError( .catchError(
(error) { (error) {
@ -43,44 +46,12 @@ class RegistrationScreen extends StatelessWidget {
); );
return AuthScreen( return AuthScreen(
title: 'Registreren', steps: registrationOptions.registrationSteps,
submitBtnTitle: 'Registreren',
steps: [
AuthStep(
fields: [
AuthTextField(
name: 'email',
title: 'Wat is je e-mailadres?',
validators: [
(email) => (email == null || email.isEmpty)
? 'Geef uw e-mailadres op'
: null,
(email) =>
RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(email!)
? null
: 'Geef een geldig e-mailadres op',
],
)
],
),
AuthStep(
fields: [
AuthTextField(
name: 'password',
title: 'Kies een wachtwoord',
obscureText: true,
validators: [
(value) => (value == null || value.isEmpty)
? 'Geef een wachtwoord op'
: null,
],
),
],
),
...additionalSteps
],
onFinish: register, onFinish: register,
title: translations.title,
submitBtnTitle: translations.registerBtn,
nextBtnTitle: translations.nextStepBtn,
previousBtnTitle: translations.previousStepBtn,
); );
} }
} }