118 lines
3.9 KiB
Dart
118 lines
3.9 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:pshared/provider/account.dart';
|
|
import 'package:pshared/provider/locale.dart';
|
|
|
|
import 'package:pweb/app/router/pages.dart';
|
|
import 'package:pweb/pages/login/buttons.dart';
|
|
import 'package:pweb/pages/login/header.dart';
|
|
import 'package:pweb/widgets/constrained_form.dart';
|
|
import 'package:pweb/widgets/password/hint/short.dart';
|
|
import 'package:pweb/widgets/password/password.dart';
|
|
import 'package:pweb/widgets/username.dart';
|
|
import 'package:pweb/widgets/vspacer.dart';
|
|
import 'package:pweb/widgets/error/snackbar.dart';
|
|
import 'package:pweb/services/posthog.dart';
|
|
|
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
|
|
|
|
|
class LoginForm extends StatefulWidget {
|
|
const LoginForm({super.key});
|
|
|
|
@override
|
|
State<LoginForm> createState() => _LoginFormState();
|
|
}
|
|
|
|
class _LoginFormState extends State<LoginForm> {
|
|
final TextEditingController _usernameController = TextEditingController();
|
|
final TextEditingController _passwordController = TextEditingController();
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
// ValueNotifiers for validation state
|
|
final ValueNotifier<bool> _isUsernameAcceptable = ValueNotifier<bool>(false);
|
|
final ValueNotifier<bool> _isPasswordAcceptable = ValueNotifier<bool>(false);
|
|
|
|
Future<String?> _login(BuildContext context, VoidCallback onLogin, void Function(Object e) onError) async {
|
|
final provider = Provider.of<AccountProvider>(context, listen: false);
|
|
|
|
try {
|
|
final outcome = await provider.login(
|
|
email: _usernameController.text,
|
|
password: _passwordController.text,
|
|
locale: context.read<LocaleProvider>().locale.languageCode,
|
|
);
|
|
unawaited(PosthogService.login(pending: outcome.isPending));
|
|
if (outcome.isPending) {
|
|
// TODO: fix context usage
|
|
navigateAndReplace(context, Pages.sfactor);
|
|
} else {
|
|
onLogin();
|
|
}
|
|
return 'ok';
|
|
} catch (e) {
|
|
onError(provider.error ?? e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_usernameController.dispose();
|
|
_passwordController.dispose();
|
|
_isUsernameAcceptable.dispose();
|
|
_isPasswordAcceptable.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Align(
|
|
alignment: Alignment.center,
|
|
child: ConstrainedBox(
|
|
constraints: BoxConstraints(maxWidth: 400, maxHeight: 300),
|
|
child: Card(
|
|
child: ConstrainedForm(
|
|
formKey: _formKey,
|
|
children: [
|
|
const LoginHeader(),
|
|
const VSpacer(multiplier: 1.5),
|
|
UsernameField(
|
|
controller: _usernameController,
|
|
onValid: (isValid) => _isUsernameAcceptable.value = isValid,
|
|
),
|
|
VSpacer(),
|
|
defaulRulesPasswordField(
|
|
context,
|
|
controller: _passwordController,
|
|
validationRuleBuilder: (rules, value) => shortValidation(context, rules, value),
|
|
onValid: (isValid) => _isPasswordAcceptable.value = isValid,
|
|
),
|
|
VSpacer(multiplier: 2.0),
|
|
ValueListenableBuilder<bool>(
|
|
valueListenable: _isUsernameAcceptable,
|
|
builder: (context, isUsernameValid, child) => ValueListenableBuilder<bool>(
|
|
valueListenable: _isPasswordAcceptable,
|
|
builder: (context, isPasswordValid, child) => ButtonsRow(
|
|
onSignUp: () => navigate(context, Pages.signup),
|
|
login: () => _login(
|
|
context,
|
|
() => navigateAndReplace(context, Pages.dashboard),
|
|
(e) => postNotifyUserOfErrorX(
|
|
context: context,
|
|
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
|
exception: e,
|
|
),
|
|
),
|
|
isEnabled: isUsernameValid && isPasswordValid,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)));
|
|
}
|