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

Serhii committed Apr 10, 2025 at 17:16 UTC 3f25d69244ff9d08ce96335b5b5e7e70de912ec2
13 files changed +292 -123
lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart
+1
@@ -381,6 +381,7 @@ class CakePayBuyCardDetailPage extends BasePage {
381 return ConfirmSendingBottomSheet(
382 key: ValueKey('send_page_confirm_sending_dialog_key'),
383 currentTheme: currentTheme,
384 + walletType: cakePayPurchaseViewModel.sendViewModel.walletType,
385 paymentId: S.of(popupContext).payment_id,
386 paymentIdValue: order?.orderId,
387 expirationTime: cakePayPurchaseViewModel.formattedRemainingTime,
lib/src/screens/exchange_trade/exchange_trade_page.dart
+1
@@ -264,6 +264,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
264 return ConfirmSendingBottomSheet(
265 key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'),
266 currentTheme: widget.currentTheme,
267 + walletType: widget.exchangeTradeViewModel.sendViewModel.walletType,
268 titleText: S.of(bottomSheetContext).confirm_transaction,
269 titleIconPath:
270 widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
lib/src/screens/receive/address_list_page.dart
+1
@@ -20,6 +20,7 @@ class AddressListPage extends BasePage {
20 children: <Widget>[
21 AddressList(
22 addressListViewModel: addressListViewModel,
23 + currentTheme: currentTheme,
24 onSelect: (String address) async {
25 Navigator.of(context).pop(address);
26 },
lib/src/screens/receive/receive_page.dart
+1 -1
@@ -124,7 +124,7 @@ class ReceivePage extends BasePage {
124 isLight: currentTheme.type == ThemeType.light,
125 ),
126 ),
127 - AddressList(addressListViewModel: addressListViewModel),
127 + AddressList(addressListViewModel: addressListViewModel, currentTheme: currentTheme),
128 Padding(
129 padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
130 child: Text(
lib/src/screens/receive/widgets/address_cell.dart
+21 -26
@@ -1,7 +1,11 @@
1 -import 'package:auto_size_text/auto_size_text.dart';
1 import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
3 +import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
4 +import 'package:cake_wallet/themes/theme_base.dart';
5 +import 'package:cake_wallet/utils/address_formatter.dart';
6 import 'package:cake_wallet/utils/responsive_layout_util.dart';
7 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
8 +import 'package:cw_core/wallet_type.dart';
9 import 'package:flutter/cupertino.dart';
10 import 'package:flutter/material.dart';
11 import 'package:flutter_slidable/flutter_slidable.dart';
@@ -14,6 +18,8 @@ class AddressCell extends StatelessWidget {
18 required this.isPrimary,
19 required this.backgroundColor,
20 required this.textColor,
21 + required this.walletType,
22 + required this.currentTheme,
23 this.onTap,
24 this.onEdit,
25 this.onHide,
@@ -30,6 +36,8 @@ class AddressCell extends StatelessWidget {
36 required bool isCurrent,
37 required Color backgroundColor,
38 required Color textColor,
39 + required WalletType walletType,
40 + required ThemeBase currentTheme,
41 Function(String)? onTap,
42 bool hasBalance = false,
43 bool hasReceived = false,
@@ -45,6 +53,8 @@ class AddressCell extends StatelessWidget {
53 isPrimary: item.isPrimary,
54 backgroundColor: backgroundColor,
55 textColor: textColor,
56 + walletType: walletType,
57 + currentTheme: currentTheme,
58 onTap: onTap,
59 onEdit: onEdit,
60 onHide: onHide,
@@ -62,6 +72,8 @@ class AddressCell extends StatelessWidget {
72 final bool isPrimary;
73 final Color backgroundColor;
74 final Color textColor;
75 + final WalletType walletType;
76 + final ThemeBase currentTheme;
77 final Function(String)? onTap;
78 final Function()? onEdit;
79 final Function()? onHide;
@@ -73,21 +85,6 @@ class AddressCell extends StatelessWidget {
85 final bool hasBalance;
86 final bool hasReceived;
87
76 - static const int addressPreviewLength = 8;
77 -
78 - String get formattedAddress {
79 - final formatIfCashAddr = address.replaceAll('bitcoincash:', '');
80 -
81 - if (formatIfCashAddr.length <= (name.isNotEmpty ? 16 : 43)) {
82 - return formatIfCashAddr;
83 - } else {
84 - return formatIfCashAddr.substring(0, addressPreviewLength) +
85 - '...' +
86 - formatIfCashAddr.substring(
87 - formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
88 - }
89 - }
90 -
88 @override
89 Widget build(BuildContext context) {
90 final Widget cell = InkWell(
@@ -139,16 +136,14 @@ class AddressCell extends StatelessWidget {
136 ],
137 ),
138 Flexible(
142 - child: AutoSizeText(
143 - responsiveLayoutUtil.shouldRenderTabletUI ? address : formattedAddress,
144 - maxLines: 1,
145 - overflow: TextOverflow.ellipsis,
146 - style: TextStyle(
147 - fontSize: isChange ? 10 : 14,
148 - color: textColor,
149 - ),
150 - ),
151 - ),
139 + child: AddressFormatter.buildSegmentedAddress(
140 + address: address,
141 + walletType: walletType,
142 + shouldTruncate: name.isNotEmpty || address.length > 43 ,
143 + evenTextStyle: TextStyle(
144 + fontSize: isChange ? 10 : 14,
145 + color: textColor
146 + ))),
147 ],
148 ),
149 if (hasBalance || hasReceived)
lib/src/screens/receive/widgets/address_list.dart
+5 -3
@@ -1,5 +1,3 @@
1 -import 'dart:math';
2 -
1 import 'package:cake_wallet/di.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/routes.dart';
@@ -9,6 +7,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
7 import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
8 import 'package:cake_wallet/src/widgets/section_divider.dart';
9 import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
10 +import 'package:cake_wallet/themes/theme_base.dart';
11 import 'package:cake_wallet/utils/list_item.dart';
12 import 'package:cake_wallet/utils/show_pop_up.dart';
13 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';
19 import 'package:flutter/foundation.dart';
20 import 'package:flutter/material.dart';
21 import 'package:flutter_mobx/flutter_mobx.dart';
23 -import 'package:mobx/mobx.dart';
22
23 class AddressList extends StatefulWidget {
24 const AddressList({
25 super.key,
26 required this.addressListViewModel,
27 + required this.currentTheme,
28 this.onSelect,
29 });
30
31 final WalletAddressListViewModel addressListViewModel;
32 + final ThemeBase currentTheme;
33 final Function(String)? onSelect;
34
35 @override
@@ -161,6 +161,8 @@ class _AddressListState extends State<AddressList> {
161 return AddressCell.fromItem(
162 item,
163 isCurrent: isCurrent,
164 + currentTheme: widget.currentTheme,
165 + walletType: widget.addressListViewModel.type,
166 hasBalance: widget.addressListViewModel.isBalanceAvailable,
167 hasReceived: widget.addressListViewModel.isReceivedAvailable,
168 // hasReceived:
lib/src/screens/receive/widgets/qr_widget.dart
+6 -6
@@ -5,6 +5,7 @@ import 'package:cake_wallet/routes.dart';
5 import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
6 import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
7 import 'package:cake_wallet/themes/theme_base.dart';
8 +import 'package:cake_wallet/utils/address_formatter.dart';
9 import 'package:cake_wallet/utils/brightness_util.dart';
10 import 'package:cake_wallet/utils/responsive_layout_util.dart';
11 import 'package:cake_wallet/utils/show_bar.dart';
@@ -160,16 +161,15 @@ class QRWidget extends StatelessWidget {
161 crossAxisAlignment: CrossAxisAlignment.start,
162 children: <Widget>[
163 Expanded(
163 - child: Text(
164 - addressUri.address,
164 + child: AddressFormatter.buildSegmentedAddress(
165 + address: addressUri.address,
166 + walletType: addressListViewModel.type,
167 textAlign: TextAlign.center,
166 - style: TextStyle(
168 + evenTextStyle: TextStyle(
169 fontSize: 15,
170 fontWeight: FontWeight.w500,
171 color:
170 - Theme.of(context).extension<DashboardPageTheme>()!.textColor),
171 - ),
172 - ),
172 + Theme.of(context).extension<DashboardPageTheme>()!.textColor))),
173 Padding(
174 padding: EdgeInsets.only(left: 12),
175 child: copyImage,
lib/src/screens/send/send_page.dart
+1
@@ -558,6 +558,7 @@ class SendPage extends BasePage {
558 key: ValueKey('send_page_confirm_sending_dialog_key'),
559 titleText: S.of(bottomSheetContext).confirm_transaction,
560 currentTheme: currentTheme,
561 + walletType: sendViewModel.walletType,
562 titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath,
563 currency: sendViewModel.selectedCryptoCurrency,
564 amount: S.of(bottomSheetContext).send_amount,
lib/src/screens/transaction_details/rbf_details_page.dart
+1
@@ -190,6 +190,7 @@ class RBFDetailsPage extends BasePage {
190 key: ValueKey('rbf_confirm_sending_bottom_sheet'),
191 titleText: S.of(bottomSheetContext).confirm_transaction,
192 currentTheme: currentTheme,
193 + walletType: transactionDetailsViewModel.sendViewModel.walletType,
194 titleIconPath: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
195 currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
196 amount: S.of(bottomSheetContext).send_amount,
lib/src/screens/transaction_details/transaction_details_page.dart
+95 -1
@@ -9,8 +9,11 @@ import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.
9 import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
10 import 'package:cake_wallet/src/widgets/list_row.dart';
11 import 'package:cake_wallet/src/widgets/standard_list.dart';
12 +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
13 +import 'package:cake_wallet/utils/address_formatter.dart';
14 import 'package:cake_wallet/utils/show_bar.dart';
15 import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
16 +import 'package:cw_core/wallet_type.dart';
17 import 'package:flutter/material.dart';
18 import 'package:flutter/services.dart';
19 import 'package:flutter_mobx/flutter_mobx.dart';
@@ -39,13 +42,28 @@ class TransactionDetailsPage extends BasePage {
42 final item = transactionDetailsViewModel.items[index];
43
44 if (item is StandartListItem) {
45 + Widget? addressTextWidget;
46 +
47 + if (item.title.toLowerCase() == 'recipient addresses' ||
48 + item.title.toLowerCase() == 'source address') {
49 + addressTextWidget = getFormattedAddress(
50 + context: context,
51 + value: item.value,
52 + walletType: transactionDetailsViewModel.sendViewModel.walletType,
53 + );
54 + }
55 +
56 return GestureDetector(
57 key: item.key,
58 onTap: () {
59 Clipboard.setData(ClipboardData(text: item.value));
60 showBar<void>(context, S.of(context).transaction_details_copied(item.title));
61 },
48 - child: ListRow(title: '${item.title}:', value: item.value),
62 + child: ListRow(
63 + title: '${item.title}:',
64 + value: item.value,
65 + textWidget: addressTextWidget,
66 + ),
67 );
68 }
69
@@ -91,4 +109,80 @@ class TransactionDetailsPage extends BasePage {
109 ],
110 );
111 }
112 +
113 + Widget getFormattedAddress({
114 + required BuildContext context,
115 + required String value,
116 + required WalletType walletType,
117 + }) {
118 + final textStyle = TextStyle(
119 + fontSize: 16,
120 + fontWeight: FontWeight.w500,
121 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
122 + );
123 + final List<Widget> children = [];
124 + final bool hasDoubleNewline = value.contains('\n\n');
125 +
126 + if (hasDoubleNewline) {
127 + final blocks = value
128 + .split('\n\n')
129 + .map((b) => b.trim())
130 + .where((b) => b.isNotEmpty)
131 + .toList();
132 + for (final block in blocks) {
133 + final lines = block
134 + .split('\n')
135 + .map((l) => l.trim())
136 + .where((l) => l.isNotEmpty)
137 + .toList();
138 + if (lines.length > 1) {
139 + children.add(Text(lines.first, style: textStyle));
140 + for (int i = 1; i < lines.length; i++) {
141 + children.add(
142 + AddressFormatter.buildSegmentedAddress(
143 + address: lines[i],
144 + walletType: walletType,
145 + evenTextStyle: textStyle,
146 + ),
147 + );
148 + }
149 + } else {
150 + children.add(
151 + AddressFormatter.buildSegmentedAddress(
152 + address: lines.first,
153 + walletType: walletType,
154 + evenTextStyle: textStyle,
155 + ),
156 + );
157 + }
158 + children.add(SizedBox(height: 8));
159 + }
160 + } else {
161 + final lines = value
162 + .split('\n')
163 + .map((l) => l.trim())
164 + .where((l) => l.isNotEmpty)
165 + .toList();
166 + bool firstLineIsContactName = (lines.length > 1 && lines.first.length < 20);
167 + int startIndex = 0;
168 + if (firstLineIsContactName) {
169 + children.add(Text(lines.first, style: textStyle));
170 + startIndex = 1;
171 + }
172 + for (int i = startIndex; i < lines.length; i++) {
173 + children.add(
174 + AddressFormatter.buildSegmentedAddress(
175 + address: lines[i],
176 + walletType: walletType,
177 + evenTextStyle: textStyle,
178 + ),
179 + );
180 + }
181 + }
182 +
183 + return Column(
184 + crossAxisAlignment: CrossAxisAlignment.start,
185 + children: children,
186 + );
187 + }
188 }
lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart
+29 -79
@@ -5,11 +5,12 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
5 import 'package:cake_wallet/themes/extensions/filter_theme.dart';
6 import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
7 import 'package:cake_wallet/themes/theme_base.dart';
8 +import 'package:cake_wallet/utils/address_formatter.dart';
9 import 'package:cake_wallet/view_model/send/output.dart';
10 import 'package:cw_core/crypto_currency.dart';
11 import 'package:cw_core/pending_transaction.dart';
12 +import 'package:cw_core/wallet_type.dart';
13 import 'package:flutter/material.dart';
12 -import 'dart:math' as math;
14
15 import 'base_bottom_sheet_widget.dart';
16
@@ -27,6 +28,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
28 final String feeFiatAmount;
29 final List<Output> outputs;
30 final VoidCallback onSlideComplete;
31 + final WalletType walletType;
32 final PendingChange? change;
33 final bool isOpenCryptoPay;
34
@@ -46,6 +48,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
48 required this.feeFiatAmount,
49 required this.outputs,
50 required this.onSlideComplete,
51 + required this.walletType,
52 this.change,
53 this.isOpenCryptoPay = false,
54 Key? key,
@@ -91,6 +94,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
94 itemTitle: paymentId!,
95 currentTheme: currentTheme,
96 itemTitleTextStyle: itemTitleTextStyle,
97 + walletType: walletType,
98 isBatchSending: false,
99 amount: '',
100 address: paymentIdValue!,
@@ -139,6 +143,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
143 name: isBatchSending ? batchContactTitle : contactName,
144 address: _address,
145 amount: _amount,
146 + walletType: walletType,
147 isBatchSending: isBatchSending,
148 itemTitleTextStyle: itemTitleTextStyle,
149 itemSubTitleTextStyle: itemSubTitleTextStyle,
@@ -149,6 +154,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
154 currentTheme: currentTheme,
155 itemTitleTextStyle: itemTitleTextStyle,
156 isBatchSending: isBatchSending,
157 + walletType: walletType,
158 amount: _amount,
159 address: _address,
160 itemSubTitleTextStyle: itemSubTitleTextStyle,
@@ -166,6 +172,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
172 address: change!.address,
173 amount: change!.amount + ' ${currency.title}',
174 isBatchSending: true,
175 + walletType: walletType,
176 itemTitleTextStyle: itemTitleTextStyle,
177 itemSubTitleTextStyle: itemSubTitleTextStyle,
178 tileBackgroundColor: tileBackgroundColor,
@@ -275,6 +282,7 @@ class AddressTile extends StatelessWidget {
282 required this.address,
283 required this.itemSubTitleTextStyle,
284 required this.tileBackgroundColor,
285 + required this.walletType,
286 });
287
288 final String itemTitle;
@@ -285,18 +293,10 @@ class AddressTile extends StatelessWidget {
293 final String address;
294 final TextStyle itemSubTitleTextStyle;
295 final Color tileBackgroundColor;
296 + final WalletType walletType;
297
298 @override
299 Widget build(BuildContext context) {
291 - final addressTextStyle = TextStyle(
292 - fontSize: 12,
293 - fontFamily: 'Lato',
294 - fontWeight: FontWeight.w600,
295 - color: currentTheme.type == ThemeType.bright
296 - ? Theme.of(context).extension<CakeTextTheme>()!.titleColor.withOpacity(0.5)
297 - : Theme.of(context).extension<CakeTextTheme>()!.titleColor,
298 - decoration: TextDecoration.none,
299 - );
300 return Container(
301 padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
302 decoration: BoxDecoration(
@@ -313,41 +313,20 @@ class AddressTile extends StatelessWidget {
313 if (isBatchSending) Text(amount, style: itemTitleTextStyle),
314 ],
315 ),
316 - buildSegmentedAddress(
316 + AddressFormatter.buildSegmentedAddress(
317 address: address,
318 - evenTextStyle: addressTextStyle,
319 - oddTextStyle: itemSubTitleTextStyle,
318 + walletType: walletType,
319 + evenTextStyle: TextStyle(
320 + fontSize: 12,
321 + fontFamily: 'Lato',
322 + fontWeight: FontWeight.w600,
323 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
324 + decoration: TextDecoration.none)
325 ),
326 ],
327 ),
328 );
329 }
325 -
326 - Widget buildSegmentedAddress({
327 - required String address,
328 - int chunkSize = 6,
329 - required TextStyle evenTextStyle,
330 - required TextStyle oddTextStyle,
331 - }) {
332 - final spans = <TextSpan>[];
333 -
334 - int index = 0;
335 - for (int i = 0; i < address.length; i += chunkSize) {
336 - final chunk = address.substring(i, math.min(i + chunkSize, address.length));
337 - final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle;
338 -
339 - spans.add(
340 - TextSpan(text: '$chunk ', style: style),
341 - );
342 -
343 - index++;
344 - }
345 -
346 - return RichText(
347 - text: TextSpan(children: spans, style: evenTextStyle),
348 - overflow: TextOverflow.visible,
349 - );
350 - }
330 }
331
332 class AddressExpansionTile extends StatelessWidget {
@@ -362,6 +341,7 @@ class AddressExpansionTile extends StatelessWidget {
341 required this.itemTitleTextStyle,
342 required this.itemSubTitleTextStyle,
343 required this.tileBackgroundColor,
344 + required this.walletType,
345 });
346
347 final String contactType;
@@ -373,19 +353,10 @@ class AddressExpansionTile extends StatelessWidget {
353 final TextStyle itemTitleTextStyle;
354 final TextStyle itemSubTitleTextStyle;
355 final Color tileBackgroundColor;
356 + final WalletType walletType;
357
358 @override
359 Widget build(BuildContext context) {
379 - final addressTextStyle = TextStyle(
380 - fontSize: 12,
381 - fontFamily: 'Lato',
382 - fontWeight: FontWeight.w600,
383 - color: currentTheme.type == ThemeType.bright
384 - ? Theme.of(context).extension<CakeTextTheme>()!.titleColor.withOpacity(0.5)
385 - : Theme.of(context).extension<CakeTextTheme>()!.titleColor,
386 - decoration: TextDecoration.none,
387 - );
388 -
360 return Container(
361 decoration: BoxDecoration(
362 borderRadius: BorderRadius.all(Radius.circular(10)),
@@ -420,10 +391,15 @@ class AddressExpansionTile extends StatelessWidget {
391 Row(
392 children: [
393 Expanded(
423 - child: buildSegmentedAddress(
424 - address: address,
425 - evenTextStyle: addressTextStyle,
426 - oddTextStyle: itemSubTitleTextStyle,
394 + child: AddressFormatter.buildSegmentedAddress(
395 + address: address,
396 + walletType: walletType,
397 + evenTextStyle: TextStyle(
398 + fontSize: 12,
399 + fontFamily: 'Lato',
400 + fontWeight: FontWeight.w600,
401 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
402 + decoration: TextDecoration.none)
403 ),
404 ),
405 ],
@@ -434,30 +410,4 @@ class AddressExpansionTile extends StatelessWidget {
410 ),
411 );
412 }
437 -
438 - Widget buildSegmentedAddress({
439 - required String address,
440 - int chunkSize = 6,
441 - required TextStyle evenTextStyle,
442 - required TextStyle oddTextStyle,
443 - }) {
444 - final spans = <TextSpan>[];
445 -
446 - int index = 0;
447 - for (int i = 0; i < address.length; i += chunkSize) {
448 - final chunk = address.substring(i, math.min(i + chunkSize, address.length));
449 - final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle;
450 -
451 - spans.add(
452 - TextSpan(text: '$chunk ', style: style),
453 - );
454 -
455 - index++;
456 - }
457 -
458 - return RichText(
459 - text: TextSpan(children: spans, style: evenTextStyle),
460 - overflow: TextOverflow.visible,
461 - );
462 - }
413 }
lib/src/widgets/list_row.dart
+13 -7
@@ -12,7 +12,8 @@ class ListRow extends StatelessWidget {
12 this.padding,
13 this.color,
14 this.hintTextColor,
15 - this.mainTextColor
15 + this.mainTextColor,
16 + this.textWidget
17 });
18
19 final String title;
@@ -24,6 +25,16 @@ class ListRow extends StatelessWidget {
25 final Color? color;
26 final Color? hintTextColor;
27 final Color? mainTextColor;
28 + final Widget? textWidget;
29 +
30 + Widget _getTextWidget (BuildContext context) => textWidget ?? Text(
31 + value,
32 + style: TextStyle(
33 + fontSize: valueFontSize,
34 + fontWeight: FontWeight.w500,
35 + color: mainTextColor ?? Theme.of(context).extension<CakeTextTheme>()!.titleColor
36 + ),
37 + );
38
39 @override
40 Widget build(BuildContext context) {
@@ -49,12 +60,7 @@ class ListRow extends StatelessWidget {
60 crossAxisAlignment: CrossAxisAlignment.start,
61 children: <Widget>[
62 Expanded(
52 - child: Text(value,
53 - style: TextStyle(
54 - fontSize: valueFontSize,
55 - fontWeight: FontWeight.w500,
56 - color: mainTextColor ?? Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
57 - ),
63 + child: _getTextWidget(context)),
64 image != null
65 ? Padding(
66 padding: EdgeInsets.only(left: 24),
lib/utils/address_formatter.dart new
+117
@@ -0,0 +1,117 @@
1 +import 'package:cw_core/wallet_type.dart';
2 +import 'package:flutter/widgets.dart';
3 +import 'dart:math' as math;
4 +
5 +class AddressFormatter {
6 + static Widget buildSegmentedAddress({
7 + required String address,
8 + required WalletType walletType,
9 + required TextStyle evenTextStyle,
10 + TextStyle? oddTextStyle,
11 + TextAlign? textAlign,
12 + bool shouldTruncate = false,
13 + }) {
14 + if (shouldTruncate) {
15 + return _buildTruncatedAddress(
16 + address: address,
17 + walletType: walletType,
18 + evenTextStyle: evenTextStyle,
19 + oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(150)),
20 + textAlign: textAlign,
21 + );
22 + } else {
23 + return _buildFullSegmentedAddress(
24 + address: address,
25 + walletType: walletType,
26 + evenTextStyle: evenTextStyle,
27 + oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128)),
28 + textAlign: textAlign,
29 + );
30 + }
31 + }
32 +
33 + static Widget _buildFullSegmentedAddress({
34 + required String address,
35 + required WalletType walletType,
36 + required TextStyle evenTextStyle,
37 + required TextStyle oddTextStyle,
38 + TextAlign? textAlign,
39 + }) {
40 +
41 + final cleanAddress = address.replaceAll('bitcoincash:', '');
42 + final chunkSize = _getChunkSize(walletType);
43 + final chunks = <String>[];
44 +
45 + for (int i = 0; i < cleanAddress.length; i += chunkSize) {
46 + final chunk = cleanAddress.substring(i, math.min(i + chunkSize, cleanAddress.length));
47 + chunks.add(chunk);
48 + }
49 +
50 + final spans = <TextSpan>[];
51 + for (int i = 0; i < chunks.length; i++) {
52 + final style = (i % 2 == 0) ? evenTextStyle : oddTextStyle;
53 + spans.add(TextSpan(text: '${chunks[i]} ', style: style));
54 + }
55 +
56 + return RichText(
57 + text: TextSpan(children: spans),
58 + textAlign: textAlign ?? TextAlign.start,
59 + overflow: TextOverflow.visible,
60 + );
61 + }
62 +
63 + static Widget _buildTruncatedAddress({
64 + required String address,
65 + required WalletType walletType,
66 + required TextStyle evenTextStyle,
67 + required TextStyle oddTextStyle,
68 + TextAlign? textAlign,
69 + }) {
70 +
71 + final cleanAddress = address.replaceAll('bitcoincash:', '');
72 +
73 + final int digitCount = (walletType == WalletType.monero ||
74 + walletType == WalletType.wownero ||
75 + walletType == WalletType.zano)
76 + ? 6
77 + : 4;
78 +
79 + if (cleanAddress.length <= 2 * digitCount) {
80 + return _buildFullSegmentedAddress(
81 + address: cleanAddress,
82 + walletType: walletType,
83 + evenTextStyle: evenTextStyle,
84 + oddTextStyle: oddTextStyle,
85 + textAlign: textAlign,
86 + );
87 + }
88 +
89 + final String firstPart = cleanAddress.substring(0, digitCount);
90 + final String secondPart = cleanAddress.substring(digitCount, digitCount * 2);
91 + final String lastPart = cleanAddress.substring(cleanAddress.length - digitCount);
92 +
93 + final spans = <TextSpan>[
94 + TextSpan(text: '$firstPart ', style: evenTextStyle),
95 + TextSpan(text: '$secondPart ', style: oddTextStyle),
96 + TextSpan(text: '... ', style: oddTextStyle),
97 + TextSpan(text: lastPart, style: evenTextStyle),
98 + ];
99 +
100 + return RichText(
101 + text: TextSpan(children: spans),
102 + textAlign: textAlign ?? TextAlign.start,
103 + overflow: TextOverflow.visible,
104 + );
105 + }
106 +
107 + static int _getChunkSize(WalletType walletType) {
108 + switch (walletType) {
109 + case WalletType.monero:
110 + case WalletType.wownero:
111 + case WalletType.zano:
112 + return 6;
113 + default:
114 + return 4;
115 + }
116 + }
117 +}