dev
dart 182 lines 5.54 KB
Raw
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 WalletType? walletType,
9 required TextStyle evenTextStyle,
10 TextStyle? oddTextStyle,
11 TextAlign? textAlign,
12 bool shouldTruncate = false,
13 }) {
14 final cleanAddress = address.replaceAll('bitcoincash:', '');
15 final isMWEB = address.startsWith('ltcmweb');
16 final chunkSize = walletType != null ? _getChunkSize(walletType) : 4;
17 final isHumanReadable = address.contains("@") || address.startsWith("Error:");
18
19 if (isHumanReadable) {
20 return Text(address, style: evenTextStyle, textAlign: textAlign ?? TextAlign.start);
21 }
22
23 if (shouldTruncate) {
24 return _buildTruncatedAddress(
25 address: cleanAddress,
26 isMWEB: isMWEB,
27 chunkSize: chunkSize,
28 evenTextStyle: evenTextStyle,
29 oddTextStyle:
30 oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(150)),
31 textAlign: textAlign,
32 );
33 } else {
34 return _buildFullSegmentedAddress(
35 address: cleanAddress,
36 isMWEB: isMWEB,
37 chunkSize: chunkSize,
38 evenTextStyle: evenTextStyle,
39 oddTextStyle:
40 oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128)),
41 textAlign: textAlign,
42 );
43 }
44 }
45
46 static Widget _buildFullSegmentedAddress({
47 required String address,
48 required bool isMWEB,
49 required int chunkSize,
50 required TextStyle evenTextStyle,
51 required TextStyle oddTextStyle,
52 TextAlign? textAlign,
53 }) {
54 final chunks = <String>[];
55
56 if (isMWEB) {
57 const mwebDisplayPrefix = 'ltcmweb';
58 chunks.add(mwebDisplayPrefix);
59 final startIndex = mwebDisplayPrefix.length;
60 for (int i = startIndex; i < address.length; i += chunkSize) {
61 final chunk = address.substring(
62 i,
63 math.min(i + chunkSize, address.length),
64 );
65 chunks.add(chunk);
66 }
67 } else {
68 for (int i = 0; i < address.length; i += chunkSize) {
69 final chunk = address.substring(
70 i,
71 math.min(i + chunkSize, address.length),
72 );
73 chunks.add(chunk);
74 }
75 }
76
77 final spans = <TextSpan>[];
78 for (int i = 0; i < chunks.length; i++) {
79 final style = (i % 2 == 0) ? evenTextStyle : oddTextStyle;
80 spans.add(TextSpan(text: '${chunks[i]} ', style: style));
81 }
82
83 return _withAddressSemantics(
84 address: address,
85 child: RichText(
86 text: TextSpan(children: spans),
87 textAlign: textAlign ?? TextAlign.start,
88 overflow: TextOverflow.visible,
89 ),
90 );
91 }
92
93 /// The visual segmentation turns an address into space-separated pseudo-words
94 /// (and, when truncated, into a literal "..."), which cannot be used to verify
95 /// an address by ear. Announce the full address instead, exactly the way
96 /// `Text.semanticsLabel` does.
97 static Widget _withAddressSemantics({required String address, required Widget child}) =>
98 Semantics(
99 label: address,
100 child: ExcludeSemantics(child: child),
101 );
102
103 static Widget _buildTruncatedAddress({
104 required String address,
105 required bool isMWEB,
106 required int chunkSize,
107 required TextStyle evenTextStyle,
108 required TextStyle oddTextStyle,
109 TextAlign? textAlign,
110 }) {
111 if (isMWEB) {
112 const fixedPrefix = 'ltcmweb';
113 final secondChunkStart = fixedPrefix.length;
114 const chunkSize = 4;
115 final secondChunk = address.substring(
116 secondChunkStart,
117 math.min(secondChunkStart + chunkSize, address.length),
118 );
119 final lastChunk = address.substring(address.length - chunkSize);
120
121 final spans = <TextSpan>[
122 TextSpan(text: '$fixedPrefix ', style: evenTextStyle),
123 TextSpan(text: '$secondChunk ', style: oddTextStyle),
124 TextSpan(text: '... ', style: oddTextStyle),
125 TextSpan(text: lastChunk, style: evenTextStyle),
126 ];
127
128 return _withAddressSemantics(
129 address: address,
130 child: RichText(
131 text: TextSpan(children: spans),
132 textAlign: textAlign ?? TextAlign.start,
133 overflow: TextOverflow.visible,
134 ),
135 );
136 } else {
137 final int digitCount = chunkSize;
138
139 if (address.length <= 2 * digitCount) {
140 return _buildFullSegmentedAddress(
141 address: address,
142 isMWEB: isMWEB,
143 chunkSize: chunkSize,
144 evenTextStyle: evenTextStyle,
145 oddTextStyle: oddTextStyle,
146 textAlign: textAlign,
147 );
148 }
149
150 final String firstPart = address.substring(0, digitCount);
151 final String secondPart = address.substring(digitCount, digitCount * 2);
152 final String lastPart = address.substring(address.length - digitCount);
153
154 final spans = <TextSpan>[
155 TextSpan(text: '$firstPart ', style: evenTextStyle),
156 TextSpan(text: '$secondPart ', style: oddTextStyle),
157 TextSpan(text: '... ', style: oddTextStyle),
158 TextSpan(text: lastPart, style: evenTextStyle),
159 ];
160
161 return _withAddressSemantics(
162 address: address,
163 child: RichText(
164 text: TextSpan(children: spans),
165 textAlign: textAlign ?? TextAlign.start,
166 overflow: TextOverflow.visible,
167 ),
168 );
169 }
170 }
171
172 static int _getChunkSize(WalletType walletType) {
173 switch (walletType) {
174 case WalletType.monero:
175 case WalletType.wownero:
176 case WalletType.zano:
177 return 6;
178 default:
179 return 4;
180 }
181 }
182 }