tx details view ui code clean up (WIP)
What changed, and why it matters
This commit is a user-interface code cleanup for the transaction details screen in Stack Wallet. It refactors repeated layout code into smaller reusable widgets, adjusts some text styles and dialog widths, and moves the block-explorer warning dialog into a new widget. There is no change to security-sensitive logic, cryptography, network handling, or data storage.
No security action required; treat as routine UI refactor. Standard code-review and QA for visual regressions is sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is a large but mechanical refactor of three Dart files under lib/pages/wallet_view/transaction_views/tx_v2/. It extracts repeated RoundedWhiteContainer/SelectableText patterns into _DetailItemBase, _DetailItem, and _TxidDetailItem widgets, and moves showExplorerWarning into _TxidDetailItemState. It also reformats code (e.g., conditional expressions, trailing commas), changes DesktopDialog maxWidth from 580 to 640 in two places, and removes the unused super.key parameter from _Divider. No logic that handles secrets, transaction signing, address derivation, or external URLs was altered in behavior.
Changed components
lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dartlib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dartlib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dartInspect captured patch +729 / −1115
diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart
index 356d106..49d51f2 100644
--- a/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart
+++ b/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart
@@ -52,8 +52,11 @@ import '../transaction_search_filter_view.dart';
import 'transaction_v2_card.dart';
import 'transaction_v2_details_view.dart' as tvd;
-typedef _GroupedTransactions =
- ({String label, DateTime startDate, List<TransactionV2> transactions});
+typedef _GroupedTransactions = ({
+ String label,
+ DateTime startDate,
+ List<TransactionV2> transactions,
+});
class AllTransactionsV2View extends ConsumerStatefulWidget {
const AllTransactionsV2View({
@@ -106,14 +109,13 @@ class _AllTransactionsV2ViewState extends ConsumerState<AllTransactionsV2View> {
// debugPrint("FILTER: $filter");
final contacts = ref.read(addressBookServiceProvider).contacts;
- final notes =
- ref
- .read(mainDBProvider)
- .isar
- .transactionNotes
- .where()
- .walletIdEqualTo(walletId)
- .findAllSync();
+ final notes = ref
+ .read(mainDBProvider)
+ .isar
+ .transactionNotes
+ .where()
+ .walletIdEqualTo(walletId)
+ .findAllSync();
return transactions.where((tx) {
if (!filter.sent && !filter.received) {
@@ -159,25 +161,23 @@ class _AllTransactionsV2ViewState extends ConsumerState<AllTransactionsV2View> {
bool contains = false;
// check if address book name contains
- contains |=
- contacts
- .where(
- (e) =>
- e.addresses
- .map((e) => e.address)
- .toSet()
- .intersection(tx.associatedAddresses())
- .isNotEmpty &&
- e.name.toLowerCase().contains(keyword),
- )
- .isNotEmpty;
+ contains |= contacts
+ .where(
+ (e) =>
+ e.addresses
+ .map((e) => e.address)
+ .toSet()
+ .intersection(tx.associatedAddresses())
+ .isNotEmpty &&
+ e.name.toLowerCase().contains(keyword),
+ )
+ .isNotEmpty;
// check if address contains
- contains |=
- tx
- .associatedAddresses()
- .where((e) => e.toLowerCase().contains(keyword))
- .isNotEmpty;
+ contains |= tx
+ .associatedAddresses()
+ .where((e) => e.toLowerCase().contains(keyword))
+ .isNotEmpty;
TransactionNote? note;
final matchingNotes = notes.where((e) => e.txid == tx.txid);
@@ -214,14 +214,13 @@ class _AllTransactionsV2ViewState extends ConsumerState<AllTransactionsV2View> {
}
text = text.toLowerCase();
final contacts = ref.read(addressBookServiceProvider).contacts;
- final notes =
- ref
- .read(mainDBProvider)
- .isar
- .transactionNotes
- .where()
- .walletIdEqualTo(walletId)
- .findAllSync();
+ final notes = ref
+ .read(mainDBProvider)
+ .isar
+ .transactionNotes
+ .where()
+ .walletIdEqualTo(walletId)
+ .findAllSync();
return transactions
.where((tx) => _isKeywordMatch(tx, text, contacts, notes))
@@ -258,94 +257,90 @@ class _AllTransactionsV2ViewState extends ConsumerState<AllTransactionsV2View> {
return MasterScaffold(
background: Theme.of(context).extension<StackColors>()!.background,
isDesktop: isDesktop,
- appBar:
- isDesktop
- ? DesktopAppBar(
- isCompactHeight: true,
- background: Theme.of(context).extension<StackColors>()!.popupBG,
- leading: Row(
- children: [
- const SizedBox(width: 32),
- AppBarIconButton(
- size: 32,
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textFieldDefaultBG,
+ appBar: isDesktop
+ ? DesktopAppBar(
+ isCompactHeight: true,
+ background: Theme.of(context).extension<StackColors>()!.popupBG,
+ leading: Row(
+ children: [
+ const SizedBox(width: 32),
+ AppBarIconButton(
+ size: 32,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textFieldDefaultBG,
+ shadows: const [],
+ icon: SvgPicture.asset(
+ Assets.svg.arrowLeft,
+ width: 18,
+ height: 18,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.topNavIconPrimary,
+ ),
+ onPressed: Navigator.of(context).pop,
+ ),
+ const SizedBox(width: 12),
+ Text("Transactions", style: STextStyles.desktopH3(context)),
+ ],
+ ),
+ )
+ : AppBar(
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.background,
+ leading: AppBarBackButton(
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future<void>.delayed(
+ const Duration(milliseconds: 75),
+ );
+ }
+ if (context.mounted) {
+ Navigator.of(context).pop();
+ }
+ },
+ ),
+ title: Text(
+ "Transactions",
+ style: STextStyles.navBarTitle(context),
+ ),
+ actions: [
+ Padding(
+ padding: const EdgeInsets.only(
+ top: 10,
+ bottom: 10,
+ right: 20,
+ ),
+ child: AspectRatio(
+ aspectRatio: 1,
+ child: AppBarIconButton(
+ key: const Key("transactionSearchFilterViewButton"),
+ size: 36,
shadows: const [],
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.background,
icon: SvgPicture.asset(
- Assets.svg.arrowLeft,
- width: 18,
- height: 18,
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.topNavIconPrimary,
- ),
- onPressed: Navigator.of(context).pop,
- ),
- const SizedBox(width: 12),
- Text("Transactions", style: STextStyles.desktopH3(context)),
- ],
- ),
- )
- : AppBar(
- backgroundColor:
- Theme.of(context).extension<StackColors>()!.background,
- leading: AppBarBackButton(
- onPressed: () async {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future<void>.delayed(
- const Duration(milliseconds: 75),
- );
- }
- if (context.mounted) {
- Navigator.of(context).pop();
- }
- },
- ),
- title: Text(
- "Transactions",
- style: STextStyles.navBarTitle(context),
- ),
- actions: [
- Padding(
- padding: const EdgeInsets.only(
- top: 10,
- bottom: 10,
- right: 20,
- ),
- child: AspectRatio(
- aspectRatio: 1,
- child: AppBarIconButton(
- key: const Key("transactionSearchFilterViewButton"),
- size: 36,
- shadows: const [],
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.background,
- icon: SvgPicture.asset(
- Assets.svg.filter,
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.accentColorDark,
- width: 20,
- height: 20,
- ),
- onPressed: () {
- Navigator.of(context).pushNamed(
- TransactionSearchFilterView.routeName,
- arguments: ref.read(pWalletCoin(walletId)),
- );
- },
+ Assets.svg.filter,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorDark,
+ width: 20,
+ height: 20,
),
+ onPressed: () {
+ Navigator.of(context).pushNamed(
+ TransactionSearchFilterView.routeName,
+ arguments: ref.read(pWalletCoin(walletId)),
+ );
+ },
),
),
- ],
- ),
+ ),
+ ],
+ ),
body: Padding(
padding: EdgeInsets.only(
left: isDesktop ? 20 : 12,
@@ -378,57 +373,57 @@ class _AllTransactionsV2ViewState extends ConsumerState<AllTransactionsV2View> {
_searchString = value;
});
},
- style:
- isDesktop
- ? STextStyles.desktopTextExtraSmall(
- context,
- ).copyWith(
- color:
- Theme.of(context)
- .extension<StackColors>()!
- .textFieldActiveText,
- height: 1.8,
- )
- : STextStyles.field(context),
- decoration: standardInputDecoration(
- "Search...",
- searchFieldFocusNode,
- context,
- desktopMed: isDesktop,
- ).copyWith(
- prefixIcon: Padding(
- padding: EdgeInsets.symmetric(
- horizontal: isDesktop ? 12 : 10,
- vertical: isDesktop ? 18 : 16,
- ),
- child: SvgPicture.asset(
- Assets.svg.search,
- width: isDesktop ? 20 : 16,
- height: isDesktop ? 20 : 16,
- ),
- ),
- suffixIcon:
- _searchController.text.isNotEmpty
+ style: isDesktop
+ ? STextStyles.desktopTextExtraSmall(
+ context,
+ ).copyWith(
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textFieldActiveText,
+ height: 1.8,
+ )
+ : STextStyles.field(context),
+ decoration:
+ standardInputDecoration(
+ "Search...",
+ searchFieldFocusNode,
+ context,
+ desktopMed: isDesktop,
+ ).copyWith(
+ prefixIcon: Padding(
+ padding: EdgeInsets.symmetric(
+ horizontal: isDesktop ? 12 : 10,
+ vertical: isDesktop ? 18 : 16,
+ ),
+ child: SvgPicture.asset(
+ Assets.svg.search,
+ width: isDesktop ? 20 : 16,
+ height: isDesktop ? 20 : 16,
+ ),
+ ),
+ suffixIcon: _searchController.text.isNotEmpty
? Padding(
- padding: const EdgeInsets.only(right: 0),
- child: UnconstrainedBox(
- child: Row(
- children: [
- TextFieldIconButton(
- child: const XIcon(),
- onTap: () async {
- setState(() {
- _searchController.text = "";
- _searchString = "";
- });
- },
- ),
- ],
+ padding: const EdgeInsets.only(
+ right: 0,
),
- ),
- )
+ child: UnconstrainedBox(
+ child: Row(
+ children: [
+ TextFieldIconButton(
+ child: const XIcon(),
+ onTap: () async {
+ setState(() {
+ _searchController.text = "";
+ _searchString = "";
+ });
+ },
+ ),
+ ],
+ ),
+ ),
+ )
: null,
- ),
+ ),
),
),
),
@@ -441,10 +436,9 @@ class _AllTransactionsV2ViewState extends ConsumerState<AllTransactionsV2View> {
label: "Filter",
icon: SvgPicture.asset(
Assets.svg.filter,
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.accentColorDark,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorDark,
width: 20,
height: 20,
),
@@ -480,39 +474,38 @@ class _AllTransactionsV2ViewState extends ConsumerState<AllTransactionsV2View> {
Expanded(
child: Consumer(
builder: (_, ref, __) {
- final criteria =
- ref.watch(transactionFilterProvider.state).state;
+ final criteria = ref
+ .watch(transactionFilterProvider.state)
+ .state;
return FutureBuilder(
- future:
- ref
- .watch(mainDBProvider)
- .isar
- .transactionV2s
- .buildQuery<TransactionV2>(
- whereClauses: [
- IndexWhereClause.equalTo(
- indexName: 'walletId',
- value: [widget.walletId],
- ),
- ],
- filter:
- widget.contractAddress == null
- ? ref
- .watch(pWallets)
- .getWallet(widget.walletId)
- .transactionFilterOperation
- : ref
- .read(pCurrentTokenWallet)!
- .transactionFilterOperation,
- sortBy: [
- const SortProperty(
- property: "timestamp",
- sort: Sort.desc,
- ),
- ],
- )
- .findAll(),
+ future: ref
+ .watch(mainDBProvider)
+ .isar
+ .transactionV2s
+ .buildQuery<TransactionV2>(
+ whereClauses: [
+ IndexWhereClause.equalTo(
+ indexName: 'walletId',
+ value: [widget.walletId],
+ ),
+ ],
+ filter: widget.contractAddress == null
+ ? ref
+ .watch(pWallets)
+ .getWallet(widget.walletId)
+ .transactionFilterOperation
+ : ref
+ .read(pCurrentTokenWallet)!
+ .transactionFilterOperation,
+ sortBy: [
+ const SortProperty(
+ property: "timestamp",
+ sort: Sort.desc,
+ ),
+ ],
+ )
+ .findAll(),
builder: (_, AsyncSnapshot<List<TransactionV2>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
@@ -553,27 +546,26 @@ class _AllTransactionsV2ViewState extends ConsumerState<AllTransactionsV2View> {
child: ListView.separated(
shrinkWrap: true,
primary: false,
- separatorBuilder:
- (context, _) => Container(
+ separatorBuilder: (context, _) =>
+ Container(
height: 1,
- color:
- Theme.of(context)
- .extension<StackColors>()!
- .background,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .background,
),
itemCount: month.transactions.length,
- itemBuilder:
- (context, index) => Padding(
- padding: const EdgeInsets.all(4),
- child: DesktopTransactionCardRow(
- key: Key(
- "transactionCard_key_${month.transactions[index].txid}",
- ),
- transaction:
- month.transactions[index],
- walletId: walletId,
- ),
+ itemBuilder: (context, index) => Padding(
+ padding: const EdgeInsets.all(4),
+ child: DesktopTransactionCardRow(
+ key: Key(
+ "transactionCard_key_"
+ "${month.transactions[index].txid}",
),
+ transaction:
+ month.transactions[index],
+ walletId: walletId,
+ ),
+ ),
),
),
if (!isDesktop)
@@ -785,8 +777,9 @@ class TransactionFilterOptionBarItem extends StatelessWidget {
child: Container(
height: 32,
decoration: BoxDecoration(
- color:
- Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.buttonBackSecondary,
borderRadius: BorderRadius.circular(1000),
),
child: Padding(
@@ -802,8 +795,9 @@ class TransactionFilterOptionBarItem extends StatelessWidget {
label,
textAlign: TextAlign.center,
style: STextStyles.labelExtraExtraSmall(context).copyWith(
- color:
- Theme.of(context).extension<StackColors>()!.textDark,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark,
),
),
),
@@ -849,23 +843,21 @@ class _DesktopTransactionCardRowState
String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
currentChainHeight: height,
minConfirms: minConfirms,
- minCoinbaseConfirms:
- ref
- .read(pWallets)
- .getWallet(widget.walletId)
- .cryptoCurrency
- .minCoinbaseConfirms,
+ minCoinbaseConfirms: ref
+ .read(pWallets)
+ .getWallet(widget.walletId)
+ .cryptoCurrency
+ .minCoinbaseConfirms,
);
@override
void initState() {
walletId = widget.walletId;
- minConfirms =
- ref
- .read(pWallets)
- .getWallet(widget.walletId)
- .cryptoCurrency
- .minConfirms;
+ minConfirms = ref
+ .read(pWallets)
+ .getWallet(widget.walletId)
+ .cryptoCurrency
+ .minConfirms;
_transaction = widget.transaction;
if (_transaction.subType == TransactionSubType.ethToken) {
@@ -897,10 +889,9 @@ class _DesktopTransactionCardRowState
)) {
price = ref.watch(
priceAnd24hChangeNotifierProvider.select(
- (value) =>
- isTokenTx
- ? value.getTokenPrice(_transaction.contractAddress!)?.value
- : value.getPrice(coin)?.value,
+ (value) => isTokenTx
+ ? value.getTokenPrice(_transaction.contractAddress!)?.value
+ : value.getPrice(coin)?.value,
),
);
}
@@ -987,16 +978,15 @@ class _DesktopTransactionCardRowState
if (Util.isDesktop) {
await showDialog<void>(
context: context,
- builder:
- (context) => DesktopDialog(
- maxHeight: MediaQuery.of(context).size.height - 64,
- maxWidth: 580,
- child: tvd.TransactionV2DetailsView(
- transaction: _transaction,
- coin: coin,
- walletId: walletId,
- ),
- ),
+ builder: (context) => DesktopDialog(
+ maxHeight: MediaQuery.of(context).size.height - 64,
+ maxWidth: 640,
+ child: tvd.TransactionV2DetailsView(
+ transaction: _transaction,
+ coin: coin,
+ walletId: walletId,
+ ),
+ ),
);
} else {
unawaited(
@@ -1021,11 +1011,12 @@ class _DesktopTransactionCardRowState
flex: 3,
child: Text(
whatIsIt(_transaction, currentHeight),
- style: STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context).extension<StackColors>()!.textDark,
- ),
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark,
+ ),
),
),
if (kDebugMode)
@@ -1047,11 +1038,12 @@ class _DesktopTransactionCardRowState
flex: 6,
child: Text(
"$prefix${ref.watch(pAmountFormatter(coin)).format(amount, ethContract: ethContract)}",
- style: STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context).extension<StackColors>()!.textDark,
- ),
+ style: STextStyles.desktopTextExtraExtraSmall(context)
+ .copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark,
+ ),
),
),
if (price != null)
@@ -1066,8 +1058,9 @@ class _DesktopTransactionCardRowState
Assets.svg.circleInfo,
width: 20,
height: 20,
- color:
- Theme.of(context).extension<StackColors>()!.textSubtitle2,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textSubtitle2,
),
],
),
diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart
index a8b30fd..5f5f952 100644
--- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart
+++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart
@@ -49,22 +49,20 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
String whatIsIt(CryptoCurrency coin, int currentHeight) =>
_transaction.isCancelled && coin is Ethereum
- ? "Failed"
- : _transaction.statusLabel(
- currentChainHeight: currentHeight,
- minConfirms:
- ref
- .read(pWallets)
- .getWallet(walletId)
- .cryptoCurrency
- .minConfirms,
- minCoinbaseConfirms:
- ref
- .read(pWallets)
- .getWallet(walletId)
- .cryptoCurrency
- .minCoinbaseConfirms,
- );
+ ? "Failed"
+ : _transaction.statusLabel(
+ currentChainHeight: currentHeight,
+ minConfirms: ref
+ .read(pWallets)
+ .getWallet(walletId)
+ .cryptoCurrency
+ .minConfirms,
+ minCoinbaseConfirms: ref
+ .read(pWallets)
+ .getWallet(walletId)
+ .cryptoCurrency
+ .minCoinbaseConfirms,
+ );
@override
void initState() {
@@ -115,10 +113,9 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
)) {
price = ref.watch(
priceAnd24hChangeNotifierProvider.select(
- (value) =>
- isTokenTx
- ? value.getTokenPrice(tokenContract!.address)?.value
- : value.getPrice(coin)?.value,
+ (value) => isTokenTx
+ ? value.getTokenPrice(tokenContract!.address)?.value
+ : value.getPrice(coin)?.value,
),
);
}
@@ -196,16 +193,15 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
- builder:
- (context) => DesktopDialog(
- maxHeight: MediaQuery.of(context).size.height - 64,
- maxWidth: 580,
- child: tvd.TransactionV2DetailsView(
- transaction: _transaction,
- coin: coin,
- walletId: walletId,
- ),
- ),
+ builder: (context) => DesktopDialog(
+ maxHeight: MediaQuery.of(context).size.height - 64,
+ maxWidth: 640,
+ child: tvd.TransactionV2DetailsView(
+ transaction: _transaction,
+ coin: coin,
+ walletId: walletId,
+ ),
+ ),
);
} else {
unawaited(
@@ -246,15 +242,14 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
coin.minConfirms,
coin.minCoinbaseConfirms,
),
- builder:
- (child) => Row(
- children: [
- child,
+ builder: (child) => Row(
+ children: [
+ child,
- const SizedBox(width: 10),
- const CoinTickerTag(ticker: "INSTANT"),
- ],
- ),
+ const SizedBox(width: 10),
+ const CoinTickerTag(ticker: "INSTANT"),
+ ],
+ ),
child: Text(
whatIsIt(coin, currentHeight),
style: STextStyles.itemSubtitle12(context),
diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart
index 748912e..521ed9f 100644
--- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart
+++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart
@@ -448,147 +448,6 @@ class _TransactionV2DetailsViewState
}
}
- Future<bool> showExplorerWarning(String explorer) async {
- final bool? shouldContinue = await showDialog<bool>(
- context: context,
- barrierDismissible: false,
- builder: (_) {
- if (!isDesktop) {
- return StackDialog(
- title: "Attention",
- message:
- "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.",
- icon: Row(
- children: [
- Consumer(
- builder: (_, ref, __) {
- return Checkbox(
- value: ref.watch(
- prefsChangeNotifierProvider.select(
- (value) => value.hideBlockExplorerWarning,
- ),
- ),
- onChanged: (value) {
- if (value is bool) {
- ref
- .read(prefsChangeNotifierProvider)
- .hideBlockExplorerWarning =
- value;
- setState(() {});
- }
- },
- );
- },
- ),
- Text(
- "Never show again",
- style: STextStyles.smallMed14(context),
- ),
- ],
- ),
- leftButton: TextButton(
- onPressed: () {
- Navigator.of(context).pop(false);
- },
- child: Text(
- "Cancel",
- style: STextStyles.button(context).copyWith(
- color: Theme.of(
- context,
- ).extension<StackColors>()!.accentColorDark,
- ),
- ),
- ),
- rightButton: TextButton(
- style: Theme.of(
- context,
- ).extension<StackColors>()!.getPrimaryEnabledButtonStyle(context),
- onPressed: () {
- Navigator.of(context).pop(true);
- },
- child: Text("Continue", style: STextStyles.button(context)),
- ),
- );
- } else {
- return DesktopDialog(
- maxWidth: 550,
- maxHeight: 300,
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20),
- child: Column(
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text("Attention", style: STextStyles.desktopH2(context)),
- Row(
- children: [
- Consumer(
- builder: (_, ref, __) {
- return Checkbox(
- value: ref.watch(
- prefsChangeNotifierProvider.select(
- (value) => value.hideBlockExplorerWarning,
- ),
- ),
- onChanged: (value) {
- if (value is bool) {
- ref
- .read(prefsChangeNotifierProvider)
- .hideBlockExplorerWarning =
- value;
- setState(() {});
- }
- },
- );
- },
- ),
- Text(
- "Never show again",
- style: STextStyles.smallMed14(context),
- ),
- ],
- ),
- ],
- ),
- const SizedBox(height: 16),
- Text(
- "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.",
- style: STextStyles.desktopTextSmall(context),
- ),
- const SizedBox(height: 35),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SecondaryButton(
- width: 200,
- buttonHeight: ButtonHeight.l,
- label: "Cancel",
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop(false);
- },
- ),
- const SizedBox(width: 20),
- PrimaryButton(
- width: 200,
- buttonHeight: ButtonHeight.l,
- label: "Continue",
- onPressed: () {
- Navigator.of(context, rootNavigator: true).pop(true);
- },
- ),
- ],
- ),
- ],
- ),
- ),
- );
- }
- },
- );
- return shouldContinue ?? false;
- }
-
@override
Widget build(BuildContext context) {
final currentHeight = ref.watch(pWalletChainHeight(walletId));
@@ -626,6 +485,16 @@ class _TransactionV2DetailsViewState
);
}
+ final labelStyle = Util.isDesktop
+ ? STextStyles.desktopTextExtraExtraSmall(context)
+ : STextStyles.itemSubtitle(context);
+
+ final detailStyle = Util.isDesktop
+ ? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
+ color: Theme.of(context).extension<StackColors>()!.textDark,
+ )
+ : STextStyles.itemSubtitle12(context);
+
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Background(child: child),
@@ -757,19 +626,7 @@ class _TransactionV2DetailsViewState
children: [
SelectableText(
"$amountPrefix${ref.watch(pAmountFormatter(coin)).format(amount, ethContract: ethContract)}",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.titleBold12(
- context,
- ),
+ style: detailStyle,
),
const SizedBox(height: 2),
if (price != null)
@@ -799,13 +656,7 @@ class _TransactionV2DetailsViewState
);
return SelectableText(
"$amountPrefix$formatted $ticker",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
+ style: labelStyle,
);
},
),
@@ -834,14 +685,7 @@ class _TransactionV2DetailsViewState
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
- Text(
- "Status",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(context),
- ),
+ Text("Status", style: labelStyle),
// Flexible(
// child: FittedBox(
// fit: BoxFit.scaleDown,
@@ -962,13 +806,7 @@ class _TransactionV2DetailsViewState
},
child: Text(
outputLabel,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
+ style: labelStyle,
),
),
const SizedBox(height: 8),
@@ -1132,30 +970,12 @@ class _TransactionV2DetailsViewState
children: [
Text(
"On chain note",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
+ style: labelStyle,
),
const SizedBox(height: 8),
SelectableText(
_transaction.onChainNote ?? "",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
],
),
@@ -1186,13 +1006,7 @@ class _TransactionV2DetailsViewState
coin is Mimblewimblecoin)
? "Local Note"
: "Note ",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
+ style: labelStyle,
),
isDesktop
? IconPencilButton(
@@ -1266,15 +1080,7 @@ class _TransactionV2DetailsViewState
)
?.value ??
"",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(context),
+ style: detailStyle,
),
],
),
@@ -1284,45 +1090,10 @@ class _TransactionV2DetailsViewState
? const _Divider()
: const SizedBox(height: 12),
if (_sparkMemo != null)
- RoundedWhiteContainer(
- padding: isDesktop
- ? const EdgeInsets.all(16)
- : const EdgeInsets.all(12),
- child: Column(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Text(
- "Memo",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
- ),
- ],
- ),
- const SizedBox(height: 8),
- SelectableText(
- _sparkMemo!,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
- ),
- ],
- ),
+ _DetailItem(
+ label: "Memo",
+ detail: _sparkMemo!,
+ vertical: true,
),
isDesktop
? const _Divider()
@@ -1340,16 +1111,7 @@ class _TransactionV2DetailsViewState
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
- Text(
- "Date",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
- ),
+ Text("Date", style: labelStyle),
if (isDesktop)
const SizedBox(height: 2),
if (isDesktop)
@@ -1357,19 +1119,7 @@ class _TransactionV2DetailsViewState
Format.extractDateFrom(
_transaction.timestamp,
),
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
],
),
@@ -1378,17 +1128,7 @@ class _TransactionV2DetailsViewState
Format.extractDateFrom(
_transaction.timestamp,
),
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
if (isDesktop)
IconCopyButton(
@@ -1444,33 +1184,14 @@ class _TransactionV2DetailsViewState
children: [
Text(
"Transaction fee",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
+ style: labelStyle,
),
if (isDesktop)
const SizedBox(height: 2),
if (isDesktop)
SelectableText(
feeString,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color:
- Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
if (supportsRbf && !confirmedTxn)
const SizedBox(height: 8),
@@ -1484,19 +1205,7 @@ class _TransactionV2DetailsViewState
if (!isDesktop)
SelectableText(
feeString,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
if (isDesktop)
IconCopyButton(data: feeString),
@@ -1535,7 +1244,8 @@ class _TransactionV2DetailsViewState
);
if (widget.coin is! Epiccash && confirmed) {
height =
- "${_transaction.height == 0 ? "Unknown" : _transaction.height}";
+ "${_transaction.height == 0 ? "Unknow"
+ "n" : _transaction.height}";
} else {
height = confirms > 0
? "${_transaction.height}"
@@ -1563,54 +1273,21 @@ class _TransactionV2DetailsViewState
children: [
Text(
"Block height",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
+ style: labelStyle,
),
if (isDesktop)
const SizedBox(height: 2),
if (isDesktop)
SelectableText(
height,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color:
- Theme.of(
- context,
- )
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
],
),
if (!isDesktop)
SelectableText(
height,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
if (isDesktop)
IconCopyButton(data: height),
@@ -1636,54 +1313,21 @@ class _TransactionV2DetailsViewState
children: [
Text(
"Confirmations",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
+ style: labelStyle,
),
if (isDesktop)
const SizedBox(height: 2),
if (isDesktop)
SelectableText(
confirmations,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color:
- Theme.of(
- context,
- )
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
],
),
if (!isDesktop)
SelectableText(
confirmations,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
if (isDesktop)
IconCopyButton(data: height),
@@ -1701,40 +1345,9 @@ class _TransactionV2DetailsViewState
: const SizedBox(height: 12),
if (coin is Ethereum &&
_transaction.type != TransactionType.incoming)
- RoundedWhiteContainer(
- padding: isDesktop
- ? const EdgeInsets.all(16)
- : const EdgeInsets.all(12),
- child: Row(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- mainAxisAlignment:
- MainAxisAlignment.spaceBetween,
- children: [
- Text(
- "Nonce",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(context),
- ),
- SelectableText(
- _transaction.nonce.toString(),
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
- ),
- ],
- ),
+ _DetailItem(
+ label: "Nonce",
+ detail: _transaction.nonce.toString(),
),
if (coin is Salvium &&
_transaction.salviumTypeString != null)
@@ -1743,86 +1356,21 @@ class _TransactionV2DetailsViewState
: const SizedBox(height: 12),
if (coin is Salvium &&
_transaction.salviumTypeString != null)
- RoundedWhiteContainer(
- padding: isDesktop
- ? const EdgeInsets.all(16)
- : const EdgeInsets.all(12),
- child: Column(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Text(
- "Type",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
- ),
- ],
- ),
- const SizedBox(height: 8),
- SelectableText(
- _transaction.salviumTypeString!,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
- ),
- ],
- ),
+ _DetailItem(
+ label: "Type",
+ detail: _transaction.salviumTypeString!,
+ vertical: true,
),
if (kDebugMode)
isDesktop
? const _Divider()
: const SizedBox(height: 12),
if (kDebugMode)
- RoundedWhiteContainer(
- padding: isDesktop
- ? const EdgeInsets.all(16)
- : const EdgeInsets.all(12),
- child: Row(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- mainAxisAlignment:
- MainAxisAlignment.spaceBetween,
- children: [
- Text(
- "Tx sub type",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(context),
- ),
- SelectableText(
- _transaction.subType.toString(),
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<StackColors>()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
- ),
- ],
- ),
+ _DetailItem(
+ label: "Tx sub type",
+ detail: _transaction.subType.toString(),
),
+
if (hasTxKeyProbably)
isDesktop
? const _Divider()
@@ -1835,299 +1383,11 @@ class _TransactionV2DetailsViewState
isDesktop
? const _Divider()
: const SizedBox(height: 12),
-
- _transaction.txid.startsWith("mweb_outputId_") &&
- _transaction.subType ==
- TransactionSubType.mweb
- ? RoundedWhiteContainer(
- padding: isDesktop
- ? const EdgeInsets.all(16)
- : const EdgeInsets.all(12),
- child: Row(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- mainAxisAlignment:
- MainAxisAlignment.spaceBetween,
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- ConditionalParent(
- condition: !isDesktop,
- builder: (child) => Row(
- children: [
- Expanded(child: child),
- SimpleCopyButton(
- data: _transaction.txid
- .replaceFirst(
- "mweb_outputId_",
- "",
- ),
- ),
- ],
- ),
- child: Text(
- "MWEB Output ID",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
- ),
- ),
- const SizedBox(height: 8),
- SelectableText(
- _transaction.txid
- .replaceFirst(
- "mweb_outputId_",
- "",
- ),
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color:
- Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
- ),
- // if (coin is Litecoin &&
- // coin.network ==
- // CryptoCurrencyNetwork
- // .main)
- // const SizedBox(height: 8),
- // if (coin is Litecoin &&
- // coin.network ==
- // CryptoCurrencyNetwork
- // .main)
- // CustomTextButton(
- // text:
- // "Open in block explorer",
- // onTap: () async {
- // final uri =
- // getBlockExplorerTransactionUrlFor(
- // coin: coin,
- // txid: _transaction
- // .txid
- // .replaceFirst(
- // "mweb_outputId_",
- // "",
- // ),
- // );
- //
- // if (ref
- // .read(
- // prefsChangeNotifierProvider,
- // )
- // .hideBlockExplorerWarning ==
- // false) {
- // final shouldContinue =
- // await showExplorerWarning(
- // "${uri.scheme}://${uri.host}",
- // );
- //
- // if (!shouldContinue) {
- // return;
- // }
- // }
- // try {
- // await launchUrl(
- // uri,
- // mode:
- // LaunchMode
- // .externalApplication,
- // );
- // } catch (_) {
- // if (context.mounted) {
- // unawaited(
- // showDialog<void>(
- // context: context,
- // builder:
- // (
- // _,
- // ) => StackOkDialog(
- // title:
- // "Could not open in block explorer",
- // message:
- // "Failed to open \"${uri.toString()}\"",
- // ),
- // ),
- // );
- // }
- // }
- // },
- // ),
- ],
- ),
- ),
- if (isDesktop)
- const SizedBox(width: 12),
- if (isDesktop)
- IconCopyButton(
- data: _transaction.txid
- .replaceFirst(
- "mweb_outputId_",
- "",
- ),
- ),
- ],
- ),
- )
- : RoundedWhiteContainer(
- padding: isDesktop
- ? const EdgeInsets.all(16)
- : const EdgeInsets.all(12),
- child: Row(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- mainAxisAlignment:
- MainAxisAlignment.spaceBetween,
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- ConditionalParent(
- condition: !isDesktop,
- builder: (child) => Row(
- children: [
- Expanded(child: child),
- SimpleCopyButton(
- data: _transaction.txid,
- ),
- ],
- ),
- child: Text(
- "Transaction ID",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
- ),
- ),
- const SizedBox(height: 8),
- // Flexible(
- // child: FittedBox(
- // fit: BoxFit.scaleDown,
- // child:
- SelectableText(
- _transaction.txid,
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color:
- Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
- ),
- if (coin is! Epiccash &&
- coin is! Mimblewimblecoin)
- const SizedBox(height: 8),
- if (coin is! Epiccash &&
- coin is! Mimblewimblecoin)
- CustomTextButton(
- text:
- "Open in block explorer",
- onTap: () async {
- final uri =
- getBlockExplorerTransactionUrlFor(
- coin: coin,
- txid: _transaction
- .txid,
- );
-
- if (ref
- .read(
- prefsChangeNotifierProvider,
- )
- .hideBlockExplorerWarning ==
- false) {
- final shouldContinue =
- await showExplorerWarning(
- "${uri.scheme}://${uri.host}",
- );
-
- if (!shouldContinue) {
- return;
- }
- }
-
- // ref
- // .read(
- // shouldShowLockscreenOnResumeStateProvider
- // .state)
- // .state = false;
- try {
- await launchUrl(
- uri,
- mode: LaunchMode
- .externalApplication,
- );
- } catch (_) {
- if (context.mounted) {
- unawaited(
- showDialog<void>(
- context: context,
- builder: (_) => StackOkDialog(
- title:
- "Could not open in block explorer",
- message:
- "Failed to open \"${uri.toString()}\"",
- maxWidth:
- Util.isDesktop
- ? 400
- : null,
- ),
- ),
- );
- }
- } finally {
- // Future<void>.delayed(
- // const Duration(seconds: 1),
- // () => ref
- // .read(
- // shouldShowLockscreenOnResumeStateProvider
- // .state)
- // .state = true,
- // );
- }
- },
- ),
- // ),
- // ),
- ],
- ),
- ),
- if (isDesktop)
- const SizedBox(width: 12),
- if (isDesktop)
- IconCopyButton(
- data: _transaction.txid,
- ),
- ],
- ),
- ),
+ _TxidDetailItem(
+ coin: coin,
+ txid: _transaction.txid,
+ subType: _transaction.subType,
+ ),
// if ((coin is FiroTestNet || coin is Firo) &&
// _transaction.subType == "mint")
// const SizedBox(
@@ -2224,35 +1484,14 @@ class _TransactionV2DetailsViewState
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
- Text(
- "Slate ID",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- )
- : STextStyles.itemSubtitle(
- context,
- ),
- ),
+ Text("Slate ID", style: labelStyle),
// Flexible(
// child: FittedBox(
// fit: BoxFit.scaleDown,
// child:
SelectableText(
_transaction.slateId ?? "Unknown",
- style: isDesktop
- ? STextStyles.desktopTextExtraExtraSmall(
- context,
- ).copyWith(
- color: Theme.of(context)
- .extension<
- StackColors
- >()!
- .textDark,
- )
- : STextStyles.itemSubtitle12(
- context,
- ),
+ style: detailStyle,
),
// ),
// ),
@@ -2429,7 +1668,8 @@ class _TransactionV2DetailsViewState
showFloatingFlushBar(
type: FlushBarType.warning,
message:
- "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin",
+ "ERROR: Wallet type is not "
+ "Epic Cash or MimbleWimbleCoin",
context: context,
),
);
@@ -2508,7 +1748,9 @@ class OutputCard extends ConsumerWidget {
}
class _Divider extends StatelessWidget {
- const _Divider({super.key});
+ const _Divider(
+ // {super.key}
+ );
@override
Widget build(BuildContext context) {
@@ -2591,3 +1833,387 @@ class IconPencilButton extends StatelessWidget {
);
}
}
+
+class _DetailItemBase extends StatelessWidget {
+ const _DetailItemBase({
+ // super.key,
+ required this.child,
+ });
+
+ final Widget child;
+
+ @override
+ Widget build(BuildContext context) {
+ return RoundedWhiteContainer(
+ padding: Util.isDesktop
+ ? const EdgeInsets.all(16)
+ : const EdgeInsets.all(12),
+
+ child: child,
+ );
+ }
+}
+
+class _DetailItem extends StatelessWidget {
+ const _DetailItem({
+ // super.key,
+ required this.label,
+ required this.detail,
+ this.vertical = false,
+ });
+
+ final String label, detail;
+ final bool vertical;
+
+ @override
+ Widget build(BuildContext context) {
+ final labelStyle = Util.isDesktop
+ ? STextStyles.desktopTextExtraExtraSmall(context)
+ : STextStyles.itemSubtitle(context);
+
+ final detailStyle = Util.isDesktop
+ ? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
+ color: Theme.of(context).extension<StackColors>()!.textDark,
+ )
+ : STextStyles.itemSubtitle12(context);
+
+ return _DetailItemBase(
+ child: vertical
+ ? Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(children: [Text(label, style: labelStyle)]),
+ const SizedBox(height: 8),
+ SelectableText(detail, style: detailStyle),
+ ],
+ )
+ : Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(label, style: labelStyle),
+ SelectableText(detail, style: detailStyle),
+ ],
+ ),
+ );
+ }
+}
+
+class _TxidDetailItem extends ConsumerStatefulWidget {
+ const _TxidDetailItem({
+ // super.key,
+ required this.coin,
+ required this.txid,
+ required this.subType,
+ });
+
+ final CryptoCurrency coin;
+ final String txid;
+ final TransactionSubType subType;
+
+ @override
+ ConsumerState<_TxidDetailItem> createState() => _TxidDetailItemState();
+}
+
+class _TxidDetailItemState extends ConsumerState<_TxidDetailItem> {
+ Future<bool> showExplorerWarning(String explorer) async {
+ final warningMessage =
+ "You are about to view this transaction in a block explorer. "
+ "The explorer may log your IP address and link it to the "
+ "transaction. Only proceed if you trust $explorer.";
+
+ final bool? shouldContinue = await showDialog<bool>(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) {
+ if (!Util.isDesktop) {
+ return StackDialog(
+ title: "Attention",
+ message: warningMessage,
+ icon: Row(
+ children: [
+ Consumer(
+ builder: (_, ref, __) {
+ return Checkbox(
+ value: ref.watch(
+ prefsChangeNotifierProvider.select(
+ (value) => value.hideBlockExplorerWarning,
+ ),
+ ),
+ onChanged: (value) {
+ if (value is bool) {
+ ref
+ .read(prefsChangeNotifierProvider)
+ .hideBlockExplorerWarning =
+ value;
+ setState(() {});
+ }
+ },
+ );
+ },
+ ),
+ Text(
+ "Never show again",
+ style: STextStyles.smallMed14(context),
+ ),
+ ],
+ ),
+ leftButton: TextButton(
+ onPressed: () {
+ Navigator.of(context).pop(false);
+ },
+ child: Text(
+ "Cancel",
+ style: STextStyles.button(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorDark,
+ ),
+ ),
+ ),
+ rightButton: TextButton(
+ style: Theme.of(
+ context,
+ ).extension<StackColors>()!.getPrimaryEnabledButtonStyle(context),
+ onPressed: () {
+ Navigator.of(context).pop(true);
+ },
+ child: Text("Continue", style: STextStyles.button(context)),
+ ),
+ );
+ } else {
+ return DesktopDialog(
+ maxWidth: 550,
+ maxHeight: 300,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20),
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text("Attention", style: STextStyles.desktopH2(context)),
+ Row(
+ children: [
+ Consumer(
+ builder: (_, ref, __) {
+ return Checkbox(
+ value: ref.watch(
+ prefsChangeNotifierProvider.select(
+ (value) => value.hideBlockExplorerWarning,
+ ),
+ ),
+ onChanged: (value) {
+ if (value is bool) {
+ ref
+ .read(prefsChangeNotifierProvider)
+ .hideBlockExplorerWarning =
+ value;
+ setState(() {});
+ }
+ },
+ );
+ },
+ ),
+ Text(
+ "Never show again",
+ style: STextStyles.smallMed14(context),
+ ),
+ ],
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Text(
+ warningMessage,
+ style: STextStyles.desktopTextSmall(context),
+ ),
+ const SizedBox(height: 35),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SecondaryButton(
+ width: 200,
+ buttonHeight: ButtonHeight.l,
+ label: "Cancel",
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop(false);
+ },
+ ),
+ const SizedBox(width: 20),
+ PrimaryButton(
+ width: 200,
+ buttonHeight: ButtonHeight.l,
+ label: "Continue",
+ onPressed: () {
+ Navigator.of(context, rootNavigator: true).pop(true);
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+ },
+ );
+ return shouldContinue ?? false;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final isDesktop = Util.isDesktop;
+
+ return _DetailItemBase(
+ child:
+ widget.txid.startsWith("mweb_outputId_") &&
+ widget.subType == TransactionSubType.mweb
+ ? Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+
+ children: [
+ ConditionalParent(
+ condition: !isDesktop,
+ builder: (child) => Row(
+ children: [
+ Expanded(child: child),
+ SimpleCopyButton(
+ data: widget.txid.replaceFirst(
+ "mweb_outputId_",
+ "",
+ ),
+ ),
+ ],
+ ),
+ child: Text(
+ "MWEB Output ID",
+ style: isDesktop
+ ? STextStyles.desktopTextExtraExtraSmall(context)
+ : STextStyles.itemSubtitle(context),
+ ),
+ ),
+ const SizedBox(height: 8),
+ SelectableText(
+ widget.txid.replaceFirst("mweb_outputId_", ""),
+ style: isDesktop
+ ? STextStyles.desktopTextExtraExtraSmall(
+ context,
+ ).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark,
+ )
+ : STextStyles.itemSubtitle12(context),
+ ),
+ ],
+ ),
+ ),
+ if (isDesktop) const SizedBox(width: 12),
+ if (isDesktop)
+ IconCopyButton(
+ data: widget.txid.replaceFirst("mweb_outputId_", ""),
+ ),
+ ],
+ )
+ : Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ConditionalParent(
+ condition: !isDesktop,
+ builder: (child) => Row(
+ children: [
+ Expanded(child: child),
+ SimpleCopyButton(data: widget.txid),
+ ],
+ ),
+ child: Text(
+ "Transaction ID",
+ style: isDesktop
+ ? STextStyles.desktopTextExtraExtraSmall(context)
+ : STextStyles.itemSubtitle(context),
+ ),
+ ),
+ const SizedBox(height: 8),
+
+ SelectableText(
+ widget.txid,
+ style: isDesktop
+ ? STextStyles.desktopTextExtraExtraSmall(
+ context,
+ ).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textDark,
+ )
+ : STextStyles.itemSubtitle12(context),
+ ),
+ if (widget.coin is! Epiccash &&
+ widget.coin is! Mimblewimblecoin)
+ const SizedBox(height: 8),
+ if (widget.coin is! Epiccash &&
+ widget.coin is! Mimblewimblecoin)
+ CustomTextButton(
+ text: "Open in block explorer",
+ onTap: () async {
+ final uri = getBlockExplorerTransactionUrlFor(
+ coin: widget.coin,
+ txid: widget.txid,
+ );
+
+ if (ref
+ .read(prefsChangeNotifierProvider)
+ .hideBlockExplorerWarning ==
+ false) {
+ final shouldContinue = await showExplorerWarning(
+ "${uri.scheme}://${uri.host}",
+ );
+
+ if (!shouldContinue) {
+ return;
+ }
+ }
+
+ try {
+ await launchUrl(
+ uri,
+ mode: LaunchMode.externalApplication,
+ );
+ } catch (_) {
+ if (context.mounted) {
+ unawaited(
+ showDialog<void>(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Could not open in block explorer",
+ message:
+ "Failed to open "
+ "\"${uri.toString()}\"",
+ maxWidth: Util.isDesktop ? 400 : null,
+ ),
+ ),
+ );
+ }
+ }
+ },
+ ),
+ ],
+ ),
+ ),
+ if (isDesktop) const SizedBox(width: 12),
+ if (isDesktop) IconCopyButton(data: widget.txid),
+ ],
+ ),
+ );
+ }
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.