CW-1277: Expand PayAnything flow to cover Solana and Tron networks (#2805)

* feat: Expand PayAnything flow to cover Solana and Tron networks

* fix: refactor utility method to prevent duplicates
This commit is contained in:
David Adegoke
2026-01-18 14:34:03 +01:00
committed by GitHub
parent 6cb28d4648
commit 5bb779d115
6 changed files with 673 additions and 485 deletions
+25 -6
View File
@@ -7,7 +7,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dar
import 'package:cake_wallet/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/evm_payment_flow_bottom_sheet.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/token_selection_bottom_sheet.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
@@ -174,10 +174,27 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
);
break;
case PaymentFlowType.evmNetworkSelection:
await _showEVMPaymentFlow(
await _showTokenSelectionFlow(
paymentViewModel,
walletSwitcherViewModel,
paymentRequest,
fixedNetwork: result.walletType,
);
break;
case PaymentFlowType.solanaTokenSelection:
await _showTokenSelectionFlow(
paymentViewModel,
walletSwitcherViewModel,
paymentRequest,
fixedNetwork: WalletType.solana,
);
break;
case PaymentFlowType.tronTokenSelection:
await _showTokenSelectionFlow(
paymentViewModel,
walletSwitcherViewModel,
paymentRequest,
fixedNetwork: WalletType.tron,
);
break;
@@ -238,11 +255,12 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
);
}
Future<void> _showEVMPaymentFlow(
Future<void> _showTokenSelectionFlow(
PaymentViewModel paymentViewModel,
WalletSwitcherViewModel walletSwitcherViewModel,
PaymentRequest paymentRequest,
) async {
PaymentRequest paymentRequest, {
WalletType? fixedNetwork,
}) async {
if (!context.mounted) {
return;
}
@@ -252,9 +270,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
isDismissible: true,
isScrollControlled: true,
builder: (BuildContext context) {
return EVMPaymentFlowBottomSheet(
return TokenSelectionBottomSheet(
paymentViewModel: paymentViewModel,
paymentRequest: paymentRequest,
fixedNetwork: fixedNetwork,
onNext: (PaymentFlowResult newResult) {
final selectedChainId = newResult.chainId;
final isCompatible = selectedChainId == evm!.getSelectedChainId(sendViewModel.wallet);
@@ -1,331 +0,0 @@
import 'package:cake_wallet/core/universal_address_detector.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/qr_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/utils/token_utilities.dart';
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class EVMPaymentFlowBottomSheet extends BaseBottomSheet {
EVMPaymentFlowBottomSheet({
Key? key,
required this.paymentViewModel,
required this.paymentRequest,
required this.onNext,
}) : super(
titleText: '',
footerType: FooterType.none,
maxHeight: 900,
);
final PaymentViewModel paymentViewModel;
final PaymentRequest paymentRequest;
final Function(PaymentFlowResult) onNext;
@override
Widget contentWidget(BuildContext context) {
return _EVMPaymentFlowContent(
paymentViewModel: paymentViewModel,
paymentRequest: paymentRequest,
onNext: onNext,
);
}
}
class _EVMPaymentFlowContent extends StatefulWidget {
const _EVMPaymentFlowContent({
required this.paymentViewModel,
required this.paymentRequest,
required this.onNext,
});
final PaymentViewModel paymentViewModel;
final PaymentRequest paymentRequest;
final Function(PaymentFlowResult) onNext;
@override
State<_EVMPaymentFlowContent> createState() => _EVMPaymentFlowContentState();
}
class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
int? selectedChainId;
CryptoCurrency? selectedToken;
@override
void initState() {
super.initState();
selectedChainId = widget.paymentViewModel.detectedChainId ?? 1;
_autoSelectToken();
}
Future<void> _autoSelectToken() async {
if (selectedChainId == null) return;
try {
final tokens = await TokenUtilities.getAvailableTokensForChainId(selectedChainId!);
if (tokens.isNotEmpty) {
setState(() {
selectedToken = tokens.first;
});
}
} catch (e) {
printV('Auto-select token error: $e');
}
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CakeImageWidget(
imageUrl: 'assets/images/eth_chain_mono.svg',
width: 50,
height: 50,
color: Theme.of(context).colorScheme.onSurface,
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Text(
'${S.current.ethereum_ecosystem}\n${S.current.address_detected.toLowerCase()}',
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w900,
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 36),
Text(
S.current.evm_ecosystem_description,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
letterSpacing: 0.0,
),
),
const SizedBox(height: 32),
EVMTileWidget(
value: selectedChainId != null
? _getChainName(selectedChainId!)
: S.current.select_network,
imagePath: selectedChainId != null ? _getChainImagePath(selectedChainId!) : null,
color: selectedChainId != null ? Theme.of(context).colorScheme.primary : null,
enabled: true,
onTap: () => _showNetworkSelection(context),
),
const SizedBox(height: 16),
EVMTileWidget(
imagePath: selectedToken != null ? selectedToken!.iconPath : null,
value: selectedToken != null ? selectedToken!.title : S.current.select_token,
enabled: selectedChainId != null,
onTap: () => _showTokenSelection(context),
color: selectedToken == null ? Theme.of(context).colorScheme.primary : null,
),
const SizedBox(height: 32),
PrimaryButton(
text: S.current.restore_next,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: selectedChainId != null && selectedToken != null
? () async => await _handleNext(context)
: null,
),
const SizedBox(height: 48),
],
),
);
}
void _showNetworkSelection(BuildContext context) async {
final allChains = evm!.getAllChains();
final chainIds = allChains.map((chainInfo) => chainInfo.chainId).toList();
final selectedIndex = selectedChainId != null ? chainIds.indexOf(selectedChainId!) : 0;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return Picker(
items: chainIds,
displayItem: (int chainId) => _getChainName(chainId),
selectedAtIndex: selectedIndex >= 0 ? selectedIndex : 0,
title: S.current.select_network,
closeOnItemSelected: true,
hasTitleSpacing: true,
images: chainIds.map((chainId) {
final imagePath = _getChainImagePath(chainId);
return imagePath != null
? CakeImageWidget(
imageUrl: imagePath,
width: 20,
height: 20,
color: Theme.of(context).colorScheme.primary)
: const SizedBox(width: 20, height: 20);
}).toList(),
onItemSelected: (int chainId) {
setState(() {
selectedChainId = chainId;
selectedToken = null;
});
_autoSelectToken();
},
);
},
);
}
String _getChainName(int chainId) {
final allChains = evm!.getAllChains();
final chainInfo = allChains.firstWhere(
(chain) => chain.chainId == chainId,
orElse: () => ChainInfo(chainId: chainId, name: 'Unknown Network', shortCode: 'unknown'),
);
return chainInfo.name;
}
String? _getChainImagePath(int chainId) {
return getChainMonoImage(WalletType.ethereum, selectedChainId: chainId);
}
void _showTokenSelection(BuildContext context) async {
if (selectedChainId == null) return;
try {
final availableTokens = await TokenUtilities.getAvailableTokensForChainId(selectedChainId!);
if (availableTokens.isEmpty) return;
final selectedIndex = selectedToken != null ? availableTokens.indexOf(selectedToken!) : 0;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return CurrencyPicker(
selectedAtIndex: selectedIndex >= 0 ? selectedIndex : 0,
items: availableTokens.cast<Currency>(),
hintText: S.current.add_token,
onItemSelected: (Currency currency) {
setState(() {
selectedToken = currency as CryptoCurrency;
});
},
);
},
);
} catch (e) {
printV('Error showing token selection: $e');
}
}
Future<void> _handleNext(BuildContext context) async {
if (selectedChainId == null || selectedToken == null) return;
Navigator.of(context).pop();
final allEVMWallets = await widget.paymentViewModel.getEVMCompatibleWallets();
final walletType = evm!.getWalletTypeByChainId(selectedChainId!) ?? WalletType.ethereum;
final detectionResult = AddressDetectionResult(
address: widget.paymentRequest.address,
detectedWalletType: walletType,
detectedCurrency: selectedToken!,
chainId: selectedChainId,
isValid: true,
amount: widget.paymentRequest.amount,
note: widget.paymentRequest.note,
scheme: widget.paymentRequest.scheme,
pjUri: widget.paymentRequest.pjUri,
callbackUrl: widget.paymentRequest.callbackUrl,
callbackMessage: widget.paymentRequest.callbackMessage,
);
final newResult = PaymentFlowResult.evmNetworkSelection(
detectionResult,
compatibleWallets: allEVMWallets,
wallet: allEVMWallets.isNotEmpty ? allEVMWallets.first : null,
);
widget.paymentViewModel.detectedWalletType = walletType;
widget.onNext(newResult);
}
}
class EVMTileWidget extends StatelessWidget {
const EVMTileWidget({
super.key,
required this.value,
required this.enabled,
required this.onTap,
this.imagePath,
this.color,
});
final String value;
final bool enabled;
final VoidCallback? onTap;
final String? imagePath;
final Color? color;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: enabled ? onTap : null,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Row(
children: [
if (imagePath != null) ...[
CakeImageWidget(
imageUrl: imagePath!,
color: color,
width: 20,
height: 20,
),
const SizedBox(width: 16),
],
Expanded(
child: Text(
value,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
Icon(
Icons.keyboard_arrow_down,
size: 24,
color: Theme.of(context).colorScheme.primary,
),
],
),
),
);
}
}
@@ -89,6 +89,13 @@ class _PaymentConfirmationContent extends StatelessWidget {
return false;
}
/// Checks if flow type is token selection (EVM, Solana, or Tron)
bool _isTokenSelectionFlow(PaymentFlowType type) {
return type == PaymentFlowType.evmNetworkSelection ||
type == PaymentFlowType.solanaTokenSelection ||
type == PaymentFlowType.tronTokenSelection;
}
@override
Widget build(BuildContext context) {
return Observer(
@@ -127,14 +134,20 @@ class _PaymentConfirmationContent extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (paymentFlowResult.type == PaymentFlowType.evmNetworkSelection) ...[
if (_isTokenSelectionFlow(paymentFlowResult.type)) ...[
Stack(
clipBehavior: Clip.none,
children: [
Image.asset(
paymentFlowResult.addressDetectionResult!.detectedCurrency!.iconPath!,
paymentFlowResult.addressDetectionResult?.detectedCurrency?.iconPath ??
'',
width: 70,
height: 70,
errorBuilder: (context, error, stackTrace) => Icon(
Icons.currency_bitcoin,
size: 70,
color: Theme.of(context).colorScheme.onSurface,
),
),
Positioned(
bottom: 0,
@@ -146,6 +159,11 @@ class _PaymentConfirmationContent extends StatelessWidget {
).iconPath!,
width: 32,
height: 32,
errorBuilder: (context, error, stackTrace) => Icon(
Icons.account_balance_wallet,
size: 32,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
],
@@ -0,0 +1,456 @@
import 'package:cake_wallet/core/universal_address_detector.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/qr_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/utils/token_utilities.dart';
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class TokenSelectionBottomSheet extends BaseBottomSheet {
TokenSelectionBottomSheet({
Key? key,
required this.paymentViewModel,
required this.paymentRequest,
required this.onNext,
this.fixedNetwork,
}) : super(
titleText: '',
footerType: FooterType.none,
maxHeight: 900,
);
final PaymentViewModel paymentViewModel;
final PaymentRequest paymentRequest;
final Function(PaymentFlowResult) onNext;
final WalletType? fixedNetwork;
@override
Widget contentWidget(BuildContext context) {
return _TokenSelectionContent(
paymentViewModel: paymentViewModel,
paymentRequest: paymentRequest,
onNext: onNext,
fixedNetwork: fixedNetwork,
);
}
}
class _TokenSelectionContent extends StatefulWidget {
const _TokenSelectionContent({
required this.paymentViewModel,
required this.paymentRequest,
required this.onNext,
this.fixedNetwork,
});
final PaymentViewModel paymentViewModel;
final PaymentRequest paymentRequest;
final Function(PaymentFlowResult) onNext;
final WalletType? fixedNetwork;
@override
State<_TokenSelectionContent> createState() => _TokenSelectionContentState();
}
class _TokenSelectionContentState extends State<_TokenSelectionContent> {
WalletType? selectedNetwork;
CryptoCurrency? selectedToken;
bool isLoadingTokens = false;
String? tokenLoadError;
@override
void initState() {
super.initState();
selectedNetwork = widget.fixedNetwork ?? WalletType.ethereum;
_autoSelectToken();
}
bool get _isNetworkSelectable => widget.fixedNetwork == null || isEVMCompatibleChain(widget.fixedNetwork!);
Future<void> _autoSelectToken() async {
if (selectedNetwork == null) return;
setState(() {
isLoadingTokens = true;
tokenLoadError = null;
});
try {
final tokens = await TokenUtilities.getAvailableTokensForNetwork(
selectedNetwork!,
);
if (tokens.isEmpty) {
setState(() {
tokenLoadError = 'No tokens available';
isLoadingTokens = false;
});
return;
}
setState(() {
selectedToken = tokens.first;
isLoadingTokens = false;
});
} catch (e) {
printV('Auto-select token error: $e');
setState(() {
tokenLoadError = 'Failed to load tokens';
isLoadingTokens = false;
selectedToken = walletTypeToCryptoCurrency(selectedNetwork!);
});
}
}
String _getNetworkTitle() {
if (selectedNetwork == null) return S.current.select_network;
return walletTypeToString(selectedNetwork!);
}
String _getEcosystemTitle() {
if (selectedNetwork == WalletType.solana) {
return 'Solana\n${S.current.address_detected.toLowerCase()}';
}
if (selectedNetwork == WalletType.tron) {
return 'Tron\n${S.current.address_detected.toLowerCase()}';
}
return '${S.current.ethereum_ecosystem}\n${S.current.address_detected.toLowerCase()}';
}
String _getEcosystemDescription() {
if (selectedNetwork == WalletType.solana) {
return 'Select a token to send on Solana network';
}
if (selectedNetwork == WalletType.tron) {
return 'Select a token to send on Tron network';
}
return S.current.evm_ecosystem_description;
}
String _getNetworkIcon() {
if (selectedNetwork == null) return 'assets/images/eth_chain_mono.svg';
return getChainMonoImage(selectedNetwork!);
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CakeImageWidget(
imageUrl: _getNetworkIcon(),
width: 50,
height: 50,
color: Theme.of(context).colorScheme.onSurface,
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Text(
_getEcosystemTitle(),
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 20,
fontWeight: FontWeight.w900,
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 36),
Text(
_getEcosystemDescription(),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurfaceVariant,
letterSpacing: 0.0,
),
),
const SizedBox(height: 32),
TokenSelectionTileWidget(
value: _getNetworkTitle(),
imagePath: selectedNetwork != null ? getChainMonoImage(selectedNetwork!) : null,
color: selectedNetwork != null ? Theme.of(context).colorScheme.primary : null,
enabled: _isNetworkSelectable,
onTap: _isNetworkSelectable ? () => _showNetworkSelection(context) : null,
),
const SizedBox(height: 16),
TokenSelectionTileWidget(
imagePath: selectedToken?.iconPath,
value: selectedToken != null ? selectedToken!.title : S.current.select_token,
enabled: selectedNetwork != null && !isLoadingTokens,
onTap: isLoadingTokens ? null : () => _showTokenSelection(context),
color: selectedToken == null ? Theme.of(context).colorScheme.primary : null,
),
if (isLoadingTokens) ...[
const SizedBox(height: 16),
const CircularProgressIndicator(),
],
if (tokenLoadError != null) ...[
const SizedBox(height: 16),
Text(
tokenLoadError!,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
],
const SizedBox(height: 32),
PrimaryButton(
text: S.current.restore_next,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: selectedNetwork != null && selectedToken != null && !isLoadingTokens
? () async => await _handleNext(context)
: null,
),
const SizedBox(height: 48),
],
),
);
}
void _showNetworkSelection(BuildContext context) async {
if (!_isNetworkSelectable) return;
final evmNetworks =
availableWalletTypes.where((walletType) => isEVMCompatibleChain(walletType)).toList();
final selectedIndex = evmNetworks.indexOf(selectedNetwork ?? WalletType.ethereum);
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return Picker(
items: evmNetworks,
displayItem: (WalletType network) => walletTypeToString(network),
selectedAtIndex: selectedIndex,
title: S.current.select_network,
closeOnItemSelected: true,
hasTitleSpacing: true,
images: evmNetworks
.map((network) => CakeImageWidget(
imageUrl: getChainMonoImage(network),
width: 20,
height: 20,
color: Theme.of(context).colorScheme.primary))
.toList(),
onItemSelected: (WalletType network) {
setState(() {
selectedNetwork = network;
selectedToken = null;
});
_autoSelectToken();
},
);
},
);
}
void _showTokenSelection(BuildContext context) async {
if (selectedNetwork == null || isLoadingTokens) return;
setState(() {
isLoadingTokens = true;
});
try {
final availableTokens = await TokenUtilities.getAvailableTokensForNetwork(
selectedNetwork!,
);
if (availableTokens.isEmpty) {
setState(() {
isLoadingTokens = false;
});
await showPopUp<void>(
context: context,
builder: (context) => AlertDialog(
title: Text(S.current.error),
content: const Text('No tokens available for this network'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(S.current.ok),
),
],
),
);
return;
}
final selectedIndex = selectedToken != null ? availableTokens.indexOf(selectedToken!) : 0;
setState(() {
isLoadingTokens = false;
});
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return CurrencyPicker(
selectedAtIndex: selectedIndex >= 0 ? selectedIndex : 0,
items: availableTokens.cast<Currency>(),
hintText: S.current.add_token,
onItemSelected: (Currency currency) {
setState(() {
selectedToken = currency as CryptoCurrency;
});
},
);
},
);
} catch (e) {
printV('Error showing token selection: $e');
setState(() {
isLoadingTokens = false;
tokenLoadError = 'Failed to load tokens';
});
}
}
Future<void> _handleNext(BuildContext context) async {
if (selectedNetwork == null || selectedToken == null) return;
Navigator.of(context).pop();
final compatibleWallets = await widget.paymentViewModel.getWalletsByType(selectedNetwork!);
PaymentFlowResult newResult;
if (selectedNetwork == WalletType.solana) {
newResult = PaymentFlowResult.solanaTokenSelection(
AddressDetectionResult(
address: widget.paymentRequest.address,
detectedWalletType: WalletType.solana,
detectedCurrency: selectedToken!,
isValid: true,
amount: widget.paymentRequest.amount,
note: widget.paymentRequest.note,
scheme: widget.paymentRequest.scheme,
pjUri: widget.paymentRequest.pjUri,
callbackUrl: widget.paymentRequest.callbackUrl,
callbackMessage: widget.paymentRequest.callbackMessage,
),
compatibleWallets: compatibleWallets,
wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
);
} else if (selectedNetwork == WalletType.tron) {
newResult = PaymentFlowResult.tronTokenSelection(
AddressDetectionResult(
address: widget.paymentRequest.address,
detectedWalletType: WalletType.tron,
detectedCurrency: selectedToken!,
isValid: true,
amount: widget.paymentRequest.amount,
note: widget.paymentRequest.note,
scheme: widget.paymentRequest.scheme,
pjUri: widget.paymentRequest.pjUri,
callbackUrl: widget.paymentRequest.callbackUrl,
callbackMessage: widget.paymentRequest.callbackMessage,
),
compatibleWallets: compatibleWallets,
wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
);
} else {
newResult = PaymentFlowResult.evmNetworkSelection(
AddressDetectionResult(
address: widget.paymentRequest.address,
detectedWalletType: selectedNetwork!,
detectedCurrency: selectedToken!,
isValid: true,
amount: widget.paymentRequest.amount,
note: widget.paymentRequest.note,
scheme: widget.paymentRequest.scheme,
pjUri: widget.paymentRequest.pjUri,
callbackUrl: widget.paymentRequest.callbackUrl,
callbackMessage: widget.paymentRequest.callbackMessage,
),
compatibleWallets: compatibleWallets,
wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
);
}
widget.paymentViewModel.detectedWalletType = selectedNetwork!;
widget.onNext(newResult);
}
}
class TokenSelectionTileWidget extends StatelessWidget {
const TokenSelectionTileWidget({
super.key,
required this.value,
required this.enabled,
required this.onTap,
this.imagePath,
this.color,
});
final String value;
final bool enabled;
final VoidCallback? onTap;
final String? imagePath;
final Color? color;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: enabled ? onTap : null,
borderRadius: BorderRadius.circular(12),
child: Opacity(
opacity: enabled ? 1.0 : 0.6,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Row(
children: [
if (imagePath != null) ...[
CakeImageWidget(
imageUrl: imagePath!,
color: color,
width: 20,
height: 20,
),
const SizedBox(width: 16),
],
Expanded(
child: Text(
value,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurface,
),
),
),
if (enabled)
Icon(
Icons.keyboard_arrow_down,
size: 24,
color: Theme.of(context).colorScheme.primary,
),
],
),
),
),
);
}
}