redesigned payment page + a lot of fixes
This commit is contained in:
63
frontend/pweb/lib/controllers/auth/account_loader.dart
Normal file
63
frontend/pweb/lib/controllers/auth/account_loader.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:pshared/models/auth/state.dart';
|
||||
import 'package:pshared/provider/account.dart';
|
||||
|
||||
import 'package:pweb/models/account/account_loader.dart';
|
||||
|
||||
|
||||
class AccountLoaderController extends ChangeNotifier {
|
||||
AccountProvider? _provider;
|
||||
AuthState? _handledState;
|
||||
AccountLoaderAction? _action;
|
||||
Object? _error;
|
||||
|
||||
AccountLoaderAction? get action => _action;
|
||||
Object? get error => _error;
|
||||
|
||||
void update(AccountProvider provider) {
|
||||
if (identical(_provider, provider)) return;
|
||||
_provider?.removeListener(_handleProviderChanged);
|
||||
_provider = provider;
|
||||
_provider?.addListener(_handleProviderChanged);
|
||||
_evaluate(provider);
|
||||
}
|
||||
|
||||
AccountLoaderAction? consumeAction() {
|
||||
final action = _action;
|
||||
_action = null;
|
||||
return action;
|
||||
}
|
||||
|
||||
void _handleProviderChanged() {
|
||||
final provider = _provider;
|
||||
if (provider == null) return;
|
||||
_evaluate(provider);
|
||||
}
|
||||
|
||||
void _evaluate(AccountProvider provider) {
|
||||
if (_handledState == provider.authState) return;
|
||||
_handledState = provider.authState;
|
||||
|
||||
switch (provider.authState) {
|
||||
case AuthState.error:
|
||||
_error = provider.error ?? Exception('Authorization failed');
|
||||
_action = AccountLoaderAction.showErrorAndGoToLogin;
|
||||
notifyListeners();
|
||||
break;
|
||||
case AuthState.empty:
|
||||
_error = null;
|
||||
_action = AccountLoaderAction.goToLogin;
|
||||
notifyListeners();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_provider?.removeListener(_handleProviderChanged);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
143
frontend/pweb/lib/controllers/auth/account_name.dart
Normal file
143
frontend/pweb/lib/controllers/auth/account_name.dart
Normal file
@@ -0,0 +1,143 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/provider/account.dart';
|
||||
|
||||
import 'package:pweb/models/state/edit_state.dart';
|
||||
|
||||
|
||||
class AccountNameController extends ChangeNotifier {
|
||||
AccountNameController({
|
||||
required this.initialFirstName,
|
||||
required this.initialLastName,
|
||||
required this.errorMessage,
|
||||
}) {
|
||||
_firstNameController = TextEditingController(text: initialFirstName);
|
||||
_lastNameController = TextEditingController(text: initialLastName);
|
||||
_lastSyncedFirstName = initialFirstName;
|
||||
_lastSyncedLastName = initialLastName;
|
||||
}
|
||||
|
||||
AccountProvider? _accountProvider;
|
||||
final String initialFirstName;
|
||||
final String initialLastName;
|
||||
final String errorMessage;
|
||||
|
||||
late final TextEditingController _firstNameController;
|
||||
late final TextEditingController _lastNameController;
|
||||
EditState _editState = EditState.view;
|
||||
String _errorText = '';
|
||||
bool _disposed = false;
|
||||
String _lastSyncedFirstName = '';
|
||||
String _lastSyncedLastName = '';
|
||||
|
||||
TextEditingController get firstNameController => _firstNameController;
|
||||
TextEditingController get lastNameController => _lastNameController;
|
||||
EditState get editState => _editState;
|
||||
String get errorText => _errorText;
|
||||
bool get isEditing => _editState != EditState.view;
|
||||
bool get isSaving => _editState == EditState.saving;
|
||||
bool get isBusy => (_accountProvider?.isLoading ?? false) || isSaving;
|
||||
|
||||
String get currentFirstName =>
|
||||
_accountProvider?.account?.name ?? initialFirstName;
|
||||
String get currentLastName =>
|
||||
_accountProvider?.account?.lastName ?? initialLastName;
|
||||
String get currentFullName {
|
||||
final first = currentFirstName.trim();
|
||||
final last = currentLastName.trim();
|
||||
if (first.isEmpty && last.isEmpty) return '';
|
||||
if (first.isEmpty) return last;
|
||||
if (last.isEmpty) return first;
|
||||
return '$first $last';
|
||||
}
|
||||
|
||||
void update(AccountProvider accountProvider) {
|
||||
_accountProvider = accountProvider;
|
||||
final changed = _syncNamesFromProvider();
|
||||
if (changed) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void startEditing() => _setState(EditState.edit);
|
||||
|
||||
void cancelEditing() {
|
||||
_firstNameController.text = currentFirstName;
|
||||
_lastNameController.text = currentLastName;
|
||||
_setError('');
|
||||
_setState(EditState.view);
|
||||
}
|
||||
|
||||
Future<bool> save() async {
|
||||
final accountProvider = _accountProvider;
|
||||
if (accountProvider == null) return false;
|
||||
|
||||
final newFirstName = _firstNameController.text.trim();
|
||||
final newLastName = _lastNameController.text.trim();
|
||||
final currentFirst = currentFirstName;
|
||||
final currentLast = currentLastName;
|
||||
|
||||
if (newFirstName.isEmpty ||
|
||||
(newFirstName == currentFirst && newLastName == currentLast)) {
|
||||
cancelEditing();
|
||||
return false;
|
||||
}
|
||||
|
||||
_setError('');
|
||||
_setState(EditState.saving);
|
||||
|
||||
try {
|
||||
await accountProvider.resetUsername(
|
||||
newFirstName,
|
||||
lastName: newLastName,
|
||||
);
|
||||
_setState(EditState.view);
|
||||
return true;
|
||||
} catch (_) {
|
||||
_setError(errorMessage);
|
||||
_setState(EditState.edit);
|
||||
return false;
|
||||
} finally {
|
||||
if (_editState == EditState.saving) {
|
||||
_setState(EditState.edit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _syncNamesFromProvider() {
|
||||
if (isEditing) return false;
|
||||
final latestFirstName = currentFirstName;
|
||||
final latestLastName = currentLastName;
|
||||
final didChange = latestFirstName != _lastSyncedFirstName ||
|
||||
latestLastName != _lastSyncedLastName;
|
||||
if (_firstNameController.text != latestFirstName) {
|
||||
_firstNameController.text = latestFirstName;
|
||||
}
|
||||
if (_lastNameController.text != latestLastName) {
|
||||
_lastNameController.text = latestLastName;
|
||||
}
|
||||
_lastSyncedFirstName = latestFirstName;
|
||||
_lastSyncedLastName = latestLastName;
|
||||
return didChange;
|
||||
}
|
||||
|
||||
void _setState(EditState value) {
|
||||
if (_disposed || _editState == value) return;
|
||||
_editState = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _setError(String value) {
|
||||
if (_disposed) return;
|
||||
_errorText = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
_firstNameController.dispose();
|
||||
_lastNameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
44
frontend/pweb/lib/controllers/auth/email.dart
Normal file
44
frontend/pweb/lib/controllers/auth/email.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:email_validator/email_validator.dart';
|
||||
|
||||
|
||||
class EmailFieldController {
|
||||
final TextEditingController textController;
|
||||
final ValueNotifier<bool> isValid;
|
||||
|
||||
EmailFieldController({
|
||||
TextEditingController? controller,
|
||||
}) : textController = controller ?? TextEditingController(),
|
||||
isValid = ValueNotifier<bool>(false);
|
||||
|
||||
String get text => textController.text;
|
||||
|
||||
void setText(String value) {
|
||||
textController.text = value;
|
||||
onChanged(value);
|
||||
}
|
||||
|
||||
bool _isValidEmail(String? value) {
|
||||
final trimmed = value?.trim() ?? '';
|
||||
if (trimmed.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return EmailValidator.validate(trimmed);
|
||||
}
|
||||
|
||||
String? validate(String? value, String invalidMessage) {
|
||||
final result = _isValidEmail(value) ? null : invalidMessage;
|
||||
isValid.value = result == null;
|
||||
return result;
|
||||
}
|
||||
|
||||
void onChanged(String? value) {
|
||||
isValid.value = _isValidEmail(value);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
textController.dispose();
|
||||
isValid.dispose();
|
||||
}
|
||||
}
|
||||
103
frontend/pweb/lib/controllers/auth/password_form.dart
Normal file
103
frontend/pweb/lib/controllers/auth/password_form.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/provider/account.dart';
|
||||
import 'package:pshared/api/responses/error/server.dart';
|
||||
|
||||
import 'package:pweb/models/state/edit_state.dart';
|
||||
import 'package:pweb/models/auth/password_field_type.dart';
|
||||
import 'package:pweb/models/state/visibility.dart';
|
||||
|
||||
|
||||
class PasswordFormController extends ChangeNotifier {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final oldPasswordController = TextEditingController();
|
||||
final newPasswordController = TextEditingController();
|
||||
final confirmPasswordController = TextEditingController();
|
||||
|
||||
final Map<PasswordFieldType, VisibilityState> _visibility = {
|
||||
PasswordFieldType.old: VisibilityState.hidden,
|
||||
PasswordFieldType.newPassword: VisibilityState.hidden,
|
||||
PasswordFieldType.confirmPassword: VisibilityState.hidden,
|
||||
};
|
||||
EditState _state = EditState.view;
|
||||
String _errorText = '';
|
||||
bool _disposed = false;
|
||||
|
||||
bool get isExpanded => _state != EditState.view;
|
||||
bool get isSaving => _state == EditState.saving;
|
||||
String get errorText => _errorText;
|
||||
EditState get state => _state;
|
||||
bool isPasswordVisible(PasswordFieldType type) =>
|
||||
_visibility[type] == VisibilityState.visible;
|
||||
|
||||
void toggleExpanded() {
|
||||
if (_state == EditState.saving) return;
|
||||
_setState(_state == EditState.view ? EditState.edit : EditState.view);
|
||||
_setError('');
|
||||
}
|
||||
|
||||
void togglePasswordVisibility(PasswordFieldType type) {
|
||||
final current = _visibility[type];
|
||||
if (current == null) return;
|
||||
_visibility[type] = current == VisibilityState.hidden
|
||||
? VisibilityState.visible
|
||||
: VisibilityState.hidden;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<bool> submit({
|
||||
required AccountProvider accountProvider,
|
||||
required String errorText,
|
||||
}) async {
|
||||
final currentForm = formKey.currentState;
|
||||
if (currentForm == null || !currentForm.validate()) return false;
|
||||
|
||||
_setState(EditState.saving);
|
||||
_setError('');
|
||||
|
||||
try {
|
||||
await accountProvider.changePassword(
|
||||
oldPasswordController.text,
|
||||
newPasswordController.text,
|
||||
);
|
||||
|
||||
oldPasswordController.clear();
|
||||
newPasswordController.clear();
|
||||
confirmPasswordController.clear();
|
||||
_setState(EditState.view);
|
||||
return true;
|
||||
} catch (e) {
|
||||
_setError(_errorMessageForException(e, errorText));
|
||||
_setState(EditState.edit);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
String _errorMessageForException(Object exception, String fallback) {
|
||||
if (exception is ErrorResponse && exception.details.isNotEmpty) {
|
||||
return exception.details;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void _setState(EditState value) {
|
||||
if (_state == value || _disposed) return;
|
||||
_state = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _setError(String value) {
|
||||
if (_disposed) return;
|
||||
_errorText = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
oldPasswordController.dispose();
|
||||
newPasswordController.dispose();
|
||||
confirmPasswordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user