Address-formatting-enhancements-MWEB (#2189)
* apply formatting to address book and MWEB * fix wallet type exception * Update cw_core/lib/wallet_type.dart [skip ci] * Update lib/src/screens/contact/contact_list_page.dart [skip ci] * Update lib/src/screens/contact/contact_list_page.dart [skip ci] * Update lib/utils/address_formatter.dart [skip ci] * Update lib/utils/address_formatter.dart [skip ci] * Update lib/utils/address_formatter.dart --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Serhii committed
Apr 11, 2025 at 20:51 UTC
a7376c3225fb03ca7209840c177e8bc6d90ef88f
5 files changed
+171
-82
cw_core/lib/wallet_type.dart
+35
@@ -247,3 +247,38 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
247
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
248
}
249
}
250
+
251
+WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
252
+ switch (type) {
253
+ case CryptoCurrency.xmr:
254
+ return WalletType.monero;
255
+ case CryptoCurrency.btc:
256
+ return WalletType.bitcoin;
257
+ case CryptoCurrency.ltc:
258
+ return WalletType.litecoin;
259
+ case CryptoCurrency.xhv:
260
+ return WalletType.haven;
261
+ case CryptoCurrency.eth:
262
+ return WalletType.ethereum;
263
+ case CryptoCurrency.bch:
264
+ return WalletType.bitcoinCash;
265
+ case CryptoCurrency.nano:
266
+ return WalletType.nano;
267
+ case CryptoCurrency.banano:
268
+ return WalletType.banano;
269
+ case CryptoCurrency.maticpoly:
270
+ return WalletType.polygon;
271
+ case CryptoCurrency.sol:
272
+ return WalletType.solana;
273
+ case CryptoCurrency.trx:
274
+ return WalletType.tron;
275
+ case CryptoCurrency.wow:
276
+ return WalletType.wownero;
277
+ case CryptoCurrency.zano:
278
+ return WalletType.zano;
279
+ case CryptoCurrency.dcr:
280
+ return WalletType.decred;
281
+ default:
282
+ return null;
283
+ }
284
+}
lib/src/screens/contact/contact_list_page.dart
+43
-40
@@ -12,9 +12,11 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
12
import 'package:cake_wallet/src/widgets/standard_list.dart';
13
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
14
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
15
+import 'package:cake_wallet/utils/address_formatter.dart';
16
import 'package:cake_wallet/utils/show_bar.dart';
17
import 'package:cake_wallet/utils/show_pop_up.dart';
18
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
19
+import 'package:cw_core/wallet_type.dart';
20
import 'package:flutter/cupertino.dart';
21
import 'package:flutter/material.dart';
22
import 'package:flutter/services.dart';
@@ -235,7 +237,7 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
237
return;
238
}
239
238
- final isCopied = await showNameAndAddressDialog(context, contact.name, contact.address);
240
+ final isCopied = await DialogService.showNameAndAddressDialog(context, contact);
241
242
if (isCopied) {
243
await Clipboard.setData(ClipboardData(text: contact.address));
@@ -280,21 +282,6 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
282
? Image.asset(image, height: 24, width: 24)
283
: const SizedBox(height: 24, width: 24);
284
}
283
-
284
- Future<bool> showNameAndAddressDialog(BuildContext context, String name, String address) async {
285
- return await showPopUp<bool>(
286
- context: context,
287
- builder: (BuildContext context) {
288
- return AlertWithTwoActions(
289
- alertTitle: name,
290
- alertContent: address,
291
- rightButtonText: S.of(context).copy,
292
- leftButtonText: S.of(context).cancel,
293
- actionRightButton: () => Navigator.of(context).pop(true),
294
- actionLeftButton: () => Navigator.of(context).pop(false));
295
- }) ??
296
- false;
297
- }
285
}
286
287
class ContactListBody extends StatefulWidget {
@@ -357,7 +344,7 @@ class _ContactListBodyState extends State<ContactListBody> {
344
}
345
346
final isCopied =
360
- await showNameAndAddressDialog(context, contact.name, contact.address);
347
+ await DialogService.showNameAndAddressDialog(context, contact);
348
349
if (isCopied) {
350
await Clipboard.setData(ClipboardData(text: contact.address));
@@ -434,7 +421,7 @@ class _ContactListBodyState extends State<ContactListBody> {
421
),
422
SlidableAction(
423
onPressed: (_) async {
437
- final isDelete = await showAlertDialog(context);
424
+ final isDelete = await DialogService.showAlertDialog(context);
425
426
if (isDelete) {
427
await widget.contactListViewModel.delete(contact);
@@ -494,33 +481,49 @@ class _ContactListBodyState extends State<ContactListBody> {
481
);
482
}
483
497
- Future<bool> showAlertDialog(BuildContext context) async {
484
+}
485
+
486
+class DialogService {
487
+ static Future<bool> showAlertDialog(BuildContext context) async {
488
return await showPopUp<bool>(
499
- context: context,
500
- builder: (BuildContext context) {
501
- return AlertWithTwoActions(
502
- alertTitle: S.of(context).address_remove_contact,
503
- alertContent: S.of(context).address_remove_content,
504
- rightButtonText: S.of(context).remove,
505
- leftButtonText: S.of(context).cancel,
506
- actionRightButton: () => Navigator.of(context).pop(true),
507
- actionLeftButton: () => Navigator.of(context).pop(false));
508
- }) ??
489
+ context: context,
490
+ builder: (BuildContext context) {
491
+ return AlertWithTwoActions(
492
+ alertTitle: S.of(context).address_remove_contact,
493
+ alertContent: S.of(context).address_remove_content,
494
+ rightButtonText: S.of(context).remove,
495
+ leftButtonText: S.of(context).cancel,
496
+ actionRightButton: () => Navigator.of(context).pop(true),
497
+ actionLeftButton: () => Navigator.of(context).pop(false));
498
+ }) ??
499
false;
500
}
501
512
- Future<bool> showNameAndAddressDialog(BuildContext context, String name, String address) async {
502
+ static Future<bool> showNameAndAddressDialog(BuildContext context,ContactBase contact) async {
503
return await showPopUp<bool>(
514
- context: context,
515
- builder: (BuildContext context) {
516
- return AlertWithTwoActions(
517
- alertTitle: name,
518
- alertContent: address,
519
- rightButtonText: S.of(context).copy,
520
- leftButtonText: S.of(context).cancel,
521
- actionRightButton: () => Navigator.of(context).pop(true),
522
- actionLeftButton: () => Navigator.of(context).pop(false));
523
- }) ??
504
+ context: context,
505
+ builder: (BuildContext context) {
506
+ return AlertWithTwoActions(
507
+ alertTitle: contact.name,
508
+ alertContent: contact.address,
509
+ alertContentTextWidget: AddressFormatter.buildSegmentedAddress(
510
+ address: contact.address,
511
+ textAlign: TextAlign.center,
512
+ walletType: cryptoCurrencyToWalletType(contact.type),
513
+ evenTextStyle: TextStyle(
514
+ fontSize: 16,
515
+ fontWeight: FontWeight.normal,
516
+ fontFamily: 'Lato',
517
+ color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
518
+ decoration: TextDecoration.none,
519
+ ),
520
+ ),
521
+ rightButtonText: S.of(context).copy,
522
+ leftButtonText: S.of(context).cancel,
523
+ actionRightButton: () => Navigator.of(context).pop(true),
524
+ actionLeftButton: () => Navigator.of(context).pop(false));
525
+ }) ??
526
false;
527
}
528
}
529
+
lib/src/widgets/alert_with_two_actions.dart
+4
@@ -6,6 +6,7 @@ class AlertWithTwoActions extends BaseAlertDialog {
6
AlertWithTwoActions({
7
required this.alertTitle,
8
required this.alertContent,
9
+ this.alertContentTextWidget,
10
required this.leftButtonText,
11
required this.rightButtonText,
12
required this.actionLeftButton,
@@ -21,6 +22,7 @@ class AlertWithTwoActions extends BaseAlertDialog {
22
23
final String alertTitle;
24
final String alertContent;
25
+ final Widget? alertContentTextWidget;
26
final String leftButtonText;
27
final String rightButtonText;
28
final VoidCallback actionLeftButton;
@@ -38,6 +40,8 @@ class AlertWithTwoActions extends BaseAlertDialog {
40
@override
41
String get contentText => alertContent;
42
@override
43
+ Widget? get contentTextWidget => alertContentTextWidget;
44
+ @override
45
String get leftActionButtonText => leftButtonText;
46
@override
47
String get rightActionButtonText => rightButtonText;
lib/src/widgets/base_alert_dialog.dart
+4
-1
@@ -13,6 +13,8 @@ class BaseAlertDialog extends StatelessWidget {
13
14
String get contentText => '';
15
16
+ Widget? get contentTextWidget => null;
17
+
18
String get leftActionButtonText => '';
19
20
String get rightActionButtonText => '';
@@ -79,7 +81,8 @@ class BaseAlertDialog extends StatelessWidget {
81
child: Column(
82
mainAxisSize: MainAxisSize.min,
83
children: [
82
- Text(
84
+ contentTextWidget ??
85
+ Text(
86
contentText,
87
textAlign: TextAlign.center,
88
style: TextStyle(
lib/utils/address_formatter.dart
+85
-41
@@ -5,24 +5,31 @@ import 'dart:math' as math;
5
class AddressFormatter {
6
static Widget buildSegmentedAddress({
7
required String address,
8
- required WalletType walletType,
8
+ WalletType? walletType,
9
required TextStyle evenTextStyle,
10
TextStyle? oddTextStyle,
11
TextAlign? textAlign,
12
bool shouldTruncate = false,
13
}) {
14
+
15
+ final cleanAddress = address.replaceAll('bitcoincash:', '');
16
+ final isMWEB = address.startsWith('ltcmweb');
17
+ final chunkSize = walletType != null ? _getChunkSize(walletType) : 4;
18
+
19
if (shouldTruncate) {
20
return _buildTruncatedAddress(
16
- address: address,
17
- walletType: walletType,
21
+ address: cleanAddress,
22
+ isMWEB: isMWEB,
23
+ chunkSize: chunkSize,
24
evenTextStyle: evenTextStyle,
25
oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(150)),
26
textAlign: textAlign,
27
);
28
} else {
29
return _buildFullSegmentedAddress(
24
- address: address,
25
- walletType: walletType,
30
+ address: cleanAddress,
31
+ isMWEB: isMWEB,
32
+ chunkSize: chunkSize,
33
evenTextStyle: evenTextStyle,
34
oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128)),
35
textAlign: textAlign,
@@ -32,19 +39,34 @@ class AddressFormatter {
39
40
static Widget _buildFullSegmentedAddress({
41
required String address,
35
- required WalletType walletType,
42
+ required bool isMWEB,
43
+ required int chunkSize,
44
required TextStyle evenTextStyle,
45
required TextStyle oddTextStyle,
46
TextAlign? textAlign,
47
}) {
48
41
- final cleanAddress = address.replaceAll('bitcoincash:', '');
42
- final chunkSize = _getChunkSize(walletType);
49
final chunks = <String>[];
50
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);
51
+ if (isMWEB) {
52
+ const mwebDisplayPrefix = 'ltcmweb';
53
+ chunks.add(mwebDisplayPrefix);
54
+ final startIndex = mwebDisplayPrefix.length;
55
+ for (int i = startIndex; i < address.length; i += chunkSize) {
56
+ final chunk = address.substring(
57
+ i,
58
+ math.min(i + chunkSize, address.length),
59
+ );
60
+ chunks.add(chunk);
61
+ }
62
+ } else {
63
+ for (int i = 0; i < address.length; i += chunkSize) {
64
+ final chunk = address.substring(
65
+ i,
66
+ math.min(i + chunkSize, address.length),
67
+ );
68
+ chunks.add(chunk);
69
+ }
70
}
71
72
final spans = <TextSpan>[];
@@ -62,46 +84,68 @@ class AddressFormatter {
84
85
static Widget _buildTruncatedAddress({
86
required String address,
65
- required WalletType walletType,
87
+ required bool isMWEB,
88
+ required int chunkSize,
89
required TextStyle evenTextStyle,
90
required TextStyle oddTextStyle,
91
TextAlign? textAlign,
92
}) {
93
71
- final cleanAddress = address.replaceAll('bitcoincash:', '');
94
+ if (isMWEB) {
95
+ const fixedPrefix = 'ltcmweb';
96
+ final secondChunkStart = fixedPrefix.length;
97
+ const chunkSize = 4;
98
+ final secondChunk = address.substring(
99
+ secondChunkStart,
100
+ math.min(secondChunkStart + chunkSize, address.length),
101
+ );
102
+ final lastChunk = address.substring(address.length - chunkSize);
103
73
- final int digitCount = (walletType == WalletType.monero ||
74
- walletType == WalletType.wownero ||
75
- walletType == WalletType.zano)
76
- ? 6
77
- : 4;
104
+ final spans = <TextSpan>[
105
+ TextSpan(text: '$fixedPrefix ', style: evenTextStyle),
106
+ TextSpan(text: '$secondChunk ', style: oddTextStyle),
107
+ TextSpan(text: '... ', style: oddTextStyle),
108
+ TextSpan(text: lastChunk, style: evenTextStyle),
109
+ ];
110
79
- if (cleanAddress.length <= 2 * digitCount) {
80
- return _buildFullSegmentedAddress(
81
- address: cleanAddress,
82
- walletType: walletType,
83
- evenTextStyle: evenTextStyle,
84
- oddTextStyle: oddTextStyle,
85
- textAlign: textAlign,
111
+ return RichText(
112
+ text: TextSpan(children: spans),
113
+ textAlign: textAlign ?? TextAlign.start,
114
+ overflow: TextOverflow.visible,
115
);
87
- }
116
+ } else {
117
+ final int digitCount = chunkSize;
118
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);
119
+ if (address.length <= 2 * digitCount) {
120
+ return _buildFullSegmentedAddress(
121
+ address: address,
122
+ isMWEB: isMWEB,
123
+ chunkSize: chunkSize,
124
+ evenTextStyle: evenTextStyle,
125
+ oddTextStyle: oddTextStyle,
126
+ textAlign: textAlign,
127
+ );
128
+ }
129
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
- ];
130
+ final String firstPart = address.substring(0, digitCount);
131
+ final String secondPart =
132
+ address.substring(digitCount, digitCount * 2);
133
+ final String lastPart =
134
+ address.substring(address.length - digitCount);
135
100
- return RichText(
101
- text: TextSpan(children: spans),
102
- textAlign: textAlign ?? TextAlign.start,
103
- overflow: TextOverflow.visible,
104
- );
136
+ final spans = <TextSpan>[
137
+ TextSpan(text: '$firstPart ', style: evenTextStyle),
138
+ TextSpan(text: '$secondPart ', style: oddTextStyle),
139
+ TextSpan(text: '... ', style: oddTextStyle),
140
+ TextSpan(text: lastPart, style: evenTextStyle),
141
+ ];
142
+
143
+ return RichText(
144
+ text: TextSpan(children: spans),
145
+ textAlign: textAlign ?? TextAlign.start,
146
+ overflow: TextOverflow.visible,
147
+ );
148
+ }
149
}
150
151
static int _getChunkSize(WalletType walletType) {
@@ -114,4 +158,4 @@ class AddressFormatter {
158
return 4;
159
}
160
}
117
-}
161
+}
\ No newline at end of file