CW-1021-Address-formatting-enhancements (#2141)

* Implement visual formatting for addresses

* fix minor issues

* Update transaction_details_page.dart

* Update transaction_details_page.dart

* fix multi recipient address formatting

* Update address_cell.dart
This commit is contained in:
Serhii 2025-04-10 17:16:43 +03:00 committed by GitHub
parent c1e9668b1e
commit 3f25d69244
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 292 additions and 123 deletions

View file

@ -381,6 +381,7 @@ class CakePayBuyCardDetailPage extends BasePage {
return ConfirmSendingBottomSheet( return ConfirmSendingBottomSheet(
key: ValueKey('send_page_confirm_sending_dialog_key'), key: ValueKey('send_page_confirm_sending_dialog_key'),
currentTheme: currentTheme, currentTheme: currentTheme,
walletType: cakePayPurchaseViewModel.sendViewModel.walletType,
paymentId: S.of(popupContext).payment_id, paymentId: S.of(popupContext).payment_id,
paymentIdValue: order?.orderId, paymentIdValue: order?.orderId,
expirationTime: cakePayPurchaseViewModel.formattedRemainingTime, expirationTime: cakePayPurchaseViewModel.formattedRemainingTime,

View file

@ -264,6 +264,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
return ConfirmSendingBottomSheet( return ConfirmSendingBottomSheet(
key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'), key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'),
currentTheme: widget.currentTheme, currentTheme: widget.currentTheme,
walletType: widget.exchangeTradeViewModel.sendViewModel.walletType,
titleText: S.of(bottomSheetContext).confirm_transaction, titleText: S.of(bottomSheetContext).confirm_transaction,
titleIconPath: titleIconPath:
widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency.iconPath, widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency.iconPath,

View file

@ -20,6 +20,7 @@ class AddressListPage extends BasePage {
children: <Widget>[ children: <Widget>[
AddressList( AddressList(
addressListViewModel: addressListViewModel, addressListViewModel: addressListViewModel,
currentTheme: currentTheme,
onSelect: (String address) async { onSelect: (String address) async {
Navigator.of(context).pop(address); Navigator.of(context).pop(address);
}, },

View file

@ -124,7 +124,7 @@ class ReceivePage extends BasePage {
isLight: currentTheme.type == ThemeType.light, isLight: currentTheme.type == ThemeType.light,
), ),
), ),
AddressList(addressListViewModel: addressListViewModel), AddressList(addressListViewModel: addressListViewModel, currentTheme: currentTheme),
Padding( Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32), padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text( child: Text(

View file

@ -1,7 +1,11 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.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_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_slidable/flutter_slidable.dart';
@ -14,6 +18,8 @@ class AddressCell extends StatelessWidget {
required this.isPrimary, required this.isPrimary,
required this.backgroundColor, required this.backgroundColor,
required this.textColor, required this.textColor,
required this.walletType,
required this.currentTheme,
this.onTap, this.onTap,
this.onEdit, this.onEdit,
this.onHide, this.onHide,
@ -30,6 +36,8 @@ class AddressCell extends StatelessWidget {
required bool isCurrent, required bool isCurrent,
required Color backgroundColor, required Color backgroundColor,
required Color textColor, required Color textColor,
required WalletType walletType,
required ThemeBase currentTheme,
Function(String)? onTap, Function(String)? onTap,
bool hasBalance = false, bool hasBalance = false,
bool hasReceived = false, bool hasReceived = false,
@ -45,6 +53,8 @@ class AddressCell extends StatelessWidget {
isPrimary: item.isPrimary, isPrimary: item.isPrimary,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
textColor: textColor, textColor: textColor,
walletType: walletType,
currentTheme: currentTheme,
onTap: onTap, onTap: onTap,
onEdit: onEdit, onEdit: onEdit,
onHide: onHide, onHide: onHide,
@ -62,6 +72,8 @@ class AddressCell extends StatelessWidget {
final bool isPrimary; final bool isPrimary;
final Color backgroundColor; final Color backgroundColor;
final Color textColor; final Color textColor;
final WalletType walletType;
final ThemeBase currentTheme;
final Function(String)? onTap; final Function(String)? onTap;
final Function()? onEdit; final Function()? onEdit;
final Function()? onHide; final Function()? onHide;
@ -73,21 +85,6 @@ class AddressCell extends StatelessWidget {
final bool hasBalance; final bool hasBalance;
final bool hasReceived; final bool hasReceived;
static const int addressPreviewLength = 8;
String get formattedAddress {
final formatIfCashAddr = address.replaceAll('bitcoincash:', '');
if (formatIfCashAddr.length <= (name.isNotEmpty ? 16 : 43)) {
return formatIfCashAddr;
} else {
return formatIfCashAddr.substring(0, addressPreviewLength) +
'...' +
formatIfCashAddr.substring(
formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Widget cell = InkWell( final Widget cell = InkWell(
@ -139,16 +136,14 @@ class AddressCell extends StatelessWidget {
], ],
), ),
Flexible( Flexible(
child: AutoSizeText( child: AddressFormatter.buildSegmentedAddress(
responsiveLayoutUtil.shouldRenderTabletUI ? address : formattedAddress, address: address,
maxLines: 1, walletType: walletType,
overflow: TextOverflow.ellipsis, shouldTruncate: name.isNotEmpty || address.length > 43 ,
style: TextStyle( evenTextStyle: TextStyle(
fontSize: isChange ? 10 : 14, fontSize: isChange ? 10 : 14,
color: textColor, color: textColor
), ))),
),
),
], ],
), ),
if (hasBalance || hasReceived) if (hasBalance || hasReceived)

View file

@ -1,5 +1,3 @@
import 'dart:math';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
@ -9,6 +7,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart'; import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/list_item.dart'; import 'package:cake_wallet/utils/list_item.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
@ -20,16 +19,17 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class AddressList extends StatefulWidget { class AddressList extends StatefulWidget {
const AddressList({ const AddressList({
super.key, super.key,
required this.addressListViewModel, required this.addressListViewModel,
required this.currentTheme,
this.onSelect, this.onSelect,
}); });
final WalletAddressListViewModel addressListViewModel; final WalletAddressListViewModel addressListViewModel;
final ThemeBase currentTheme;
final Function(String)? onSelect; final Function(String)? onSelect;
@override @override
@ -161,6 +161,8 @@ class _AddressListState extends State<AddressList> {
return AddressCell.fromItem( return AddressCell.fromItem(
item, item,
isCurrent: isCurrent, isCurrent: isCurrent,
currentTheme: widget.currentTheme,
walletType: widget.addressListViewModel.type,
hasBalance: widget.addressListViewModel.isBalanceAvailable, hasBalance: widget.addressListViewModel.isBalanceAvailable,
hasReceived: widget.addressListViewModel.isReceivedAvailable, hasReceived: widget.addressListViewModel.isReceivedAvailable,
// hasReceived: // hasReceived:

View file

@ -5,6 +5,7 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/brightness_util.dart'; import 'package:cake_wallet/utils/brightness_util.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_bar.dart';
@ -160,16 +161,15 @@ class QRWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Text( child: AddressFormatter.buildSegmentedAddress(
addressUri.address, address: addressUri.address,
walletType: addressListViewModel.type,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( evenTextStyle: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: color:
Theme.of(context).extension<DashboardPageTheme>()!.textColor), Theme.of(context).extension<DashboardPageTheme>()!.textColor))),
),
),
Padding( Padding(
padding: EdgeInsets.only(left: 12), padding: EdgeInsets.only(left: 12),
child: copyImage, child: copyImage,

View file

@ -558,6 +558,7 @@ class SendPage extends BasePage {
key: ValueKey('send_page_confirm_sending_dialog_key'), key: ValueKey('send_page_confirm_sending_dialog_key'),
titleText: S.of(bottomSheetContext).confirm_transaction, titleText: S.of(bottomSheetContext).confirm_transaction,
currentTheme: currentTheme, currentTheme: currentTheme,
walletType: sendViewModel.walletType,
titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath, titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath,
currency: sendViewModel.selectedCryptoCurrency, currency: sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount, amount: S.of(bottomSheetContext).send_amount,

View file

@ -190,6 +190,7 @@ class RBFDetailsPage extends BasePage {
key: ValueKey('rbf_confirm_sending_bottom_sheet'), key: ValueKey('rbf_confirm_sending_bottom_sheet'),
titleText: S.of(bottomSheetContext).confirm_transaction, titleText: S.of(bottomSheetContext).confirm_transaction,
currentTheme: currentTheme, currentTheme: currentTheme,
walletType: transactionDetailsViewModel.sendViewModel.walletType,
titleIconPath: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath, titleIconPath: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency, currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount, amount: S.of(bottomSheetContext).send_amount,

View file

@ -9,8 +9,11 @@ import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart'; import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
import 'package:cake_wallet/src/widgets/list_row.dart'; import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart'; import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
@ -39,13 +42,28 @@ class TransactionDetailsPage extends BasePage {
final item = transactionDetailsViewModel.items[index]; final item = transactionDetailsViewModel.items[index];
if (item is StandartListItem) { if (item is StandartListItem) {
Widget? addressTextWidget;
if (item.title.toLowerCase() == 'recipient addresses' ||
item.title.toLowerCase() == 'source address') {
addressTextWidget = getFormattedAddress(
context: context,
value: item.value,
walletType: transactionDetailsViewModel.sendViewModel.walletType,
);
}
return GestureDetector( return GestureDetector(
key: item.key, key: item.key,
onTap: () { onTap: () {
Clipboard.setData(ClipboardData(text: item.value)); Clipboard.setData(ClipboardData(text: item.value));
showBar<void>(context, S.of(context).transaction_details_copied(item.title)); showBar<void>(context, S.of(context).transaction_details_copied(item.title));
}, },
child: ListRow(title: '${item.title}:', value: item.value), child: ListRow(
title: '${item.title}:',
value: item.value,
textWidget: addressTextWidget,
),
); );
} }
@ -91,4 +109,80 @@ class TransactionDetailsPage extends BasePage {
], ],
); );
} }
Widget getFormattedAddress({
required BuildContext context,
required String value,
required WalletType walletType,
}) {
final textStyle = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
);
final List<Widget> children = [];
final bool hasDoubleNewline = value.contains('\n\n');
if (hasDoubleNewline) {
final blocks = value
.split('\n\n')
.map((b) => b.trim())
.where((b) => b.isNotEmpty)
.toList();
for (final block in blocks) {
final lines = block
.split('\n')
.map((l) => l.trim())
.where((l) => l.isNotEmpty)
.toList();
if (lines.length > 1) {
children.add(Text(lines.first, style: textStyle));
for (int i = 1; i < lines.length; i++) {
children.add(
AddressFormatter.buildSegmentedAddress(
address: lines[i],
walletType: walletType,
evenTextStyle: textStyle,
),
);
}
} else {
children.add(
AddressFormatter.buildSegmentedAddress(
address: lines.first,
walletType: walletType,
evenTextStyle: textStyle,
),
);
}
children.add(SizedBox(height: 8));
}
} else {
final lines = value
.split('\n')
.map((l) => l.trim())
.where((l) => l.isNotEmpty)
.toList();
bool firstLineIsContactName = (lines.length > 1 && lines.first.length < 20);
int startIndex = 0;
if (firstLineIsContactName) {
children.add(Text(lines.first, style: textStyle));
startIndex = 1;
}
for (int i = startIndex; i < lines.length; i++) {
children.add(
AddressFormatter.buildSegmentedAddress(
address: lines[i],
walletType: walletType,
evenTextStyle: textStyle,
),
);
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
);
}
} }

View file

@ -5,11 +5,12 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/filter_theme.dart'; import 'package:cake_wallet/themes/extensions/filter_theme.dart';
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'base_bottom_sheet_widget.dart'; import 'base_bottom_sheet_widget.dart';
@ -27,6 +28,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
final String feeFiatAmount; final String feeFiatAmount;
final List<Output> outputs; final List<Output> outputs;
final VoidCallback onSlideComplete; final VoidCallback onSlideComplete;
final WalletType walletType;
final PendingChange? change; final PendingChange? change;
final bool isOpenCryptoPay; final bool isOpenCryptoPay;
@ -46,6 +48,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
required this.feeFiatAmount, required this.feeFiatAmount,
required this.outputs, required this.outputs,
required this.onSlideComplete, required this.onSlideComplete,
required this.walletType,
this.change, this.change,
this.isOpenCryptoPay = false, this.isOpenCryptoPay = false,
Key? key, Key? key,
@ -91,6 +94,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
itemTitle: paymentId!, itemTitle: paymentId!,
currentTheme: currentTheme, currentTheme: currentTheme,
itemTitleTextStyle: itemTitleTextStyle, itemTitleTextStyle: itemTitleTextStyle,
walletType: walletType,
isBatchSending: false, isBatchSending: false,
amount: '', amount: '',
address: paymentIdValue!, address: paymentIdValue!,
@ -139,6 +143,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
name: isBatchSending ? batchContactTitle : contactName, name: isBatchSending ? batchContactTitle : contactName,
address: _address, address: _address,
amount: _amount, amount: _amount,
walletType: walletType,
isBatchSending: isBatchSending, isBatchSending: isBatchSending,
itemTitleTextStyle: itemTitleTextStyle, itemTitleTextStyle: itemTitleTextStyle,
itemSubTitleTextStyle: itemSubTitleTextStyle, itemSubTitleTextStyle: itemSubTitleTextStyle,
@ -149,6 +154,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
currentTheme: currentTheme, currentTheme: currentTheme,
itemTitleTextStyle: itemTitleTextStyle, itemTitleTextStyle: itemTitleTextStyle,
isBatchSending: isBatchSending, isBatchSending: isBatchSending,
walletType: walletType,
amount: _amount, amount: _amount,
address: _address, address: _address,
itemSubTitleTextStyle: itemSubTitleTextStyle, itemSubTitleTextStyle: itemSubTitleTextStyle,
@ -166,6 +172,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
address: change!.address, address: change!.address,
amount: change!.amount + ' ${currency.title}', amount: change!.amount + ' ${currency.title}',
isBatchSending: true, isBatchSending: true,
walletType: walletType,
itemTitleTextStyle: itemTitleTextStyle, itemTitleTextStyle: itemTitleTextStyle,
itemSubTitleTextStyle: itemSubTitleTextStyle, itemSubTitleTextStyle: itemSubTitleTextStyle,
tileBackgroundColor: tileBackgroundColor, tileBackgroundColor: tileBackgroundColor,
@ -275,6 +282,7 @@ class AddressTile extends StatelessWidget {
required this.address, required this.address,
required this.itemSubTitleTextStyle, required this.itemSubTitleTextStyle,
required this.tileBackgroundColor, required this.tileBackgroundColor,
required this.walletType,
}); });
final String itemTitle; final String itemTitle;
@ -285,18 +293,10 @@ class AddressTile extends StatelessWidget {
final String address; final String address;
final TextStyle itemSubTitleTextStyle; final TextStyle itemSubTitleTextStyle;
final Color tileBackgroundColor; final Color tileBackgroundColor;
final WalletType walletType;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final addressTextStyle = TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: currentTheme.type == ThemeType.bright
? Theme.of(context).extension<CakeTextTheme>()!.titleColor.withOpacity(0.5)
: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
);
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -313,41 +313,20 @@ class AddressTile extends StatelessWidget {
if (isBatchSending) Text(amount, style: itemTitleTextStyle), if (isBatchSending) Text(amount, style: itemTitleTextStyle),
], ],
), ),
buildSegmentedAddress( AddressFormatter.buildSegmentedAddress(
address: address, address: address,
evenTextStyle: addressTextStyle, walletType: walletType,
oddTextStyle: itemSubTitleTextStyle, evenTextStyle: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none)
), ),
], ],
), ),
); );
} }
Widget buildSegmentedAddress({
required String address,
int chunkSize = 6,
required TextStyle evenTextStyle,
required TextStyle oddTextStyle,
}) {
final spans = <TextSpan>[];
int index = 0;
for (int i = 0; i < address.length; i += chunkSize) {
final chunk = address.substring(i, math.min(i + chunkSize, address.length));
final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle;
spans.add(
TextSpan(text: '$chunk ', style: style),
);
index++;
}
return RichText(
text: TextSpan(children: spans, style: evenTextStyle),
overflow: TextOverflow.visible,
);
}
} }
class AddressExpansionTile extends StatelessWidget { class AddressExpansionTile extends StatelessWidget {
@ -362,6 +341,7 @@ class AddressExpansionTile extends StatelessWidget {
required this.itemTitleTextStyle, required this.itemTitleTextStyle,
required this.itemSubTitleTextStyle, required this.itemSubTitleTextStyle,
required this.tileBackgroundColor, required this.tileBackgroundColor,
required this.walletType,
}); });
final String contactType; final String contactType;
@ -373,19 +353,10 @@ class AddressExpansionTile extends StatelessWidget {
final TextStyle itemTitleTextStyle; final TextStyle itemTitleTextStyle;
final TextStyle itemSubTitleTextStyle; final TextStyle itemSubTitleTextStyle;
final Color tileBackgroundColor; final Color tileBackgroundColor;
final WalletType walletType;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final addressTextStyle = TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: currentTheme.type == ThemeType.bright
? Theme.of(context).extension<CakeTextTheme>()!.titleColor.withOpacity(0.5)
: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
);
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)), borderRadius: BorderRadius.all(Radius.circular(10)),
@ -420,10 +391,15 @@ class AddressExpansionTile extends StatelessWidget {
Row( Row(
children: [ children: [
Expanded( Expanded(
child: buildSegmentedAddress( child: AddressFormatter.buildSegmentedAddress(
address: address, address: address,
evenTextStyle: addressTextStyle, walletType: walletType,
oddTextStyle: itemSubTitleTextStyle, evenTextStyle: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none)
), ),
), ),
], ],
@ -434,30 +410,4 @@ class AddressExpansionTile extends StatelessWidget {
), ),
); );
} }
Widget buildSegmentedAddress({
required String address,
int chunkSize = 6,
required TextStyle evenTextStyle,
required TextStyle oddTextStyle,
}) {
final spans = <TextSpan>[];
int index = 0;
for (int i = 0; i < address.length; i += chunkSize) {
final chunk = address.substring(i, math.min(i + chunkSize, address.length));
final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle;
spans.add(
TextSpan(text: '$chunk ', style: style),
);
index++;
}
return RichText(
text: TextSpan(children: spans, style: evenTextStyle),
overflow: TextOverflow.visible,
);
}
} }

View file

@ -12,7 +12,8 @@ class ListRow extends StatelessWidget {
this.padding, this.padding,
this.color, this.color,
this.hintTextColor, this.hintTextColor,
this.mainTextColor this.mainTextColor,
this.textWidget
}); });
final String title; final String title;
@ -24,6 +25,16 @@ class ListRow extends StatelessWidget {
final Color? color; final Color? color;
final Color? hintTextColor; final Color? hintTextColor;
final Color? mainTextColor; final Color? mainTextColor;
final Widget? textWidget;
Widget _getTextWidget (BuildContext context) => textWidget ?? Text(
value,
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: mainTextColor ?? Theme.of(context).extension<CakeTextTheme>()!.titleColor
),
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -49,12 +60,7 @@ class ListRow extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Text(value, child: _getTextWidget(context)),
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: mainTextColor ?? Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
),
image != null image != null
? Padding( ? Padding(
padding: EdgeInsets.only(left: 24), padding: EdgeInsets.only(left: 24),

View file

@ -0,0 +1,117 @@
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/widgets.dart';
import 'dart:math' as math;
class AddressFormatter {
static Widget buildSegmentedAddress({
required String address,
required WalletType walletType,
required TextStyle evenTextStyle,
TextStyle? oddTextStyle,
TextAlign? textAlign,
bool shouldTruncate = false,
}) {
if (shouldTruncate) {
return _buildTruncatedAddress(
address: address,
walletType: walletType,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(150)),
textAlign: textAlign,
);
} else {
return _buildFullSegmentedAddress(
address: address,
walletType: walletType,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128)),
textAlign: textAlign,
);
}
}
static Widget _buildFullSegmentedAddress({
required String address,
required WalletType walletType,
required TextStyle evenTextStyle,
required TextStyle oddTextStyle,
TextAlign? textAlign,
}) {
final cleanAddress = address.replaceAll('bitcoincash:', '');
final chunkSize = _getChunkSize(walletType);
final chunks = <String>[];
for (int i = 0; i < cleanAddress.length; i += chunkSize) {
final chunk = cleanAddress.substring(i, math.min(i + chunkSize, cleanAddress.length));
chunks.add(chunk);
}
final spans = <TextSpan>[];
for (int i = 0; i < chunks.length; i++) {
final style = (i % 2 == 0) ? evenTextStyle : oddTextStyle;
spans.add(TextSpan(text: '${chunks[i]} ', style: style));
}
return RichText(
text: TextSpan(children: spans),
textAlign: textAlign ?? TextAlign.start,
overflow: TextOverflow.visible,
);
}
static Widget _buildTruncatedAddress({
required String address,
required WalletType walletType,
required TextStyle evenTextStyle,
required TextStyle oddTextStyle,
TextAlign? textAlign,
}) {
final cleanAddress = address.replaceAll('bitcoincash:', '');
final int digitCount = (walletType == WalletType.monero ||
walletType == WalletType.wownero ||
walletType == WalletType.zano)
? 6
: 4;
if (cleanAddress.length <= 2 * digitCount) {
return _buildFullSegmentedAddress(
address: cleanAddress,
walletType: walletType,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle,
textAlign: textAlign,
);
}
final String firstPart = cleanAddress.substring(0, digitCount);
final String secondPart = cleanAddress.substring(digitCount, digitCount * 2);
final String lastPart = cleanAddress.substring(cleanAddress.length - digitCount);
final spans = <TextSpan>[
TextSpan(text: '$firstPart ', style: evenTextStyle),
TextSpan(text: '$secondPart ', style: oddTextStyle),
TextSpan(text: '... ', style: oddTextStyle),
TextSpan(text: lastPart, style: evenTextStyle),
];
return RichText(
text: TextSpan(children: spans),
textAlign: textAlign ?? TextAlign.start,
overflow: TextOverflow.visible,
);
}
static int _getChunkSize(WalletType walletType) {
switch (walletType) {
case WalletType.monero:
case WalletType.wownero:
case WalletType.zano:
return 6;
default:
return 4;
}
}
}