solyanka iz fix for payout page design, ledger wallet now clickable
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
import 'package:pshared/data/dto/payment/operation.dart';
|
import 'package:pshared/data/dto/payment/operation.dart';
|
||||||
|
import 'package:pshared/data/dto/payment/intent/payment.dart';
|
||||||
import 'package:pshared/data/dto/payment/payment_quote.dart';
|
import 'package:pshared/data/dto/payment/payment_quote.dart';
|
||||||
|
|
||||||
part 'payment.g.dart';
|
part 'payment.g.dart';
|
||||||
|
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class PaymentDTO {
|
class PaymentDTO {
|
||||||
final String? paymentRef;
|
final String? paymentRef;
|
||||||
@@ -13,6 +13,7 @@ class PaymentDTO {
|
|||||||
final String? state;
|
final String? state;
|
||||||
final String? failureCode;
|
final String? failureCode;
|
||||||
final String? failureReason;
|
final String? failureReason;
|
||||||
|
final PaymentIntentDTO? intent;
|
||||||
final List<PaymentOperationDTO> operations;
|
final List<PaymentOperationDTO> operations;
|
||||||
final PaymentQuoteDTO? lastQuote;
|
final PaymentQuoteDTO? lastQuote;
|
||||||
final Map<String, String>? metadata;
|
final Map<String, String>? metadata;
|
||||||
@@ -24,6 +25,7 @@ class PaymentDTO {
|
|||||||
this.state,
|
this.state,
|
||||||
this.failureCode,
|
this.failureCode,
|
||||||
this.failureReason,
|
this.failureReason,
|
||||||
|
this.intent,
|
||||||
this.operations = const <PaymentOperationDTO>[],
|
this.operations = const <PaymentOperationDTO>[],
|
||||||
this.lastQuote,
|
this.lastQuote,
|
||||||
this.metadata,
|
this.metadata,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:pshared/data/dto/payment/payment.dart';
|
import 'package:pshared/data/dto/payment/payment.dart';
|
||||||
|
import 'package:pshared/data/mapper/payment/intent/payment.dart';
|
||||||
import 'package:pshared/data/mapper/payment/operation.dart';
|
import 'package:pshared/data/mapper/payment/operation.dart';
|
||||||
import 'package:pshared/data/mapper/payment/quote.dart';
|
import 'package:pshared/data/mapper/payment/quote.dart';
|
||||||
import 'package:pshared/models/payment/payment.dart';
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
import 'package:pshared/models/payment/state.dart';
|
import 'package:pshared/models/payment/state.dart';
|
||||||
|
|
||||||
|
|
||||||
extension PaymentDTOMapper on PaymentDTO {
|
extension PaymentDTOMapper on PaymentDTO {
|
||||||
Payment toDomain() => Payment(
|
Payment toDomain() => Payment(
|
||||||
paymentRef: paymentRef,
|
paymentRef: paymentRef,
|
||||||
@@ -13,6 +13,7 @@ extension PaymentDTOMapper on PaymentDTO {
|
|||||||
orchestrationState: paymentOrchestrationStateFromValue(state),
|
orchestrationState: paymentOrchestrationStateFromValue(state),
|
||||||
failureCode: failureCode,
|
failureCode: failureCode,
|
||||||
failureReason: failureReason,
|
failureReason: failureReason,
|
||||||
|
intent: intent?.toDomain(),
|
||||||
operations: operations.map((item) => item.toDomain()).toList(),
|
operations: operations.map((item) => item.toDomain()).toList(),
|
||||||
lastQuote: lastQuote?.toDomain(),
|
lastQuote: lastQuote?.toDomain(),
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
@@ -27,6 +28,7 @@ extension PaymentMapper on Payment {
|
|||||||
state: state ?? paymentOrchestrationStateToValue(orchestrationState),
|
state: state ?? paymentOrchestrationStateToValue(orchestrationState),
|
||||||
failureCode: failureCode,
|
failureCode: failureCode,
|
||||||
failureReason: failureReason,
|
failureReason: failureReason,
|
||||||
|
intent: intent?.toDTO(),
|
||||||
operations: operations.map((item) => item.toDTO()).toList(),
|
operations: operations.map((item) => item.toDTO()).toList(),
|
||||||
lastQuote: lastQuote?.toDTO(),
|
lastQuote: lastQuote?.toDTO(),
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:pshared/models/payment/execution_operation.dart';
|
import 'package:pshared/models/payment/execution_operation.dart';
|
||||||
|
import 'package:pshared/models/payment/intent.dart';
|
||||||
import 'package:pshared/models/payment/quote/quote.dart';
|
import 'package:pshared/models/payment/quote/quote.dart';
|
||||||
import 'package:pshared/models/payment/state.dart';
|
import 'package:pshared/models/payment/state.dart';
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ class Payment {
|
|||||||
final PaymentOrchestrationState orchestrationState;
|
final PaymentOrchestrationState orchestrationState;
|
||||||
final String? failureCode;
|
final String? failureCode;
|
||||||
final String? failureReason;
|
final String? failureReason;
|
||||||
|
final PaymentIntent? intent;
|
||||||
final List<PaymentExecutionOperation> operations;
|
final List<PaymentExecutionOperation> operations;
|
||||||
final PaymentQuote? lastQuote;
|
final PaymentQuote? lastQuote;
|
||||||
final Map<String, String>? metadata;
|
final Map<String, String>? metadata;
|
||||||
@@ -21,6 +23,7 @@ class Payment {
|
|||||||
required this.orchestrationState,
|
required this.orchestrationState,
|
||||||
required this.failureCode,
|
required this.failureCode,
|
||||||
required this.failureReason,
|
required this.failureReason,
|
||||||
|
this.intent,
|
||||||
required this.operations,
|
required this.operations,
|
||||||
required this.lastQuote,
|
required this.lastQuote,
|
||||||
required this.metadata,
|
required this.metadata,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||||
import 'package:pshared/controllers/payment/source.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
import 'package:pshared/models/payment/type.dart';
|
import 'package:pshared/models/payment/type.dart';
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
import 'package:pshared/provider/ledger.dart';
|
import 'package:pshared/provider/ledger.dart';
|
||||||
@@ -228,6 +229,7 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
_startPayment(context, recipient: null, paymentType: type),
|
_startPayment(context, recipient: null, paymentType: type),
|
||||||
onTopUp: (wallet) => _openWalletTopUp(context, wallet),
|
onTopUp: (wallet) => _openWalletTopUp(context, wallet),
|
||||||
onWalletTap: (wallet) => _openWalletEdit(context, wallet),
|
onWalletTap: (wallet) => _openWalletEdit(context, wallet),
|
||||||
|
onLedgerTap: (account) => _openLedgerEdit(context, account),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -340,17 +342,9 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: PayoutRoutes.editWallet,
|
name: PayoutRoutes.editWallet,
|
||||||
path: PayoutRoutes.editWalletPath,
|
path: PayoutRoutes.editWalletPath,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) => NoTransitionPage(
|
||||||
final walletsProvider = context.read<WalletsController>();
|
child: WalletEditPage(onBack: () => _popOrGo(context)),
|
||||||
final wallet = walletsProvider.selectedWallet;
|
),
|
||||||
final loc = AppLocalizations.of(context)!;
|
|
||||||
|
|
||||||
return NoTransitionPage(
|
|
||||||
child: wallet != null
|
|
||||||
? WalletEditPage(onBack: () => _popOrGo(context))
|
|
||||||
: Center(child: Text(loc.noWalletSelected)),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: PayoutRoutes.walletTopUp,
|
name: PayoutRoutes.walletTopUp,
|
||||||
@@ -389,10 +383,18 @@ void _openEditRecipient(BuildContext context, {required Recipient recipient}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _openWalletEdit(BuildContext context, Wallet wallet) {
|
void _openWalletEdit(BuildContext context, Wallet wallet) {
|
||||||
|
context.read<PaymentSourceController>().selectWallet(wallet);
|
||||||
context.read<WalletsController>().selectWallet(wallet);
|
context.read<WalletsController>().selectWallet(wallet);
|
||||||
context.pushToEditWallet();
|
context.pushToEditWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _openLedgerEdit(BuildContext context, LedgerAccount account) {
|
||||||
|
context.read<PaymentSourceController>().selectLedgerByRef(
|
||||||
|
account.ledgerAccountRef,
|
||||||
|
);
|
||||||
|
context.pushToEditWallet();
|
||||||
|
}
|
||||||
|
|
||||||
void _openWalletTopUp(BuildContext context, Wallet wallet) {
|
void _openWalletTopUp(BuildContext context, Wallet wallet) {
|
||||||
context.read<WalletsController>().selectWallet(wallet);
|
context.read<WalletsController>().selectWallet(wallet);
|
||||||
context.pushToWalletTopUp();
|
context.pushToWalletTopUp();
|
||||||
|
|||||||
@@ -3,18 +3,24 @@ import 'dart:collection';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/payment/operation.dart';
|
import 'package:pshared/models/payment/operation.dart';
|
||||||
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
|
import 'package:pshared/models/payment/source_type.dart';
|
||||||
import 'package:pshared/models/payment/status.dart';
|
import 'package:pshared/models/payment/status.dart';
|
||||||
import 'package:pshared/provider/payment/payments.dart';
|
import 'package:pshared/provider/payment/payments.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/state/load_more_state.dart';
|
import 'package:pweb/models/state/load_more_state.dart';
|
||||||
import 'package:pweb/utils/report/operations/operations.dart';
|
import 'package:pweb/utils/report/operations/operations.dart';
|
||||||
import 'package:pweb/utils/report/payment_mapper.dart';
|
import 'package:pweb/utils/report/payment_mapper.dart';
|
||||||
|
import 'package:pweb/utils/report/source_filter.dart';
|
||||||
|
|
||||||
|
|
||||||
class ReportOperationsController extends ChangeNotifier {
|
class ReportOperationsController extends ChangeNotifier {
|
||||||
PaymentsProvider? _payments;
|
PaymentsProvider? _payments;
|
||||||
|
PaymentSourceType? _sourceType;
|
||||||
|
Set<String> _sourceRefs = const <String>{};
|
||||||
DateTimeRange? _selectedRange;
|
DateTimeRange? _selectedRange;
|
||||||
final Set<OperationStatus> _selectedStatuses = {};
|
final Set<OperationStatus> _selectedStatuses = {};
|
||||||
|
List<Payment> _paymentItems = const [];
|
||||||
List<OperationItem> _operations = const [];
|
List<OperationItem> _operations = const [];
|
||||||
List<OperationItem> _filtered = const [];
|
List<OperationItem> _filtered = const [];
|
||||||
|
|
||||||
@@ -36,10 +42,20 @@ class ReportOperationsController extends ChangeNotifier {
|
|||||||
return LoadMoreState.hidden;
|
return LoadMoreState.hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(PaymentsProvider provider) {
|
void update(
|
||||||
|
PaymentsProvider provider, {
|
||||||
|
PaymentSourceType? sourceType,
|
||||||
|
String? sourceRef,
|
||||||
|
List<String>? sourceRefs,
|
||||||
|
}) {
|
||||||
if (!identical(_payments, provider)) {
|
if (!identical(_payments, provider)) {
|
||||||
_payments = provider;
|
_payments = provider;
|
||||||
}
|
}
|
||||||
|
_sourceType = sourceType;
|
||||||
|
final effectiveSourceRefs =
|
||||||
|
sourceRefs ??
|
||||||
|
(sourceRef == null ? const <String>[] : <String>[sourceRef]);
|
||||||
|
_sourceRefs = _normalizeRefs(effectiveSourceRefs);
|
||||||
_rebuildOperations();
|
_rebuildOperations();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,13 +90,16 @@ class ReportOperationsController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _rebuildOperations() {
|
void _rebuildOperations() {
|
||||||
final items = _payments?.payments ?? const [];
|
_paymentItems = _payments?.payments ?? const [];
|
||||||
_operations = items.map(mapPaymentToOperation).toList();
|
_operations = _paymentItems
|
||||||
|
.where(_matchesCurrentSource)
|
||||||
|
.map(mapPaymentToOperation)
|
||||||
|
.toList();
|
||||||
_rebuildFiltered(notify: true);
|
_rebuildFiltered(notify: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _rebuildFiltered({bool notify = true}) {
|
void _rebuildFiltered({bool notify = true}) {
|
||||||
_filtered = _applyFilters(_operations);
|
_filtered = _applyFilters(sortOperations(_operations));
|
||||||
if (notify) {
|
if (notify) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -88,13 +107,14 @@ class ReportOperationsController extends ChangeNotifier {
|
|||||||
|
|
||||||
List<OperationItem> _applyFilters(List<OperationItem> operations) {
|
List<OperationItem> _applyFilters(List<OperationItem> operations) {
|
||||||
if (_selectedRange == null && _selectedStatuses.isEmpty) {
|
if (_selectedRange == null && _selectedStatuses.isEmpty) {
|
||||||
return sortOperations(operations);
|
return operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
final filtered = operations.where((op) {
|
final filtered = operations.where((op) {
|
||||||
final statusMatch =
|
final statusMatch =
|
||||||
_selectedStatuses.isEmpty || _selectedStatuses.contains(op.status);
|
_selectedStatuses.isEmpty || _selectedStatuses.contains(op.status);
|
||||||
final dateMatch = _selectedRange == null ||
|
final dateMatch =
|
||||||
|
_selectedRange == null ||
|
||||||
isUnknownDate(op.date) ||
|
isUnknownDate(op.date) ||
|
||||||
(op.date.isAfter(
|
(op.date.isAfter(
|
||||||
_selectedRange!.start.subtract(const Duration(seconds: 1)),
|
_selectedRange!.start.subtract(const Duration(seconds: 1)),
|
||||||
@@ -105,7 +125,30 @@ class ReportOperationsController extends ChangeNotifier {
|
|||||||
return statusMatch && dateMatch;
|
return statusMatch && dateMatch;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return sortOperations(filtered);
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _matchesCurrentSource(Payment payment) {
|
||||||
|
final sourceType = _sourceType;
|
||||||
|
if (sourceType == null || _sourceRefs.isEmpty) return true;
|
||||||
|
for (final sourceRef in _sourceRefs) {
|
||||||
|
if (paymentMatchesSource(
|
||||||
|
payment,
|
||||||
|
sourceType: sourceType,
|
||||||
|
sourceRef: sourceRef,
|
||||||
|
)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> _normalizeRefs(List<String> refs) {
|
||||||
|
final normalized = refs
|
||||||
|
.map((value) => value.trim())
|
||||||
|
.where((value) => value.isNotEmpty)
|
||||||
|
.toSet();
|
||||||
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isSameRange(DateTimeRange? left, DateTimeRange? right) {
|
bool _isSameRange(DateTimeRange? left, DateTimeRange? right) {
|
||||||
|
|||||||
@@ -71,16 +71,24 @@ class WalletTransactionsController extends ChangeNotifier {
|
|||||||
|
|
||||||
void _rebuildFiltered({bool notify = true}) {
|
void _rebuildFiltered({bool notify = true}) {
|
||||||
final source = _provider?.transactions ?? const <WalletTransaction>[];
|
final source = _provider?.transactions ?? const <WalletTransaction>[];
|
||||||
|
final activeWalletId = _provider?.walletId;
|
||||||
_filteredTransactions = source.where((tx) {
|
_filteredTransactions = source.where((tx) {
|
||||||
|
final walletMatch =
|
||||||
|
activeWalletId == null || tx.walletId == activeWalletId;
|
||||||
final statusMatch =
|
final statusMatch =
|
||||||
_selectedStatuses.isEmpty || _selectedStatuses.contains(tx.status);
|
_selectedStatuses.isEmpty || _selectedStatuses.contains(tx.status);
|
||||||
final typeMatch =
|
final typeMatch =
|
||||||
_selectedTypes.isEmpty || _selectedTypes.contains(tx.type);
|
_selectedTypes.isEmpty || _selectedTypes.contains(tx.type);
|
||||||
final dateMatch = _dateRange == null ||
|
final dateMatch =
|
||||||
(tx.date.isAfter(_dateRange!.start.subtract(const Duration(seconds: 1))) &&
|
_dateRange == null ||
|
||||||
tx.date.isBefore(_dateRange!.end.add(const Duration(seconds: 1))));
|
(tx.date.isAfter(
|
||||||
|
_dateRange!.start.subtract(const Duration(seconds: 1)),
|
||||||
|
) &&
|
||||||
|
tx.date.isBefore(
|
||||||
|
_dateRange!.end.add(const Duration(seconds: 1)),
|
||||||
|
));
|
||||||
|
|
||||||
return statusMatch && typeMatch && dateMatch;
|
return walletMatch && statusMatch && typeMatch && dateMatch;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
if (notify) notifyListeners();
|
if (notify) notifyListeners();
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ import 'package:flutter/foundation.dart';
|
|||||||
|
|
||||||
import 'package:pshared/models/payment/execution_operation.dart';
|
import 'package:pshared/models/payment/execution_operation.dart';
|
||||||
import 'package:pshared/models/payment/payment.dart';
|
import 'package:pshared/models/payment/payment.dart';
|
||||||
import 'package:pshared/models/payment/status.dart';
|
|
||||||
import 'package:pshared/provider/payment/payments.dart';
|
import 'package:pshared/provider/payment/payments.dart';
|
||||||
import 'package:pweb/models/documents/operation.dart';
|
import 'package:pweb/models/documents/operation.dart';
|
||||||
|
|
||||||
import 'package:pweb/utils/report/operations/document_rule.dart';
|
import 'package:pweb/utils/report/operations/document_rule.dart';
|
||||||
import 'package:pweb/utils/report/payment_mapper.dart';
|
|
||||||
|
|
||||||
class PaymentDetailsController extends ChangeNotifier {
|
class PaymentDetailsController extends ChangeNotifier {
|
||||||
PaymentDetailsController({required String paymentId})
|
PaymentDetailsController({required String paymentId})
|
||||||
@@ -22,25 +20,6 @@ class PaymentDetailsController extends ChangeNotifier {
|
|||||||
bool get isLoading => _payments?.isLoading ?? false;
|
bool get isLoading => _payments?.isLoading ?? false;
|
||||||
Exception? get error => _payments?.error;
|
Exception? get error => _payments?.error;
|
||||||
|
|
||||||
bool get canDownload {
|
|
||||||
final current = _payment;
|
|
||||||
if (current == null) return false;
|
|
||||||
if (statusFromPayment(current) != OperationStatus.success) return false;
|
|
||||||
return primaryOperationDocumentRequest != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
OperationDocumentRequestModel? get primaryOperationDocumentRequest {
|
|
||||||
final current = _payment;
|
|
||||||
if (current == null) return null;
|
|
||||||
for (final operation in current.operations) {
|
|
||||||
final request = operationDocumentRequest(operation);
|
|
||||||
if (request != null) {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
OperationDocumentRequestModel? operationDocumentRequest(
|
OperationDocumentRequestModel? operationDocumentRequest(
|
||||||
PaymentExecutionOperation operation,
|
PaymentExecutionOperation operation,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/add/card.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/add/card.dart';
|
||||||
@@ -10,13 +11,13 @@ import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
|||||||
import 'package:pweb/pages/dashboard/buttons/balance/indicator.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/indicator.dart';
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/ledger.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/ledger.dart';
|
||||||
|
|
||||||
|
|
||||||
class BalanceCarousel extends StatefulWidget {
|
class BalanceCarousel extends StatefulWidget {
|
||||||
final List<BalanceItem> items;
|
final List<BalanceItem> items;
|
||||||
final int currentIndex;
|
final int currentIndex;
|
||||||
final ValueChanged<int> onIndexChanged;
|
final ValueChanged<int> onIndexChanged;
|
||||||
final ValueChanged<Wallet> onTopUp;
|
final ValueChanged<Wallet> onTopUp;
|
||||||
final ValueChanged<Wallet> onWalletTap;
|
final ValueChanged<Wallet> onWalletTap;
|
||||||
|
final ValueChanged<LedgerAccount> onLedgerTap;
|
||||||
|
|
||||||
const BalanceCarousel({
|
const BalanceCarousel({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -25,6 +26,7 @@ class BalanceCarousel extends StatefulWidget {
|
|||||||
required this.onIndexChanged,
|
required this.onIndexChanged,
|
||||||
required this.onTopUp,
|
required this.onTopUp,
|
||||||
required this.onWalletTap,
|
required this.onWalletTap,
|
||||||
|
required this.onLedgerTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -105,7 +107,10 @@ class _BalanceCarouselState extends State<BalanceCarousel> {
|
|||||||
onTopUp: () => widget.onTopUp(item.wallet!),
|
onTopUp: () => widget.onTopUp(item.wallet!),
|
||||||
onTap: () => widget.onWalletTap(item.wallet!),
|
onTap: () => widget.onWalletTap(item.wallet!),
|
||||||
),
|
),
|
||||||
BalanceItemType.ledger => LedgerAccountCard(account: item.account!),
|
BalanceItemType.ledger => LedgerAccountCard(
|
||||||
|
account: item.account!,
|
||||||
|
onTap: () => widget.onLedgerTap(item.account!),
|
||||||
|
),
|
||||||
BalanceItemType.addAction => const AddBalanceCard(),
|
BalanceItemType.addAction => const AddBalanceCard(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,19 +128,16 @@ class _BalanceCarouselState extends State<BalanceCarousel> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: safeIndex > 0
|
onPressed: safeIndex > 0 ? () => _goToPage(safeIndex - 1) : null,
|
||||||
? () => _goToPage(safeIndex - 1)
|
|
||||||
: null,
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
icon: const Icon(Icons.arrow_back),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
CarouselIndicator(
|
CarouselIndicator(itemCount: widget.items.length, index: safeIndex),
|
||||||
itemCount: widget.items.length,
|
|
||||||
index: safeIndex,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: safeIndex < widget.items.length - 1 ? () => _goToPage(safeIndex + 1) : null,
|
onPressed: safeIndex < widget.items.length - 1
|
||||||
|
? () => _goToPage(safeIndex + 1)
|
||||||
|
: null,
|
||||||
icon: const Icon(Icons.arrow_forward),
|
icon: const Icon(Icons.arrow_forward),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
|||||||
|
|
||||||
class LedgerAccountCard extends StatelessWidget {
|
class LedgerAccountCard extends StatelessWidget {
|
||||||
final LedgerAccount account;
|
final LedgerAccount account;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
const LedgerAccountCard({super.key, required this.account});
|
const LedgerAccountCard({super.key, required this.account, this.onTap});
|
||||||
|
|
||||||
String _formatBalance() {
|
String _formatBalance() {
|
||||||
final money = account.balance?.balance;
|
final money = account.balance?.balance;
|
||||||
@@ -73,50 +74,58 @@ class LedgerAccountCard extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: InkWell(
|
||||||
padding: WalletCardConfig.contentPadding,
|
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
||||||
child: Column(
|
onTap: onTap,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Padding(
|
||||||
children: [
|
padding: WalletCardConfig.contentPadding,
|
||||||
BalanceHeader(title: title, subtitle: subtitle, badge: badge),
|
child: Column(
|
||||||
Row(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Consumer<LedgerBalanceMaskController>(
|
BalanceHeader(title: title, subtitle: subtitle, badge: badge),
|
||||||
builder: (context, controller, _) {
|
Row(
|
||||||
final isMasked = controller.isBalanceMasked(
|
children: [
|
||||||
account.ledgerAccountRef,
|
Consumer<LedgerBalanceMaskController>(
|
||||||
);
|
builder: (context, controller, _) {
|
||||||
return Row(
|
final isMasked = controller.isBalanceMasked(
|
||||||
children: [
|
account.ledgerAccountRef,
|
||||||
Text(
|
);
|
||||||
isMasked ? _formatMaskedBalance() : _formatBalance(),
|
return Row(
|
||||||
style: textTheme.headlineSmall?.copyWith(
|
children: [
|
||||||
fontWeight: FontWeight.bold,
|
Text(
|
||||||
color: colorScheme.onSurface,
|
isMasked
|
||||||
|
? _formatMaskedBalance()
|
||||||
|
: _formatBalance(),
|
||||||
|
style: textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: colorScheme.onSurface,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 12),
|
||||||
const SizedBox(width: 12),
|
GestureDetector(
|
||||||
GestureDetector(
|
onTap: () => controller.toggleBalanceMask(
|
||||||
onTap: () => controller.toggleBalanceMask(
|
account.ledgerAccountRef,
|
||||||
account.ledgerAccountRef,
|
),
|
||||||
|
child: Icon(
|
||||||
|
isMasked
|
||||||
|
? Icons.visibility_off
|
||||||
|
: Icons.visibility,
|
||||||
|
size: 24,
|
||||||
|
color: colorScheme.onSurface,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Icon(
|
],
|
||||||
isMasked ? Icons.visibility_off : Icons.visibility,
|
);
|
||||||
size: 24,
|
},
|
||||||
color: colorScheme.onSurface,
|
),
|
||||||
),
|
const SizedBox(width: 12),
|
||||||
),
|
LedgerBalanceRefreshButton(
|
||||||
],
|
ledgerAccountRef: account.ledgerAccountRef,
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
],
|
||||||
LedgerBalanceRefreshButton(
|
),
|
||||||
ledgerAccountRef: account.ledgerAccountRef,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||||
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
import 'package:pshared/provider/ledger.dart';
|
import 'package:pshared/provider/ledger.dart';
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
|
|
||||||
@@ -11,14 +12,17 @@ import 'package:pweb/pages/dashboard/buttons/balance/controller.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class BalanceWidget extends StatelessWidget {
|
class BalanceWidget extends StatelessWidget {
|
||||||
final ValueChanged<Wallet> onTopUp;
|
final ValueChanged<Wallet> onTopUp;
|
||||||
final ValueChanged<Wallet> onWalletTap;
|
final ValueChanged<Wallet> onWalletTap;
|
||||||
|
final ValueChanged<LedgerAccount> onLedgerTap;
|
||||||
|
|
||||||
const BalanceWidget({
|
const BalanceWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onTopUp,
|
required this.onTopUp,
|
||||||
required this.onWalletTap,
|
required this.onWalletTap,
|
||||||
|
required this.onLedgerTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -46,6 +50,7 @@ class BalanceWidget extends StatelessWidget {
|
|||||||
onIndexChanged: carousel.onPageChanged,
|
onIndexChanged: carousel.onPageChanged,
|
||||||
onTopUp: onTopUp,
|
onTopUp: onTopUp,
|
||||||
onWalletTap: onWalletTap,
|
onWalletTap: onWalletTap,
|
||||||
|
onLedgerTap: onLedgerTap,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (wallets.isEmpty && accounts.isEmpty) {
|
if (wallets.isEmpty && accounts.isEmpty) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
import 'package:pshared/models/payment/type.dart';
|
import 'package:pshared/models/payment/type.dart';
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
@@ -15,6 +16,7 @@ import 'package:pweb/pages/loader.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class AppSpacing {
|
class AppSpacing {
|
||||||
static const double small = 10;
|
static const double small = 10;
|
||||||
static const double medium = 16;
|
static const double medium = 16;
|
||||||
@@ -26,6 +28,7 @@ class DashboardPage extends StatefulWidget {
|
|||||||
final void Function(PaymentType type) onGoToPaymentWithoutRecipient;
|
final void Function(PaymentType type) onGoToPaymentWithoutRecipient;
|
||||||
final ValueChanged<Wallet> onTopUp;
|
final ValueChanged<Wallet> onTopUp;
|
||||||
final ValueChanged<Wallet> onWalletTap;
|
final ValueChanged<Wallet> onWalletTap;
|
||||||
|
final ValueChanged<LedgerAccount> onLedgerTap;
|
||||||
|
|
||||||
const DashboardPage({
|
const DashboardPage({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -33,6 +36,7 @@ class DashboardPage extends StatefulWidget {
|
|||||||
required this.onGoToPaymentWithoutRecipient,
|
required this.onGoToPaymentWithoutRecipient,
|
||||||
required this.onTopUp,
|
required this.onTopUp,
|
||||||
required this.onWalletTap,
|
required this.onWalletTap,
|
||||||
|
required this.onLedgerTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -87,6 +91,7 @@ class _DashboardPageState extends State<DashboardPage> {
|
|||||||
child: BalanceWidget(
|
child: BalanceWidget(
|
||||||
onTopUp: widget.onTopUp,
|
onTopUp: widget.onTopUp,
|
||||||
onWalletTap: widget.onWalletTap,
|
onWalletTap: widget.onWalletTap,
|
||||||
|
onLedgerTap: widget.onLedgerTap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: AppSpacing.small),
|
const SizedBox(height: AppSpacing.small),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:pweb/pages/dashboard/payouts/amount/mode/selector.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentAmountField extends StatelessWidget {
|
class PaymentAmountField extends StatelessWidget {
|
||||||
const PaymentAmountField();
|
const PaymentAmountField();
|
||||||
|
|
||||||
@@ -37,10 +38,6 @@ class PaymentAmountField extends StatelessWidget {
|
|||||||
labelText: loc.amount,
|
labelText: loc.amount,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
prefixText: symbol == null ? null : '$symbol\u00A0',
|
prefixText: symbol == null ? null : '$symbol\u00A0',
|
||||||
helperText: switch (ui.mode) {
|
|
||||||
PaymentAmountMode.debit => loc.debitAmountLabel,
|
|
||||||
PaymentAmountMode.settlement => loc.expectedSettlementAmountLabel,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
onChanged: ui.handleChanged,
|
onChanged: ui.handleChanged,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class PaymentFormWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
detailsHeader,
|
detailsHeader,
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
@@ -114,7 +114,7 @@ class PaymentFormWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: _smallSpacing),
|
const SizedBox(height: _smallSpacing),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
|
|||||||
@@ -26,15 +26,18 @@ class QuoteStatusCard extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
static const double _cardRadius = 12;
|
static const double _cardRadius = 12;
|
||||||
static const double _cardSpacing = 12;
|
static const double _cardSpacing = 8;
|
||||||
static const double _iconSize = 18;
|
static const double _iconSize = 18;
|
||||||
|
|
||||||
@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)!;
|
||||||
final foregroundColor = _resolveForegroundColor(theme, statusType);
|
final foregroundColor = _resolveForegroundColor(theme, statusType);
|
||||||
final elementColor = _resolveElementColor(theme, statusType);
|
final elementColor = _resolveElementColor(theme, statusType);
|
||||||
final statusStyle = theme.textTheme.bodyMedium?.copyWith(color: elementColor);
|
final statusStyle = theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: elementColor,
|
||||||
|
);
|
||||||
final helperStyle = theme.textTheme.bodySmall?.copyWith(
|
final helperStyle = theme.textTheme.bodySmall?.copyWith(
|
||||||
color: foregroundColor.withValues(alpha: 0.8),
|
color: foregroundColor.withValues(alpha: 0.8),
|
||||||
);
|
);
|
||||||
@@ -44,12 +47,10 @@ class QuoteStatusCard extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _resolveCardColor(theme, statusType),
|
color: _resolveCardColor(theme, statusType),
|
||||||
borderRadius: BorderRadius.circular(_cardRadius),
|
borderRadius: BorderRadius.circular(_cardRadius),
|
||||||
border: Border.all(
|
border: Border.all(color: elementColor.withValues(alpha: 0.5)),
|
||||||
color: elementColor.withValues(alpha: 0.5),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 2),
|
padding: const EdgeInsets.only(top: 2),
|
||||||
@@ -59,7 +60,9 @@ class QuoteStatusCard extends StatelessWidget {
|
|||||||
height: _iconSize,
|
height: _iconSize,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(foregroundColor),
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
foregroundColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
@@ -81,19 +84,15 @@ class QuoteStatusCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (canRefresh)
|
if (canRefresh) ...[
|
||||||
Padding(
|
const SizedBox(width: _cardSpacing),
|
||||||
padding: const EdgeInsets.only(left: _cardSpacing),
|
IconButton(
|
||||||
child: showPrimaryRefresh
|
onPressed: onRefresh,
|
||||||
? ElevatedButton(
|
tooltip: loc.quoteRefresh,
|
||||||
onPressed: canRefresh ? onRefresh : null,
|
icon: const Icon(Icons.refresh),
|
||||||
child: Text(AppLocalizations.of(context)!.quoteRefresh),
|
color: showPrimaryRefresh ? foregroundColor : elementColor,
|
||||||
)
|
|
||||||
: TextButton(
|
|
||||||
onPressed: canRefresh ? onRefresh : null,
|
|
||||||
child: Text(AppLocalizations.of(context)!.quoteRefresh),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/payout_page/wallet/edit/buttons/send.dart';
|
import 'package:pweb/pages/payout_page/wallet/edit/buttons/send.dart';
|
||||||
import 'package:pweb/pages/payout_page/wallet/edit/buttons/top_up.dart';
|
import 'package:pweb/pages/payout_page/wallet/edit/buttons/top_up.dart';
|
||||||
import 'package:pshared/provider/payment/wallets.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class ButtonsWalletWidget extends StatelessWidget {
|
class ButtonsWalletWidget extends StatelessWidget {
|
||||||
@@ -12,25 +13,20 @@ class ButtonsWalletWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<WalletsProvider>();
|
final source = context.watch<PaymentSourceController>();
|
||||||
|
if (!source.hasSources) return const SizedBox.shrink();
|
||||||
if (provider.wallets.isEmpty) return const SizedBox.shrink();
|
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(child: SendPayoutButton()),
|
||||||
child: SendPayoutButton(),
|
VerticalDivider(
|
||||||
),
|
color: Theme.of(context).colorScheme.primary,
|
||||||
VerticalDivider(
|
thickness: 1,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
width: 10,
|
||||||
thickness: 1,
|
),
|
||||||
width: 10,
|
Expanded(child: TopUpButton()),
|
||||||
),
|
],
|
||||||
Expanded(
|
|
||||||
child: TopUpButton(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import 'package:go_router/go_router.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
|
import 'package:pshared/models/payment/source_type.dart';
|
||||||
import 'package:pshared/models/payment/type.dart';
|
import 'package:pshared/models/payment/type.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/payout_routes.dart';
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
@@ -18,24 +19,27 @@ class SendPayoutButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
final source = context.watch<PaymentSourceController>();
|
||||||
|
|
||||||
|
final sourceType = source.selectedType;
|
||||||
|
final paymentType = switch (sourceType) {
|
||||||
|
PaymentSourceType.wallet => PaymentType.wallet,
|
||||||
|
PaymentSourceType.ledger => PaymentType.ledger,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(shadowColor: null, elevation: 0),
|
||||||
shadowColor: null,
|
onPressed: paymentType == null
|
||||||
elevation: 0,
|
? null
|
||||||
),
|
: () {
|
||||||
onPressed: () {
|
context.pushNamed(
|
||||||
final wallets = context.read<WalletsController>();
|
PayoutRoutes.payment,
|
||||||
final wallet = wallets.selectedWallet;
|
queryParameters: PayoutRoutes.buildQueryParameters(
|
||||||
|
paymentType: paymentType,
|
||||||
if (wallet != null) {
|
),
|
||||||
context.pushNamed(
|
);
|
||||||
PayoutRoutes.payment,
|
},
|
||||||
queryParameters: PayoutRoutes.buildQueryParameters(
|
|
||||||
paymentType: PaymentType.wallet,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(loc.payoutNavSendPayout),
|
child: Text(loc.payoutNavSendPayout),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,34 +2,26 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
|
import 'package:pshared/models/payment/source_type.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/payout_routes.dart';
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.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)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
final source = context.watch<PaymentSourceController>();
|
||||||
|
final canTopUp = source.selectedType == PaymentSourceType.wallet;
|
||||||
|
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(shadowColor: null, elevation: 0),
|
||||||
shadowColor: null,
|
onPressed: canTopUp ? () => context.pushToWalletTopUp() : null,
|
||||||
elevation: 0,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
final wallet = context.read<WalletsController>().selectedWallet;
|
|
||||||
if (wallet == null) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text(loc.noWalletSelected)),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
context.pushToWalletTopUp();
|
|
||||||
},
|
|
||||||
child: Text(loc.topUpBalance),
|
child: Text(loc.topUpBalance),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,30 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
|
||||||
import 'package:pweb/widgets/refresh_balance/wallet.dart';
|
|
||||||
|
|
||||||
|
import 'package:pweb/pages/payout_page/wallet/edit/fields/ledger/section.dart';
|
||||||
|
import 'package:pweb/pages/payout_page/wallet/edit/fields/wallet/wallet_section.dart';
|
||||||
|
|
||||||
class WalletEditFields extends StatelessWidget {
|
class WalletEditFields extends StatelessWidget {
|
||||||
const WalletEditFields({super.key});
|
const WalletEditFields({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<WalletsController>(
|
return Consumer<PaymentSourceController>(
|
||||||
builder: (context, controller, _) {
|
builder: (context, sourceController, _) {
|
||||||
final wallet = controller.selectedWallet;
|
final wallet = sourceController.selectedWallet;
|
||||||
|
if (wallet != null) {
|
||||||
if (wallet == null) {
|
return WalletSection(wallet: wallet);
|
||||||
return SizedBox.shrink();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
final ledger = sourceController.selectedLedgerAccount;
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
if (ledger != null) {
|
||||||
children: [
|
return LedgerSection(ledger: ledger);
|
||||||
Row(
|
}
|
||||||
children: [
|
|
||||||
Expanded(
|
return const SizedBox.shrink();
|
||||||
child: BalanceAmount(
|
|
||||||
wallet: wallet,
|
|
||||||
onToggleMask: () => controller.toggleBalanceMask(wallet.id),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
WalletBalanceRefreshButton(walletRef: wallet.id),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(wallet.walletUserID, style: Theme.of(context).textTheme.bodyLarge),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.copy),
|
|
||||||
iconSize: 18,
|
|
||||||
onPressed: () => Clipboard.setData(ClipboardData(text: wallet.walletUserID)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
import 'package:pshared/utils/money.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class LedgerBalanceFormatter {
|
||||||
|
const LedgerBalanceFormatter._();
|
||||||
|
|
||||||
|
static String format(LedgerAccount account) {
|
||||||
|
final money = account.balance?.balance;
|
||||||
|
if (money == null) return '--';
|
||||||
|
|
||||||
|
final amount = parseMoneyAmount(money.amount, fallback: double.nan);
|
||||||
|
if (amount.isNaN) {
|
||||||
|
return '${money.amount} ${money.currency}';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final currency = currencyStringToCode(money.currency);
|
||||||
|
final symbol = currencyCodeToSymbol(currency);
|
||||||
|
if (symbol.trim().isEmpty) {
|
||||||
|
return '${amountToString(amount)} ${money.currency}';
|
||||||
|
}
|
||||||
|
return '${amountToString(amount)} $symbol';
|
||||||
|
} catch (_) {
|
||||||
|
return '${amountToString(amount)} ${money.currency}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String formatMasked(LedgerAccount account) {
|
||||||
|
final currency = account.currency.trim();
|
||||||
|
if (currency.isEmpty) return '••••';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final symbol = currencyCodeToSymbol(currencyStringToCode(currency));
|
||||||
|
if (symbol.trim().isEmpty) {
|
||||||
|
return '•••• $currency';
|
||||||
|
}
|
||||||
|
return '•••• $symbol';
|
||||||
|
} catch (_) {
|
||||||
|
return '•••• $currency';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class LedgerBalanceRow extends StatelessWidget {
|
||||||
|
final String balance;
|
||||||
|
final bool isMasked;
|
||||||
|
final VoidCallback onToggleMask;
|
||||||
|
|
||||||
|
const LedgerBalanceRow({
|
||||||
|
super.key,
|
||||||
|
required this.balance,
|
||||||
|
required this.isMasked,
|
||||||
|
required this.onToggleMask,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
balance,
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: onToggleMask,
|
||||||
|
child: Icon(
|
||||||
|
isMasked ? Icons.visibility_off : Icons.visibility,
|
||||||
|
size: 22,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/controllers/balance_mask/ledger_accounts.dart';
|
||||||
|
import 'package:pshared/models/ledger/account.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/payout_page/wallet/edit/fields/ledger/balance_formatter.dart';
|
||||||
|
import 'package:pweb/pages/payout_page/wallet/edit/fields/ledger/balance_row.dart';
|
||||||
|
import 'package:pweb/pages/payout_page/wallet/edit/fields/shared/copyable_value_row.dart';
|
||||||
|
import 'package:pweb/widgets/refresh_balance/ledger.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class LedgerSection extends StatelessWidget {
|
||||||
|
final LedgerAccount ledger;
|
||||||
|
|
||||||
|
const LedgerSection({super.key, required this.ledger});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<LedgerBalanceMaskController>(
|
||||||
|
builder: (context, balanceMask, _) {
|
||||||
|
final isMasked = balanceMask.isBalanceMasked(ledger.ledgerAccountRef);
|
||||||
|
final accountCode = ledger.accountCode.trim();
|
||||||
|
final hasAccountCode = accountCode.isNotEmpty;
|
||||||
|
final balance = isMasked
|
||||||
|
? LedgerBalanceFormatter.formatMasked(ledger)
|
||||||
|
: LedgerBalanceFormatter.format(ledger);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: LedgerBalanceRow(
|
||||||
|
balance: balance,
|
||||||
|
isMasked: isMasked,
|
||||||
|
onToggleMask: () {
|
||||||
|
balanceMask.toggleBalanceMask(ledger.ledgerAccountRef);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
LedgerBalanceRefreshButton(
|
||||||
|
ledgerAccountRef: ledger.ledgerAccountRef,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
CopyableValueRow(
|
||||||
|
value: hasAccountCode ? accountCode : '-',
|
||||||
|
canCopy: hasAccountCode,
|
||||||
|
onCopy: hasAccountCode
|
||||||
|
? () {
|
||||||
|
Clipboard.setData(ClipboardData(text: accountCode));
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
wrapValueWithFlexible: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class CopyableValueRow extends StatelessWidget {
|
||||||
|
final String value;
|
||||||
|
final bool canCopy;
|
||||||
|
final VoidCallback? onCopy;
|
||||||
|
final TextOverflow overflow;
|
||||||
|
final bool wrapValueWithFlexible;
|
||||||
|
|
||||||
|
const CopyableValueRow({
|
||||||
|
super.key,
|
||||||
|
required this.value,
|
||||||
|
required this.canCopy,
|
||||||
|
required this.onCopy,
|
||||||
|
this.overflow = TextOverflow.visible,
|
||||||
|
this.wrapValueWithFlexible = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final valueText = Text(
|
||||||
|
value,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
overflow: overflow,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
if (wrapValueWithFlexible) Flexible(child: valueText) else valueText,
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.copy),
|
||||||
|
iconSize: 18,
|
||||||
|
onPressed: canCopy ? onCopy : null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||||
|
import 'package:pshared/models/payment/wallet.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
||||||
|
import 'package:pweb/pages/payout_page/wallet/edit/fields/shared/copyable_value_row.dart';
|
||||||
|
import 'package:pweb/widgets/refresh_balance/wallet.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class WalletSection extends StatelessWidget {
|
||||||
|
final Wallet wallet;
|
||||||
|
|
||||||
|
const WalletSection({super.key, required this.wallet});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final depositAddress = wallet.depositAddress?.trim();
|
||||||
|
final hasDepositAddress =
|
||||||
|
depositAddress != null && depositAddress.isNotEmpty;
|
||||||
|
final copyAddress = hasDepositAddress ? depositAddress : '';
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: BalanceAmount(
|
||||||
|
wallet: wallet,
|
||||||
|
onToggleMask: () {
|
||||||
|
context.read<WalletsController>().toggleBalanceMask(
|
||||||
|
wallet.id,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
WalletBalanceRefreshButton(walletRef: wallet.id),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
CopyableValueRow(
|
||||||
|
value: hasDepositAddress ? depositAddress : '-',
|
||||||
|
canCopy: hasDepositAddress,
|
||||||
|
onCopy: hasDepositAddress
|
||||||
|
? () {
|
||||||
|
Clipboard.setData(ClipboardData(text: copyAddress));
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -12,15 +12,20 @@ class WalletEditHeader extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final controller = context.watch<WalletsController>();
|
final controller = context.watch<PaymentSourceController>();
|
||||||
final wallet = controller.selectedWallet;
|
final wallet = controller.selectedWallet;
|
||||||
|
final ledger = controller.selectedLedgerAccount;
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
if (wallet == null) {
|
if (wallet == null && ledger == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final title = wallet != null
|
||||||
|
? loc.paymentTypeCryptoWallet
|
||||||
|
: loc.paymentTypeLedger;
|
||||||
|
final subtitle = wallet?.tokenSymbol;
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
@@ -32,14 +37,14 @@ class WalletEditHeader extends StatelessWidget {
|
|||||||
spacing: 4,
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
loc.paymentTypeCryptoWallet,
|
title,
|
||||||
style: theme.textTheme.headlineMedium!.copyWith(
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (wallet.tokenSymbol != null)
|
if (subtitle != null && subtitle.trim().isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
wallet.tokenSymbol!,
|
subtitle,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,17 +2,16 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
import 'package:pshared/controllers/payment/source.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/payout_page/wallet/edit/buttons/buttons.dart';
|
import 'package:pweb/pages/payout_page/wallet/edit/buttons/buttons.dart';
|
||||||
import 'package:pweb/pages/payout_page/wallet/edit/fields.dart';
|
import 'package:pweb/pages/payout_page/wallet/edit/fields.dart';
|
||||||
import 'package:pweb/pages/payout_page/wallet/edit/header.dart';
|
import 'package:pweb/pages/payout_page/wallet/edit/header.dart';
|
||||||
import 'package:pweb/pages/payout_page/wallet/history/history.dart';
|
|
||||||
import 'package:pweb/utils/dimensions.dart';
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
//TODO make this page more generic and reusable
|
||||||
class WalletEditPage extends StatelessWidget {
|
class WalletEditPage extends StatelessWidget {
|
||||||
final VoidCallback onBack;
|
final VoidCallback onBack;
|
||||||
|
|
||||||
@@ -23,11 +22,11 @@ class WalletEditPage extends StatelessWidget {
|
|||||||
final dimensions = AppDimensions();
|
final dimensions = AppDimensions();
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return Consumer<WalletsController>(
|
return Consumer<PaymentSourceController>(
|
||||||
builder: (context, controller, child) {
|
builder: (context, controller, child) {
|
||||||
final wallet = controller.selectedWallet;
|
final sourceType = controller.selectedType;
|
||||||
|
|
||||||
if (wallet == null) {
|
if (sourceType == null) {
|
||||||
return Center(child: Text(loc.noWalletSelected));
|
return Center(child: Text(loc.noWalletSelected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,11 +35,15 @@ class WalletEditPage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: dimensions.maxContentWidth),
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: dimensions.maxContentWidth,
|
||||||
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: dimensions.elevationSmall,
|
elevation: dimensions.elevationSmall,
|
||||||
color: Theme.of(context).colorScheme.onSecondary,
|
color: Theme.of(context).colorScheme.onSecondary,
|
||||||
borderRadius: BorderRadius.circular(dimensions.borderRadiusMedium),
|
borderRadius: BorderRadius.circular(
|
||||||
|
dimensions.borderRadiusMedium,
|
||||||
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(dimensions.paddingLarge),
|
padding: EdgeInsets.all(dimensions.paddingLarge),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@@ -55,19 +58,12 @@ class WalletEditPage extends StatelessWidget {
|
|||||||
WalletEditFields(),
|
WalletEditFields(),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
ButtonsWalletWidget(),
|
ButtonsWalletWidget(),
|
||||||
const SizedBox(height: 24),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: WalletHistory(wallet: wallet),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,118 +2,87 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/payment/wallet.dart';
|
import 'package:pshared/models/payment/source_type.dart';
|
||||||
|
import 'package:pshared/provider/payment/payments.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/payout_page/wallet/history/filters.dart';
|
import 'package:pweb/controllers/operations/report_operations.dart';
|
||||||
import 'package:pweb/pages/payout_page/wallet/history/table.dart';
|
import 'package:pweb/models/state/load_more_state.dart';
|
||||||
import 'package:pweb/controllers/operations/wallet_transactions.dart';
|
import 'package:pweb/pages/report/cards/list.dart';
|
||||||
import 'package:pweb/providers/wallet_transactions.dart';
|
import 'package:pweb/pages/report/operations/actions.dart';
|
||||||
|
import 'package:pweb/pages/report/operations/states/error.dart';
|
||||||
|
import 'package:pweb/pages/report/operations/states/loading.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletHistory extends StatefulWidget {
|
class WalletHistory extends StatelessWidget {
|
||||||
final Wallet wallet;
|
final String sourceRef;
|
||||||
|
final PaymentSourceType sourceType;
|
||||||
|
final List<String> sourceRefs;
|
||||||
|
|
||||||
const WalletHistory({super.key, required this.wallet});
|
const WalletHistory({
|
||||||
|
super.key,
|
||||||
@override
|
required this.sourceRef,
|
||||||
State<WalletHistory> createState() => _WalletHistoryState();
|
required this.sourceType,
|
||||||
}
|
required this.sourceRefs,
|
||||||
|
});
|
||||||
class _WalletHistoryState extends State<WalletHistory> {
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
Widget build(BuildContext context) {
|
||||||
super.initState();
|
return ChangeNotifierProxyProvider<
|
||||||
_load();
|
PaymentsProvider,
|
||||||
}
|
ReportOperationsController
|
||||||
|
>(
|
||||||
@override
|
create: (_) => ReportOperationsController(),
|
||||||
void didUpdateWidget(covariant WalletHistory oldWidget) {
|
update: (_, payments, controller) => controller!
|
||||||
super.didUpdateWidget(oldWidget);
|
..update(
|
||||||
if (oldWidget.wallet.id != widget.wallet.id) {
|
payments,
|
||||||
_load();
|
sourceType: sourceType,
|
||||||
}
|
sourceRef: sourceRef,
|
||||||
}
|
sourceRefs: sourceRefs,
|
||||||
|
),
|
||||||
void _load() {
|
child: const _WalletHistoryContent(),
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
);
|
||||||
context
|
}
|
||||||
.read<WalletTransactionsProvider>()
|
}
|
||||||
.load(walletId: widget.wallet.id);
|
|
||||||
});
|
class _WalletHistoryContent extends StatelessWidget {
|
||||||
}
|
const _WalletHistoryContent();
|
||||||
|
|
||||||
Future<void> _pickRange() async {
|
|
||||||
final provider = context.read<WalletTransactionsController>();
|
|
||||||
final now = DateTime.now();
|
|
||||||
final initial = provider.dateRange ??
|
|
||||||
DateTimeRange(
|
|
||||||
start: now.subtract(const Duration(days: 30)),
|
|
||||||
end: now,
|
|
||||||
);
|
|
||||||
|
|
||||||
final picked = await showDateRangePicker(
|
|
||||||
context: context,
|
|
||||||
firstDate: now.subtract(const Duration(days: 365)),
|
|
||||||
lastDate: now.add(const Duration(days: 1)),
|
|
||||||
initialDateRange: initial,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (picked != null) {
|
|
||||||
provider.setDateRange(picked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return Consumer<WalletTransactionsController>(
|
return Consumer<ReportOperationsController>(
|
||||||
builder: (context, provider, child) {
|
builder: (context, controller, child) {
|
||||||
if (provider.isLoading) {
|
if (controller.isLoading) {
|
||||||
return const Padding(
|
return const OperationHistoryLoading();
|
||||||
padding: EdgeInsets.all(16.0),
|
}
|
||||||
child: Center(child: CircularProgressIndicator()),
|
|
||||||
|
if (controller.error != null) {
|
||||||
|
final message =
|
||||||
|
controller.error?.toString() ?? loc.noErrorInformation;
|
||||||
|
return OperationHistoryError(
|
||||||
|
message: loc.notificationError(message),
|
||||||
|
retryLabel: loc.retry,
|
||||||
|
onRetry: controller.refresh,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider.error != null) {
|
final hasLoadMore = controller.loadMoreState != LoadMoreState.hidden;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
OperationsCardsList(
|
||||||
loc.failedToLoadHistory,
|
operations: controller.filteredOperations,
|
||||||
style: theme.textTheme.titleMedium!
|
onTap: (operation) => openPaymentDetails(context, operation),
|
||||||
.copyWith(color: theme.colorScheme.error),
|
loadMoreState: controller.loadMoreState,
|
||||||
),
|
onLoadMore: hasLoadMore ? controller.loadMore : null,
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
|
],
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
OutlinedButton(
|
|
||||||
onPressed: _load,
|
|
||||||
child: Text(loc.retry),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final transactions = provider.filteredTransactions;
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
WalletHistoryFilters(
|
|
||||||
provider: provider,
|
|
||||||
onPickRange: _pickRange,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
WalletTransactionsTable(transactions: transactions),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
|||||||
class PaymentDetailsContent extends StatelessWidget {
|
class PaymentDetailsContent extends StatelessWidget {
|
||||||
final Payment payment;
|
final Payment payment;
|
||||||
final VoidCallback onBack;
|
final VoidCallback onBack;
|
||||||
final VoidCallback? onDownloadAct;
|
|
||||||
final bool Function(PaymentExecutionOperation operation)?
|
final bool Function(PaymentExecutionOperation operation)?
|
||||||
canDownloadOperationDocument;
|
canDownloadOperationDocument;
|
||||||
final ValueChanged<PaymentExecutionOperation>? onDownloadOperationDocument;
|
final ValueChanged<PaymentExecutionOperation>? onDownloadOperationDocument;
|
||||||
@@ -22,7 +21,6 @@ class PaymentDetailsContent extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.payment,
|
required this.payment,
|
||||||
required this.onBack,
|
required this.onBack,
|
||||||
this.onDownloadAct,
|
|
||||||
this.canDownloadOperationDocument,
|
this.canDownloadOperationDocument,
|
||||||
this.onDownloadOperationDocument,
|
this.onDownloadOperationDocument,
|
||||||
});
|
});
|
||||||
@@ -37,7 +35,7 @@ class PaymentDetailsContent extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
PaymentDetailsHeader(title: loc.paymentInfo, onBack: onBack),
|
PaymentDetailsHeader(title: loc.paymentInfo, onBack: onBack),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
PaymentSummaryCard(payment: payment, onDownloadAct: onDownloadAct),
|
PaymentSummaryCard(payment: payment),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
PaymentDetailsSections(
|
PaymentDetailsSections(
|
||||||
payment: payment,
|
payment: payment,
|
||||||
|
|||||||
@@ -64,17 +64,6 @@ class _PaymentDetailsView extends StatelessWidget {
|
|||||||
return PaymentDetailsContent(
|
return PaymentDetailsContent(
|
||||||
payment: payment,
|
payment: payment,
|
||||||
onBack: () => _handleBack(context),
|
onBack: () => _handleBack(context),
|
||||||
onDownloadAct: controller.canDownload
|
|
||||||
? () {
|
|
||||||
final request = controller.primaryOperationDocumentRequest;
|
|
||||||
if (request == null) return;
|
|
||||||
downloadPaymentAct(
|
|
||||||
context,
|
|
||||||
gatewayService: request.gatewayService,
|
|
||||||
operationRef: request.operationRef,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
canDownloadOperationDocument:
|
canDownloadOperationDocument:
|
||||||
controller.canDownloadOperationDocument,
|
controller.canDownloadOperationDocument,
|
||||||
onDownloadOperationDocument: (operation) {
|
onDownloadOperationDocument: (operation) {
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ class PaymentOperationsSection extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
if (i < operations.length - 1) {
|
if (i < operations.length - 1) {
|
||||||
children.addAll([
|
children.addAll([
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 10),
|
||||||
Divider(
|
Divider(
|
||||||
height: 1,
|
height: 1,
|
||||||
color: Theme.of(context).dividerColor.withAlpha(20),
|
color: Theme.of(context).dividerColor.withAlpha(20),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 10),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,11 @@ import 'package:pweb/utils/clipboard.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentSummaryCard extends StatelessWidget {
|
class PaymentSummaryCard extends StatelessWidget {
|
||||||
final Payment payment;
|
final Payment payment;
|
||||||
final VoidCallback? onDownloadAct;
|
|
||||||
|
|
||||||
const PaymentSummaryCard({
|
const PaymentSummaryCard({super.key, required this.payment});
|
||||||
super.key,
|
|
||||||
required this.payment,
|
|
||||||
this.onDownloadAct,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -93,14 +89,6 @@ class PaymentSummaryCard extends StatelessWidget {
|
|||||||
text: feeText,
|
text: feeText,
|
||||||
muted: true,
|
muted: true,
|
||||||
),
|
),
|
||||||
if (onDownloadAct != null) ...[
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: onDownloadAct,
|
|
||||||
icon: const Icon(Icons.download),
|
|
||||||
label: Text(loc.downloadAct),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
if (showPaymentId) ...[
|
if (showPaymentId) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Divider(color: theme.dividerColor.withAlpha(35), height: 1),
|
Divider(color: theme.dividerColor.withAlpha(35), height: 1),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class WalletTransactionsProvider extends ChangeNotifier {
|
|||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
String? _error;
|
String? _error;
|
||||||
String? _walletId;
|
String? _walletId;
|
||||||
|
int _loadSeq = 0;
|
||||||
|
|
||||||
List<WalletTransaction> get transactions => List.unmodifiable(_transactions);
|
List<WalletTransaction> get transactions => List.unmodifiable(_transactions);
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
@@ -20,18 +21,28 @@ class WalletTransactionsProvider extends ChangeNotifier {
|
|||||||
String? get walletId => _walletId;
|
String? get walletId => _walletId;
|
||||||
|
|
||||||
Future<void> load({String? walletId}) async {
|
Future<void> load({String? walletId}) async {
|
||||||
|
final targetWalletId = walletId ?? _walletId;
|
||||||
|
final requestSeq = ++_loadSeq;
|
||||||
|
_walletId = targetWalletId;
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
_error = null;
|
_error = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_walletId = walletId ?? _walletId;
|
final fetched = await _service.fetchHistory(walletId: targetWalletId);
|
||||||
_transactions = await _service.fetchHistory(walletId: _walletId);
|
if (requestSeq != _loadSeq) return;
|
||||||
|
|
||||||
|
_transactions = targetWalletId == null
|
||||||
|
? fetched
|
||||||
|
: fetched.where((tx) => tx.walletId == targetWalletId).toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (requestSeq != _loadSeq) return;
|
||||||
_error = e.toString();
|
_error = e.toString();
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
if (requestSeq == _loadSeq) {
|
||||||
notifyListeners();
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user