Added Localizations and ran small fixes
This commit is contained in:
BIN
frontend/.DS_Store
vendored
Normal file
BIN
frontend/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
frontend/pshared/.DS_Store
vendored
Normal file
BIN
frontend/pshared/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -21,8 +21,8 @@ class ResourceContainer<T extends GenericProvider> extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => Consumer<T>(builder: (context, provider, _) {
|
||||
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.isEmpty) return empty ?? Text('Empty data'); //TODO: need to implement localizations too
|
||||
if (provider.error != null) return error ?? Text('Error while loading data. Try again');
|
||||
if (provider.isEmpty) return empty ?? Text('Empty data');
|
||||
return builder(context, provider);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ class Constants {
|
||||
class AppConfig {
|
||||
static const String appName = String.fromEnvironment(
|
||||
'APP_NAME',
|
||||
defaultValue: 'SendiCo',
|
||||
defaultValue: 'sendico',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -433,5 +433,36 @@
|
||||
"companyDescriptionHint": "Describe any of the fields of the Company's business",
|
||||
"optional": "optional",
|
||||
"ownerRole": "Organization Owner",
|
||||
"ownerRoleDescription": "This role is granted to the organization’s creator, providing full administrative privileges"
|
||||
"ownerRoleDescription": "This role is granted to the organization’s creator, providing full administrative privileges",
|
||||
"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"
|
||||
}
|
||||
@@ -425,5 +425,36 @@
|
||||
"noRecipientSelected": "Получатель не выбран",
|
||||
|
||||
"ownerRole": "Владелец организации",
|
||||
"ownerRoleDescription": "Эта роль предоставляется создателю организации и даёт ему полные административные права"
|
||||
"ownerRoleDescription": "Эта роль предоставляется создателю организации и даёт ему полные административные права",
|
||||
"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": "Немецкий"
|
||||
}
|
||||
@@ -1,18 +1,22 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:pshared/models/payment/status.dart';
|
||||
|
||||
import 'package:pweb/models/currency.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
enum WalletTransactionType { topUp, payout }
|
||||
|
||||
extension WalletTransactionTypeX on WalletTransactionType {
|
||||
String label(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
switch (this) {
|
||||
case WalletTransactionType.topUp:
|
||||
return 'Top up';
|
||||
return loc.walletTopUp;
|
||||
case WalletTransactionType.payout:
|
||||
return 'Payout';
|
||||
return loc.payout;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/recipient/recipient.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/header.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';
|
||||
|
||||
|
||||
class RecipientAddressBookPage extends StatelessWidget {
|
||||
class RecipientAddressBookPage extends StatefulWidget {
|
||||
final ValueChanged<Recipient> onRecipientSelected;
|
||||
final VoidCallback onAddRecipient;
|
||||
final ValueChanged<Recipient>? onEditRecipient;
|
||||
@@ -31,32 +31,65 @@ class RecipientAddressBookPage extends StatelessWidget {
|
||||
static const double _bigBox = 30;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final provider = context.watch<RecipientProvider>();
|
||||
_syncSearchField(provider);
|
||||
|
||||
if (provider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator()); //TODO This should be in the provider
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (provider.error != null) {
|
||||
return Center(child: Text('Error: ${provider.error}'));
|
||||
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
RecipientAddressBookHeader(onAddRecipient: onAddRecipient),
|
||||
const SizedBox(height: _smallBox),
|
||||
RecipientAddressBookHeader(onAddRecipient: widget.onAddRecipient),
|
||||
const SizedBox(height: RecipientAddressBookPage._smallBox),
|
||||
RecipientSearchField(
|
||||
controller: TextEditingController(text: provider.query),
|
||||
focusNode: FocusNode(),
|
||||
controller: _searchController,
|
||||
focusNode: _searchFocusNode,
|
||||
onChanged: provider.setQuery,
|
||||
),
|
||||
const SizedBox(height: _bigBox),
|
||||
const SizedBox(height: RecipientAddressBookPage._bigBox),
|
||||
Row(
|
||||
children: [
|
||||
RecipientFilterButton(
|
||||
@@ -86,17 +119,17 @@ class RecipientAddressBookPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: _expandedHeight,
|
||||
height: RecipientAddressBookPage._expandedHeight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(_paddingAll),
|
||||
padding: const EdgeInsets.all(RecipientAddressBookPage._paddingAll),
|
||||
child: RecipientAddressBookList(
|
||||
filteredRecipients: provider.filteredRecipients,
|
||||
onEdit: (recipient) => onEditRecipient?.call(recipient),
|
||||
onSelected: onRecipientSelected,
|
||||
onEdit: (recipient) => widget.onEditRecipient?.call(recipient),
|
||||
onSelected: widget.onRecipientSelected,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class BalanceAddFunds extends StatelessWidget {
|
||||
final VoidCallback onTopUp;
|
||||
@@ -19,6 +21,7 @@ class BalanceAddFunds extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return InkWell(
|
||||
onTap: onTopUp,
|
||||
@@ -37,7 +40,7 @@ class BalanceAddFunds extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: _spacingMedium),
|
||||
Text(
|
||||
'Add funds',
|
||||
loc.addFunds,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:provider/provider.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart';
|
||||
import 'package:pweb/providers/wallets.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class BalanceWidget extends StatelessWidget {
|
||||
const BalanceWidget({super.key});
|
||||
@@ -12,6 +14,7 @@ class BalanceWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletsProvider = context.watch<WalletsProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (walletsProvider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@@ -20,7 +23,7 @@ class BalanceWidget extends StatelessWidget {
|
||||
final wallets = walletsProvider.wallets;
|
||||
|
||||
if (wallets == null || wallets.isEmpty) {
|
||||
return const Center(child: Text('No wallets available'));
|
||||
return Center(child: Text(loc.noWalletsAvailable));
|
||||
}
|
||||
|
||||
return
|
||||
@@ -29,4 +32,4 @@ class BalanceWidget extends StatelessWidget {
|
||||
onWalletChanged: walletsProvider.selectWallet,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class UploadHistorySection extends StatelessWidget {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (provider.error != null) {
|
||||
return Text("Error: ${provider.error}");
|
||||
return Text(l10.notificationError(provider.error ?? l10.noErrorInformation));
|
||||
}
|
||||
final items = provider.data ?? [];
|
||||
|
||||
|
||||
@@ -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/providers/recipient.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class AdressBookPayout extends StatefulWidget {
|
||||
final ValueChanged<Recipient> onSelected;
|
||||
@@ -54,6 +56,7 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final provider = context.watch<RecipientProvider>();
|
||||
|
||||
if (provider.isLoading) {
|
||||
@@ -61,7 +64,7 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
||||
}
|
||||
|
||||
if (provider.error != null) {
|
||||
return Center(child: Text('Error: ${provider.error}'));
|
||||
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
@@ -100,4 +103,4 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class PaymentMethodTypeSelector extends StatelessWidget {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return DropdownButtonFormField<PaymentType>(
|
||||
value: value,
|
||||
initialValue: value,
|
||||
decoration: InputDecoration(labelText: l10n.paymentType),
|
||||
items: PaymentType.values.map((type) {
|
||||
final label = getPaymentTypeLabel(context, type);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/pages/payment_methods/icon.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';
|
||||
|
||||
|
||||
class PaymentMethodTile extends StatelessWidget {
|
||||
const PaymentMethodTile({
|
||||
super.key,
|
||||
@@ -62,7 +64,7 @@ class PaymentMethodTile extends StatelessWidget {
|
||||
Widget _buildMakeMainButton(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return IconButton(
|
||||
tooltip: 'Make main',
|
||||
tooltip: AppLocalizations.of(context)!.makeMain,
|
||||
icon: Icon(
|
||||
method.isMain ? Icons.star : Icons.star_outline,
|
||||
color: method.isMain ? theme.colorScheme.primary : null,
|
||||
@@ -97,4 +99,4 @@ class PaymentMethodTile extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
frontend/pweb/lib/pages/payment_methods/widgets/card.dart
Normal file
67
frontend/pweb/lib/pages/payment_methods/widgets/card.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import 'package:pweb/providers/payment_flow_provider.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
import 'package:pweb/utils/payment/selector_type.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentInfoSection extends StatelessWidget {
|
||||
final AppDimensions dimensions;
|
||||
@@ -27,13 +29,14 @@ class PaymentInfoSection extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final hasRecipient = recipient != null;
|
||||
final availableTypes = hasRecipient
|
||||
? pageSelector.getAvailablePaymentTypes()
|
||||
: {for (final type in PaymentType.values) type: type};
|
||||
|
||||
if (hasRecipient && availableTypes.isEmpty) {
|
||||
return const Text('This recipient has no available payment details.');
|
||||
return Text(loc.recipientNoPaymentDetails);
|
||||
}
|
||||
|
||||
final selectedType = flowProvider.selectedType;
|
||||
@@ -41,7 +44,7 @@ class PaymentInfoSection extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SectionTitle('Payment info'),
|
||||
SectionTitle(loc.paymentInfo),
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
PaymentTypeSelector(
|
||||
availableTypes: availableTypes,
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'package:pweb/providers/payment_flow_provider.dart';
|
||||
import 'package:pweb/providers/payment_methods.dart';
|
||||
import 'package:pweb/providers/recipient.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentPageBody extends StatelessWidget {
|
||||
@@ -45,13 +46,14 @@ class PaymentPageBody extends StatelessWidget {
|
||||
final recipientProvider = context.watch<RecipientProvider>();
|
||||
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||
final recipient = pageSelector.selectedRecipient;
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (methodsProvider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (methodsProvider.error != null) {
|
||||
return Center(child: Text('Error: ${methodsProvider.error}'));
|
||||
return Center(child: Text(loc.notificationError(methodsProvider.error ?? loc.noErrorInformation)));
|
||||
}
|
||||
|
||||
return Align(
|
||||
@@ -74,7 +76,7 @@ class PaymentPageBody extends StatelessWidget {
|
||||
PaymentHeader(),
|
||||
SizedBox(height: dimensions.paddingXXLarge),
|
||||
|
||||
const SectionTitle('Source of funds'),
|
||||
SectionTitle(loc.sourceOfFunds),
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
PaymentMethodSelector(
|
||||
methodsProvider: methodsProvider,
|
||||
|
||||
@@ -3,10 +3,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:pshared/models/recipient/recipient.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/providers/recipient.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class RecipientSection extends StatelessWidget {
|
||||
final Recipient? recipient;
|
||||
@@ -32,6 +36,7 @@ class RecipientSection extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
if (recipient != null) {
|
||||
return SelectedRecipientCard(
|
||||
dimensions: dimensions,
|
||||
@@ -43,7 +48,7 @@ class RecipientSection extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SectionTitle('Recipient'),
|
||||
SectionTitle(loc.recipient),
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
RecipientSearchField(
|
||||
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),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
71
frontend/pweb/lib/pages/payment_methods/widgets/search.dart
Normal file
71
frontend/pweb/lib/pages/payment_methods/widgets/search.dart
Normal 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),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,12 @@ class PaymentConfigController {
|
||||
}
|
||||
|
||||
Future<void> addMethod() async {
|
||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||
await showDialog<PaymentMethodData>(
|
||||
context: context,
|
||||
builder: (_) => const AddPaymentMethodDialog(),
|
||||
);
|
||||
loadMethods();
|
||||
methodsProvider.loadMethods();
|
||||
}
|
||||
|
||||
Future<void> editMethod(PaymentMethod method) async {
|
||||
@@ -33,19 +34,20 @@ class PaymentConfigController {
|
||||
}
|
||||
|
||||
Future<void> deleteMethod(PaymentMethod method) async {
|
||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(l10n.delete),
|
||||
content: Text(l10n.deletePaymentConfirmation),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
onPressed: () => Navigator.pop(dialogContext, false),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
onPressed: () => Navigator.pop(dialogContext, true),
|
||||
child: Text(l10n.delete),
|
||||
),
|
||||
],
|
||||
@@ -53,7 +55,7 @@ class PaymentConfigController {
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
context.read<PaymentMethodsProvider>().deleteMethod(method);
|
||||
methodsProvider.deleteMethod(method);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/models/wallet.dart';
|
||||
|
||||
import 'package:pweb/pages/payout_page/methods/widget.dart';
|
||||
import 'package:pweb/pages/payout_page/wallet/wigets.dart';
|
||||
import 'package:pweb/providers/payment_methods.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentConfigPage extends StatelessWidget {
|
||||
final Function(Wallet) onWalletTap;
|
||||
@@ -16,13 +19,14 @@ class PaymentConfigPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<PaymentMethodsProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (provider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (provider.error != null) {
|
||||
return Center(child: Text('Error: ${provider.error}'));
|
||||
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
||||
}
|
||||
|
||||
return Column(
|
||||
@@ -34,4 +38,4 @@ class PaymentConfigPage extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,28 @@ import 'package:flutter/material.dart';
|
||||
import 'package:pweb/models/wallet.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class SaveWalletButton extends StatelessWidget {
|
||||
final Wallet wallet;
|
||||
final TextEditingController nameController;
|
||||
final TextEditingController balanceController;
|
||||
final VoidCallback onSave; // Changed to VoidCallback
|
||||
final VoidCallback onSave;
|
||||
|
||||
const SaveWalletButton({
|
||||
super.key,
|
||||
required this.wallet,
|
||||
required this.nameController,
|
||||
required this.balanceController,
|
||||
required this.onSave, // Now matches _saveWallet signature
|
||||
required this.onSave,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final dimensions = AppDimensions();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
@@ -29,7 +32,7 @@ class SaveWalletButton extends StatelessWidget {
|
||||
height: dimensions.buttonHeight,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||
onTap: onSave, // Directly use onSave now
|
||||
onTap: onSave,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
@@ -37,7 +40,7 @@ class SaveWalletButton extends StatelessWidget {
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Save',
|
||||
loc.save,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: theme.colorScheme.onSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -49,4 +52,4 @@ class SaveWalletButton extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/providers/page_selector.dart';
|
||||
import 'package:pweb/providers/wallets.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class SendPayoutButton extends StatelessWidget {
|
||||
const SendPayoutButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shadowColor: null,
|
||||
@@ -23,7 +28,7 @@ class SendPayoutButton extends StatelessWidget {
|
||||
pageSelectorProvider.startPaymentFromWallet(wallet);
|
||||
}
|
||||
},
|
||||
child: Text('Send Payout'),
|
||||
child: Text(loc.payoutNavSendPayout),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class TopUpButton extends StatelessWidget{
|
||||
const TopUpButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shadowColor: null,
|
||||
@@ -13,10 +16,10 @@ class TopUpButton extends StatelessWidget{
|
||||
),
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Add functionality')),
|
||||
SnackBar(content: Text(loc.addFunctionality)),
|
||||
);
|
||||
},
|
||||
child: Text('Top Up Balance'),
|
||||
child: Text(loc.topUpBalance),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/providers/wallets.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletEditHeader extends StatefulWidget {
|
||||
const WalletEditHeader({super.key});
|
||||
@@ -32,7 +34,9 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<WalletsProvider>();
|
||||
final wallet = provider.selectedWallet;
|
||||
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final messanger = ScaffoldMessenger.of(context);
|
||||
|
||||
if (wallet == null) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
@@ -74,10 +78,10 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _controller,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
isDense: true,
|
||||
hintText: 'Wallet name',
|
||||
hintText: loc.walletName,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -87,8 +91,8 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
|
||||
onPressed: () async {
|
||||
provider.updateName(wallet.id, _controller.text);
|
||||
await provider.updateWallet(wallet.copyWith(name: _controller.text));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Wallet name saved')),
|
||||
messanger.showSnackBar(
|
||||
SnackBar(content: Text(loc.walletNameSaved)),
|
||||
);
|
||||
setState(() {
|
||||
_isEditing = false;
|
||||
@@ -110,4 +114,4 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import 'package:pweb/pages/payout_page/wallet/history/history.dart';
|
||||
import 'package:pweb/providers/wallets.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletEditPage extends StatelessWidget {
|
||||
final VoidCallback onBack;
|
||||
@@ -18,13 +20,14 @@ class WalletEditPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dimensions = AppDimensions();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Consumer<WalletsProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final wallet = provider.selectedWallet;
|
||||
|
||||
if (wallet == null) {
|
||||
return Center(child: Text('Кошелёк не выбран'));
|
||||
return Center(child: Text(loc.noWalletSelected));
|
||||
}
|
||||
|
||||
return Align(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:flutter/material.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/providers/wallet_transactions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletHistoryFilters extends StatelessWidget {
|
||||
final WalletTransactionsProvider provider;
|
||||
@@ -21,6 +22,7 @@ class WalletHistoryFilters extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
@@ -35,13 +37,13 @@ class WalletHistoryFilters extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Wallet activity',
|
||||
loc.walletActivity,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
if (provider.hasFilters)
|
||||
TextButton(
|
||||
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),
|
||||
label: Text(
|
||||
provider.dateRange == null
|
||||
? 'Select period'
|
||||
? loc.selectPeriod
|
||||
: '${dateToLocalFormat(context, provider.dateRange!.start)} – ${dateToLocalFormat(context, provider.dateRange!.end)}',
|
||||
),
|
||||
),
|
||||
@@ -91,4 +93,4 @@ class WalletHistoryFilters extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/providers/wallet_transactions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletHistory extends StatefulWidget {
|
||||
final Wallet wallet;
|
||||
@@ -64,6 +66,7 @@ class _WalletHistoryState extends State<WalletHistory> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Consumer<WalletTransactionsProvider>(
|
||||
builder: (context, provider, child) {
|
||||
@@ -81,16 +84,16 @@ class _WalletHistoryState extends State<WalletHistory> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Failed to load history',
|
||||
loc.failedToLoadHistory,
|
||||
style: theme.textTheme.titleMedium!
|
||||
.copyWith(color: theme.colorScheme.error),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(provider.error!),
|
||||
Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
|
||||
const SizedBox(height: 8),
|
||||
OutlinedButton(
|
||||
onPressed: _load,
|
||||
child: const Text('Retry'),
|
||||
child: Text(loc.retry),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -113,4 +116,4 @@ class _WalletHistoryState extends State<WalletHistory> {
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:flutter/material.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/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletTransactionsTable extends StatelessWidget {
|
||||
final List<WalletTransaction> transactions;
|
||||
@@ -15,14 +16,15 @@ class WalletTransactionsTable extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (transactions.isEmpty) {
|
||||
return Card(
|
||||
color: theme.colorScheme.onSecondary,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text('No history yet'),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(loc.walletHistoryEmpty),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -38,14 +40,14 @@ class WalletTransactionsTable extends StatelessWidget {
|
||||
child: DataTable(
|
||||
columnSpacing: 18,
|
||||
headingTextStyle: const TextStyle(fontWeight: FontWeight.w600),
|
||||
columns: const [
|
||||
DataColumn(label: Text('Status')),
|
||||
DataColumn(label: Text('Type')),
|
||||
DataColumn(label: Text('Amount')),
|
||||
DataColumn(label: Text('Balance')),
|
||||
DataColumn(label: Text('Counterparty')),
|
||||
DataColumn(label: Text('Date')),
|
||||
DataColumn(label: Text('Comment')),
|
||||
columns: [
|
||||
DataColumn(label: Text(loc.colStatus)),
|
||||
DataColumn(label: Text(loc.colType)),
|
||||
DataColumn(label: Text(loc.colAmount)),
|
||||
DataColumn(label: Text(loc.colBalance)),
|
||||
DataColumn(label: Text(loc.colCounterparty)),
|
||||
DataColumn(label: Text(loc.colDate)),
|
||||
DataColumn(label: Text(loc.colComment)),
|
||||
],
|
||||
rows: List.generate(
|
||||
transactions.length,
|
||||
@@ -85,4 +87,4 @@ class WalletTransactionsTable extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class PayoutDistributionChart extends StatelessWidget {
|
||||
// 1) Aggregate sums
|
||||
final sums = <String, double>{};
|
||||
for (var op in operations) {
|
||||
final name = op.name ?? AppLocalizations.of(context)!.unknown;
|
||||
final name = op.name;
|
||||
sums[name] = (sums[name] ?? 0) + op.amount;
|
||||
}
|
||||
if (sums.isEmpty) {
|
||||
@@ -32,7 +32,7 @@ class PayoutDistributionChart extends StatelessWidget {
|
||||
final palette = [
|
||||
Theme.of(context).colorScheme.primary,
|
||||
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.secondaryContainer,
|
||||
];
|
||||
|
||||
@@ -8,6 +8,8 @@ import 'package:pweb/pages/report/table/filters.dart';
|
||||
import 'package:pweb/pages/report/table/widget.dart';
|
||||
import 'package:pweb/providers/operatioins.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class OperationHistoryPage extends StatefulWidget {
|
||||
const OperationHistoryPage({super.key});
|
||||
@@ -48,6 +50,7 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
return Consumer<OperationProvider>(
|
||||
builder: (context, provider, child) {
|
||||
if (provider.isLoading) {
|
||||
@@ -59,10 +62,10 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Error: ${provider.error}'),
|
||||
Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
|
||||
ElevatedButton(
|
||||
onPressed: () => provider.loadOperations(),
|
||||
child: const Text('Retry'),
|
||||
child: Text(loc.retry),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -108,4 +111,4 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/locale.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
import 'package:pweb/services/amplitude.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class LocalePicker extends StatelessWidget {
|
||||
final String title;
|
||||
@@ -74,13 +75,13 @@ class LocalePicker extends StatelessWidget {
|
||||
String _localizedLocaleName(Locale locale, AppLocalizations loc) {
|
||||
switch (locale.languageCode) {
|
||||
case 'en':
|
||||
return 'English';
|
||||
return loc.englishLanguage;
|
||||
case 'ru':
|
||||
return 'Русский';
|
||||
return loc.russianLanguage;
|
||||
case 'de':
|
||||
return 'Deutsch';
|
||||
return loc.germanLanguage;
|
||||
default:
|
||||
return locale.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class ProfileSettingsPage extends StatelessWidget {
|
||||
errorText: loc.avatarUpdateError,
|
||||
),
|
||||
AccountName(
|
||||
name: 'User Name',
|
||||
name: loc.userNamePlaceholder,
|
||||
title: loc.accountName,
|
||||
hintText: loc.accountNameHint,
|
||||
errorText: loc.accountNameUpdateError,
|
||||
@@ -50,4 +50,4 @@ class ProfileSettingsPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,14 +78,21 @@ class SelectValueTile<T> extends BaseEditTile<T> {
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: displayedOptions.map((o) => RadioListTile<T>(
|
||||
value: o,
|
||||
groupValue: initial,
|
||||
title: Text(labelBuilder(o)),
|
||||
onChanged: isSaving ? null : (v) { if (v != null) onSave(v); },
|
||||
)).toList(),
|
||||
child: RadioGroup<T>(
|
||||
groupValue: initial,
|
||||
onChanged: (value) {
|
||||
if (value != null && !isSaving) {
|
||||
onSave(value);
|
||||
}
|
||||
},
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: displayedOptions.map((o) => RadioListTile<T>(
|
||||
value: o,
|
||||
title: Text(labelBuilder(o)),
|
||||
enabled: !isSaving,
|
||||
)).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
|
||||
18
frontend/pweb/lib/services/auth.dart
Normal file
18
frontend/pweb/lib/services/auth.dart
Normal 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';
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class MockWalletsService implements WalletsService {
|
||||
Future<Wallet> getWallet(String walletId) async {
|
||||
return _wallets.firstWhere(
|
||||
(wallet) => wallet.id == walletId,
|
||||
orElse: () => throw Exception('Wallet not found'),
|
||||
orElse: () => throw const WalletNotFoundException(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,4 +38,11 @@ class MockWalletsService implements WalletsService {
|
||||
|
||||
@override
|
||||
Future<List<Wallet>> deleteWallet() async => [];
|
||||
}
|
||||
}
|
||||
|
||||
class WalletNotFoundException implements Exception {
|
||||
const WalletNotFoundException();
|
||||
|
||||
@override
|
||||
String toString() => 'WalletNotFoundException';
|
||||
}
|
||||
|
||||
@@ -6,16 +6,19 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
import 'package:pshared/provider/account.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class AccountAvatar extends StatelessWidget {
|
||||
const AccountAvatar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
return Consumer<AccountProvider>(
|
||||
builder: (context, provider, _) => UserAccountsDrawerHeader(
|
||||
accountName: Text(provider.account?.name ?? 'John Doe'),
|
||||
accountEmail: Text(provider.account?.login ?? 'john.doe@acme.com'),
|
||||
accountName: Text(provider.account?.name ?? loc.userNamePlaceholder),
|
||||
accountEmail: Text(provider.account?.login ?? loc.usernameHint),
|
||||
currentAccountPicture: CircleAvatar(
|
||||
backgroundImage: (provider.account?.avatarUrl?.isNotEmpty ?? false)
|
||||
? CachedNetworkImageProvider(provider.account!.avatarUrl!)
|
||||
|
||||
@@ -40,7 +40,7 @@ enum PayoutDestination {
|
||||
case PayoutDestination.addrecipient:
|
||||
return loc.addRecipient;
|
||||
case PayoutDestination.editwallet:
|
||||
return 'Edit Wallet';
|
||||
return loc.editWallet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import 'package:pweb/pages/dashboard/dashboard.dart';
|
||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||
import 'package:pweb/widgets/sidebar/sidebar.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PageSelector extends StatelessWidget {
|
||||
const PageSelector({super.key});
|
||||
@@ -22,6 +24,7 @@ class PageSelector extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<PageSelectorProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
Widget content;
|
||||
switch (provider.selected) {
|
||||
@@ -76,7 +79,7 @@ class PageSelector extends StatelessWidget {
|
||||
? WalletEditPage(
|
||||
onBack: provider.goBackFromWalletEdit,
|
||||
)
|
||||
: const Center(child: Text('No wallet selected')); //TODO Localize
|
||||
: Center(child: Text(loc.noWalletSelected));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UserProfileCard extends StatelessWidget {
|
||||
final ThemeData theme;
|
||||
@@ -21,6 +23,7 @@ class UserProfileCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
bool isSelected = selected == PayoutDestination.settings;
|
||||
final backgroundColor = isSelected
|
||||
? theme.colorScheme.primaryContainer
|
||||
@@ -52,7 +55,7 @@ class UserProfileCard extends StatelessWidget {
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
userName ?? 'User Name',
|
||||
userName ?? loc.userNamePlaceholder,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 20,
|
||||
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 |
Reference in New Issue
Block a user