Added Localizations and ran small fixes #6

Merged
tech merged 3 commits from devKA into main 2025-11-26 14:00:56 +00:00
53 changed files with 620 additions and 296 deletions
Showing only changes of commit 44446c6ad4 - Show all commits

BIN
frontend/.DS_Store vendored Normal file

Binary file not shown.

BIN
frontend/pshared/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -22,10 +22,6 @@ enum ResourceType {
@JsonValue('clients') @JsonValue('clients')
clients, clients,
/// Represents comments on tasks or other resources
@JsonValue('comments')
comments,
/// Represents invitations sent to users /// Represents invitations sent to users
@JsonValue('invitations') @JsonValue('invitations')
invitations, invitations,
@@ -46,6 +42,9 @@ enum ResourceType {
@JsonValue('organizations') @JsonValue('organizations')
organizations, organizations,
@JsonValue('chain_wallets')
chainWallets,
/// Represents permissions service /// Represents permissions service
@JsonValue('permissions') @JsonValue('permissions')
permissions, permissions,
@@ -54,25 +53,6 @@ enum ResourceType {
@JsonValue('policies') @JsonValue('policies')
policies, policies,
/// Represents task or project priorities
@JsonValue('priorities')
priorities,
/// Represents priority groups
@JsonValue('priority_groups')
priorityGroups,
/// Represents projects managed in the system
@JsonValue('projects')
projects,
@JsonValue('properties')
properties,
/// Represents reactions
@JsonValue('reactions')
reactions,
/// Represents refresh tokens for authentication /// Represents refresh tokens for authentication
@JsonValue('refresh_tokens') @JsonValue('refresh_tokens')
refreshTokens, refreshTokens,
@@ -88,20 +68,4 @@ enum ResourceType {
/// Represents steps in workflows or processes /// Represents steps in workflows or processes
@JsonValue('steps') @JsonValue('steps')
steps, steps,
/// Represents tasks managed in the system
@JsonValue('tasks')
tasks,
/// Represents teams managed in the system
@JsonValue('teams')
teams,
/// Represents workflows for tasks or projects
@JsonValue('workflows')
workflows,
/// Represents workspaces containing projects and teams
@JsonValue('workspaces')
workspaces;
} }

View File

@@ -21,8 +21,8 @@ class ResourceContainer<T extends GenericProvider> extends StatelessWidget {
@override @override
Widget build(BuildContext context) => Consumer<T>(builder: (context, provider, _) { Widget build(BuildContext context) => Consumer<T>(builder: (context, provider, _) {
if (provider.isLoading) return loading ?? Center(child: CircularProgressIndicator()); if (provider.isLoading) return loading ?? Center(child: CircularProgressIndicator());
if (provider.error != null) return error ?? Text('Error while loading data. Try again'); //TODO: need to implement localizations and add more details to the error if (provider.error != null) return error ?? Text('Error while loading data. Try again');
if (provider.isEmpty) return empty ?? Text('Empty data'); //TODO: need to implement localizations too if (provider.isEmpty) return empty ?? Text('Empty data');
return builder(context, provider); return builder(context, provider);
}); });
} }

View File

@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@@ -5,6 +5,6 @@ class Constants {
class AppConfig { class AppConfig {
static const String appName = String.fromEnvironment( static const String appName = String.fromEnvironment(
'APP_NAME', 'APP_NAME',
defaultValue: 'SendiCo', defaultValue: 'sendico',
); );
} }

View File

@@ -36,9 +36,9 @@
"signupError": "Failed to signup: {error}", "signupError": "Failed to signup: {error}",
"signupSuccess": "Email confirmation message has been sent to {email}. Please, open it and click link to activate your account.", "signupSuccess": "Email confirmation message has been sent to {email}. Please, open it and click link to activate your account.",
"connectivityError": "Cannot reach the server at {serverAddress}. Check your network and try again.", "connectivityError": "Cannot reach the server at {serverAddress}. Check your network and try again.",
"errorAccountExists": "Account already exists",
"errorAccountNotVerified": "Your account hasn't been verified yet. Please check your email to complete the verification", "errorAccountNotVerified": "Your account hasn't been verified yet. Please check your email to complete the verification",
"errorLoginUnauthorized": "Login or password is incorrect. Please try again", "errorLoginUnauthorized": "Login or password is incorrect. Please try again",
"errorAccountExists": "Account with this login already exists",
"errorInternalError": "An internal error occurred. We're aware of the issue and working to resolve it. Please try again later", "errorInternalError": "An internal error occurred. We're aware of the issue and working to resolve it. Please try again later",
"errorVerificationTokenNotFound": "Account for verification not found. Sign up again", "errorVerificationTokenNotFound": "Account for verification not found. Sign up again",
"created": "Created", "created": "Created",
@@ -441,5 +441,36 @@
"verificationStatusErrorUnknown": "Unexpected error occurred while verification. Try once again or contact support", "verificationStatusErrorUnknown": "Unexpected error occurred while verification. Try once again or contact support",
"accountVerified": "Account Verified!", "accountVerified": "Account Verified!",
"accountVerifiedDescription": "Your account has been successfully verified. You can now log in to access your account.", "accountVerifiedDescription": "Your account has been successfully verified. You can now log in to access your account.",
"retryVerification": "Retry Verification" "retryVerification": "Retry Verification",
} "save": "Save",
"editWallet": "Edit Wallet",
"userNamePlaceholder": "User Name",
"noWalletSelected": "No wallet selected",
"noWalletsAvailable": "No wallets available",
"walletActivity": "Wallet activity",
"reset": "Reset",
"failedToLoadHistory": "Failed to load history",
"retry": "Retry",
"walletName": "Wallet name",
"walletNameSaved": "Wallet name saved",
"topUpBalance": "Top Up Balance",
"addFunctionality": "Add functionality",
"walletHistoryEmpty": "No history yet",
"colType": "Type",
"colAmount": "Amount",
"colBalance": "Balance",
"colCounterparty": "Counterparty",
"colDate": "Date",
"colComment": "Comment",
"recipientNoPaymentDetails": "This recipient has no available payment details.",
"paymentInfo": "Payment info",
"recipient": "Recipient",
"chooseAnotherRecipient": "Choose another recipient",
"noRecipientsYet": "No recipients yet.",
"noRecipientsFound": "No recipients found for this query.",
"sourceOfFunds": "Source of funds",
"walletTopUp": "Top up",
"englishLanguage": "English",
"russianLanguage": "Russian",
"germanLanguage": "German"
}

View File

@@ -38,6 +38,7 @@
"connectivityError": "Не удается связаться с сервером {serverAddress}. Проверьте ваше интернет-соединение и попробуйте снова.", "connectivityError": "Не удается связаться с сервером {serverAddress}. Проверьте ваше интернет-соединение и попробуйте снова.",
"errorAccountNotVerified": "Ваш аккаунт еще не подтвержден. Пожалуйста, проверьте вашу электронную почту для завершения верификации", "errorAccountNotVerified": "Ваш аккаунт еще не подтвержден. Пожалуйста, проверьте вашу электронную почту для завершения верификации",
"errorLoginUnauthorized": "Неверный логин или пароль. Пожалуйста, попробуйте снова", "errorLoginUnauthorized": "Неверный логин или пароль. Пожалуйста, попробуйте снова",
"errorAccountExists": "Аккаунт с таким логином уже существует",
"errorInternalError": "Произошла внутренняя ошибка. Мы в курсе проблемы и работаем над ее решением. Пожалуйста, попробуйте позже", "errorInternalError": "Произошла внутренняя ошибка. Мы в курсе проблемы и работаем над ее решением. Пожалуйста, попробуйте позже",
"errorVerificationTokenNotFound": "Аккаунт для верификации не найден. Зарегистрируйтесь снова", "errorVerificationTokenNotFound": "Аккаунт для верификации не найден. Зарегистрируйтесь снова",
"created": "Создано", "created": "Создано",
@@ -433,5 +434,36 @@
"verificationStatusErrorUnknown": "Произошла непредвиденная ошибка при подтверждении. Попробуйте еще раз или обратитесь в службу поддержки", "verificationStatusErrorUnknown": "Произошла непредвиденная ошибка при подтверждении. Попробуйте еще раз или обратитесь в службу поддержки",
"accountVerified": "Аккаунт подтвержден!", "accountVerified": "Аккаунт подтвержден!",
"accountVerifiedDescription": "Ваш аккаунт успешно подтвержден. Теперь вы можете войти, чтобы получить доступ к своему аккаунту", "accountVerifiedDescription": "Ваш аккаунт успешно подтвержден. Теперь вы можете войти, чтобы получить доступ к своему аккаунту",
"retryVerification": "Повторить подтверждение" "retryVerification": "Повторить подтверждение",
} "save": "Сохранить",
"editWallet": "Редактировать кошелек",
"userNamePlaceholder": "Имя пользователя",
"noWalletSelected": "Кошелек не выбран",
"noWalletsAvailable": "Кошельки отсутствуют",
"walletActivity": "Активность кошелька",
"reset": "Сбросить",
"failedToLoadHistory": "Не удалось загрузить историю",
"retry": "Повторить",
"walletName": "Название кошелька",
"walletNameSaved": "Название кошелька сохранено",
"topUpBalance": "Пополнить баланс",
"addFunctionality": "Добавить функциональность",
"walletHistoryEmpty": "История пуста",
"colType": "Тип",
"colAmount": "Сумма",
"colBalance": "Баланс",
"colCounterparty": "Контрагент",
"colDate": "Дата",
"colComment": "Комментарий",
"recipientNoPaymentDetails": "У этого получателя нет доступных платежных данных.",
"paymentInfo": "Платежная информация",
"recipient": "Получатель",
"chooseAnotherRecipient": "Выбрать другого получателя",
"noRecipientsYet": "Получателей пока нет.",
"noRecipientsFound": "Получатели по запросу не найдены.",
"sourceOfFunds": "Источник средств",
"walletTopUp": "Пополнение",
"englishLanguage": "Английский",
"russianLanguage": "Русский",
"germanLanguage": "Немецкий"
}

View File

@@ -8,8 +8,9 @@ import 'package:provider/provider.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:pshared/config/constants.dart'; import 'package:pshared/config/constants.dart';
import 'package:pshared/provider/account.dart';
import 'package:pshared/provider/locale.dart'; import 'package:pshared/provider/locale.dart';
import 'package:pshared/provider/permissions.dart';
import 'package:pshared/provider/account.dart';
import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/organizations.dart';
import 'package:pweb/app/app.dart'; import 'package:pweb/app/app.dart';
@@ -63,6 +64,11 @@ void main() async {
create: (_) => TwoFactorProvider(), create: (_) => TwoFactorProvider(),
update: (context, accountProvider, provider) => provider!..update(accountProvider), update: (context, accountProvider, provider) => provider!..update(accountProvider),
), ),
ChangeNotifierProxyProvider<OrganizationsProvider, PermissionsProvider>(
create: (_) => PermissionsProvider(),
update: (context, orgnization, provider) => provider!..update(orgnization),
),
ChangeNotifierProvider(create: (_) => TwoFactorProvider()),
ChangeNotifierProvider(create: (_) => OrganizationsProvider()), ChangeNotifierProvider(create: (_) => OrganizationsProvider()),
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()), ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),

View File

@@ -1,18 +1,22 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:pshared/models/payment/status.dart'; import 'package:pshared/models/payment/status.dart';
import 'package:pweb/models/currency.dart'; import 'package:pweb/models/currency.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
enum WalletTransactionType { topUp, payout } enum WalletTransactionType { topUp, payout }
extension WalletTransactionTypeX on WalletTransactionType { extension WalletTransactionTypeX on WalletTransactionType {
String label(BuildContext context) { String label(BuildContext context) {
final loc = AppLocalizations.of(context)!;
switch (this) { switch (this) {
case WalletTransactionType.topUp: case WalletTransactionType.topUp:
return 'Top up'; return loc.walletTopUp;
case WalletTransactionType.payout: case WalletTransactionType.payout:
return 'Payout'; return loc.payout;
} }
} }

View File

@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/models/recipient/filter.dart'; import 'package:pshared/models/recipient/filter.dart';
import 'package:pweb/pages/address_book/page/filter_button.dart'; import 'package:pweb/pages/address_book/page/filter_button.dart';
import 'package:pweb/pages/address_book/page/header.dart'; import 'package:pweb/pages/address_book/page/header.dart';
import 'package:pweb/pages/address_book/page/list.dart'; import 'package:pweb/pages/address_book/page/list.dart';
@@ -14,7 +14,7 @@ import 'package:pweb/providers/recipient.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
class RecipientAddressBookPage extends StatelessWidget { class RecipientAddressBookPage extends StatefulWidget {
final ValueChanged<Recipient> onRecipientSelected; final ValueChanged<Recipient> onRecipientSelected;
final VoidCallback onAddRecipient; final VoidCallback onAddRecipient;
final ValueChanged<Recipient>? onEditRecipient; final ValueChanged<Recipient>? onEditRecipient;
@@ -31,32 +31,65 @@ class RecipientAddressBookPage extends StatelessWidget {
static const double _bigBox = 30; static const double _bigBox = 30;
static const double _smallBox = 20; static const double _smallBox = 20;
@override
State<RecipientAddressBookPage> createState() => _RecipientAddressBookPageState();
}
class _RecipientAddressBookPageState extends State<RecipientAddressBookPage> {
late final TextEditingController _searchController;
late final FocusNode _searchFocusNode;
@override
void initState() {
super.initState();
final provider = context.read<RecipientProvider>();
_searchController = TextEditingController(text: provider.query);
_searchFocusNode = FocusNode();
}
@override
void dispose() {
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}
void _syncSearchField(RecipientProvider provider) {
final query = provider.query;
if (_searchController.text == query) return;
_searchController.value = TextEditingValue(
text: query,
selection: TextSelection.collapsed(offset: query.length),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!; final loc = AppLocalizations.of(context)!;
final provider = context.watch<RecipientProvider>(); final provider = context.watch<RecipientProvider>();
_syncSearchField(provider);
if (provider.isLoading) { if (provider.isLoading) {
return const Center(child: CircularProgressIndicator()); //TODO This should be in the provider return const Center(child: CircularProgressIndicator());
} }
if (provider.error != null) { if (provider.error != null) {
return Center(child: Text('Error: ${provider.error}')); return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
} }
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
RecipientAddressBookHeader(onAddRecipient: onAddRecipient), RecipientAddressBookHeader(onAddRecipient: widget.onAddRecipient),
const SizedBox(height: _smallBox), const SizedBox(height: RecipientAddressBookPage._smallBox),
RecipientSearchField( RecipientSearchField(
controller: TextEditingController(text: provider.query), controller: _searchController,
focusNode: FocusNode(), focusNode: _searchFocusNode,
onChanged: provider.setQuery, onChanged: provider.setQuery,
), ),
const SizedBox(height: _bigBox), const SizedBox(height: RecipientAddressBookPage._bigBox),
Row( Row(
children: [ children: [
RecipientFilterButton( RecipientFilterButton(
@@ -86,17 +119,17 @@ class RecipientAddressBookPage extends StatelessWidget {
], ],
), ),
SizedBox( SizedBox(
height: _expandedHeight, height: RecipientAddressBookPage._expandedHeight,
child: Padding( child: Padding(
padding: const EdgeInsets.all(_paddingAll), padding: const EdgeInsets.all(RecipientAddressBookPage._paddingAll),
child: RecipientAddressBookList( child: RecipientAddressBookList(
filteredRecipients: provider.filteredRecipients, filteredRecipients: provider.filteredRecipients,
onEdit: (recipient) => onEditRecipient?.call(recipient), onEdit: (recipient) => widget.onEditRecipient?.call(recipient),
onSelected: onRecipientSelected, onSelected: widget.onRecipientSelected,
), ),
), ),
), ),
], ],
); );
} }
} }

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class BalanceAddFunds extends StatelessWidget { class BalanceAddFunds extends StatelessWidget {
final VoidCallback onTopUp; final VoidCallback onTopUp;
@@ -19,6 +21,7 @@ class BalanceAddFunds extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme; final textTheme = Theme.of(context).textTheme;
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
final loc = AppLocalizations.of(context)!;
return InkWell( return InkWell(
onTap: onTopUp, onTap: onTopUp,
@@ -37,7 +40,7 @@ class BalanceAddFunds extends StatelessWidget {
), ),
const SizedBox(width: _spacingMedium), const SizedBox(width: _spacingMedium),
Text( Text(
'Add funds', loc.addFunds,
style: textTheme.bodyMedium?.copyWith( style: textTheme.bodyMedium?.copyWith(
color: colorScheme.primary, color: colorScheme.primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,

View File

@@ -5,6 +5,8 @@ import 'package:provider/provider.dart';
import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart'; import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart';
import 'package:pweb/providers/wallets.dart'; import 'package:pweb/providers/wallets.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class BalanceWidget extends StatelessWidget { class BalanceWidget extends StatelessWidget {
const BalanceWidget({super.key}); const BalanceWidget({super.key});
@@ -12,6 +14,7 @@ class BalanceWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final walletsProvider = context.watch<WalletsProvider>(); final walletsProvider = context.watch<WalletsProvider>();
final loc = AppLocalizations.of(context)!;
if (walletsProvider.isLoading) { if (walletsProvider.isLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
@@ -20,7 +23,7 @@ class BalanceWidget extends StatelessWidget {
final wallets = walletsProvider.wallets; final wallets = walletsProvider.wallets;
if (wallets == null || wallets.isEmpty) { if (wallets == null || wallets.isEmpty) {
return const Center(child: Text('No wallets available')); return Center(child: Text(loc.noWalletsAvailable));
} }
return return
@@ -29,4 +32,4 @@ class BalanceWidget extends StatelessWidget {
onWalletChanged: walletsProvider.selectWallet, onWalletChanged: walletsProvider.selectWallet,
); );
} }
} }

View File

@@ -23,7 +23,7 @@ class UploadHistorySection extends StatelessWidget {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (provider.error != null) { if (provider.error != null) {
return Text("Error: ${provider.error}"); return Text(l10.notificationError(provider.error ?? l10.noErrorInformation));
} }
final items = provider.data ?? []; final items = provider.data ?? [];

View File

@@ -9,6 +9,8 @@ import 'package:pweb/pages/dashboard/payouts/single/adress_book/long_list/long_l
import 'package:pweb/pages/dashboard/payouts/single/adress_book/short_list.dart'; import 'package:pweb/pages/dashboard/payouts/single/adress_book/short_list.dart';
import 'package:pweb/providers/recipient.dart'; import 'package:pweb/providers/recipient.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class AdressBookPayout extends StatefulWidget { class AdressBookPayout extends StatefulWidget {
final ValueChanged<Recipient> onSelected; final ValueChanged<Recipient> onSelected;
@@ -54,6 +56,7 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
final provider = context.watch<RecipientProvider>(); final provider = context.watch<RecipientProvider>();
if (provider.isLoading) { if (provider.isLoading) {
@@ -61,7 +64,7 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
} }
if (provider.error != null) { if (provider.error != null) {
return Center(child: Text('Error: ${provider.error}')); return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
} }
return SizedBox( return SizedBox(
@@ -100,4 +103,4 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
), ),
); );
} }
} }

View File

@@ -26,13 +26,10 @@ class AccountLoader extends StatelessWidget {
); );
navigateAndReplace(context, Pages.login); navigateAndReplace(context, Pages.login);
} }
if ((provider.error == null) && (provider.account == null)) { if (provider.account == null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) => navigateAndReplace(context, Pages.login));
provider.restore();
});
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
return child; return child;
}); });
} }

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pshared/provider/account.dart';
import 'package:pshared/provider/permissions.dart'; import 'package:pshared/provider/permissions.dart';
import 'package:pweb/app/router/pages.dart'; import 'package:pweb/app/router/pages.dart';
@@ -16,8 +17,10 @@ class PermissionsLoader extends StatelessWidget {
const PermissionsLoader({super.key, required this.child}); const PermissionsLoader({super.key, required this.child});
@override @override
Widget build(BuildContext context) => Consumer<PermissionsProvider>(builder: (context, provider, _) { Widget build(BuildContext context) => Consumer2<PermissionsProvider, AccountProvider>(builder: (context, provider, accountProvider, _) {
if (provider.isLoading) return const Center(child: CircularProgressIndicator()); if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.error != null) { if (provider.error != null) {
postNotifyUserOfErrorX( postNotifyUserOfErrorX(
context: context, context: context,
@@ -26,7 +29,7 @@ class PermissionsLoader extends StatelessWidget {
); );
navigateAndReplace(context, Pages.login); navigateAndReplace(context, Pages.login);
} }
if ((provider.error == null) && (provider.permissions.isEmpty)) { if (provider.error == null && !provider.isReady && accountProvider.account != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
provider.load(); provider.load();
}); });

View File

@@ -21,7 +21,7 @@ class PaymentMethodTypeSelector extends StatelessWidget {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
return DropdownButtonFormField<PaymentType>( return DropdownButtonFormField<PaymentType>(
value: value, initialValue: value,
decoration: InputDecoration(labelText: l10n.paymentType), decoration: InputDecoration(labelText: l10n.paymentType),
items: PaymentType.values.map((type) { items: PaymentType.values.map((type) {
final label = getPaymentTypeLabel(context, type); final label = getPaymentTypeLabel(context, type);

View File

@@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pweb/pages/payment_methods/icon.dart';
import 'package:pshared/models/payment/methods/type.dart'; import 'package:pshared/models/payment/methods/type.dart';
import 'package:pweb/pages/payment_methods/icon.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentMethodTile extends StatelessWidget { class PaymentMethodTile extends StatelessWidget {
const PaymentMethodTile({ const PaymentMethodTile({
super.key, super.key,
@@ -62,7 +64,7 @@ class PaymentMethodTile extends StatelessWidget {
Widget _buildMakeMainButton(BuildContext context) { Widget _buildMakeMainButton(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
return IconButton( return IconButton(
tooltip: 'Make main', tooltip: AppLocalizations.of(context)!.makeMain,
icon: Icon( icon: Icon(
method.isMain ? Icons.star : Icons.star_outline, method.isMain ? Icons.star : Icons.star_outline,
color: method.isMain ? theme.colorScheme.primary : null, color: method.isMain ? theme.colorScheme.primary : null,
@@ -97,4 +99,4 @@ class PaymentMethodTile extends StatelessWidget {
], ],
); );
} }
} }

View File

@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class SelectedRecipientCard extends StatelessWidget {
final AppDimensions dimensions;
final Recipient recipient;
final VoidCallback onClear;
const SelectedRecipientCard({
super.key,
required this.dimensions,
required this.recipient,
required this.onClear,
});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
final theme = Theme.of(context);
return Container(
width: double.infinity,
padding: EdgeInsets.all(dimensions.paddingMedium),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SectionTitle(loc.recipient),
SizedBox(height: dimensions.paddingSmall),
Row(
children: [
CircleAvatar(
child: Text(recipient.name.substring(0, 1).toUpperCase()),
),
SizedBox(width: dimensions.paddingMedium),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(recipient.name, style: theme.textTheme.titleMedium),
if (recipient.email.isNotEmpty)
Text(
recipient.email,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface,
),
),
],
),
),
TextButton(
onPressed: onClear,
child: Text(loc.chooseAnotherRecipient),
),
],
),
],
),
);
}
}

View File

@@ -10,6 +10,8 @@ import 'package:pweb/providers/payment_flow_provider.dart';
import 'package:pweb/utils/dimensions.dart'; import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/utils/payment/selector_type.dart'; import 'package:pweb/utils/payment/selector_type.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentInfoSection extends StatelessWidget { class PaymentInfoSection extends StatelessWidget {
final AppDimensions dimensions; final AppDimensions dimensions;
@@ -27,13 +29,14 @@ class PaymentInfoSection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
final hasRecipient = recipient != null; final hasRecipient = recipient != null;
final availableTypes = hasRecipient final availableTypes = hasRecipient
? pageSelector.getAvailablePaymentTypes() ? pageSelector.getAvailablePaymentTypes()
: {for (final type in PaymentType.values) type: type}; : {for (final type in PaymentType.values) type: type};
if (hasRecipient && availableTypes.isEmpty) { if (hasRecipient && availableTypes.isEmpty) {
return const Text('This recipient has no available payment details.'); return Text(loc.recipientNoPaymentDetails);
} }
final selectedType = flowProvider.selectedType; final selectedType = flowProvider.selectedType;
@@ -41,7 +44,7 @@ class PaymentInfoSection extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SectionTitle('Payment info'), SectionTitle(loc.paymentInfo),
SizedBox(height: dimensions.paddingSmall), SizedBox(height: dimensions.paddingSmall),
PaymentTypeSelector( PaymentTypeSelector(
availableTypes: availableTypes, availableTypes: availableTypes,

View File

@@ -15,6 +15,7 @@ import 'package:pweb/providers/payment_flow_provider.dart';
import 'package:pweb/providers/payment_methods.dart'; import 'package:pweb/providers/payment_methods.dart';
import 'package:pweb/providers/recipient.dart'; import 'package:pweb/providers/recipient.dart';
import 'package:pweb/utils/dimensions.dart'; import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentPageBody extends StatelessWidget { class PaymentPageBody extends StatelessWidget {
@@ -45,13 +46,14 @@ class PaymentPageBody extends StatelessWidget {
final recipientProvider = context.watch<RecipientProvider>(); final recipientProvider = context.watch<RecipientProvider>();
final flowProvider = context.watch<PaymentFlowProvider>(); final flowProvider = context.watch<PaymentFlowProvider>();
final recipient = pageSelector.selectedRecipient; final recipient = pageSelector.selectedRecipient;
final loc = AppLocalizations.of(context)!;
if (methodsProvider.isLoading) { if (methodsProvider.isLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (methodsProvider.error != null) { if (methodsProvider.error != null) {
return Center(child: Text('Error: ${methodsProvider.error}')); return Center(child: Text(loc.notificationError(methodsProvider.error ?? loc.noErrorInformation)));
} }
return Align( return Align(
@@ -74,7 +76,7 @@ class PaymentPageBody extends StatelessWidget {
PaymentHeader(), PaymentHeader(),
SizedBox(height: dimensions.paddingXXLarge), SizedBox(height: dimensions.paddingXXLarge),
const SectionTitle('Source of funds'), SectionTitle(loc.sourceOfFunds),
SizedBox(height: dimensions.paddingSmall), SizedBox(height: dimensions.paddingSmall),
PaymentMethodSelector( PaymentMethodSelector(
methodsProvider: methodsProvider, methodsProvider: methodsProvider,

View File

@@ -3,10 +3,14 @@ import 'package:flutter/material.dart';
import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/recipient/recipient.dart';
import 'package:pweb/pages/address_book/page/search.dart'; import 'package:pweb/pages/address_book/page/search.dart';
import 'package:pweb/pages/payment_methods/widgets/card.dart';
import 'package:pweb/pages/payment_methods/widgets/search.dart';
import 'package:pweb/pages/payment_methods/widgets/section_title.dart'; import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
import 'package:pweb/providers/recipient.dart'; import 'package:pweb/providers/recipient.dart';
import 'package:pweb/utils/dimensions.dart'; import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class RecipientSection extends StatelessWidget { class RecipientSection extends StatelessWidget {
final Recipient? recipient; final Recipient? recipient;
@@ -32,6 +36,7 @@ class RecipientSection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
if (recipient != null) { if (recipient != null) {
return SelectedRecipientCard( return SelectedRecipientCard(
dimensions: dimensions, dimensions: dimensions,
@@ -43,7 +48,7 @@ class RecipientSection extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SectionTitle('Recipient'), SectionTitle(loc.recipient),
SizedBox(height: dimensions.paddingSmall), SizedBox(height: dimensions.paddingSmall),
RecipientSearchField( RecipientSearchField(
controller: searchController, controller: searchController,
@@ -61,127 +66,4 @@ class RecipientSection extends StatelessWidget {
], ],
); );
} }
} }
class SelectedRecipientCard extends StatelessWidget {
final AppDimensions dimensions;
final Recipient recipient;
final VoidCallback onClear;
const SelectedRecipientCard({
super.key,
required this.dimensions,
required this.recipient,
required this.onClear,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
width: double.infinity,
padding: EdgeInsets.all(dimensions.paddingMedium),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceVariant.withOpacity(0.4),
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SectionTitle('Recipient'),
SizedBox(height: dimensions.paddingSmall),
Row(
children: [
CircleAvatar(
child: Text(recipient.name.substring(0, 1).toUpperCase()),
),
SizedBox(width: dimensions.paddingMedium),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(recipient.name, style: theme.textTheme.titleMedium),
if (recipient.email.isNotEmpty)
Text(
recipient.email,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.7),
),
),
],
),
),
TextButton(
onPressed: onClear,
child: const Text('Choose another recipient'),
),
],
),
],
),
);
}
}
class RecipientSearchResults extends StatelessWidget {
final AppDimensions dimensions;
final RecipientProvider recipientProvider;
final ValueChanged<Recipient> onRecipientSelected;
const RecipientSearchResults({
super.key,
required this.dimensions,
required this.recipientProvider,
required this.onRecipientSelected,
});
@override
Widget build(BuildContext context) {
if (recipientProvider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (recipientProvider.error != null) {
return Text(
recipientProvider.error!,
style: TextStyle(color: Theme.of(context).colorScheme.error),
);
}
if (recipientProvider.recipients.isEmpty) {
return const Text('No recipients yet.');
}
final results = recipientProvider.filteredRecipients;
if (results.isEmpty) {
return const Text('No recipients found for this query.');
}
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 240),
child: ListView.separated(
shrinkWrap: true,
itemCount: results.length,
separatorBuilder: (_, __) => SizedBox(height: dimensions.paddingSmall),
itemBuilder: (context, index) {
final recipient = results[index];
return ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
),
tileColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.2),
leading: CircleAvatar(
child: Text(recipient.name.substring(0, 1).toUpperCase()),
),
title: Text(recipient.name),
subtitle: Text(recipient.email),
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16),
onTap: () => onRecipientSelected(recipient),
);
},
),
);
}
}

View File

@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pweb/providers/recipient.dart';
import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class RecipientSearchResults extends StatelessWidget {
final AppDimensions dimensions;
final RecipientProvider recipientProvider;
final ValueChanged<Recipient> onRecipientSelected;
const RecipientSearchResults({
super.key,
required this.dimensions,
required this.recipientProvider,
required this.onRecipientSelected,
});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
if (recipientProvider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (recipientProvider.error != null) {
return Text(
loc.notificationError(recipientProvider.error ?? loc.noErrorInformation),
style: TextStyle(color: Theme.of(context).colorScheme.error),
);
}
if (recipientProvider.recipients.isEmpty) {
return Text(loc.noRecipientsYet);
}
final results = recipientProvider.filteredRecipients;
if (results.isEmpty) {
return Text(loc.noRecipientsFound);
}
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 240),
child: ListView.separated(
shrinkWrap: true,
itemCount: results.length,
separatorBuilder: (_, _) => SizedBox(height: dimensions.paddingSmall),
itemBuilder: (context, index) {
final recipient = results[index];
return ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
),
leading: CircleAvatar(
child: Text(recipient.name.substring(0, 1).toUpperCase()),
),
title: Text(recipient.name),
subtitle: Text(recipient.email),
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16),
onTap: () => onRecipientSelected(recipient),
);
},
),
);
}
}

View File

@@ -21,11 +21,12 @@ class PaymentConfigController {
} }
Future<void> addMethod() async { Future<void> addMethod() async {
final methodsProvider = context.read<PaymentMethodsProvider>();
await showDialog<PaymentMethodData>( await showDialog<PaymentMethodData>(
context: context, context: context,
builder: (_) => const AddPaymentMethodDialog(), builder: (_) => const AddPaymentMethodDialog(),
); );
loadMethods(); methodsProvider.loadMethods();
} }
Future<void> editMethod(PaymentMethod method) async { Future<void> editMethod(PaymentMethod method) async {
@@ -33,19 +34,20 @@ class PaymentConfigController {
} }
Future<void> deleteMethod(PaymentMethod method) async { Future<void> deleteMethod(PaymentMethod method) async {
final methodsProvider = context.read<PaymentMethodsProvider>();
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
builder: (_) => AlertDialog( builder: (dialogContext) => AlertDialog(
title: Text(l10n.delete), title: Text(l10n.delete),
content: Text(l10n.deletePaymentConfirmation), content: Text(l10n.deletePaymentConfirmation),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context, false), onPressed: () => Navigator.pop(dialogContext, false),
child: Text(l10n.cancel), child: Text(l10n.cancel),
), ),
ElevatedButton( ElevatedButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.pop(dialogContext, true),
child: Text(l10n.delete), child: Text(l10n.delete),
), ),
], ],
@@ -53,7 +55,7 @@ class PaymentConfigController {
); );
if (confirmed == true) { if (confirmed == true) {
context.read<PaymentMethodsProvider>().deleteMethod(method); methodsProvider.deleteMethod(method);
} }
} }

View File

@@ -1,12 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pweb/models/wallet.dart'; import 'package:pweb/models/wallet.dart';
import 'package:pweb/pages/payout_page/methods/widget.dart'; import 'package:pweb/pages/payout_page/methods/widget.dart';
import 'package:pweb/pages/payout_page/wallet/wigets.dart'; import 'package:pweb/pages/payout_page/wallet/wigets.dart';
import 'package:pweb/providers/payment_methods.dart'; import 'package:pweb/providers/payment_methods.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentConfigPage extends StatelessWidget { class PaymentConfigPage extends StatelessWidget {
final Function(Wallet) onWalletTap; final Function(Wallet) onWalletTap;
@@ -16,13 +19,14 @@ class PaymentConfigPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final provider = context.watch<PaymentMethodsProvider>(); final provider = context.watch<PaymentMethodsProvider>();
final loc = AppLocalizations.of(context)!;
if (provider.isLoading) { if (provider.isLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (provider.error != null) { if (provider.error != null) {
return Center(child: Text('Error: ${provider.error}')); return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
} }
return Column( return Column(
@@ -34,4 +38,4 @@ class PaymentConfigPage extends StatelessWidget {
], ],
); );
} }
} }

View File

@@ -3,25 +3,28 @@ import 'package:flutter/material.dart';
import 'package:pweb/models/wallet.dart'; import 'package:pweb/models/wallet.dart';
import 'package:pweb/utils/dimensions.dart'; import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class SaveWalletButton extends StatelessWidget { class SaveWalletButton extends StatelessWidget {
final Wallet wallet; final Wallet wallet;
final TextEditingController nameController; final TextEditingController nameController;
final TextEditingController balanceController; final TextEditingController balanceController;
final VoidCallback onSave; // Changed to VoidCallback final VoidCallback onSave;
const SaveWalletButton({ const SaveWalletButton({
super.key, super.key,
required this.wallet, required this.wallet,
required this.nameController, required this.nameController,
required this.balanceController, required this.balanceController,
required this.onSave, // Now matches _saveWallet signature required this.onSave,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final dimensions = AppDimensions(); final dimensions = AppDimensions();
final loc = AppLocalizations.of(context)!;
return Center( return Center(
child: SizedBox( child: SizedBox(
@@ -29,7 +32,7 @@ class SaveWalletButton extends StatelessWidget {
height: dimensions.buttonHeight, height: dimensions.buttonHeight,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall), borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
onTap: onSave, // Directly use onSave now onTap: onSave,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.colorScheme.primary, color: theme.colorScheme.primary,
@@ -37,7 +40,7 @@ class SaveWalletButton extends StatelessWidget {
), ),
child: Center( child: Center(
child: Text( child: Text(
'Save', loc.save,
style: theme.textTheme.bodyLarge?.copyWith( style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSecondary, color: theme.colorScheme.onSecondary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -49,4 +52,4 @@ class SaveWalletButton extends StatelessWidget {
), ),
); );
} }
} }

View File

@@ -1,14 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pweb/providers/page_selector.dart'; import 'package:pweb/providers/page_selector.dart';
import 'package:pweb/providers/wallets.dart'; import 'package:pweb/providers/wallets.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class SendPayoutButton extends StatelessWidget { class SendPayoutButton extends StatelessWidget {
const SendPayoutButton({super.key}); const SendPayoutButton({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
return ElevatedButton( return ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
shadowColor: null, shadowColor: null,
@@ -23,7 +28,7 @@ class SendPayoutButton extends StatelessWidget {
pageSelectorProvider.startPaymentFromWallet(wallet); pageSelectorProvider.startPaymentFromWallet(wallet);
} }
}, },
child: Text('Send Payout'), child: Text(loc.payoutNavSendPayout),
); );
} }
} }

View File

@@ -1,11 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class TopUpButton extends StatelessWidget{ class TopUpButton extends StatelessWidget{
const TopUpButton({super.key}); const TopUpButton({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
return ElevatedButton( return ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
shadowColor: null, shadowColor: null,
@@ -13,10 +16,10 @@ class TopUpButton extends StatelessWidget{
), ),
onPressed: () { onPressed: () {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Add functionality')), SnackBar(content: Text(loc.addFunctionality)),
); );
}, },
child: Text('Top Up Balance'), child: Text(loc.topUpBalance),
); );
} }
} }

View File

@@ -5,6 +5,8 @@ import 'package:provider/provider.dart';
import 'package:pweb/providers/wallets.dart'; import 'package:pweb/providers/wallets.dart';
import 'package:pweb/widgets/error/snackbar.dart'; import 'package:pweb/widgets/error/snackbar.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class WalletEditHeader extends StatefulWidget { class WalletEditHeader extends StatefulWidget {
const WalletEditHeader({super.key}); const WalletEditHeader({super.key});
@@ -33,7 +35,9 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final provider = context.watch<WalletsProvider>(); final provider = context.watch<WalletsProvider>();
final wallet = provider.selectedWallet; final wallet = provider.selectedWallet;
final loc = AppLocalizations.of(context)!;
final messanger = ScaffoldMessenger.of(context);
if (wallet == null) { if (wallet == null) {
return SizedBox.shrink(); return SizedBox.shrink();
} }
@@ -75,10 +79,10 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
Expanded( Expanded(
child: TextFormField( child: TextFormField(
controller: _controller, controller: _controller,
decoration: const InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
isDense: true, isDense: true,
hintText: 'Wallet name', hintText: loc.walletName,
), ),
), ),
), ),
@@ -112,4 +116,4 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
], ],
); );
} }
} }

View File

@@ -9,6 +9,8 @@ import 'package:pweb/pages/payout_page/wallet/history/history.dart';
import 'package:pweb/providers/wallets.dart'; import 'package:pweb/providers/wallets.dart';
import 'package:pweb/utils/dimensions.dart'; import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class WalletEditPage extends StatelessWidget { class WalletEditPage extends StatelessWidget {
final VoidCallback onBack; final VoidCallback onBack;
@@ -18,13 +20,14 @@ class WalletEditPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final dimensions = AppDimensions(); final dimensions = AppDimensions();
final loc = AppLocalizations.of(context)!;
return Consumer<WalletsProvider>( return Consumer<WalletsProvider>(
builder: (context, provider, child) { builder: (context, provider, child) {
final wallet = provider.selectedWallet; final wallet = provider.selectedWallet;
if (wallet == null) { if (wallet == null) {
return Center(child: Text('Кошелёк не выбран')); return Center(child: Text(loc.noWalletSelected));
} }
return Align( return Align(

View File

@@ -1,4 +1,3 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pshared/models/payment/status.dart'; import 'package:pshared/models/payment/status.dart';
@@ -7,6 +6,8 @@ import 'package:pshared/utils/localization.dart';
import 'package:pweb/models/wallet_transaction.dart'; import 'package:pweb/models/wallet_transaction.dart';
import 'package:pweb/providers/wallet_transactions.dart'; import 'package:pweb/providers/wallet_transactions.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class WalletHistoryFilters extends StatelessWidget { class WalletHistoryFilters extends StatelessWidget {
final WalletTransactionsProvider provider; final WalletTransactionsProvider provider;
@@ -21,6 +22,7 @@ class WalletHistoryFilters extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final loc = AppLocalizations.of(context)!;
return Card( return Card(
elevation: 2, elevation: 2,
@@ -35,13 +37,13 @@ class WalletHistoryFilters extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'Wallet activity', loc.walletActivity,
style: theme.textTheme.titleMedium, style: theme.textTheme.titleMedium,
), ),
if (provider.hasFilters) if (provider.hasFilters)
TextButton( TextButton(
onPressed: provider.resetFilters, onPressed: provider.resetFilters,
child: const Text('Reset'), child: Text(loc.reset),
), ),
], ],
), ),
@@ -81,7 +83,7 @@ class WalletHistoryFilters extends StatelessWidget {
icon: const Icon(Icons.date_range_outlined), icon: const Icon(Icons.date_range_outlined),
label: Text( label: Text(
provider.dateRange == null provider.dateRange == null
? 'Select period' ? loc.selectPeriod
: '${dateToLocalFormat(context, provider.dateRange!.start)} ${dateToLocalFormat(context, provider.dateRange!.end)}', : '${dateToLocalFormat(context, provider.dateRange!.start)} ${dateToLocalFormat(context, provider.dateRange!.end)}',
), ),
), ),
@@ -91,4 +93,4 @@ class WalletHistoryFilters extends StatelessWidget {
), ),
); );
} }
} }

View File

@@ -7,6 +7,8 @@ import 'package:pweb/pages/payout_page/wallet/history/filters.dart';
import 'package:pweb/pages/payout_page/wallet/history/table.dart'; import 'package:pweb/pages/payout_page/wallet/history/table.dart';
import 'package:pweb/providers/wallet_transactions.dart'; import 'package:pweb/providers/wallet_transactions.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class WalletHistory extends StatefulWidget { class WalletHistory extends StatefulWidget {
final Wallet wallet; final Wallet wallet;
@@ -64,6 +66,7 @@ class _WalletHistoryState extends State<WalletHistory> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final loc = AppLocalizations.of(context)!;
return Consumer<WalletTransactionsProvider>( return Consumer<WalletTransactionsProvider>(
builder: (context, provider, child) { builder: (context, provider, child) {
@@ -81,16 +84,16 @@ class _WalletHistoryState extends State<WalletHistory> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Failed to load history', loc.failedToLoadHistory,
style: theme.textTheme.titleMedium! style: theme.textTheme.titleMedium!
.copyWith(color: theme.colorScheme.error), .copyWith(color: theme.colorScheme.error),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(provider.error!), Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
const SizedBox(height: 8), const SizedBox(height: 8),
OutlinedButton( OutlinedButton(
onPressed: _load, onPressed: _load,
child: const Text('Retry'), child: Text(loc.retry),
), ),
], ],
), ),
@@ -113,4 +116,4 @@ class _WalletHistoryState extends State<WalletHistory> {
}, },
); );
} }
} }

View File

@@ -1,4 +1,3 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pweb/models/wallet_transaction.dart'; import 'package:pweb/models/wallet_transaction.dart';
@@ -6,6 +5,8 @@ import 'package:pweb/pages/payout_page/wallet/history/chip.dart';
import 'package:pweb/pages/report/table/badge.dart'; import 'package:pweb/pages/report/table/badge.dart';
import 'package:pweb/utils/currency.dart'; import 'package:pweb/utils/currency.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class WalletTransactionsTable extends StatelessWidget { class WalletTransactionsTable extends StatelessWidget {
final List<WalletTransaction> transactions; final List<WalletTransaction> transactions;
@@ -15,14 +16,15 @@ class WalletTransactionsTable extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final loc = AppLocalizations.of(context)!;
if (transactions.isEmpty) { if (transactions.isEmpty) {
return Card( return Card(
color: theme.colorScheme.onSecondary, color: theme.colorScheme.onSecondary,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: const Padding( child: Padding(
padding: EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Text('No history yet'), child: Text(loc.walletHistoryEmpty),
), ),
); );
} }
@@ -38,14 +40,14 @@ class WalletTransactionsTable extends StatelessWidget {
child: DataTable( child: DataTable(
columnSpacing: 18, columnSpacing: 18,
headingTextStyle: const TextStyle(fontWeight: FontWeight.w600), headingTextStyle: const TextStyle(fontWeight: FontWeight.w600),
columns: const [ columns: [
DataColumn(label: Text('Status')), DataColumn(label: Text(loc.colStatus)),
DataColumn(label: Text('Type')), DataColumn(label: Text(loc.colType)),
DataColumn(label: Text('Amount')), DataColumn(label: Text(loc.colAmount)),
DataColumn(label: Text('Balance')), DataColumn(label: Text(loc.colBalance)),
DataColumn(label: Text('Counterparty')), DataColumn(label: Text(loc.colCounterparty)),
DataColumn(label: Text('Date')), DataColumn(label: Text(loc.colDate)),
DataColumn(label: Text('Comment')), DataColumn(label: Text(loc.colComment)),
], ],
rows: List.generate( rows: List.generate(
transactions.length, transactions.length,
@@ -85,4 +87,4 @@ class WalletTransactionsTable extends StatelessWidget {
), ),
); );
} }
} }

View File

@@ -16,7 +16,7 @@ class PayoutDistributionChart extends StatelessWidget {
// 1) Aggregate sums // 1) Aggregate sums
final sums = <String, double>{}; final sums = <String, double>{};
for (var op in operations) { for (var op in operations) {
final name = op.name ?? AppLocalizations.of(context)!.unknown; final name = op.name;
sums[name] = (sums[name] ?? 0) + op.amount; sums[name] = (sums[name] ?? 0) + op.amount;
} }
if (sums.isEmpty) { if (sums.isEmpty) {
@@ -32,7 +32,7 @@ class PayoutDistributionChart extends StatelessWidget {
final palette = [ final palette = [
Theme.of(context).colorScheme.primary, Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.secondary, Theme.of(context).colorScheme.secondary,
Theme.of(context).colorScheme.tertiary ?? Colors.grey, Theme.of(context).colorScheme.tertiary,
Theme.of(context).colorScheme.primaryContainer, Theme.of(context).colorScheme.primaryContainer,
Theme.of(context).colorScheme.secondaryContainer, Theme.of(context).colorScheme.secondaryContainer,
]; ];

View File

@@ -8,6 +8,8 @@ import 'package:pweb/pages/report/table/filters.dart';
import 'package:pweb/pages/report/table/widget.dart'; import 'package:pweb/pages/report/table/widget.dart';
import 'package:pweb/providers/operatioins.dart'; import 'package:pweb/providers/operatioins.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class OperationHistoryPage extends StatefulWidget { class OperationHistoryPage extends StatefulWidget {
const OperationHistoryPage({super.key}); const OperationHistoryPage({super.key});
@@ -48,6 +50,7 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
return Consumer<OperationProvider>( return Consumer<OperationProvider>(
builder: (context, provider, child) { builder: (context, provider, child) {
if (provider.isLoading) { if (provider.isLoading) {
@@ -59,10 +62,10 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text('Error: ${provider.error}'), Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
ElevatedButton( ElevatedButton(
onPressed: () => provider.loadOperations(), onPressed: () => provider.loadOperations(),
child: const Text('Retry'), child: Text(loc.retry),
), ),
], ],
), ),
@@ -108,4 +111,4 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
}, },
); );
} }
} }

View File

@@ -4,9 +4,10 @@ import 'package:provider/provider.dart';
import 'package:pshared/provider/locale.dart'; import 'package:pshared/provider/locale.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
import 'package:pweb/services/amplitude.dart'; import 'package:pweb/services/amplitude.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class LocalePicker extends StatelessWidget { class LocalePicker extends StatelessWidget {
final String title; final String title;
@@ -74,13 +75,13 @@ class LocalePicker extends StatelessWidget {
String _localizedLocaleName(Locale locale, AppLocalizations loc) { String _localizedLocaleName(Locale locale, AppLocalizations loc) {
switch (locale.languageCode) { switch (locale.languageCode) {
case 'en': case 'en':
return 'English'; return loc.englishLanguage;
case 'ru': case 'ru':
return 'Русский'; return loc.russianLanguage;
case 'de': case 'de':
return 'Deutsch'; return loc.germanLanguage;
default: default:
return locale.toString(); return locale.toString();
} }
} }
} }

View File

@@ -37,7 +37,7 @@ class ProfileSettingsPage extends StatelessWidget {
errorText: loc.avatarUpdateError, errorText: loc.avatarUpdateError,
), ),
AccountName( AccountName(
name: 'User Name', name: loc.userNamePlaceholder,
title: loc.accountName, title: loc.accountName,
hintText: loc.accountNameHint, hintText: loc.accountNameHint,
errorText: loc.accountNameUpdateError, errorText: loc.accountNameUpdateError,
@@ -50,4 +50,4 @@ class ProfileSettingsPage extends StatelessWidget {
), ),
); );
} }
} }

View File

@@ -78,14 +78,21 @@ class SelectValueTile<T> extends BaseEditTile<T> {
), ),
), ),
Flexible( Flexible(
child: ListView( child: RadioGroup<T>(
shrinkWrap: true, groupValue: initial,
children: displayedOptions.map((o) => RadioListTile<T>( onChanged: (value) {
value: o, if (value != null && !isSaving) {
groupValue: initial, onSave(value);
title: Text(labelBuilder(o)), }
onChanged: isSaving ? null : (v) { if (v != null) onSave(v); }, },
)).toList(), child: ListView(
shrinkWrap: true,
children: displayedOptions.map((o) => RadioListTile<T>(
value: o,
title: Text(labelBuilder(o)),
enabled: !isSaving,
)).toList(),
),
), ),
), ),
const Divider(), const Divider(),

View File

@@ -8,8 +8,8 @@ import 'package:provider/provider.dart';
import 'package:pshared/api/requests/login_data.dart'; import 'package:pshared/api/requests/login_data.dart';
import 'package:pshared/models/describable.dart'; import 'package:pshared/models/describable.dart';
import 'package:pshared/provider/account.dart';
import 'package:pshared/provider/locale.dart'; import 'package:pshared/provider/locale.dart';
import 'package:pshared/provider/account.dart';
import 'package:pweb/app/router/pages.dart'; import 'package:pweb/app/router/pages.dart';
import 'package:pweb/pages/signup/form/content.dart'; import 'package:pweb/pages/signup/form/content.dart';

View File

@@ -71,4 +71,4 @@ class TwoFactorProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
} }
} }

View File

@@ -0,0 +1,105 @@
import 'package:collection/collection.dart';
import 'package:pshared/api/requests/login_data.dart';
import 'package:pshared/models/account/account.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/storable.dart';
import 'mock_ids.dart';
class InvalidCredentialsException implements Exception {
@override
String toString() => 'InvalidCredentialsException';
}
class DuplicateAccountException implements Exception {
@override
String toString() => 'DuplicateAccountException';
}
class AccountLoginResult {
final Account account;
final String roleId;
const AccountLoginResult({
required this.account,
required this.roleId,
});
}
class _AccountRecord {
final Account account;
final String password;
final String roleId;
const _AccountRecord({
required this.account,
required this.password,
required this.roleId,
});
}
class AccountsService {
final List<_AccountRecord> _accounts = [
_AccountRecord(
account: Account(
storable: newStorable(id: companyAccountRef),
describable: newDescribable(name: 'Sendico Company'),
avatarUrl: null,
lastName: 'Owner',
login: 'company@sendico.com',
locale: 'ru',
),
password: 'password123A',
roleId: companyRoleId,
),
_AccountRecord(
account: Account(
storable: newStorable(id: recipientAccountRef),
describable: newDescribable(name: 'John Recipient'),
avatarUrl: null,
lastName: 'Doe',
login: 'recipient@sendico.com',
locale: 'ru',
),
password: 'password123A',
roleId: recipientRoleId,
),
];
Future<AccountLoginResult> login(String email, String password, {String? locale}) async {
await Future.delayed(const Duration(milliseconds: 300));
final normalized = email.trim().toLowerCase();
final record = _accounts.where((acc) => acc.account.login.toLowerCase() == normalized).singleOrNull;
if (record == null || record.password != password) {
throw InvalidCredentialsException();
}
return AccountLoginResult(account: record.account, roleId: record.roleId);
}
Future<AccountLoginResult> signup(AccountData data, {String roleId = recipientRoleId}) async {
await Future.delayed(const Duration(milliseconds: 300));
final normalized = data.login.trim().toLowerCase();
if (_accounts.any((acc) => acc.account.login.toLowerCase() == normalized)) {
throw DuplicateAccountException();
}
final account = Account(
storable: newStorable(id: 'account-${_accounts.length + 1}'),
describable: newDescribable(name: data.name),
avatarUrl: null,
lastName: data.lastName,
login: normalized,
locale: data.locale,
);
_accounts.add(_AccountRecord(account: account, password: data.password, roleId: roleId));
return AccountLoginResult(account: account, roleId: roleId);
}
Account? getByRef(String accountRef) => _accounts.where((acc) => acc.account.id == accountRef).singleOrNull?.account;
}

View File

@@ -0,0 +1,18 @@
class AuthenticationService {
Future<bool> verifyTwoFactorCode(String code) async {
await Future.delayed(const Duration(seconds: 2));
if (code == '000000') {
return true;
} else {
throw const WrongCodeException();
}
}
}
class WrongCodeException implements Exception {
const WrongCodeException();
@override
String toString() => 'WrongCodeException';
}

View File

@@ -0,0 +1,12 @@
// Centralized identifiers for mock auth/permission data to keep the
// mock services in sync and make it easy to swap in a real API later.
const String mockOrganizationRef = 'org-sendico';
const String companyRoleId = 'role-company';
const String recipientRoleId = 'role-recipient';
const String companyAccountRef = 'account-company';
const String recipientAccountRef = 'account-recipient';
const String accountsPolicyDescriptionId = 'policy-accounts';
const String rolesPolicyDescriptionId = 'policy-roles';

View File

@@ -5,6 +5,7 @@ import 'package:pshared/api/responses/error/server.dart';
import 'package:pshared/config/constants.dart'; import 'package:pshared/config/constants.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
import 'package:pweb/services/accounts.dart';
class ErrorHandler { class ErrorHandler {
@@ -48,6 +49,8 @@ class ErrorHandler {
final errorHandlers = <Type, String Function(Object)>{ final errorHandlers = <Type, String Function(Object)>{
ErrorResponse: (ex) => _handleErrorResponseLocs(locs, ex as ErrorResponse), ErrorResponse: (ex) => _handleErrorResponseLocs(locs, ex as ErrorResponse),
ConnectivityError: (ex) => _handleConnectivityErrorLocs(locs, ex as ConnectivityError), ConnectivityError: (ex) => _handleConnectivityErrorLocs(locs, ex as ConnectivityError),
InvalidCredentialsException: (_) => locs.errorLoginUnauthorized,
DuplicateAccountException: (_) => locs.errorAccountExists,
}; };
return errorHandlers[e.runtimeType]?.call(e) ?? e.toString(); return errorHandlers[e.runtimeType]?.call(e) ?? e.toString();

View File

@@ -6,16 +6,19 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:pshared/provider/account.dart'; import 'package:pshared/provider/account.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class AccountAvatar extends StatelessWidget { class AccountAvatar extends StatelessWidget {
const AccountAvatar({super.key}); const AccountAvatar({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
return Consumer<AccountProvider>( return Consumer<AccountProvider>(
builder: (context, provider, _) => UserAccountsDrawerHeader( builder: (context, provider, _) => UserAccountsDrawerHeader(
accountName: Text(provider.account?.name ?? 'John Doe'), accountName: Text(provider.account?.name ?? loc.userNamePlaceholder),
accountEmail: Text(provider.account?.login ?? 'john.doe@acme.com'), accountEmail: Text(provider.account?.login ?? loc.usernameHint),
currentAccountPicture: CircleAvatar( currentAccountPicture: CircleAvatar(
backgroundImage: (provider.account?.avatarUrl?.isNotEmpty ?? false) backgroundImage: (provider.account?.avatarUrl?.isNotEmpty ?? false)
? CachedNetworkImageProvider(provider.account!.avatarUrl!) ? CachedNetworkImageProvider(provider.account!.avatarUrl!)

View File

@@ -13,4 +13,4 @@ T? protectedWidgetctx<T extends Widget>(BuildContext context, ResourceType resou
T? protectedWidget<T extends Widget>(PermissionsProvider provider, ResourceType resource, T child, {perm.Action? action}) { T? protectedWidget<T extends Widget>(PermissionsProvider provider, ResourceType resource, T child, {perm.Action? action}) {
return provider.canAccessResource(resource, action: action) ? child : null; return provider.canAccessResource(resource, action: action) ? child : null;
} }

View File

@@ -40,7 +40,7 @@ enum PayoutDestination {
case PayoutDestination.addrecipient: case PayoutDestination.addrecipient:
return loc.addRecipient; return loc.addRecipient;
case PayoutDestination.editwallet: case PayoutDestination.editwallet:
return 'Edit Wallet'; return loc.editWallet;
} }
} }
} }

View File

@@ -1,8 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pshared/models/resources.dart';
import 'package:pshared/provider/account.dart'; import 'package:pshared/provider/account.dart';
import 'package:pshared/provider/permissions.dart';
import 'package:pweb/pages/address_book/form/page.dart'; import 'package:pweb/pages/address_book/form/page.dart';
import 'package:pweb/pages/address_book/page/page.dart'; import 'package:pweb/pages/address_book/page/page.dart';
@@ -13,25 +15,50 @@ import 'package:pweb/pages/report/page.dart';
import 'package:pweb/pages/settings/profile/page.dart'; import 'package:pweb/pages/settings/profile/page.dart';
import 'package:pweb/pages/dashboard/dashboard.dart'; import 'package:pweb/pages/dashboard/dashboard.dart';
import 'package:pweb/providers/page_selector.dart'; import 'package:pweb/providers/page_selector.dart';
import 'package:pweb/app/router/pages.dart';
import 'package:pweb/widgets/appbar/app_bar.dart'; import 'package:pweb/widgets/appbar/app_bar.dart';
import 'package:pweb/widgets/sidebar/destinations.dart'; import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/widgets/sidebar/sidebar.dart'; import 'package:pweb/widgets/sidebar/sidebar.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class PageSelector extends StatelessWidget { class PageSelector extends StatelessWidget {
const PageSelector({super.key}); const PageSelector({super.key});
void _logout(BuildContext context) => context.read<AccountProvider>().logout(); void _logout(BuildContext context) {
context.read<AccountProvider>().logout();
navigateAndReplace(context, Pages.login);
}
@override @override
Widget build(BuildContext context) => Consumer<PageSelectorProvider>(builder:(context, provider, _) { Widget build(BuildContext context) {
final provider = context.watch<PageSelectorProvider>();
final permissions = context.watch<PermissionsProvider>();
final loc = AppLocalizations.of(context)!;
final bool restrictedAccess = !permissions.canRead(ResourceType.chainWallets);
final allowedDestinations = restrictedAccess
? <PayoutDestination>{
PayoutDestination.settings,
PayoutDestination.methods,
PayoutDestination.editwallet,
}
: PayoutDestination.values.toSet();
final selected = allowedDestinations.contains(provider.selected)
? provider.selected
: (restrictedAccess ? PayoutDestination.settings : PayoutDestination.dashboard);
if (selected != provider.selected) {
WidgetsBinding.instance.addPostFrameCallback((_) => provider.selectPage(selected));
}
Widget content; Widget content;
switch (provider.selected) { switch (selected) {
case PayoutDestination.dashboard: case PayoutDestination.dashboard:
content = DashboardPage( content = DashboardPage(
onRecipientSelected: (recipient) => onRecipientSelected: (recipient) => provider.selectRecipient(recipient),
provider.selectRecipient(recipient),
onGoToPaymentWithoutRecipient: provider.startPaymentWithoutRecipient, onGoToPaymentWithoutRecipient: provider.startPaymentWithoutRecipient,
); );
break; break;
@@ -79,16 +106,16 @@ class PageSelector extends StatelessWidget {
? WalletEditPage( ? WalletEditPage(
onBack: provider.goBackFromWalletEdit, onBack: provider.goBackFromWalletEdit,
) )
: const Center(child: Text('No wallet selected')); //TODO Localize : Center(child: Text(loc.noWalletSelected));
break; break;
default: default:
content = Text(provider.selected.name); content = Text(selected.name);
} }
return Scaffold( return Scaffold(
appBar: PayoutAppBar( appBar: PayoutAppBar(
title: Text(provider.selected.localizedLabel(context)), title: Text(selected.localizedLabel(context)),
onAddFundsPressed: () {}, onAddFundsPressed: () {},
onLogout: () => _logout(context), onLogout: () => _logout(context),
), ),
@@ -99,7 +126,7 @@ class PageSelector extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
PayoutSidebar( PayoutSidebar(
selected: provider.selected, selected: selected,
onSelected: provider.selectPage, onSelected: provider.selectPage,
onLogout: () => _logout(context), onLogout: () => _logout(context),
), ),
@@ -108,5 +135,5 @@ class PageSelector extends StatelessWidget {
), ),
), ),
); );
}); }
} }

View File

@@ -13,6 +13,7 @@ class PayoutSidebar extends StatelessWidget {
this.onLogout, this.onLogout,
this.userName, this.userName,
this.avatarUrl, this.avatarUrl,
this.items,
}); });
final PayoutDestination selected; final PayoutDestination selected;
@@ -21,11 +22,13 @@ class PayoutSidebar extends StatelessWidget {
final String? userName; final String? userName;
final String? avatarUrl; final String? avatarUrl;
final List<PayoutDestination>? items;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final items = [ final menuItems = items ??
<PayoutDestination>[
PayoutDestination.dashboard, PayoutDestination.dashboard,
PayoutDestination.recipients, PayoutDestination.recipients,
PayoutDestination.methods, PayoutDestination.methods,
@@ -49,11 +52,11 @@ class PayoutSidebar extends StatelessWidget {
theme: theme, theme: theme,
avatarUrl: avatarUrl, avatarUrl: avatarUrl,
userName: userName, userName: userName,
items: items, items: menuItems,
selected: selected, selected: selected,
onSelected: onSelected, onSelected: onSelected,
), ),
], ],
); );
} }
} }

View File

@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:pweb/widgets/sidebar/destinations.dart'; import 'package:pweb/widgets/sidebar/destinations.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class UserProfileCard extends StatelessWidget { class UserProfileCard extends StatelessWidget {
final ThemeData theme; final ThemeData theme;
@@ -21,6 +23,7 @@ class UserProfileCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
bool isSelected = selected == PayoutDestination.settings; bool isSelected = selected == PayoutDestination.settings;
final backgroundColor = isSelected final backgroundColor = isSelected
? theme.colorScheme.primaryContainer ? theme.colorScheme.primaryContainer
@@ -52,7 +55,7 @@ class UserProfileCard extends StatelessWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Flexible( Flexible(
child: Text( child: Text(
userName ?? 'User Name', userName ?? loc.userNamePlaceholder,
style: theme.textTheme.bodyLarge?.copyWith( style: theme.textTheme.bodyLarge?.copyWith(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -67,4 +70,4 @@ class UserProfileCard extends StatelessWidget {
), ),
); );
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,6 +1,5 @@
{ {
"ru": [ "ru": [
"errorAccountExists",
"companyName", "companyName",
"companynameRequired", "companynameRequired",
"errorSignUp", "errorSignUp",