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>
This commit is contained in:
Serhii 2025-04-11 20:51:30 +03:00 committed by GitHub
parent 2f28ea3fb7
commit a7376c3225
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 173 additions and 84 deletions

View file

@ -247,3 +247,38 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
}
}
WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
switch (type) {
case CryptoCurrency.xmr:
return WalletType.monero;
case CryptoCurrency.btc:
return WalletType.bitcoin;
case CryptoCurrency.ltc:
return WalletType.litecoin;
case CryptoCurrency.xhv:
return WalletType.haven;
case CryptoCurrency.eth:
return WalletType.ethereum;
case CryptoCurrency.bch:
return WalletType.bitcoinCash;
case CryptoCurrency.nano:
return WalletType.nano;
case CryptoCurrency.banano:
return WalletType.banano;
case CryptoCurrency.maticpoly:
return WalletType.polygon;
case CryptoCurrency.sol:
return WalletType.solana;
case CryptoCurrency.trx:
return WalletType.tron;
case CryptoCurrency.wow:
return WalletType.wownero;
case CryptoCurrency.zano:
return WalletType.zano;
case CryptoCurrency.dcr:
return WalletType.decred;
default:
return null;
}
}

View file

@ -12,9 +12,11 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -235,7 +237,7 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
return;
}
final isCopied = await showNameAndAddressDialog(context, contact.name, contact.address);
final isCopied = await DialogService.showNameAndAddressDialog(context, contact);
if (isCopied) {
await Clipboard.setData(ClipboardData(text: contact.address));
@ -280,21 +282,6 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
? Image.asset(image, height: 24, width: 24)
: const SizedBox(height: 24, width: 24);
}
Future<bool> showNameAndAddressDialog(BuildContext context, String name, String address) async {
return await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: name,
alertContent: address,
rightButtonText: S.of(context).copy,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
false;
}
}
class ContactListBody extends StatefulWidget {
@ -357,7 +344,7 @@ class _ContactListBodyState extends State<ContactListBody> {
}
final isCopied =
await showNameAndAddressDialog(context, contact.name, contact.address);
await DialogService.showNameAndAddressDialog(context, contact);
if (isCopied) {
await Clipboard.setData(ClipboardData(text: contact.address));
@ -434,7 +421,7 @@ class _ContactListBodyState extends State<ContactListBody> {
),
SlidableAction(
onPressed: (_) async {
final isDelete = await showAlertDialog(context);
final isDelete = await DialogService.showAlertDialog(context);
if (isDelete) {
await widget.contactListViewModel.delete(contact);
@ -494,33 +481,49 @@ class _ContactListBodyState extends State<ContactListBody> {
);
}
Future<bool> showAlertDialog(BuildContext context) async {
}
class DialogService {
static Future<bool> showAlertDialog(BuildContext context) async {
return await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).address_remove_contact,
alertContent: S.of(context).address_remove_content,
rightButtonText: S.of(context).remove,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).address_remove_contact,
alertContent: S.of(context).address_remove_content,
rightButtonText: S.of(context).remove,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
false;
}
Future<bool> showNameAndAddressDialog(BuildContext context, String name, String address) async {
static Future<bool> showNameAndAddressDialog(BuildContext context,ContactBase contact) async {
return await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: name,
alertContent: address,
rightButtonText: S.of(context).copy,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: contact.name,
alertContent: contact.address,
alertContentTextWidget: AddressFormatter.buildSegmentedAddress(
address: contact.address,
textAlign: TextAlign.center,
walletType: cryptoCurrencyToWalletType(contact.type),
evenTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
rightButtonText: S.of(context).copy,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
false;
}
}

View file

@ -6,6 +6,7 @@ class AlertWithTwoActions extends BaseAlertDialog {
AlertWithTwoActions({
required this.alertTitle,
required this.alertContent,
this.alertContentTextWidget,
required this.leftButtonText,
required this.rightButtonText,
required this.actionLeftButton,
@ -21,6 +22,7 @@ class AlertWithTwoActions extends BaseAlertDialog {
final String alertTitle;
final String alertContent;
final Widget? alertContentTextWidget;
final String leftButtonText;
final String rightButtonText;
final VoidCallback actionLeftButton;
@ -38,6 +40,8 @@ class AlertWithTwoActions extends BaseAlertDialog {
@override
String get contentText => alertContent;
@override
Widget? get contentTextWidget => alertContentTextWidget;
@override
String get leftActionButtonText => leftButtonText;
@override
String get rightActionButtonText => rightButtonText;

View file

@ -13,6 +13,8 @@ class BaseAlertDialog extends StatelessWidget {
String get contentText => '';
Widget? get contentTextWidget => null;
String get leftActionButtonText => '';
String get rightActionButtonText => '';
@ -79,7 +81,8 @@ class BaseAlertDialog extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
contentTextWidget ??
Text(
contentText,
textAlign: TextAlign.center,
style: TextStyle(

View file

@ -5,24 +5,31 @@ import 'dart:math' as math;
class AddressFormatter {
static Widget buildSegmentedAddress({
required String address,
required WalletType walletType,
WalletType? walletType,
required TextStyle evenTextStyle,
TextStyle? oddTextStyle,
TextAlign? textAlign,
bool shouldTruncate = false,
}) {
final cleanAddress = address.replaceAll('bitcoincash:', '');
final isMWEB = address.startsWith('ltcmweb');
final chunkSize = walletType != null ? _getChunkSize(walletType) : 4;
if (shouldTruncate) {
return _buildTruncatedAddress(
address: address,
walletType: walletType,
address: cleanAddress,
isMWEB: isMWEB,
chunkSize: chunkSize,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(150)),
textAlign: textAlign,
);
} else {
return _buildFullSegmentedAddress(
address: address,
walletType: walletType,
address: cleanAddress,
isMWEB: isMWEB,
chunkSize: chunkSize,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128)),
textAlign: textAlign,
@ -32,19 +39,34 @@ class AddressFormatter {
static Widget _buildFullSegmentedAddress({
required String address,
required WalletType walletType,
required bool isMWEB,
required int chunkSize,
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);
if (isMWEB) {
const mwebDisplayPrefix = 'ltcmweb';
chunks.add(mwebDisplayPrefix);
final startIndex = mwebDisplayPrefix.length;
for (int i = startIndex; i < address.length; i += chunkSize) {
final chunk = address.substring(
i,
math.min(i + chunkSize, address.length),
);
chunks.add(chunk);
}
} else {
for (int i = 0; i < address.length; i += chunkSize) {
final chunk = address.substring(
i,
math.min(i + chunkSize, address.length),
);
chunks.add(chunk);
}
}
final spans = <TextSpan>[];
@ -62,46 +84,68 @@ class AddressFormatter {
static Widget _buildTruncatedAddress({
required String address,
required WalletType walletType,
required bool isMWEB,
required int chunkSize,
required TextStyle evenTextStyle,
required TextStyle oddTextStyle,
TextAlign? textAlign,
}) {
final cleanAddress = address.replaceAll('bitcoincash:', '');
if (isMWEB) {
const fixedPrefix = 'ltcmweb';
final secondChunkStart = fixedPrefix.length;
const chunkSize = 4;
final secondChunk = address.substring(
secondChunkStart,
math.min(secondChunkStart + chunkSize, address.length),
);
final lastChunk = address.substring(address.length - chunkSize);
final int digitCount = (walletType == WalletType.monero ||
walletType == WalletType.wownero ||
walletType == WalletType.zano)
? 6
: 4;
final spans = <TextSpan>[
TextSpan(text: '$fixedPrefix ', style: evenTextStyle),
TextSpan(text: '$secondChunk ', style: oddTextStyle),
TextSpan(text: '... ', style: oddTextStyle),
TextSpan(text: lastChunk, style: evenTextStyle),
];
if (cleanAddress.length <= 2 * digitCount) {
return _buildFullSegmentedAddress(
address: cleanAddress,
walletType: walletType,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle,
textAlign: textAlign,
return RichText(
text: TextSpan(children: spans),
textAlign: textAlign ?? TextAlign.start,
overflow: TextOverflow.visible,
);
} else {
final int digitCount = chunkSize;
if (address.length <= 2 * digitCount) {
return _buildFullSegmentedAddress(
address: address,
isMWEB: isMWEB,
chunkSize: chunkSize,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle,
textAlign: textAlign,
);
}
final String firstPart = address.substring(0, digitCount);
final String secondPart =
address.substring(digitCount, digitCount * 2);
final String lastPart =
address.substring(address.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,
);
}
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) {
@ -114,4 +158,4 @@ class AddressFormatter {
return 4;
}
}
}
}