refactor: address resolver service
BIN
assets/images/add_contact_coins_img.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
assets/images/fio.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
10
assets/images/mastodon.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.7872 5.31335C22.4522 2.73468 20.2819 0.702486 17.7091 0.308701C17.275 0.242161 15.6304 0 11.8207 0H11.7923C7.98156 0 7.16402 0.242161 6.72995 0.308701C4.2288 0.691576 1.94467 2.5176 1.3905 5.12682C1.12395 6.41183 1.0955 7.83642 1.14502 9.14324C1.21561 11.0172 1.2293 12.888 1.39366 14.7544C1.50728 15.9941 1.7055 17.224 1.98681 18.4348C2.51359 20.671 4.646 22.5319 6.7352 23.2911C8.972 24.0829 11.3775 24.2143 13.6824 23.6707C13.9359 23.6096 14.1863 23.5387 14.4336 23.458C14.9941 23.2737 15.6504 23.0675 16.133 22.7054C16.1396 22.7003 16.145 22.6937 16.1488 22.6862C16.1527 22.6786 16.1548 22.6702 16.1551 22.6617V20.8531C16.155 20.8452 16.1531 20.8373 16.1497 20.8302C16.1463 20.8231 16.1414 20.8168 16.1353 20.8119C16.1292 20.807 16.1222 20.8035 16.1147 20.8018C16.1072 20.8001 16.0994 20.8001 16.0919 20.8019C14.6152 21.1671 13.102 21.3502 11.5837 21.3473C8.97086 21.3473 8.26815 20.0634 8.0669 19.5289C7.90516 19.0669 7.80246 18.5851 7.76138 18.0956C7.76094 18.0873 7.76241 18.0791 7.76564 18.0716C7.76884 18.0641 7.77373 18.0574 7.7799 18.0522C7.78606 18.047 7.7933 18.0434 7.80108 18.0416C7.80885 18.0399 7.81689 18.04 7.8246 18.0421C9.2767 18.4048 10.7653 18.5879 12.259 18.5875C12.6183 18.5875 12.9765 18.5875 13.3358 18.5777C14.8381 18.5341 16.4216 18.4544 17.8998 18.1556C17.9367 18.1479 17.9735 18.1414 18.0051 18.1316C20.3367 17.6679 22.5555 16.2128 22.7809 12.528C22.7894 12.383 22.8104 11.0085 22.8104 10.858C22.8115 10.3464 22.9695 7.22883 22.7872 5.31335Z" fill="url(#paint0_linear_3451_1074)"/>
|
||||
<path d="M19.0934 8.21051V14.6169H16.6418V8.39923C16.6418 7.09026 16.115 6.42267 15.0436 6.42267C13.8657 6.42267 13.2757 7.21242 13.2757 8.7723V12.1756H10.8388V8.7723C10.8388 7.21242 10.2478 6.42267 9.0699 6.42267C8.00473 6.42267 7.47267 7.09026 7.47267 8.39923V14.6169H5.02209V8.21051C5.02209 6.90154 5.34483 5.86164 5.99033 5.09079C6.65617 4.32176 7.52959 3.92688 8.61368 3.92688C9.86848 3.92688 10.8167 4.42647 11.4488 5.42457L12.0588 6.48485L12.6699 5.42457C13.302 4.42647 14.2502 3.92688 15.5029 3.92688C16.586 3.92688 17.4594 4.32176 18.1273 5.09079C18.7721 5.86091 19.0941 6.90081 19.0934 8.21051Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_3451_1074" x1="12" y1="0" x2="12" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6364FF"/>
|
||||
<stop offset="1" stop-color="#563ACC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/images/nostr.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
assets/images/open_alias.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/images/ud.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
assets/images/x_social.png
Normal file
After Width: | Height: | Size: 733 B |
16
lib/di.dart
|
@ -951,8 +951,10 @@ Future<void> setup({
|
|||
(ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact));
|
||||
|
||||
getIt.registerFactoryParam<ContactListViewModel, CryptoCurrency?, void>(
|
||||
(CryptoCurrency? cur, _) =>
|
||||
ContactListViewModel(_contactSource, _walletInfoSource, cur, getIt.get<SettingsStore>()));
|
||||
(CryptoCurrency? cur, _) =>
|
||||
ContactListViewModel(_contactSource, _walletInfoSource, getIt
|
||||
.get<AppStore>()
|
||||
.wallet!, cur, getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactoryParam<ContactListPage, CryptoCurrency?, void>((CryptoCurrency? cur, _) =>
|
||||
ContactListPage(getIt.get<ContactListViewModel>(param1: cur), getIt.get<AuthService>()));
|
||||
|
@ -1354,10 +1356,12 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => YatService());
|
||||
|
||||
getIt.registerFactory(() => AddressResolver(
|
||||
yatService: getIt.get<YatService>(),
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
settingsStore: getIt.get<SettingsStore>()));
|
||||
getIt.registerLazySingleton<AddressResolverService>(
|
||||
() => AddressResolverService(
|
||||
yatService: getIt<YatService>(),
|
||||
settingsStore: getIt<SettingsStore>(),
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
|
||||
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
|
||||
|
|
|
@ -82,7 +82,6 @@ class Bip353Record {
|
|||
}
|
||||
|
||||
static Future<String?> pickBip353AddressChoice(
|
||||
BuildContext context,
|
||||
String bip353Name,
|
||||
Map<String, String> addressMap,
|
||||
) async {
|
||||
|
@ -90,13 +89,12 @@ class Bip353Record {
|
|||
return addressMap.values.first;
|
||||
}
|
||||
|
||||
final chosenAddress = await _showAddressChoiceDialog(context, bip353Name, addressMap);
|
||||
final chosenAddress = await _showAddressChoiceDialog(bip353Name, addressMap);
|
||||
|
||||
return chosenAddress;
|
||||
}
|
||||
|
||||
static Future<String?> _showAddressChoiceDialog(
|
||||
BuildContext context,
|
||||
String bip353Name,
|
||||
Map<String, String> addressMap,
|
||||
) async {
|
||||
|
@ -118,24 +116,24 @@ class Bip353Record {
|
|||
|
||||
String? selectedOriginalValue;
|
||||
|
||||
if (context.mounted) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithPickerOption(
|
||||
alertTitle: S.of(context).multiple_addresses_detected + '\n$bip353Name',
|
||||
alertTitleTextSize: 14,
|
||||
alertSubtitle: S.of(context).please_choose_one + ':',
|
||||
options: displayItems,
|
||||
onOptionSelected: (Map<String, String> chosenItem) {
|
||||
selectedOriginalValue = chosenItem['originalValue'];
|
||||
},
|
||||
alertBarrierDismissible: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
return selectedOriginalValue;
|
||||
// if (context.mounted) {
|
||||
// await showPopUp<void>(
|
||||
// context: context,
|
||||
// builder: (dialogContext) {
|
||||
// return AlertWithPickerOption(
|
||||
// alertTitle: S.of(context).multiple_addresses_detected + '\n$bip353Name',
|
||||
// alertTitleTextSize: 14,
|
||||
// alertSubtitle: S.of(context).please_choose_one + ':',
|
||||
// options: displayItems,
|
||||
// onOptionSelected: (Map<String, String> chosenItem) {
|
||||
// selectedOriginalValue = chosenItem['originalValue'];
|
||||
// },
|
||||
// alertBarrierDismissible: true,
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
return selectedOriginalValue = displayItems.first['originalValue'];
|
||||
}
|
||||
|
||||
static String _truncate(String value, {int front = 6, int back = 6}) {
|
||||
|
|
|
@ -21,13 +21,12 @@ import 'package:flutter/cupertino.dart';
|
|||
|
||||
import 'bip_353_record.dart';
|
||||
|
||||
class AddressResolver {
|
||||
AddressResolver({required this.yatService, required this.wallet, required this.settingsStore})
|
||||
: walletType = wallet.type;
|
||||
class AddressResolverService {
|
||||
AddressResolverService({required this.yatService, required this.settingsStore}) {
|
||||
_buildLookupTable();
|
||||
}
|
||||
|
||||
final YatService yatService;
|
||||
final WalletType walletType;
|
||||
final WalletBase wallet;
|
||||
final SettingsStore settingsStore;
|
||||
|
||||
static const unstoppableDomains = [
|
||||
|
@ -165,6 +164,115 @@ class AddressResolver {
|
|||
"zone"
|
||||
];
|
||||
|
||||
late final List<LookupEntry> _lookupTable;
|
||||
|
||||
void _buildLookupTable() {
|
||||
_lookupTable = [
|
||||
LookupEntry(
|
||||
source: AddressSource.twitter,
|
||||
currencies: [CryptoCurrency.xmr],
|
||||
applies: (q) => settingsStore.lookupsTwitter && q.startsWith('@'),
|
||||
// x handle example: @username
|
||||
run: _lookupTwitter,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.zanoAlias,
|
||||
currencies: [CryptoCurrency.zano],
|
||||
applies: (q) => settingsStore.lookupsZanoAlias && q.startsWith('@'),
|
||||
// zano handle example: @username
|
||||
run: _lookupZano,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.mastodon,
|
||||
currencies: [CryptoCurrency.btc],
|
||||
applies: (q) =>
|
||||
settingsStore.lookupsMastodon &&
|
||||
q.startsWith('@') &&
|
||||
q.contains('@', 1) &&
|
||||
q.contains('.', 1),
|
||||
// Mastodon handle example: @username@hostname.xxx
|
||||
run: _lookupMastodon,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.wellKnown,
|
||||
currencies: [CryptoCurrency.btc],
|
||||
applies: (q) => settingsStore.lookupsWellKnown && q.contains('.') && q.contains('@'),
|
||||
// .well-known handle example:
|
||||
run: _lookupWellKnown,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.fio,
|
||||
currencies: [CryptoCurrency.btc],
|
||||
applies: (q) => !q.startsWith('@') && q.contains('@') && !q.contains('.'),
|
||||
// TODO: Add condition for FIO lookups
|
||||
// FIO handle example: username@domain
|
||||
run: _lookupFio,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.yatRecord,
|
||||
currencies: [CryptoCurrency.btc],
|
||||
applies: (q) => settingsStore.lookupsYatService && q.hasOnlyEmojis,
|
||||
// Yat handle example: 🐶🐾
|
||||
run: _lookupYatService,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.thorChain,
|
||||
currencies: [CryptoCurrency.rune],
|
||||
applies: (q) => true,
|
||||
// ThorChain handles can be any string //TODO: Add condition for ThorChain lookups
|
||||
run: _lookupThorChain,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.unstoppableDomains,
|
||||
currencies: [CryptoCurrency.btc],
|
||||
applies: (q) {
|
||||
if (settingsStore.lookupsUnstoppableDomains) return false;
|
||||
|
||||
// Unstoppable Domains handle example: name.crypto
|
||||
final formattedName = OpenaliasRecord.formatDomainName(q);
|
||||
final domainParts = formattedName.split('.');
|
||||
final name = domainParts.last;
|
||||
return domainParts.length > 1 &&
|
||||
domainParts.first.isNotEmpty &&
|
||||
name.isNotEmpty &&
|
||||
unstoppableDomains.any((domain) => name.trim() == domain);
|
||||
},
|
||||
run: _lookupsUnstoppableDomains,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.bip353,
|
||||
currencies: [CryptoCurrency.btc, CryptoCurrency.xmr],
|
||||
applies: (q) => true, //TODO: Add condition for BIP-353 lookups
|
||||
run: _lookupsBip353,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.ens,
|
||||
currencies: [CryptoCurrency.eth],
|
||||
applies: (q) => settingsStore.lookupsENS && q.endsWith('.eth'),
|
||||
// ENS handle example: name.eth
|
||||
run: _lookupEns,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.openAlias,
|
||||
currencies: [CryptoCurrency.btc],
|
||||
applies: (q) {
|
||||
if (settingsStore.lookupsOpenAlias) return false;
|
||||
// OpenAlias handle example:
|
||||
final formattedName = OpenaliasRecord.formatDomainName(q);
|
||||
return formattedName.contains(".");
|
||||
},
|
||||
run: _lookupsOpenAlias,
|
||||
),
|
||||
LookupEntry(
|
||||
source: AddressSource.nostr,
|
||||
currencies: [CryptoCurrency.btc],
|
||||
applies: (q) => isEmailFormat(q),
|
||||
// Nostr handle example: name@domain //TODO: Add condition for Nostr lookups
|
||||
run: _lookupsNostr,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
static String? extractAddressByType({required String raw, required CryptoCurrency type}) {
|
||||
final addressPattern = AddressValidator.getAddressFromStringPattern(type);
|
||||
|
||||
|
@ -193,197 +301,227 @@ class AddressResolver {
|
|||
return emailRegex.hasMatch(address);
|
||||
}
|
||||
|
||||
Future<ParsedAddress> resolve(BuildContext context, String text, CryptoCurrency currency) async {
|
||||
final ticker = currency.title;
|
||||
try {
|
||||
// twitter handle example: @username
|
||||
if (text.startsWith('@') && !text.substring(1).contains('@')) {
|
||||
if (currency == CryptoCurrency.zano && settingsStore.lookupsZanoAlias) {
|
||||
final formattedName = text.substring(1);
|
||||
final zanoAddress = await ZanoAlias.fetchZanoAliasAddress(formattedName);
|
||||
if (zanoAddress != null && zanoAddress.isNotEmpty) {
|
||||
return ParsedAddress.zanoAddress(
|
||||
address: zanoAddress,
|
||||
name: text,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (settingsStore.lookupsTwitter) {
|
||||
final formattedName = text.substring(1);
|
||||
final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);
|
||||
final addressFromBio = extractAddressByType(
|
||||
raw: twitterUser.description,
|
||||
type: CryptoCurrency.fromString(ticker, walletCurrency: wallet.currency));
|
||||
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||
return ParsedAddress.fetchTwitterAddress(
|
||||
address: addressFromBio,
|
||||
name: text,
|
||||
profileImageUrl: twitterUser.profileImageUrl,
|
||||
profileName: twitterUser.name);
|
||||
}
|
||||
Future<List<ParsedAddress>> resolve(
|
||||
{required String query, required WalletBase wallet, CryptoCurrency? currency}) async {
|
||||
final List<ParsedAddress> out = [];
|
||||
final List<Future<void>> tasks = [];
|
||||
|
||||
final pinnedTweet = twitterUser.pinnedTweet?.text;
|
||||
if (pinnedTweet != null) {
|
||||
final addressFromPinnedTweet = extractAddressByType(
|
||||
raw: pinnedTweet,
|
||||
type: CryptoCurrency.fromString(ticker, walletCurrency: wallet.currency));
|
||||
if (addressFromPinnedTweet != null) {
|
||||
return ParsedAddress.fetchTwitterAddress(
|
||||
address: addressFromPinnedTweet,
|
||||
name: text,
|
||||
profileImageUrl: twitterUser.profileImageUrl,
|
||||
profileName: twitterUser.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final entry in _lookupTable) {
|
||||
if (!entry.applies(query)) continue;
|
||||
|
||||
final Iterable<CryptoCurrency> coins = currency == null
|
||||
? entry.currencies
|
||||
: (entry.currencies.contains(currency) ? [currency] : const <CryptoCurrency>[]);
|
||||
|
||||
for (final cur in coins) {
|
||||
tasks.add(entry.run(query, cur, wallet).then((res) {
|
||||
if (res != null) out.add(res);
|
||||
}));
|
||||
}
|
||||
|
||||
// Mastodon example: @username@hostname.xxx
|
||||
if (text.startsWith('@') && text.contains('@', 1) && text.contains('.', 1)) {
|
||||
if (settingsStore.lookupsMastodon) {
|
||||
final subText = text.substring(1);
|
||||
final hostNameIndex = subText.indexOf('@');
|
||||
final hostName = subText.substring(hostNameIndex + 1);
|
||||
final userName = subText.substring(0, hostNameIndex);
|
||||
|
||||
final mastodonUser =
|
||||
await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName);
|
||||
|
||||
if (mastodonUser != null) {
|
||||
String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency);
|
||||
|
||||
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||
return ParsedAddress.fetchMastodonAddress(
|
||||
address: addressFromBio,
|
||||
name: text,
|
||||
profileImageUrl: mastodonUser.profileImageUrl,
|
||||
profileName: mastodonUser.username);
|
||||
} else {
|
||||
final pinnedPosts =
|
||||
await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName);
|
||||
|
||||
if (pinnedPosts.isNotEmpty) {
|
||||
final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n');
|
||||
String? addressFromPinnedPost =
|
||||
extractAddressByType(raw: userPinnedPostsText, type: currency);
|
||||
|
||||
if (addressFromPinnedPost != null && addressFromPinnedPost.isNotEmpty) {
|
||||
return ParsedAddress.fetchMastodonAddress(
|
||||
address: addressFromPinnedPost,
|
||||
name: text,
|
||||
profileImageUrl: mastodonUser.profileImageUrl,
|
||||
profileName: mastodonUser.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .well-known scheme:
|
||||
if (text.contains('.') && text.contains('@')) {
|
||||
if (settingsStore.lookupsWellKnown) {
|
||||
final record =
|
||||
await WellKnownRecord.fetchAddressAndName(formattedName: text, currency: currency);
|
||||
if (record != null) {
|
||||
return ParsedAddress.fetchWellKnownAddress(address: record.address, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) {
|
||||
final bool isFioRegistered = await FioAddressProvider.checkAvail(text);
|
||||
if (isFioRegistered) {
|
||||
final address = await FioAddressProvider.getPubAddress(text, ticker);
|
||||
return ParsedAddress.fetchFioAddress(address: address, name: text);
|
||||
}
|
||||
}
|
||||
if (text.hasOnlyEmojis) {
|
||||
if (settingsStore.lookupsYatService) {
|
||||
if (walletType != WalletType.haven) {
|
||||
final addresses = await yatService.fetchYatAddress(text, ticker);
|
||||
return ParsedAddress.fetchEmojiAddress(addresses: addresses, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final thorChainAddress = await ThorChainExchangeProvider.lookupAddressByName(text);
|
||||
if (thorChainAddress != null && thorChainAddress.isNotEmpty) {
|
||||
String? address =
|
||||
thorChainAddress[ticker] ?? (ticker == 'RUNE' ? thorChainAddress['THOR'] : null);
|
||||
if (address != null) {
|
||||
return ParsedAddress.thorChainAddress(address: address, name: text);
|
||||
}
|
||||
}
|
||||
|
||||
final formattedName = OpenaliasRecord.formatDomainName(text);
|
||||
final domainParts = formattedName.split('.');
|
||||
final name = domainParts.last;
|
||||
|
||||
if (domainParts.length <= 1 || domainParts.first.isEmpty || name.isEmpty) {
|
||||
return ParsedAddress(addresses: [text]);
|
||||
}
|
||||
|
||||
if (unstoppableDomains.any((domain) => name.trim() == domain)) {
|
||||
if (settingsStore.lookupsUnstoppableDomains) {
|
||||
final address = await fetchUnstoppableDomainAddress(text, ticker);
|
||||
if (address.isNotEmpty) {
|
||||
return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final bip353AddressMap = await Bip353Record.fetchUriByCryptoCurrency(text, ticker);
|
||||
|
||||
if (bip353AddressMap != null && bip353AddressMap.isNotEmpty) {
|
||||
final chosenAddress = await Bip353Record.pickBip353AddressChoice(context, text, bip353AddressMap);
|
||||
if (chosenAddress != null) {
|
||||
return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text);
|
||||
}
|
||||
}
|
||||
|
||||
if (text.endsWith(".eth")) {
|
||||
if (settingsStore.lookupsENS) {
|
||||
final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet);
|
||||
if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") {
|
||||
return ParsedAddress.fetchEnsAddress(name: text, address: address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (formattedName.contains(".")) {
|
||||
if (settingsStore.lookupsOpenAlias) {
|
||||
final txtRecord = await OpenaliasRecord.lookupOpenAliasRecord(formattedName);
|
||||
|
||||
if (txtRecord != null) {
|
||||
final record = await OpenaliasRecord.fetchAddressAndName(
|
||||
formattedName: formattedName, ticker: ticker.toLowerCase(), txtRecord: txtRecord);
|
||||
return ParsedAddress.fetchOpenAliasAddress(record: record, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isEmailFormat(text)) {
|
||||
final nostrProfile = await NostrProfileHandler.queryProfile(context, text);
|
||||
if (nostrProfile?.relays != null) {
|
||||
final nostrUserData =
|
||||
await NostrProfileHandler.processRelays(context, nostrProfile!, text);
|
||||
|
||||
if (nostrUserData != null) {
|
||||
String? addressFromBio = extractAddressByType(raw: nostrUserData.about, type: currency);
|
||||
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||
return ParsedAddress.nostrAddress(
|
||||
address: addressFromBio,
|
||||
name: text,
|
||||
profileImageUrl: nostrUserData.picture,
|
||||
profileName: nostrUserData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
}
|
||||
|
||||
return ParsedAddress(addresses: [text]);
|
||||
await Future.wait(tasks);
|
||||
|
||||
if (out.isEmpty) out.add(ParsedAddress(addresses: [query]));
|
||||
return out;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupTwitter(
|
||||
String text, CryptoCurrency currency, WalletBase wallet) async {
|
||||
final formattedName = text.substring(1);
|
||||
final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);
|
||||
final addressFromBio = extractAddressByType(
|
||||
raw: twitterUser.description,
|
||||
type: CryptoCurrency.fromString(currency.title, walletCurrency: wallet.currency));
|
||||
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||
return ParsedAddress.fetchTwitterAddress(
|
||||
address: addressFromBio,
|
||||
name: text,
|
||||
profileImageUrl: twitterUser.profileImageUrl,
|
||||
profileName: twitterUser.name);
|
||||
}
|
||||
|
||||
final pinnedTweet = twitterUser.pinnedTweet?.text;
|
||||
if (pinnedTweet != null) {
|
||||
final addressFromPinnedTweet = extractAddressByType(
|
||||
raw: pinnedTweet,
|
||||
type: CryptoCurrency.fromString(currency.title, walletCurrency: wallet.currency));
|
||||
if (addressFromPinnedTweet != null) {
|
||||
return ParsedAddress.fetchTwitterAddress(
|
||||
address: addressFromPinnedTweet,
|
||||
name: text,
|
||||
profileImageUrl: twitterUser.profileImageUrl,
|
||||
profileName: twitterUser.name);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupZano(String text, CryptoCurrency currency, WalletBase _) async {
|
||||
final formattedName = text.substring(1);
|
||||
final zanoAddress = await ZanoAlias.fetchZanoAliasAddress(formattedName);
|
||||
if (zanoAddress != null && zanoAddress.isNotEmpty) {
|
||||
return ParsedAddress.zanoAddress(
|
||||
address: zanoAddress,
|
||||
name: text,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupMastodon(String text, CryptoCurrency currency, WalletBase _) async {
|
||||
final subText = text.substring(1);
|
||||
final hostNameIndex = subText.indexOf('@');
|
||||
final hostName = subText.substring(hostNameIndex + 1);
|
||||
final userName = subText.substring(0, hostNameIndex);
|
||||
|
||||
final mastodonUser =
|
||||
await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName);
|
||||
|
||||
if (mastodonUser != null) {
|
||||
String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency);
|
||||
|
||||
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||
return ParsedAddress.fetchMastodonAddress(
|
||||
address: addressFromBio,
|
||||
name: text,
|
||||
profileImageUrl: mastodonUser.profileImageUrl,
|
||||
profileName: mastodonUser.username);
|
||||
} else {
|
||||
final pinnedPosts =
|
||||
await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName);
|
||||
|
||||
if (pinnedPosts.isNotEmpty) {
|
||||
final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n');
|
||||
String? addressFromPinnedPost =
|
||||
extractAddressByType(raw: userPinnedPostsText, type: currency);
|
||||
|
||||
if (addressFromPinnedPost != null && addressFromPinnedPost.isNotEmpty) {
|
||||
return ParsedAddress.fetchMastodonAddress(
|
||||
address: addressFromPinnedPost,
|
||||
name: text,
|
||||
profileImageUrl: mastodonUser.profileImageUrl,
|
||||
profileName: mastodonUser.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupWellKnown(
|
||||
String text, CryptoCurrency currency, WalletBase _) async {
|
||||
final record =
|
||||
await WellKnownRecord.fetchAddressAndName(formattedName: text, currency: currency);
|
||||
if (record != null) {
|
||||
return ParsedAddress.fetchWellKnownAddress(address: record.address, name: text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupFio(String text, CryptoCurrency currency, WalletBase _) async {
|
||||
final bool isFioRegistered = await FioAddressProvider.checkAvail(text);
|
||||
if (isFioRegistered) {
|
||||
final address = await FioAddressProvider.getPubAddress(text, currency.title);
|
||||
return ParsedAddress.fetchFioAddress(address: address, name: text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupYatService(
|
||||
String text, CryptoCurrency currency, WalletBase _) async {
|
||||
final addresses = await yatService.fetchYatAddress(text, currency.title);
|
||||
return ParsedAddress.fetchEmojiAddress(addresses: addresses, name: text);
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupThorChain(
|
||||
String text, CryptoCurrency currency, WalletBase _) async {
|
||||
final thorChainAddress = await ThorChainExchangeProvider.lookupAddressByName(text);
|
||||
if (thorChainAddress != null && thorChainAddress.isNotEmpty) {
|
||||
String? address = thorChainAddress[currency.title] ??
|
||||
(currency.title == 'RUNE' ? thorChainAddress['THOR'] : null);
|
||||
if (address != null) {
|
||||
return ParsedAddress.thorChainAddress(address: address, name: text);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupsUnstoppableDomains(
|
||||
String text, CryptoCurrency currency, WalletBase _) async {
|
||||
final address = await fetchUnstoppableDomainAddress(text, currency.title);
|
||||
if (address.isNotEmpty) {
|
||||
return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupsBip353(String text, CryptoCurrency currency, WalletBase _) async {
|
||||
final bip353AddressMap = await Bip353Record.fetchUriByCryptoCurrency(text, currency.title);
|
||||
|
||||
if (bip353AddressMap != null && bip353AddressMap.isNotEmpty) {
|
||||
final chosenAddress =
|
||||
await Bip353Record.pickBip353AddressChoice(text, bip353AddressMap); //TODO fix context
|
||||
if (chosenAddress != null) {
|
||||
return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupEns(String text, CryptoCurrency currency, WalletBase wallet) async {
|
||||
final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet);
|
||||
if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") {
|
||||
return ParsedAddress.fetchEnsAddress(name: text, address: address);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupsOpenAlias(
|
||||
String text, CryptoCurrency currency, WalletBase _) async {
|
||||
final formattedName = OpenaliasRecord.formatDomainName(text);
|
||||
final txtRecord = await OpenaliasRecord.lookupOpenAliasRecord(formattedName);
|
||||
|
||||
if (txtRecord != null) {
|
||||
final record = await OpenaliasRecord.fetchAddressAndName(
|
||||
formattedName: formattedName, ticker: currency.title.toLowerCase(), txtRecord: txtRecord);
|
||||
return ParsedAddress.fetchOpenAliasAddress(record: record, name: text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ParsedAddress?> _lookupsNostr(String text, CryptoCurrency currency, WalletBase _) async {
|
||||
//TODO implement Nostr lookup logic
|
||||
// final nostrProfile = await NostrProfileHandler.queryProfile(context, text);
|
||||
// if (nostrProfile?.relays != null) {
|
||||
// final nostrUserData =
|
||||
// await NostrProfileHandler.processRelays(context, nostrProfile!, text);
|
||||
//
|
||||
// if (nostrUserData != null) {
|
||||
// String? addressFromBio = extractAddressByType(raw: nostrUserData.about, type: currency);
|
||||
// if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||
// return ParsedAddress.nostrAddress(
|
||||
// address: addressFromBio,
|
||||
// name: text,
|
||||
// profileImageUrl: nostrUserData.picture,
|
||||
// profileName: nostrUserData.name);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class LookupEntry {
|
||||
const LookupEntry({
|
||||
required this.source,
|
||||
required this.currencies,
|
||||
required this.applies,
|
||||
required this.run,
|
||||
});
|
||||
|
||||
final AddressSource source;
|
||||
final List<CryptoCurrency> currencies;
|
||||
final bool Function(String query) applies;
|
||||
final Future<ParsedAddress?> Function(String query, CryptoCurrency currenc, WalletBase walle) run;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,82 @@
|
|||
import 'package:cake_wallet/entities/openalias_record.dart';
|
||||
import 'package:cake_wallet/entities/yat_record.dart';
|
||||
|
||||
enum ParseFrom {
|
||||
unstoppableDomains,
|
||||
openAlias,
|
||||
yatRecord,
|
||||
fio,
|
||||
notParsed,
|
||||
twitter,
|
||||
ens,
|
||||
contact,
|
||||
mastodon,
|
||||
nostr,
|
||||
thorChain,
|
||||
wellKnown,
|
||||
zanoAlias,
|
||||
bip353
|
||||
enum AddressSource {
|
||||
twitter(
|
||||
label: 'X',
|
||||
iconPath: 'assets/images/x_social.png',
|
||||
alias: '@username'
|
||||
),
|
||||
unstoppableDomains(
|
||||
label: 'Unstoppable Domains',
|
||||
iconPath: 'assets/images/ud.png',
|
||||
alias: 'domain.tld',
|
||||
),
|
||||
openAlias(
|
||||
label: 'OpenAlias',
|
||||
iconPath: 'assets/images/open_alias.png',
|
||||
alias: 'oa',
|
||||
),
|
||||
yatRecord(
|
||||
label: 'Yat',
|
||||
iconPath: 'assets/images/yat_mini_logo.png',
|
||||
),
|
||||
fio(
|
||||
label: 'FIO',
|
||||
iconPath: 'assets/images/fio.png',
|
||||
),
|
||||
ens(
|
||||
label: 'Ethereum Name Service',
|
||||
iconPath: 'assets/images/ens_icon.png',
|
||||
),
|
||||
mastodon(
|
||||
label: 'Mastodon',
|
||||
iconPath: 'assets/images/mastodon.svg',
|
||||
alias: 'user@domain.tld'
|
||||
),
|
||||
nostr(
|
||||
label: 'Nostr',
|
||||
iconPath: 'assets/images/nostr.png',
|
||||
),
|
||||
thorChain(
|
||||
label: 'ThorChain',
|
||||
iconPath: 'assets/images/thorchain.png',
|
||||
),
|
||||
wellKnown(
|
||||
label: '.well-known',
|
||||
iconPath: 'assets/icons/wk.svg',
|
||||
),
|
||||
zanoAlias(
|
||||
label: 'Zano Alias',
|
||||
iconPath: 'assets/images/zano_icon.png',
|
||||
),
|
||||
bip353(
|
||||
label: 'BIP-353',
|
||||
iconPath: 'assets/images/bip353.svg',
|
||||
),
|
||||
contact(
|
||||
label: 'Contact',
|
||||
iconPath: '',
|
||||
),
|
||||
notParsed(
|
||||
label: 'Unknown',
|
||||
iconPath: '',
|
||||
);
|
||||
|
||||
const AddressSource({
|
||||
required this.label,
|
||||
required this.iconPath,
|
||||
this.alias = '',
|
||||
});
|
||||
|
||||
final String label;
|
||||
final String iconPath;
|
||||
final String alias;
|
||||
|
||||
static List<AddressSource> supported({
|
||||
Set<AddressSource> exclude = const {AddressSource.notParsed, AddressSource.contact},
|
||||
}) =>
|
||||
values.where((src) => !exclude.contains(src)).toList();
|
||||
}
|
||||
|
||||
class ParsedAddress {
|
||||
|
@ -25,7 +86,7 @@ class ParsedAddress {
|
|||
this.description = '',
|
||||
this.profileImageUrl = '',
|
||||
this.profileName = '',
|
||||
this.parseFrom = ParseFrom.notParsed,
|
||||
this.parseFrom = AddressSource.notParsed,
|
||||
});
|
||||
|
||||
factory ParsedAddress.fetchEmojiAddress({
|
||||
|
@ -33,12 +94,12 @@ class ParsedAddress {
|
|||
required String name,
|
||||
}) {
|
||||
if (addresses?.isEmpty ?? true) {
|
||||
return ParsedAddress(addresses: [name], parseFrom: ParseFrom.yatRecord);
|
||||
return ParsedAddress(addresses: [name], parseFrom: AddressSource.yatRecord);
|
||||
}
|
||||
return ParsedAddress(
|
||||
addresses: addresses!.map((e) => e.address).toList(),
|
||||
name: name,
|
||||
parseFrom: ParseFrom.yatRecord,
|
||||
parseFrom: AddressSource.yatRecord,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -52,18 +113,18 @@ class ParsedAddress {
|
|||
return ParsedAddress(
|
||||
addresses: [address!],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.unstoppableDomains,
|
||||
parseFrom: AddressSource.unstoppableDomains,
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchBip353AddressAddress ({
|
||||
factory ParsedAddress.fetchBip353AddressAddress({
|
||||
required String address,
|
||||
required String name,
|
||||
}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.bip353,
|
||||
parseFrom: AddressSource.bip353,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -76,7 +137,7 @@ class ParsedAddress {
|
|||
addresses: [record.address],
|
||||
name: record.name,
|
||||
description: record.description,
|
||||
parseFrom: ParseFrom.openAlias,
|
||||
parseFrom: AddressSource.openAlias,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -84,7 +145,7 @@ class ParsedAddress {
|
|||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.fio,
|
||||
parseFrom: AddressSource.fio,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -100,7 +161,7 @@ class ParsedAddress {
|
|||
description: description ?? '',
|
||||
profileImageUrl: profileImageUrl,
|
||||
profileName: profileName,
|
||||
parseFrom: ParseFrom.twitter,
|
||||
parseFrom: AddressSource.twitter,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -112,7 +173,7 @@ class ParsedAddress {
|
|||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.mastodon,
|
||||
parseFrom: AddressSource.mastodon,
|
||||
profileImageUrl: profileImageUrl,
|
||||
profileName: profileName,
|
||||
);
|
||||
|
@ -122,7 +183,7 @@ class ParsedAddress {
|
|||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.contact,
|
||||
parseFrom: AddressSource.contact,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -130,7 +191,7 @@ class ParsedAddress {
|
|||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.ens,
|
||||
parseFrom: AddressSource.ens,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -142,7 +203,7 @@ class ParsedAddress {
|
|||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.nostr,
|
||||
parseFrom: AddressSource.nostr,
|
||||
profileImageUrl: profileImageUrl,
|
||||
profileName: profileName,
|
||||
);
|
||||
|
@ -152,7 +213,7 @@ class ParsedAddress {
|
|||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.thorChain,
|
||||
parseFrom: AddressSource.thorChain,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -160,7 +221,7 @@ class ParsedAddress {
|
|||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.zanoAlias,
|
||||
parseFrom: AddressSource.zanoAlias,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -168,7 +229,7 @@ class ParsedAddress {
|
|||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.wellKnown,
|
||||
parseFrom: AddressSource.wellKnown,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -177,5 +238,5 @@ class ParsedAddress {
|
|||
final String description;
|
||||
final String profileImageUrl;
|
||||
final String profileName;
|
||||
final ParseFrom parseFrom;
|
||||
final AddressSource parseFrom;
|
||||
}
|
||||
|
|
|
@ -509,8 +509,8 @@ class BuySellPage extends BasePage {
|
|||
|
||||
Future<String> fetchParsedAddress(
|
||||
BuildContext context, String domain, CryptoCurrency currency) async {
|
||||
final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
|
||||
final address = await extractAddressFromParsed(context, parsedAddress);
|
||||
final parsedAddress = await getIt.get<AddressResolverService>().resolve(query: domain,wallet: buySellViewModel.wallet, currency: currency);
|
||||
final address = await extractAddressFromParsed(context, parsedAddress.first); //TODO handle multiple addresses
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/contact_base.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/entities/wallet_list_order_types.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
|
@ -8,7 +10,10 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
|||
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_list_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/filtered_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/add_contact_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_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';
|
||||
|
@ -64,7 +69,10 @@ class ContactListPage extends BasePage {
|
|||
contactListViewModel.shouldRequireTOTP2FAForAddingContacts,
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(Routes.addressBookAddContact);
|
||||
//await Navigator.of(context).pushNamed(Routes.addressBookAddContact); //TODO remove old flow
|
||||
|
||||
await _showAddContactBottomSheet(context, currentTheme, contactListViewModel);
|
||||
|
||||
}
|
||||
},
|
||||
child: Offstage(),
|
||||
|
@ -75,8 +83,34 @@ class ContactListPage extends BasePage {
|
|||
),
|
||||
),
|
||||
);
|
||||
} Future<void> _showAddContactBottomSheet (BuildContext context, MaterialThemeBase currentTheme, ContactListViewModel contactListViewModel) async {
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext bottomSheetContext) {
|
||||
return AddContactBottomSheet(
|
||||
titleText: S.of(context).add_contact,
|
||||
currentTheme: currentTheme,
|
||||
footerType: FooterType.none,
|
||||
contentImage: 'assets/images/add_contact_coins_img.png',
|
||||
content: 'Contacts allows you to create a profile with multiple addresses, as well as detect them automatically from social media profiles.Start by entering a social handle or an address manually',
|
||||
singleActionButtonText: S.of(context).seed_language_next,
|
||||
onHandlerSearch: (query) async {
|
||||
final address = await getIt.get<AddressResolverService>().resolve(query: query,wallet: contactListViewModel.wallet);
|
||||
print('Address resolved: $address');
|
||||
return address;
|
||||
},
|
||||
onSingleActionButtonPressed: () async {
|
||||
await Navigator.of(bottomSheetContext).pushNamed(Routes.addressBookAddContact);
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => ContactPageBody(contactListViewModel: contactListViewModel);
|
||||
}
|
||||
|
|
|
@ -641,8 +641,8 @@ class ExchangePage extends BasePage {
|
|||
|
||||
Future<String> fetchParsedAddress(
|
||||
BuildContext context, String domain, CryptoCurrency currency) async {
|
||||
final parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
|
||||
final address = await extractAddressFromParsed(context, parsedAddress);
|
||||
final parsedAddress = await getIt.get<AddressResolverService>().resolve(query: domain, currency: currency, wallet: exchangeViewModel.wallet);
|
||||
final address = await extractAddressFromParsed(context, parsedAddress.first); //TODO handle multiple addresses
|
||||
return address;
|
||||
}
|
||||
|
||||
|
|
|
@ -626,7 +626,7 @@ class SendPage extends BasePage {
|
|||
contentImage: 'assets/images/contact.png',
|
||||
contentImageColor: Theme.of(context).colorScheme.onSurface,
|
||||
content: S.of(bottomSheetContext).add_contact_to_address_book,
|
||||
bottomActionPanel: Padding(
|
||||
actionPanel: Padding(
|
||||
padding: const EdgeInsets.only(left: 34.0),
|
||||
child: Row(
|
||||
children: [
|
||||
|
|
|
@ -15,68 +15,68 @@ Future<String> extractAddressFromParsed(
|
|||
var profileName = '';
|
||||
|
||||
switch (parsedAddress.parseFrom) {
|
||||
case ParseFrom.unstoppableDomains:
|
||||
case AddressSource.unstoppableDomains:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).address_from_domain(parsedAddress.name);
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.ens:
|
||||
case AddressSource.ens:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (ENS)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.openAlias:
|
||||
case AddressSource.openAlias:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (OpenAlias)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.wellKnown:
|
||||
case AddressSource.wellKnown:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Well-Known)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.fio:
|
||||
case AddressSource.fio:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (FIO)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.twitter:
|
||||
case AddressSource.twitter:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Twitter)');
|
||||
address = parsedAddress.addresses.first;
|
||||
profileImageUrl = parsedAddress.profileImageUrl;
|
||||
profileName = parsedAddress.profileName;
|
||||
break;
|
||||
case ParseFrom.mastodon:
|
||||
case AddressSource.mastodon:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Mastodon)');
|
||||
address = parsedAddress.addresses.first;
|
||||
profileImageUrl = parsedAddress.profileImageUrl;
|
||||
profileName = parsedAddress.profileName;
|
||||
break;
|
||||
case ParseFrom.nostr:
|
||||
case AddressSource.nostr:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Nostr NIP-05)');
|
||||
address = parsedAddress.addresses.first;
|
||||
profileImageUrl = parsedAddress.profileImageUrl;
|
||||
profileName = parsedAddress.profileName;
|
||||
break;
|
||||
case ParseFrom.thorChain:
|
||||
case AddressSource.thorChain:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (ThorChain)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.zanoAlias:
|
||||
case AddressSource.zanoAlias:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Zano Alias)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.bip353:
|
||||
case AddressSource.bip353:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (BIP-353)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.yatRecord:
|
||||
case AddressSource.yatRecord:
|
||||
if (parsedAddress.name.isEmpty) {
|
||||
title = S.of(context).yat_error;
|
||||
content = S.of(context).yat_error_content;
|
||||
|
@ -111,8 +111,8 @@ Future<String> extractAddressFromParsed(
|
|||
}
|
||||
|
||||
return address;
|
||||
case ParseFrom.contact:
|
||||
case ParseFrom.notParsed:
|
||||
case AddressSource.contact:
|
||||
case AddressSource.notParsed:
|
||||
address = parsedAddress.addresses.first;
|
||||
return address;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,359 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
|
||||
import 'package:cake_wallet/utils/image_utill.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AddContactBottomSheet extends InfoBottomSheet {
|
||||
AddContactBottomSheet({
|
||||
required String titleText,
|
||||
String? titleIconPath,
|
||||
required this.currentTheme,
|
||||
required FooterType footerType,
|
||||
this.contentImage,
|
||||
this.contentImageColor,
|
||||
this.content,
|
||||
required this.onHandlerSearch,
|
||||
String? singleActionButtonText,
|
||||
VoidCallback? onSingleActionButtonPressed,
|
||||
Key? singleActionButtonKey,
|
||||
String? doubleActionLeftButtonText,
|
||||
String? doubleActionRightButtonText,
|
||||
VoidCallback? onLeftActionButtonPressed,
|
||||
VoidCallback? onRightActionButtonPressed,
|
||||
Key? leftActionButtonKey,
|
||||
Key? rightActionButtonKey,
|
||||
}) : _onSingleActionButtonPressed = onSingleActionButtonPressed,
|
||||
_singleActionButtonText = singleActionButtonText,
|
||||
_singleActionButtonKey = singleActionButtonKey,
|
||||
super(
|
||||
titleText: titleText,
|
||||
titleIconPath: titleIconPath,
|
||||
currentTheme: currentTheme,
|
||||
footerType: footerType,
|
||||
contentImage: contentImage,
|
||||
contentImageColor: contentImageColor,
|
||||
content: content,
|
||||
singleActionButtonText: singleActionButtonText,
|
||||
onSingleActionButtonPressed: onSingleActionButtonPressed,
|
||||
singleActionButtonKey: singleActionButtonKey,
|
||||
doubleActionLeftButtonText: doubleActionLeftButtonText,
|
||||
doubleActionRightButtonText: doubleActionRightButtonText,
|
||||
onLeftActionButtonPressed: onLeftActionButtonPressed,
|
||||
onRightActionButtonPressed: onRightActionButtonPressed,
|
||||
leftActionButtonKey: leftActionButtonKey,
|
||||
rightActionButtonKey: rightActionButtonKey,
|
||||
);
|
||||
|
||||
final MaterialThemeBase currentTheme;
|
||||
final String? contentImage;
|
||||
final Color? contentImageColor;
|
||||
final String? content;
|
||||
final String? _singleActionButtonText;
|
||||
final VoidCallback? _onSingleActionButtonPressed;
|
||||
final Key? _singleActionButtonKey;
|
||||
final Future<List<ParsedAddress>> Function(String query) onHandlerSearch;
|
||||
|
||||
@override
|
||||
Widget? buildHeader(BuildContext context) => null;
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
final maxHeight = MediaQuery.of(context).size.height * 0.65;
|
||||
|
||||
return SizedBox(
|
||||
height: maxHeight,
|
||||
child: Navigator(
|
||||
onPopPage: (route, result) => route.didPop(result),
|
||||
pages: [
|
||||
MaterialPage(
|
||||
child: _MainPage(
|
||||
currentTheme: currentTheme,
|
||||
contentImage: contentImage,
|
||||
contentImageColor: contentImageColor,
|
||||
contentText: content,
|
||||
singleActionButtonText: _singleActionButtonText,
|
||||
onSingleActionButtonPressed: _onSingleActionButtonPressed,
|
||||
singleActionButtonKey: _singleActionButtonKey,
|
||||
onSearch: (query) async => await onHandlerSearch(query),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MainPage extends StatefulWidget {
|
||||
const _MainPage({
|
||||
required this.currentTheme,
|
||||
required this.onSearch,
|
||||
this.contentImage,
|
||||
this.contentImageColor,
|
||||
this.contentText,
|
||||
this.singleActionButtonText,
|
||||
this.onSingleActionButtonPressed,
|
||||
this.singleActionButtonKey,
|
||||
});
|
||||
|
||||
final MaterialThemeBase currentTheme;
|
||||
final Future<List<ParsedAddress>> Function(String query) onSearch;
|
||||
final String? contentImage;
|
||||
final Color? contentImageColor;
|
||||
final String? contentText;
|
||||
final String? singleActionButtonText;
|
||||
final VoidCallback? onSingleActionButtonPressed;
|
||||
final Key? singleActionButtonKey;
|
||||
|
||||
@override
|
||||
State<_MainPage> createState() => _MainPageState();
|
||||
}
|
||||
|
||||
class _MainPageState extends State<_MainPage> {
|
||||
final _controller = TextEditingController();
|
||||
final _debouncer = Duration(milliseconds: 500);
|
||||
Timer? _debounceTimer;
|
||||
List<ParsedAddress> _results = [];
|
||||
bool _isSearching = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounceTimer?.cancel();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleChanged(String query) {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = Timer(_debouncer, () async {
|
||||
if (query.trim().isEmpty) {
|
||||
setState(() => _results = []);
|
||||
return;
|
||||
}
|
||||
setState(() => _isSearching = true);
|
||||
try {
|
||||
final res = await widget.onSearch(query);
|
||||
if (mounted) setState(() => _results = res);
|
||||
} finally {
|
||||
if (mounted) setState(() => _isSearching = false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||
final fillColor = widget.currentTheme.isDark
|
||||
? CustomThemeColors.backgroundGradientColorDark
|
||||
: CustomThemeColors.backgroundGradientColorLight;
|
||||
|
||||
return AnimatedPadding(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
padding: EdgeInsets.only(bottom: bottomInset),
|
||||
child: SingleChildScrollView(
|
||||
reverse: true,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.contentImage != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: ImageUtil.getImageFromPath(
|
||||
imagePath: widget.contentImage!,
|
||||
svgImageColor: widget.contentImageColor,
|
||||
fit: BoxFit.contain,
|
||||
height: 120,
|
||||
),
|
||||
),
|
||||
if (widget.contentText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
widget.contentText!,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyLarge!,
|
||||
),
|
||||
),
|
||||
if (_results.isNotEmpty || _isSearching)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 0),
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxHeight: 180),
|
||||
decoration: BoxDecoration(
|
||||
color: fillColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: _isSearching
|
||||
? const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
)
|
||||
: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: _results.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1, thickness: .5),
|
||||
itemBuilder: (_, i) {
|
||||
final addr = _results[i];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
title: Text(addr.name.isEmpty ? addr.addresses.first : addr.name),
|
||||
subtitle: addr.name.isEmpty
|
||||
? null
|
||||
: Text(addr.addresses.first,
|
||||
style: Theme.of(context).textTheme.bodySmall),
|
||||
onTap: () {
|
||||
// pass selection back
|
||||
Navigator.of(context).pop(addr);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
BaseTextFormField(
|
||||
controller: _controller,
|
||||
fillColor: fillColor,
|
||||
hintText: 'Enter an address or handle',
|
||||
onChanged: _handleChanged,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium!,
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => Navigator.of(context).push(
|
||||
_slideLeft(_SupportedHandlesPage(fillColor: fillColor)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('View supported handles',
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
const SizedBox(width: 4),
|
||||
Icon(Icons.arrow_forward_ios,
|
||||
size: 14, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 34),
|
||||
child: LoadingPrimaryButton(
|
||||
key: widget.singleActionButtonKey,
|
||||
text: widget.singleActionButtonText ?? '',
|
||||
onPressed: widget.onSingleActionButtonPressed ?? () {},
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SupportedHandlesPage extends StatelessWidget {
|
||||
const _SupportedHandlesPage({required this.fillColor});
|
||||
|
||||
final Color fillColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final supportedHandles = AddressSource.supported();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
appBar: AppBar(
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
leading: MergeSemantics(
|
||||
child: SizedBox(
|
||||
height: 37,
|
||||
width: 37,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: Semantics(
|
||||
label: S.of(context).seed_alert_back,
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
overlayColor: WidgetStateColor.resolveWith((states) => Colors.transparent)),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: backButton(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text('Supported handles', style: Theme.of(context).textTheme.titleLarge),
|
||||
),
|
||||
body: ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
itemCount: supportedHandles.length,
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 6),
|
||||
itemBuilder: (context, index) {
|
||||
final src = supportedHandles[index];
|
||||
return ListTile(
|
||||
title: Text(src.label, style: Theme.of(context).textTheme.bodyMedium),
|
||||
trailing: Text(src.alias, style: Theme.of(context).textTheme.bodyMedium),
|
||||
tileColor: fillColor,
|
||||
dense: true,
|
||||
visualDensity: VisualDensity(horizontal: 0, vertical: -3),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 12),
|
||||
leading: ImageUtil.getImageFromPath(imagePath: src.iconPath, height: 24, width: 24),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
onTap: () {
|
||||
// Handle tap on the supported handle
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget backButton(BuildContext context) => Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
size: 16,
|
||||
);
|
||||
|
||||
Route<Object?> _slideLeft(Widget page) {
|
||||
return PageRouteBuilder(
|
||||
transitionDuration: const Duration(milliseconds: 250),
|
||||
pageBuilder: (_, __, ___) => page,
|
||||
transitionsBuilder: (_, animation, __, child) => SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(1, 0),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(parent: animation, curve: Curves.easeOut)),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
|
@ -44,6 +44,29 @@ abstract class BaseBottomSheet extends StatelessWidget {
|
|||
final Key? leftActionButtonKey;
|
||||
final Key? rightActionButtonKey;
|
||||
|
||||
Widget _buildDragHandle(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(flex: 4),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(flex: 4),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget contentWidget(BuildContext context);
|
||||
|
||||
@override
|
||||
|
@ -58,7 +81,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
_buildHeader(context),
|
||||
buildHeader(context) ?? _buildDragHandle(context),
|
||||
contentWidget(context),
|
||||
_buildFooter(context),
|
||||
],
|
||||
|
@ -68,18 +91,9 @@ abstract class BaseBottomSheet extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(BuildContext context) => Column(
|
||||
Widget? buildHeader(BuildContext context) => Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
width: 64,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildDragHandle(context),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
@ -124,8 +138,8 @@ abstract class BaseBottomSheet extends StatelessWidget {
|
|||
key: singleActionButtonKey,
|
||||
text: singleActionButtonText ?? '',
|
||||
onPressed: onSingleActionButtonPressed ?? () {},
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
),
|
||||
|
|
|
@ -33,7 +33,6 @@ class InfoBottomSheet extends BaseBottomSheet {
|
|||
this.contentImage,
|
||||
this.contentImageColor,
|
||||
this.content,
|
||||
this.bottomActionPanel,
|
||||
this.singleActionButtonText,
|
||||
this.onSingleActionButtonPressed,
|
||||
this.singleActionButtonKey,
|
||||
|
@ -43,8 +42,10 @@ class InfoBottomSheet extends BaseBottomSheet {
|
|||
this.onRightActionButtonPressed,
|
||||
this.leftActionButtonKey,
|
||||
this.rightActionButtonKey,
|
||||
Widget? actionPanel,
|
||||
Key? key,
|
||||
}) : super(
|
||||
}) : _actionPanel = actionPanel,
|
||||
super(
|
||||
titleText: titleText,
|
||||
titleIconPath: titleIconPath,
|
||||
currentTheme: currentTheme,
|
||||
|
@ -65,7 +66,7 @@ class InfoBottomSheet extends BaseBottomSheet {
|
|||
final String? contentImage;
|
||||
final Color? contentImageColor;
|
||||
final String? content;
|
||||
final Widget? bottomActionPanel;
|
||||
final Widget? _actionPanel;
|
||||
final String? singleActionButtonText;
|
||||
final VoidCallback? onSingleActionButtonPressed;
|
||||
final Key? singleActionButtonKey;
|
||||
|
@ -76,6 +77,9 @@ class InfoBottomSheet extends BaseBottomSheet {
|
|||
final Key? rightActionButtonKey;
|
||||
final Key? leftActionButtonKey;
|
||||
|
||||
Widget get defaultActionPanel => const SizedBox();
|
||||
Widget get actionPanel => _actionPanel ?? defaultActionPanel;
|
||||
|
||||
@override
|
||||
Widget contentWidget(BuildContext context) {
|
||||
return SizedBox(
|
||||
|
@ -118,7 +122,7 @@ class InfoBottomSheet extends BaseBottomSheet {
|
|||
],
|
||||
),
|
||||
),
|
||||
bottomActionPanel ?? const SizedBox(),
|
||||
actionPanel ?? const SizedBox(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:cake_wallet/generated/i18n.dart';
|
|||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/utils/mobx.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -17,11 +18,11 @@ import 'package:mobx/mobx.dart';
|
|||
|
||||
part 'contact_list_view_model.g.dart';
|
||||
|
||||
class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel;
|
||||
class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel;
|
||||
|
||||
abstract class ContactListViewModelBase with Store {
|
||||
ContactListViewModelBase(
|
||||
this.contactSource, this.walletInfoSource, this._currency, this.settingsStore)
|
||||
this.contactSource, this.walletInfoSource,this.wallet, this._currency, this.settingsStore)
|
||||
: contacts = ObservableList<ContactRecord>(),
|
||||
walletContacts = [],
|
||||
isAutoGenerateEnabled =
|
||||
|
@ -100,6 +101,7 @@ abstract class ContactListViewModelBase with Store {
|
|||
final ObservableList<ContactRecord> contacts;
|
||||
final List<WalletContact> walletContacts;
|
||||
final CryptoCurrency? _currency;
|
||||
final WalletBase wallet;
|
||||
StreamSubscription<BoxEvent>? _subscription;
|
||||
final SettingsStore settingsStore;
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class WalletRestoreFromQRCode {
|
|||
|
||||
static String? _extractAddressFromUrl(String rawString, WalletType type) {
|
||||
try {
|
||||
return AddressResolver.extractAddressByType(
|
||||
return AddressResolverService.extractAddressByType(
|
||||
raw: rawString, type: walletTypeToCryptoCurrency(type));
|
||||
} catch (_) {
|
||||
return null;
|
||||
|
|
|
@ -79,7 +79,7 @@ abstract class OutputBase with Store {
|
|||
|
||||
@computed
|
||||
bool get isParsedAddress =>
|
||||
parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
|
||||
parsedAddress.parseFrom != AddressSource.notParsed && parsedAddress.name.isNotEmpty;
|
||||
|
||||
@observable
|
||||
String? stealthAddress;
|
||||
|
@ -323,8 +323,9 @@ abstract class OutputBase with Store {
|
|||
Future<void> fetchParsedAddress(BuildContext context) async {
|
||||
final domain = address;
|
||||
final currency = cryptoCurrencyHandler();
|
||||
parsedAddress = await getIt.get<AddressResolver>().resolve(context, domain, currency);
|
||||
extractedAddress = await extractAddressFromParsed(context, parsedAddress);
|
||||
final parsedAddresses = await getIt.get<AddressResolverService>().resolve(query: domain, currency: currency, wallet: _wallet);
|
||||
parsedAddress = parsedAddresses.first;
|
||||
extractedAddress = await extractAddressFromParsed(context, parsedAddress); //TODO handle multiple addresses
|
||||
note = parsedAddress.description;
|
||||
}
|
||||
|
||||
|
|
|
@ -312,7 +312,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
List<CryptoCurrency> currencies;
|
||||
|
||||
bool get hasYat => outputs
|
||||
.any((out) => out.isParsedAddress && out.parsedAddress.parseFrom == ParseFrom.yatRecord);
|
||||
.any((out) => out.isParsedAddress && out.parsedAddress.parseFrom == AddressSource.yatRecord);
|
||||
|
||||
WalletType get walletType => wallet.type;
|
||||
|
||||
|
|