mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
Merge branch 'dev' into cw-1440-new-buy-sell-flow
This commit is contained in:
+203
-659
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ import "package:cake_wallet/exchange/exchange_trade_state.dart";
|
||||
import "package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart";
|
||||
import "package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/modern_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
@@ -27,6 +28,7 @@ import "package:cake_wallet/src/widgets/primary_button.dart";
|
||||
import "package:cake_wallet/utils/debounce.dart";
|
||||
import "package:cake_wallet/utils/payment_request.dart";
|
||||
import "package:cake_wallet/utils/show_pop_up.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/balance_view_model.dart";
|
||||
import "package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart";
|
||||
import "package:cake_wallet/view_model/exchange/exchange_view_model.dart";
|
||||
import "package:cake_wallet/view_model/wallet_switcher_view_model.dart";
|
||||
@@ -51,6 +53,7 @@ class NewSwapPage extends StatefulWidget {
|
||||
required this.walletSwitcherViewModel,
|
||||
CryptoCurrency? initialCurrency,
|
||||
this.fromSend,
|
||||
this.balanceViewModel,
|
||||
}) {
|
||||
depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr
|
||||
? exchangeViewModel.wallet.name
|
||||
@@ -69,6 +72,7 @@ class NewSwapPage extends StatefulWidget {
|
||||
final AddressResolverService adrResService;
|
||||
final PaymentRequest? initialPaymentRequest;
|
||||
final SwapFromSendArgs? fromSend;
|
||||
final BalanceViewModel? balanceViewModel;
|
||||
late final String? depositWalletName;
|
||||
late final String? receiveWalletName;
|
||||
|
||||
@@ -93,6 +97,25 @@ class _NewSwapPageState extends State<NewSwapPage> {
|
||||
.contains(widget.exchangeViewModel.depositCurrency) &&
|
||||
!(widget.exchangeViewModel.status is SyncedSyncStatus);
|
||||
|
||||
Map<CryptoCurrency, CurrencyPickerBalance>? _depositBalanceByAsset() {
|
||||
final balanceViewModel = widget.balanceViewModel;
|
||||
if (balanceViewModel == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
for (final r in balanceViewModel.formattedBalances)
|
||||
r.asset: CurrencyPickerBalance(
|
||||
amount: "${r.availableBalance} ${r.asset.title}",
|
||||
fiat: balanceViewModel.isFiatDisabled
|
||||
? null
|
||||
: "${r.fiatAvailableBalanceRaw} ${r.fiatCurrency?.symbol}",
|
||||
fiatValue:
|
||||
balanceViewModel.isFiatDisabled ? null : double.tryParse(r.fiatAvailableBalanceRaw),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -258,8 +281,8 @@ class _NewSwapPageState extends State<NewSwapPage> {
|
||||
(bool isReceiveAmountEditable) {},
|
||||
));
|
||||
|
||||
_disposers
|
||||
.add(reaction((_) => widget.exchangeViewModel.tradeState, (ExchangeTradeState state) {
|
||||
_disposers.add(
|
||||
reaction((_) => widget.exchangeViewModel.tradeState, (ExchangeTradeState state) async {
|
||||
if (state is TradeIsCreatedFailure) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
@@ -298,11 +321,15 @@ class _NewSwapPageState extends State<NewSwapPage> {
|
||||
exchangeTradeViewModel: vm,
|
||||
receiveAmount: receiveAmount,
|
||||
);
|
||||
showMaterialModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => page,
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
try {
|
||||
await showMaterialModalBottomSheet<void>(
|
||||
context: context,
|
||||
builder: (context) => page,
|
||||
backgroundColor: Colors.transparent,
|
||||
);
|
||||
} finally {
|
||||
widget.exchangeViewModel.tradeStarted = false;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -629,7 +656,8 @@ class _NewSwapPageState extends State<NewSwapPage> {
|
||||
walletName: fromSend != null
|
||||
? widget.exchangeViewModel.wallet.name
|
||||
: null,
|
||||
balanceByAsset: fromSend?.depositBalanceByAsset,
|
||||
balanceByAsset:
|
||||
fromSend != null ? _depositBalanceByAsset() : null,
|
||||
useSingleNetworkLayout: fromSend != null,
|
||||
filteredNetwork: fromSend != null
|
||||
? widget.exchangeViewModel.wallet.type
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import "package:cake_wallet/core/wallet_loading_service.dart";
|
||||
import "package:cake_wallet/store/app_store.dart";
|
||||
import "package:cw_core/exceptions.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
|
||||
class WalletSwitchService {
|
||||
WalletSwitchService({
|
||||
required WalletLoadingService walletLoadingService,
|
||||
required AppStore appStore,
|
||||
}) : _walletLoadingService = walletLoadingService,
|
||||
_appStore = appStore;
|
||||
|
||||
final AppStore _appStore;
|
||||
final WalletLoadingService _walletLoadingService;
|
||||
|
||||
Future<void> switchToWallet(WalletInfo walletInfo) async {
|
||||
final wallet = await _walletLoadingService.load(walletInfo.type, walletInfo.name);
|
||||
|
||||
// load() recovers from a corrupted wallet by opening any other wallet it can,
|
||||
// so the one it returns may not be the one that was requested.
|
||||
if (wallet.name != walletInfo.name || wallet.type != walletInfo.type) {
|
||||
throw WalletSwitchException(
|
||||
"wallet switch loaded ${wallet.name} instead of ${walletInfo.name}",
|
||||
);
|
||||
}
|
||||
|
||||
await _appStore.changeCurrentWallet(wallet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
import "package:cake_wallet/core/address_resolver/address_resolver_service.dart";
|
||||
import "package:cake_wallet/core/anypay/anypay_models.dart";
|
||||
import "package:cake_wallet/core/anypay/anypay_service.dart";
|
||||
import "package:cake_wallet/core/auth_service.dart";
|
||||
import "package:cake_wallet/di.dart";
|
||||
import "package:cake_wallet/evm/evm.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/pages/swap_page.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/anypay/evm_address_detected_sheet.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/anypay/network_decision_page.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/anypay/select_recipient_network_sheet.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/anypay/switch_network_wallet_page.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/swap_page/swap_from_send_args.dart";
|
||||
import "package:cake_wallet/reactions/wallet_connect.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
|
||||
import "package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart";
|
||||
import "package:cake_wallet/utils/show_pop_up.dart";
|
||||
import "package:cake_wallet/view_model/exchange/exchange_view_model.dart";
|
||||
import "package:cake_wallet/view_model/send/send_view_model.dart";
|
||||
import "package:cake_wallet/view_model/wallet_switcher_view_model.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/unspent_coin_type.dart";
|
||||
import "package:cw_core/utils/print_verbose.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class AnyPayFormFill {
|
||||
const AnyPayFormFill({
|
||||
required this.request,
|
||||
this.token,
|
||||
this.fallbackCurrency,
|
||||
this.amountOverride,
|
||||
});
|
||||
|
||||
final AnyPayRequest request;
|
||||
final CryptoCurrency? token;
|
||||
final CryptoCurrency? fallbackCurrency;
|
||||
final String? amountOverride;
|
||||
}
|
||||
|
||||
class AnyPayFlow {
|
||||
AnyPayFlow({
|
||||
required this.anyPayService,
|
||||
required this.sendViewModel,
|
||||
required this.authService,
|
||||
required this.walletSwitcherViewModel,
|
||||
});
|
||||
|
||||
final AnyPayService anyPayService;
|
||||
final SendViewModel sendViewModel;
|
||||
final AuthService authService;
|
||||
final WalletSwitcherViewModel walletSwitcherViewModel;
|
||||
|
||||
bool _isSwitchingWallet = false;
|
||||
|
||||
bool get isSwitchingWallet => _isSwitchingWallet;
|
||||
|
||||
Future<AnyPayFormFill?> handleEvaluation(
|
||||
BuildContext context,
|
||||
AnyPayEvaluation evaluation, {
|
||||
CryptoCurrency? fallbackCurrency,
|
||||
}) async {
|
||||
final request = evaluation.request;
|
||||
|
||||
if (!request.rawInput.toLowerCase().startsWith("ethereum:") &&
|
||||
(request.rawInput.contains("@") || request.address.contains("@"))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return await _handleDecision(context, evaluation, fallbackCurrency);
|
||||
} catch (e) {
|
||||
printV("Payment flow error: $e");
|
||||
return AnyPayFormFill(request: request);
|
||||
}
|
||||
}
|
||||
|
||||
Future<AnyPayFormFill?> _handleDecision(
|
||||
BuildContext context,
|
||||
AnyPayEvaluation evaluation,
|
||||
CryptoCurrency? fallbackCurrency,
|
||||
) async {
|
||||
final request = evaluation.request;
|
||||
|
||||
switch (evaluation.decision) {
|
||||
case final AnyPayApplyToCurrentWallet decision:
|
||||
if (request.isLightning) {
|
||||
_enterLightningMode();
|
||||
}
|
||||
return AnyPayFormFill(
|
||||
request: request,
|
||||
token: decision.token,
|
||||
fallbackCurrency: fallbackCurrency ?? decision.fallbackCurrency,
|
||||
amountOverride: decision.amountOverride,
|
||||
);
|
||||
case AnyPayEvmNetworkChoice():
|
||||
return _pickEvmNetwork(context, request);
|
||||
case final AnyPayCrossChainPayment decision:
|
||||
return _handleCrossChain(context, request, decision, fallbackCurrency);
|
||||
case final AnyPayUnsupportedNetwork decision:
|
||||
_showError(
|
||||
context,
|
||||
S.of(context).unsupported_network_requested(decision.chainId.toString()),
|
||||
);
|
||||
return null;
|
||||
case AnyPayUnsupportedToken():
|
||||
_showError(context, S.of(context).unsupported_token_requested);
|
||||
return null;
|
||||
case AnyPayEmptyInput():
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<AnyPayFormFill?> _pickEvmNetwork(BuildContext context, AnyPayRequest request) async {
|
||||
final chains = evm!.getAllChains();
|
||||
final networks = chains.map((chain) {
|
||||
final walletType = evm!.getWalletTypeByChainId(chain.chainId) ?? sendViewModel.wallet.type;
|
||||
return RecipientNetworkItem(
|
||||
chainId: chain.chainId,
|
||||
name: chain.name,
|
||||
iconPath: getCryptoCurrencyIconForWalletListItem(walletType, chainId: chain.chainId),
|
||||
chainBadgeIconPath: symbolIconPathForWalletType(walletType),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
final selectedChainId =
|
||||
await EvmAddressDetectedSheet.show(context: context, networks: networks);
|
||||
if (!context.mounted || selectedChainId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final target = chains.firstWhere(
|
||||
(chain) => chain.chainId == selectedChainId,
|
||||
orElse: () => chains.first,
|
||||
);
|
||||
|
||||
final evaluation = await anyPayService.evaluateForEvmChain(request, target.chainId);
|
||||
if (!context.mounted) {
|
||||
return null;
|
||||
}
|
||||
return handleEvaluation(context, evaluation, fallbackCurrency: target.currency);
|
||||
}
|
||||
|
||||
Future<AnyPayFormFill?> _handleCrossChain(
|
||||
BuildContext context,
|
||||
AnyPayRequest request,
|
||||
AnyPayCrossChainPayment decision,
|
||||
CryptoCurrency? fallbackCurrency,
|
||||
) async {
|
||||
final destinationType = decision.targetWalletType;
|
||||
final isEvmTarget = isEVMCompatibleChain(destinationType);
|
||||
final destinationNetworkName = networkDisplayName(destinationType, decision.targetChainId);
|
||||
final destinationNetworkIcon = symbolIconPathForWalletType(destinationType) ?? "";
|
||||
|
||||
if (!decision.canSwap && !decision.hasCompatibleWallet) {
|
||||
_showError(
|
||||
context,
|
||||
S.of(context).swap_unavailable_needs_network_wallet(destinationNetworkName),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final currentType = sendViewModel.wallet.type;
|
||||
final currentChainId = isEVMCompatibleChain(currentType) ? _currentEvmChainId() : null;
|
||||
final currentNetworkName = networkDisplayName(currentType, currentChainId);
|
||||
final currentNetworkIcon = symbolIconPathForWalletType(currentType) ?? "";
|
||||
|
||||
if (!decision.hasCompatibleWallet) {
|
||||
final swapConfirmTitle = isEvmTarget
|
||||
? S.of(context).swap_from_network(currentNetworkName)
|
||||
: S.of(context).network_address_detected(destinationNetworkName);
|
||||
final swapConfirmPrimary = isEvmTarget
|
||||
? S.of(context).continue_to_swap
|
||||
: S.of(context).swap_from_network(currentNetworkName);
|
||||
|
||||
await Navigator.of(context).push<void>(
|
||||
CupertinoPageRoute(
|
||||
builder: (pageContext) => NetworkDecisionPage(
|
||||
title: swapConfirmTitle,
|
||||
description: S.of(context).swap_from_network_description(
|
||||
destinationNetworkName,
|
||||
currentNetworkName,
|
||||
),
|
||||
destinationIconPath: destinationNetworkIcon,
|
||||
currentIconPath: currentNetworkIcon,
|
||||
primaryText: swapConfirmPrimary,
|
||||
primaryIconPath: isEvmTarget ? null : "assets/new-ui/swap_arrows.svg",
|
||||
onPrimary: () => _openSwap(pageContext, request, decision),
|
||||
secondaryText: S.of(context).cancel,
|
||||
),
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final decisionTitle = isEvmTarget
|
||||
? S.of(context).send_to_network(destinationNetworkName)
|
||||
: S.of(context).network_address_detected(destinationNetworkName);
|
||||
|
||||
final selectedWallet = await Navigator.of(context).push<WalletInfo>(
|
||||
CupertinoPageRoute(
|
||||
builder: (pageContext) => NetworkDecisionPage(
|
||||
title: decisionTitle,
|
||||
description: decision.canSwap
|
||||
? S.of(context).send_to_network_description(
|
||||
destinationNetworkName,
|
||||
currentNetworkName,
|
||||
)
|
||||
: S.of(context).swap_unavailable_switch_to_network(destinationNetworkName),
|
||||
destinationIconPath: destinationNetworkIcon,
|
||||
primaryText: S.of(context).switch_to_x_wallet(destinationNetworkName),
|
||||
primaryIconPath: "assets/new-ui/wallet_filled.svg",
|
||||
onPrimary: () => _pickDestinationWallet(pageContext, decision, destinationNetworkName),
|
||||
secondaryText: decision.canSwap
|
||||
? S.of(context).swap_from_network(currentNetworkName)
|
||||
: S.of(context).cancel,
|
||||
secondaryIconPath: decision.canSwap ? "assets/new-ui/swap_arrows.svg" : null,
|
||||
onSecondary: decision.canSwap ? () => _openSwap(pageContext, request, decision) : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (selectedWallet == null || !context.mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return _switchWallet(context, selectedWallet, request, decision, fallbackCurrency);
|
||||
}
|
||||
|
||||
Future<void> _pickDestinationWallet(
|
||||
BuildContext pageContext,
|
||||
AnyPayCrossChainPayment decision,
|
||||
String networkName,
|
||||
) async {
|
||||
WalletInfo? destinationWalletInfo;
|
||||
if (decision.wallets.length > 1) {
|
||||
destinationWalletInfo = await SwitchNetworkWalletPage.push(
|
||||
context: pageContext,
|
||||
networkName: networkName,
|
||||
targetIconPath: symbolIconPathForWalletType(decision.targetWalletType) ?? "",
|
||||
wallets: decision.wallets,
|
||||
);
|
||||
} else {
|
||||
destinationWalletInfo = decision.wallets.first;
|
||||
}
|
||||
|
||||
if (destinationWalletInfo == null || !pageContext.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(pageContext).pop(destinationWalletInfo);
|
||||
}
|
||||
|
||||
Future<AnyPayFormFill?> _switchWallet(
|
||||
BuildContext context,
|
||||
WalletInfo walletInfo,
|
||||
AnyPayRequest request,
|
||||
AnyPayCrossChainPayment decision,
|
||||
CryptoCurrency? fallbackCurrency,
|
||||
) async {
|
||||
if (request.isLightning) {
|
||||
_enterLightningMode();
|
||||
}
|
||||
|
||||
_isSwitchingWallet = true;
|
||||
BuildContext? loadingSheetContext;
|
||||
bool completedFlow = false;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!context.mounted || completedFlow) {
|
||||
return;
|
||||
}
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
builder: (sheetContext) {
|
||||
if (completedFlow) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (Navigator.canPop(sheetContext)) {
|
||||
Navigator.of(sheetContext).pop();
|
||||
}
|
||||
});
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
loadingSheetContext = sheetContext;
|
||||
return LoadingBottomSheet(titleText: S.of(sheetContext).loading_your_wallet);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
bool success = false;
|
||||
try {
|
||||
success = await anyPayService.switchWalletForPayment(
|
||||
walletInfo,
|
||||
chainId: decision.targetChainId,
|
||||
);
|
||||
} finally {
|
||||
_isSwitchingWallet = false;
|
||||
completedFlow = true;
|
||||
if (loadingSheetContext != null &&
|
||||
loadingSheetContext!.mounted &&
|
||||
Navigator.canPop(loadingSheetContext!)) {
|
||||
Navigator.of(loadingSheetContext!).pop();
|
||||
}
|
||||
loadingSheetContext = null;
|
||||
}
|
||||
|
||||
if (!context.mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
_showError(context, S.of(context).network_switch_failed);
|
||||
return null;
|
||||
}
|
||||
|
||||
return AnyPayFormFill(
|
||||
request: request,
|
||||
token: decision.token,
|
||||
fallbackCurrency: fallbackCurrency ?? request.detection.detectedCurrency,
|
||||
amountOverride: decision.amountOverride,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openSwap(
|
||||
BuildContext presentContext,
|
||||
AnyPayRequest request,
|
||||
AnyPayCrossChainPayment decision,
|
||||
) async {
|
||||
if (!presentContext.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.hasContract && decision.token == null) {
|
||||
_showError(presentContext, S.of(presentContext).unsupported_token_requested);
|
||||
return;
|
||||
}
|
||||
|
||||
final intent = anyPayService.buildSwapIntent(
|
||||
request,
|
||||
targetWalletType: decision.targetWalletType,
|
||||
targetChainId: decision.targetChainId,
|
||||
token: decision.token,
|
||||
);
|
||||
|
||||
final page = NewSwapPage(
|
||||
getIt.get<ExchangeViewModel>(),
|
||||
authService,
|
||||
getIt.get<AddressResolverService>(),
|
||||
null,
|
||||
walletSwitcherViewModel: walletSwitcherViewModel,
|
||||
fromSend: SwapFromSendArgs.fromIntent(intent),
|
||||
balanceViewModel: sendViewModel.balanceViewModel,
|
||||
);
|
||||
await Navigator.of(presentContext).push<void>(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: SafeArea(bottom: false, child: page),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showError(BuildContext context, String message) {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: message,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _enterLightningMode() {
|
||||
sendViewModel.selectedCryptoCurrency = CryptoCurrency.btcln;
|
||||
sendViewModel.coinTypeToSpendFrom = UnspentCoinType.lightning;
|
||||
}
|
||||
|
||||
int _currentEvmChainId() {
|
||||
final wallet = sendViewModel.wallet;
|
||||
return evm!.getSelectedChainId(wallet) ?? evm!.getChainIdByWalletType(wallet.type);
|
||||
}
|
||||
}
|
||||
+87
-53
@@ -5,31 +5,37 @@ import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class SwapFromNetworkPage extends StatelessWidget {
|
||||
const SwapFromNetworkPage({
|
||||
class NetworkDecisionPage extends StatelessWidget {
|
||||
const NetworkDecisionPage({
|
||||
required this.title,
|
||||
required this.primaryButtonText,
|
||||
required this.destinationNetworkName,
|
||||
required this.destinationNetworkIconPath,
|
||||
required this.currentNetworkName,
|
||||
required this.currentNetworkIconPath,
|
||||
required this.onProceed,
|
||||
required this.description,
|
||||
required this.destinationIconPath,
|
||||
required this.primaryText,
|
||||
required this.onPrimary,
|
||||
required this.secondaryText,
|
||||
this.currentIconPath,
|
||||
this.primaryIconPath,
|
||||
this.secondaryIconPath,
|
||||
this.onSecondary,
|
||||
super.key,
|
||||
this.primaryHasSwapIcon = false,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String primaryButtonText;
|
||||
final bool primaryHasSwapIcon;
|
||||
final String destinationNetworkName;
|
||||
final String destinationNetworkIconPath;
|
||||
final String currentNetworkName;
|
||||
final String currentNetworkIconPath;
|
||||
final VoidCallback onProceed;
|
||||
final String description;
|
||||
final String destinationIconPath;
|
||||
final String? currentIconPath;
|
||||
final String primaryText;
|
||||
final VoidCallback onPrimary;
|
||||
final String? primaryIconPath;
|
||||
final String secondaryText;
|
||||
final String? secondaryIconPath;
|
||||
final VoidCallback? onSecondary;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final primaryIcon = primaryIconPath;
|
||||
final secondaryIcon = secondaryIconPath;
|
||||
return Material(
|
||||
color: colors.surface,
|
||||
child: SafeArea(
|
||||
@@ -40,9 +46,6 @@ class SwapFromNetworkPage extends StatelessWidget {
|
||||
leadingIcon: const Icon(Icons.arrow_back_ios_new),
|
||||
leadingSemanticLabel: S.of(context).seed_alert_back,
|
||||
onLeadingPressed: () => Navigator.of(context).maybePop(),
|
||||
trailingIcon: const Icon(Icons.close),
|
||||
trailingSemanticLabel: S.of(context).close,
|
||||
onTrailingPressed: () => Navigator.of(context).maybePop(),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
@@ -51,30 +54,11 @@ class SwapFromNetworkPage extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: currentNetworkIconPath,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.contain,
|
||||
color: isMonochromeSymbolIcon(currentNetworkIconPath)
|
||||
? colors.primary
|
||||
: null,
|
||||
),
|
||||
Icon(Icons.arrow_forward, color: colors.primary, size: 28),
|
||||
CakeImageWidget(
|
||||
imageUrl: destinationNetworkIconPath,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.contain,
|
||||
color: isMonochromeSymbolIcon(destinationNetworkIconPath)
|
||||
? colors.primary
|
||||
: null,
|
||||
),
|
||||
],
|
||||
ExcludeSemantics(
|
||||
child: _DecisionHeader(
|
||||
destinationIconPath: destinationIconPath,
|
||||
currentIconPath: currentIconPath,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
@@ -88,10 +72,7 @@ class SwapFromNetworkPage extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
S.of(context).swap_from_network_description(
|
||||
destinationNetworkName,
|
||||
currentNetworkName,
|
||||
),
|
||||
description,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
@@ -109,23 +90,31 @@ class SwapFromNetworkPage extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
NewPrimaryButton(
|
||||
onPressed: onProceed,
|
||||
image: primaryHasSwapIcon
|
||||
onPressed: onPrimary,
|
||||
image: primaryIcon != null
|
||||
? CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/swap_arrows.svg",
|
||||
imageUrl: primaryIcon,
|
||||
width: 24,
|
||||
height: 24,
|
||||
colorFilter: ColorFilter.mode(colors.onPrimary, BlendMode.srcIn),
|
||||
)
|
||||
: null,
|
||||
text: primaryButtonText,
|
||||
text: primaryText,
|
||||
color: colors.primary,
|
||||
textColor: colors.onPrimary,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
NewPrimaryButton(
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
text: S.of(context).cancel,
|
||||
onPressed: onSecondary ?? () => Navigator.of(context).maybePop(),
|
||||
image: secondaryIcon != null
|
||||
? CakeImageWidget(
|
||||
imageUrl: secondaryIcon,
|
||||
width: 24,
|
||||
height: 24,
|
||||
colorFilter: ColorFilter.mode(colors.primary, BlendMode.srcIn),
|
||||
)
|
||||
: null,
|
||||
text: secondaryText,
|
||||
color: colors.surfaceContainer,
|
||||
textColor: colors.primary,
|
||||
),
|
||||
@@ -138,3 +127,48 @@ class SwapFromNetworkPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DecisionHeader extends StatelessWidget {
|
||||
const _DecisionHeader({required this.destinationIconPath, this.currentIconPath});
|
||||
|
||||
final String destinationIconPath;
|
||||
final String? currentIconPath;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final current = currentIconPath;
|
||||
|
||||
if (current == null) {
|
||||
return CakeImageWidget(
|
||||
imageUrl: destinationIconPath,
|
||||
width: 75,
|
||||
height: 75,
|
||||
fit: BoxFit.contain,
|
||||
color: isMonochromeSymbolIcon(destinationIconPath) ? colors.primary : null,
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: current,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.contain,
|
||||
color: isMonochromeSymbolIcon(current) ? colors.primary : null,
|
||||
),
|
||||
Icon(Icons.arrow_forward, color: colors.primary, size: 28),
|
||||
CakeImageWidget(
|
||||
imageUrl: destinationIconPath,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.contain,
|
||||
color: isMonochromeSymbolIcon(destinationIconPath) ? colors.primary : null,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,14 @@
|
||||
import "package:cake_wallet/evm/evm.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/utils/print_verbose.dart";
|
||||
import "package:cw_core/wallet_base.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class RecipientNetworkSelector extends StatelessWidget {
|
||||
const RecipientNetworkSelector({
|
||||
super.key,
|
||||
required this.wallet,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final WalletBase wallet;
|
||||
final void Function(ChainInfo currentChain) onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (evm == null) return const SizedBox.shrink();
|
||||
|
||||
final currentChainId =
|
||||
evm!.getSelectedChainId(wallet) ?? evm!.getChainIdByWalletType(wallet.type);
|
||||
final currentChain = evm!.getChainInfoByChainId(currentChainId);
|
||||
if (currentChain == null) {
|
||||
printV("RecipientNetworkSelector: no chain info for chainId $currentChainId");
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return _RecipientNetworkRow(
|
||||
networkName: currentChain.name,
|
||||
networkIconPath: symbolIconPathForWalletType(wallet.type) ?? "",
|
||||
onTap: () => onTap(currentChain),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RecipientNetworkRow extends StatelessWidget {
|
||||
const _RecipientNetworkRow({
|
||||
required this.networkName,
|
||||
required this.networkIconPath,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String networkName;
|
||||
@@ -50,50 +18,56 @@ class _RecipientNetworkRow extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).network_prefix_on,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: colors.onSurfaceVariant, letterSpacing: -0.07),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
CakeImageWidget(
|
||||
imageUrl: networkIconPath,
|
||||
width: 16,
|
||||
height: 16,
|
||||
fit: BoxFit.cover,
|
||||
color: isMonochromeSymbolIcon(networkIconPath) ? colors.primary : null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
networkName,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: -0.07,
|
||||
color: colors.primary,
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: "${S.of(context).network_prefix_on} $networkName",
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: ExcludeSemantics(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).network_prefix_on,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: colors.onSurfaceVariant, letterSpacing: -0.07),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
CakeImageWidget(
|
||||
imageUrl: networkIconPath,
|
||||
width: 16,
|
||||
height: 16,
|
||||
fit: BoxFit.cover,
|
||||
color: isMonochromeSymbolIcon(networkIconPath) ? colors.primary : null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
networkName,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: -0.07,
|
||||
color: colors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainer,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
size: 18,
|
||||
color: colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainer,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
size: 18,
|
||||
color: colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class SendToNetworkPage extends StatelessWidget {
|
||||
const SendToNetworkPage({
|
||||
required this.title,
|
||||
required this.destinationNetworkName,
|
||||
required this.destinationIconPath,
|
||||
required this.currentNetworkName,
|
||||
required this.onSwitchWallet,
|
||||
required this.onSwap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String destinationNetworkName;
|
||||
final String destinationIconPath;
|
||||
final String currentNetworkName;
|
||||
final VoidCallback onSwitchWallet;
|
||||
final VoidCallback onSwap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Material(
|
||||
color: colors.surface,
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: "",
|
||||
leadingIcon: const Icon(Icons.arrow_back_ios_new),
|
||||
leadingSemanticLabel: S.of(context).seed_alert_back,
|
||||
onLeadingPressed: () => Navigator.of(context).maybePop(),
|
||||
trailingIcon: const Icon(Icons.close),
|
||||
trailingSemanticLabel: S.of(context).close,
|
||||
onTrailingPressed: () => Navigator.of(context).maybePop(),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: destinationIconPath,
|
||||
width: 75,
|
||||
height: 75,
|
||||
fit: BoxFit.contain,
|
||||
color: isMonochromeSymbolIcon(destinationIconPath) ? colors.primary : null,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: -0.1,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
S.of(context).send_to_network_description(
|
||||
destinationNetworkName,
|
||||
currentNetworkName,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: colors.onSurfaceVariant, letterSpacing: -0.07),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
NewPrimaryButton(
|
||||
onPressed: onSwitchWallet,
|
||||
image: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/wallet_filled.svg",
|
||||
width: 24,
|
||||
height: 24,
|
||||
colorFilter: ColorFilter.mode(colors.onPrimary, BlendMode.srcIn),
|
||||
),
|
||||
text: S.of(context).switch_to_x_wallet(destinationNetworkName),
|
||||
color: colors.primary,
|
||||
textColor: colors.onPrimary,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
NewPrimaryButton(
|
||||
onPressed: onSwap,
|
||||
image: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/swap_arrows.svg",
|
||||
width: 24,
|
||||
height: 24,
|
||||
colorFilter: ColorFilter.mode(colors.primary, BlendMode.srcIn),
|
||||
),
|
||||
text: S.of(context).swap_from_network(currentNetworkName),
|
||||
color: colors.surfaceContainer,
|
||||
textColor: colors.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -181,23 +181,28 @@ class ChangelogItemWidget extends StatelessWidget {
|
||||
width: 36,
|
||||
colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 4,
|
||||
children: [
|
||||
if (item.title.isNotEmpty)
|
||||
Text(
|
||||
item.title,
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
if (item.description.isNotEmpty)
|
||||
Text(item.description,
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 4,
|
||||
children: [
|
||||
if (item.title.isNotEmpty)
|
||||
Text(
|
||||
item.title,
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
if (item.description.isNotEmpty)
|
||||
Text(
|
||||
item.description,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
))
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,8 @@ class CoinActionRow extends StatelessWidget {
|
||||
|
||||
if (code == null || code.isEmpty) return;
|
||||
|
||||
late final PaymentRequest req;
|
||||
PaymentRequest? req;
|
||||
String? rawInput;
|
||||
var unspentCoinType = UnspentCoinType.any;
|
||||
if (SendViewModelBase.isNonZeroAmountLightningInvoice(code)) {
|
||||
unspentCoinType = UnspentCoinType.lightning;
|
||||
@@ -172,8 +173,6 @@ class CoinActionRow extends StatelessWidget {
|
||||
unspentCoinType = UnspentCoinType.lightning;
|
||||
final amount = (await LNURL.getPayRequestAmount(code))?.toString() ?? "0";
|
||||
req = PaymentRequest(code, amount, "", "", "");
|
||||
} else if (OpenCryptoPayService.isOpenCryptoPayQR(code)) {
|
||||
req = PaymentRequest(code, "", "", "", "");
|
||||
} else if (Uri.tryParse(code)?.scheme == "wc") {
|
||||
if (!isWalletConnectCompatibleChain(walletType)) {
|
||||
showPopUp<void>(
|
||||
@@ -190,19 +189,19 @@ class CoinActionRow extends StatelessWidget {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.walletConnectConnectionsListing, arguments: Uri.parse(code));
|
||||
return;
|
||||
} else if (["http", "https", "tcp"].contains(Uri.tryParse(code)?.scheme)) {
|
||||
} else if (!OpenCryptoPayService.isOpenCryptoPayQR(code) &&
|
||||
["http", "https", "tcp"].contains(Uri.tryParse(code)?.scheme)) {
|
||||
Navigator.of(context).pushNamed(Routes.newNode,
|
||||
arguments: {"editingNode": Node.fromUri(Uri.parse(code), walletType)});
|
||||
return;
|
||||
} else {
|
||||
final uri = Uri.tryParse(code);
|
||||
if (uri == null) return;
|
||||
req = PaymentRequest.fromUri(uri);
|
||||
rawInput = code;
|
||||
}
|
||||
|
||||
final sendPage = getIt.get<NewSendPage>(
|
||||
param1: SendPageParams(
|
||||
initialPaymentRequest: req,
|
||||
initialRawInput: rawInput,
|
||||
unspentCoinType: unspentCoinType,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -10,18 +10,20 @@ class ChainIcon extends StatelessWidget {
|
||||
{super.key,
|
||||
required this.iconPath,
|
||||
required this.dashboardViewModel,
|
||||
required this.isSyncHeavy});
|
||||
required this.isSyncHeavy,
|
||||
required this.showSyncedMessage});
|
||||
|
||||
final String iconPath;
|
||||
final bool isSyncHeavy;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final bool showSyncedMessage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
final progress = dashboardViewModel.status.progress();
|
||||
final done = !isSyncHeavy || progress >= 1;
|
||||
final done = !showSyncedMessage && (!isSyncHeavy || progress >= 1);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
@@ -30,10 +32,10 @@ class ChainIcon extends StatelessWidget {
|
||||
opacity: done ? 0 : 1,
|
||||
// Faded out means "nothing to report", so it must leave the tree too.
|
||||
child: ExcludeSemantics(
|
||||
excluding: done,
|
||||
excluding: done || showSyncedMessage,
|
||||
child: CircularProgressIndicator(
|
||||
value: progress,
|
||||
color: Color(0xFFFFB84E),
|
||||
color: showSyncedMessage ? Color(0xFFFF12A439) : Color(0xFFFFB84E),
|
||||
strokeWidth: 2,
|
||||
semanticsLabel: S.of(context).synchronizing,
|
||||
semanticsValue: "${(progress * 100).round()}%",
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import 'package:cake_wallet/core/sync_status_title.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/pulsing_dot.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import "package:flutter/cupertino.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class SyncBar extends StatelessWidget {
|
||||
SyncBar({super.key, required this.dashboardViewModel, required this.isSyncHeavy});
|
||||
SyncBar({
|
||||
super.key,
|
||||
required this.dashboardViewModel,
|
||||
required this.isSyncHeavy,
|
||||
required this.showSyncedMessage,
|
||||
});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final bool isSyncHeavy;
|
||||
final bool showSyncedMessage;
|
||||
|
||||
static const failStatuses = [
|
||||
FailedSyncStatus,
|
||||
@@ -37,9 +42,10 @@ class SyncBar extends StatelessWidget {
|
||||
ConnectedSyncStatus,
|
||||
];
|
||||
|
||||
static const syncedColor = Color(0xFF12A439);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
Widget build(BuildContext context) => Observer(
|
||||
builder: (_) {
|
||||
final status = dashboardViewModel.status;
|
||||
final Widget? icon = _getIcon(context, status.runtimeType);
|
||||
@@ -106,7 +112,6 @@ class SyncBar extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _openNodeManagement(BuildContext context) =>
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
@@ -130,8 +135,9 @@ class SyncBar extends StatelessWidget {
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
if (_showDot()) PulsingDot(),
|
||||
],
|
||||
if (_showDot()) const CupertinoActivityIndicator(radius: 8),
|
||||
if(_showLightSyncCheck()) const Icon(Icons.check, color: syncedColor, size: 18),
|
||||
],
|
||||
);
|
||||
|
||||
final label = _joinLabels([
|
||||
@@ -144,6 +150,9 @@ class SyncBar extends StatelessWidget {
|
||||
return Semantics(label: label, child: ExcludeSemantics(child: row));
|
||||
}
|
||||
|
||||
bool get _isShowingSyncedMessage =>
|
||||
showSyncedMessage && dashboardViewModel.status.runtimeType == SyncedSyncStatus;
|
||||
|
||||
String _statusSemanticsLabel(BuildContext context, SyncStatus status) {
|
||||
final isFailure = failStatuses.contains(status.runtimeType);
|
||||
|
||||
@@ -166,7 +175,7 @@ class SyncBar extends StatelessWidget {
|
||||
}
|
||||
|
||||
Border? _getBorder(BuildContext context, Type status) {
|
||||
if (progressStatuses.contains(status)) {
|
||||
if (progressStatuses.contains(status) || _isShowingSyncedMessage) {
|
||||
return Border.all(color: Theme.of(context).colorScheme.surfaceContainerHigh, width: 1);
|
||||
}
|
||||
|
||||
@@ -174,19 +183,26 @@ class SyncBar extends StatelessWidget {
|
||||
}
|
||||
|
||||
TextStyle? _getTextStyle(BuildContext context, Type status) {
|
||||
final Color color;
|
||||
if (failStatuses.contains(status)) {
|
||||
return TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.w400, color: Theme.of(context).colorScheme.error);
|
||||
color = Theme.of(context).colorScheme.error;
|
||||
} else if (status == SyncedSyncStatus) {
|
||||
color = syncedColor;
|
||||
} else {
|
||||
return TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant);
|
||||
color = Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
}
|
||||
|
||||
return TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.w400, color: color);
|
||||
}
|
||||
|
||||
Widget? _getIcon(BuildContext context, Type status) {
|
||||
if (status == LostConnectionSyncStatus || status == FailedSyncStatus) {
|
||||
|
||||
if(status == SyncedSyncStatus) {
|
||||
return Icon(Icons.check, color: syncedColor, size: 12);
|
||||
}
|
||||
|
||||
if (status == LostConnectionSyncStatus) {
|
||||
return CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/offline.svg",
|
||||
colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.error, BlendMode.srcIn),
|
||||
@@ -228,11 +244,14 @@ class SyncBar extends StatelessWidget {
|
||||
}
|
||||
|
||||
bool _showFullBar() {
|
||||
if (dashboardViewModel.status.runtimeType == SyncedSyncStatus) return false;
|
||||
if (dashboardViewModel.status.runtimeType == SyncedSyncStatus)
|
||||
return isSyncHeavy && _isShowingSyncedMessage;
|
||||
return isSyncHeavy || failStatuses.contains(dashboardViewModel.status.runtimeType);
|
||||
}
|
||||
|
||||
bool _showDot() {
|
||||
return !isSyncHeavy && progressStatuses.contains(dashboardViewModel.status.runtimeType);
|
||||
}
|
||||
bool _showDot() =>
|
||||
!isSyncHeavy && progressStatuses.contains(dashboardViewModel.status.runtimeType);
|
||||
|
||||
bool _showLightSyncCheck() =>
|
||||
!isSyncHeavy && _isShowingSyncedMessage;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
@@ -6,11 +7,13 @@ import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/lightning_s
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/sync_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class TopBar extends StatelessWidget {
|
||||
class TopBar extends StatefulWidget {
|
||||
const TopBar({
|
||||
super.key,
|
||||
required this.lightningMode,
|
||||
@@ -25,33 +28,76 @@ class TopBar extends StatelessWidget {
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
State<TopBar> createState() => _TopBarState();
|
||||
}
|
||||
|
||||
class _TopBarState extends State<TopBar> {
|
||||
static const syncedMessageDuration = Duration(seconds: 3);
|
||||
|
||||
bool showSyncedMessage = false;
|
||||
Timer? syncedMessageTimer;
|
||||
late final ReactionDisposer? _statusReactionDisposer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_statusReactionDisposer = reaction(
|
||||
(_) => widget.dashboardViewModel.status.runtimeType,
|
||||
(status) {
|
||||
syncedMessageTimer?.cancel();
|
||||
|
||||
if (status == SyncedSyncStatus) {
|
||||
if(mounted) {
|
||||
setState(() => showSyncedMessage = true);
|
||||
}
|
||||
syncedMessageTimer =
|
||||
Timer(syncedMessageDuration, () => setState(() => showSyncedMessage = false));
|
||||
} else {
|
||||
if(mounted) {
|
||||
setState(() => showSyncedMessage = false);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
syncedMessageTimer?.cancel();
|
||||
_statusReactionDisposer?.reaction.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 10, left: 18, right: 18, top: 10 + _additionalTopPadding(context)),
|
||||
child: Observer(
|
||||
builder: (_) => Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
(dashboardViewModel.hasLightning)
|
||||
(widget.dashboardViewModel.hasLightning)
|
||||
? LightningSwitcher(
|
||||
lightningMode: lightningMode,
|
||||
onLightningSwitchPress: onLightningSwitchPress,
|
||||
lightningMode: widget.lightningMode,
|
||||
onLightningSwitchPress: widget.onLightningSwitchPress,
|
||||
)
|
||||
: ChainIcon(
|
||||
iconPath: dashboardViewModel.wallet.currency.flatIconPath ?? "",
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
isSyncHeavy: dashboardViewModel.isSyncHeavy),
|
||||
iconPath: widget.dashboardViewModel.wallet.currency.flatIconPath ?? "",
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage),
|
||||
SyncBar(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
isSyncHeavy: dashboardViewModel.isSyncHeavy,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
),
|
||||
ModernButton.svg(
|
||||
iconColor: Theme.of(context).colorScheme.primary,
|
||||
size: 36,
|
||||
onPressed: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
onSettingsButtonPress();
|
||||
widget.onSettingsButtonPress();
|
||||
},
|
||||
svgPath: "assets/new-ui/top-settings.svg",
|
||||
semanticLabel: S.of(context).settings_title,
|
||||
@@ -60,7 +106,6 @@ class TopBar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
//FIXME remove after this gets fixed flutter-side
|
||||
double _additionalTopPadding(BuildContext context) {
|
||||
|
||||
@@ -18,6 +18,22 @@ class AnyPaySwapFooter extends StatelessWidget {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
if (exchangeViewModel.noProviderForPair) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: colors.error),
|
||||
),
|
||||
child: Text(
|
||||
S.of(context).no_providers_available,
|
||||
style: textTheme.bodyMedium?.copyWith(color: colors.error, letterSpacing: -0.07),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (exchangeViewModel.depositAmount.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import "package:cake_wallet/new-ui/widgets/send_page/fiat_amount_bar.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/send_page/send_memo_input.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/swap_page/refund_address_modal.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/swap_page/swap_address_selection_modal.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/swap_page/swap_source_selector.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/utils/decimal_input_formatter.dart";
|
||||
import "package:cake_wallet/utils/permission_handler.dart";
|
||||
@@ -82,10 +83,14 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
|
||||
final fiatAmountController = TextEditingController();
|
||||
final amountFocusNode = FocusNode();
|
||||
final memoController = TextEditingController();
|
||||
final _amountFieldKey = GlobalKey<FormFieldState<String>>();
|
||||
mobx.ReactionDisposer? _memoReactionDisposer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
amountController
|
||||
.addListener(() => _amountFieldKey.currentState?.didChange(amountController.text));
|
||||
|
||||
if (widget.isReceiverCard) {
|
||||
memoController.text = widget.exchangeViewModel.receiveAddressExtraId;
|
||||
|
||||
@@ -145,95 +150,13 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
if (widget.sourceSelectorMode) {
|
||||
final available = _sourceBalance()?.amount.split(" ").first ?? "—";
|
||||
return Column(
|
||||
spacing: 12,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: _presentCurrencyPicker,
|
||||
child: Container(
|
||||
height: 52,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: widget.currency.iconPath ?? "",
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
currencyToShow,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(letterSpacing: -0.08),
|
||||
),
|
||||
if (chainIconPath != null && chainIconPath.isNotEmpty) ...[
|
||||
const SizedBox(width: 4),
|
||||
CakeImageWidget(
|
||||
imageUrl: chainIconPath,
|
||||
width: 12,
|
||||
height: 12,
|
||||
colorFilter: ColorFilter.mode(colors.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/chooser.svg",
|
||||
width: 12,
|
||||
height: 12,
|
||||
colorFilter: ColorFilter.mode(colors.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/wallet_filled.svg",
|
||||
width: 16,
|
||||
height: 16,
|
||||
colorFilter: ColorFilter.mode(colors.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.walletName ?? "",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: -0.06,
|
||||
color: colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"${S.of(context).avl}: $available",
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: -0.06,
|
||||
color: colors.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
return SwapSourceSelector(
|
||||
currencyIconPath: widget.currency.iconPath ?? "",
|
||||
currencyLabel: currencyToShow,
|
||||
chainIconPath: chainIconPath,
|
||||
walletName: widget.walletName,
|
||||
availableBalance: _sourceBalance()?.amount.split(" ").first ?? "—",
|
||||
onTap: _presentCurrencyPicker,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,139 +183,168 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: amountFocusNode.requestFocus,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 4,
|
||||
FormField<String>(
|
||||
key: _amountFieldKey,
|
||||
initialValue: amountController.text,
|
||||
validator: _fiatInputMode ? null : widget.currencyValueValidator,
|
||||
builder: (state) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: amountFocusNode.requestFocus,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 4,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: IntrinsicWidth(
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final showFetching = !widget.isReceiverCard &&
|
||||
widget.exchangeViewModel.isFixedRateMode &&
|
||||
widget.exchangeViewModel.receiveAmount.isNotEmpty &&
|
||||
widget.exchangeViewModel.depositAmount.isEmpty;
|
||||
return TextFormField(
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false,
|
||||
decimal: !widget.useBaseUnit,
|
||||
Expanded(
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: IntrinsicWidth(
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final showFetching = !widget.isReceiverCard &&
|
||||
widget.exchangeViewModel.isFixedRateMode &&
|
||||
widget.exchangeViewModel.receiveAmount.isNotEmpty &&
|
||||
widget.exchangeViewModel.depositAmount.isEmpty;
|
||||
return TextField(
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false,
|
||||
decimal: !widget.useBaseUnit,
|
||||
),
|
||||
onChanged: state.didChange,
|
||||
controller: _fiatInputMode
|
||||
? fiatAmountController
|
||||
: amountController,
|
||||
focusNode: amountFocusNode,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true,
|
||||
hintText: showFetching ? S.of(context).fetching : "0",
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
),
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
DecimalInputFormatter(
|
||||
maxDecimals: widget.useBaseUnit
|
||||
? 0
|
||||
: widget.currency.decimals,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
validator:
|
||||
_fiatInputMode ? null : widget.currencyValueValidator,
|
||||
controller:
|
||||
_fiatInputMode ? fiatAmountController : amountController,
|
||||
focusNode: amountFocusNode,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true,
|
||||
hintText: showFetching ? S.of(context).fetching : "0",
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
),
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
DecimalInputFormatter(
|
||||
maxDecimals:
|
||||
widget.useBaseUnit ? 0 : widget.currency.decimals,
|
||||
),
|
||||
),
|
||||
if (_fiatInputMode)
|
||||
Center(
|
||||
child: Text(
|
||||
widget.exchangeViewModel.fiat.title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_fiatInputMode)
|
||||
Center(
|
||||
child: Text(
|
||||
widget.exchangeViewModel.fiat.title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
GestureDetector(
|
||||
onTap: _presentCurrencyPicker,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 4, bottom: 4, left: 4, right: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: widget.currency.iconPath ?? "",
|
||||
width: 28,
|
||||
height: 28,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
currencyToShow,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (chainIconPath != null && chainIconPath.isNotEmpty) ...[
|
||||
const SizedBox(width: 4),
|
||||
CakeImageWidget(
|
||||
imageUrl: chainIconPath,
|
||||
width: 12,
|
||||
height: 12,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
] else
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(9999999999),
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/dropdown_arrow.svg",
|
||||
width: 4,
|
||||
height: 4,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _presentCurrencyPicker,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(999999),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 4, right: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: widget.currency.iconPath ?? "",
|
||||
width: 28,
|
||||
height: 28,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
currencyToShow,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (chainIconPath != null && chainIconPath.isNotEmpty) ...[
|
||||
const SizedBox(width: 4),
|
||||
CakeImageWidget(
|
||||
imageUrl: chainIconPath,
|
||||
width: 12,
|
||||
height: 12,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
] else
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(9999999999),
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/dropdown_arrow.svg",
|
||||
width: 4,
|
||||
height: 4,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
if (state.hasError)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Semantics(
|
||||
container: true,
|
||||
liveRegion: true,
|
||||
label: "${S.of(context).amount}${state.errorText!}",
|
||||
excludeSemantics: true,
|
||||
child: Text(
|
||||
state.errorText!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart";
|
||||
import "package:cake_wallet/core/anypay/anypay_models.dart";
|
||||
import "package:cw_core/amount/money.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
@@ -8,13 +8,18 @@ class SwapFromSendArgs {
|
||||
required this.recipientAddress,
|
||||
required this.receiveCurrency,
|
||||
required this.targetWalletType,
|
||||
this.depositBalanceByAsset,
|
||||
this.receiveAmount,
|
||||
});
|
||||
|
||||
factory SwapFromSendArgs.fromIntent(AnyPaySwapIntent intent) => SwapFromSendArgs(
|
||||
recipientAddress: intent.recipientAddress,
|
||||
receiveCurrency: intent.receiveCurrency,
|
||||
targetWalletType: intent.targetWalletType,
|
||||
receiveAmount: intent.receiveAmount,
|
||||
);
|
||||
|
||||
final String recipientAddress;
|
||||
final CryptoCurrency receiveCurrency;
|
||||
final WalletType targetWalletType;
|
||||
final Map<CryptoCurrency, CurrencyPickerBalance>? depositBalanceByAsset;
|
||||
final Money? receiveAmount;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class SwapSourceSelector extends StatelessWidget {
|
||||
const SwapSourceSelector({
|
||||
required this.currencyIconPath,
|
||||
required this.currencyLabel,
|
||||
required this.availableBalance,
|
||||
required this.onTap,
|
||||
this.chainIconPath,
|
||||
this.walletName,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String currencyIconPath;
|
||||
final String currencyLabel;
|
||||
final String availableBalance;
|
||||
final VoidCallback onTap;
|
||||
final String? chainIconPath;
|
||||
final String? walletName;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final chainIcon = chainIconPath;
|
||||
|
||||
return Column(
|
||||
spacing: 12,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 52,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: currencyIconPath,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
currencyLabel,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(letterSpacing: -0.08),
|
||||
),
|
||||
if (chainIcon != null && chainIcon.isNotEmpty) ...[
|
||||
const SizedBox(width: 4),
|
||||
CakeImageWidget(
|
||||
imageUrl: chainIcon,
|
||||
width: 12,
|
||||
height: 12,
|
||||
colorFilter: ColorFilter.mode(colors.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/chooser.svg",
|
||||
width: 12,
|
||||
height: 12,
|
||||
colorFilter: ColorFilter.mode(colors.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/wallet_filled.svg",
|
||||
width: 16,
|
||||
height: 16,
|
||||
colorFilter: ColorFilter.mode(colors.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
walletName ?? "",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: -0.06,
|
||||
color: colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
S.of(context).available_balance_short(availableBalance),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: -0.06,
|
||||
color: colors.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user