Cw 1539 lightning enhancements (#3400)

* feat: add Lightning transaction URL support in transaction details view model

* fix: update currency selection logic in WalletAddressListViewModel and ReceivePage

* feat: integrate computed transaction amount and fee getters in TransactionDetailsViewModel, refactor CopyWrapper logic in TransactionDetailsModal

* refactor: apply lint rules

* refactor: apply lint rules and improve LNURL handling with new methods for withdrawal requests and error checks

* auto-reformat

* fix: receive_page regression

* fix: Withdraw lightning to other btc wallet gives LN address

* chore: update .lock files [skip ci]

---------

Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Konstantin Ullrich
2026-07-17 17:49:17 +03:00
committed by GitHub
co-authored by Robert Malikowski Omar Hatem
parent 28c2cc152e
commit 03edd658a5
18 changed files with 1193 additions and 1003 deletions
+121 -115
View File
@@ -1,54 +1,54 @@
import 'package:cake_wallet/core/utilities.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/payjoin_copy_modal.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_type.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_widget.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_display.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_modal.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_bottom_buttons.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_info_box.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_widget.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_large_amount_preview.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/payment_uris.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/routes.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import "package:cake_wallet/anonpay/anonpay_donation_link_info.dart";
import "package:cake_wallet/bitcoin/bitcoin.dart";
import "package:cake_wallet/core/utilities.dart";
import "package:cake_wallet/di.dart";
import "package:cake_wallet/entities/auto_generate_subaddress_status.dart";
import "package:cake_wallet/entities/preferences_key.dart";
import "package:cake_wallet/generated/i18n.dart";
import "package:cake_wallet/new-ui/widgets/modern_button.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/payjoin_copy_modal.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_address_type.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_address_widget.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_amount_display.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_amount_modal.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_bottom_buttons.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_info_box.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_label_widget.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_large_amount_preview.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
import "package:cake_wallet/routes.dart";
import "package:cake_wallet/src/screens/receive/anonpay_receive_page.dart";
import "package:cake_wallet/utils/share_util.dart";
import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
import "package:cake_wallet/view_model/dashboard/receive_option_view_model.dart";
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart";
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart";
import "package:cake_wallet/zcash/zcash.dart";
import "package:cw_core/crypto_currency.dart";
import "package:cw_core/payment_uris.dart";
import "package:cw_core/receive_page_option.dart";
import "package:cw_core/utils/print_verbose.dart";
import "package:cw_core/wallet_type.dart";
import "package:flutter/cupertino.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_mobx/flutter_mobx.dart";
import "package:mobx/mobx.dart";
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
import "package:shared_preferences/shared_preferences.dart";
class NewReceivePage extends StatefulWidget {
NewReceivePage(
{super.key,
required this.addressListViewModel,
required this.receiveOptionViewModel,
required this.dashboardViewModel,
required this.lightningMode,
CryptoCurrency? initialCurrency}) {
NewReceivePage({
required this.addressListViewModel,
required this.receiveOptionViewModel,
required this.dashboardViewModel,
required this.lightningMode,
super.key,
CryptoCurrency? initialCurrency,
}) {
if (initialCurrency != null && initialCurrency != addressListViewModel.selectedCurrency) {
addressListViewModel.setTokenCurrency(initialCurrency);
}
@@ -78,31 +78,33 @@ class _NewReceivePageState extends State<NewReceivePage> {
.firstWhereOrNull((item) => item.value.contains("Lightning")) ??
ReceivePageOption.mainnet,
);
widget.addressListViewModel.setTokenCurrency(CryptoCurrency.btcln);
widget.addressListViewModel.selectedCurrency = CryptoCurrency.btcln;
} else if (widget.addressListViewModel.wallet.type == WalletType.bitcoin) {
widget.receiveOptionViewModel.selectReceiveOption(
widget.receiveOptionViewModel.options
.firstWhereOrNull((item) => item.value.contains("Standard")) ??
ReceivePageOption.mainnet,
);
widget.addressListViewModel.selectedCurrency = CryptoCurrency.btc;
}
});
reaction((_) => widget.addressListViewModel.uri, (newAddress) {
_reloadAddressWithLabel(newAddress);
});
reaction((_) => widget.addressListViewModel.uri, _reloadAddressWithLabel);
_addressItemWithLabel =
widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull((item) {
return (item is WalletAddressListItem &&
item.address == widget.addressListViewModel.uri.address);
}) as WalletAddressListItem?;
_addressItemWithLabel = widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull(
(item) =>
item is WalletAddressListItem && item.address == widget.addressListViewModel.uri.address,
) as WalletAddressListItem?;
reaction((_) => widget.receiveOptionViewModel.selectedReceiveOption,
(ReceivePageOption option) {
reaction((_) => widget.receiveOptionViewModel.selectedReceiveOption, (option) {
if (widget.dashboardViewModel.type == WalletType.bitcoin &&
bitcoin!.isBitcoinReceivePageOption(option)) {
widget.addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
if (option.value.contains("Lightning")) {
widget.addressListViewModel.selectedCurrency = CryptoCurrency.btcln;
} else {
widget.addressListViewModel.selectedCurrency = CryptoCurrency.btc;
}
return;
}
if (widget.dashboardViewModel.type == WalletType.zcash) {
@@ -166,16 +168,18 @@ class _NewReceivePageState extends State<NewReceivePage> {
final hasAddressTypeSelector = widget.receiveOptionViewModel.options.length > 1;
final hasLabel = _addressItemWithLabel?.name != null && _addressItemWithLabel!.name!.isNotEmpty;
final infoboxDismissed = widget.addressListViewModel.wallet.walletInfo.receiveInfoboxDismissed;
final infobox = ReceiveInfoBox.forWalletType(widget.addressListViewModel.type,
supportedCurrencies: widget.addressListViewModel.tokenCurrencies
.whereType<CryptoCurrency>()
.toList(), onDismissed: () {
widget.addressListViewModel.dismissInfobox();
setState(() {});
},
autoGenerateSubaddressStatus: widget.lightningMode
? AutoGenerateSubaddressStatus.disabled
: widget.dashboardViewModel.settingsStore.autoGenerateSubaddressStatus);
final infobox = ReceiveInfoBox.forWalletType(
widget.addressListViewModel.type,
supportedCurrencies:
widget.addressListViewModel.tokenCurrencies.whereType<CryptoCurrency>().toList(),
onDismissed: () {
widget.addressListViewModel.dismissInfobox();
setState(() {});
},
autoGenerateSubaddressStatus: widget.lightningMode
? AutoGenerateSubaddressStatus.disabled
: widget.dashboardViewModel.settingsStore.autoGenerateSubaddressStatus,
);
return Container(
decoration: BoxDecoration(
@@ -187,9 +191,7 @@ class _NewReceivePageState extends State<NewReceivePage> {
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
borderRadius: BorderRadius.vertical(
top: Radius.circular(24),
),
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: SafeArea(
child: Column(
@@ -198,13 +200,13 @@ class _NewReceivePageState extends State<NewReceivePage> {
children: [
ModalTopBar(
title: _largeQrMode ? "" : S.of(context).receive,
leadingIcon: Icon(Icons.close),
leadingIcon: const Icon(Icons.close),
onLeadingPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
trailingWidget: Observer(
builder: (_) => AnimatedSwitcher(
duration: Duration(milliseconds: 300),
duration: const Duration(milliseconds: 300),
child: _largeQrMode ||
widget.addressListViewModel.hasAddressRotation
/* TODO rotating is broken on mweb, disabling for now, fix after mvp*/
@@ -217,10 +219,10 @@ class _NewReceivePageState extends State<NewReceivePage> {
key: ValueKey(_largeQrMode),
size: 36,
icon: _largeQrMode
? Icon(Icons.share)
? const Icon(Icons.share)
: widget.addressListViewModel.isRotatingAddress
? CupertinoActivityIndicator()
: Icon(Icons.refresh),
? const CupertinoActivityIndicator()
: const Icon(Icons.refresh),
onPressed: () {
if (_largeQrMode) {
ShareUtil.share(
@@ -232,8 +234,9 @@ class _NewReceivePageState extends State<NewReceivePage> {
widget.addressListViewModel.rotateAddress();
}
}
})
: SizedBox.shrink(),
},
)
: const SizedBox.shrink(),
),
),
),
@@ -269,11 +272,12 @@ class _NewReceivePageState extends State<NewReceivePage> {
addressListViewModel: widget.addressListViewModel,
),
GestureDetector(
onTap: _showLabelModal,
child: ReceiveLabelWidget(
name: _addressItemWithLabel?.name ?? "",
largeQrMode: _largeQrMode,
)),
onTap: _showLabelModal,
child: ReceiveLabelWidget(
name: _addressItemWithLabel?.name ?? "",
largeQrMode: _largeQrMode,
),
),
Observer(
builder: (_) => ReceiveBottomButtons(
key: const ValueKey(0),
@@ -285,14 +289,16 @@ class _NewReceivePageState extends State<NewReceivePage> {
: ClipboardData(
text: widget.addressListViewModel.displayAmount.isEmpty
? widget.addressListViewModel.uri.address
: widget.addressListViewModel.uri.toString()),
: widget.addressListViewModel.uri.toString(),
),
onCopyButtonPressed: () {
if (widget.addressListViewModel.hasPayjoin) {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) =>
PayjoinCopyModal(uri: widget.addressListViewModel.uri));
isScrollControlled: true,
context: context,
builder: (context) =>
PayjoinCopyModal(uri: widget.addressListViewModel.uri),
);
}
},
onAmountButtonPressed: () {
@@ -300,12 +306,10 @@ class _NewReceivePageState extends State<NewReceivePage> {
context: context,
backgroundColor: Colors.transparent,
barrierColor: Colors.black.withAlpha(80),
builder: (context) {
return ReceiveAmountModal(
walletAddressListViewModel: widget.addressListViewModel,
onSubmitted: (amount) {},
);
},
builder: (context) => ReceiveAmountModal(
walletAddressListViewModel: widget.addressListViewModel,
onSubmitted: (amount) {},
),
);
},
onLabelButtonPressed: _showLabelModal,
@@ -318,22 +322,25 @@ class _NewReceivePageState extends State<NewReceivePage> {
),
),
ReceiveLargeAmountPreview(
amount: widget.addressListViewModel.displayAmount,
currency: widget.addressListViewModel.cryptoCurrencySymbol,
largeQrMode: _largeQrMode),
amount: widget.addressListViewModel.displayAmount,
currency: widget.addressListViewModel.cryptoCurrencySymbol,
largeQrMode: _largeQrMode,
),
if (infobox != null && !widget.addressListViewModel.isLightning)
ClipRect(
child: AnimatedAlign(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOutCubic,
heightFactor: infoboxDismissed ? 0 : 1,
alignment: Alignment.center,
child: AnimatedOpacity(
child: AnimatedAlign(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOutCubic,
heightFactor: infoboxDismissed ? 0 : 1,
alignment: Alignment.center,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: infoboxDismissed ? 0 : 1,
curve: Curves.easeOutCubic,
child: infobox),
))
child: infobox,
),
),
),
],
),
),
@@ -345,12 +352,11 @@ class _NewReceivePageState extends State<NewReceivePage> {
void _showLabelModal() {
showMaterialModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
barrierColor: Colors.black.withAlpha(80),
builder: (context) {
return getIt.get<ReceiveLabelModal>(param1: _addressItemWithLabel);
}).then((value) {
context: context,
backgroundColor: Colors.transparent,
barrierColor: Colors.black.withAlpha(80),
builder: (_) => getIt.get<ReceiveLabelModal>(param1: _addressItemWithLabel),
).then((value) {
_reloadAddressWithLabel(widget.addressListViewModel.uri);
});
}
@@ -359,8 +365,8 @@ class _NewReceivePageState extends State<NewReceivePage> {
// FIXME: viewmodel doesn't want to load address name here, so we make it. investigate why later
setState(() {
_addressItemWithLabel = widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull(
(item) => (item is WalletAddressListItem && item.address == newAddress.address))
as WalletAddressListItem?;
(item) => item is WalletAddressListItem && item.address == newAddress.address,
) as WalletAddressListItem?;
});
}
}
+2 -2
View File
@@ -594,7 +594,7 @@ class _NewSendPageState extends State<NewSendPage> {
builder: (context) => Material(
child: L2ActionWalletSelector(
showOtherWallets: false,
action: l2actions.deposit,
action: L2Actions.deposit,
sendViewModel: widget.sendViewModel,
contactListViewModel:
widget.contactListViewModel,
@@ -609,7 +609,7 @@ class _NewSendPageState extends State<NewSendPage> {
builder: (context) => Material(
child: L2ActionWalletSelector(
showOtherWallets: false,
action: l2actions.withdraw,
action: L2Actions.withdraw,
sendViewModel: widget.sendViewModel,
contactListViewModel:
widget.contactListViewModel,
@@ -59,14 +59,13 @@ class _AssetsHistorySectionState extends State<AssetsHistorySection> {
AssetsSection(
dashboardViewModel: widget.dashboardViewModel,
),
hasAssetsButton ?
AssetsHistorySectionActionButton(S.current.tokens, "assets/new-ui/options_slider.svg",
() {
Navigator.of(context).pushNamed(
hasAssetsButton ? AssetsHistorySectionActionButton(S.current.tokens, "assets/new-ui/options_slider.svg",
() {Navigator.of(context).pushNamed(
Routes.homeSettings,
arguments: widget.dashboardViewModel.balanceViewModel,
);
}) : null),
}): null),
AssetsHistorySectionTab(
S.current.history,
HistorySection(
@@ -1,24 +1,28 @@
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
import 'package:cake_wallet/new-ui/widgets/copy_wrapper.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/address_list_item.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart";
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart";
import "package:cake_wallet/generated/i18n.dart";
import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart";
import "package:cake_wallet/new-ui/widgets/copy_wrapper.dart";
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
import "package:cake_wallet/routes.dart";
import "package:cake_wallet/src/screens/transaction_details/address_list_item.dart";
import "package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart";
import "package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart";
import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart";
import "package:cake_wallet/utils/address_formatter.dart";
import "package:cake_wallet/view_model/transaction_details_view_model.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_mobx/flutter_mobx.dart";
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
class TransactionDetailsModal extends StatefulWidget {
const TransactionDetailsModal(
{super.key, required this.transactionDetailsViewModel, this.highlightNoteField = false});
{
required this.transactionDetailsViewModel,
this.highlightNoteField = false,
super.key,
});
final TransactionDetailsViewModel transactionDetailsViewModel;
final bool highlightNoteField;
@@ -48,177 +52,195 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
}
@override
Widget build(BuildContext context) {
final transactionInfoAmount = widget.transactionDetailsViewModel.transactionInfo.amount;
return SafeArea(
bottom: false,
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: GestureDetector(
onTap: FocusScope.of(context).unfocus,
child: Container(
decoration: BoxDecoration(
Widget build(BuildContext context) => SafeArea(
bottom: false,
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: GestureDetector(
onTap: FocusScope.of(context).unfocus,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.vertical(top: Radius.circular(25))),
child: Column(
children: [
ModalTopBar(
title: S.of(context).transaction,
leadingIcon: Icon(Icons.close),
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
child: SingleChildScrollView(
controller: ModalScrollController.of(context),
child: Column(
children: [
TokenImageWidget(
imageUrl:
widget.transactionDetailsViewModel.transactionAsset.iconPath ?? "",
size: 64,
),
SizedBox(height: 10),
Text(
widget.transactionDetailsViewModel.formattedTitle +
widget.transactionDetailsViewModel.formattedStatus,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
CopyWrapper(
requireLongPress: true,
data: ClipboardData(text: transactionInfoAmount.toString()),
builder: (context, copied) => AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: Text(
key: ValueKey(copied),
copied
? S.of(context).copied
: transactionInfoAmount.toStringWithSymbol(),
style: TextStyle(
fontSize: 28,
color: copied
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface),
borderRadius: const BorderRadius.vertical(top: Radius.circular(25)),
),
child: Column(
children: [
ModalTopBar(
title: S.of(context).transaction,
leadingIcon: const Icon(Icons.close),
onLeadingPressed: Navigator.of(context).pop,
),
Expanded(
child: SingleChildScrollView(
controller: ModalScrollController.of(context),
child: Column(
children: [
TokenImageWidget(
imageUrl:
widget.transactionDetailsViewModel.transactionAsset.iconPath ?? "",
size: 64,
),
const SizedBox(height: 10),
Text(
widget.transactionDetailsViewModel.formattedTitle +
widget.transactionDetailsViewModel.formattedStatus,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
Observer(
builder: (_) => CopyWrapper(
requireLongPress: true,
data: ClipboardData(
text: widget.transactionDetailsViewModel.transactionCopyAmount,
),
builder: (context, copied) => AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Text(
key: ValueKey(copied),
copied
? S.of(context).copied
: widget.transactionDetailsViewModel.transactionAmount,
style: TextStyle(
fontSize: 28,
color: copied
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
),
),
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
spacing: 12,
children: [
NewListSections(sections: {
"": widget.transactionDetailsViewModel.items
.map((item) {
if (item.value.isEmpty) return null;
Padding(
padding: const EdgeInsets.all(16),
child: Column(
spacing: 12,
children: [
NewListSections(
sections: {
"": widget.transactionDetailsViewModel.items
.map((item) {
if (item.value.isEmpty) {
return null;
}
final shouldBuildBottomWidget = item.value.length > 25;
final shouldBuildBottomWidget = item.value.length > 25;
return ListItemRegularRow(
copyableText: item.value,
showArrow: false,
keyValue: ((item.key as ValueKey?)?.value as String?) ??
item.title,
label: item.title,
trailingWidget: shouldBuildBottomWidget
? null
: _buildTrailingWIdget(item),
bottomWidget: shouldBuildBottomWidget
? _buildBottomWidget(item)
: null);
})
.whereType<ListItem>()
.toList(),
}),
Container(
decoration: BoxDecoration(
return ListItemRegularRow(
copyableText: item.value,
showArrow: false,
keyValue: ((item.key as ValueKey?)?.value as String?) ??
item.title,
label: item.title,
trailingWidget: shouldBuildBottomWidget
? null
: _buildTrailingWidget(item),
bottomWidget: shouldBuildBottomWidget
? _buildBottomWidget(item)
: null,
);
})
.whereType<ListItem>()
.toList(),
},
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Theme.of(context).colorScheme.surfaceContainer),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.of(context).note),
TextField(
focusNode: noteFocusNode,
controller: noteController,
decoration: InputDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.of(context).note),
TextField(
focusNode: noteFocusNode,
controller: noteController,
decoration: InputDecoration(
hintText: S.of(context).add_a_note,
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
contentPadding: EdgeInsets.zero,
isDense: true),
)
],
isDense: true,
),
),
],
),
),
),
),
Observer(
builder: (_) => NewListSections(sections: {
"view tx": [
ListItemRegularRow(
keyValue: "view tx on",
label:
widget.transactionDetailsViewModel.explorerDescription,
onTap: widget.transactionDetailsViewModel.launchExplorer,
foregroundColor: Theme.of(context).colorScheme.primary,
trailingIconPath: "assets/new-ui/link_arrow.svg",
trailingIconSize: 8)
],
if (widget.transactionDetailsViewModel.canReplaceByFee)
"rbf": [
ListItemRegularRow(
keyValue: "replace by fee",
label: S.of(context).bump_fee,
onTap: () {
Navigator.of(context)
.pushNamed(Routes.bumpFeePage, arguments: [
widget.transactionDetailsViewModel.transactionInfo,
widget.transactionDetailsViewModel.rawTransaction
]);
})
]
}),
)
],
Observer(
builder: (_) => NewListSections(
sections: {
"view tx": [
ListItemRegularRow(
keyValue: "view tx on",
label: widget
.transactionDetailsViewModel.explorerDescription,
onTap: widget.transactionDetailsViewModel.launchExplorer,
foregroundColor: Theme.of(context).colorScheme.primary,
trailingIconPath: "assets/new-ui/link_arrow.svg",
trailingIconSize: 8,
),
],
if (widget.transactionDetailsViewModel.canReplaceByFee)
"rbf": [
ListItemRegularRow(
keyValue: "replace by fee",
label: S.of(context).bump_fee,
onTap: () {
Navigator.of(context).pushNamed(
Routes.bumpFeePage,
arguments: [
widget
.transactionDetailsViewModel.transactionInfo,
widget.transactionDetailsViewModel.rawTransaction,
],
);
},
),
],
},
),
),
],
),
),
),
SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
],
SizedBox(height: MediaQuery.of(context).viewPadding.bottom),
],
),
),
),
)
],
],
),
),
),
),
),
);
}
);
Widget _buildTrailingWIdget(TransactionDetailsListItem item) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: switch (item.runtimeType) {
ConfirmationsListItem => Row(
children: [
Text((item as ConfirmationsListItem).current.toString(),
style: TextStyle(color: Theme.of(context).colorScheme.primary)),
if (item.needed > 0)
Text("/${item.needed}",
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant))
],
),
_ => Text(
item.value,
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
)
},
);
}
Widget _buildTrailingWidget(TransactionDetailsListItem item) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: switch (item.runtimeType) {
ConfirmationsListItem => Row(
children: [
Text(
(item as ConfirmationsListItem).current.toString(),
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
if (item.needed > 0)
Text(
"/${item.needed}",
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
),
],
),
_ => Text(
item.value,
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
)
},
);
Widget _buildBottomWidget(TransactionDetailsListItem item) {
return switch (item.runtimeType) {
@@ -1,41 +1,41 @@
import 'dart:async';
import "dart:async";
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.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/new-ui/widgets/send_page/l2_send_external_modal.dart';
import 'package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_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/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import "package:cake_wallet/bitcoin/bitcoin.dart";
import "package:cake_wallet/generated/i18n.dart";
import "package:cake_wallet/main.dart";
import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.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/new-ui/widgets/send_page/l2_send_external_modal.dart";
import "package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart";
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
import "package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart";
import "package:cake_wallet/view_model/contact_list/contact_list_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/currency_for_wallet_type.dart";
import "package:cw_core/wallet_info.dart";
import "package:cw_core/wallet_type.dart";
import "package:flutter/cupertino.dart";
import "package:flutter/material.dart";
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
enum l2actions { deposit, withdraw }
enum L2Actions { deposit, withdraw }
class L2ActionWalletSelector extends StatefulWidget {
const L2ActionWalletSelector({
super.key,
required this.showOtherWallets,
required this.sendViewModel,
required this.action,
required this.onSendInitiated,
required this.contactListViewModel,
required this.walletSwitcherViewModel,
super.key,
});
final bool showOtherWallets;
final SendViewModel sendViewModel;
final l2actions action;
final L2Actions action;
final VoidCallback onSendInitiated;
final ContactListViewModel contactListViewModel;
final WalletSwitcherViewModel walletSwitcherViewModel;
@@ -56,12 +56,18 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
super.initState();
if (widget.showOtherWallets) {
() async {
items.addAll((await WalletInfo.getAll()).where((item) =>
item.type == widget.sendViewModel.walletType && item.hardwareWalletType == null));
items.addAll(
(await WalletInfo.getAll()).where(
(item) =>
item.type == widget.sendViewModel.walletType && item.hardwareWalletType == null,
),
);
items.sort((a, b) {
if (a.name == widget.sendViewModel.wallet.name)
if (a.name == widget.sendViewModel.wallet.name) {
return -1;
else if (b.name == widget.sendViewModel.wallet.name) return 1;
} else if (b.name == widget.sendViewModel.wallet.name) {
return 1;
}
return 0;
});
setState(() {});
@@ -80,16 +86,16 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
mainAxisSize: MainAxisSize.min,
children: [
ModalTopBar(
title: widget.action == l2actions.deposit
title: widget.action == L2Actions.deposit
? "${S.of(context).send_from}..."
: "${S.of(context).receive_to}...",
leadingIcon: Icon(Icons.arrow_back_ios_new),
leadingIcon: const Icon(Icons.arrow_back_ios_new),
onLeadingPressed: Navigator.of(context).pop,
),
Flexible(
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
spacing: 12,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -123,29 +129,17 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
itemBuilder: (context, index) {
final item = items[index];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
padding: const EdgeInsets.symmetric(vertical: 4),
child: WalletRow(
currencyIconPath: getCryptoCurrencyIconForWalletListItem(item.type),
walletName: item.name,
isCurrent: item.name == widget.sendViewModel.wallet.name,
isSelected: _selectedWalletIndex == index && !textEntered,
onTap: () async {
onTap: () {
setState(() {
addressController.text = "";
_selectedWalletIndex = index;
});
// if (widget.action == l2actions.withdraw) {
// widget.sendViewModel.outputs.first.address = item.address;
// widget.onSendInitiated();
// } else if (widget.action == l2actions.deposit) {
// setState(() {
// loadingWalletName = item.name;
// });
// await _handleChangeWallet(item);
// widget.onSendInitiated();
// setState(() {
// loadingWalletName = null;
// });
},
),
);
@@ -198,7 +192,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
// ),
// ),
// ),
if (widget.action == l2actions.withdraw)
if (widget.action == L2Actions.withdraw)
Row(
children: [
Flexible(
@@ -215,12 +209,12 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
Column(
spacing: 8,
children: [
if (widget.action == l2actions.deposit && widget.showOtherWallets) ...[
if (widget.action == L2Actions.deposit && widget.showOtherWallets) ...[
Container(
height: 1,
color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(64),
),
SizedBox(),
const SizedBox(),
Container(
height: 52,
decoration: BoxDecoration(
@@ -232,7 +226,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () async {
onTap: () {
Navigator.of(context, rootNavigator: true).pop();
showCupertinoModalBottomSheet(
context: navigatorKey.currentContext ?? context,
@@ -246,7 +240,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 10,
@@ -275,19 +269,22 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
onPressed: () async {
if (widget.sendViewModel.wallet.type == WalletType.bitcoin ||
widget.sendViewModel.wallet.type == WalletType.litecoin) {
if (widget.action == l2actions.withdraw) {
if (widget.action == L2Actions.withdraw) {
widget.sendViewModel.outputs.first.address =
bitcoin!.getUnusedSegwitAddress(widget.sendViewModel.wallet)!;
}
if (widget.showOtherWallets) {
if (widget.action == l2actions.deposit) {
if (widget.action == L2Actions.deposit) {
await _handleChangeWallet(items[_selectedWalletIndex]);
} else {
widget.sendViewModel.outputs.first.address =
addressController.text.isNotEmpty
? addressController.text
: items[_selectedWalletIndex].address;
if (items[_selectedWalletIndex].name !=
widget.sendViewModel.wallet.name) {
widget.sendViewModel.outputs.first.address =
addressController.text.isNotEmpty
? addressController.text
: items[_selectedWalletIndex].address;
}
}
}
widget.onSendInitiated();
@@ -298,7 +295,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: _isLoading,
),
SizedBox()
const SizedBox()
],
),
],
@@ -339,13 +336,13 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
class WalletRow extends StatelessWidget {
const WalletRow({
super.key,
required this.currencyIconPath,
required this.walletName,
required this.onTap,
this.isLoading = false,
this.isCurrent = false,
this.isSelected,
super.key,
});
final String currencyIconPath;
@@ -370,7 +367,7 @@ class WalletRow extends StatelessWidget {
borderRadius: BorderRadius.circular(16),
onTap: onTap,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [