redesigned payment page + a lot of fixes

This commit is contained in:
Arseni
2026-02-21 21:55:20 +03:00
parent a68aa2abff
commit 0c6fa03aba
208 changed files with 4062 additions and 2217 deletions

View 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();
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}