accounts creation
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
This commit is contained in:
32
frontend/pshared/lib/api/requests/ledger/create.dart
Normal file
32
frontend/pshared/lib/api/requests/ledger/create.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/data/dto/describable.dart';
|
||||
import 'package:pshared/data/dto/ledger/type.dart';
|
||||
|
||||
part 'create.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateLedgerAccountRequest {
|
||||
final Map<String, String>? metadata;
|
||||
final String currency;
|
||||
final bool allowNegative;
|
||||
final bool isSettlement;
|
||||
final DescribableDTO describable;
|
||||
final String? ownerRef;
|
||||
final LedgerAccountTypeDTO accountType;
|
||||
|
||||
const CreateLedgerAccountRequest({
|
||||
this.metadata,
|
||||
required this.currency,
|
||||
required this.allowNegative,
|
||||
required this.isSettlement,
|
||||
required this.describable,
|
||||
required this.accountType,
|
||||
this.ownerRef,
|
||||
});
|
||||
|
||||
factory CreateLedgerAccountRequest.fromJson(Map<String, dynamic> json) => _$CreateLedgerAccountRequestFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$CreateLedgerAccountRequestToJson(this);
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
|
||||
part 'base.g.dart';
|
||||
|
||||
|
||||
|
||||
25
frontend/pshared/lib/api/requests/wallet/create.dart
Normal file
25
frontend/pshared/lib/api/requests/wallet/create.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/data/dto/describable.dart';
|
||||
import 'package:pshared/data/dto/wallet/chain_asset.dart';
|
||||
|
||||
part 'create.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateWalletRequest {
|
||||
final Map<String, String>? metadata;
|
||||
final DescribableDTO describable;
|
||||
final String? ownerRef;
|
||||
final ChainAssetDTO asset;
|
||||
|
||||
const CreateWalletRequest({
|
||||
this.metadata,
|
||||
required this.asset,
|
||||
required this.describable,
|
||||
this.ownerRef,
|
||||
});
|
||||
|
||||
factory CreateWalletRequest.fromJson(Map<String, dynamic> json) => _$CreateWalletRequestFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$CreateWalletRequestToJson(this);
|
||||
}
|
||||
@@ -60,22 +60,23 @@ class WalletsController with ChangeNotifier {
|
||||
|
||||
String? get selectedWalletRef => _selectedWalletRef;
|
||||
|
||||
void selectWallet(Wallet wallet) => selectWalletByRef(wallet.id);
|
||||
void selectWallet(Wallet wallet, {bool allowHidden = false}) =>
|
||||
selectWalletByRef(wallet.id, allowHidden: allowHidden);
|
||||
|
||||
void selectWalletByRef(String walletRef) {
|
||||
void selectWalletByRef(String walletRef, {bool allowHidden = false}) {
|
||||
if (_selectedWalletRef == walletRef) return;
|
||||
|
||||
// Prevent selecting a hidden wallet
|
||||
if (!_visibleWalletRefs.contains(walletRef)) return;
|
||||
if (!allowHidden && !_visibleWalletRefs.contains(walletRef)) return;
|
||||
|
||||
_selectedWalletRef = walletRef;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Toggle wallet visibility
|
||||
void toggleVisibility(String walletId) {
|
||||
final existed = _visibleWalletRefs.remove(walletId);
|
||||
if (!existed) _visibleWalletRefs.add(walletId);
|
||||
void toggleVisibility(String accountRef) {
|
||||
final existed = _visibleWalletRefs.remove(accountRef);
|
||||
if (!existed) _visibleWalletRefs.add(accountRef);
|
||||
|
||||
_selectedWalletRef = _resolveSelectedId(
|
||||
currentRef: _selectedWalletRef,
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/data/dto/describable.dart';
|
||||
import 'package:pshared/data/dto/ledger/balance.dart';
|
||||
import 'package:pshared/data/dto/ledger/status.dart';
|
||||
import 'package:pshared/data/dto/ledger/type.dart';
|
||||
|
||||
part 'account.g.dart';
|
||||
|
||||
@@ -12,9 +14,9 @@ class LedgerAccountDTO {
|
||||
final String organizationRef;
|
||||
final String? ownerRef;
|
||||
final String accountCode;
|
||||
final String accountType;
|
||||
final LedgerAccountTypeDTO accountType;
|
||||
final String currency;
|
||||
final String status;
|
||||
final LedgerAccountStatusDTO status;
|
||||
final bool allowNegative;
|
||||
final bool isSettlement;
|
||||
final Map<String, String>? metadata;
|
||||
|
||||
13
frontend/pshared/lib/data/dto/ledger/status.dart
Normal file
13
frontend/pshared/lib/data/dto/ledger/status.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
|
||||
enum LedgerAccountStatusDTO {
|
||||
@JsonValue('unspecified')
|
||||
unspecified,
|
||||
|
||||
@JsonValue('active')
|
||||
active,
|
||||
|
||||
@JsonValue('frozen')
|
||||
frozen,
|
||||
}
|
||||
19
frontend/pshared/lib/data/dto/ledger/type.dart
Normal file
19
frontend/pshared/lib/data/dto/ledger/type.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
|
||||
enum LedgerAccountTypeDTO {
|
||||
@JsonValue('unspecified')
|
||||
unspecified,
|
||||
|
||||
@JsonValue('asset')
|
||||
asset,
|
||||
|
||||
@JsonValue('liability')
|
||||
liability,
|
||||
|
||||
@JsonValue('revenue')
|
||||
revenue,
|
||||
|
||||
@JsonValue('expense')
|
||||
expense,
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/data/dto/wallet/chain_asset.dart';
|
||||
|
||||
part 'asset.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable()
|
||||
class WalletAssetDTO {
|
||||
final String chain;
|
||||
final String tokenSymbol;
|
||||
class WalletAssetDTO extends ChainAssetDTO {
|
||||
final String contractAddress;
|
||||
|
||||
const WalletAssetDTO({
|
||||
required this.chain,
|
||||
required this.tokenSymbol,
|
||||
required super.chain,
|
||||
required super.tokenSymbol,
|
||||
required this.contractAddress,
|
||||
});
|
||||
|
||||
factory WalletAssetDTO.fromJson(Map<String, dynamic> json) => _$WalletAssetDTOFromJson(json);
|
||||
@override
|
||||
Map<String, dynamic> toJson() => _$WalletAssetDTOToJson(this);
|
||||
}
|
||||
|
||||
18
frontend/pshared/lib/data/dto/wallet/chain_asset.dart
Normal file
18
frontend/pshared/lib/data/dto/wallet/chain_asset.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'chain_asset.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable()
|
||||
class ChainAssetDTO {
|
||||
final String chain;
|
||||
final String tokenSymbol;
|
||||
|
||||
const ChainAssetDTO({
|
||||
required this.chain,
|
||||
required this.tokenSymbol,
|
||||
});
|
||||
|
||||
factory ChainAssetDTO.fromJson(Map<String, dynamic> json) => _$ChainAssetDTOFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$ChainAssetDTOToJson(this);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:pshared/data/dto/ledger/account.dart';
|
||||
import 'package:pshared/data/mapper/describable.dart';
|
||||
import 'package:pshared/data/mapper/ledger/balance.dart';
|
||||
import 'package:pshared/data/mapper/ledger/status.dart';
|
||||
import 'package:pshared/data/mapper/ledger/type.dart';
|
||||
import 'package:pshared/models/ledger/account.dart';
|
||||
|
||||
|
||||
@@ -10,9 +12,9 @@ extension LedgerAccountDTOMapper on LedgerAccountDTO {
|
||||
organizationRef: organizationRef,
|
||||
ownerRef: ownerRef,
|
||||
accountCode: accountCode,
|
||||
accountType: accountType,
|
||||
accountType: accountType.toDomain(),
|
||||
currency: currency,
|
||||
status: status,
|
||||
status: status.toDomain(),
|
||||
allowNegative: allowNegative,
|
||||
isSettlement: isSettlement,
|
||||
metadata: metadata,
|
||||
@@ -29,9 +31,9 @@ extension LedgerAccountModelMapper on LedgerAccount {
|
||||
organizationRef: organizationRef,
|
||||
ownerRef: ownerRef,
|
||||
accountCode: accountCode,
|
||||
accountType: accountType,
|
||||
accountType: accountType.toDTO(),
|
||||
currency: currency,
|
||||
status: status,
|
||||
status: status.toDTO(),
|
||||
allowNegative: allowNegative,
|
||||
isSettlement: isSettlement,
|
||||
metadata: metadata,
|
||||
|
||||
29
frontend/pshared/lib/data/mapper/ledger/status.dart
Normal file
29
frontend/pshared/lib/data/mapper/ledger/status.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:pshared/data/dto/ledger/status.dart';
|
||||
import 'package:pshared/models/ledger/status.dart';
|
||||
|
||||
|
||||
extension LedgerAccountStatusDTOMapper on LedgerAccountStatusDTO {
|
||||
LedgerAccountStatus toDomain() {
|
||||
switch (this) {
|
||||
case LedgerAccountStatusDTO.unspecified:
|
||||
return LedgerAccountStatus.unspecified;
|
||||
case LedgerAccountStatusDTO.active:
|
||||
return LedgerAccountStatus.active;
|
||||
case LedgerAccountStatusDTO.frozen:
|
||||
return LedgerAccountStatus.frozen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LedgerAccountStatusMapper on LedgerAccountStatus {
|
||||
LedgerAccountStatusDTO toDTO() {
|
||||
switch (this) {
|
||||
case LedgerAccountStatus.unspecified:
|
||||
return LedgerAccountStatusDTO.unspecified;
|
||||
case LedgerAccountStatus.active:
|
||||
return LedgerAccountStatusDTO.active;
|
||||
case LedgerAccountStatus.frozen:
|
||||
return LedgerAccountStatusDTO.frozen;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
frontend/pshared/lib/data/mapper/ledger/type.dart
Normal file
37
frontend/pshared/lib/data/mapper/ledger/type.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:pshared/data/dto/ledger/type.dart';
|
||||
import 'package:pshared/models/ledger/type.dart';
|
||||
|
||||
|
||||
extension LedgerAccountTypeDTOMapper on LedgerAccountTypeDTO {
|
||||
LedgerAccountType toDomain() {
|
||||
switch (this) {
|
||||
case LedgerAccountTypeDTO.unspecified:
|
||||
return LedgerAccountType.unspecified;
|
||||
case LedgerAccountTypeDTO.asset:
|
||||
return LedgerAccountType.asset;
|
||||
case LedgerAccountTypeDTO.liability:
|
||||
return LedgerAccountType.liability;
|
||||
case LedgerAccountTypeDTO.revenue:
|
||||
return LedgerAccountType.revenue;
|
||||
case LedgerAccountTypeDTO.expense:
|
||||
return LedgerAccountType.expense;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LedgerAccountTypeModelMapper on LedgerAccountType {
|
||||
LedgerAccountTypeDTO toDTO() {
|
||||
switch (this) {
|
||||
case LedgerAccountType.unspecified:
|
||||
return LedgerAccountTypeDTO.unspecified;
|
||||
case LedgerAccountType.asset:
|
||||
return LedgerAccountTypeDTO.asset;
|
||||
case LedgerAccountType.liability:
|
||||
return LedgerAccountTypeDTO.liability;
|
||||
case LedgerAccountType.revenue:
|
||||
return LedgerAccountTypeDTO.revenue;
|
||||
case LedgerAccountType.expense:
|
||||
return LedgerAccountTypeDTO.expense;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
frontend/pshared/lib/data/mapper/wallet/chain_asset.dart
Normal file
18
frontend/pshared/lib/data/mapper/wallet/chain_asset.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:pshared/data/dto/wallet/chain_asset.dart';
|
||||
import 'package:pshared/data/mapper/payment/enums.dart';
|
||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
||||
|
||||
|
||||
extension ChainAssetDTOMapper on ChainAssetDTO {
|
||||
ChainAsset toDomain() => ChainAsset(
|
||||
chain: chainNetworkFromValue(chain),
|
||||
tokenSymbol: tokenSymbol,
|
||||
);
|
||||
}
|
||||
|
||||
extension ChainAssetMapper on ChainAsset {
|
||||
ChainAssetDTO toDTO() => ChainAssetDTO(
|
||||
chain: chainNetworkToValue(chain),
|
||||
tokenSymbol: tokenSymbol,
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/ledger/balance.dart';
|
||||
import 'package:pshared/models/ledger/status.dart';
|
||||
import 'package:pshared/models/ledger/type.dart';
|
||||
|
||||
|
||||
class LedgerAccount implements Describable {
|
||||
@@ -7,9 +9,9 @@ class LedgerAccount implements Describable {
|
||||
final String organizationRef;
|
||||
final String? ownerRef;
|
||||
final String accountCode;
|
||||
final String accountType;
|
||||
final LedgerAccountType accountType;
|
||||
final String currency;
|
||||
final String status;
|
||||
final LedgerAccountStatus status;
|
||||
final bool allowNegative;
|
||||
final bool isSettlement;
|
||||
final Map<String, String>? metadata;
|
||||
|
||||
5
frontend/pshared/lib/models/ledger/status.dart
Normal file
5
frontend/pshared/lib/models/ledger/status.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
enum LedgerAccountStatus {
|
||||
unspecified,
|
||||
active,
|
||||
frozen,
|
||||
}
|
||||
7
frontend/pshared/lib/models/ledger/type.dart
Normal file
7
frontend/pshared/lib/models/ledger/type.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
enum LedgerAccountType {
|
||||
unspecified,
|
||||
asset,
|
||||
liability,
|
||||
revenue,
|
||||
expense,
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
import 'package:pshared/models/payment/chain_network.dart';
|
||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
||||
|
||||
|
||||
class WalletAsset {
|
||||
final ChainNetwork chain;
|
||||
final String tokenSymbol;
|
||||
class WalletAsset extends ChainAsset {
|
||||
final String contractAddress;
|
||||
|
||||
const WalletAsset({
|
||||
required this.chain,
|
||||
required this.tokenSymbol,
|
||||
required super.chain,
|
||||
required super.tokenSymbol,
|
||||
required this.contractAddress,
|
||||
});
|
||||
}
|
||||
|
||||
12
frontend/pshared/lib/models/wallet/chain_asset.dart
Normal file
12
frontend/pshared/lib/models/wallet/chain_asset.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:pshared/models/payment/chain_network.dart';
|
||||
|
||||
|
||||
class ChainAsset {
|
||||
final ChainNetwork chain;
|
||||
final String tokenSymbol;
|
||||
|
||||
const ChainAsset({
|
||||
required this.chain,
|
||||
required this.tokenSymbol,
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import 'package:pshared/models/organization/employee.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/resource.dart';
|
||||
import 'package:pshared/service/accounts/employees.dart';
|
||||
import 'package:pshared/utils/exception.dart';
|
||||
|
||||
|
||||
class EmployeesProvider extends ChangeNotifier {
|
||||
@@ -46,10 +47,7 @@ class EmployeesProvider extends ChangeNotifier {
|
||||
error: null,
|
||||
);
|
||||
} catch (e) {
|
||||
_employees = _employees.copyWith(
|
||||
error: e is Exception ? e : Exception('Unknown error: ${e.toString()}'),
|
||||
isLoading: false,
|
||||
);
|
||||
_employees = _employees.copyWith(error: toException(e), isLoading: false);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
||||
@@ -4,8 +4,10 @@ import 'dart:math';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:pshared/models/ledger/account.dart';
|
||||
|
||||
import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/ledger/account.dart';
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/resource.dart';
|
||||
@@ -159,6 +161,30 @@ class LedgerAccountsProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> create({
|
||||
required Describable describable,
|
||||
required Currency currency,
|
||||
String? ownerRef,
|
||||
}) async {
|
||||
final org = _organizations;
|
||||
if (org == null || !org.isOrganizationSet) return;
|
||||
|
||||
_applyResource(_resource.copyWith(isLoading: true, error: null), notify: true);
|
||||
|
||||
try {
|
||||
await _service.create(
|
||||
organizationRef: org.current.id,
|
||||
currency: currency,
|
||||
describable: describable,
|
||||
ownerRef: ownerRef,
|
||||
);
|
||||
await loadAccountsWithBalances();
|
||||
} catch (e) {
|
||||
_applyResource(_resource.copyWith(isLoading: false, error: toException(e)), notify: true);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- internals ----------
|
||||
|
||||
void _applyResource(Resource<List<LedgerAccount>> newResource, {required bool notify}) {
|
||||
|
||||
@@ -5,7 +5,9 @@ import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/resource.dart';
|
||||
import 'package:pshared/service/payment/wallets.dart';
|
||||
@@ -159,6 +161,30 @@ class WalletsProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> create({
|
||||
required Describable describable,
|
||||
required ChainAsset asset,
|
||||
required String? ownerRef,
|
||||
}) async {
|
||||
final org = _organizations;
|
||||
if (org == null || !org.isOrganizationSet) return;
|
||||
|
||||
_applyResource(_resource.copyWith(isLoading: true, error: null), notify: true);
|
||||
|
||||
try {
|
||||
await _service.create(
|
||||
organizationRef: org.current.id,
|
||||
describable: describable,
|
||||
asset: asset,
|
||||
ownerRef: ownerRef,
|
||||
);
|
||||
await loadWalletsWithBalances();
|
||||
} catch (e) {
|
||||
_applyResource(_resource.copyWith(isLoading: false, error: toException(e)), notify: true);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- internals ----------
|
||||
|
||||
void _applyResource(Resource<List<Wallet>> newResource, {required bool notify}) {
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import 'package:pshared/api/requests/ledger/create.dart';
|
||||
import 'package:pshared/api/responses/ledger/accounts.dart';
|
||||
import 'package:pshared/api/responses/ledger/balance.dart';
|
||||
import 'package:pshared/data/mapper/describable.dart';
|
||||
import 'package:pshared/data/mapper/ledger/account.dart';
|
||||
import 'package:pshared/data/mapper/ledger/balance.dart';
|
||||
import 'package:pshared/data/mapper/ledger/type.dart';
|
||||
import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/ledger/account.dart';
|
||||
import 'package:pshared/models/ledger/balance.dart';
|
||||
import 'package:pshared/models/ledger/type.dart';
|
||||
import 'package:pshared/service/authorization/service.dart';
|
||||
import 'package:pshared/service/services.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
|
||||
class LedgerService {
|
||||
@@ -29,4 +36,22 @@ class LedgerService {
|
||||
);
|
||||
return LedgerBalanceResponse.fromJson(json).balance.toDomain();
|
||||
}
|
||||
|
||||
Future<void> create({
|
||||
required String organizationRef,
|
||||
required Describable describable,
|
||||
required String? ownerRef,
|
||||
required Currency currency,
|
||||
}) async => AuthorizationService.getPOSTResponse(
|
||||
_objectType,
|
||||
'/$organizationRef',
|
||||
CreateLedgerAccountRequest(
|
||||
describable: describable.toDTO(),
|
||||
ownerRef: ownerRef,
|
||||
allowNegative: false,
|
||||
isSettlement: false,
|
||||
accountType: LedgerAccountType.asset.toDTO(),
|
||||
currency: currencyCodeToString(currency),
|
||||
).toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import 'package:pshared/data/mapper/wallet/ui.dart';
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
||||
import 'package:pshared/service/wallet.dart' as shared_wallet_service;
|
||||
|
||||
|
||||
abstract class WalletsService {
|
||||
Future<List<Wallet>> getWallets(String organizationRef);
|
||||
Future<double> getBalance(String organizationRef, String walletRef);
|
||||
Future<void> create({
|
||||
required String organizationRef,
|
||||
required Describable describable,
|
||||
required ChainAsset asset,
|
||||
required String? ownerRef,
|
||||
});
|
||||
}
|
||||
|
||||
class ApiWalletsService implements WalletsService {
|
||||
@@ -24,4 +32,17 @@ class ApiWalletsService implements WalletsService {
|
||||
final amount = balance.available?.amount;
|
||||
return amount == null ? 0 : double.tryParse(amount) ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> create({
|
||||
required String organizationRef,
|
||||
required Describable describable,
|
||||
required ChainAsset asset,
|
||||
required String? ownerRef,
|
||||
}) => shared_wallet_service.WalletService.create(
|
||||
organizationRef: organizationRef,
|
||||
describable: describable,
|
||||
asset: asset,
|
||||
ownerRef: ownerRef,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import 'package:pshared/api/requests/wallet/create.dart';
|
||||
import 'package:pshared/api/responses/wallet_balance.dart';
|
||||
import 'package:pshared/api/responses/wallets.dart';
|
||||
import 'package:pshared/data/mapper/describable.dart';
|
||||
import 'package:pshared/data/mapper/wallet/chain_asset.dart';
|
||||
import 'package:pshared/data/mapper/wallet/response.dart';
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/wallet/balance.dart';
|
||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
||||
import 'package:pshared/models/wallet/wallet.dart';
|
||||
import 'package:pshared/service/authorization/service.dart';
|
||||
import 'package:pshared/service/services.dart';
|
||||
@@ -28,4 +33,19 @@ class WalletService {
|
||||
);
|
||||
return WalletBalanceResponse.fromJson(json).toDomain();
|
||||
}
|
||||
|
||||
static Future<void> create({
|
||||
required String organizationRef,
|
||||
required Describable describable,
|
||||
required ChainAsset asset,
|
||||
required String? ownerRef,
|
||||
}) async => AuthorizationService.getPOSTResponse(
|
||||
_objectType,
|
||||
'/$organizationRef',
|
||||
CreateWalletRequest(
|
||||
asset: asset.toDTO(),
|
||||
describable: describable.toDTO(),
|
||||
ownerRef: ownerRef,
|
||||
).toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ void _openWalletEdit(
|
||||
Wallet wallet, {
|
||||
required PayoutDestination returnTo,
|
||||
}) {
|
||||
context.read<WalletsController>().selectWallet(wallet);
|
||||
context.read<WalletsController>().selectWallet(wallet, allowHidden: true);
|
||||
context.pushToEditWallet(returnTo: returnTo);
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ void _openWalletTopUp(
|
||||
Wallet wallet, {
|
||||
required PayoutDestination returnTo,
|
||||
}) {
|
||||
context.read<WalletsController>().selectWallet(wallet);
|
||||
context.read<WalletsController>().selectWallet(wallet, allowHidden: true);
|
||||
context.pushToWalletTopUp(returnTo: returnTo);
|
||||
}
|
||||
|
||||
|
||||
@@ -608,6 +608,8 @@
|
||||
"noRecipientsFound": "No recipients found for this query.",
|
||||
"sourceOfFunds": "Source of funds",
|
||||
"walletTopUp": "Top up",
|
||||
"errorCreateManagedWallet": "Failed to create managed wallet.",
|
||||
"errorCreateLedgerAccount": "Failed to create ledger account.",
|
||||
"englishLanguage": "English",
|
||||
"russianLanguage": "Russian",
|
||||
"germanLanguage": "German"
|
||||
|
||||
@@ -609,6 +609,8 @@
|
||||
"noRecipientsFound": "Получатели по запросу не найдены.",
|
||||
"sourceOfFunds": "Источник средств",
|
||||
"walletTopUp": "Пополнение",
|
||||
"errorCreateManagedWallet": "Не удалось создать управляемый кошелек.",
|
||||
"errorCreateLedgerAccount": "Не удалось создать счет бухгалтерской книги.",
|
||||
"englishLanguage": "Английский",
|
||||
"russianLanguage": "Русский",
|
||||
"germanLanguage": "Немецкий"
|
||||
|
||||
@@ -16,8 +16,10 @@ import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/accounts/employees.dart';
|
||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
import 'package:pshared/provider/ledger.dart';
|
||||
import 'package:pshared/provider/payment/wallets.dart';
|
||||
import 'package:pshared/provider/invitations.dart';
|
||||
import 'package:pshared/service/ledger.dart';
|
||||
import 'package:pshared/service/payment/wallets.dart';
|
||||
|
||||
import 'package:pweb/app/app.dart';
|
||||
@@ -96,6 +98,10 @@ void main() async {
|
||||
create: (_) => WalletsProvider(ApiWalletsService()),
|
||||
update: (context, organizations, provider) => provider!..update(organizations),
|
||||
),
|
||||
ChangeNotifierProxyProvider<OrganizationsProvider, LedgerAccountsProvider>(
|
||||
create: (_) => LedgerAccountsProvider(LedgerService()),
|
||||
update: (context, organizations, provider) => provider!..update(organizations),
|
||||
),
|
||||
ChangeNotifierProxyProvider<WalletsProvider, WalletsController>(
|
||||
create: (_) => WalletsController(),
|
||||
update: (_, wallets, controller) => controller!..update(wallets),
|
||||
@@ -111,4 +117,4 @@ void main() async {
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
|
||||
import 'package:pweb/utils/text_field_styles.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class AssetTypeField extends StatelessWidget {
|
||||
final PaymentType value;
|
||||
final ValueChanged<PaymentType?>? onChanged;
|
||||
|
||||
const AssetTypeField({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return DropdownButtonFormField<PaymentType>(
|
||||
initialValue: value,
|
||||
decoration: getInputDecoration(context, l10n.paymentType, true),
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: PaymentType.managedWallet,
|
||||
child: Text(l10n.paymentTypeManagedWallet),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: PaymentType.ledger,
|
||||
child: Text(l10n.paymentTypeLedger),
|
||||
),
|
||||
],
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class DialogCancelButton extends StatelessWidget {
|
||||
final String label;
|
||||
final bool isSaving;
|
||||
final VoidCallback onCancel;
|
||||
|
||||
const DialogCancelButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.isSaving,
|
||||
required this.onCancel,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => TextButton(
|
||||
onPressed: isSaving ? null : onCancel,
|
||||
child: Text(label),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/dialog.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class AddBalanceCard extends StatelessWidget {
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const AddBalanceCard({super.key, this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
final textTheme = theme.textTheme;
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
final borderRadius = BorderRadius.circular(WalletCardConfig.borderRadius);
|
||||
|
||||
final subtitle = '${loc.paymentTypeLedger} / ${loc.paymentTypeManagedWallet}';
|
||||
final effectiveOnTap = onTap ?? () => showAddBalanceDialog(context);
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: DottedBorder(
|
||||
options: RoundedRectDottedBorderOptions(
|
||||
radius: Radius.circular(WalletCardConfig.borderRadius),
|
||||
dashPattern: const [8, 5],
|
||||
strokeWidth: 1.6,
|
||||
color: colorScheme.primary.withAlpha(110),
|
||||
),
|
||||
child: Material(
|
||||
color: colorScheme.primary.withAlpha(14),
|
||||
child: InkWell(
|
||||
onTap: effectiveOnTap,
|
||||
child: SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: WalletCardConfig.contentPadding,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: colorScheme.primary.withAlpha(28),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.add_rounded,
|
||||
size: 28,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
loc.actionAddNew,
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/models/payment/chain_network.dart';
|
||||
|
||||
|
||||
const String orgOwnerRef = '';
|
||||
const Currency managedCurrencyDefault = Currency.usdt;
|
||||
const Currency ledgerCurrencyDefault = Currency.rub;
|
||||
const ChainNetwork managedNetworkDefault = ChainNetwork.tronMainnet;
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/utils/text_field_styles.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class DescriptionField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
|
||||
const DescriptionField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
decoration: getInputDecoration(context, '${l10n.comment} (${l10n.optional})', true),
|
||||
style: getTextFieldStyle(context, true),
|
||||
minLines: 2,
|
||||
maxLines: 3,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/payment/chain_network.dart';
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
||||
import 'package:pshared/provider/accounts/employees.dart';
|
||||
import 'package:pshared/provider/ledger.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/payment/wallets.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/form.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/constants.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/cancel.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/submit.dart';
|
||||
import 'package:pweb/utils/error/snackbar.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
Future<void> showAddBalanceDialog(BuildContext context) => showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) => const AddBalanceDialog(),
|
||||
);
|
||||
|
||||
class AddBalanceDialog extends StatefulWidget {
|
||||
const AddBalanceDialog({super.key});
|
||||
|
||||
@override
|
||||
State<AddBalanceDialog> createState() => _AddBalanceDialogState();
|
||||
}
|
||||
|
||||
class _AddBalanceDialogState extends State<AddBalanceDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _nameController = TextEditingController();
|
||||
final _descriptionController = TextEditingController();
|
||||
|
||||
PaymentType _assetType = PaymentType.managedWallet;
|
||||
String _ownerRef = orgOwnerRef;
|
||||
Currency _managedCurrency = managedCurrencyDefault;
|
||||
ChainNetwork _network = managedNetworkDefault;
|
||||
Currency _ledgerCurrency = ledgerCurrencyDefault;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_descriptionController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _setAssetType(PaymentType? value) {
|
||||
if (value == null) return;
|
||||
setState(() => _assetType = value);
|
||||
}
|
||||
|
||||
void _setOwnerRef(String? value) {
|
||||
if (value == null) return;
|
||||
setState(() => _ownerRef = value);
|
||||
}
|
||||
|
||||
void _setManagedCurrency(Currency? value) {
|
||||
if (value == null) return;
|
||||
setState(() => _managedCurrency = value);
|
||||
}
|
||||
|
||||
void _setNetwork(ChainNetwork? value) {
|
||||
if (value == null) return;
|
||||
setState(() => _network = value);
|
||||
}
|
||||
|
||||
void _setLedgerCurrency(Currency? value) {
|
||||
if (value == null) return;
|
||||
setState(() => _ledgerCurrency = value);
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
final form = _formKey.currentState;
|
||||
if (form == null || !form.validate()) return;
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final name = _nameController.text.trim();
|
||||
final description = _descriptionController.text.trim();
|
||||
final employees = context.read<EmployeesProvider>().employees;
|
||||
final effectiveOwnerRef = employees.any((employee) => employee.id == _ownerRef) ? _ownerRef : orgOwnerRef;
|
||||
final isOrgWallet = effectiveOwnerRef == orgOwnerRef;
|
||||
final owner = isOrgWallet ? null : employees.firstWhereOrNull((employee) => employee.id == effectiveOwnerRef);
|
||||
|
||||
final errorMessage = _assetType == PaymentType.managedWallet
|
||||
? l10n.errorCreateManagedWallet
|
||||
: l10n.errorCreateLedgerAccount;
|
||||
|
||||
final result = await executeActionWithNotification<bool>(
|
||||
context: context,
|
||||
errorMessage: errorMessage,
|
||||
action: () async {
|
||||
if (_assetType == PaymentType.managedWallet) {
|
||||
await context.read<WalletsProvider>().create(
|
||||
describable: newDescribable(name: name, description: description),
|
||||
asset: ChainAsset(chain: _network, tokenSymbol: currencyCodeToString(_managedCurrency)),
|
||||
ownerRef: owner?.id,
|
||||
);
|
||||
} else {
|
||||
await context.read<LedgerAccountsProvider>().create(
|
||||
describable: newDescribable(name: name, description: description),
|
||||
currency: _ledgerCurrency,
|
||||
ownerRef: owner?.id,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
if (result == true && mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final orgName = context.select<OrganizationsProvider, String?>(
|
||||
(provider) => provider.isOrganizationSet ? provider.current.name : null,
|
||||
);
|
||||
final employeesProvider = context.watch<EmployeesProvider>();
|
||||
final employees = employeesProvider.employees;
|
||||
final isSaving = context.watch<WalletsProvider>().isLoading ||
|
||||
context.watch<LedgerAccountsProvider>().isLoading;
|
||||
|
||||
final ownerItems = <DropdownMenuItem<String>>[
|
||||
DropdownMenuItem(
|
||||
value: orgOwnerRef,
|
||||
child: Text(orgName ?? l10n.companyName),
|
||||
),
|
||||
...employees.map((employee) => DropdownMenuItem(
|
||||
value: employee.id,
|
||||
child: Text(employee.fullName.isNotEmpty ? employee.fullName : employee.login),
|
||||
)),
|
||||
];
|
||||
|
||||
final resolvedOwnerRef = ownerItems.any((item) => item.value == _ownerRef)
|
||||
? _ownerRef
|
||||
: orgOwnerRef;
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(l10n.actionAddNew),
|
||||
content: AddBalanceForm(
|
||||
formKey: _formKey,
|
||||
assetType: _assetType,
|
||||
isSaving: isSaving,
|
||||
ownerItems: ownerItems,
|
||||
ownerValue: resolvedOwnerRef,
|
||||
onAssetTypeChanged: _setAssetType,
|
||||
onOwnerChanged: _setOwnerRef,
|
||||
nameController: _nameController,
|
||||
descriptionController: _descriptionController,
|
||||
managedCurrency: _managedCurrency,
|
||||
network: _network,
|
||||
ledgerCurrency: _ledgerCurrency,
|
||||
onManagedCurrencyChanged: _setManagedCurrency,
|
||||
onNetworkChanged: _setNetwork,
|
||||
onLedgerCurrencyChanged: _setLedgerCurrency,
|
||||
showEmployeesLoading: employeesProvider.isLoading,
|
||||
),
|
||||
actions: [
|
||||
DialogCancelButton(
|
||||
label: l10n.cancel,
|
||||
isSaving: isSaving,
|
||||
onCancel: () => Navigator.of(context).pop(),
|
||||
),
|
||||
DialogSubmitButton(
|
||||
label: l10n.add,
|
||||
isSaving: isSaving,
|
||||
onSubmit: _submit,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class EmployeesLoadingIndicator extends StatelessWidget {
|
||||
const EmployeesLoadingIndicator({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => const Padding(
|
||||
padding: EdgeInsets.only(top: 4),
|
||||
child: LinearProgressIndicator(minHeight: 2),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/models/payment/chain_network.dart';
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/asset_type_field.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/description.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/employees_loading_indicator.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/ledger_fields.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/managed_wallet_fields.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/name.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/owner.dart';
|
||||
|
||||
|
||||
class AddBalanceForm extends StatelessWidget {
|
||||
final GlobalKey<FormState> formKey;
|
||||
final PaymentType assetType;
|
||||
final bool isSaving;
|
||||
final List<DropdownMenuItem<String>> ownerItems;
|
||||
final String ownerValue;
|
||||
final ValueChanged<PaymentType?> onAssetTypeChanged;
|
||||
final ValueChanged<String?> onOwnerChanged;
|
||||
final TextEditingController nameController;
|
||||
final TextEditingController descriptionController;
|
||||
final Currency managedCurrency;
|
||||
final ChainNetwork network;
|
||||
final Currency ledgerCurrency;
|
||||
final ValueChanged<Currency?> onManagedCurrencyChanged;
|
||||
final ValueChanged<ChainNetwork?> onNetworkChanged;
|
||||
final ValueChanged<Currency?> onLedgerCurrencyChanged;
|
||||
final bool showEmployeesLoading;
|
||||
|
||||
const AddBalanceForm({
|
||||
super.key,
|
||||
required this.formKey,
|
||||
required this.assetType,
|
||||
required this.isSaving,
|
||||
required this.ownerItems,
|
||||
required this.ownerValue,
|
||||
required this.onAssetTypeChanged,
|
||||
required this.onOwnerChanged,
|
||||
required this.nameController,
|
||||
required this.descriptionController,
|
||||
required this.managedCurrency,
|
||||
required this.network,
|
||||
required this.ledgerCurrency,
|
||||
required this.onManagedCurrencyChanged,
|
||||
required this.onNetworkChanged,
|
||||
required this.onLedgerCurrencyChanged,
|
||||
required this.showEmployeesLoading,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Form(
|
||||
key: formKey,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
AssetTypeField(
|
||||
value: assetType,
|
||||
onChanged: isSaving ? null : onAssetTypeChanged,
|
||||
),
|
||||
OwnerField(
|
||||
value: ownerValue,
|
||||
items: ownerItems,
|
||||
onChanged: isSaving ? null : onOwnerChanged,
|
||||
),
|
||||
NameField(controller: nameController),
|
||||
DescriptionField(controller: descriptionController),
|
||||
if (assetType == PaymentType.managedWallet)
|
||||
ManagedWalletFields(
|
||||
currency: managedCurrency,
|
||||
network: network,
|
||||
onCurrencyChanged: isSaving ? null : onManagedCurrencyChanged,
|
||||
onNetworkChanged: isSaving ? null : onNetworkChanged,
|
||||
)
|
||||
else
|
||||
LedgerFields(
|
||||
currency: ledgerCurrency,
|
||||
onCurrencyChanged: isSaving ? null : onLedgerCurrencyChanged,
|
||||
),
|
||||
if (showEmployeesLoading) const EmployeesLoadingIndicator(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/constants.dart';
|
||||
import 'package:pweb/utils/text_field_styles.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class LedgerFields extends StatelessWidget {
|
||||
final Currency currency;
|
||||
final ValueChanged<Currency?>? onCurrencyChanged;
|
||||
|
||||
const LedgerFields({
|
||||
super.key,
|
||||
required this.currency,
|
||||
required this.onCurrencyChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => DropdownButtonFormField<Currency>(
|
||||
initialValue: currency,
|
||||
decoration: getInputDecoration(context, AppLocalizations.of(context)!.currency, true),
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: ledgerCurrencyDefault,
|
||||
child: Text(currencyCodeToString(ledgerCurrencyDefault)),
|
||||
),
|
||||
],
|
||||
onChanged: onCurrencyChanged,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/models/payment/chain_network.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/l10n/chain.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/constants.dart';
|
||||
import 'package:pweb/utils/text_field_styles.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class ManagedWalletFields extends StatelessWidget {
|
||||
final Currency currency;
|
||||
final ChainNetwork network;
|
||||
final ValueChanged<Currency?>? onCurrencyChanged;
|
||||
final ValueChanged<ChainNetwork?>? onNetworkChanged;
|
||||
|
||||
const ManagedWalletFields({
|
||||
super.key,
|
||||
required this.currency,
|
||||
required this.network,
|
||||
required this.onCurrencyChanged,
|
||||
required this.onNetworkChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
DropdownButtonFormField<Currency>(
|
||||
initialValue: currency,
|
||||
decoration: getInputDecoration(context, l10n.currency, true),
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: managedCurrencyDefault,
|
||||
child: Text(currencyCodeToString(managedCurrencyDefault)),
|
||||
),
|
||||
],
|
||||
onChanged: onCurrencyChanged,
|
||||
),
|
||||
DropdownButtonFormField<ChainNetwork>(
|
||||
initialValue: network,
|
||||
decoration: getInputDecoration(context, l10n.walletTopUpNetworkLabel, true),
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: managedNetworkDefault,
|
||||
child: Text(managedNetworkDefault.localizedName(context)),
|
||||
),
|
||||
],
|
||||
onChanged: onNetworkChanged,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/utils/text_field_styles.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class NameField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
|
||||
const NameField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => TextFormField(
|
||||
controller: controller,
|
||||
decoration: getInputDecoration(context, AppLocalizations.of(context)!.accountName, true),
|
||||
style: getTextFieldStyle(context, true),
|
||||
validator: (value) => (value == null || value.trim().isEmpty) ? AppLocalizations.of(context)!.errorNameMissing : null,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/utils/text_field_styles.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class OwnerField extends StatelessWidget {
|
||||
final String value;
|
||||
final List<DropdownMenuItem<String>> items;
|
||||
final ValueChanged<String?>? onChanged;
|
||||
|
||||
const OwnerField({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.items,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => DropdownButtonFormField<String>(
|
||||
initialValue: value,
|
||||
decoration: getInputDecoration(context, AppLocalizations.of(context)!.colDataOwner, true),
|
||||
items: items,
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class DialogSubmitButton extends StatelessWidget {
|
||||
final String label;
|
||||
final bool isSaving;
|
||||
final VoidCallback onSubmit;
|
||||
|
||||
const DialogSubmitButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.isSaving,
|
||||
required this.onSubmit,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ElevatedButton(
|
||||
onPressed: isSaving ? null : onSubmit,
|
||||
child: isSaving
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: Text(label),
|
||||
);
|
||||
}
|
||||
@@ -3,10 +3,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/controllers/wallets.dart';
|
||||
import 'package:pshared/provider/ledger.dart';
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/controller.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/balance_item.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
@@ -22,38 +24,69 @@ class BalanceWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletsController = context.watch<WalletsController>();
|
||||
final ledgerProvider = context.watch<LedgerAccountsProvider>();
|
||||
final carousel = context.watch<CarouselIndexController>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (walletsController.isLoading) {
|
||||
final wallets = walletsController.wallets;
|
||||
final accounts = ledgerProvider.accounts;
|
||||
final isLoading = walletsController.isLoading &&
|
||||
ledgerProvider.isLoading &&
|
||||
wallets.isEmpty &&
|
||||
accounts.isEmpty;
|
||||
|
||||
if (isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final wallets = walletsController.wallets;
|
||||
final items = [
|
||||
...wallets.map(BalanceItem.wallet),
|
||||
...accounts.map(BalanceItem.ledger),
|
||||
const BalanceItem.addAction(),
|
||||
];
|
||||
|
||||
if (wallets.isEmpty) {
|
||||
return Center(child: Text(loc.noWalletsAvailable));
|
||||
if (items.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
// Ensure index is always valid when wallets list changes
|
||||
carousel.setIndex(carousel.index, wallets.length);
|
||||
// Ensure index is always valid when list changes
|
||||
carousel.setIndex(carousel.index, items.length);
|
||||
|
||||
final index = carousel.index;
|
||||
final wallet = wallets[index];
|
||||
final current = items[index];
|
||||
|
||||
// Single source of truth: controller
|
||||
if (walletsController.selectedWallet?.id != wallet.id) {
|
||||
walletsController.selectWallet(wallet);
|
||||
if (current.isWallet) {
|
||||
final wallet = current.wallet!;
|
||||
if (walletsController.selectedWallet?.id != wallet.id) {
|
||||
walletsController.selectWallet(wallet);
|
||||
}
|
||||
}
|
||||
|
||||
return WalletCarousel(
|
||||
wallets: wallets,
|
||||
final carouselWidget = BalanceCarousel(
|
||||
items: items,
|
||||
currentIndex: index,
|
||||
onIndexChanged: (i) {
|
||||
carousel.setIndex(i, wallets.length);
|
||||
walletsController.selectWallet(wallets[i]);
|
||||
carousel.setIndex(i, items.length);
|
||||
final next = items[carousel.index];
|
||||
if (next.isWallet) {
|
||||
walletsController.selectWallet(next.wallet!);
|
||||
}
|
||||
},
|
||||
onTopUp: onTopUp,
|
||||
);
|
||||
|
||||
if (wallets.isEmpty && accounts.isEmpty) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Center(child: Text(loc.noWalletsAvailable)),
|
||||
const SizedBox(height: 12),
|
||||
carouselWidget,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return carouselWidget;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:pshared/models/ledger/account.dart';
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
|
||||
|
||||
enum BalanceItemType { wallet, ledger, addAction }
|
||||
|
||||
class BalanceItem {
|
||||
final BalanceItemType type;
|
||||
final Wallet? wallet;
|
||||
final LedgerAccount? account;
|
||||
|
||||
const BalanceItem.wallet(this.wallet) : type = BalanceItemType.wallet, account = null;
|
||||
|
||||
const BalanceItem.ledger(this.account) : type = BalanceItemType.ledger, wallet = null;
|
||||
|
||||
const BalanceItem.addAction() : type = BalanceItemType.addAction, wallet = null, account = null;
|
||||
|
||||
bool get isWallet => type == BalanceItemType.wallet;
|
||||
bool get isLedger => type == BalanceItemType.ledger;
|
||||
bool get isAdd => type == BalanceItemType.addAction;
|
||||
}
|
||||
@@ -4,12 +4,16 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/controllers/wallets.dart';
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/models/payment/chain_network.dart';
|
||||
import 'package:pshared/utils/l10n/chain.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add_funds.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/header.dart';
|
||||
import 'package:pweb/widgets/wallet_balance_refresh_button.dart';
|
||||
import 'package:pweb/widgets/refresh_balance/wallet.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletCard extends StatelessWidget {
|
||||
@@ -24,42 +28,49 @@ class WalletCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final networkLabel = (wallet.network == null || wallet.network == ChainNetwork.unspecified)
|
||||
? null
|
||||
: wallet.network!.localizedName(context);
|
||||
final symbol = wallet.tokenSymbol?.trim();
|
||||
|
||||
return Card(
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
elevation: WalletCardConfig.elevation,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
||||
),
|
||||
child: Padding(
|
||||
padding: WalletCardConfig.contentPadding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BalanceHeader(
|
||||
walletNetwork: wallet.network,
|
||||
tokenSymbol: wallet.tokenSymbol,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
BalanceAmount(
|
||||
wallet: wallet,
|
||||
onToggleVisibility: () {
|
||||
context.read<WalletsController>().toggleVisibility(wallet.id);
|
||||
},
|
||||
),
|
||||
WalletBalanceRefreshButton(
|
||||
walletId: wallet.id,
|
||||
),
|
||||
],
|
||||
),
|
||||
BalanceAddFunds(
|
||||
onTopUp: () {
|
||||
onTopUp();
|
||||
},
|
||||
),
|
||||
],
|
||||
child: SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: WalletCardConfig.contentPadding,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BalanceHeader(
|
||||
title: loc.paymentTypeCryptoWallet,
|
||||
subtitle: networkLabel,
|
||||
badge: (symbol == null || symbol.isEmpty) ? null : symbol,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
BalanceAmount(
|
||||
wallet: wallet,
|
||||
onToggleVisibility: () {
|
||||
context.read<WalletsController>().toggleVisibility(wallet.id);
|
||||
},
|
||||
),
|
||||
WalletBalanceRefreshButton(
|
||||
walletRef: wallet.id,
|
||||
),
|
||||
],
|
||||
),
|
||||
BalanceAddFunds(onTopUp: onTopUp),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/gestures.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/balance_item.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/card.dart';
|
||||
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/ledger.dart';
|
||||
|
||||
|
||||
class WalletCarousel extends StatelessWidget {
|
||||
final List<Wallet> wallets;
|
||||
class BalanceCarousel extends StatefulWidget {
|
||||
final List<BalanceItem> items;
|
||||
final int currentIndex;
|
||||
final ValueChanged<int> onIndexChanged;
|
||||
final ValueChanged<Wallet> onTopUp;
|
||||
|
||||
const WalletCarousel({
|
||||
const BalanceCarousel({
|
||||
super.key,
|
||||
required this.wallets,
|
||||
required this.items,
|
||||
required this.currentIndex,
|
||||
required this.onIndexChanged,
|
||||
required this.onTopUp,
|
||||
});
|
||||
|
||||
@override
|
||||
State<BalanceCarousel> createState() => _BalanceCarouselState();
|
||||
}
|
||||
|
||||
class _BalanceCarouselState extends State<BalanceCarousel> {
|
||||
late final PageController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = PageController(
|
||||
initialPage: widget.currentIndex,
|
||||
viewportFraction: WalletCardConfig.viewportFraction,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant BalanceCarousel oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (!mounted) return;
|
||||
if (_controller.hasClients) {
|
||||
final currentPage = _controller.page?.round();
|
||||
if (currentPage != widget.currentIndex) {
|
||||
_controller.jumpToPage(widget.currentIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _goToPage(int index) {
|
||||
if (!_controller.hasClients) return;
|
||||
_controller.animateToPage(
|
||||
index,
|
||||
duration: const Duration(milliseconds: 220),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (wallets.isEmpty) {
|
||||
if (widget.items.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final safeIndex = currentIndex.clamp(0, wallets.length - 1);
|
||||
final wallet = wallets[safeIndex];
|
||||
final safeIndex = widget.currentIndex.clamp(0, widget.items.length - 1);
|
||||
final scrollBehavior = ScrollConfiguration.of(context).copyWith(
|
||||
dragDevices: const {
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.trackpad,
|
||||
},
|
||||
);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: WalletCardConfig.cardHeight,
|
||||
child: Padding(
|
||||
padding: WalletCardConfig.cardPadding,
|
||||
child: WalletCard(
|
||||
wallet: wallet,
|
||||
onTopUp: () => onTopUp(wallet),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.grab,
|
||||
child: ScrollConfiguration(
|
||||
behavior: scrollBehavior,
|
||||
child: PageView.builder(
|
||||
controller: _controller,
|
||||
onPageChanged: widget.onIndexChanged,
|
||||
itemCount: widget.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = widget.items[index];
|
||||
final Widget card = switch (item.type) {
|
||||
BalanceItemType.wallet => WalletCard(
|
||||
wallet: item.wallet!,
|
||||
onTopUp: () => widget.onTopUp(item.wallet!),
|
||||
),
|
||||
BalanceItemType.ledger => LedgerAccountCard(account: item.account!),
|
||||
BalanceItemType.addAction => const AddBalanceCard(),
|
||||
};
|
||||
|
||||
return Padding(
|
||||
padding: WalletCardConfig.cardPadding,
|
||||
child: card,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -47,20 +121,18 @@ class WalletCarousel extends StatelessWidget {
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: safeIndex > 0
|
||||
? () => onIndexChanged(safeIndex - 1)
|
||||
? () => _goToPage(safeIndex - 1)
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
CarouselIndicator(
|
||||
itemCount: wallets.length,
|
||||
itemCount: widget.items.length,
|
||||
index: safeIndex,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
IconButton(
|
||||
onPressed: safeIndex < wallets.length - 1
|
||||
? () => onIndexChanged(safeIndex + 1)
|
||||
: null,
|
||||
onPressed: safeIndex < widget.items.length - 1 ? () => _goToPage(safeIndex + 1) : null,
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/chain_network.dart';
|
||||
import 'package:pshared/utils/l10n/chain.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
class BalanceHeader extends StatelessWidget {
|
||||
final ChainNetwork? walletNetwork;
|
||||
final String? tokenSymbol;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final String? badge;
|
||||
|
||||
const BalanceHeader({
|
||||
super.key,
|
||||
this.walletNetwork,
|
||||
this.tokenSymbol,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.badge,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final symbol = tokenSymbol?.trim();
|
||||
final networkLabel = (walletNetwork == null || walletNetwork == ChainNetwork.unspecified)
|
||||
? null
|
||||
: walletNetwork!.localizedName(context);
|
||||
final subtitleText = subtitle?.trim();
|
||||
final badgeText = badge?.trim();
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
@@ -32,14 +27,14 @@ class BalanceHeader extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
loc.paymentTypeCryptoWallet,
|
||||
title,
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
if (networkLabel != null)
|
||||
if (subtitleText != null && subtitleText.isNotEmpty)
|
||||
Text(
|
||||
networkLabel,
|
||||
subtitleText,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -48,7 +43,7 @@ class BalanceHeader extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (symbol != null && symbol.isNotEmpty) ...[
|
||||
if (badgeText != null && badgeText.isNotEmpty) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
@@ -57,7 +52,7 @@ class BalanceHeader extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(
|
||||
symbol,
|
||||
badgeText,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/ledger/account.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/header.dart';
|
||||
import 'package:pweb/widgets/refresh_balance/ledger.dart';
|
||||
|
||||
|
||||
class LedgerAccountCard extends StatelessWidget {
|
||||
final LedgerAccount account;
|
||||
|
||||
const LedgerAccountCard({
|
||||
super.key,
|
||||
required this.account,
|
||||
});
|
||||
|
||||
String _formatBalance() {
|
||||
final money = account.balance?.balance;
|
||||
if (money == null) return '--';
|
||||
|
||||
final amount = double.tryParse(money.amount);
|
||||
if (amount == null) {
|
||||
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}';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final subtitle = account.name.isNotEmpty ? account.name : account.accountCode;
|
||||
final badge = account.currency.trim().isEmpty ? null : account.currency.toUpperCase();
|
||||
|
||||
return Card(
|
||||
color: colorScheme.onSecondary,
|
||||
elevation: WalletCardConfig.elevation,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
||||
),
|
||||
child: Padding(
|
||||
padding: WalletCardConfig.contentPadding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BalanceHeader(
|
||||
title: loc.paymentTypeLedger,
|
||||
subtitle: subtitle.isNotEmpty ? subtitle : null,
|
||||
badge: badge,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
_formatBalance(),
|
||||
style: textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
LedgerBalanceRefreshButton(
|
||||
ledgerAccountRef: account.ledgerAccountRef,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import 'package:pweb/pages/payment_methods/widgets/recipient_section.dart';
|
||||
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||
import 'package:pweb/widgets/wallet_balance_refresh_button.dart';
|
||||
import 'package:pweb/widgets/refresh_balance/wallet.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
@@ -87,7 +87,7 @@ class PaymentPageContent extends StatelessWidget {
|
||||
if (selectedWalletId == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return WalletBalanceRefreshButton(walletId: selectedWalletId);
|
||||
return WalletBalanceRefreshButton(walletRef: selectedWalletId);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:pshared/models/payment/wallet.dart';
|
||||
|
||||
import 'package:pweb/models/visibility.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
||||
import 'package:pweb/widgets/wallet_balance_refresh_button.dart';
|
||||
import 'package:pweb/widgets/refresh_balance/wallet.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
@@ -55,7 +55,7 @@ class WalletCard extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
WalletBalanceRefreshButton(
|
||||
walletId: wallet.id,
|
||||
walletRef: wallet.id,
|
||||
iconOnly: VisibilityState.hidden,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:pshared/controllers/wallets.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
||||
import 'package:pweb/widgets/wallet_balance_refresh_button.dart';
|
||||
import 'package:pweb/widgets/refresh_balance/wallet.dart';
|
||||
|
||||
|
||||
class WalletEditFields extends StatelessWidget {
|
||||
@@ -33,7 +33,7 @@ class WalletEditFields extends StatelessWidget {
|
||||
onToggleVisibility: () => controller.toggleVisibility(wallet.id),
|
||||
),
|
||||
),
|
||||
WalletBalanceRefreshButton(walletId: wallet.id),
|
||||
WalletBalanceRefreshButton(walletRef: wallet.id),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
57
frontend/pweb/lib/widgets/refresh_balance/button.dart
Normal file
57
frontend/pweb/lib/widgets/refresh_balance/button.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/models/visibility.dart';
|
||||
|
||||
|
||||
class BalanceRefreshButton extends StatelessWidget {
|
||||
final bool isBusy;
|
||||
final bool enabled;
|
||||
final VoidCallback onPressed;
|
||||
final VisibilityState iconOnly;
|
||||
final String label;
|
||||
final String tooltip;
|
||||
final double iconSize;
|
||||
|
||||
const BalanceRefreshButton({
|
||||
super.key,
|
||||
required this.isBusy,
|
||||
required this.enabled,
|
||||
required this.onPressed,
|
||||
required this.iconOnly,
|
||||
required this.label,
|
||||
required this.tooltip,
|
||||
this.iconSize = 18,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final canPress = enabled && !isBusy;
|
||||
|
||||
if (iconOnly == VisibilityState.hidden) {
|
||||
return IconButton(
|
||||
tooltip: tooltip,
|
||||
onPressed: canPress ? onPressed : null,
|
||||
icon: isBusy
|
||||
? SizedBox(
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
child: const CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
);
|
||||
}
|
||||
|
||||
return TextButton.icon(
|
||||
onPressed: canPress ? onPressed : null,
|
||||
icon: isBusy
|
||||
? SizedBox(
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
child: const CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
label: Text(label),
|
||||
style: TextButton.styleFrom(visualDensity: VisualDensity.compact),
|
||||
);
|
||||
}
|
||||
}
|
||||
46
frontend/pweb/lib/widgets/refresh_balance/ledger.dart
Normal file
46
frontend/pweb/lib/widgets/refresh_balance/ledger.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/ledger.dart';
|
||||
|
||||
import 'package:pweb/models/visibility.dart';
|
||||
import 'package:pweb/widgets/refresh_balance/button.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class LedgerBalanceRefreshButton extends StatelessWidget {
|
||||
final String ledgerAccountRef;
|
||||
final VisibilityState iconOnly;
|
||||
final double iconSize = 18;
|
||||
|
||||
const LedgerBalanceRefreshButton({
|
||||
super.key,
|
||||
required this.ledgerAccountRef,
|
||||
this.iconOnly = VisibilityState.visible,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ledgerProvider = context.watch<LedgerAccountsProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final isBusy = ledgerProvider.isWalletRefreshing(ledgerAccountRef) || ledgerProvider.isLoading;
|
||||
final hasTarget = ledgerProvider.accounts.any((a) => a.ledgerAccountRef == ledgerAccountRef);
|
||||
|
||||
void refresh() {
|
||||
final provider = context.read<LedgerAccountsProvider>();
|
||||
provider.refreshBalance(ledgerAccountRef);
|
||||
}
|
||||
|
||||
return BalanceRefreshButton(
|
||||
isBusy: isBusy,
|
||||
enabled: hasTarget,
|
||||
onPressed: refresh,
|
||||
iconOnly: iconOnly,
|
||||
label: loc.refreshBalance,
|
||||
tooltip: loc.refreshBalance,
|
||||
iconSize: iconSize,
|
||||
);
|
||||
}
|
||||
}
|
||||
46
frontend/pweb/lib/widgets/refresh_balance/wallet.dart
Normal file
46
frontend/pweb/lib/widgets/refresh_balance/wallet.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/wallets.dart';
|
||||
|
||||
import 'package:pweb/models/visibility.dart';
|
||||
import 'package:pweb/widgets/refresh_balance/button.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletBalanceRefreshButton extends StatelessWidget {
|
||||
final String walletRef;
|
||||
final VisibilityState iconOnly;
|
||||
final double iconSize = 18;
|
||||
|
||||
const WalletBalanceRefreshButton({
|
||||
super.key,
|
||||
required this.walletRef,
|
||||
this.iconOnly = VisibilityState.visible,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletsProvider = context.watch<WalletsProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final isBusy = walletsProvider.isWalletRefreshing(walletRef) || walletsProvider.isLoading;
|
||||
final hasTarget = walletsProvider.wallets.any((w) => w.id == walletRef);
|
||||
|
||||
void refresh() {
|
||||
final provider = context.read<WalletsProvider>();
|
||||
provider.refreshBalance(walletRef);
|
||||
}
|
||||
|
||||
return BalanceRefreshButton(
|
||||
isBusy: isBusy,
|
||||
enabled: hasTarget,
|
||||
onPressed: refresh,
|
||||
iconOnly: iconOnly,
|
||||
label: loc.refreshBalance,
|
||||
tooltip: loc.refreshBalance,
|
||||
iconSize: iconSize,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/wallets.dart';
|
||||
|
||||
import 'package:pweb/models/visibility.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletBalanceRefreshButton extends StatelessWidget {
|
||||
final String walletId;
|
||||
final VisibilityState iconOnly;
|
||||
final double iconSize = 18;
|
||||
|
||||
const WalletBalanceRefreshButton({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
this.iconOnly = VisibilityState.visible,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletsProvider = context.watch<WalletsProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final isBusy = walletsProvider.isWalletRefreshing(walletId) || walletsProvider.isLoading;
|
||||
final hasTarget = walletsProvider.wallets.any((w) => w.id == walletId);
|
||||
|
||||
void refresh() {
|
||||
final provider = context.read<WalletsProvider>();
|
||||
provider.refreshBalance(walletId);
|
||||
}
|
||||
|
||||
if (iconOnly == VisibilityState.hidden) {
|
||||
return IconButton(
|
||||
tooltip: loc.refreshBalance,
|
||||
onPressed: hasTarget && !isBusy ? refresh : null,
|
||||
icon: isBusy
|
||||
? SizedBox(
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
child: const CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
);
|
||||
}
|
||||
|
||||
return TextButton.icon(
|
||||
onPressed: hasTarget && !isBusy ? refresh : null,
|
||||
icon: isBusy
|
||||
? SizedBox(
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
child: const CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
label: Text(loc.refreshBalance),
|
||||
style: TextButton.styleFrom(visualDensity: VisualDensity.compact),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user