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 createState() => _LoginFormState(); } class _LoginFormState extends State { final TextEditingController _usernameController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final _formKey = GlobalKey(); // ValueNotifiers for validation state final ValueNotifier _isUsernameAcceptable = ValueNotifier(false); final ValueNotifier _isPasswordAcceptable = ValueNotifier(false); Future _login(BuildContext context, VoidCallback onLogin, void Function(Object e) onError) async { final provider = Provider.of(context, listen: false); try { final outcome = await provider.login( email: _usernameController.text, password: _passwordController.text, locale: context.read().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( valueListenable: _isUsernameAcceptable, builder: (context, isUsernameValid, child) => ValueListenableBuilder( 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, ), ), ), ], ), ))); }