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
+}