Files
sendico/frontend/pweb/lib/pages/login/form.dart
2025-12-11 17:41:25 +03:00

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