mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Merge branch 'main' into add-indonesian-language
This commit is contained in:
@@ -57,7 +57,7 @@ class AnyPayPayment {
|
||||
|
||||
List<String> get outAddresses {
|
||||
return instructions
|
||||
.map((instuction) => instuction.outputs.map((out) => out.address))
|
||||
.map((instruction) => instruction.outputs.map((out) => out.address))
|
||||
.expand((e) => e)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class AnyPayApi {
|
||||
final response = await post(Uri.parse(uri), headers: headers, body: utf8.encode(json.encode(body)));
|
||||
if (response.statusCode == 400) {
|
||||
final decodedBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
throw Exception(decodedBody['message'] as String);
|
||||
throw Exception(decodedBody['message'] as String? ?? 'Unexpected response\nError code: 400');
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
|
||||
@@ -198,4 +198,26 @@ class AddressValidator extends TextValidator {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static String? getAddressFromStringPattern(CryptoCurrency type) {
|
||||
switch (type) {
|
||||
case CryptoCurrency.xmr:
|
||||
return '([^0-9a-zA-Z]|^)4[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
|
||||
case CryptoCurrency.btc:
|
||||
return '([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{39}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{59}([^0-9a-zA-Z]|\$)';
|
||||
case CryptoCurrency.ltc:
|
||||
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ class WalletLoadingService {
|
||||
final wallet = await walletService.openWallet(name, password);
|
||||
|
||||
if (type == WalletType.monero) {
|
||||
await upateMoneroWalletPassword(wallet);
|
||||
await updateMoneroWalletPassword(wallet);
|
||||
}
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
Future<void> upateMoneroWalletPassword(WalletBase wallet) async {
|
||||
Future<void> updateMoneroWalletPassword(WalletBase wallet) async {
|
||||
final key = PreferencesKey.moneroWalletUpdateV1Key(wallet.name);
|
||||
var isPasswordUpdated = sharedPreferences.getBool(key) ?? false;
|
||||
|
||||
@@ -37,8 +37,8 @@ class WalletLoadingService {
|
||||
}
|
||||
|
||||
final password = generateWalletPassword();
|
||||
// Save new generated password with backup key for case
|
||||
// if wallet will change password, but it will faild to updated in secure storage
|
||||
// Save new generated password with backup key for case where
|
||||
// wallet will change password, but it will fail to update in secure storage
|
||||
final bakWalletName = '#__${wallet.name}_bak__#';
|
||||
await keyService.saveWalletPassword(walletName: bakWalletName, password: password);
|
||||
await wallet.changePassword(password);
|
||||
|
||||
@@ -23,7 +23,10 @@ class LanguageService {
|
||||
'ar': 'العربية (Arabic)',
|
||||
'tr': 'Türkçe (Turkish)',
|
||||
'my': 'မြန်မာ (Burmese)',
|
||||
'id': 'Bahasa Indonesia (Indonesian)',
|
||||
'bg': 'Български (Bulgarian)',
|
||||
'cs': 'čeština (Czech)',
|
||||
'ur': 'اردو (Urdu)',
|
||||
'id': 'Bahasa Indonesia (Indonesian)'
|
||||
};
|
||||
|
||||
static const Map<String, String> localeCountryCode = {
|
||||
@@ -46,7 +49,10 @@ class LanguageService {
|
||||
'ar': 'sau',
|
||||
'tr': 'tur',
|
||||
'my': 'mmr',
|
||||
'id': 'idn',
|
||||
'bg': 'bgr',
|
||||
'cs': 'czk',
|
||||
'ur': 'pak',
|
||||
'id': 'idn'
|
||||
};
|
||||
|
||||
static final list = <String, String> {};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:basic_utils/basic_utils.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
class OpenaliasRecord {
|
||||
OpenaliasRecord({
|
||||
@@ -22,69 +21,68 @@ class OpenaliasRecord {
|
||||
return formattedName;
|
||||
}
|
||||
|
||||
static Future<OpenaliasRecord> fetchAddressAndName({
|
||||
static Future<List<RRecord>?> lookupOpenAliasRecord(String name) async {
|
||||
try {
|
||||
final txtRecord = await DnsUtils.lookupRecord(name, RRecordType.TXT, dnssec: true);
|
||||
|
||||
return txtRecord;
|
||||
} catch (e) {
|
||||
print("${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static OpenaliasRecord fetchAddressAndName({
|
||||
required String formattedName,
|
||||
required String ticker,
|
||||
}) async {
|
||||
required List<RRecord> txtRecord,
|
||||
}) {
|
||||
String address = formattedName;
|
||||
String name = formattedName;
|
||||
String note = '';
|
||||
|
||||
if (formattedName.contains(".")) {
|
||||
try {
|
||||
final txtRecord = await DnsUtils.lookupRecord(
|
||||
formattedName, RRecordType.TXT,
|
||||
dnssec: true);
|
||||
for (RRecord element in txtRecord) {
|
||||
String record = element.data;
|
||||
|
||||
if (txtRecord != null) {
|
||||
for (RRecord element in txtRecord) {
|
||||
String record = element.data;
|
||||
if (record.contains("oa1:$ticker") && record.contains("recipient_address")) {
|
||||
record = record.replaceAll('\"', "");
|
||||
|
||||
if (record.contains("oa1:$ticker") &&
|
||||
record.contains("recipient_address")) {
|
||||
record = record.replaceAll('\"', "");
|
||||
final dataList = record.split(";");
|
||||
|
||||
final dataList = record.split(";");
|
||||
address = dataList
|
||||
.where((item) => (item.contains("recipient_address")))
|
||||
.toString()
|
||||
.replaceAll("oa1:$ticker recipient_address=", "")
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.trim();
|
||||
|
||||
address = dataList
|
||||
.where((item) => (item.contains("recipient_address")))
|
||||
.toString()
|
||||
.replaceAll("oa1:$ticker recipient_address=", "")
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.trim();
|
||||
final recipientName = dataList
|
||||
.where((item) => (item.contains("recipient_name")))
|
||||
.toString()
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.trim();
|
||||
|
||||
final recipientName = dataList
|
||||
.where((item) => (item.contains("recipient_name")))
|
||||
.toString()
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.trim();
|
||||
|
||||
if (recipientName.isNotEmpty) {
|
||||
name = recipientName.replaceAll("recipient_name=", "");
|
||||
}
|
||||
|
||||
final description = dataList
|
||||
.where((item) => (item.contains("tx_description")))
|
||||
.toString()
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.trim();
|
||||
|
||||
if (description.isNotEmpty) {
|
||||
note = description.replaceAll("tx_description=", "");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (recipientName.isNotEmpty) {
|
||||
name = recipientName.replaceAll("recipient_name=", "");
|
||||
}
|
||||
} catch (e) {
|
||||
print("${e.toString()}");
|
||||
}
|
||||
}
|
||||
|
||||
return OpenaliasRecord(address: address, name: name, description: note);
|
||||
final description = dataList
|
||||
.where((item) => (item.contains("tx_description")))
|
||||
.toString()
|
||||
.replaceAll("(", "")
|
||||
.replaceAll(")", "")
|
||||
.trim();
|
||||
|
||||
if (description.isNotEmpty) {
|
||||
note = description.replaceAll("tx_description=", "");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return OpenaliasRecord(address: address, name: name, description: note);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,75 @@
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/core/yat_service.dart';
|
||||
import 'package:cake_wallet/entities/openalias_record.dart';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
|
||||
import 'package:cake_wallet/entities/emoji_string_extension.dart';
|
||||
import 'package:cake_wallet/twitter/twitter_api.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/fio_address_provider.dart';
|
||||
|
||||
class AddressResolver {
|
||||
|
||||
AddressResolver({required this.yatService, required this.walletType});
|
||||
|
||||
|
||||
final YatService yatService;
|
||||
final WalletType walletType;
|
||||
|
||||
|
||||
static const unstoppableDomains = [
|
||||
'crypto',
|
||||
'zil',
|
||||
'x',
|
||||
'coin',
|
||||
'wallet',
|
||||
'bitcoin',
|
||||
'888',
|
||||
'nft',
|
||||
'dao',
|
||||
'blockchain'
|
||||
];
|
||||
'crypto',
|
||||
'zil',
|
||||
'x',
|
||||
'coin',
|
||||
'wallet',
|
||||
'bitcoin',
|
||||
'888',
|
||||
'nft',
|
||||
'dao',
|
||||
'blockchain'
|
||||
];
|
||||
|
||||
static String? extractAddressByType({required String raw, required CryptoCurrency type}) {
|
||||
final addressPattern = AddressValidator.getAddressFromStringPattern(type);
|
||||
|
||||
if (addressPattern == null) {
|
||||
throw 'Unexpected token: $type for getAddressFromStringPattern';
|
||||
}
|
||||
|
||||
final match = RegExp(addressPattern).firstMatch(raw);
|
||||
return match?.group(0)?.replaceAll(RegExp('[^0-9a-zA-Z]'), '');
|
||||
}
|
||||
|
||||
Future<ParsedAddress> resolve(String text, String ticker) async {
|
||||
try {
|
||||
if (text.contains('@') && !text.contains('.')) {
|
||||
if (text.startsWith('@') && !text.substring(1).contains('@')) {
|
||||
final formattedName = text.substring(1);
|
||||
final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);
|
||||
final addressFromBio = extractAddressByType(
|
||||
raw: twitterUser.description, type: CryptoCurrency.fromString(ticker));
|
||||
if (addressFromBio != null) {
|
||||
return ParsedAddress.fetchTwitterAddress(address: addressFromBio, name: text);
|
||||
}
|
||||
final tweets = twitterUser.tweets;
|
||||
if (tweets != null) {
|
||||
var subString = StringBuffer();
|
||||
tweets.forEach((item) {
|
||||
subString.writeln(item.text);
|
||||
});
|
||||
final userTweetsText = subString.toString();
|
||||
final addressFromPinnedTweet =
|
||||
extractAddressByType(raw: userTweetsText, type: CryptoCurrency.fromString(ticker));
|
||||
|
||||
if (addressFromPinnedTweet != null) {
|
||||
return ParsedAddress.fetchTwitterAddress(address: addressFromPinnedTweet, 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 (walletType != WalletType.haven) {
|
||||
@@ -55,10 +90,14 @@ class AddressResolver {
|
||||
return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text);
|
||||
}
|
||||
|
||||
final record = await OpenaliasRecord.fetchAddressAndName(
|
||||
formattedName: formattedName, ticker: ticker);
|
||||
return ParsedAddress.fetchOpenAliasAddress(record: record, name: text);
|
||||
|
||||
if (formattedName.contains(".")) {
|
||||
final txtRecord = await OpenaliasRecord.lookupOpenAliasRecord(formattedName);
|
||||
if (txtRecord != null) {
|
||||
final record = await OpenaliasRecord.fetchAddressAndName(
|
||||
formattedName: formattedName, ticker: ticker, txtRecord: txtRecord);
|
||||
return ParsedAddress.fetchOpenAliasAddress(record: record, name: text);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:cake_wallet/entities/openalias_record.dart';
|
||||
import 'package:cake_wallet/entities/yat_record.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed }
|
||||
enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter }
|
||||
|
||||
class ParsedAddress {
|
||||
ParsedAddress({
|
||||
@@ -41,11 +40,7 @@ class ParsedAddress {
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchOpenAliasAddress({OpenaliasRecord? record, required String name}){
|
||||
final formattedName = OpenaliasRecord.formatDomainName(name);
|
||||
if (record == null || record.address.contains(formattedName)) {
|
||||
return ParsedAddress(addresses: [name]);
|
||||
}
|
||||
factory ParsedAddress.fetchOpenAliasAddress({required OpenaliasRecord record, required String name}){
|
||||
return ParsedAddress(
|
||||
addresses: [record.address],
|
||||
name: record.name,
|
||||
@@ -62,6 +57,14 @@ class ParsedAddress {
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchTwitterAddress({required String address, required String name}){
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.twitter,
|
||||
);
|
||||
}
|
||||
|
||||
final List<String> addresses;
|
||||
final String name;
|
||||
final String description;
|
||||
|
||||
@@ -29,6 +29,7 @@ class PreferencesKey {
|
||||
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
|
||||
static const pinTimeOutDuration = 'pin_timeout_duration';
|
||||
static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds';
|
||||
static const lastPopupDate = 'last_popup_date';
|
||||
|
||||
|
||||
static String moneroWalletUpdateV1Key(String name)
|
||||
|
||||
@@ -143,6 +143,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
||||
final inputAddress = responseJSON['payinAddress'] as String;
|
||||
final refundAddress = responseJSON['refundAddress'] as String;
|
||||
final extraId = responseJSON['payinExtraId'] as String?;
|
||||
final payoutAddress = responseJSON['payoutAddress'] as String;
|
||||
|
||||
return Trade(
|
||||
id: id,
|
||||
@@ -154,7 +155,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
||||
extraId: extraId,
|
||||
createdAt: DateTime.now(),
|
||||
amount: responseJSON['fromAmount']?.toString() ?? _request.fromAmount,
|
||||
state: TradeState.created);
|
||||
state: TradeState.created,
|
||||
payoutAddress: payoutAddress);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -192,6 +194,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
||||
final extraId = responseJSON['payinExtraId'] as String;
|
||||
final outputTransaction = responseJSON['payoutHash'] as String;
|
||||
final expiredAtRaw = responseJSON['validUntil'] as String;
|
||||
final payoutAddress = responseJSON['payoutAddress'] as String;
|
||||
final expiredAt = DateTime.tryParse(expiredAtRaw)?.toLocal();
|
||||
|
||||
return Trade(
|
||||
@@ -204,7 +207,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
||||
state: state,
|
||||
extraId: extraId,
|
||||
expiredAt: expiredAt,
|
||||
outputTransaction: outputTransaction);
|
||||
outputTransaction: outputTransaction,
|
||||
payoutAddress: payoutAddress);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -271,6 +275,10 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
||||
switch(currency) {
|
||||
case CryptoCurrency.zec:
|
||||
return 'zec';
|
||||
case CryptoCurrency.usdcpoly:
|
||||
return 'usdcmatic';
|
||||
case CryptoCurrency.maticpoly:
|
||||
return 'maticmainnet';
|
||||
default:
|
||||
return currency.title.toLowerCase();
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
||||
refundAddress: settleAddress,
|
||||
state: TradeState.created,
|
||||
amount: _request.depositAmount,
|
||||
payoutAddress: settleAddress,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
@@ -244,6 +245,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
||||
final inputAddress = responseJSON['depositAddress']['address'] as String;
|
||||
final expectedSendAmount = responseJSON['depositAmount'].toString();
|
||||
final deposits = responseJSON['deposits'] as List?;
|
||||
final settleAddress = responseJSON['settleAddress']['address'] as String;
|
||||
TradeState? state;
|
||||
String? status;
|
||||
|
||||
@@ -264,6 +266,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
||||
amount: expectedSendAmount,
|
||||
state: state,
|
||||
expiredAt: expiredAt,
|
||||
payoutAddress: settleAddress
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
final id = responseJSON['id'] as String;
|
||||
final inputAddress = responseJSON['address_from'] as String;
|
||||
final payoutAddress = responseJSON['address_to'] as String;
|
||||
final settleAddress = responseJSON['user_refund_address'] as String;
|
||||
final extraId = responseJSON['extra_id_from'] as String?;
|
||||
return Trade(
|
||||
@@ -120,6 +121,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
|
||||
extraId: extraId,
|
||||
state: TradeState.created,
|
||||
amount: _request.amount,
|
||||
payoutAddress: payoutAddress,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
@@ -189,6 +191,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
|
||||
final expectedSendAmount = responseJSON['expected_amount'].toString();
|
||||
final extraId = responseJSON['extra_id_from'] as String?;
|
||||
final status = responseJSON['status'] as String;
|
||||
final payoutAddress = responseJSON['address_to'] as String;
|
||||
final state = TradeState.deserialize(raw: status);
|
||||
|
||||
return Trade(
|
||||
@@ -200,6 +203,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
|
||||
inputAddress: inputAddress,
|
||||
amount: expectedSendAmount,
|
||||
state: state,
|
||||
payoutAddress: payoutAddress,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -231,6 +235,10 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
|
||||
return 'usdcpoly';
|
||||
case CryptoCurrency.usdcsol:
|
||||
return 'usdcspl';
|
||||
case CryptoCurrency.matic:
|
||||
return 'maticerc20';
|
||||
case CryptoCurrency.maticpoly:
|
||||
return 'matic';
|
||||
default:
|
||||
return currency.title.toLowerCase();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ class Trade extends HiveObject {
|
||||
this.extraId,
|
||||
this.outputTransaction,
|
||||
this.refundAddress,
|
||||
this.walletId}) {
|
||||
this.walletId,
|
||||
this.payoutAddress}) {
|
||||
if (provider != null) {
|
||||
providerRaw = provider.raw;
|
||||
}
|
||||
@@ -88,6 +89,9 @@ class Trade extends HiveObject {
|
||||
@HiveField(12)
|
||||
String? walletId;
|
||||
|
||||
@HiveField(13)
|
||||
String? payoutAddress;
|
||||
|
||||
static Trade fromMap(Map<String, Object?> map) {
|
||||
return Trade(
|
||||
id: map['id'] as String,
|
||||
|
||||
+4
-91
@@ -1,22 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/entities/language_service.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_category.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_merchant.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||
import 'package:cake_wallet/themes/theme_list.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mailer/flutter_mailer.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -56,14 +46,12 @@ Future<void> main() async {
|
||||
await runZonedGuarded(() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
FlutterError.onError = (errorDetails) {
|
||||
_onError(errorDetails);
|
||||
};
|
||||
FlutterError.onError = ExceptionHandler.onError;
|
||||
|
||||
/// A callback that is invoked when an unhandled error occurs in the root
|
||||
/// isolate.
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
_onError(FlutterErrorDetails(exception: error, stack: stack));
|
||||
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -154,85 +142,10 @@ Future<void> main() async {
|
||||
initialMigrationVersion: 19);
|
||||
runApp(App());
|
||||
}, (error, stackTrace) async {
|
||||
_onError(FlutterErrorDetails(exception: error, stack: stackTrace));
|
||||
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
|
||||
});
|
||||
}
|
||||
|
||||
void _saveException(String? error, StackTrace? stackTrace) async {
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
final file = File('${appDocDir.path}/error.txt');
|
||||
final exception = {
|
||||
"${DateTime.now()}": {
|
||||
"Error": error,
|
||||
"StackTrace": stackTrace.toString(),
|
||||
}
|
||||
};
|
||||
|
||||
const String separator =
|
||||
'''\n\n==========================================================
|
||||
==========================================================\n\n''';
|
||||
|
||||
await file.writeAsString(
|
||||
jsonEncode(exception) + separator,
|
||||
mode: FileMode.append,
|
||||
);
|
||||
}
|
||||
|
||||
void _sendExceptionFile() async {
|
||||
try {
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
final file = File('${appDocDir.path}/error.txt');
|
||||
|
||||
final MailOptions mailOptions = MailOptions(
|
||||
subject: 'Mobile App Issue',
|
||||
recipients: ['support@cakewallet.com'],
|
||||
attachments: [file.path],
|
||||
);
|
||||
|
||||
final result = await FlutterMailer.send(mailOptions);
|
||||
|
||||
// Clear file content if the error was sent or saved.
|
||||
// On android we can't know if it was sent or saved
|
||||
if (result.name == MailerResponse.sent.name ||
|
||||
result.name == MailerResponse.saved.name ||
|
||||
result.name == MailerResponse.android.name) {
|
||||
file.writeAsString("", mode: FileMode.write);
|
||||
}
|
||||
} catch (e, s) {
|
||||
_saveException(e.toString(), s);
|
||||
}
|
||||
}
|
||||
|
||||
void _onError(FlutterErrorDetails errorDetails) {
|
||||
_saveException(errorDetails.exception.toString(), errorDetails.stack);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(timeStamp) {
|
||||
showPopUp<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
builder: (context) {
|
||||
return AlertWithTwoActions(
|
||||
isDividerExist: true,
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: S.of(context).error_dialog_content,
|
||||
rightButtonText: S.of(context).send,
|
||||
leftButtonText: S.of(context).do_not_send,
|
||||
actionRightButton: () {
|
||||
Navigator.of(context).pop();
|
||||
_sendExceptionFile();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> initialSetup(
|
||||
{required SharedPreferences sharedPreferences,
|
||||
required Box<Node> nodes,
|
||||
|
||||
+1
-1
@@ -330,7 +330,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
getIt.get<ContactListPage>());
|
||||
|
||||
case Routes.pickerAddressBook:
|
||||
final selectedCurrency = settings.arguments as CryptoCurrency;
|
||||
final selectedCurrency = settings.arguments as CryptoCurrency?;
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<ContactListPage>(param1: selectedCurrency));
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'dart:io';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/utils/share_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
@@ -110,7 +109,7 @@ class BackupPage extends BasePage {
|
||||
if (Platform.isAndroid) {
|
||||
onExportAndroid(context, backup);
|
||||
} else {
|
||||
await share(backup);
|
||||
await share(backup, context);
|
||||
}
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop());
|
||||
@@ -140,18 +139,14 @@ class BackupPage extends BasePage {
|
||||
},
|
||||
actionLeftButton: () async {
|
||||
Navigator.of(dialogContext).pop();
|
||||
await share(backup);
|
||||
await share(backup, context);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> share(BackupExportFile backup) async {
|
||||
const mimeType = 'application/*';
|
||||
Future<void> share(BackupExportFile backup, BuildContext context) async {
|
||||
final path = await backupViewModelBase.saveBackupFileLocally(backup);
|
||||
await Share.shareXFiles(<XFile>[XFile(
|
||||
path,
|
||||
name: backup.name,
|
||||
mimeType: mimeType)]);
|
||||
await ShareUtil.shareFile(filePath: path, fileName: backup.name, context: context);
|
||||
await backupViewModelBase.removeBackupFileLocally(backup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
|
||||
@@ -229,7 +228,8 @@ class DashboardPage extends BasePage {
|
||||
}
|
||||
|
||||
await Future<void>.delayed(Duration(seconds: 1));
|
||||
await showPopUp<void>(
|
||||
if (context.mounted) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
@@ -239,6 +239,7 @@ class DashboardPage extends BasePage {
|
||||
buttonText: S.of(context).understand,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var needToPresentYat = false;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/share_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -15,7 +13,6 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class AddressPage extends BasePage {
|
||||
AddressPage({
|
||||
@@ -100,7 +97,12 @@ class AddressPage extends BasePage {
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
iconSize: 25,
|
||||
onPressed: () => Share.share(addressListViewModel.address.address),
|
||||
onPressed: () {
|
||||
ShareUtil.share(
|
||||
text: addressListViewModel.address.address,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
icon: shareImage,
|
||||
),
|
||||
) : null;
|
||||
@@ -115,7 +117,8 @@ class AddressPage extends BasePage {
|
||||
}
|
||||
|
||||
await Future<void>.delayed(Duration(seconds: 1));
|
||||
await showPopUp<void>(
|
||||
if (context.mounted) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
@@ -129,6 +132,7 @@ class AddressPage extends BasePage {
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return KeyboardActions(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class TransactionRow extends StatelessWidget {
|
||||
TransactionRow(
|
||||
@@ -9,6 +8,7 @@ class TransactionRow extends StatelessWidget {
|
||||
required this.formattedAmount,
|
||||
required this.formattedFiatAmount,
|
||||
required this.isPending,
|
||||
required this.title,
|
||||
required this.onTap});
|
||||
|
||||
final VoidCallback onTap;
|
||||
@@ -17,6 +17,7 @@ class TransactionRow extends StatelessWidget {
|
||||
final String formattedAmount;
|
||||
final String formattedFiatAmount;
|
||||
final bool isPending;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -49,11 +50,7 @@ class TransactionRow extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
(direction == TransactionDirection.incoming
|
||||
? S.of(context).received
|
||||
: S.of(context).sent) +
|
||||
(isPending ? S.of(context).pending : ''),
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -60,7 +60,8 @@ class TransactionsPage extends StatelessWidget {
|
||||
formattedFiatAmount:
|
||||
dashboardViewModel.balanceViewModel.isFiatDisabled
|
||||
? '' : item.formattedFiatAmount,
|
||||
isPending: transaction.isPending));
|
||||
isPending: transaction.isPending,
|
||||
title: item.formattedTitle + item.formattedStatus));
|
||||
}
|
||||
|
||||
if (item is TradeListItem) {
|
||||
|
||||
@@ -112,7 +112,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
width: 16,
|
||||
color: Theme.of(context).primaryTextTheme!.overline!.color!);
|
||||
|
||||
_setEffects(context);
|
||||
_setEffects();
|
||||
|
||||
return Container(
|
||||
child: ScrollableWithBottomSection(
|
||||
@@ -165,14 +165,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
.color!
|
||||
)
|
||||
),
|
||||
child: QrImage(
|
||||
data: trade.inputAddress ?? fetchingLabel,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.subtitle2!
|
||||
.color!,
|
||||
),
|
||||
child: QrImage(data: trade.inputAddress ?? fetchingLabel),
|
||||
)))),
|
||||
Spacer(flex: 3)
|
||||
]),
|
||||
@@ -241,7 +234,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
);
|
||||
}
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
void _setEffects() {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
}
|
||||
@@ -252,12 +245,12 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertTitle: S.of(popupContext).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -266,118 +259,24 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
builder: (BuildContext popupContext) {
|
||||
return ConfirmSendingAlert(
|
||||
alertTitle: S.of(context).confirm_sending,
|
||||
amount: S.of(context).send_amount,
|
||||
alertTitle: S.of(popupContext).confirm_sending,
|
||||
amount: S.of(popupContext).send_amount,
|
||||
amountValue: widget.exchangeTradeViewModel.sendViewModel
|
||||
.pendingTransaction!.amountFormatted,
|
||||
fee: S.of(context).send_fee,
|
||||
fee: S.of(popupContext).send_fee,
|
||||
feeValue: widget.exchangeTradeViewModel.sendViewModel
|
||||
.pendingTransaction!.feeFormatted,
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(popupContext).ok,
|
||||
leftButtonText: S.of(popupContext).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(popupContext).pop();
|
||||
await widget.exchangeTradeViewModel.sendViewModel
|
||||
.commitTransaction();
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Observer(builder: (_) {
|
||||
final state = widget
|
||||
.exchangeTradeViewModel.sendViewModel.state;
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
'assets/images/birthday_cake.png'),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 220, left: 24, right: 24),
|
||||
child: Text(
|
||||
S.of(context).send_success(widget
|
||||
.exchangeTradeViewModel
|
||||
.wallet
|
||||
.currency
|
||||
.toString()),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.headline6!
|
||||
.color,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 24,
|
||||
right: 24,
|
||||
bottom: 24,
|
||||
child: PrimaryButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(),
|
||||
text: S.of(context).send_got_it,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.bodyText1!
|
||||
.color!,
|
||||
textColor: Colors.white))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
'assets/images/birthday_cake.png'),
|
||||
),
|
||||
),
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 3.0, sigmaY: 3.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.backgroundColor
|
||||
.withOpacity(0.25)),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 220),
|
||||
child: Text(
|
||||
S.of(context).send_sending,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).primaryTextTheme!.headline6!.color!,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
transactionStatePopup();
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(context).pop(),
|
||||
actionLeftButton: () => Navigator.of(popupContext).pop(),
|
||||
feeFiatAmount: widget.exchangeTradeViewModel
|
||||
.pendingTransactionFeeFiatAmountFormatted,
|
||||
fiatAmountValue: widget.exchangeTradeViewModel
|
||||
@@ -392,12 +291,12 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).sending,
|
||||
alertContent: S.of(context).transaction_sent,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
alertTitle: S.of(popupContext).sending,
|
||||
alertContent: S.of(popupContext).transaction_sent,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -405,4 +304,102 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
|
||||
_effectsInstalled = true;
|
||||
}
|
||||
|
||||
void transactionStatePopup() {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return Observer(builder: (_) {
|
||||
final state = widget
|
||||
.exchangeTradeViewModel.sendViewModel.state;
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
color: Theme.of(popupContext).backgroundColor,
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
'assets/images/birthday_cake.png'),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 220, left: 24, right: 24),
|
||||
child: Text(
|
||||
S.of(popupContext).send_success(widget
|
||||
.exchangeTradeViewModel
|
||||
.wallet
|
||||
.currency
|
||||
.toString()),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(popupContext)
|
||||
.primaryTextTheme!
|
||||
.headline6!
|
||||
.color,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 24,
|
||||
right: 24,
|
||||
bottom: 24,
|
||||
child: PrimaryButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(popupContext).pop(),
|
||||
text: S.of(popupContext).send_got_it,
|
||||
color: Theme.of(popupContext)
|
||||
.accentTextTheme!
|
||||
.bodyText1!
|
||||
.color!,
|
||||
textColor: Colors.white))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
color: Theme.of(popupContext).backgroundColor,
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
'assets/images/birthday_cake.png'),
|
||||
),
|
||||
),
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 3.0, sigmaY: 3.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(popupContext)
|
||||
.backgroundColor
|
||||
.withOpacity(0.25)),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 220),
|
||||
child: Text(
|
||||
S.of(popupContext).send_sending,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(popupContext).primaryTextTheme!.headline6!.color!,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,8 @@ class _IoniaPaymentStatusPageBodyBodyState extends State<_IoniaPaymentStatusPage
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 40, right: 40, bottom: 20),
|
||||
child: Text(
|
||||
S.of(context).proceed_after_one_minute,
|
||||
widget.viewModel.payingByBitcoin ? S.of(context).bitcoin_payments_require_1_confirmation
|
||||
: S.of(context).proceed_after_one_minute,
|
||||
style: textMedium(
|
||||
color: Theme.of(context).primaryTextTheme!.headline6!.color!,
|
||||
).copyWith(fontWeight: FontWeight.w500),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
|
||||
class FullscreenQRPage extends BasePage {
|
||||
@@ -69,14 +68,10 @@ class FullscreenQRPage extends BasePage {
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5),
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(width: 3, color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!)),
|
||||
child: QrImage(
|
||||
data: qrData,
|
||||
backgroundColor: isLight ? Colors.transparent : Colors.black,
|
||||
foregroundColor: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
|
||||
),
|
||||
child: QrImage(data: qrData),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/share_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
@@ -101,7 +99,12 @@ class ReceivePage extends BasePage {
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
iconSize: 25,
|
||||
onPressed: () => Share.share(addressListViewModel.address.address),
|
||||
onPressed: () {
|
||||
ShareUtil.share(
|
||||
text: addressListViewModel.address.address,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
icon: shareImage
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr/qr.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_painter.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart' as qr;
|
||||
|
||||
class QrImage extends StatelessWidget {
|
||||
QrImage({
|
||||
required String data,
|
||||
required this.data,
|
||||
this.size = 100.0,
|
||||
this.backgroundColor,
|
||||
Color foregroundColor = Colors.black,
|
||||
int version = 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ???
|
||||
int errorCorrectionLevel = QrErrorCorrectLevel.L,
|
||||
}) : _painter = QrPainter(data, foregroundColor, version, errorCorrectionLevel);
|
||||
this.version = 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ???
|
||||
this.errorCorrectionLevel = qr.QrErrorCorrectLevel.L,
|
||||
});
|
||||
|
||||
final QrPainter _painter;
|
||||
final Color? backgroundColor;
|
||||
final double size;
|
||||
final String data;
|
||||
final int version;
|
||||
final int errorCorrectionLevel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
color: backgroundColor,
|
||||
child: CustomPaint(
|
||||
painter: _painter,
|
||||
),
|
||||
return qr.QrImage(
|
||||
data: data,
|
||||
errorCorrectionLevel: errorCorrectionLevel,
|
||||
version: version,
|
||||
size: size,
|
||||
foregroundColor: Colors.black,
|
||||
backgroundColor: Colors.white,
|
||||
padding: EdgeInsets.zero,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr/qr.dart';
|
||||
|
||||
class QrPainter extends CustomPainter {
|
||||
QrPainter(
|
||||
String data,
|
||||
this.color,
|
||||
this.version,
|
||||
this.errorCorrectionLevel,
|
||||
) : this._qr = QrCode(version, errorCorrectionLevel)..addData(data) {
|
||||
_p.color = this.color;
|
||||
_qrImage = QrImage(_qr);
|
||||
}
|
||||
|
||||
final int version;
|
||||
final int errorCorrectionLevel;
|
||||
final Color color;
|
||||
|
||||
final QrCode _qr;
|
||||
final _p = Paint()..style = PaintingStyle.fill;
|
||||
late QrImage _qrImage;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final squareSize = size.shortestSide / _qr.moduleCount;
|
||||
for (int x = 0; x < _qr.moduleCount; x++) {
|
||||
for (int y = 0; y < _qr.moduleCount; y++) {
|
||||
if (_qrImage.isDark(y, x)) {
|
||||
final squareRect = Rect.fromLTWH(
|
||||
x * squareSize, y * squareSize, squareSize, squareSize);
|
||||
canvas.drawRect(squareRect, _p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||
if (oldDelegate is QrPainter) {
|
||||
return this.color != oldDelegate.color ||
|
||||
this.errorCorrectionLevel != oldDelegate.errorCorrectionLevel ||
|
||||
this.version != oldDelegate.version;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -89,11 +89,7 @@ class QRWidget extends StatelessWidget {
|
||||
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
|
||||
),
|
||||
),
|
||||
child: QrImage(
|
||||
data: addressListViewModel.uri.toString(),
|
||||
backgroundColor: isLight ? Colors.transparent : Colors.black,
|
||||
foregroundColor: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
|
||||
),
|
||||
child: QrImage(data: addressListViewModel.uri.toString()),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/share_util.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
@@ -159,8 +158,12 @@ class WalletSeedPage extends BasePage {
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(right: 8.0),
|
||||
child: PrimaryButton(
|
||||
onPressed: () =>
|
||||
Share.share(walletSeedViewModel.seed),
|
||||
onPressed: () {
|
||||
ShareUtil.share(
|
||||
text: walletSeedViewModel.seed,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
text: S.of(context).save,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white),
|
||||
|
||||
@@ -339,7 +339,7 @@ class SendPage extends BasePage {
|
||||
showErrorValidationAlert(context);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await sendViewModel.createTransaction();
|
||||
|
||||
},
|
||||
@@ -379,7 +379,8 @@ class SendPage extends BasePage {
|
||||
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
if (context.mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return ConfirmSendingAlert(
|
||||
@@ -423,6 +424,7 @@ class SendPage extends BasePage {
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,13 +19,18 @@ Future<String> extractAddressFromParsed(
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.openAlias:
|
||||
title = S.of(context).openalias_alert_title;
|
||||
content = S.of(context).openalias_alert_content(parsedAddress.name);
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (OpenAlias)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.fio:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).openalias_alert_content(parsedAddress.name);
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (FIO)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.twitter:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Twitter)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.yatRecord:
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arro
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
@@ -35,10 +36,11 @@ class ConnectionSyncPage extends BasePage {
|
||||
handler: (context) => _presentReconnectAlert(context),
|
||||
),
|
||||
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.rescan,
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
|
||||
),
|
||||
if (dashboardViewModel.hasRescan)
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.rescan,
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
|
||||
),
|
||||
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||
NodeHeaderListRow(
|
||||
title: S.of(context).add_new_node,
|
||||
|
||||
@@ -30,6 +30,8 @@ class SettingsLinkProviderCell extends StandardListRow {
|
||||
color: Palette.blueCraiola));
|
||||
|
||||
static void _launchUrl(String url) async {
|
||||
if (await canLaunch(url)) await launch(url, forceSafariVC: false);
|
||||
try {
|
||||
await launch(url, forceSafariVC: false);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_menu_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
|
||||
class WalletMenu {
|
||||
WalletMenu(this.context, this.walletListViewModel);
|
||||
|
||||
final WalletListViewModel walletListViewModel;
|
||||
final BuildContext context;
|
||||
|
||||
final List<WalletMenuItem> menuItems = [
|
||||
WalletMenuItem(
|
||||
title: S.current.wallet_list_load_wallet,
|
||||
firstGradientColor: Palette.cornflower,
|
||||
secondGradientColor: Palette.royalBlue,
|
||||
image: Image.asset('assets/images/load.png',
|
||||
height: 24, width: 24, color: Colors.white)),
|
||||
WalletMenuItem(
|
||||
title: S.current.show_seed,
|
||||
firstGradientColor: Palette.moderateOrangeYellow,
|
||||
secondGradientColor: Palette.moderateOrange,
|
||||
image: Image.asset('assets/images/eye_action.png',
|
||||
height: 24, width: 24, color: Colors.white)),
|
||||
WalletMenuItem(
|
||||
title: S.current.remove,
|
||||
firstGradientColor: Palette.lightRed,
|
||||
secondGradientColor: Palette.persianRed,
|
||||
image: Image.asset('assets/images/trash.png',
|
||||
height: 24, width: 24, color: Colors.white)),
|
||||
WalletMenuItem(
|
||||
title: S.current.rescan,
|
||||
firstGradientColor: Palette.shineGreen,
|
||||
secondGradientColor: Palette.moderateGreen,
|
||||
image: Image.asset('assets/images/scanner.png',
|
||||
height: 24, width: 24, color: Colors.white))
|
||||
];
|
||||
|
||||
List<WalletMenuItem> generateItemsForWalletMenu(bool isCurrentWallet) {
|
||||
final items = <WalletMenuItem>[];
|
||||
|
||||
if (!isCurrentWallet) items.add(menuItems[0]);
|
||||
if (isCurrentWallet) items.add(menuItems[1]);
|
||||
if (!isCurrentWallet) items.add(menuItems[2]);
|
||||
if (isCurrentWallet) items.add(menuItems[3]);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
Future<void> action(
|
||||
int index, WalletListItem wallet) async {
|
||||
switch (index) {
|
||||
case 0:
|
||||
await Navigator.of(context).pushNamed(Routes.auth, arguments:
|
||||
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auth.changeProcessText(
|
||||
S.of(context).wallet_list_loading_wallet(wallet.name));
|
||||
await walletListViewModel.loadWallet(wallet);
|
||||
auth.close();
|
||||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
auth.changeProcessText(S
|
||||
.of(context)
|
||||
.wallet_list_failed_to_load(wallet.name, e.toString()));
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
await Navigator.of(context).pushNamed(Routes.auth, arguments:
|
||||
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
auth.close();
|
||||
await Navigator.of(context).pushNamed(Routes.seed, arguments: false);
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
final isComfirmed = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: 'Remove wallet',
|
||||
alertContent: S.of(context).confirm_delete_wallet,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).remove,
|
||||
actionLeftButton: () => Navigator.of(context).pop(false),
|
||||
actionRightButton: () => Navigator.of(context).pop(true));
|
||||
});
|
||||
|
||||
if (isComfirmed == null || !isComfirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Navigator.of(context).pushNamed(Routes.auth, arguments:
|
||||
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auth.changeProcessText(
|
||||
S.of(context).wallet_list_removing_wallet(wallet.name));
|
||||
await walletListViewModel.remove(wallet);
|
||||
auth.close();
|
||||
} catch (e) {
|
||||
auth.changeProcessText(S
|
||||
.of(context)
|
||||
.wallet_list_failed_to_remove(wallet.name, e.toString()));
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
await Navigator.of(context).pushNamed(Routes.rescan);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class WalletMenuItem {
|
||||
WalletMenuItem({
|
||||
required this.title,
|
||||
required this.firstGradientColor,
|
||||
required this.secondGradientColor,
|
||||
required this.image
|
||||
});
|
||||
|
||||
final String title;
|
||||
final Color firstGradientColor;
|
||||
final Color secondGradientColor;
|
||||
final Image image;
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_menu.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_menu_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
|
||||
|
||||
class WalletMenuAlert extends StatelessWidget {
|
||||
WalletMenuAlert({
|
||||
required this.wallet,
|
||||
required this.walletMenu,
|
||||
required this.items
|
||||
});
|
||||
|
||||
final WalletListItem wallet;
|
||||
final WalletMenu walletMenu;
|
||||
final List<WalletMenuItem> items;
|
||||
final closeButton = Image.asset('assets/images/close.png',
|
||||
color: Palette.darkBlueCraiola,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertBackground(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 24,
|
||||
right: 24,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14)),
|
||||
child: Container(
|
||||
color: Theme.of(context).textTheme!.bodyText1!.decorationColor!,
|
||||
padding: EdgeInsets.only(left: 24),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: items.length,
|
||||
separatorBuilder: (context, _) => Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).accentTextTheme!.subtitle1!.backgroundColor!,
|
||||
),
|
||||
itemBuilder: (_, index) {
|
||||
final item = items[index];
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
walletMenu.action(
|
||||
walletMenu.menuItems.indexOf(item),
|
||||
wallet);
|
||||
},
|
||||
child: Container(
|
||||
height: 60,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 32,
|
||||
width: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(4)),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
item.firstGradientColor,
|
||||
item.secondGradientColor
|
||||
]
|
||||
)
|
||||
),
|
||||
child: Center(
|
||||
child: item.image,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme!.headline6!.color!,
|
||||
fontSize: 18,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
decoration: TextDecoration.none
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AlertCloseButton(image: closeButton)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'dart:convert';
|
||||
import 'package:cake_wallet/twitter/twitter_user.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
class TwitterApi {
|
||||
static const twitterBearerToken = secrets.twitterBearerToken;
|
||||
static const httpsScheme = 'https';
|
||||
static const apiHost = 'api.twitter.com';
|
||||
static const userPath = '/2/users/by/username/';
|
||||
|
||||
static Future<TwitterUser> lookupUserByName({required String userName}) async {
|
||||
final queryParams = {'user.fields': 'description', 'expansions': 'pinned_tweet_id'};
|
||||
|
||||
final headers = {'authorization': 'Bearer $twitterBearerToken'};
|
||||
|
||||
final uri = Uri(
|
||||
scheme: httpsScheme,
|
||||
host: apiHost,
|
||||
path: userPath + userName,
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
|
||||
var response = await http.get(uri, headers: headers);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||
}
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (responseJSON['errors'] != null) {
|
||||
throw Exception(responseJSON['errors'][0]['detail']);
|
||||
}
|
||||
|
||||
return TwitterUser.fromJson(responseJSON);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
class TwitterUser {
|
||||
TwitterUser(
|
||||
{required this.id,
|
||||
required this.username,
|
||||
required this.name,
|
||||
required this.description,
|
||||
this.tweets});
|
||||
|
||||
final String id;
|
||||
final String username;
|
||||
final String name;
|
||||
final String description;
|
||||
final List<Tweet>? tweets;
|
||||
|
||||
factory TwitterUser.fromJson(Map<String, dynamic> json) {
|
||||
return TwitterUser(
|
||||
id: json['data']['id'] as String,
|
||||
username: json['data']['username'] as String,
|
||||
name: json['data']['name'] as String,
|
||||
description: json['data']['description'] as String? ?? '',
|
||||
tweets: json['includes'] != null
|
||||
? List.from(json['includes']['tweets'] as List)
|
||||
.map((e) => Tweet.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Tweet {
|
||||
Tweet({
|
||||
required this.id,
|
||||
required this.text,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String text;
|
||||
|
||||
factory Tweet.fromJson(Map<String, dynamic> json) {
|
||||
return Tweet(
|
||||
id: json['id'] as String,
|
||||
text: json['text'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mailer/flutter_mailer.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class ExceptionHandler {
|
||||
static bool _hasError = false;
|
||||
static const _coolDownDurationInDays = 7;
|
||||
|
||||
static void _saveException(String? error, StackTrace? stackTrace) async {
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
final file = File('${appDocDir.path}/error.txt');
|
||||
final exception = {
|
||||
"${DateTime.now()}": {
|
||||
"Error": error,
|
||||
"StackTrace": stackTrace.toString(),
|
||||
}
|
||||
};
|
||||
|
||||
const String separator = '''\n\n==========================================================
|
||||
==========================================================\n\n''';
|
||||
|
||||
await file.writeAsString(
|
||||
jsonEncode(exception) + separator,
|
||||
mode: FileMode.append,
|
||||
);
|
||||
}
|
||||
|
||||
static void _sendExceptionFile() async {
|
||||
try {
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
final file = File('${appDocDir.path}/error.txt');
|
||||
|
||||
final MailOptions mailOptions = MailOptions(
|
||||
subject: 'Mobile App Issue',
|
||||
recipients: ['support@cakewallet.com'],
|
||||
attachments: [file.path],
|
||||
);
|
||||
|
||||
final result = await FlutterMailer.send(mailOptions);
|
||||
|
||||
// Clear file content if the error was sent or saved.
|
||||
// On android we can't know if it was sent or saved
|
||||
if (result.name == MailerResponse.sent.name ||
|
||||
result.name == MailerResponse.saved.name ||
|
||||
result.name == MailerResponse.android.name) {
|
||||
file.writeAsString("", mode: FileMode.write);
|
||||
}
|
||||
} catch (e, s) {
|
||||
_saveException(e.toString(), s);
|
||||
}
|
||||
}
|
||||
|
||||
static void onError(FlutterErrorDetails errorDetails) async {
|
||||
if (kDebugMode) {
|
||||
FlutterError.presentError(errorDetails);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ignoreError(errorDetails.exception.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_saveException(errorDetails.exception.toString(), errorDetails.stack);
|
||||
|
||||
final sharedPrefs = await SharedPreferences.getInstance();
|
||||
|
||||
final lastPopupDate =
|
||||
DateTime.tryParse(sharedPrefs.getString(PreferencesKey.lastPopupDate) ?? '') ??
|
||||
DateTime.now().subtract(Duration(days: _coolDownDurationInDays + 1));
|
||||
|
||||
final durationSinceLastReport = DateTime.now().difference(lastPopupDate).inDays;
|
||||
|
||||
if (_hasError || durationSinceLastReport < _coolDownDurationInDays) {
|
||||
return;
|
||||
}
|
||||
_hasError = true;
|
||||
|
||||
sharedPrefs.setString(PreferencesKey.lastPopupDate, DateTime.now().toString());
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(timeStamp) async {
|
||||
await showPopUp<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
builder: (context) {
|
||||
return AlertWithTwoActions(
|
||||
isDividerExist: true,
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: S.of(context).error_dialog_content,
|
||||
rightButtonText: S.of(context).send,
|
||||
leftButtonText: S.of(context).do_not_send,
|
||||
actionRightButton: () {
|
||||
Navigator.of(context).pop();
|
||||
_sendExceptionFile();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
_hasError = false;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Ignore User related errors or system errors
|
||||
static bool _ignoreError(String error) =>
|
||||
_ignoredErrors.any((element) => error.contains(element));
|
||||
|
||||
static const List<String> _ignoredErrors = const [
|
||||
"errno = 103", // SocketException: Software caused connection abort
|
||||
"errno = 9", // SocketException: Bad file descriptor
|
||||
"errno = 32", // SocketException: Write failed (OS Error: Broken pipe)
|
||||
"errno = 60", // SocketException: Operation timed out
|
||||
"errno = 110", // SocketException: Connection timed out
|
||||
"errno = 54", // SocketException: Connection reset by peer
|
||||
"errno = 49", // SocketException: Can't assign requested address
|
||||
"errno = 28", // OS Error: No space left on device
|
||||
"PERMISSION_NOT_GRANTED",
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
|
||||
class ShareUtil {
|
||||
static const _mimeType = 'application/*';
|
||||
|
||||
static void share({required String text, required BuildContext context}) {
|
||||
Share.share(
|
||||
text,
|
||||
sharePositionOrigin: _sharePosition(context),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> shareFile({
|
||||
required String filePath,
|
||||
required String fileName,
|
||||
required BuildContext context,
|
||||
}) async {
|
||||
Share.shareXFiles(
|
||||
<XFile>[
|
||||
XFile(
|
||||
filePath,
|
||||
name: fileName,
|
||||
mimeType: _mimeType,
|
||||
)
|
||||
],
|
||||
sharePositionOrigin: _sharePosition(context),
|
||||
);
|
||||
}
|
||||
|
||||
static Rect? _sharePosition(BuildContext context) {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
|
||||
return box!.localToGlobal(Offset.zero) & box.size;
|
||||
}
|
||||
}
|
||||
@@ -239,7 +239,7 @@ abstract class DashboardViewModelBase with Store {
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||
wallet;
|
||||
|
||||
bool get hasRescan => wallet.type == WalletType.monero;
|
||||
bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
|
||||
|
||||
BalanceViewModel balanceViewModel;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||
@@ -11,6 +13,7 @@ import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cw_core/keyable.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
|
||||
class TransactionListItem extends ActionListItem with Keyable {
|
||||
TransactionListItem(
|
||||
{required this.transaction,
|
||||
@@ -35,6 +38,30 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
? '---'
|
||||
: transaction.amountFormatted();
|
||||
}
|
||||
String get formattedTitle {
|
||||
if (transaction.direction == TransactionDirection.incoming) {
|
||||
return S.current.received;
|
||||
}
|
||||
|
||||
return S.current.sent;
|
||||
}
|
||||
|
||||
String get formattedPendingStatus {
|
||||
if (transaction.confirmations >= 0 && transaction.confirmations < 10) {
|
||||
return ' (${transaction.confirmations}/10)';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
String get formattedStatus {
|
||||
if (transaction.direction == TransactionDirection.incoming) {
|
||||
if (balanceViewModel.wallet.type == WalletType.monero ||
|
||||
balanceViewModel.wallet.type == WalletType.haven) {
|
||||
return formattedPendingStatus;
|
||||
}
|
||||
}
|
||||
return transaction.isPending ? S.current.pending : '';
|
||||
}
|
||||
|
||||
String get formattedFiatAmount {
|
||||
var amount = '';
|
||||
|
||||
@@ -18,19 +18,18 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
part 'exchange_trade_view_model.g.dart';
|
||||
|
||||
class ExchangeTradeViewModel = ExchangeTradeViewModelBase
|
||||
with _$ExchangeTradeViewModel;
|
||||
class ExchangeTradeViewModel = ExchangeTradeViewModelBase with _$ExchangeTradeViewModel;
|
||||
|
||||
abstract class ExchangeTradeViewModelBase with Store {
|
||||
ExchangeTradeViewModelBase(
|
||||
{required this.wallet,
|
||||
required this.trades,
|
||||
required this.tradesStore,
|
||||
required this.sendViewModel})
|
||||
: trade = tradesStore.trade!,
|
||||
isSendable = tradesStore.trade!.from == wallet.currency ||
|
||||
tradesStore.trade!.provider == ExchangeProviderDescription.xmrto,
|
||||
items = ObservableList<ExchangeTradeItem>() {
|
||||
required this.trades,
|
||||
required this.tradesStore,
|
||||
required this.sendViewModel})
|
||||
: trade = tradesStore.trade!,
|
||||
isSendable = tradesStore.trade!.from == wallet.currency ||
|
||||
tradesStore.trade!.provider == ExchangeProviderDescription.xmrto,
|
||||
items = ObservableList<ExchangeTradeItem>() {
|
||||
switch (trade.provider) {
|
||||
case ExchangeProviderDescription.xmrto:
|
||||
_provider = XMRTOExchangeProvider();
|
||||
@@ -67,22 +66,20 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
|
||||
@computed
|
||||
String get extraInfo => trade.from == CryptoCurrency.xlm
|
||||
? '\n\n' + S.current.xlm_extra_info
|
||||
: trade.from == CryptoCurrency.xrp
|
||||
? '\n\n' + S.current.xrp_extra_info
|
||||
: '';
|
||||
? '\n\n' + S.current.xlm_extra_info
|
||||
: trade.from == CryptoCurrency.xrp
|
||||
? '\n\n' + S.current.xrp_extra_info
|
||||
: '';
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFiatAmountValueFormatted =>
|
||||
sendViewModel.isFiatDisabled
|
||||
? '' : sendViewModel.pendingTransactionFiatAmount
|
||||
+ ' ' + sendViewModel.fiat.title;
|
||||
String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled
|
||||
? ''
|
||||
: sendViewModel.pendingTransactionFiatAmount + ' ' + sendViewModel.fiat.title;
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFeeFiatAmountFormatted =>
|
||||
sendViewModel.isFiatDisabled
|
||||
? '' : sendViewModel.pendingTransactionFeeFiatAmount
|
||||
+ ' ' + sendViewModel.fiat.title;
|
||||
String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled
|
||||
? ''
|
||||
: sendViewModel.pendingTransactionFeeFiatAmount + ' ' + sendViewModel.fiat.title;
|
||||
|
||||
@observable
|
||||
ObservableList<ExchangeTradeItem> items;
|
||||
@@ -122,6 +119,8 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
}
|
||||
|
||||
void _updateItems() {
|
||||
final tagFrom = trade.from.tag != null ? '${trade.from.tag}' + ' ' : '';
|
||||
final tagTo = trade.to.tag != null ? '${trade.to.tag}' + ' ' : '';
|
||||
items.clear();
|
||||
items.add(ExchangeTradeItem(
|
||||
title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true));
|
||||
@@ -130,22 +129,22 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
final title = trade.from == CryptoCurrency.xrp
|
||||
? S.current.destination_tag
|
||||
: trade.from == CryptoCurrency.xlm
|
||||
? S.current.memo
|
||||
: S.current.extra_id;
|
||||
? S.current.memo
|
||||
: S.current.extra_id;
|
||||
|
||||
items.add(ExchangeTradeItem(
|
||||
title: title, data: '${trade.extraId}', isCopied: false));
|
||||
items.add(ExchangeTradeItem(title: title, data: '${trade.extraId}', isCopied: false));
|
||||
}
|
||||
|
||||
items.addAll([
|
||||
ExchangeTradeItem(title: S.current.amount, data: '${trade.amount}', isCopied: false),
|
||||
ExchangeTradeItem(
|
||||
title: S.current.amount, data: '${trade.amount}', isCopied: false),
|
||||
ExchangeTradeItem(
|
||||
title: S.current.status, data: '${trade.state}', isCopied: false),
|
||||
ExchangeTradeItem(
|
||||
title: S.current.widgets_address + ':',
|
||||
title: S.current.send_to_this_address('${trade.from}', tagFrom) + ':',
|
||||
data: trade.inputAddress ?? '',
|
||||
isCopied: true),
|
||||
ExchangeTradeItem(
|
||||
title: S.current.arrive_in_this_address('${trade.to}', tagTo) + ':',
|
||||
data: trade.payoutAddress ?? '',
|
||||
isCopied: true),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ abstract class ExchangeViewModelBase with Store {
|
||||
ObservableList<ExchangeTemplate> get templates =>
|
||||
_exchangeTemplateStore.templates;
|
||||
|
||||
|
||||
|
||||
@computed
|
||||
TransactionPriority get transactionPriority {
|
||||
final priority = _settingsStore.priority[wallet.type];
|
||||
@@ -308,10 +308,11 @@ abstract class ExchangeViewModelBase with Store {
|
||||
Future<void> _calculateBestRate() async {
|
||||
final amount = double.tryParse(isFixedRateMode ? receiveAmount : depositAmount) ?? 1;
|
||||
|
||||
final _providers = _tradeAvailableProviders
|
||||
.where((element) => !isFixedRateMode || element.supportsFixedRate).toList();
|
||||
|
||||
final result = await Future.wait<double>(
|
||||
_tradeAvailableProviders
|
||||
.where((element) => !isFixedRateMode || element.supportsFixedRate)
|
||||
.map((element) => element.fetchRate(
|
||||
_providers.map((element) => element.fetchRate(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
amount: amount,
|
||||
@@ -324,7 +325,12 @@ abstract class ExchangeViewModelBase with Store {
|
||||
for (int i=0;i<result.length;i++) {
|
||||
if (result[i] != 0) {
|
||||
/// add this provider as its valid for this trade
|
||||
_sortedAvailableProviders[result[i]] = _tradeAvailableProviders[i];
|
||||
try {
|
||||
_sortedAvailableProviders[result[i]] = _providers[i];
|
||||
} catch (e) {
|
||||
// will throw "Concurrent modification during iteration" error if modified at the same
|
||||
// time [createTrade] is called, as this is not a normal map, but a sorted map
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_sortedAvailableProviders.isNotEmpty) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/anypay/any_pay_chain.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
@@ -39,6 +40,8 @@ abstract class IoniaPaymentStatusViewModelBase with Store {
|
||||
|
||||
Timer? get timer => _timer;
|
||||
|
||||
bool get payingByBitcoin => paymentInfo.anyPayPayment.chain == AnyPayChain.btc;
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
@action
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
|
||||
part 'support_view_model.g.dart';
|
||||
|
||||
@@ -19,7 +18,9 @@ abstract class SupportViewModelBase with Store {
|
||||
RegularListItem(
|
||||
title: S.current.faq,
|
||||
handler: (BuildContext context) async {
|
||||
if (await canLaunch(url)) await launch(url);
|
||||
try {
|
||||
await launch(url);
|
||||
} catch (e) {}
|
||||
},
|
||||
),
|
||||
LinkListItem(
|
||||
@@ -42,7 +43,7 @@ abstract class SupportViewModelBase with Store {
|
||||
title: 'Telegram',
|
||||
icon: 'assets/images/Telegram.png',
|
||||
linkTitle: '@cakewallet_bot',
|
||||
link: 'https:t.me/cakewallet_bot'),
|
||||
link: 'https://t.me/cakewallet_bot'),
|
||||
LinkListItem(
|
||||
title: 'Twitter',
|
||||
icon: 'assets/images/Twitter.png',
|
||||
@@ -84,7 +85,7 @@ abstract class SupportViewModelBase with Store {
|
||||
// link: 'mailto:support@y.at')
|
||||
];
|
||||
|
||||
static const url = 'https://cakewallet.com/guide/';
|
||||
static const url = 'https://guides.cakewallet.com';
|
||||
|
||||
List<SettingsListItem> items;
|
||||
}
|
||||
Reference in New Issue
Block a user