finnow/flutter_app/lib/pages/register_page.dart

140 lines
4.6 KiB
Dart

import 'package:finnow_app/api/auth.dart';
import 'package:finnow_app/auth/model.dart';
import 'package:finnow_app/components/form_label.dart';
import 'package:finnow_app/components/title_text.dart';
import 'package:finnow_app/main.dart';
import 'package:finnow_app/util/debouncer.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class RegisterPage extends StatelessWidget {
const RegisterPage({super.key});
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 300.0),
padding: const EdgeInsets.all(10),
child: const RegisterForm())));
}
}
class RegisterForm extends StatefulWidget {
const RegisterForm({super.key});
@override
State<RegisterForm> createState() => _RegisterFormState();
}
class _RegisterFormState extends State<RegisterForm> {
final formKey = GlobalKey<FormState>();
final usernameTextController = TextEditingController();
final passwordTextController = TextEditingController();
var loading = false;
final usernameAvailabilityDebouncer = Debouncer();
bool? usernameAvailable;
var canCreateAccount = false;
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
const TitleText('Create a Finnow Account'),
const SizedBox(height: 20),
const FormLabel('Username'),
TextFormField(
controller: usernameTextController,
onChanged: (s) {
formValuesUpdated();
usernameAvailabilityDebouncer
.run(() => checkUsernameAvailability());
},
decoration: const InputDecoration(
hintText: 'Enter username', border: OutlineInputBorder()),
validator: usernameValidator,
),
if (usernameAvailable != null && usernameAvailable == true) ...[
const Text('Username is available.',
style: TextStyle(color: Colors.green))
],
if (usernameAvailable != null && usernameAvailable == false) ...[
const Text('Username is taken.',
style: TextStyle(color: Colors.red))
],
const SizedBox(
height: 10,
),
const FormLabel('Password'),
TextFormField(
controller: passwordTextController,
onChanged: (s) => formValuesUpdated(),
obscureText: true,
decoration: const InputDecoration(
hintText: 'Enter password', border: OutlineInputBorder()),
validator: passwordValidator),
const SizedBox(height: 10),
TextButton(
onPressed: loading || !canCreateAccount ? null : createAccount,
child: const Text('Create Account'),
),
const SizedBox(
height: 10,
),
TextButton(
onPressed: () {
getIt<GoRouter>().replace('/login');
},
child: const Text('Back to Login'))
]));
}
@override
void dispose() {
usernameTextController.dispose();
passwordTextController.dispose();
super.dispose();
}
void formValuesUpdated() {
final usernameValidation = usernameValidator(usernameTextController.text);
final passwordValidation = passwordValidator(passwordTextController.text);
setState(() {
canCreateAccount =
usernameValidation == null && passwordValidation == null;
});
}
void checkUsernameAvailability() async {
final usernameText = usernameTextController.text;
// Set usernameAvailable to null if the username is invalid.
if (usernameValidator(usernameText) != null) {
setState(() => usernameAvailable = null);
return;
}
// Otherwise, there's a valid username, so check if it's available.
final available = await getUsernameAvailability(usernameText);
setState(() => usernameAvailable = available);
}
void createAccount() async {
print('Creating account...');
if (formKey.currentState!.validate()) {
setState(() => loading = true);
final credentials = LoginCredentials(
usernameTextController.text, passwordTextController.text);
try {
final token = await postRegister(credentials);
getIt<AuthenticationModel>().state =
Authenticated(token, credentials.username);
} catch (e) {
print(e);
} finally {
setState(() => loading = false);
}
}
}
}