Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-78-Ethereum

 Conflicts:
	lib/main.dart
This commit is contained in:
OmarHatem 2023-04-05 19:24:31 +02:00
commit ef7fde9de7
102 changed files with 3408 additions and 742 deletions

View file

@ -113,6 +113,9 @@ jobs:
echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
- name: Rename app
run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties

View file

@ -13,9 +13,6 @@
-
uri: node.c3pool.com:18081
is_default: false
-
uri: node.supportxmr.com:18081
is_default: false
-
uri: node.community.rino.io:18081
is_default: false

View file

@ -40,7 +40,7 @@ class ElectrumClient {
unterminatedString = '';
static const connectionTimeout = Duration(seconds: 5);
static const aliveTimerDuration = Duration(seconds: 2);
static const aliveTimerDuration = Duration(seconds: 4);
bool get isConnected => _isConnected;
Socket? socket;
@ -358,7 +358,7 @@ class ElectrumClient {
Future<dynamic> callWithTimeout(
{required String method,
List<Object> params = const [],
int timeout = 2000}) async {
int timeout = 4000}) async {
try {
final completer = Completer<dynamic>();
_id += 1;

View file

@ -1,6 +1,7 @@
import 'package:cw_core/currency.dart';
import 'package:cw_core/enumerable_item.dart';
class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implements Currency {
const CryptoCurrency({
String title = '',
int raw = -1,
@ -162,6 +163,14 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
return acc;
});
static final Map<String, CryptoCurrency> _fullNameCurrencyMap =
[...all, ...havenCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
if(item.fullName != null){
acc.addAll({item.fullName!.toLowerCase(): item});
}
return acc;
});
static CryptoCurrency deserialize({required int raw}) {
if (CryptoCurrency._rawCurrencyMap[raw] == null) {
@ -180,6 +189,16 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
return CryptoCurrency._nameCurrencyMap[name.toLowerCase()]!;
}
static CryptoCurrency fromFullName(String name) {
if (CryptoCurrency._fullNameCurrencyMap[name.toLowerCase()] == null) {
final s = 'Unexpected token: $name for CryptoCurrency fromFullName';
throw ArgumentError.value(name, 'Fullname', s);
}
return CryptoCurrency._fullNameCurrencyMap[name.toLowerCase()]!;
}
@override
String toString() => title;
}

View file

@ -0,0 +1,6 @@
abstract class Currency {
String get name;
String? get tag;
String? get fullName;
String? get iconPath;
}

View file

@ -1,4 +1,6 @@
import 'package:intl/intl.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
// FIXME: Hardcoded values; Works only for monero
@ -93,7 +95,7 @@ int getMoneroHeigthByDate({required DateTime date}) {
int height = 0;
try {
if ((dates[raw] == null)||(dates[raw] == lastHeight)) {
if ((dates[raw] == null) || (dates[raw] == lastHeight)) {
startHeight = dates.values.toList()[dates.length - 2];
endHeight = dates.values.toList()[dates.length - 1];
final heightPerDay = (endHeight - startHeight) / 31;
@ -118,3 +120,86 @@ int getMoneroHeigthByDate({required DateTime date}) {
return height;
}
const havenDates = {
"2023-03": 1309180,
"2023-01": 1266810,
"2022-12": 1244510,
"2022-11": 1222970,
"2022-10": 1200700,
"2022-09": 1179140,
"2022-08": 1156870,
"2022-07": 1134600,
"2022-06": 1113030,
"2022-05": 1090800,
"2022-04": 1069250,
"2022-03": 1047000,
"2022-02": 1026960,
"2022-01": 1004700,
"2021-12": 982400,
"2021-11": 961000,
"2021-10": 938600,
"2021-09": 917000,
"2021-08": 894800,
"2021-07": 886000,
"2021-06": 867300,
"2021-05": 845000,
"2021-04": 823500,
"2021-03": 801500,
"2021-02": 781000,
"2021-01": 759000,
"2020-12": 736500,
"2020-11": 715000,
"2020-10": 693000,
"2020-09": 671000,
"2020-08": 649000,
"2020-07": 626600,
"2020-06": 605000,
"2020-05": 582700,
"2020-04": 561100,
"2020-03": 539000,
"2020-02": 518000,
"2020-01": 496000,
"2019-12": 473400,
"2019-11": 451900,
"2019-10": 429600,
"2019-09": 408000,
"2019-08": 385700,
"2019-07": 363800,
"2019-06": 342200,
"2019-05": 320000,
"2019-04": 298400,
"2019-03": 276000,
"2019-02": 256000,
"2019-01": 233700,
"2018-12": 211400,
"2018-11": 189800,
"2018-10": 167500,
"2018-09": 145900,
"2018-08": 123700,
"2018-07": 101400,
"2018-06": 80000,
"2018-05": 57550,
"2018-04": 32000,
"2018-03": 8500
};
DateTime formatMapKey(String key) => dateFormat.parse(key);
int getHavenHeightByDate({required DateTime date}) {
String closestKey =
havenDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => '');
return havenDates[closestKey] ?? 0;
}
Future<int> getHavenCurrentHeight() async {
final response = await http.get(Uri.parse('https://explorer.havenprotocol.org/api/networkinfo'));
if (response.statusCode == 200) {
final info = jsonDecode(response.body);
return info['data']['height'] as int;
} else {
throw Exception('Failed to load current blockchain height');
}
}

View file

@ -7,7 +7,7 @@ PODS:
- connectivity (0.0.1):
- Flutter
- Reachability
- CryptoSwift (1.3.2)
- CryptoSwift (1.6.0)
- cw_haven (0.0.1):
- cw_haven/Boost (= 0.0.1)
- cw_haven/Haven (= 0.0.1)
@ -65,16 +65,18 @@ PODS:
- Flutter
- device_info (0.0.1):
- Flutter
- device_info_plus (0.0.1):
- Flutter
- devicelocale (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.2):
- DKImagePickerController/Core (4.3.4):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.2)
- DKImagePickerController/PhotoGallery (4.3.2):
- DKImagePickerController/ImageDataManager (4.3.4)
- DKImagePickerController/PhotoGallery (4.3.4):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.2)
- DKImagePickerController/Resource (4.3.4)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
@ -102,29 +104,41 @@ PODS:
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_inappwebview (0.0.1):
- Flutter
- flutter_inappwebview/Core (= 0.0.1)
- OrderedSet (~> 5.0)
- flutter_inappwebview/Core (0.0.1):
- Flutter
- OrderedSet (~> 5.0)
- flutter_mailer (0.0.1):
- Flutter
- flutter_secure_storage (3.3.1):
- Flutter
- local_auth_ios (0.0.1):
- Flutter
- MTBBarcodeScanner (5.0.11)
- OrderedSet (5.0.0)
- package_info (0.0.1):
- Flutter
- path_provider_ios (0.0.1):
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.0.4):
- Flutter
- platform_device_id (0.0.1):
- Flutter
- Reachability (3.2)
- SDWebImage (5.9.1):
- SDWebImage/Core (= 5.9.1)
- SDWebImage/Core (5.9.1)
- SDWebImage (5.15.5):
- SDWebImage/Core (= 5.15.5)
- SDWebImage/Core (5.15.5)
- share_plus (0.0.1):
- Flutter
- shared_preferences_ios (0.0.1):
- shared_preferences_foundation (0.0.1):
- Flutter
- SwiftProtobuf (1.18.0)
- SwiftyGif (5.3.0)
- FlutterMacOS
- SwiftProtobuf (1.21.0)
- SwiftyGif (5.4.4)
- uni_links (0.0.1):
- Flutter
- UnstoppableDomainsResolution (4.0.0):
@ -132,8 +146,6 @@ PODS:
- CryptoSwift
- url_launcher_ios (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES:
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
@ -144,21 +156,23 @@ DEPENDENCIES:
- cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
- device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
- device_info (from `.symlinks/plugins/device_info/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- platform_device_id (from `.symlinks/plugins/platform_device_id/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`)
- UnstoppableDomainsResolution (~> 4.0.0)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
@ -167,6 +181,7 @@ SPEC REPOS:
- DKImagePickerController
- DKPhotoGallery
- MTBBarcodeScanner
- OrderedSet
- Reachability
- SDWebImage
- SwiftProtobuf
@ -188,67 +203,74 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_display_brightness/ios"
device_info:
:path: ".symlinks/plugins/device_info/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
devicelocale:
:path: ".symlinks/plugins/devicelocale/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_inappwebview:
:path: ".symlinks/plugins/flutter_inappwebview/ios"
flutter_mailer:
:path: ".symlinks/plugins/flutter_mailer/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
local_auth_ios:
:path: ".symlinks/plugins/local_auth_ios/ios"
package_info:
:path: ".symlinks/plugins/package_info/ios"
path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
platform_device_id:
:path: ".symlinks/plugins/platform_device_id/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
uni_links:
:path: ".symlinks/plugins/uni_links/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS:
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
BigInt: f668a80089607f521586bbe29513d708491ef2f7
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060
CryptoSwift: 562f8eceb40e80796fffc668b0cad9313284cfa6
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
cw_monero: 4cf3b96f2da8e95e2ef7d6703dd4d2c509127b7d
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
device_info: d7d233b645a32c40dfdc212de5cf646ca482f175
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
devicelocale: b22617f40038496deffba44747101255cee005b0
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
local_auth_ios: 0d333dde7780f669e66f19d2ff6005f3ea84008d
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
platform_device_id: 81b3e2993881f87d0c82ef151dc274df4869aef5
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5
SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
SwiftProtobuf: c3c12645230d9b09c72267e0de89468c5543bd86
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472
SwiftProtobuf: afced68785854575756db965e9da52bbf3dc45e7
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
PODFILE CHECKSUM: ae71bdf0eb731a1ffc399c122f6aa4dea0cb5f6f

View file

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@ -228,6 +228,7 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -242,6 +243,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);

View file

@ -0,0 +1,211 @@
import 'dart:convert';
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/anonpay/anonpay_request.dart';
import 'package:cake_wallet/anonpay/anonpay_status_response.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:http/http.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
class AnonPayApi {
const AnonPayApi({
this.useTorOnly = false,
required this.wallet,
});
final bool useTorOnly;
final WalletBase wallet;
static const anonpayRef = secrets.anonPayReferralCode;
static const onionApiAuthority = 'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion';
static const clearNetAuthority = 'trocador.app';
static const markup = secrets.trocadorExchangeMarkup;
static const anonPayPath = '/anonpay';
static const anonPayStatus = '/anonpay/status';
static const coinPath = 'api/coin';
static const apiKey = secrets.trocadorApiKey;
Future<AnonpayStatusResponse> paymentStatus(String id) async {
final authority = await _getAuthority();
final response = await get(Uri.https(authority, "$anonPayStatus/$id"));
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final status = responseJSON['Status'] as String;
final fiatAmount = responseJSON['Fiat_Amount'] as double?;
final fiatEquiv = responseJSON['Fiat_Equiv'] as String?;
final amountTo = responseJSON['AmountTo'] as double?;
final coinTo = responseJSON['CoinTo'] as String;
final address = responseJSON['Address'] as String;
return AnonpayStatusResponse(
status: status,
fiatAmount: fiatAmount,
amountTo: amountTo,
coinTo: coinTo,
address: address,
fiatEquiv: fiatEquiv,
);
}
Future<AnonpayInvoiceInfo> createInvoice(AnonPayRequest request) async {
final description = Uri.encodeComponent(request.description);
final body = <String, dynamic>{
'ticker_to': request.cryptoCurrency.title.toLowerCase(),
'network_to': _networkFor(request.cryptoCurrency),
'address': request.address,
'name': request.name,
'description': description,
'email': request.email,
'ref': anonpayRef,
'markup': markup,
'direct': 'False',
};
if (request.amount != null) {
body['amount'] = request.amount;
}
if (request.fiatEquivalent != null) {
body['fiat_equiv'] = request.fiatEquivalent;
}
final authority = await _getAuthority();
final response = await get(Uri.https(authority, anonPayPath, body));
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['ID'] as String;
final url = responseJSON['url'] as String;
final urlOnion = responseJSON['url_onion'] as String;
final statusUrl = responseJSON['status_url'] as String;
final statusUrlOnion = responseJSON['status_url_onion'] as String;
final statusInfo = await paymentStatus(id);
return AnonpayInvoiceInfo(
invoiceId: id,
clearnetUrl: url,
onionUrl: urlOnion,
status: statusInfo.status,
fiatAmount: statusInfo.fiatAmount,
fiatEquiv: statusInfo.fiatEquiv,
amountTo: statusInfo.amountTo,
coinTo: statusInfo.coinTo,
address: statusInfo.address,
clearnetStatusUrl: statusUrl,
onionStatusUrl: statusUrlOnion,
walletId: wallet.id,
createdAt: DateTime.now(),
provider: 'Trocador AnonPay invoice',
);
}
Future<AnonpayDonationLinkInfo> generateDonationLink(AnonPayRequest request) async {
final body = <String, dynamic>{
'ticker_to': request.cryptoCurrency.title.toLowerCase(),
'network_to': _networkFor(request.cryptoCurrency),
'address': request.address,
'ref': anonpayRef,
'direct': 'True',
};
if (request.name.isNotEmpty) {
body['name'] = request.name;
}
if (request.description.isNotEmpty) {
body['description'] = request.description;
}
if (request.email.isNotEmpty) {
body['email'] = request.email;
}
final clearnetUrl = Uri.https(clearNetAuthority, anonPayPath, body);
final onionUrl = Uri.https(onionApiAuthority, anonPayPath, body);
return AnonpayDonationLinkInfo(
clearnetUrl: clearnetUrl.toString(),
onionUrl: onionUrl.toString(),
address: request.address,
);
}
Future<Limits> fetchLimits({
FiatCurrency? fiatCurrency,
required CryptoCurrency cryptoCurrency,
}) async {
double fiatRate = 0.0;
if (fiatCurrency != null) {
fiatRate = await FiatConversionService.fetchPrice(
crypto: cryptoCurrency,
fiat: fiatCurrency,
torOnly: useTorOnly,
);
}
final params = <String, String>{
'api_key': apiKey,
'ticker': cryptoCurrency.title.toLowerCase(),
'name': cryptoCurrency.name,
};
final String apiAuthority = await _getAuthority();
final uri = Uri.https(apiAuthority, coinPath, params);
final response = await get(uri);
if (response.statusCode != 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as List<dynamic>;
if (responseJSON.isEmpty) {
throw Exception('No data');
}
final coinJson = responseJSON.first as Map<String, dynamic>;
final minimum = coinJson['minimum'] as double;
final maximum = coinJson['maximum'] as double;
if (fiatCurrency != null) {
return Limits(
min: double.tryParse((minimum * fiatRate).toStringAsFixed(2)),
max: double.tryParse((maximum * fiatRate).toStringAsFixed(2)),
);
}
return Limits(
min: minimum,
max: maximum,
);
}
String _networkFor(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.usdt:
return CryptoCurrency.btc.title.toLowerCase();
default:
return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet';
}
}
String _normalizeTag(String tag) {
switch (tag) {
case 'ETH':
return 'ERC20';
default:
return tag.toLowerCase();
}
}
Future<String> _getAuthority() async {
try {
if (useTorOnly) {
return onionApiAuthority;
}
final uri = Uri.https(onionApiAuthority, '/anonpay');
await get(uri);
return onionApiAuthority;
} catch (e) {
return clearNetAuthority;
}
}
}

View file

@ -0,0 +1,13 @@
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
class AnonpayDonationLinkInfo implements AnonpayInfoBase{
final String clearnetUrl;
final String onionUrl;
final String address;
AnonpayDonationLinkInfo({
required this.clearnetUrl,
required this.onionUrl,
required this.address,
});
}

View file

@ -0,0 +1,5 @@
abstract class AnonpayInfoBase {
String get clearnetUrl;
String get onionUrl;
String get address;
}

View file

@ -0,0 +1,57 @@
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cw_core/keyable.dart';
import 'package:hive/hive.dart';
part 'anonpay_invoice_info.g.dart';
@HiveType(typeId: AnonpayInvoiceInfo.typeId)
class AnonpayInvoiceInfo extends HiveObject with Keyable implements AnonpayInfoBase {
@HiveField(0)
final String invoiceId;
@HiveField(1)
String status;
@HiveField(2)
final double? fiatAmount;
@HiveField(3)
final String? fiatEquiv;
@HiveField(4)
final double? amountTo;
@HiveField(5)
final String coinTo;
@HiveField(6)
final String address;
@HiveField(7)
final String clearnetUrl;
@HiveField(8)
final String onionUrl;
@HiveField(9)
final String clearnetStatusUrl;
@HiveField(10)
final String onionStatusUrl;
@HiveField(11)
final DateTime createdAt;
@HiveField(12)
final String walletId;
@HiveField(13)
final String provider;
static const typeId = 10;
static const boxName = 'AnonpayInvoiceInfo';
AnonpayInvoiceInfo({
required this.invoiceId,
required this.clearnetUrl,
required this.onionUrl,
required this.clearnetStatusUrl,
required this.onionStatusUrl,
required this.status,
this.fiatAmount,
this.fiatEquiv,
this.amountTo,
required this.coinTo,
required this.address,
required this.createdAt,
required this.walletId,
required this.provider,
});
}

View file

@ -0,0 +1,21 @@
import 'package:cw_core/crypto_currency.dart';
class AnonPayRequest {
CryptoCurrency cryptoCurrency;
String address;
String name;
String? amount;
String email;
String description;
String? fiatEquivalent;
AnonPayRequest({
required this.cryptoCurrency,
required this.address,
required this.name,
required this.email,
this.amount,
required this.description,
this.fiatEquivalent,
});
}

View file

@ -0,0 +1,17 @@
class AnonpayStatusResponse {
final String status;
final double? fiatAmount;
final String? fiatEquiv;
final double? amountTo;
final String coinTo;
final String address;
const AnonpayStatusResponse({
required this.status,
this.fiatAmount,
this.fiatEquiv,
this.amountTo,
required this.coinTo,
required this.address,
});
}

View file

@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:cw_core/crypto_currency.dart';
@ -55,6 +56,7 @@ class AnyPayApi {
final response = await post(url, headers: headers, body: utf8.encode(json.encode(body)));
if (response.statusCode != 200) {
ExceptionHandler.onError(FlutterErrorDetails(exception: response));
throw Exception('Unexpected response http code: ${response.statusCode}');
}

View file

@ -1,32 +1,59 @@
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/crypto_currency.dart';
class AmountValidator extends TextValidator {
AmountValidator({required WalletType type, bool isAutovalidate = false})
AmountValidator({required CryptoCurrency currency, bool isAutovalidate = false}) {
symbolsAmountValidator =
SymbolsAmountValidator(isAutovalidate: isAutovalidate);
decimalAmountValidator = DecimalAmountValidator(currency: currency,isAutovalidate: isAutovalidate);
}
late final SymbolsAmountValidator symbolsAmountValidator;
late final DecimalAmountValidator decimalAmountValidator;
String? call(String? value) => symbolsAmountValidator(value) ?? decimalAmountValidator(value);
}
class SymbolsAmountValidator extends TextValidator {
SymbolsAmountValidator({required bool isAutovalidate})
: super(
errorMessage: S.current.error_text_amount,
pattern: _pattern(type),
errorMessage: S.current.error_text_amount,
pattern: _pattern(),
isAutovalidate: isAutovalidate,
minLength: 0,
maxLength: 0);
static String _pattern() => '^([0-9]+([.\,][0-9]+)?|[.\,][0-9]+)\$';
}
class DecimalAmountValidator extends TextValidator {
DecimalAmountValidator({required CryptoCurrency currency, required bool isAutovalidate })
: super(
errorMessage: S.current.decimal_places_error,
pattern: _pattern(currency),
isAutovalidate: isAutovalidate,
minLength: 0,
maxLength: 0);
static String _pattern(WalletType type) {
switch (type) {
case WalletType.monero:
return '^([0-9]+([.\,][0-9]{0,12})?|[.\,][0-9]{1,12})\$';
case WalletType.bitcoin:
return '^([0-9]+([.\,][0-9]{0,8})?|[.\,][0-9]{1,8})\$';
static String _pattern(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.xmr:
return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$';
case CryptoCurrency.btc:
return '^([0-9]+([.\,][0-9]{1,8})?|[.\,][0-9]{1,8})\$';
default:
return '';
return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$';
}
}
}
class AllAmountValidator extends TextValidator {
AllAmountValidator() : super(
errorMessage: S.current.error_text_amount,
pattern: S.current.all,
minLength: 0,
maxLength: 0);
AllAmountValidator()
: super(
errorMessage: S.current.error_text_amount,
pattern: S.current.all,
minLength: 0,
maxLength: 0);
}

View file

@ -3,6 +3,8 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
const _fiatApiClearNetAuthority = 'fiat-api.cakewallet.com';
const _fiatApiOnionAuthority = 'n4z7bdcmwk2oyddxvzaap3x2peqcplh3pzdy7tpkk5ejz5n4mhfvoxqd.onion';
@ -17,6 +19,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
'interval_count': '1',
'base': crypto.toString(),
'quote': fiat.toString(),
'key' : secrets.fiatApiKey,
};
double price = 0.0;

View file

@ -1,11 +1,20 @@
import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/core/yat_service.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cake_wallet/entities/wake_lock.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/ionia/ionia_anypay.dart';
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
import 'package:cake_wallet/ionia/ionia_tip.dart';
import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
import 'package:cake_wallet/src/screens/buy/onramper_page.dart';
import 'package:cake_wallet/src/screens/buy/payfura_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
@ -14,7 +23,11 @@ import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dar
import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart';
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_buy_card_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_custom_tip_view_model.dart';
@ -177,6 +190,7 @@ late Box<ExchangeTemplate> _exchangeTemplates;
late Box<TransactionDescription> _transactionDescriptionBox;
late Box<Order> _ordersSource;
late Box<UnspentCoinsInfo>? _unspentCoinsInfoSource;
late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
Future setup(
{required Box<WalletInfo> walletInfoSource,
@ -187,7 +201,9 @@ Future setup(
required Box<ExchangeTemplate> exchangeTemplates,
required Box<TransactionDescription> transactionDescriptionBox,
required Box<Order> ordersSource,
Box<UnspentCoinsInfo>? unspentCoinsInfoSource}) async {
Box<UnspentCoinsInfo>? unspentCoinsInfoSource,
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource
}) async {
_walletInfoSource = walletInfoSource;
_nodeSource = nodeSource;
_contactSource = contactSource;
@ -197,6 +213,7 @@ Future setup(
_transactionDescriptionBox = transactionDescriptionBox;
_ordersSource = ordersSource;
_unspentCoinsInfoSource = unspentCoinsInfoSource;
_anonpayInvoiceInfoSource = anonpayInvoiceInfoSource;
if (!_isSetupFinished) {
getIt.registerSingletonAsync<SharedPreferences>(
@ -241,6 +258,8 @@ Future setup(
appStore: getIt.get<AppStore>(),
secureStorage: getIt.get<FlutterSecureStorage>())
..init());
getIt.registerSingleton<AnonpayTransactionsStore>(AnonpayTransactionsStore(
anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
final secretStore =
await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
@ -307,7 +326,9 @@ Future setup(
transactionFilterStore: getIt.get<TransactionFilterStore>(),
settingsStore: settingsStore,
yatStore: getIt.get<YatStore>(),
ordersStore: getIt.get<OrdersStore>()));
ordersStore: getIt.get<OrdersStore>(),
anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>())
);
getIt.registerFactory<AuthService>(() => AuthService(
secureStorage: getIt.get<FlutterSecureStorage>(),
@ -361,11 +382,37 @@ Future setup(
BalancePage(dashboardViewModel: getIt.get<DashboardViewModel>(), settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactory<DashboardPage>(() => DashboardPage( balancePage: getIt.get<BalancePage>(), walletViewModel: getIt.get<DashboardViewModel>(), addressListViewModel: getIt.get<WalletAddressListViewModel>()));
getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>((pageOption, _) => ReceiveOptionViewModel(
getIt.get<AppStore>().wallet!, pageOption));
getIt.registerFactoryParam<AnonInvoicePageViewModel, List<dynamic>, void>((args, _) {
final address = args.first as String;
final pageOption = args.last as ReceivePageOption;
return AnonInvoicePageViewModel(
getIt.get<AnonPayApi>(),
address,
getIt.get<SettingsStore>(),
getIt.get<AppStore>().wallet!,
_anonpayInvoiceInfoSource,
getIt.get<SharedPreferences>(),
pageOption,
);
});
getIt.registerFactoryParam<AnonPayInvoicePage, List<dynamic>, void>((List<dynamic> args, _) {
final pageOption = args.last as ReceivePageOption;
return AnonPayInvoicePage(
getIt.get<AnonInvoicePageViewModel>(param1: args),
getIt.get<ReceiveOptionViewModel>(param1: pageOption));
});
getIt.registerFactory<ReceivePage>(() => ReceivePage(
addressListViewModel: getIt.get<WalletAddressListViewModel>()));
getIt.registerFactory<AddressPage>(() => AddressPage(
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
walletViewModel: getIt.get<DashboardViewModel>()));
walletViewModel: getIt.get<DashboardViewModel>(),
receiveOptionViewModel: getIt.get<ReceiveOptionViewModel>()));
getIt.registerFactoryParam<WalletAddressEditOrCreateViewModel, WalletAddressListItem?, void>(
(WalletAddressListItem? item, _) => WalletAddressEditOrCreateViewModel(
@ -515,16 +562,23 @@ Future setup(
(WalletType? type, _) => NodeCreateOrEditViewModel(
_nodeSource,
type ?? getIt.get<AppStore>().wallet!.type,
getIt.get<SettingsStore>(),
getIt.get<SettingsStore>()
));
getIt.registerFactory(
() => NodeCreateOrEditPage(getIt.get<NodeCreateOrEditViewModel>()));
getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
(Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(),
editingNode: editingNode,
isSelected: isSelected));
getIt.registerFactory(() => OnRamperPage(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory(() => PayFuraPage(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory(() => ExchangeViewModel(
getIt.get<AppStore>().wallet!,
_tradesSource,
@ -715,8 +769,8 @@ Future setup(
getIt.registerFactory(() => AddressResolver(yatService: getIt.get<YatService>(),
walletType: getIt.get<AppStore>().wallet!.type));
getIt.registerFactoryParam<FullscreenQRPage, String, bool>(
(String qrData, bool isLight) => FullscreenQRPage(qrData: qrData, isLight: isLight,));
getIt.registerFactoryParam<FullscreenQRPage, String, int?>(
(String qrData, int? version) => FullscreenQRPage(qrData: qrData, version: version,));
getIt.registerFactory(() => IoniaApi());
@ -799,7 +853,7 @@ Future setup(
return IoniaMoreOptionsPage(giftCard);
});
getIt.registerFactoryParam<IoniaCustomRedeemViewModel, IoniaGiftCard, void>((IoniaGiftCard giftCard, _)
getIt.registerFactoryParam<IoniaCustomRedeemViewModel, IoniaGiftCard, void>((IoniaGiftCard giftCard, _)
=> IoniaCustomRedeemViewModel(giftCard: giftCard, ioniaService: getIt.get<IoniaService>()));
getIt.registerFactoryParam<IoniaCustomRedeemPage, List, void>((List args, _){
@ -822,6 +876,25 @@ Future setup(
getIt.registerFactory(() => IoniaAccountPage(getIt.get<IoniaAccountViewModel>()));
getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get<IoniaAccountViewModel>()));
getIt.registerFactory(() => AnonPayApi(useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
wallet: getIt.get<AppStore>().wallet!)
);
getIt.registerFactoryParam<AnonpayDetailsViewModel, AnonpayInvoiceInfo, void>(
(AnonpayInvoiceInfo anonpayInvoiceInfo, _)
=> AnonpayDetailsViewModel(
anonPayApi: getIt.get<AnonPayApi>(),
anonpayInvoiceInfo: anonpayInvoiceInfo,
settingsStore: getIt.get<SettingsStore>(),
));
getIt.registerFactoryParam<AnonPayReceivePage, AnonpayInfoBase, void>(
(AnonpayInfoBase anonpayInvoiceInfo, _) => AnonPayReceivePage(invoiceInfo: anonpayInvoiceInfo));
getIt.registerFactoryParam<AnonpayDetailsPage, AnonpayInvoiceInfo, void>(
(AnonpayInvoiceInfo anonpayInvoiceInfo, _)
=> AnonpayDetailsPage(anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
getIt.registerFactoryParam<IoniaPaymentStatusViewModel, IoniaAnyPayPaymentInfo, AnyPayPaymentCommittedInfo>(
(IoniaAnyPayPaymentInfo paymentInfo, AnyPayPaymentCommittedInfo committedInfo)

View file

@ -40,6 +40,9 @@ Future defaultSettingsMigration(
await ios_migrate_v1(walletInfoSource, tradeSource, contactSource);
}
// check current nodes for nullability regardless of the version
await checkCurrentNodes(nodes, sharedPreferences);
final currentVersion = sharedPreferences
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ??
0;

View file

@ -1,6 +1,7 @@
import 'package:cw_core/currency.dart';
import 'package:cw_core/enumerable_item.dart';
class FiatCurrency extends EnumerableItem<String> with Serializable<String> {
class FiatCurrency extends EnumerableItem<String> with Serializable<String> implements Currency {
const FiatCurrency({required String symbol, required this.countryCode, required this.fullName}) : super(title: symbol, raw: symbol);
final String countryCode;
@ -118,4 +119,13 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> {
@override
int get hashCode => raw.hashCode ^ title.hashCode;
@override
String get name => raw;
@override
String? get tag => null;
@override
String get iconPath => "assets/images/flags/$countryCode.png";
}

View file

@ -25,7 +25,8 @@ class AddressResolver {
'888',
'nft',
'dao',
'blockchain'
'blockchain',
'polygon'
];
static String? extractAddressByType({required String raw, required CryptoCurrency type}) {

View file

@ -39,4 +39,6 @@ class PreferencesKey {
=> '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
static const exchangeProvidersSelection = 'exchange-providers-selection';
static const clearnetDonationLink = 'clearnet_donation_link';
static const onionDonationLink = 'onion_donation_link';
}

View file

@ -0,0 +1,23 @@
enum ReceivePageOption {
mainnet,
anonPayInvoice,
anonPayDonationLink;
@override
String toString() {
String label = '';
switch (this) {
case ReceivePageOption.mainnet:
label = 'Mainnet';
break;
case ReceivePageOption.anonPayInvoice:
label = 'Trocador AnonPay Invoice';
break;
case ReceivePageOption.anonPayDonationLink:
label = 'Trocador AnonPay Donation Link';
break;
}
return label;
}
}

View file

@ -219,9 +219,9 @@ class TrocadorExchangeProvider extends ExchangeProvider {
final responseJSON = responseListJson.first;
final id = responseJSON['trade_id'] as String;
final inputAddress = responseJSON['address_user'] as String;
final payoutAddress = responseJSON['address_user'] as String;
final refundAddress = responseJSON['refund_address'] as String;
final payoutAddress = responseJSON['address_provider'] as String;
final inputAddress = responseJSON['address_provider'] as String;
final fromAmount = responseJSON['amount_from']?.toString() ?? '0';
final from = CryptoCurrency.fromString(responseJSON['ticker_from'] as String);
final to = CryptoCurrency.fromString(responseJSON['ticker_to'] as String);

View file

@ -1,346 +1,331 @@
part of 'haven.dart';
class CWHavenAccountList extends HavenAccountList {
CWHavenAccountList(this._wallet);
final Object _wallet;
CWHavenAccountList(this._wallet);
@override
@computed
final Object _wallet;
@override
@computed
ObservableList<Account> get accounts {
final havenWallet = _wallet as HavenWallet;
final accounts = havenWallet.walletAddresses.accountList
.accounts
.map((acc) => Account(id: acc.id, label: acc.label))
.toList();
return ObservableList<Account>.of(accounts);
final havenWallet = _wallet as HavenWallet;
final accounts = havenWallet.walletAddresses.accountList.accounts
.map((acc) => Account(id: acc.id, label: acc.label))
.toList();
return ObservableList<Account>.of(accounts);
}
@override
void update(Object wallet) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.accountList.update();
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.accountList.update();
}
@override
void refresh(Object wallet) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.accountList.refresh();
}
void refresh(Object wallet) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.accountList.refresh();
}
@override
@override
List<Account> getAll(Object wallet) {
final havenWallet = wallet as HavenWallet;
return havenWallet.walletAddresses.accountList
.getAll()
.map((acc) => Account(id: acc.id, label: acc.label))
.toList();
final havenWallet = wallet as HavenWallet;
return havenWallet.walletAddresses.accountList
.getAll()
.map((acc) => Account(id: acc.id, label: acc.label))
.toList();
}
@override
Future<void> addAccount(Object wallet, {required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.accountList.addAccount(label: label);
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.accountList.addAccount(label: label);
}
@override
Future<void> setLabelAccount(Object wallet, {required int accountIndex, required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.accountList
.setLabelAccount(
accountIndex: accountIndex,
label: label);
Future<void> setLabelAccount(Object wallet,
{required int accountIndex, required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.accountList
.setLabelAccount(accountIndex: accountIndex, label: label);
}
}
class CWHavenSubaddressList extends MoneroSubaddressList {
CWHavenSubaddressList(this._wallet);
final Object _wallet;
CWHavenSubaddressList(this._wallet);
@override
@computed
final Object _wallet;
@override
@computed
ObservableList<Subaddress> get subaddresses {
final havenWallet = _wallet as HavenWallet;
final subAddresses = havenWallet.walletAddresses.subaddressList
.subaddresses
.map((sub) => Subaddress(
id: sub.id,
address: sub.address,
label: sub.label))
.toList();
return ObservableList<Subaddress>.of(subAddresses);
final havenWallet = _wallet as HavenWallet;
final subAddresses = havenWallet.walletAddresses.subaddressList.subaddresses
.map((sub) => Subaddress(id: sub.id, address: sub.address, label: sub.label))
.toList();
return ObservableList<Subaddress>.of(subAddresses);
}
@override
void update(Object wallet, {required int accountIndex}) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.subaddressList.update(accountIndex: accountIndex);
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.subaddressList.update(accountIndex: accountIndex);
}
@override
void refresh(Object wallet, {required int accountIndex}) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.subaddressList.refresh(accountIndex: accountIndex);
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.subaddressList.refresh(accountIndex: accountIndex);
}
@override
List<Subaddress> getAll(Object wallet) {
final havenWallet = wallet as HavenWallet;
return havenWallet.walletAddresses
.subaddressList
.getAll()
.map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address))
.toList();
final havenWallet = wallet as HavenWallet;
return havenWallet.walletAddresses.subaddressList
.getAll()
.map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address))
.toList();
}
@override
Future<void> addSubaddress(Object wallet, {required int accountIndex, required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.subaddressList
.addSubaddress(
accountIndex: accountIndex,
label: label);
Future<void> addSubaddress(Object wallet,
{required int accountIndex, required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.subaddressList
.addSubaddress(accountIndex: accountIndex, label: label);
}
@override
Future<void> setLabelSubaddress(Object wallet,
{required int accountIndex, required int addressIndex, required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.subaddressList
.setLabelSubaddress(
accountIndex: accountIndex,
addressIndex: addressIndex,
label: label);
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.subaddressList
.setLabelSubaddress(accountIndex: accountIndex, addressIndex: addressIndex, label: label);
}
}
class CWHavenWalletDetails extends HavenWalletDetails {
CWHavenWalletDetails(this._wallet);
final Object _wallet;
CWHavenWalletDetails(this._wallet);
@computed
final Object _wallet;
@computed
@override
Account get account {
final havenWallet = _wallet as HavenWallet;
final acc = havenWallet.walletAddresses.account as monero_account.Account;
return Account(id: acc.id, label: acc.label);
final havenWallet = _wallet as HavenWallet;
final acc = havenWallet.walletAddresses.account as monero_account.Account;
return Account(id: acc.id, label: acc.label);
}
@computed
@override
HavenBalance get balance {
final havenWallet = _wallet as HavenWallet;
final balance = havenWallet.balance;
throw Exception('Unimplemented');
//return HavenBalance(
// fullBalance: balance.fullBalance,
// unlockedBalance: balance.unlockedBalance);
}
HavenBalance get balance {
final havenWallet = _wallet as HavenWallet;
final balance = havenWallet.balance;
throw Exception('Unimplemented');
//return HavenBalance(
// fullBalance: balance.fullBalance,
// unlockedBalance: balance.unlockedBalance);
}
}
class CWHaven extends Haven {
@override
HavenAccountList getAccountList(Object wallet) {
return CWHavenAccountList(wallet);
}
@override
MoneroSubaddressList getSubaddressList(Object wallet) {
return CWHavenSubaddressList(wallet);
}
return CWHavenAccountList(wallet);
}
@override
TransactionHistoryBase getTransactionHistory(Object wallet) {
final havenWallet = wallet as HavenWallet;
return havenWallet.transactionHistory;
}
MoneroSubaddressList getSubaddressList(Object wallet) {
return CWHavenSubaddressList(wallet);
}
@override
HavenWalletDetails getMoneroWalletDetails(Object wallet) {
return CWHavenWalletDetails(wallet);
}
TransactionHistoryBase getTransactionHistory(Object wallet) {
final havenWallet = wallet as HavenWallet;
return havenWallet.transactionHistory;
}
@override
int getHeigthByDate({required DateTime date}) {
return getMoneroHeigthByDate(date: date);
}
@override
TransactionPriority getDefaultTransactionPriority() {
return MoneroTransactionPriority.automatic;
}
HavenWalletDetails getMoneroWalletDetails(Object wallet) {
return CWHavenWalletDetails(wallet);
}
@override
TransactionPriority deserializeMoneroTransactionPriority({required int raw}) {
return MoneroTransactionPriority.deserialize(raw: raw);
}
int getHeightByDate({required DateTime date}) => getHavenHeightByDate(date: date);
@override
List<TransactionPriority> getTransactionPriorities() {
return MoneroTransactionPriority.all;
}
Future<int> getCurrentHeight() => getHavenCurrentHeight();
@override
List<String> getMoneroWordList(String language) {
switch (language.toLowerCase()) {
case 'english':
return EnglishMnemonics.words;
case 'chinese (simplified)':
return ChineseSimplifiedMnemonics.words;
case 'dutch':
return DutchMnemonics.words;
case 'german':
return GermanMnemonics.words;
case 'japanese':
return JapaneseMnemonics.words;
case 'portuguese':
return PortugueseMnemonics.words;
case 'russian':
return RussianMnemonics.words;
case 'spanish':
return SpanishMnemonics.words;
case 'french':
return FrenchMnemonics.words;
case 'italian':
return ItalianMnemonics.words;
default:
return EnglishMnemonics.words;
}
}
TransactionPriority getDefaultTransactionPriority() {
return MoneroTransactionPriority.automatic;
}
@override
WalletCredentials createHavenRestoreWalletFromKeysCredentials({
required String name,
TransactionPriority deserializeMoneroTransactionPriority({required int raw}) {
return MoneroTransactionPriority.deserialize(raw: raw);
}
@override
List<TransactionPriority> getTransactionPriorities() {
return MoneroTransactionPriority.all;
}
@override
List<String> getMoneroWordList(String language) {
switch (language.toLowerCase()) {
case 'english':
return EnglishMnemonics.words;
case 'chinese (simplified)':
return ChineseSimplifiedMnemonics.words;
case 'dutch':
return DutchMnemonics.words;
case 'german':
return GermanMnemonics.words;
case 'japanese':
return JapaneseMnemonics.words;
case 'portuguese':
return PortugueseMnemonics.words;
case 'russian':
return RussianMnemonics.words;
case 'spanish':
return SpanishMnemonics.words;
case 'french':
return FrenchMnemonics.words;
case 'italian':
return ItalianMnemonics.words;
default:
return EnglishMnemonics.words;
}
}
@override
WalletCredentials createHavenRestoreWalletFromKeysCredentials(
{required String name,
required String spendKey,
required String viewKey,
required String address,
required String password,
required String language,
required int height}) {
return HavenRestoreWalletFromKeysCredentials(
name: name,
spendKey: spendKey,
viewKey: viewKey,
address: address,
password: password,
language: language,
height: height);
}
@override
WalletCredentials createHavenRestoreWalletFromSeedCredentials({
required String name,
required String password,
required int height,
required String mnemonic}) {
return HavenRestoreWalletFromSeedCredentials(
name: name,
password: password,
height: height,
mnemonic: mnemonic);
}
return HavenRestoreWalletFromKeysCredentials(
name: name,
spendKey: spendKey,
viewKey: viewKey,
address: address,
password: password,
language: language,
height: height);
}
@override
WalletCredentials createHavenNewWalletCredentials({
required String name,
required String language,
String? password}) {
return HavenNewWalletCredentials(
name: name,
password: password,
language: language);
}
WalletCredentials createHavenRestoreWalletFromSeedCredentials(
{required String name,
required String password,
required int height,
required String mnemonic}) {
return HavenRestoreWalletFromSeedCredentials(
name: name, password: password, height: height, mnemonic: mnemonic);
}
@override
Map<String, String> getKeys(Object wallet) {
final havenWallet = wallet as HavenWallet;
final keys = havenWallet.keys;
return <String, String>{
'privateSpendKey': keys.privateSpendKey,
WalletCredentials createHavenNewWalletCredentials(
{required String name, required String language, String? password}) {
return HavenNewWalletCredentials(name: name, password: password, language: language);
}
@override
Map<String, String> getKeys(Object wallet) {
final havenWallet = wallet as HavenWallet;
final keys = havenWallet.keys;
return <String, String>{
'privateSpendKey': keys.privateSpendKey,
'privateViewKey': keys.privateViewKey,
'publicSpendKey': keys.publicSpendKey,
'publicViewKey': keys.publicViewKey};
}
'publicViewKey': keys.publicViewKey
};
}
@override
Object createHavenTransactionCreationCredentials({
required List<Output> outputs,
required TransactionPriority priority,
required String assetType}) {
return HavenTransactionCreationCredentials(
outputs: outputs.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as MoneroTransactionPriority,
assetType: assetType);
}
Object createHavenTransactionCreationCredentials(
{required List<Output> outputs,
required TransactionPriority priority,
required String assetType}) {
return HavenTransactionCreationCredentials(
outputs: outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as MoneroTransactionPriority,
assetType: assetType);
}
@override
String formatterMoneroAmountToString({required int amount}) {
return moneroAmountToString(amount: amount);
}
@override
double formatterMoneroAmountToDouble({required int amount}) {
return moneroAmountToDouble(amount: amount);
}
String formatterMoneroAmountToString({required int amount}) {
return moneroAmountToString(amount: amount);
}
@override
int formatterMoneroParseAmount({required String amount}) {
return moneroParseAmount(amount: amount);
}
double formatterMoneroAmountToDouble({required int amount}) {
return moneroAmountToDouble(amount: amount);
}
@override
Account getCurrentAccount(Object wallet) {
final havenWallet = wallet as HavenWallet;
final acc = havenWallet.walletAddresses.account as monero_account.Account;
return Account(id: acc.id, label: acc.label);
}
int formatterMoneroParseAmount({required String amount}) {
return moneroParseAmount(amount: amount);
}
@override
void setCurrentAccount(Object wallet, int id, String label) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.account = monero_account.Account(id: id, label: label);
}
Account getCurrentAccount(Object wallet) {
final havenWallet = wallet as HavenWallet;
final acc = havenWallet.walletAddresses.account as monero_account.Account;
return Account(id: acc.id, label: acc.label);
}
@override
void onStartup() {
monero_wallet_api.onStartup();
}
void setCurrentAccount(Object wallet, int id, String label) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.account = monero_account.Account(id: id, label: label);
}
@override
int getTransactionInfoAccountId(TransactionInfo tx) {
final havenTransactionInfo = tx as HavenTransactionInfo;
return havenTransactionInfo.accountIndex;
}
void onStartup() {
monero_wallet_api.onStartup();
}
@override
WalletService createHavenWalletService(Box<WalletInfo> walletInfoSource) {
return HavenWalletService(walletInfoSource);
}
int getTransactionInfoAccountId(TransactionInfo tx) {
final havenTransactionInfo = tx as HavenTransactionInfo;
return havenTransactionInfo.accountIndex;
}
@override
String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) {
final havenWallet = wallet as HavenWallet;
return havenWallet.getTransactionAddress(accountIndex, addressIndex);
}
WalletService createHavenWalletService(Box<WalletInfo> walletInfoSource) {
return HavenWalletService(walletInfoSource);
}
@override
CryptoCurrency assetOfTransaction(TransactionInfo tx) {
final transaction = tx as HavenTransactionInfo;
final asset = CryptoCurrency.fromString(transaction.assetType);
return asset;
}
String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) {
final havenWallet = wallet as HavenWallet;
return havenWallet.getTransactionAddress(accountIndex, addressIndex);
}
@override
List<AssetRate> getAssetRate()
=> getRate()
.map((rate) => AssetRate(rate.getAssetType(), rate.getRate()))
.toList();
CryptoCurrency assetOfTransaction(TransactionInfo tx) {
final transaction = tx as HavenTransactionInfo;
final asset = CryptoCurrency.fromString(transaction.assetType);
return asset;
}
@override
List<AssetRate> getAssetRate() =>
getRate().map((rate) => AssetRate(rate.getAssetType(), rate.getRate())).toList();
}

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/buy/order.dart';
@ -100,6 +101,10 @@ Future<void> main() async {
Hive.registerAdapter(UnspentCoinsInfoAdapter());
}
if (!Hive.isAdapterRegistered(AnonpayInvoiceInfo.typeId)) {
Hive.registerAdapter(AnonpayInvoiceInfoAdapter());
}
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
@ -120,6 +125,7 @@ Future<void> main() async {
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates =
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
final anonpayInvoiceInfo = await Hive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
Box<UnspentCoinsInfo>? unspentCoinsInfoSource;
if (!isMoneroOnly) {
@ -139,6 +145,7 @@ Future<void> main() async {
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 21);
runApp(App());
}, (error, stackTrace) async {
@ -158,6 +165,7 @@ Future<void> initialSetup(
required Box<ExchangeTemplate> exchangeTemplates,
required Box<TransactionDescription> transactionDescriptions,
required FlutterSecureStorage secureStorage,
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfo,
Box<UnspentCoinsInfo>? unspentCoinsInfoSource,
int initialMigrationVersion = 15}) async {
LanguageService.loadLocaleList();
@ -178,6 +186,7 @@ Future<void> initialSetup(
exchangeTemplates: exchangeTemplates,
transactionDescriptionBox: transactionDescriptions,
ordersSource: ordersSource,
anonpayInvoiceInfoSource: anonpayInvoiceInfo,
unspentCoinsInfoSource: unspentCoinsInfoSource,
);
await bootstrap(navigatorKey);

View file

@ -25,7 +25,7 @@ Future<void> bootstrap(GlobalKey<NavigatorState> navigatorKey) async {
.get<SharedPreferences>()
.getString(PreferencesKey.currentWalletName);
if (currentWalletName != null) {
authenticationStore.state = AuthenticationState.installed;
authenticationStore.installed();
}
startAuthenticationStateChange(authenticationStore, navigatorKey);

View file

@ -1,10 +1,16 @@
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/onramper_page.dart';
import 'package:cake_wallet/src/screens/buy/payfura_page.dart';
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
@ -82,6 +88,7 @@ import 'package:cake_wallet/src/screens/ionia/cards/ionia_payment_status_page.da
import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
import 'package:cake_wallet/ionia/ionia_any_pay_payment_info.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart';
late RouteSettings currentRouteSettings;
@ -307,8 +314,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<OtherSettingsPage>());
case Routes.newNode:
final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NodeCreateOrEditPage>());
builder: (_) => getIt.get<NodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?,
param2: args?['isSelected'] as bool?));
case Routes.login:
return CupertinoPageRoute<void>(
@ -436,7 +446,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) =>
getIt.get<FullscreenQRPage>(
param1: args['qrData'] as String,
param2: args['isLight'] as bool,
param2: args['version'] as int?,
));
case Routes.ioniaWelcomePage:
@ -502,6 +513,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.onramperPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<OnRamperPage>());
case Routes.payfuraPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<PayFuraPage>());
case Routes.advancedPrivacySettings:
final type = settings.arguments as WalletType;
@ -510,7 +524,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type),
));
case Routes.anonPayInvoicePage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
case Routes.anonPayReceivePage:
final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
case Routes.anonPayDetailsPage:
final anonInvoiceViewData = settings.arguments as AnonpayInvoiceInfo;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(

View file

@ -82,4 +82,8 @@ class Routes {
static const displaySettingsPage = '/display_settings_page';
static const otherSettingsPage = '/other_settings_page';
static const advancedPrivacySettings = '/advanced_privacy_settings';
static const anonPayInvoicePage = '/anon_pay_invoice_page';
static const anonPayReceivePage = '/anon_pay_receive_page';
static const anonPayDetailsPage = '/anon_pay_details_page';
static const payfuraPage = '/pay_fura_page';
}

View file

@ -0,0 +1,56 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/src/widgets/standard_list_card.dart';
import 'package:cake_wallet/src/widgets/standard_list_status_row.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AnonpayDetailsPage extends BasePage {
AnonpayDetailsPage({required this.anonpayDetailsViewModel});
@override
String get title => S.current.invoice_details;
final AnonpayDetailsViewModel anonpayDetailsViewModel;
@override
Widget body(BuildContext context) {
return SectionStandardList(
context: context,
sectionCount: 1,
itemCounter: (int _) => anonpayDetailsViewModel.items.length,
itemBuilder: (_, __, index) {
final item = anonpayDetailsViewModel.items[index];
if (item is DetailsListStatusItem) {
return StandardListStatusRow(title: item.title, value: item.value);
}
if (item is TradeDetailsListCardItem) {
return TradeDetailsStandardListCard(
id: item.id,
create: item.createdAt,
pair: item.pair,
currentTheme: anonpayDetailsViewModel.settingsStore.currentTheme.type,
onTap: item.onTap,
);
}
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
},
child: ListRow(title: '${item.title}:', value: item.value),
);
});
}
}

View file

@ -0,0 +1,73 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';
class PayFuraPage extends BasePage {
PayFuraPage({required this.settingsStore, required this.wallet});
final SettingsStore settingsStore;
final WalletBase wallet;
@override
String get title => S.current.buy;
@override
Widget body(BuildContext context) {
return PayFuraPageBody(
settingsStore: settingsStore,
wallet: wallet);
}
}
class PayFuraPageBody extends StatefulWidget {
PayFuraPageBody(
{required this.settingsStore,
required this.wallet});
static const baseUrl = 'exchange.payfura.com';
final SettingsStore settingsStore;
final WalletBase wallet;
Uri get uri => Uri.https(baseUrl, '', <String, dynamic>{
'apiKey': secrets.payfuraApiKey,
'to': wallet.currency.title,
'from': settingsStore.fiatCurrency.title,
'walletAddress': '${wallet.currency.title}:${wallet.walletAddresses.address}',
'mode': 'buy'
});
@override
PayFuraPageBodyState createState() => PayFuraPageBodyState();
}
class PayFuraPageBodyState extends State<PayFuraPageBody> {
PayFuraPageBodyState();
@override
Widget build(BuildContext context) {
return InAppWebView(
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(transparentBackground: true),
),
initialUrlRequest: URLRequest(url: widget.uri),
androidOnPermissionRequest: (_, __, resources) async {
bool permissionGranted = await Permission.camera.status == PermissionStatus.granted;
if (!permissionGranted) {
permissionGranted = await Permission.camera.request().isGranted;
}
return PermissionRequestResponse(
resources: resources,
action: permissionGranted
? PermissionRequestResponseAction.GRANT
: PermissionRequestResponseAction.DENY,
);
},
);
}
}

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -153,8 +154,8 @@ class ContactPage extends BasePage {
items: contactViewModel.currencies,
title: S.of(context).please_select,
hintText: S.of(context).search_currency,
onItemSelected: (CryptoCurrency item) =>
contactViewModel.currency = item),
onItemSelected: (Currency item) =>
contactViewModel.currency = item as CryptoCurrency),
context: context);
}

View file

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
@ -277,6 +278,9 @@ class DashboardPage extends BasePage {
case WalletType.litecoin:
Navigator.of(context).pushNamed(Routes.onramperPage);
break;
case WalletType.monero:
Navigator.of(context).pushNamed(Routes.payfuraPage);
break;
default:
await showPopUp<void>(
context: context,
@ -308,7 +312,8 @@ class DashboardPage extends BasePage {
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).sell,
alertContent: S.of(context).sell_alert_content,
alertContent: isMoneroOnly ? S.of(context).sell_monero_com_alert_content
: S.of(context).sell_alert_content,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});

View file

@ -1,9 +1,14 @@
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.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/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/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
@ -13,24 +18,25 @@ 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:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/di.dart';
class AddressPage extends BasePage {
AddressPage({
required this.addressListViewModel,
required this.walletViewModel})
: _cryptoAmountFocus = FocusNode();
required this.walletViewModel,
required this.receiveOptionViewModel,
}) : _cryptoAmountFocus = FocusNode();
final WalletAddressListViewModel addressListViewModel;
final DashboardViewModel walletViewModel;
final ReceiveOptionViewModel receiveOptionViewModel;
final FocusNode _cryptoAmountFocus;
@override
String get title => S.current.receive;
@override
Color get backgroundLightColor => currentTheme.type == ThemeType.bright
? Colors.transparent : Colors.white;
Color get backgroundLightColor =>
currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
@override
Color get backgroundDarkColor => Colors.transparent;
@ -38,11 +44,15 @@ class AddressPage extends BasePage {
@override
bool get resizeToAvoidBottomInset => false;
bool effectsInstalled = false;
@override
Widget leading(BuildContext context) {
final _backButton = Icon(Icons.arrow_back_ios,
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
size: 16,);
final _backButton = Icon(
Icons.arrow_back_ios,
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
size: 16,
);
return SizedBox(
height: 37,
@ -61,16 +71,8 @@ class AddressPage extends BasePage {
}
@override
Widget middle(BuildContext context) {
return Text(
title,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!),
);
}
Widget middle(BuildContext context) =>
PresentReceiveOptionPicker(receiveOptionViewModel: receiveOptionViewModel);
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
@ -85,53 +87,56 @@ class AddressPage extends BasePage {
@override
Widget? trailing(BuildContext context) {
final shareImage =
Image.asset('assets/images/share.png',
final shareImage = Image.asset('assets/images/share.png',
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!);
return !addressListViewModel.hasAddressList ? Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () {
ShareUtil.share(
text: addressListViewModel.address.address,
context: context,
);
},
icon: shareImage,
),
) : null;
return !addressListViewModel.hasAddressList
? Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () {
ShareUtil.share(
text: addressListViewModel.address.address,
context: context,
);
},
icon: shareImage,
),
)
: null;
}
@override
Widget body(BuildContext context) {
_setEffects(context);
autorun((_) async {
if (!walletViewModel.isOutdatedElectrumWallet
|| !walletViewModel.settingsStore.shouldShowReceiveWarning) {
if (!walletViewModel.isOutdatedElectrumWallet ||
!walletViewModel.settingsStore.shouldShowReceiveWarning) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_receive_warning,
leftButtonText: S.of(context).understand,
actionLeftButton: () => Navigator.of(context).pop(),
rightButtonText: S.of(context).do_not_show_me,
actionRightButton: () {
walletViewModel.settingsStore.setShouldShowReceiveWarning(false);
Navigator.of(context).pop();
});
});
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_receive_warning,
leftButtonText: S.of(context).understand,
actionLeftButton: () => Navigator.of(context).pop(),
rightButtonText: S.of(context).do_not_show_me,
actionRightButton: () {
walletViewModel.settingsStore.setShouldShowReceiveWarning(false);
Navigator.of(context).pop();
});
});
}
});
@ -141,8 +146,7 @@ class AddressPage extends BasePage {
tapOutsideToDismiss: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor:
Theme.of(context).accentTextTheme!.bodyText1!.backgroundColor!,
keyboardBarColor: Theme.of(context).accentTextTheme.bodyText1!.backgroundColor!,
nextFocus: false,
actions: [
KeyboardActionsItem(
@ -154,29 +158,25 @@ class AddressPage extends BasePage {
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Column(
children: <Widget>[
Expanded(
child: Observer(builder: (_) => QRWidget(
addressListViewModel: addressListViewModel,
amountTextFieldFocusNode: _cryptoAmountFocus,
isAmountFieldShow: !addressListViewModel.hasAccounts,
isLight: walletViewModel.settingsStore.currentTheme.type == ThemeType.light))
Expanded(
child: Observer(builder: (_) => QRWidget(
addressListViewModel: addressListViewModel,
amountTextFieldFocusNode: _cryptoAmountFocus,
isAmountFieldShow: !addressListViewModel.hasAccounts,
isLight: walletViewModel.settingsStore.currentTheme.type == ThemeType.light))
),
Observer(builder: (_) {
return addressListViewModel.hasAddressList
? GestureDetector(
onTap: () =>
Navigator.of(context).pushNamed(Routes.receive),
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
child: Container(
height: 50,
padding: EdgeInsets.only(left: 24, right: 12),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(25)),
borderRadius: BorderRadius.all(Radius.circular(25)),
border: Border.all(
color:
Theme.of(context).textTheme!.subtitle1!.color!,
width: 1),
color: Theme.of(context).textTheme.subtitle1!.color!, width: 1),
color: Theme.of(context).buttonColor),
child: Row(
mainAxisSize: MainAxisSize.max,
@ -185,42 +185,79 @@ class AddressPage extends BasePage {
Observer(
builder: (_) => Text(
addressListViewModel.hasAccounts
? S
.of(context)
.accounts_subaddresses
? S.of(context).accounts_subaddresses
: S.of(context).addresses,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.accentTextTheme!
.accentTextTheme
.headline2!
.backgroundColor!),
)),
Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context)
.accentTextTheme!
.headline2!
.backgroundColor!,
color:
Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
)
],
),
),
)
: Text(
S.of(context).electrum_address_disclaimer,
: Text(S.of(context).electrum_address_disclaimer,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Theme.of(context)
.accentTextTheme!
.headline3!
.backgroundColor!));
color: Theme.of(context).accentTextTheme.headline3!.backgroundColor!));
})
],
),
));
}
void _setEffects(BuildContext context) {
if (effectsInstalled) {
return;
}
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
Navigator.pop(context);
switch (option) {
case ReceivePageOption.anonPayInvoice:
Navigator.pushReplacementNamed(
context,
Routes.anonPayInvoicePage,
arguments: [addressListViewModel.address.address, option],
);
break;
case ReceivePageOption.anonPayDonationLink:
final sharedPreferences = getIt.get<SharedPreferences>();
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
if (clearnetUrl != null && onionUrl != null) {
Navigator.pushReplacementNamed(
context,
Routes.anonPayReceivePage,
arguments: AnonpayDonationLinkInfo(
clearnetUrl: clearnetUrl,
onionUrl: onionUrl,
address: addressListViewModel.address.address,
),
);
} else {
Navigator.pushReplacementNamed(
context,
Routes.anonPayInvoicePage,
arguments: [addressListViewModel.address.address, option],
);
}
break;
default:
}
});
effectsInstalled = true;
}
}

View file

@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
class AnonpayTransactionRow extends StatelessWidget {
AnonpayTransactionRow({
required this.provider,
required this.createdAt,
required this.currency,
required this.onTap,
required this.amount,
});
final VoidCallback? onTap;
final String provider;
final String createdAt;
final String amount;
final String currency;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
color: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_getImage(),
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
Text(provider,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!)),
Text(amount + ' ' + currency,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!))
]),
SizedBox(height: 5),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
Text(createdAt,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).textTheme.overline!.backgroundColor!))
])
],
))
],
),
));
}
Widget _getImage() => ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
}

View file

@ -0,0 +1,140 @@
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
class PresentReceiveOptionPicker extends StatelessWidget {
PresentReceiveOptionPicker({required this.receiveOptionViewModel});
final ReceiveOptionViewModel receiveOptionViewModel;
@override
Widget build(BuildContext context) {
final arrowBottom =
Image.asset('assets/images/arrow_bottom_purple_icon.png', color: Colors.white, height: 6);
return TextButton(
onPressed: () => _showPicker(context),
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
splashFactory: NoSplash.splashFactory,
foregroundColor: MaterialStateProperty.all(Colors.transparent),
overlayColor: MaterialStateProperty.all(Colors.transparent),
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
S.current.receive,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!),
),
Observer(
builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(),
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.headline5!.color!)))
],
),
SizedBox(width: 5),
Padding(
padding: EdgeInsets.only(top: 12),
child: arrowBottom,
)
],
),
);
}
void _showPicker(BuildContext context) async {
await showPopUp<void>(
builder: (BuildContext popUpContext) => Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: AlertBackground(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(),
Container(
margin: EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Theme.of(context).backgroundColor,
),
child: Padding(
padding: const EdgeInsets.only(top: 24, bottom: 24),
child: (ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: receiveOptionViewModel.options.length,
itemBuilder: (_, index) {
final option = receiveOptionViewModel.options[index];
return InkWell(
onTap: () => receiveOptionViewModel.selectReceiveOption(option),
child: Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Observer(builder: (_) {
final value = receiveOptionViewModel.selectedReceiveOption;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(option.toString(),
textAlign: TextAlign.left,
style: textSmall(
color: Theme.of(context).primaryTextTheme.headline6!.color!,
).copyWith(
fontWeight:
value == option ? FontWeight.w800 : FontWeight.w500,
)),
RoundedCheckbox(
value: value == option,
)
],
);
}),
),
);
},
separatorBuilder: (_, index) => SizedBox(height: 30),
)),
),
),
Spacer(),
Container(
margin: EdgeInsets.only(bottom: 40),
child: InkWell(
onTap: () => Navigator.pop(context),
child: CircleAvatar(
child: Icon(
Icons.close,
color: Palette.darkBlueCraiola,
),
backgroundColor: Colors.white,
),
),
)
],
),
),
),
context: context,
);
}
}

View file

@ -1,5 +1,8 @@
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -22,102 +25,104 @@ class TransactionsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(
top: 24,
bottom: 24
),
padding: EdgeInsets.only(top: 24, bottom: 24),
child: Column(
children: <Widget>[
HeaderRow(dashboardViewModel: dashboardViewModel),
Expanded(
child: Observer(
builder: (_) {
final items = dashboardViewModel.items;
Expanded(child: Observer(builder: (_) {
final items = dashboardViewModel.items;
return items?.isNotEmpty ?? false
? ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return items.isNotEmpty
? ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
final item = items[index];
if (item is DateSectionItem) {
return DateSectionRaw(date: item.date);
}
if (item is TransactionListItem) {
final transaction = item.transaction;
return Observer(
builder: (_) => TransactionRow(
onTap: () => Navigator.of(context).pushNamed(
Routes.transactionDetails,
arguments: transaction),
direction: transaction.direction,
formattedDate: DateFormat('HH:mm')
.format(transaction.date),
formattedAmount: item.formattedCryptoAmount,
formattedFiatAmount:
dashboardViewModel.balanceViewModel.isFiatDisabled
? '' : item.formattedFiatAmount,
isPending: transaction.isPending,
title: item.formattedTitle + item.formattedStatus));
}
if (item is TradeListItem) {
final trade = item.trade;
return Observer(builder: (_) => TradeRow(
onTap: () => Navigator.of(context).pushNamed(
Routes.tradeDetails,
arguments: trade),
provider: trade.provider,
from: trade.from,
to: trade.to,
createdAtFormattedDate:
trade.createdAt != null
? DateFormat('HH:mm').format(trade.createdAt!)
: null,
formattedAmount: item.tradeFormattedAmount
));
}
if (item is OrderListItem) {
final order = item.order;
return Observer(builder: (_) => OrderRow(
onTap: () => Navigator.of(context).pushNamed(
Routes.orderDetails,
arguments: order),
provider: order.provider,
from: order.from!,
to: order.to!,
createdAtFormattedDate:
DateFormat('HH:mm').format(order.createdAt),
formattedAmount: item.orderFormattedAmount,
));
}
return Container(
color: Colors.transparent,
height: 1);
if (item is DateSectionItem) {
return DateSectionRaw(date: item.date);
}
)
: Center(
if (item is TransactionListItem) {
final transaction = item.transaction;
return Observer(
builder: (_) => TransactionRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.transactionDetails, arguments: transaction),
direction: transaction.direction,
formattedDate: DateFormat('HH:mm').format(transaction.date),
formattedAmount: item.formattedCryptoAmount,
formattedFiatAmount:
dashboardViewModel.balanceViewModel.isFiatDisabled
? ''
: item.formattedFiatAmount,
isPending: transaction.isPending,
title: item.formattedTitle + item.formattedStatus));
}
if (item is AnonpayTransactionListItem) {
final transactionInfo = item.transaction;
return AnonpayTransactionRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo),
currency: transactionInfo.fiatAmount != null
? transactionInfo.fiatEquiv ?? ''
: CryptoCurrency.fromFullName(transactionInfo.coinTo)
.name
.toUpperCase(),
provider: transactionInfo.provider,
amount: transactionInfo.fiatAmount?.toString() ??
(transactionInfo.amountTo?.toString() ?? ''),
createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
);
}
if (item is TradeListItem) {
final trade = item.trade;
return Observer(
builder: (_) => TradeRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails, arguments: trade),
provider: trade.provider,
from: trade.from,
to: trade.to,
createdAtFormattedDate: trade.createdAt != null
? DateFormat('HH:mm').format(trade.createdAt!)
: null,
formattedAmount: item.tradeFormattedAmount));
}
if (item is OrderListItem) {
final order = item.order;
return Observer(
builder: (_) => OrderRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.orderDetails, arguments: order),
provider: order.provider,
from: order.from!,
to: order.to!,
createdAtFormattedDate:
DateFormat('HH:mm').format(order.createdAt),
formattedAmount: item.orderFormattedAmount,
));
}
return Container(color: Colors.transparent, height: 1);
})
: Center(
child: Text(
S.of(context).placeholder_transactions,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme!
.overline!.decorationColor!
),
fontSize: 14,
color: Theme.of(context).primaryTextTheme.overline!.decorationColor!),
),
);
}
)
)
}))
],
),
);
}
}
}

View file

@ -240,7 +240,7 @@ class ExchangePage extends BasePage {
.bodyText1!
.color!,
currencyValueValidator: AmountValidator(
type: exchangeViewModel.wallet.type),
currency: exchangeViewModel.depositCurrency),
addressTextFieldValidator: AddressValidator(
type: exchangeViewModel.depositCurrency),
onPushPasteButton: (context) async {
@ -304,7 +304,7 @@ class ExchangePage extends BasePage {
.bodyText1!
.decorationColor!,
currencyValueValidator: AmountValidator(
type: exchangeViewModel.wallet.type),
currency: exchangeViewModel.receiveCurrency),
addressTextFieldValidator:
AddressValidator(
type: exchangeViewModel

View file

@ -165,7 +165,7 @@ class ExchangeTemplatePage extends BasePage {
.bodyText1!
.color!,
currencyValueValidator: AmountValidator(
type: exchangeViewModel.wallet.type),
currency: exchangeViewModel.depositCurrency),
//addressTextFieldValidator: AddressValidator(
// type: exchangeViewModel.depositCurrency),
),
@ -206,7 +206,7 @@ class ExchangeTemplatePage extends BasePage {
.bodyText1!
.decorationColor!,
currencyValueValidator: AmountValidator(
type: exchangeViewModel.wallet.type),
currency: exchangeViewModel.receiveCurrency),
//addressTextFieldValidator: AddressValidator(
// type: exchangeViewModel.receiveCurrency),
)),

View file

@ -2,6 +2,7 @@ import 'dart:ui';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker_item_widget.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/picker_item.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cw_core/crypto_currency.dart';
@ -20,9 +21,9 @@ class CurrencyPicker extends StatefulWidget {
this.isConvertFrom = false});
int selectedAtIndex;
final List<CryptoCurrency> items;
final List<Currency> items;
final String? title;
final Function(CryptoCurrency) onItemSelected;
final Function(Currency) onItemSelected;
final bool isMoneroWallet;
final bool isConvertFrom;
final String? hintText;
@ -38,13 +39,13 @@ class CurrencyPickerState extends State<CurrencyPicker> {
subPickerItemsList = items,
appBarTextStyle =
TextStyle(fontSize: 20, fontFamily: 'Lato', backgroundColor: Colors.transparent, color: Colors.white),
pickerItemsList = <PickerItem<CryptoCurrency>>[];
pickerItemsList = <PickerItem<Currency>>[];
List<PickerItem<CryptoCurrency>> pickerItemsList;
List<CryptoCurrency> items;
List<PickerItem<Currency>> pickerItemsList;
List<Currency> items;
bool isSearchBarActive;
String textFieldValue;
List<CryptoCurrency> subPickerItemsList;
List<Currency> subPickerItemsList;
TextStyle appBarTextStyle;
void cleanSubPickerItemsList() => subPickerItemsList = items;
@ -54,7 +55,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
if (subString.isNotEmpty) {
subPickerItemsList = items
.where((element) =>
(element.title != null ? element.title.toLowerCase().contains(subString.toLowerCase()) : false) ||
element.name.toLowerCase().contains(subString.toLowerCase()) ||
(element.tag != null ? element.tag!.toLowerCase().contains(subString.toLowerCase()) : false) ||
(element.fullName != null ? element.fullName!.toLowerCase().contains(subString.toLowerCase()) : false))
.toList();
@ -139,7 +140,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
AspectRatio(
aspectRatio: 6,
child: PickerItemWidget(
title: items[widget.selectedAtIndex].title,
title: items[widget.selectedAtIndex].name,
iconPath: items[widget.selectedAtIndex].iconPath,
isSelected: true,
tag: items[widget.selectedAtIndex].tag,

View file

@ -32,12 +32,12 @@ class PickerItemWidget extends StatelessWidget {
width: 20.0,
),
),
const SizedBox(width: 6),
const SizedBox(width: 12),
Expanded(
child: Row(
children: [
Text(
title,
title.toUpperCase(),
style: TextStyle(
color: isSelected ? Palette.blueCraiola : Theme.of(context).primaryTextTheme!.headline6!.color!,
fontSize: isSelected ? 16 : 14.0,

View file

@ -1,6 +1,5 @@
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
import 'package:cw_core/crypto_currency.dart';
import 'picker_item.dart';
import 'currency_picker_item_widget.dart';
class CurrencyPickerWidget extends StatelessWidget {
@ -14,7 +13,7 @@ class CurrencyPickerWidget extends StatelessWidget {
final int crossAxisCount;
final int selectedAtIndex;
final Function pickListItem;
final List<CryptoCurrency> pickerItemsList;
final List<Currency> pickerItemsList;
final ScrollController _scrollController = ScrollController();
@ -39,8 +38,8 @@ class CurrencyPickerWidget extends StatelessWidget {
onTap: () {
pickListItem(index);
},
title: pickerItemsList[index].title,
iconPath: pickerItemsList[index].iconPath,
title: pickerItemsList[index].name,
iconPath: pickerItemsList[index].iconPath,
tag: pickerItemsList[index].tag,
);
}),

View file

@ -4,6 +4,7 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -501,9 +502,9 @@ class ExchangeCardState extends State<ExchangeCard> {
hintText: S.of(context).search_currency,
isMoneroWallet: _isMoneroWallet,
isConvertFrom: widget.hasRefundAddress,
onItemSelected: (CryptoCurrency item) =>
onItemSelected: (Currency item) =>
widget.onCurrencySelected != null
? widget.onCurrencySelected(item)
? widget.onCurrencySelected(item as CryptoCurrency)
: null),
context: context);
}

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/node.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -13,7 +14,7 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
class NodeCreateOrEditPage extends BasePage {
NodeCreateOrEditPage(this.nodeCreateOrEditViewModel)
NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
: _formKey = GlobalKey<FormState>(),
_addressController = TextEditingController(),
_portController = TextEditingController(),
@ -62,9 +63,11 @@ class NodeCreateOrEditPage extends BasePage {
final TextEditingController _passwordController;
@override
String get title => S.current.node_new;
String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
final Node? editingNode;
final bool? isSelected;
@override
Widget body(BuildContext context) {
@ -108,6 +111,7 @@ class NodeCreateOrEditPage extends BasePage {
content: NodeForm(
formKey: _formKey,
nodeViewModel: nodeCreateOrEditViewModel,
editingNode: editingNode,
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
@ -140,7 +144,8 @@ class NodeCreateOrEditPage extends BasePage {
return;
}
await nodeCreateOrEditViewModel.save();
await nodeCreateOrEditViewModel.save(
editingNode: editingNode, saveAsCurrent: isSelected ?? false);
Navigator.of(context).pop();
},
text: S.of(context).save,

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/core/node_port_validator.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -12,22 +13,20 @@ class NodeForm extends StatelessWidget {
NodeForm({
required this.nodeViewModel,
required this.formKey,
}) : _addressController = TextEditingController(),
_portController = TextEditingController(),
_loginController = TextEditingController(),
_passwordController = TextEditingController() {
reaction((_) => nodeViewModel.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
reaction((_) => nodeViewModel.port, (String port) {
if (port != _portController.text) {
_portController.text = port;
}
});
this.editingNode,
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
_portController = TextEditingController(text: editingNode?.uri.port.toString()),
_loginController = TextEditingController(text: editingNode?.login),
_passwordController = TextEditingController(text: editingNode?.password) {
if (editingNode != null) {
nodeViewModel
..setAddress((editingNode!.uri.host.toString()))
..setPort((editingNode!.uri.port.toString()))
..setPassword((editingNode!.password ?? ''))
..setLogin((editingNode!.login ?? ''))
..setSSL((editingNode!.isSSL))
..setTrusted((editingNode!.trusted));
}
if (nodeViewModel.hasAuthCredentials) {
reaction((_) => nodeViewModel.login, (String login) {
if (login != _loginController.text) {
@ -42,18 +41,15 @@ class NodeForm extends StatelessWidget {
});
}
_addressController
.addListener(() => nodeViewModel.address = _addressController.text);
_portController
.addListener(() => nodeViewModel.port = _portController.text);
_loginController
.addListener(() => nodeViewModel.login = _loginController.text);
_passwordController
.addListener(() => nodeViewModel.password = _passwordController.text);
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
_portController.addListener(() => nodeViewModel.port = _portController.text);
_loginController.addListener(() => nodeViewModel.login = _loginController.text);
_passwordController.addListener(() => nodeViewModel.password = _passwordController.text);
}
final NodeCreateOrEditViewModel nodeViewModel;
final GlobalKey<FormState> formKey;
final Node? editingNode;
final TextEditingController _addressController;
final TextEditingController _portController;
@ -84,8 +80,7 @@ class NodeForm extends StatelessWidget {
child: BaseTextFormField(
controller: _portController,
hintText: S.of(context).node_port,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: false),
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
validator: NodePortValidator(),
))
],

View file

@ -0,0 +1,219 @@
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AnonPayInvoicePage extends BasePage {
AnonPayInvoicePage(
this.anonInvoicePageViewModel,
this.receiveOptionViewModel,
) : _amountFocusNode = FocusNode() {
}
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _descriptionController = TextEditingController();
final _amountController = TextEditingController();
final FocusNode _amountFocusNode;
final AnonInvoicePageViewModel anonInvoicePageViewModel;
final ReceiveOptionViewModel receiveOptionViewModel;
final _formKey = GlobalKey<FormState>();
bool effectsInstalled = false;
@override
Color get titleColor => Colors.white;
@override
bool get resizeToAvoidBottomInset => false;
@override
bool get extendBodyBehindAppBar => true;
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override
Widget middle(BuildContext context) =>
PresentReceiveOptionPicker(receiveOptionViewModel: receiveOptionViewModel);
@override
Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).clear,
onPressed: () {
_formKey.currentState?.reset();
anonInvoicePageViewModel.reset();
});
@override
Widget body(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context));
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.bodyText1!.backgroundColor!,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFocusNode,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
]),
child: Container(
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(
colors: [
Theme.of(context).primaryTextTheme.subtitle2!.color!,
Theme.of(context).primaryTextTheme.subtitle2!.decorationColor!,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Observer(builder: (_) {
return Padding(
padding: EdgeInsets.fromLTRB(24, 100, 24, 0),
child: AnonInvoiceForm(
nameController: _nameController,
descriptionController: _descriptionController,
amountController: _amountController,
emailController: _emailController,
depositAmountFocus: _amountFocusNode,
formKey: _formKey,
isInvoice: receiveOptionViewModel.selectedReceiveOption ==
ReceivePageOption.anonPayInvoice,
anonInvoicePageViewModel: anonInvoicePageViewModel,
),
);
}),
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Observer(builder: (_) {
final isInvoice =
receiveOptionViewModel.selectedReceiveOption == ReceivePageOption.anonPayInvoice;
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Center(
child: Text(
isInvoice
? S.of(context).anonpay_description("an invoice", "pay")
: S.of(context).anonpay_description("a donation link", "donate"),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.headline1!.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 12),
),
),
),
LoadingPrimaryButton(
text:
isInvoice ? S.of(context).create_invoice : S.of(context).create_donation_link,
onPressed: () {
anonInvoicePageViewModel.setRequestParams(
inputAmount: _amountController.text,
inputName: _nameController.text,
inputEmail: _emailController.text,
inputDescription: _descriptionController.text,
);
if (anonInvoicePageViewModel.receipientEmail.isNotEmpty &&
_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
return;
}
if (isInvoice) {
anonInvoicePageViewModel.createInvoice();
} else {
anonInvoicePageViewModel.generateDonationLink();
}
},
color: Theme.of(context).accentTextTheme.bodyText1!.color!,
textColor: Colors.white,
isLoading: anonInvoicePageViewModel.state is IsExecutingState,
),
],
);
}),
),
),
);
}
void _setReactions(BuildContext context) {
if (effectsInstalled) {
return;
}
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
Navigator.pop(context);
switch (option) {
case ReceivePageOption.mainnet:
Navigator.popAndPushNamed(context, Routes.addressPage);
break;
case ReceivePageOption.anonPayDonationLink:
final sharedPreferences = getIt.get<SharedPreferences>();
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
if (clearnetUrl != null && onionUrl != null) {
Navigator.pushReplacementNamed(context, Routes.anonPayReceivePage,
arguments: AnonpayDonationLinkInfo(
clearnetUrl: clearnetUrl,
onionUrl: onionUrl,
address: anonInvoicePageViewModel.address,
));
}
break;
default:
}
});
reaction((_) => anonInvoicePageViewModel.state, (ExecutionState state) {
if (state is ExecutedSuccessfullyState) {
Navigator.pushNamed(context, Routes.anonPayReceivePage, arguments: state.payload);
}
if (state is FailureState) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
});
effectsInstalled = true;
}
}

View file

@ -0,0 +1,181 @@
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/receive/widgets/anonpay_status_section.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/src/screens/receive/widgets/copy_link_item.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:device_display_brightness/device_display_brightness.dart';
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart' as qr;
class AnonPayReceivePage extends BasePage {
final AnonpayInfoBase invoiceInfo;
AnonPayReceivePage({required this.invoiceInfo});
@override
String get title => S.current.receive;
@override
Color get backgroundLightColor =>
currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
@override
Color get backgroundDarkColor => Colors.transparent;
@override
bool get resizeToAvoidBottomInset => false;
@override
Widget leading(BuildContext context) {
final _backButton = Icon(
Icons.arrow_back_ios,
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
size: 16,
);
return SizedBox(
height: 37,
width: 37,
child: ButtonTheme(
minWidth: double.minPositive,
child: TextButton(
onPressed: () =>
Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false),
child: _backButton),
),
);
}
@override
Widget middle(BuildContext context) {
return Column(
children: [
Text(
title,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!),
),
Text(
invoiceInfo is AnonpayInvoiceInfo
? ReceivePageOption.anonPayInvoice.toString()
: ReceivePageOption.anonPayDonationLink.toString(),
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.headline5!.color!),
)
],
);
}
@override
Widget? trailing(BuildContext context) {
if (invoiceInfo is AnonpayInvoiceInfo) {
return null;
}
return Material(
color: Colors.transparent,
child: IconButton(
onPressed: () => Navigator.popAndPushNamed(
context,
Routes.anonPayInvoicePage,
arguments: [invoiceInfo.address, ReceivePageOption.anonPayDonationLink],
),
icon: Icon(
Icons.edit,
color: Theme.of(context).accentTextTheme.caption!.color!,
size: 22.0,
),
),
);
}
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
Theme.of(context).accentColor,
Theme.of(context).scaffoldBackgroundColor,
Theme.of(context).primaryColor,
], begin: Alignment.topRight, end: Alignment.bottomLeft)),
child: scaffold);
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(height: 24),
if (invoiceInfo is AnonpayInvoiceInfo)
AnonInvoiceStatusSection(invoiceInfo: invoiceInfo as AnonpayInvoiceInfo),
Padding(
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.5,
),
child: GestureDetector(
onTap: () async {
final double brightness = await DeviceDisplayBrightness.getBrightness();
// ignore: unawaited_futures
DeviceDisplayBrightness.setBrightness(1.0);
await Navigator.pushNamed(
context,
Routes.fullscreenQR,
arguments: {
'qrData': invoiceInfo.clearnetUrl,
'version': qr.QrVersions.auto,
},
);
// ignore: unawaited_futures
DeviceDisplayBrightness.setBrightness(brightness);
},
child: Hero(
tag: Key(invoiceInfo.clearnetUrl),
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
width: 3,
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
),
),
child: QrImage(
data: invoiceInfo.clearnetUrl,
version: qr.QrVersions.auto,
),
),
),
),
),
),
),
),
SizedBox(height: 24),
Column(
children: [
CopyLinkItem(url: invoiceInfo.clearnetUrl, title: S.of(context).clearnet_link),
SizedBox(height: 16),
CopyLinkItem(url: invoiceInfo.onionUrl, title: S.of(context).onion_link),
],
),
SizedBox(height: 100),
],
),
);
}
}

View file

@ -4,10 +4,10 @@ import 'package:flutter/material.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class FullscreenQRPage extends BasePage {
FullscreenQRPage({required this.qrData, required this.isLight});
FullscreenQRPage({required this.qrData, int? this.version});
final bool isLight;
final String qrData;
final int? version;
@override
Color get backgroundLightColor => currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white;
@ -71,7 +71,7 @@ class FullscreenQRPage extends BasePage {
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border.all(width: 3, color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!)),
child: QrImage(data: qrData),
child: QrImage(data: qrData, version: version),
),
),
),

View file

@ -0,0 +1,153 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AnonpayCurrencyInputField extends StatelessWidget {
const AnonpayCurrencyInputField(
{super.key,
required this.onTapPicker,
required this.selectedCurrency,
required this.focusNode,
required this.controller,
required this.minAmount,
required this.maxAmount});
final Function() onTapPicker;
final Currency selectedCurrency;
final FocusNode focusNode;
final TextEditingController controller;
final String minAmount;
final String maxAmount;
@override
Widget build(BuildContext context) {
final arrowBottomPurple = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Colors.white,
height: 8,
);
return Column(
children: [
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!,
width: 1)),
),
child: Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
children: [
Container(
padding: EdgeInsets.only(right: 8),
height: 32,
child: InkWell(
onTap: onTapPicker,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 5),
child: arrowBottomPurple,
),
Text(selectedCurrency.name.toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white))
]),
),
),
selectedCurrency.tag != null
? Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Container(
height: 32,
decoration: BoxDecoration(
color: Theme.of(context).primaryTextTheme.headline4!.color!,
borderRadius: BorderRadius.all(Radius.circular(6))),
child: Center(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
selectedCurrency.tag!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.headline4!
.decorationColor!,
),
),
),
),
),
)
: Container(),
Padding(
padding: const EdgeInsets.only(right: 4.0),
child: Text(':',
style: TextStyle(
fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)),
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: BaseTextFormField(
focusNode: focusNode,
controller: controller,
textInputAction: TextInputAction.next,
enabled: true,
textAlign: TextAlign.left,
keyboardType:
TextInputType.numberWithOptions(signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
],
hintText: '0.0000',
borderColor: Colors.transparent,
//widget.borderColor,
textStyle: TextStyle(
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).accentTextTheme.headline1!.decorationColor!,
),
validator: null,
),
),
],
),
),
],
),
)),
Container(
height: 15,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).min_value(minAmount, selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).accentTextTheme.headline1!.decorationColor!),
),
SizedBox(width: 10),
Text(S.of(context).max_value(maxAmount, selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).accentTextTheme.headline1!.decorationColor!)),
],
),
)
],
);
}
}

View file

@ -0,0 +1,137 @@
import 'package:cake_wallet/core/email_validator.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/screens/receive/widgets/anonpay_currency_input_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class AnonInvoiceForm extends StatelessWidget {
AnonInvoiceForm({
super.key,
required this.formKey,
required this.anonInvoicePageViewModel,
required this.isInvoice,
required this.amountController,
required this.nameController,
required this.emailController,
required this.descriptionController,
required this.depositAmountFocus,
}) : _nameFocusNode = FocusNode(),
_emailFocusNode = FocusNode(),
_descriptionFocusNode = FocusNode(){
amountController.text = anonInvoicePageViewModel.amount;
nameController.text = anonInvoicePageViewModel.receipientName;
descriptionController.text = anonInvoicePageViewModel.description;
emailController.text = anonInvoicePageViewModel.receipientEmail;
}
final TextEditingController amountController;
final TextEditingController nameController;
final TextEditingController emailController;
final TextEditingController descriptionController;
final AnonInvoicePageViewModel anonInvoicePageViewModel;
final FocusNode depositAmountFocus;
final FocusNode _nameFocusNode;
final FocusNode _emailFocusNode;
final FocusNode _descriptionFocusNode;
final GlobalKey<FormState> formKey;
final bool isInvoice;
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
isInvoice ? S.of(context).invoice_details : S.of(context).donation_link_details,
style: textMediumSemiBold(),
),
if (isInvoice)
Observer(builder: (_) {
return AnonpayCurrencyInputField(
onTapPicker: () => _presentPicker(context),
controller: amountController,
focusNode: depositAmountFocus,
maxAmount: anonInvoicePageViewModel.maximum?.toString() ?? '...',
minAmount: anonInvoicePageViewModel.minimum?.toString() ?? '...',
selectedCurrency: anonInvoicePageViewModel.selectedCurrency,
);
}),
SizedBox(
height: 24,
),
BaseTextFormField(
controller: nameController,
focusNode: _nameFocusNode,
borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).optional_name,
textInputAction: TextInputAction.next,
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).accentTextTheme.headline1!.decorationColor!,
),
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
),
SizedBox(
height: 24,
),
BaseTextFormField(
controller: descriptionController,
focusNode: _descriptionFocusNode,
textInputAction: TextInputAction.next,
borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).optional_description,
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).accentTextTheme.headline1!.decorationColor!,
),
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
),
SizedBox(height: 24),
BaseTextFormField(
controller: emailController,
textInputAction: TextInputAction.next,
focusNode: _emailFocusNode,
borderColor: Theme.of(context).accentTextTheme.headline6!.backgroundColor,
suffixIcon: SizedBox(width: 36),
keyboardType: TextInputType.emailAddress,
hintText: S.of(context).optional_email_hint,
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).accentTextTheme.headline1!.decorationColor!,
),
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: EmailValidator(),
),
SizedBox(
height: 52,
),
],
));
}
void _presentPicker(BuildContext context) {
showPopUp<void>(
builder: (_) => CurrencyPicker(
selectedAtIndex: anonInvoicePageViewModel.selectedCurrencyIndex,
items: anonInvoicePageViewModel.currencies,
hintText: S.of(context).search_currency,
onItemSelected: anonInvoicePageViewModel.selectCurrency,
),
context: context,
);
}
}

View file

@ -0,0 +1,87 @@
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
import 'package:cake_wallet/typography.dart';
import 'package:flutter/material.dart';
class AnonInvoiceStatusSection extends StatelessWidget {
const AnonInvoiceStatusSection({
super.key,
required this.invoiceInfo,
});
final AnonpayInvoiceInfo invoiceInfo;
@override
Widget build(BuildContext context) {
return Container(
width: 200,
padding: EdgeInsets.all(19),
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(30),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.current.status,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryTextTheme.headline1!.decorationColor!,
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Theme.of(context).accentTextTheme.headline3!.color!,
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SyncIndicatorIcon(
boolMode: false,
value: invoiceInfo.status ?? '',
size: 6,
),
SizedBox(width: 5),
Text(
invoiceInfo.status ?? '',
style: textSmallSemiBold(
color: Theme.of(context).primaryTextTheme.headline6!.color,
),
)
],
),
)
],
),
SizedBox(height: 27),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'ID',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryTextTheme.headline1!.decorationColor!,
),
),
Text(
invoiceInfo.invoiceId ?? '',
style: textSmallSemiBold(
color: Theme.of(context).primaryTextTheme.headline6!.color,
),
),
],
),
],
),
);
}
}

View file

@ -0,0 +1,56 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/typography.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:share_plus/share_plus.dart';
class CopyLinkItem extends StatelessWidget {
const CopyLinkItem({super.key, required this.url, required this.title});
final String url;
final String title;
@override
Widget build(BuildContext context) {
final copyImage = Image.asset('assets/images/copy_address.png',
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
style: textMedium(
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
),
),
SizedBox(width: 50),
Row(
children: [
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: url));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: copyImage,
),
SizedBox(width: 20),
IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () => Share.share(url),
icon: Icon(
Icons.share,
color: Theme.of(context).accentTextTheme.headline2!.backgroundColor!,
),
)
],
)
],
);
}
}

View file

@ -5,13 +5,13 @@ class QrImage extends StatelessWidget {
QrImage({
required this.data,
this.size = 100.0,
this.version = 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ???
this.version,
this.errorCorrectionLevel = qr.QrErrorCorrectLevel.L,
});
final double size;
final String data;
final int version;
final int? version;
final int errorCorrectionLevel;
@override
@ -19,7 +19,7 @@ class QrImage extends StatelessWidget {
return qr.QrImage(
data: data,
errorCorrectionLevel: errorCorrectionLevel,
version: version,
version: version ?? 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ???
size: size,
foregroundColor: Colors.black,
backgroundColor: Colors.white,

View file

@ -1,8 +1,8 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:device_display_brightness/device_display_brightness.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -15,11 +15,12 @@ class QRWidget extends StatelessWidget {
QRWidget(
{required this.addressListViewModel,
required this.isLight,
this.qrVersion,
this.isAmountFieldShow = false,
this.amountTextFieldFocusNode})
: amountController = TextEditingController(),
_formKey = GlobalKey<FormState>() {
amountController.addListener(() => addressListViewModel.amount =
amountController.addListener(() => addressListViewModel?.amount =
_formKey.currentState!.validate() ? amountController.text : '');
}
@ -29,6 +30,7 @@ class QRWidget extends StatelessWidget {
final FocusNode? amountTextFieldFocusNode;
final GlobalKey<FormState> _formKey;
final bool isLight;
final int? qrVersion;
@override
Widget build(BuildContext context) {
@ -56,46 +58,48 @@ class QRWidget extends StatelessWidget {
children: <Widget>[
Spacer(flex: 3),
Observer(
builder: (_) => Flexible(
flex: 5,
child: GestureDetector(
onTap: () async {
// Get the current brightness:
final double brightness = await DeviceDisplayBrightness.getBrightness();
builder: (_) {
return Flexible(
flex: 5,
child: GestureDetector(
onTap: () async {
// Get the current brightness:
final double brightness = await DeviceDisplayBrightness.getBrightness();
// ignore: unawaited_futures
DeviceDisplayBrightness.setBrightness(1.0);
await Navigator.pushNamed(
context,
Routes.fullscreenQR,
arguments: {
'qrData': addressListViewModel.uri.toString(),
'isLight': isLight,
},
);
// ignore: unawaited_futures
DeviceDisplayBrightness.setBrightness(brightness);
},
child: Hero(
tag: Key(addressListViewModel.uri.toString()),
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
width: 3,
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
// ignore: unawaited_futures
DeviceDisplayBrightness.setBrightness(1.0);
await Navigator.pushNamed(
context,
Routes.fullscreenQR,
arguments: {
'qrData': addressListViewModel.uri.toString(),
},
);
// ignore: unawaited_futures
DeviceDisplayBrightness.setBrightness(brightness);
},
child: Hero(
tag: Key(addressListViewModel.uri.toString()),
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
width: 3,
color:
Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
),
),
child: QrImage(data: addressListViewModel.uri.toString(), version: qrVersion),
),
child: QrImage(data: addressListViewModel.uri.toString()),
),
),
),
),
),
),
);
}
),
Spacer(flex: 3)
],
@ -119,7 +123,9 @@ class QRWidget extends StatelessWidget {
hintText: S.of(context).receive_amount,
textColor: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!,
borderColor: Theme.of(context).textTheme!.headline5!.decorationColor!,
validator: AmountValidator(type: addressListViewModel.type, isAutovalidate: true),
validator: AmountValidator(
currency: walletTypeToCryptoCurrency(addressListViewModel!.type),
isAutovalidate: true),
// FIX-ME: Check does it equal to autovalidate: true,
autovalidateMode: AutovalidateMode.always,
placeholderTextStyle: TextStyle(
@ -133,39 +139,40 @@ class QRWidget extends StatelessWidget {
],
),
),
Padding(
padding: EdgeInsets.only(top: 8, bottom: 8),
child: Builder(
builder: (context) => Observer(
builder: (context) => GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: addressListViewModel.address.address));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(
addressListViewModel.address.address,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!),
Padding(
padding: EdgeInsets.only(top: 8, bottom: 8),
child: Builder(
builder: (context) => Observer(
builder: (context) => GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: addressListViewModel!.address.address));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(
addressListViewModel!.address.address,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color:
Theme.of(context).accentTextTheme!.headline2!.backgroundColor!),
),
),
),
Padding(
padding: EdgeInsets.only(left: 12),
child: copyImage,
)
],
Padding(
padding: EdgeInsets.only(left: 12),
child: copyImage,
)
],
),
),
),
),
),
)
)
],
);
}

View file

@ -2,7 +2,6 @@ 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';
@ -90,12 +89,12 @@ class ConnectionSyncPage extends BasePage {
final dismissibleRow = Slidable(
key: Key('${node.keyIndex}'),
startActionPane: _actionPane(context, node),
endActionPane: _actionPane(context, node),
startActionPane: _actionPane(context, node, isSelected),
endActionPane: _actionPane(context, node, isSelected),
child: nodeListRow,
);
return isSelected ? nodeListRow : dismissibleRow;
return dismissibleRow;
},
),
);
@ -124,33 +123,42 @@ class ConnectionSyncPage extends BasePage {
);
}
ActionPane _actionPane(BuildContext context, Node node) => ActionPane(
ActionPane _actionPane(BuildContext context, Node node, bool isSelected) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.3,
extentRatio: isSelected ? 0.3 : 0.6,
children: [
SlidableAction(
onPressed: (context) async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).remove_node,
alertContent: S.of(context).remove_node_message,
rightButtonText: S.of(context).remove,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false));
}) ??
false;
if (!isSelected)
SlidableAction(
onPressed: (context) async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).remove_node,
alertContent: S.of(context).remove_node_message,
rightButtonText: S.of(context).remove,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
await nodeListViewModel.delete(node);
}
},
backgroundColor: Colors.red,
if (confirmed) {
await nodeListViewModel.delete(node);
}
},
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: CupertinoIcons.delete,
label: S.of(context).delete,
),
SlidableAction(
onPressed: (_) => Navigator.of(context).pushNamed(Routes.newNode,
arguments: {'editingNode': node, 'isSelected': isSelected}),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
icon: CupertinoIcons.delete,
label: S.of(context).delete,
icon: Icons.edit,
label: S.of(context).edit,
),
],
);

View file

@ -27,26 +27,22 @@ class SecurityBackupPage extends BasePage {
child: Column(mainAxisSize: MainAxisSize.min, children: [
SettingsCellWithArrow(
title: S.current.show_keys,
handler: (_) => _securitySettingsViewModel.checkPinCodeRiquired()
? Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close(route: Routes.showKeys);
}
})
: Navigator.of(context).pushNamed(Routes.showKeys),
handler: (_) => Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close(route: Routes.showKeys);
}
}),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SettingsCellWithArrow(
title: S.current.create_backup,
handler: (_) => _securitySettingsViewModel.checkPinCodeRiquired()
? Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close(route: Routes.backup);
}
})
: Navigator.of(context).pushNamed(Routes.backup),
handler: (_) => Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close(route: Routes.backup);
}
}),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SettingsCellWithArrow(

View file

@ -1,6 +1,7 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:device_display_brightness/device_display_brightness.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
@ -9,6 +10,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
import 'package:cake_wallet/routes.dart';
class WalletKeysPage extends BasePage {
WalletKeysPage(this.walletKeysViewModel);
@ -18,6 +20,31 @@ class WalletKeysPage extends BasePage {
final WalletKeysViewModel walletKeysViewModel;
@override
Widget trailing(BuildContext context) => IconButton(
onPressed: () async {
// Get the current brightness:
final double brightness = await DeviceDisplayBrightness.getBrightness();
// ignore: unawaited_futures
DeviceDisplayBrightness.setBrightness(1.0);
await Navigator.pushNamed(
context,
Routes.fullscreenQR,
arguments: {
'qrData': (await walletKeysViewModel.url).toString(),
},
);
// ignore: unawaited_futures
DeviceDisplayBrightness.setBrightness(brightness);
},
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
icon: Image.asset(
'assets/images/qr_code_icon.png',
));
@override
Widget body(BuildContext context) {
return Column(

View file

@ -56,6 +56,8 @@ class AddressTextField extends StatelessWidget {
return Stack(
children: <Widget>[
TextFormField(
enableIMEPersonalizedLearning: false,
keyboardType: TextInputType.visiblePassword,
onFieldSubmitted: (_) => FocusScope.of(context).unfocus(),
enabled: isActive,
controller: controller,

View file

@ -0,0 +1,34 @@
import 'dart:async';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
part 'anonpay_transactions_store.g.dart';
class AnonpayTransactionsStore = AnonpayTransactionsStoreBase with _$AnonpayTransactionsStore;
abstract class AnonpayTransactionsStoreBase with Store {
AnonpayTransactionsStoreBase({
required this.anonpayInvoiceInfoSource,
}) : transactions = <AnonpayTransactionListItem>[] {
anonpayInvoiceInfoSource.watch().listen(
(_) async => await updateTransactionList(),
);
updateTransactionList();
}
Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource;
@observable
List<AnonpayTransactionListItem> transactions;
@action
Future<void> updateTransactionList() async {
transactions = anonpayInvoiceInfoSource.values
.map(
(transaction) => AnonpayTransactionListItem(transaction: transaction),
)
.toList();
}
}

View file

@ -1,8 +1,8 @@
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
part 'transaction_filter_store.g.dart';
@ -57,8 +57,8 @@ abstract class TransactionFilterStoreBase with Store {
@action
void changeEndDate(DateTime date) => endDate = date;
List<TransactionListItem> filtered({required List<TransactionListItem> transactions}) {
var _transactions = <TransactionListItem>[];
List<ActionListItem> filtered({required List<ActionListItem> transactions}) {
var _transactions = <ActionListItem>[];
final needToFilter = !displayAll ||
(startDate != null && endDate != null);
@ -67,16 +67,26 @@ abstract class TransactionFilterStoreBase with Store {
var allowed = true;
if (allowed && startDate != null && endDate != null) {
if(item is TransactionListItem){
allowed = (startDate?.isBefore(item.transaction.date) ?? false)
&& (endDate?.isAfter(item.transaction.date) ?? false);
}else if(item is AnonpayTransactionListItem){
allowed = (startDate?.isBefore(item.transaction.createdAt) ?? false)
&& (endDate?.isAfter(item.transaction.createdAt) ?? false);
}
}
if (allowed && (!displayAll)) {
if(item is TransactionListItem){
allowed = (displayOutgoing &&
item.transaction.direction ==
TransactionDirection.outgoing) ||
(displayIncoming &&
item.transaction.direction == TransactionDirection.incoming);
} else if(item is AnonpayTransactionListItem){
allowed = displayIncoming;
}
}
return allowed;

View file

@ -17,13 +17,14 @@ class ExceptionHandler {
static bool _hasError = false;
static const _coolDownDurationInDays = 7;
static void _saveException(String? error, StackTrace? stackTrace) async {
static void _saveException(String? error, StackTrace? stackTrace, {String? library}) async {
final appDocDir = await getApplicationDocumentsDirectory();
final file = File('${appDocDir.path}/error.txt');
final exception = {
"${DateTime.now()}": {
"Error": error,
"Error": "$error\n\n",
"Library": "$library\n\n",
"StackTrace": stackTrace.toString(),
}
};
@ -31,7 +32,7 @@ class ExceptionHandler {
const String separator = '''\n\n==========================================================
==========================================================\n\n''';
await file.writeAsString(
file.writeAsStringSync(
"$exception $separator",
mode: FileMode.append,
);
@ -75,7 +76,11 @@ class ExceptionHandler {
return;
}
_saveException(errorDetails.exception.toString(), errorDetails.stack);
_saveException(
errorDetails.exceptionAsString(),
errorDetails.stack,
library: errorDetails.library,
);
final sharedPrefs = await SharedPreferences.getInstance();
@ -131,9 +136,13 @@ class ExceptionHandler {
"errno = 54", // SocketException: Connection reset by peer
"errno = 57", // SocketException: Read failed (OS Error: Socket is not connected)
"errno = 60", // SocketException: Operation timed out
"errno = 65", // SocketException: No route to host
"errno = 103", // SocketException: Software caused connection abort
"errno = 104", // SocketException: Connection reset by peer
"errno = 110", // SocketException: Connection timed out
"HttpException: Connection reset by peer",
"HttpException: Connection closed before full header was received",
"HandshakeException: Connection terminated during handshake",
"PERMISSION_NOT_GRANTED",
];

View file

@ -3,8 +3,6 @@ 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,
@ -17,7 +15,8 @@ class ShareUtil {
required String fileName,
required BuildContext context,
}) async {
Share.shareXFiles(
const _mimeType = 'application/*';
await Share.shareXFiles(
<XFile>[
XFile(
filePath,
@ -29,7 +28,11 @@ class ShareUtil {
);
}
static Rect? _sharePosition(BuildContext context) {
static Rect _sharePosition(BuildContext context) {
if (!context.mounted) {
return Rect.zero;
}
final box = context.findRenderObject() as RenderBox?;
return box!.localToGlobal(Offset.zero) & box.size;

View file

@ -0,0 +1,186 @@
import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/anonpay/anonpay_request.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'anon_invoice_page_view_model.g.dart';
class AnonInvoicePageViewModel = AnonInvoicePageViewModelBase with _$AnonInvoicePageViewModel;
abstract class AnonInvoicePageViewModelBase with Store {
AnonInvoicePageViewModelBase(
this.anonPayApi,
this.address,
this.settingsStore,
this._wallet,
this._anonpayInvoiceInfoSource,
this.sharedPreferences,
this.pageOption,
) : receipientEmail = '',
receipientName = '',
description = '',
amount = '',
state = InitialExecutionState(),
selectedCurrency = walletTypeToCryptoCurrency(_wallet.type),
cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type) {
_getPreviousDonationLink();
_fetchLimits();
}
List<Currency> get currencies => [walletTypeToCryptoCurrency(_wallet.type), ...FiatCurrency.all];
final AnonPayApi anonPayApi;
final String address;
final SettingsStore settingsStore;
final WalletBase _wallet;
final Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
final SharedPreferences sharedPreferences;
final ReceivePageOption pageOption;
@observable
Currency selectedCurrency;
CryptoCurrency cryptoCurrency;
@observable
String receipientEmail;
@observable
String receipientName;
@observable
String description;
@observable
String amount;
@observable
ExecutionState state;
@computed
int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency);
@observable
double? minimum;
@observable
double? maximum;
@action
void selectCurrency(Currency currency) {
selectedCurrency = currency;
maximum = minimum = null;
if (currency is CryptoCurrency) {
cryptoCurrency = currency;
} else {
cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
}
_fetchLimits();
}
@action
Future<void> createInvoice() async {
state = IsExecutingState();
if (amount.isNotEmpty) {
final amountInCrypto = double.parse(amount);
if (minimum != null && amountInCrypto < minimum!) {
state = FailureState('Amount is too small');
return;
}
if (maximum != null && amountInCrypto > maximum!) {
state = FailureState('Amount is too big');
return;
}
}
final result = await anonPayApi.createInvoice(AnonPayRequest(
cryptoCurrency: cryptoCurrency,
address: address,
amount: amount.isEmpty ? null : amount,
description: description,
email: receipientEmail,
name: receipientName,
fiatEquivalent:
selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null,
));
_anonpayInvoiceInfoSource.add(result);
state = ExecutedSuccessfullyState(payload: result);
}
@action
void setRequestParams({
required String inputAmount,
required String inputName,
required String inputEmail,
required String inputDescription,
}) {
receipientName = inputName;
receipientEmail = inputEmail;
description = inputDescription;
amount = inputAmount;
}
@action
Future<void> generateDonationLink() async {
state = IsExecutingState();
final result = await anonPayApi.generateDonationLink(AnonPayRequest(
cryptoCurrency: cryptoCurrency,
address: address,
description: description,
email: receipientEmail,
name: receipientName,
));
await sharedPreferences.setString(PreferencesKey.clearnetDonationLink, result.clearnetUrl);
await sharedPreferences.setString(PreferencesKey.onionDonationLink, result.onionUrl);
state = ExecutedSuccessfullyState(payload: result);
}
Future<void> _fetchLimits() async {
final limit = await anonPayApi.fetchLimits(
cryptoCurrency: cryptoCurrency,
fiatCurrency: selectedCurrency is FiatCurrency ? selectedCurrency as FiatCurrency : null,
);
minimum = limit.min;
maximum = limit.max != null ? limit.max! / 4 : null;
}
@action
void reset() {
selectedCurrency = walletTypeToCryptoCurrency(_wallet.type);
cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
receipientEmail = '';
receipientName = '';
description = '';
amount = '';
_fetchLimits();
}
Future<void> _getPreviousDonationLink() async {
if (pageOption == ReceivePageOption.anonPayDonationLink) {
final donationLink = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
if (donationLink != null) {
final url = Uri.parse(donationLink);
url.queryParameters.forEach((key, value) {
if (key == 'name') receipientName = value;
if (key == 'email') receipientEmail = value;
if (key == 'description') description = Uri.decodeComponent(value);
});
}
}
}
}

View file

@ -0,0 +1,78 @@
import 'dart:async';
import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:url_launcher/url_launcher_string.dart';
part 'anonpay_details_view_model.g.dart';
class AnonpayDetailsViewModel = AnonpayDetailsViewModelBase with _$AnonpayDetailsViewModel;
abstract class AnonpayDetailsViewModelBase with Store {
AnonpayDetailsViewModelBase(
{required this.anonPayApi,
required AnonpayInvoiceInfo anonpayInvoiceInfo,
required this.settingsStore})
: items = ObservableList<StandartListItem>(),
invoiceDetail = anonpayInvoiceInfo {
_updateItems();
_updateInvoiceDetail();
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateInvoiceDetail());
}
final AnonPayApi anonPayApi;
final SettingsStore settingsStore;
final AnonpayInvoiceInfo invoiceDetail;
final ObservableList<StandartListItem> items;
Timer? timer;
@action
Future<void> _updateInvoiceDetail() async {
try {
final data = await anonPayApi.paymentStatus(invoiceDetail.invoiceId);
invoiceDetail.status = data.status;
_updateItems();
} catch (e) {
print(e.toString());
}
}
void _updateItems() {
final dateFormat = DateFormatter.withCurrentLocal();
items.clear();
items.addAll([
DetailsListStatusItem(title: S.current.status, value: invoiceDetail.status),
TradeDetailsListCardItem(
id: invoiceDetail.invoiceId,
createdAt: dateFormat.format(invoiceDetail.createdAt).toString(),
pair: (invoiceDetail.fiatAmount != null)
? "${invoiceDetail.fiatAmount} ${invoiceDetail.fiatEquiv ?? ''}"
: '${invoiceDetail.amountTo ?? ''} ${CryptoCurrency.fromFullName(invoiceDetail.coinTo).name.toUpperCase()}',
onTap: (BuildContext context) {
Clipboard.setData(ClipboardData(text: '${invoiceDetail.invoiceId}'));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
),
StandartListItem(title: S.current.trade_details_provider, value: invoiceDetail.provider)
]);
items.add(TrackTradeListItem(
title: 'Track',
value: invoiceDetail.clearnetStatusUrl,
onTap: () => launchUrlString(invoiceDetail.clearnetStatusUrl)));
}
}

View file

@ -0,0 +1,11 @@
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
class AnonpayTransactionListItem extends ActionListItem {
AnonpayTransactionListItem({required this.transaction});
final AnonpayInvoiceInfo transaction;
@override
DateTime get date => transaction.createdAt;
}

View file

@ -1,5 +1,7 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/balance.dart';
@ -48,7 +50,9 @@ abstract class DashboardViewModelBase with Store {
required this.transactionFilterStore,
required this.settingsStore,
required this.yatStore,
required this.ordersStore})
required this.ordersStore,
required this.anonpayTransactionsStore,
})
: isOutdatedElectrumWallet = false,
hasSellAction = false,
isEnabledSellAction = false,
@ -227,6 +231,11 @@ abstract class DashboardViewModelBase with Store {
List<OrderListItem> get orders => ordersStore.orders
.where((item) => item.order.walletId == wallet.id)
.toList();
@computed
List<AnonpayTransactionListItem> get anonpayTransactons => anonpayTransactionsStore.transactions
.where((item) => item.transaction.walletId == wallet.id)
.toList();
@computed
double get price => balanceViewModel.price;
@ -235,7 +244,7 @@ abstract class DashboardViewModelBase with Store {
List<ActionListItem> get items {
final _items = <ActionListItem>[];
_items.addAll(transactionFilterStore.filtered(transactions: transactions));
_items.addAll(transactionFilterStore.filtered(transactions: [...transactions, ...anonpayTransactons]));
_items.addAll(tradeFilterStore.filtered(trades: trades, wallet: wallet));
_items.addAll(orders);
@ -262,6 +271,8 @@ abstract class DashboardViewModelBase with Store {
TradeFilterStore tradeFilterStore;
AnonpayTransactionsStore anonpayTransactionsStore;
TransactionFilterStore transactionFilterStore;
Map<String, List<FilterItem>> filterItems;
@ -391,12 +402,11 @@ abstract class DashboardViewModelBase with Store {
void updateActions() {
hasExchangeAction = !isHaven;
isEnabledBuyAction = wallet.type != WalletType.haven
&& wallet.type != WalletType.monero;
hasBuyAction = !isMoneroOnly && !isHaven;
isEnabledBuyAction = wallet.type != WalletType.haven;
hasBuyAction = !isHaven;
isEnabledSellAction = wallet.type != WalletType.haven
&& wallet.type != WalletType.monero
&& wallet.type != WalletType.litecoin;
hasSellAction = !isMoneroOnly && !isHaven;
hasSellAction = !isHaven;
}
}

View file

@ -0,0 +1,34 @@
import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
part 'receive_option_view_model.g.dart';
class ReceiveOptionViewModel = ReceiveOptionViewModelBase with _$ReceiveOptionViewModel;
abstract class ReceiveOptionViewModelBase with Store {
ReceiveOptionViewModelBase(this._wallet, this.initialPageOption)
: selectedReceiveOption = initialPageOption ?? ReceivePageOption.mainnet,
_options = [] {
final walletType = _wallet.type;
_options =
walletType == WalletType.haven ? [ReceivePageOption.mainnet] : ReceivePageOption.values;
}
final WalletBase _wallet;
final ReceivePageOption? initialPageOption;
List<ReceivePageOption> _options;
@observable
ReceivePageOption selectedReceiveOption;
List<ReceivePageOption> get options => _options;
@action
void selectReceiveOption(ReceivePageOption option) {
selectedReceiveOption = option;
}
}

View file

@ -97,7 +97,6 @@ abstract class ExchangeViewModelBase with Store {
receiveAddress = '';
depositAddress = depositCurrency == wallet.currency
? wallet.walletAddresses.address : '';
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = wallet.type == WalletType.bitcoin ? 8 : 12;
provider = providersForCurrentPair().first;
final initialProvider = provider;
provider!.checkIsAvailable().then((bool isAvailable) {
@ -287,6 +286,7 @@ abstract class ExchangeViewModelBase with Store {
await _calculateBestRate();
}
_cryptoNumberFormat.maximumFractionDigits = depositMaxDigits;
depositAmount = _cryptoNumberFormat
.format(_enteredAmount / _bestRate)
@ -312,6 +312,7 @@ abstract class ExchangeViewModelBase with Store {
await _calculateBestRate();
}
_cryptoNumberFormat.maximumFractionDigits = receiveMaxDigits;
receiveAmount = _cryptoNumberFormat
.format(_bestRate * _enteredAmount)
@ -713,4 +714,8 @@ abstract class ExchangeViewModelBase with Store {
providerList = _allProviders;
}
}
int get depositMaxDigits => depositCurrency == CryptoCurrency.btc ? 8 : 12;
int get receiveMaxDigits => receiveCurrency == CryptoCurrency.btc ? 8 : 12;
}

View file

@ -5,6 +5,8 @@ import 'package:mobx/mobx.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
import 'node_list_view_model.dart';
part 'node_create_or_edit_view_model.g.dart';
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase
@ -77,14 +79,39 @@ abstract class NodeCreateOrEditViewModelBase with Store {
}
@action
Future<void> save({bool saveAsCurrent = false}) async {
void setPort (String val) => port = val;
@action
void setAddress (String val) => address = val;
@action
void setLogin (String val) => login = val;
@action
void setPassword (String val) => password = val;
@action
void setSSL (bool val) => useSSL = val;
@action
void setTrusted (bool val) => trusted = val;
@action
Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async {
final node = Node(
uri: uri,
type: _walletType,
login: login,
password: password,
useSSL: useSSL,
trusted: trusted);
try {
state = IsExecutingState();
final node =
Node(uri: uri, type: _walletType, login: login, password: password,
useSSL: useSSL, trusted: trusted);
await _nodeSource.add(node);
if (editingNode != null) {
await _nodeSource.put(editingNode.key, node);
} else {
await _nodeSource.add(node);
}
if (saveAsCurrent) {
_settingsStore.nodes[_walletType] = node;
}

View file

@ -81,21 +81,23 @@ abstract class OrderDetailsViewModelBase with Store {
: S.current.trade_details_fetching),
]);
if (order.provider != null) {
items.add(
StandartListItem(
title: 'Buy provider',
value: order.provider.title)
);
}
items.add(
StandartListItem(
title: 'Buy provider',
value: order.provider.title)
);
if (_provider!.trackUrl?.isNotEmpty ?? false) {
if (_provider?.trackUrl.isNotEmpty ?? false) {
final buildURL = _provider!.trackUrl + '${order.transferId}';
items.add(
TrackTradeListItem(
title: 'Track',
value: buildURL,
onTap: () => launch(buildURL)
onTap: () {
try {
launch(buildURL);
} catch (e) {}
}
)
);
}

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/store/templates/send_template_store.dart';
@ -26,7 +27,8 @@ abstract class SendTemplateViewModelBase with Store {
Output output;
Validator get amountValidator => AmountValidator(type: _wallet.type);
Validator get amountValidator =>
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
Validator get addressValidator => AddressValidator(type: _wallet.currency);

View file

@ -127,7 +127,8 @@ abstract class SendViewModelBase with Store {
CryptoCurrency get currency => _wallet.currency;
Validator get amountValidator => AmountValidator(type: _wallet.type);
Validator get amountValidator =>
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
Validator get allAmountValidator => AllAmountValidator();

View file

@ -21,6 +21,7 @@ import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart
import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
import 'package:url_launcher/url_launcher.dart';
part 'trade_details_view_model.g.dart';
class TradeDetailsViewModel = TradeDetailsViewModelBase with _$TradeDetailsViewModel;
@ -121,23 +122,26 @@ abstract class TradeDetailsViewModelBase with Store {
title: 'Track',
value: buildURL,
onTap: () {
launch(buildURL);
_launchUrl(buildURL);
}));
}
if (trade.provider == ExchangeProviderDescription.sideShift) {
final buildURL = 'https://sideshift.ai/orders/${trade.id.toString()}';
items.add(TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => launch(buildURL)));
items.add(
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
}
if (trade.provider == ExchangeProviderDescription.simpleSwap) {
final buildURL = 'https://simpleswap.io/exchange?id=${trade.id.toString()}';
items.add(TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => launch(buildURL)));
items.add(
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
}
if (trade.provider == ExchangeProviderDescription.trocador) {
final buildURL = 'https://trocador.app/en/checkout/${trade.id.toString()}';
items.add(TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => launch(buildURL)));
items.add(
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
items.add(StandartListItem(
title: '${trade.providerName} ${S.current.id.toUpperCase()}',
@ -148,4 +152,10 @@ abstract class TradeDetailsViewModelBase with Store {
title: '${trade.providerName} ${S.current.password}', value: trade.password ?? ''));
}
}
void _launchUrl(String url) {
try {
launch(url);
} catch (e) {}
}
}

View file

@ -14,7 +14,6 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/haven/haven.dart';
part 'transaction_details_view_model.g.dart';
@ -150,7 +149,11 @@ abstract class TransactionDetailsViewModelBase with Store {
items.add(BlockExplorerListItem(
title: S.current.view_in_block_explorer,
value: _explorerDescription(type),
onTap: () => launch(_explorerUrl(type, tx.id))));
onTap: () {
try {
launch(_explorerUrl(type, tx.id));
} catch (e) {}
}));
final description = transactionDescriptionBox.values.firstWhere(
(val) => val.id == transactionInfo.id,

View file

@ -5,6 +5,7 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cw_monero/api/wallet.dart' as monero_wallet;
part 'wallet_keys_view_model.g.dart';
@ -17,10 +18,11 @@ abstract class WalletKeysViewModelBase with Store {
wallet.type == WalletType.ethereum
? S.current.wallet_seed
: S.current.wallet_keys,
_wallet = wallet,
_restoreHeight = wallet.walletInfo.restoreHeight,
items = ObservableList<StandartListItem>() {
if (wallet.type == WalletType.monero) {
final keys = monero!.getKeys(wallet);
items.addAll([
if (keys['publicSpendKey'] != null)
StandartListItem(title: S.current.spend_key_public, value: keys['publicSpendKey']!),
@ -36,7 +38,6 @@ abstract class WalletKeysViewModelBase with Store {
if (wallet.type == WalletType.haven) {
final keys = haven!.getKeys(wallet);
items.addAll([
if (keys['publicSpendKey'] != null)
StandartListItem(title: S.current.spend_key_public, value: keys['publicSpendKey']!),
@ -62,4 +63,59 @@ abstract class WalletKeysViewModelBase with Store {
final ObservableList<StandartListItem> items;
final String title;
final WalletBase _wallet;
final int _restoreHeight;
Future<int?> currentHeight() async {
if (_wallet.type == WalletType.haven) {
return await haven!.getCurrentHeight();
}
if (_wallet.type == WalletType.monero) {
return monero_wallet.getCurrentHeight();
}
return null;
}
String get _path {
switch (_wallet.type) {
case WalletType.monero:
return 'monero_wallet:';
case WalletType.bitcoin:
return 'bitcoin_wallet:';
case WalletType.litecoin:
return 'litecoin_wallet:';
case WalletType.haven:
return 'haven_wallet:';
default:
throw Exception('Unexpected wallet type: ${_wallet.toString()}');
}
}
Future<String?> get restoreHeight async {
if (_restoreHeight != 0) {
return _restoreHeight.toString();
}
final _currentHeight = await currentHeight();
if (_currentHeight == null) {
return null;
}
return ((_currentHeight / 1000).floor() * 1000).toString();
}
Future<Map<String, String>> get _queryParams async {
final restoreHeightResult = await restoreHeight;
return {
'seed': _wallet.seed,
if (restoreHeightResult != null) ...{'height': restoreHeightResult}
};
}
Future<Uri> get url async {
return Uri(
path: _path,
queryParameters: await _queryParams,
);
}
}

View file

@ -485,7 +485,7 @@
"submit_request":"تقديم طلب",
"buy_alert_content":"لا ندعم حاليًا سوى شراء Bitcoin و Litecoin. لشراء Bitcoin أو Litecoin ، يرجى إنشاء محفظة Bitcoin أو Litecoin أو التبديل إليها.",
"buy_alert_content":"لا ندعم حاليًا سوى شراء Bitcoin و Litecoin و Monero. يرجى إنشاء محفظة Bitcoin أو Litecoin أو Monero أو التبديل إليها.",
"sell_alert_content":"نحن ندعم حاليًا بيع البيتكوين فقط. لبيع Bitcoin ، يرجى إنشاء أو التبديل إلى محفظة Bitcoin الخاصة بك.",
"outdated_electrum_wallet_description":"محافظ Bitcoin الجديدة التي تم إنشاؤها في Cake الآن سييد مكونة من 24 كلمة. من الضروري أن تقوم بإنشاء محفظة Bitcoin جديدة وتحويل جميع أموالك إلى المحفظة الجديدة المكونة من 24 كلمة ، والتوقف عن استخدام محافظ سييد مكونة من 12 كلمة. يرجى القيام بذلك على الفور لتأمين أموالك.",
@ -682,5 +682,18 @@
"send_to_this_address" : "أرسل ${currency} ${tag}إلى هذا العنوان",
"arrive_in_this_address" : "سيصل ${currency} ${tag}إلى هذا العنوان",
"do_not_send": "لا ترسل",
"error_dialog_content": "عفوًا ، لقد حصلنا على بعض الخطأ.\n\nيرجى إرسال تقرير التعطل إلى فريق الدعم لدينا لتحسين التطبيق."
"error_dialog_content": "عفوًا ، لقد حصلنا على بعض الخطأ.\n\nيرجى إرسال تقرير التعطل إلى فريق الدعم لدينا لتحسين التطبيق.",
"decimal_places_error": "عدد كبير جدًا من المنازل العشرية",
"edit_node": "تحرير العقدة",
"invoice_details": "تفاصيل الفاتورة",
"donation_link_details": "تفاصيل رابط التبرع",
"anonpay_description": "توليد ${type}. يمكن للمستلم ${method} بأي عملة مشفرة مدعومة ، وستتلقى أموالاً في هذه",
"create_invoice": "إنشاء فاتورة",
"create_donation_link": "إنشاء رابط التبرع",
"optional_email_hint": "البريد الإلكتروني إخطار المدفوع لأمره الاختياري",
"optional_description": "وصف اختياري",
"optional_name": "اسم المستلم الاختياري",
"clearnet_link": "رابط Clearnet",
"onion_link": "رابط البصل",
"sell_monero_com_alert_content": "بيع Monero غير مدعوم حتى الآن"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "изпращане на заявка",
"buy_alert_content" : "В момента поддържаме покупката само на Bitcoin и Litecoin. За да закупите Bitcoin или Litecoin, създайте или изберете своя Bitcoin или Litecoin портфейл.",
"buy_alert_content" : "Понастоящем поддържаме само закупуване на Bitcoin, Litecoin и Monero. Моля, създайте или преминете към своя портфейл Bitcoin, Litecoin или Monero.",
"sell_alert_content": "В момента поддържаме само продажбата на Bitcoin. За да продавате Bitcoin, създайте или изберете своя Bitcoin портфейл.",
"outdated_electrum_wallet_description" : "Нови Bitcoin портфейли, създадени в Cake, сега имат seed от 24 думи. Трябва да създадете нов Bitcoin адрес и да прехвърлите всичките си средства в него и веднага да спрете използването на стари портфейли. Моля, напревете това незабавно, за да подсигурите средствата си.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Send ${currency} ${tag}to this address",
"arrive_in_this_address" : "${currency} ${tag}ще отидат на този адрес",
"do_not_send": "Не изпращай",
"error_dialog_content": "Получихме грешка.\n\nМоля, изпратете доклада до нашия отдел поддръжка, за да подобрим приложението."
"error_dialog_content": "Получихме грешка.\n\nМоля, изпратете доклада до нашия отдел поддръжка, за да подобрим приложението.",
"decimal_places_error": "Твърде много знаци след десетичната запетая",
"edit_node": "Редактиране на възел",
"invoice_details": "IДанни за фактура",
"donation_link_details": "Подробности за връзката за дарение",
"anonpay_description": "Генерирайте ${type}. Получателят може да ${method} с всяка поддържана криптовалута и вие ще получите средства в този портфейл.",
"create_invoice": "Създайте фактура",
"create_donation_link": "Създайте връзка за дарение",
"optional_email_hint": "Незадължителен имейл за уведомяване на получателя",
"optional_description": "Описание по избор",
"optional_name": "Незадължително име на получател",
"clearnet_link": "Clearnet връзка",
"onion_link": "Лукова връзка",
"sell_monero_com_alert_content": "Продажбата на Monero все още не се поддържа"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "odeslat požadavek",
"buy_alert_content" : "V současné době podporujeme nákup pouze Bitcoinu a Litecoinu. Pro nákup Bitcoinu, nebo Litecoinu si prosím vytvořte Bitcoinovou, nebo Litecoinovou peněženku, nebo se do ní přepněte.",
"buy_alert_content" : "V současné době podporujeme pouze nákup Bitcoinů, Litecoinů a Monero. Vytvořte nebo přepněte na svou peněženku Bitcoinů, Litecoinů nebo Monero.",
"sell_alert_content": "V současné době podporujeme pouze prodej Bitcoinu. Pro prodej Bitcoinu si prosím vytvořte Bitcoinovou peněženku, nebo se do ní přepněte.",
"outdated_electrum_wallet_description" : "Nové Bitcoinové peněženky vytvořené v Cake mají nyní seed se 24 slovy. Je třeba si vytvořit novou Bitcoinovou peněženku se 24 slovy, převést na ni všechny prostředky a přestat používat seed se 12 slovy. Prosím udělejte to hned pro zabezpečení svých prostředků.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Poslat ${currency} ${tag}na tuto adresu",
"arrive_in_this_address" : "${currency} ${tag}přijde na tuto adresu",
"do_not_send": "Neodesílat",
"error_dialog_content": "Nastala chyba.\n\nProsím odešlete zprávu o chybě naší podpoře, aby mohli zajistit opravu."
"error_dialog_content": "Nastala chyba.\n\nProsím odešlete zprávu o chybě naší podpoře, aby mohli zajistit opravu.",
"decimal_places_error": "Příliš mnoho desetinných míst",
"edit_node": "Upravit uzel",
"invoice_details": "detaily faktury",
"donation_link_details": "Podrobnosti odkazu na darování",
"anonpay_description": "Vygenerujte ${type}. Příjemce může ${method} s jakoukoli podporovanou kryptoměnou a vy obdržíte prostředky v této peněžence.",
"create_invoice": "Vytvořit fakturu",
"create_donation_link": "Vytvořit odkaz na darování",
"optional_email_hint": "Volitelný e-mail s upozorněním na příjemce platby",
"optional_description": "Volitelný popis",
"optional_name": "Volitelné jméno příjemce",
"clearnet_link": "Odkaz na Clearnet",
"onion_link": "Cibulový odkaz",
"sell_monero_com_alert_content": "Prodej Monero zatím není podporován"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "Eine Anfrage stellen",
"buy_alert_content" : "Derzeit unterstützen wir nur den Kauf von Bitcoin und Litecoin. Um Bitcoin oder Litecoin zu kaufen, erstellen oder wechseln Sie bitte zu Ihrem Bitcoin- oder Litecoin-Wallet.",
"buy_alert_content" : "Derzeit unterstützen wir nur den Kauf von Bitcoin, Litecoin und Monero. Bitte erstellen oder wechseln Sie zu Ihrer Bitcoin-, Litecoin- oder Monero-Wallet.",
"sell_alert_content": "Wir unterstützen derzeit nur den Verkauf von Bitcoin. Um Bitcoin zu verkaufen, erstellen Sie bitte Ihre Bitcoin-Wallet oder wechseln Sie zu ihr.",
"outdated_electrum_wallet_description" : "Neue Bitcoin-Wallets, die in Cake erstellt wurden, haben jetzt einen 24-Wort-Seed. Sie müssen eine neue Bitcoin-Wallet erstellen, Ihr gesamtes Geld in die neue 24-Wort-Wallet überweisen und keine Wallet mit einem 12-Wort-Seed mehr verwenden. Bitte tun Sie dies sofort, um Ihr Geld zu sichern.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Senden Sie ${currency} ${tag}an diese Adresse",
"arrive_in_this_address" : "${currency} ${tag}wird an dieser Adresse ankommen",
"do_not_send": "Nicht senden",
"error_dialog_content": "Hoppla, wir haben einen Fehler.\n\nBitte senden Sie den Absturzbericht an unser Support-Team, um die Anwendung zu verbessern."
"error_dialog_content": "Hoppla, wir haben einen Fehler.\n\nBitte senden Sie den Absturzbericht an unser Support-Team, um die Anwendung zu verbessern.",
"decimal_places_error": "Zu viele Nachkommastellen",
"edit_node": "Knoten bearbeiten",
"invoice_details": "Rechnungs-Details",
"donation_link_details": "Details zum Spendenlink",
"anonpay_description": "Generieren Sie ${type}. Der Empfänger kann ${method} mit jeder unterstützten Kryptowährung verwenden, und Sie erhalten Geld in dieser Brieftasche.",
"create_invoice": "Rechnung erstellen",
"create_donation_link": "Spendenlink erstellen",
"optional_email_hint": "Optionale Benachrichtigungs-E-Mail für den Zahlungsempfänger",
"optional_description": "Optionale Beschreibung",
"optional_name": "Optionaler Empfängername",
"clearnet_link": "Clearnet-Link",
"onion_link": "Zwiebel-Link",
"sell_monero_com_alert_content": "Der Verkauf von Monero wird noch nicht unterstützt"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "submit a request",
"buy_alert_content" : "Currently we only support the purchase of Bitcoin and Litecoin. To buy Bitcoin or Litecoin, please create or switch to your Bitcoin or Litecoin wallet.",
"buy_alert_content" : "Currently we only support the purchase of Bitcoin, Litecoin, and Monero. Please create or switch to your Bitcoin, Litecoin, or Monero wallet.",
"sell_alert_content": "We currently only support the sale of Bitcoin. To sell Bitcoin, please create or switch to your Bitcoin wallet.",
"outdated_electrum_wallet_description" : "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Send ${currency} ${tag}to this address",
"arrive_in_this_address" : "${currency} ${tag}will arrive in this address",
"do_not_send": "Don't send",
"error_dialog_content": "Oops, we got some error.\n\nPlease send the crash report to our support team to make the application better."
"error_dialog_content": "Oops, we got some error.\n\nPlease send the crash report to our support team to make the application better.",
"invoice_details": "Invoice details",
"donation_link_details": "Donation link details",
"anonpay_description": "Generate ${type}. The recipient can ${method} with any supported cryptocurrency, and you will receive funds in this wallet.",
"create_invoice": "Create invoice",
"create_donation_link": "Create donation link",
"optional_email_hint": "Optional payee notification email",
"optional_description": "Optional description",
"optional_name": "Optional recipient name",
"clearnet_link": "Clearnet link",
"onion_link": "Onion link",
"decimal_places_error": "Too many decimal places",
"edit_node": "Edit Node",
"sell_monero_com_alert_content": "Selling Monero is not supported yet"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "presentar una solicitud",
"buy_alert_content" : "Actualmente solo apoyamos la compra de Bitcoin y Litecoin. Para comprar Bitcoin o Litecoin, cree o cambie a su billetera Bitcoin o Litecoin.",
"buy_alert_content" : "Actualmente solo admitimos la compra de Bitcoin, Litecoin y Monero. Cree o cambie a su billetera Bitcoin, Litecoin o Monero.",
"sell_alert_content": "Actualmente solo admitimos la venta de Bitcoin. Para vender Bitcoin, cree o cambie a su billetera Bitcoin.",
"outdated_electrum_wallet_description" : "Las nuevas carteras de Bitcoin creadas en Cake ahora tienen una semilla de 24 palabras. Es obligatorio que cree una nueva billetera de Bitcoin y transfiera todos sus fondos a la nueva billetera de 24 palabras, y deje de usar billeteras con una semilla de 12 palabras. Haga esto de inmediato para asegurar sus fondos.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Enviar ${currency} ${tag}a esta dirección",
"arrive_in_this_address" : "${currency} ${tag}llegará a esta dirección",
"do_not_send": "no enviar",
"error_dialog_content": "Vaya, tenemos un error.\n\nEnvíe el informe de bloqueo a nuestro equipo de soporte para mejorar la aplicación."
"error_dialog_content": "Vaya, tenemos un error.\n\nEnvíe el informe de bloqueo a nuestro equipo de soporte para mejorar la aplicación.",
"decimal_places_error": "Demasiados lugares decimales",
"edit_node": "Edit Node",
"invoice_details": "Detalles de la factura",
"donation_link_details": "Detalles del enlace de donación",
"anonpay_description": "Genera ${type}. El destinatario puede ${method} con cualquier criptomoneda admitida, y recibirá fondos en esta billetera.",
"create_invoice": "Crear factura",
"create_donation_link": "Crear enlace de donación",
"optional_email_hint": "Correo electrónico de notificación del beneficiario opcional",
"optional_description": "Descripción opcional",
"optional_name": "Nombre del destinatario opcional",
"clearnet_link": "enlace Clearnet",
"onion_link": "Enlace de cebolla",
"sell_monero_com_alert_content": "Aún no se admite la venta de Monero"
}

View file

@ -483,7 +483,7 @@
"submit_request" : "soumettre une requête",
"buy_alert_content" : "Pour le moment nous ne supportons que l'achat de Bitcoin et Litecoin. Pour acheter du Bitcoin ou du Litecoin, merci de créer ou de sélectionner votre portefeuille (wallet) Bitcoin ou Litecoin.",
"buy_alert_content" : "Actuellement, nous ne prenons en charge que l'achat de Bitcoin, Litecoin et Monero. Veuillez créer ou basculer vers votre portefeuille Bitcoin, Litecoin ou Monero.",
"sell_alert_content": "Pour le moment nous ne supportons que la vente de Bitcoin. Pour vendre du Bitcoin, merci de créer ou de sélectionner votre portefeuille (wallet) Bitcoin.",
"outdated_electrum_wallet_description" : "Les nouveaux portefeuilles (wallets) Bitcoin créés dans Cake ont dorénavant une phrase secrète (seed) de 24 mots. Il est impératif que vous créiez un nouveau portefeuille Bitcoin, que vous y transfériez tous vos fonds puis que vous cessiez d'utiliser le portefeuille avec une phrase secrète de 12 mots. Merci de faire cela immédiatement pour assurer la sécurité de vos avoirs.",
@ -682,5 +682,18 @@
"send_to_this_address" : "Envoyez ${currency} ${tag}à cette adresse",
"arrive_in_this_address" : "${currency} ${tag}arrivera à cette adresse",
"do_not_send": "N'envoyez pas",
"error_dialog_content": "Oups, nous avons eu une erreur.\n\nVeuillez envoyer le rapport de plantage à notre équipe d'assistance pour améliorer l'application."
"error_dialog_content": "Oups, nous avons eu une erreur.\n\nVeuillez envoyer le rapport de plantage à notre équipe d'assistance pour améliorer l'application.",
"decimal_places_error": "Trop de décimales",
"edit_node": "Modifier le nœud",
"invoice_details": "Détails de la facture",
"donation_link_details": "Détails du lien de don",
"anonpay_description": "Générez ${type}. Le destinataire peut ${method} avec n'importe quelle crypto-monnaie prise en charge, et vous recevrez des fonds dans ce portefeuille.",
"create_invoice": "Créer une facture",
"create_donation_link": "Créer un lien de don",
"optional_email_hint": "E-mail de notification du bénéficiaire facultatif",
"optional_description": "Descriptif facultatif",
"optional_name": "Nom du destinataire facultatif",
"clearnet_link": "Lien Clearnet",
"onion_link": "Lien d'oignon",
"sell_monero_com_alert_content": "La vente de Monero n'est pas encore prise en charge"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "एक अनुरोध सबमिट करें",
"buy_alert_content" : "वर्तमान में हम केवल बिटकॉइन और लिटकोइन की खरीद का समर्थन करते हैं। बिटकॉइन या लाइटकोइन खरीदने के लिए, कृपया अपना बिटकॉइन या लाइटकोइन वॉलेट बनाएं या स्विच करें।",
"buy_alert_content" : "वर्तमान में हम केवल बिटकॉइन, लाइटकॉइन और मोनेरो की खरीद का समर्थन करते हैं। कृपया अपना बिटकॉइन, लाइटकॉइन, या मोनेरो वॉलेट बनाएं या स्विच करें।",
"sell_alert_content": "हम वर्तमान में केवल बिटकॉइन की बिक्री का समर्थन करते हैं। बिटकॉइन बेचने के लिए, कृपया अपना बिटकॉइन वॉलेट बनाएं या उसमें स्विच करें।",
"outdated_electrum_wallet_description" : "केक में बनाए गए नए बिटकॉइन वॉलेट में अब 24-शब्द का बीज है। यह अनिवार्य है कि आप एक नया बिटकॉइन वॉलेट बनाएं और अपने सभी फंड को नए 24-शब्द वाले वॉलेट में स्थानांतरित करें, और 12-शब्द बीज वाले वॉलेट का उपयोग करना बंद करें। कृपया अपने धन को सुरक्षित करने के लिए इसे तुरंत करें।",
@ -684,5 +684,18 @@
"send_to_this_address" : "इस पते पर ${currency} ${tag}भेजें",
"arrive_in_this_address" : "${currency} ${tag}इस पते पर पहुंचेंगे",
"do_not_send": "मत भेजो",
"error_dialog_content": "ओह, हमसे कुछ गड़बड़ी हुई है.\n\nएप्लिकेशन को बेहतर बनाने के लिए कृपया क्रैश रिपोर्ट हमारी सहायता टीम को भेजें।"
"error_dialog_content": "ओह, हमसे कुछ गड़बड़ी हुई है.\n\nएप्लिकेशन को बेहतर बनाने के लिए कृपया क्रैश रिपोर्ट हमारी सहायता टीम को भेजें।",
"decimal_places_error": "बहुत अधिक दशमलव स्थान",
"edit_node": "नोड संपादित करें",
"invoice_details": "चालान विवरण",
"donation_link_details": "दान लिंक विवरण",
"anonpay_description": "${type} उत्पन्न करें। प्राप्तकर्ता किसी भी समर्थित क्रिप्टोकरेंसी के साथ ${method} कर सकता है, और आपको इस वॉलेट में धन प्राप्त होगा।",
"create_invoice": "इनवॉयस बनाएँ",
"create_donation_link": "दान लिंक बनाएं",
"optional_email_hint": "वैकल्पिक प्राप्तकर्ता सूचना ईमेल",
"optional_description": "वैकल्पिक विवरण",
"optional_name": "वैकल्पिक प्राप्तकर्ता नाम",
"clearnet_link": "क्लियरनेट लिंक",
"onion_link": "प्याज का लिंक",
"sell_monero_com_alert_content": "मोनेरो बेचना अभी तक समर्थित नहीं है"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "podnesi zahtjev",
"buy_alert_content" : "Trenutno podržavamo samo kupnju Bitcoina i Litecoina. Da biste kupili Bitcoin ili Litecoin, stvorite ili pređite na svoj Bitcoin ili Litecoin novčanik.",
"buy_alert_content" : "Trenutno podržavamo samo kupnju Bitcoina, Litecoina i Monera. Izradite ili prijeđite na svoj Bitcoin, Litecoin ili Monero novčanik.",
"sell_alert_content": "Trenutno podržavamo samo prodaju Bitcoina. Da biste prodali Bitcoin, stvorite ili prijeđite na svoj Bitcoin novčanik.",
"outdated_electrum_wallet_description" : "Novi Bitcoin novčanici stvoreni u Cakeu sada imaju sjeme od 24 riječi. Obavezno je stvoriti novi Bitcoin novčanik i prenijeti sva svoja sredstva u novi novčanik od 24 riječi te prestati koristiti novčanike s sjemenkom od 12 riječi. Učinite to odmah kako biste osigurali svoja sredstva.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Pošaljite ${currency} ${tag}na ovu adresu",
"arrive_in_this_address" : "${currency} ${tag}će stići na ovu adresu",
"do_not_send": "Ne šalji",
"error_dialog_content": "Ups, imamo grešku.\n\nPošaljite izvješće o padu našem timu za podršku kako bismo poboljšali aplikaciju."
"error_dialog_content": "Ups, imamo grešku.\n\nPošaljite izvješće o padu našem timu za podršku kako bismo poboljšali aplikaciju.",
"decimal_places_error": "Previše decimalnih mjesta",
"edit_node": "Uredi čvor",
"invoice_details": "Podaci o fakturi",
"donation_link_details": "Detalji veza za donacije",
"anonpay_description": "Generiraj ${type}. Primatelj može ${method} s bilo kojom podržanom kriptovalutom, a vi ćete primiti sredstva u ovaj novčanik.",
"create_invoice": "Izradite fakturu",
"create_donation_link": "Izradi poveznicu za donaciju",
"optional_email_hint": "Neobavezna e-pošta za obavijest primatelja",
"optional_description": "Opcijski opis",
"optional_name": "Izborno ime primatelja",
"clearnet_link": "Clearnet veza",
"onion_link": "Poveznica luka",
"sell_monero_com_alert_content": "Prodaja Monera još nije podržana"
}

View file

@ -472,7 +472,7 @@
"submit_request" : "kirim permintaan",
"buy_alert_content" : "Saat ini kami hanya mendukung pembelian Bitcoin dan Litecoin. Untuk membeli Bitcoin atau Litecoin, silakan buat atau beralih ke dompet Bitcoin atau Litecoin Anda.",
"buy_alert_content" : "Saat ini kami hanya mendukung pembelian Bitcoin, Litecoin, dan Monero. Harap buat atau alihkan ke dompet Bitcoin, Litecoin, atau Monero Anda.",
"sell_alert_content": "Saat ini kami hanya mendukung penjualan Bitcoin. Untuk menjual Bitcoin, silakan buat atau beralih ke dompet Bitcoin Anda.",
"outdated_electrum_wallet_description" : "Dompet Bitcoin baru yang dibuat di Cake sekarang memiliki biji semai 24 kata. Wajib bagi Anda untuk membuat dompet Bitcoin baru dan mentransfer semua dana Anda ke dompet 24 kata baru, dan berhenti menggunakan dompet dengan biji semai 12 kata. Silakan lakukan ini segera untuk mengamankan dana Anda.",
@ -666,5 +666,18 @@
"unmatched_currencies": "Mata uang dompet Anda saat ini tidak cocok dengan yang ditandai QR",
"orbot_running_alert": "Pastikan Orbot sedang berjalan sebelum menghubungkan ke node ini.",
"contact_list_contacts": "Kontak",
"contact_list_wallets": "Dompet Saya"
"contact_list_wallets": "Dompet Saya",
"decimal_places_error": "Terlalu banyak tempat desimal",
"edit_node": "Sunting Node",
"invoice_details": "Detail faktur",
"donation_link_details": "Detail tautan donasi",
"anonpay_description": "Hasilkan ${type}. Penerima dapat ${method} dengan cryptocurrency apa pun yang didukung, dan Anda akan menerima dana di dompet ini.",
"create_invoice": "Buat faktur",
"create_donation_link": "Buat tautan donasi",
"optional_email_hint": "Email pemberitahuan penerima pembayaran opsional",
"optional_description": "Deskripsi opsional",
"optional_name": "Nama penerima opsional",
"clearnet_link": "Tautan clearnet",
"onion_link": "Tautan bawang",
"sell_monero_com_alert_content": "Menjual Monero belum didukung"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "invia una richiesta",
"buy_alert_content" : "Attualmente supportiamo solo l'acquisto di Bitcoin e Litecoin. Per acquistare Bitcoin o Litecoin, crea o passa al tuo portafoglio Bitcoin o Litecoin.",
"buy_alert_content" : "Attualmente supportiamo solo l'acquisto di Bitcoin, Litecoin e Monero. Crea o passa al tuo portafoglio Bitcoin, Litecoin o Monero.",
"sell_alert_content": "Al momento supportiamo solo la vendita di Bitcoin. Per vendere Bitcoin, crea o passa al tuo portafoglio Bitcoin.",
"outdated_electrum_wallet_description" : "I nuovi portafogli Bitcoin creati in Cake ora hanno un seme di 24 parole. È obbligatorio creare un nuovo portafoglio Bitcoin e trasferire tutti i fondi nel nuovo portafoglio di 24 parole e smettere di usare portafogli con un seme di 12 parole. Ti preghiamo di farlo immediatamente per proteggere i tuoi fondi.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Invia ${currency} ${tag}a questo indirizzo",
"arrive_in_this_address" : "${currency} ${tag}arriverà a questo indirizzo",
"do_not_send": "Non inviare",
"error_dialog_content": "Ups, imamo grešku.\n\nPošaljite izvješće o padu našem timu za podršku kako bismo poboljšali aplikaciju."
"error_dialog_content": "Spiacenti, abbiamo riscontrato un errore.\n\nSi prega di inviare il rapporto sull'arresto anomalo al nostro team di supporto per migliorare l'applicazione.",
"decimal_places_error": "Troppe cifre decimali",
"edit_node": "Modifica nodo",
"invoice_details": "Dettagli della fattura",
"donation_link_details": "Dettagli del collegamento alla donazione",
"anonpay_description": "Genera ${type}. Il destinatario può ${method} con qualsiasi criptovaluta supportata e riceverai fondi in questo portafoglio.",
"create_invoice": "Crea fattura",
"create_donation_link": "Crea un link per la donazione",
"optional_email_hint": "Email di notifica del beneficiario facoltativa",
"optional_description": "Descrizione facoltativa",
"optional_name": "Nome del destinatario facoltativo",
"clearnet_link": "Collegamento Clearnet",
"onion_link": "Collegamento a cipolla",
"sell_monero_com_alert_content": "La vendita di Monero non è ancora supportata"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "リクエストを送信する",
"buy_alert_content" : "現在、ビットコインとライトコインの購入のみをサポートしています。 ビットコインまたはライトコインを購入するには、ビットコインまたはライトコインのウォレットを作成するか、ウォレットに切り替えてください。",
"buy_alert_content" : "現在、ビットコイン、ライトコイン、モネロの購入のみをサポートしています。 Bitcoin、Litecoin、または Monero ウォレットを作成するか、切り替えてください。",
"sell_alert_content": "現在、ビットコインの販売のみをサポートしています。ビットコインを販売するには、ビットコインウォレットを作成するか切り替えてください。",
"outdated_electrum_wallet_description" : "Cakeで作成された新しいビットコインウォレットには、24ワードのシードがあります。 新しいビットコインウォレットを作成し、すべての資金を新しい24ワードのウォレットに転送し、12ワードのシードを持つウォレットの使用を停止することが必須です。 あなたの資金を確保するためにこれをすぐに行ってください。",
@ -684,5 +684,18 @@
"send_to_this_address" : "${currency} ${tag}をこのアドレスに送金",
"arrive_in_this_address" : "${currency} ${tag}はこの住所に到着します",
"do_not_send": "送信しない",
"error_dialog_content": "Spiacenti, abbiamo riscontrato un errore.\n\nSi prega di inviare il rapporto sull'arresto anomalo al nostro team di supporto per migliorare l'applicazione."
"error_dialog_content": "エラーが発生しました。\n\nアプリケーションを改善するために、クラッシュ レポートをサポート チームに送信してください。",
"decimal_places_error": "小数点以下の桁数が多すぎる",
"edit_node": "ノードを編集",
"invoice_details": "請求の詳細",
"donation_link_details": "寄付リンクの詳細",
"anonpay_description": "${type} を生成します。受取人はサポートされている任意の暗号通貨で ${method} でき、あなたはこのウォレットで資金を受け取ります。",
"create_invoice": "請求書の作成",
"create_donation_link": "寄付リンクを作成",
"optional_email_hint": "オプションの受取人通知メール",
"optional_description": "オプションの説明",
"optional_name": "オプションの受信者名",
"clearnet_link": "クリアネット リンク",
"onion_link": "オニオンリンク",
"sell_monero_com_alert_content": "モネロの販売はまだサポートされていません"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "요청을 제출",
"buy_alert_content" : "현재 우리는 비트 코인과 라이트 코인 구매 만 지원합니다. 비트 코인 또는 라이트 코인을 구매하려면 비트 코인 또는 라이트 코인 지갑을 생성하거나 전환하십시오.",
"buy_alert_content" : "현재 우리는 Bitcoin, Litecoin 및 Monero 구매만 지원합니다. Bitcoin, Litecoin 또는 Monero 지갑을 생성하거나 전환하십시오.",
"sell_alert_content": "현재 비트코인 ​​판매만 지원합니다. 비트코인을 판매하려면 비트코인 ​​지갑을 생성하거나 전환하세요.",
"outdated_electrum_wallet_description" : "Cake에서 생성 된 새로운 비트 코인 지갑에는 이제 24 단어 시드가 있습니다. 새로운 비트 코인 지갑을 생성하고 모든 자금을 새로운 24 단어 지갑으로 이체하고 12 단어 시드가있는 지갑 사용을 중지해야합니다. 자금을 확보하려면 즉시이 작업을 수행하십시오.",
@ -684,5 +684,18 @@
"send_to_this_address" : "이 주소로 ${currency} ${tag}송금",
"arrive_in_this_address" : "${currency} ${tag}이(가) 이 주소로 도착합니다",
"do_not_send": "보내지 마세요",
"error_dialog_content": "죄송합니다. 오류가 발생했습니다.\n\n응용 프로그램을 개선하려면 지원 팀에 충돌 보고서를 보내주십시오."
"error_dialog_content": "죄송합니다. 오류가 발생했습니다.\n\n응용 프로그램을 개선하려면 지원 팀에 충돌 보고서를 보내주십시오.",
"decimal_places_error": "소수점 이하 자릿수가 너무 많습니다.",
"edit_node": "노드 편집",
"invoice_details": "인보이스 세부정보",
"donation_link_details": "기부 링크 세부정보",
"anonpay_description": "${type} 생성. 수신자는 지원되는 모든 암호화폐로 ${method}할 수 있으며 이 지갑에서 자금을 받게 됩니다.",
"create_invoice": "인보이스 생성",
"create_donation_link": "기부 링크 만들기",
"optional_email_hint": "선택적 수취인 알림 이메일",
"optional_description": "선택적 설명",
"optional_name": "선택적 수신자 이름",
"clearnet_link": "클리어넷 링크",
"onion_link": "양파 링크",
"sell_monero_com_alert_content": "지원되지 않습니다."
}

View file

@ -485,7 +485,7 @@
"submit_request" : "တောင်းဆိုချက်တစ်ခုတင်ပြပါ။",
"buy_alert_content" : "လောလောဆယ် ကျွန်ုပ်တို့သည် Bitcoin နှင့် Litecoin ဝယ်ယူမှုကိုသာ ပံ့ပိုးပေးပါသည်။ Bitcoin သို့မဟုတ် Litecoin ဝယ်ယူရန်၊ သင်၏ Bitcoin သို့မဟုတ် Litecoin ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။",
"buy_alert_content" : "လောလောဆယ်တွင် ကျွန်ုပ်တို့သည် Bitcoin၊ Litecoin နှင့် Monero တို့ကိုသာ ဝယ်ယူမှုကို ပံ့ပိုးပေးပါသည်။ သင်၏ Bitcoin၊ Litecoin သို့မဟုတ် Monero ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။",
"sell_alert_content" : "ကျွန်ုပ်တို့သည် လက်ရှိတွင် Bitcoin ရောင်းချခြင်းကိုသာ ပံ့ပိုးပေးပါသည်။ Bitcoin ရောင်းချရန်၊ သင်၏ Bitcoin ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။",
"outdated_electrum_wallet_description" : "ယခု Cake တွင်ဖန်တီးထားသော Bitcoin ပိုက်ဆံအိတ်အသစ်တွင် စကားလုံး 24 မျိုးရှိသည်။ Bitcoin ပိုက်ဆံအိတ်အသစ်တစ်ခုကို ဖန်တီးပြီး သင့်ငွေအားလုံးကို 24 စကားလုံးပိုက်ဆံအိတ်အသစ်သို့ လွှဲပြောင်းပြီး 12 စကားလုံးမျိုးစေ့ဖြင့် ပိုက်ဆံအိတ်များကို အသုံးပြုခြင်းကို ရပ်တန့်ရန် မဖြစ်မနေလိုအပ်ပါသည်။ သင့်ရန်ပုံငွေများကို လုံခြုံစေရန်အတွက် ၎င်းကိုချက်ချင်းလုပ်ဆောင်ပါ။",
@ -684,5 +684,18 @@
"send_to_this_address" : "ဤလိပ်စာသို့ ${currency} ${tag}သို့ ပို့ပါ။",
"arrive_in_this_address" : "${currency} ${tag}ဤလိပ်စာသို့ ရောက်ရှိပါမည်။",
"do_not_send": "မပို့ပါနှင့်",
"error_dialog_content": "အိုး၊ ကျွန်ုပ်တို့တွင် အမှားအယွင်းအချို့ရှိသည်။\n\nအပလီကေးရှင်းကို ပိုမိုကောင်းမွန်စေရန်အတွက် ပျက်စီးမှုအစီရင်ခံစာကို ကျွန်ုပ်တို့၏ပံ့ပိုးကူညီရေးအဖွဲ့ထံ ပေးပို့ပါ။"
"error_dialog_content": "အိုး၊ ကျွန်ုပ်တို့တွင် အမှားအယွင်းအချို့ရှိသည်။\n\nအပလီကေးရှင်းကို ပိုမိုကောင်းမွန်စေရန်အတွက် ပျက်စီးမှုအစီရင်ခံစာကို ကျွန်ုပ်တို့၏ပံ့ပိုးကူညီရေးအဖွဲ့ထံ ပေးပို့ပါ။",
"decimal_places_error": "ဒဿမနေရာများ များလွန်းသည်။",
"edit_node": "Node ကို တည်းဖြတ်ပါ။",
"invoice_details": "ပြေစာအသေးစိတ်",
"donation_link_details": "လှူဒါန်းရန်လင့်ခ်အသေးစိတ်",
"anonpay_description": "${type} ကို ဖန်တီးပါ။ လက်ခံသူက ${method} ကို ပံ့ပိုးပေးထားသည့် cryptocurrency တစ်ခုခုဖြင့် လုပ်ဆောင်နိုင်ပြီး၊ သင်သည် ဤပိုက်ဆံအိတ်တွင် ရံပုံငွေများ ရရှိမည်ဖြစ်သည်။",
"create_invoice": "ပြေစာဖန်တီးပါ။",
"create_donation_link": "လှူဒါန်းမှုလင့်ခ်ကို ဖန်တီးပါ။",
"optional_email_hint": "ရွေးချယ်နိုင်သော ငွေလက်ခံသူ အကြောင်းကြားချက် အီးမေးလ်",
"optional_description": "ရွေးချယ်နိုင်သော ဖော်ပြချက်",
"optional_name": "ရွေးချယ်နိုင်သော လက်ခံသူအမည်",
"clearnet_link": "Clearnet လင့်ခ်",
"onion_link": "ကြက်သွန်လင့်",
"sell_monero_com_alert_content": "Monero ရောင်းချခြင်းကို မပံ့ပိုးရသေးပါ။"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "een verzoek indienen",
"buy_alert_content" : "Momenteel ondersteunen we alleen de aankoop van Bitcoin en Litecoin. Om Bitcoin of Litecoin te kopen, maakt u uw Bitcoin- of Litecoin-portemonnee aan of schakelt u over naar deze.",
"buy_alert_content" : "Momenteel ondersteunen we alleen de aankoop van Bitcoin, Litecoin en Monero. Maak of schakel over naar uw Bitcoin-, Litecoin- of Monero-portemonnee.",
"sell_alert_content": "We ondersteunen momenteel alleen de verkoop van Bitcoin. Om Bitcoin te verkopen, maakt u uw Bitcoin-portemonnee aan of schakelt u over naar deze.",
"outdated_electrum_wallet_description" : "Nieuwe Bitcoin-portefeuilles die in Cake zijn gemaakt, hebben nu een zaadje van 24 woorden. Het is verplicht dat u een nieuwe Bitcoin-portemonnee maakt en al uw geld overmaakt naar de nieuwe portemonnee van 24 woorden, en stopt met het gebruik van wallets met een seed van 12 woorden. Doe dit onmiddellijk om uw geld veilig te stellen.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Stuur ${currency} ${tag}naar dit adres",
"arrive_in_this_address" : "${currency} ${tag}komt aan op dit adres",
"do_not_send": "Niet sturen",
"error_dialog_content": "Oeps, er is een fout opgetreden.\n\nStuur het crashrapport naar ons ondersteuningsteam om de applicatie te verbeteren."
"error_dialog_content": "Oeps, er is een fout opgetreden.\n\nStuur het crashrapport naar ons ondersteuningsteam om de applicatie te verbeteren.",
"decimal_places_error": "Te veel decimalen",
"edit_node": "Knooppunt bewerken",
"invoice_details": "Factuurgegevens",
"donation_link_details": "Details van de donatielink",
"anonpay_description": "Genereer ${type}. De ontvanger kan ${method} gebruiken met elke ondersteunde cryptocurrency en u ontvangt geld in deze portemonnee",
"create_invoice": "Factuur maken",
"create_donation_link": "Maak een donatielink aan",
"optional_email_hint": "Optionele kennisgeving per e-mail aan de begunstigde",
"optional_description": "Optionele beschrijving",
"optional_name": "Optionele naam ontvanger",
"clearnet_link": "Clearnet-link",
"onion_link": "Ui koppeling",
"sell_monero_com_alert_content": "Het verkopen van Monero wordt nog niet ondersteund"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "Złóż wniosek",
"buy_alert_content" : "Obecnie obsługujemy tylko zakup Bitcoina i Litecoina. Aby kupić Bitcoin lub Litecoin, utwórz lub przełącz się na swój portfel Bitcoin lub Litecoin.",
"buy_alert_content" : "Obecnie obsługujemy tylko zakup Bitcoin, Litecoin i Monero. Utwórz lub przełącz się na swój portfel Bitcoin, Litecoin lub Monero.",
"sell_alert_content": "Obecnie obsługujemy tylko sprzedaż Bitcoina. Aby sprzedać Bitcoin, utwórz lub przełącz się na swój portfel Bitcoin.",
"outdated_electrum_wallet_description" : "Nowe portfele Bitcoin utworzone w Cake mają teraz fraze seed składające się z 24 słów. Konieczne jest utworzenie nowego portfela Bitcoin i przeniesienie wszystkich środków do nowego portfela na 24 słowa oraz zaprzestanie korzystania z portfeli z frazą seed na 12 słów. Zrób to natychmiast, aby zabezpieczyć swoje fundusze.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Wyślij ${currency} ${tag}na ten adres",
"arrive_in_this_address" : "${currency} ${tag}dotrze na ten adres",
"do_not_send": "Nie wysyłaj",
"error_dialog_content": "Ups, wystąpił błąd.\n\nPrześlij raport o awarii do naszego zespołu wsparcia, aby ulepszyć aplikację."
"error_dialog_content": "Ups, wystąpił błąd.\n\nPrześlij raport o awarii do naszego zespołu wsparcia, aby ulepszyć aplikację.",
"decimal_places_error": "Za dużo miejsc dziesiętnych",
"edit_node": "Edytuj węzeł",
"invoice_details": "Dane do faktury",
"donation_link_details": "Szczegóły linku darowizny",
"anonpay_description": "Wygeneruj ${type}. Odbiorca może ${method} z dowolną obsługiwaną kryptowalutą, a Ty otrzymasz środki w tym portfelu.",
"create_invoice": "Wystaw fakturę",
"create_donation_link": "Utwórz link do darowizny",
"optional_email_hint": "Opcjonalny e-mail z powiadomieniem odbiorcy płatności",
"optional_description": "Opcjonalny opis",
"optional_name": "Opcjonalna nazwa odbiorcy",
"clearnet_link": "łącze Clearnet",
"onion_link": "Łącznik cebulowy",
"sell_monero_com_alert_content": "Sprzedaż Monero nie jest jeszcze obsługiwana"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "enviar um pedido",
"buy_alert_content" : "Atualmente, apoiamos apenas a compra de Bitcoin e Litecoin. Para comprar Bitcoin ou Litecoin, crie ou troque para sua carteira Bitcoin ou Litecoin.",
"buy_alert_content" : "Atualmente, oferecemos suporte apenas à compra de Bitcoin, Litecoin e Monero. Crie ou troque para sua carteira Bitcoin, Litecoin ou Monero.",
"sell_alert_content": "Atualmente, apoiamos apenas a venda de Bitcoin. Para vender Bitcoin, crie ou mude para sua carteira Bitcoin.",
"outdated_electrum_wallet_description" : "As novas carteiras Bitcoin criadas no Cake agora têm uma semente de 24 palavras. É obrigatório que você crie uma nova carteira Bitcoin e transfira todos os seus fundos para a nova carteira de 24 palavras, e pare de usar carteiras com semente de 12 palavras. Faça isso imediatamente para garantir seus fundos.",
@ -683,5 +683,18 @@
"send_to_this_address" : "Envie ${currency} ${tag}para este endereço",
"arrive_in_this_address" : "${currency} ${tag}chegará neste endereço",
"do_not_send": "não envie",
"error_dialog_content": "Ops, houve algum erro.\n\nPor favor, envie o relatório de falha para nossa equipe de suporte para melhorar o aplicativo."
"error_dialog_content": "Ops, houve algum erro.\n\nPor favor, envie o relatório de falha para nossa equipe de suporte para melhorar o aplicativo.",
"decimal_places_error": "Muitas casas decimais",
"edit_node": "Editar nó",
"invoice_details": "Detalhes da fatura",
"donation_link_details": "Detalhes do link de doação",
"anonpay_description": "Gere ${type}. O destinatário pode ${method} com qualquer criptomoeda suportada e você receberá fundos nesta carteira.",
"create_invoice": "Criar recibo",
"create_donation_link": "Criar link de doação",
"optional_email_hint": "E-mail opcional de notificação do beneficiário",
"optional_description": "Descrição opcional",
"optional_name": "Nome do destinatário opcional",
"clearnet_link": "link clear net",
"onion_link": "ligação de cebola",
"sell_monero_com_alert_content": "A venda de Monero ainda não é suportada"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "отправить запрос",
"buy_alert_content" : "В настоящее время мы поддерживаем только покупку Bitcoin и Litecoin. Чтобы купить Bitcoin или Litecoin, создайте или переключитесь на свой Bitcoin или Litecoin кошелек.",
"buy_alert_content" : "В настоящее время мы поддерживаем только покупку Bitcoin, Litecoin и Monero. Пожалуйста, создайте или переключитесь на свой кошелек Bitcoin, Litecoin или Monero.",
"sell_alert_content": "В настоящее время мы поддерживаем только продажу биткойнов. Чтобы продать биткойны, создайте или переключитесь на свой биткойн-кошелек.",
"outdated_electrum_wallet_description" : "Новые биткойн-кошельки, созданные в Cake, теперь содержат мнемоническую фразу из 24 слов. Вы обязательно должны создать новый биткойн-кошелек и перевести все свои средства в новый кошелек из 24 слов, а также прекратить использование кошельков с мнемонической фразой из 12 слов. Пожалуйста, сделайте это немедленно, чтобы обезопасить свои средства.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Отправить ${currency} ${tag}на этот адрес",
"arrive_in_this_address" : "${currency} ${tag}придет на этот адрес",
"do_not_send": "Не отправлять",
"error_dialog_content": "Ой, у нас какая-то ошибка.\n\nПожалуйста, отправьте отчет о сбое в нашу службу поддержки, чтобы сделать приложение лучше."
"error_dialog_content": "Ой, у нас какая-то ошибка.\n\nПожалуйста, отправьте отчет о сбое в нашу службу поддержки, чтобы сделать приложение лучше.",
"decimal_places_error": "Слишком много десятичных знаков",
"edit_node": "Редактировать узел",
"invoice_details": "Детали счета",
"donation_link_details": "Информация о ссылке для пожертвований",
"anonpay_description": "Создайте ${type}. Получатель может использовать ${method} с любой поддерживаемой криптовалютой, и вы получите средства на этот кошелек.",
"create_invoice": "Создать счет",
"create_donation_link": "Создать ссылку для пожертвований",
"optional_email_hint": "Необязательное электронное письмо с уведомлением получателя платежа",
"optional_description": "Дополнительное описание",
"optional_name": "Необязательное имя получателя",
"clearnet_link": "Клирнет ссылка",
"onion_link": "Луковая ссылка",
"sell_monero_com_alert_content": "Продажа Monero пока не поддерживается"
}

View file

@ -483,7 +483,7 @@
"submit_request" : "ส่งคำขอ",
"buy_alert_content" : "ในปัจจุบันเรารองรับการซื้อ Bitcoin และ Litecoin เท่านั้น หากต้องการซื้อ Bitcoin หรือ Litecoin โปรดสร้างหรือเปลี่ยนเป็นกระเป๋า Bitcoin หรือ Litecoin ของคุณ",
"buy_alert_content" : "ขณะนี้เรารองรับการซื้อ Bitcoin, Litecoin และ Monero เท่านั้น โปรดสร้างหรือเปลี่ยนเป็นกระเป๋าเงิน Bitcoin, Litecoin หรือ Monero ของคุณ",
"sell_alert_content" : "ในปัจจุบันเรารองรับการขาย Bitcoin เท่านั้น หากต้องการขาย Bitcoin โปรดสร้างหรือเปลี่ยนเป็นกระเป๋า Bitcoin ของคุณ",
"outdated_electrum_wallet_description" : "กระเป๋า Bitcoin ใหม่ที่สร้างใน Cake มี seed ขนาด 24 คำ ซึ่งจำเป็นต้องสร้างกระเป๋า Bitcoin ใหม่และโอนทุกเงินของคุณไปยังกระเป๋าใหม่ขนาด 24 คำ และหยุดใช้กระเป๋าที่มี seed ขนาด 12 คำ กรุณาทำด่วนเพื่อรักษาเงินของคุณ",
@ -682,5 +682,18 @@
"send_to_this_address" : "ส่ง ${currency} ${tag}ไปยังที่อยู่นี้",
"arrive_in_this_address" : "${currency} ${tag}จะมาถึงที่อยู่นี้",
"do_not_send": "อย่าส่ง",
"error_dialog_content": "อ๊ะ เราพบข้อผิดพลาดบางอย่าง\n\nโปรดส่งรายงานข้อขัดข้องไปยังทีมสนับสนุนของเราเพื่อปรับปรุงแอปพลิเคชันให้ดียิ่งขึ้น"
"error_dialog_content": "อ๊ะ เราพบข้อผิดพลาดบางอย่าง\n\nโปรดส่งรายงานข้อขัดข้องไปยังทีมสนับสนุนของเราเพื่อปรับปรุงแอปพลิเคชันให้ดียิ่งขึ้น",
"decimal_places_error": "ทศนิยมมากเกินไป",
"edit_node": "แก้ไขโหนด",
"invoice_details": "รายละเอียดใบแจ้งหนี้",
"donation_link_details": "รายละเอียดลิงค์บริจาค",
"anonpay_description": "สร้าง ${type} ผู้รับสามารถ ${method} ด้วยสกุลเงินดิจิทัลที่รองรับ และคุณจะได้รับเงินในกระเป๋าสตางค์นี้",
"create_invoice": "สร้างใบแจ้งหนี้",
"create_donation_link": "สร้างลิงค์บริจาค",
"optional_email_hint": "อีเมลแจ้งผู้รับเงินเพิ่มเติม",
"optional_description": "คำอธิบายเพิ่มเติม",
"optional_name": "ชื่อผู้รับเพิ่มเติม",
"clearnet_link": "ลิงค์เคลียร์เน็ต",
"onion_link": "ลิงค์หัวหอม",
"sell_monero_com_alert_content": "ยังไม่รองรับการขาย Monero"
}

View file

@ -485,7 +485,7 @@
"submit_request" : "talep gönder",
"buy_alert_content" : "Şu anda sadece Bitcoin ve Litecoin satın alımını destekliyoruz. Bitcoin veya Litecoin satın almak için lütfen Bitcoin veya Litecoin cüzdanınızı oluşturun veya bu cüzdanlardan birine geçiş yapın.",
"buy_alert_content" : "Şu anda yalnızca Bitcoin, Litecoin ve Monero satın alımını destekliyoruz. Lütfen Bitcoin, Litecoin veya Monero cüzdanınızı oluşturun veya cüzdanınıza geçiş yapın.",
"sell_alert_content": "Şu anda sadece Bitcoin satışını destekliyoruz. Bitcoin satmak için lütfen Bitcoin cüzdanınızı oluşturun veya Bitcoin cüzdanınıza geçiş yapın.",
"outdated_electrum_wallet_description" : "Cake'te oluşturulan yeni Bitcoin cüzdanları artık 24 kelimelik bir tohuma sahip. Yeni bir Bitcoin cüzdanı oluşturmanız ve tüm paranızı 24 kelimelik yeni cüzdana aktarmanız ve 12 kelimelik tohuma sahip cüzdanları kullanmayı bırakmanız zorunludur. Lütfen paranızı güvence altına almak için bunu hemen yapın.",
@ -684,5 +684,18 @@
"send_to_this_address" : "Bu adrese ${currency} ${tag}gönder",
"arrive_in_this_address" : "${currency} ${tag}bu adrese ulaşacak",
"do_not_send": "Gönderme",
"error_dialog_content": "Hay aksi, bir hatamız var.\n\nUygulamayı daha iyi hale getirmek için lütfen kilitlenme raporunu destek ekibimize gönderin."
"error_dialog_content": "Hay aksi, bir hatamız var.\n\nUygulamayı daha iyi hale getirmek için lütfen kilitlenme raporunu destek ekibimize gönderin.",
"decimal_places_error": "Çok fazla ondalık basamak",
"edit_node": "Düğümü Düzenle",
"invoice_details": "fatura detayları",
"donation_link_details": "Bağış bağlantısı ayrıntıları",
"anonpay_description": "${type} oluşturun. Alıcı, desteklenen herhangi bir kripto para birimi ile ${method} yapabilir ve bu cüzdanda para alırsınız.",
"create_invoice": "Fatura oluşturmak",
"create_donation_link": "Bağış bağlantısı oluştur",
"optional_email_hint": "İsteğe bağlı alacaklı bildirim e-postası",
"optional_description": "İsteğe bağlııklama",
"optional_name": "İsteğe bağlı alıcı adı",
"clearnet_link": "Net bağlantı",
"onion_link": "soğan bağlantısı",
"sell_monero_com_alert_content": "Monero satışı henüz desteklenmiyor"
}

View file

@ -484,7 +484,7 @@
"submit_request" : "надіслати запит",
"buy_alert_content" : "В даний час ми підтримуємо лише придбання Bitcoin та Litecoin. Щоб купити Bitcoin або Litecoin, будь ласка, створіть або перейдіть на свій гаманець Bitcoin або Litecoin.",
"buy_alert_content" : "Наразі ми підтримуємо лише придбання Bitcoin, Litecoin і Monero. Створіть або перейдіть на свій гаманець Bitcoin, Litecoin або Monero.",
"sell_alert_content": "Наразі ми підтримуємо лише продаж біткойнів. Щоб продати біткойн, створіть або перейдіть на свій біткойн-гаманець.",
"outdated_electrum_wallet_description" : "Нові біткойн-гаманці, створені в Cake, тепер містять мнемонічну фразу з 24 слів. Обов’язково стовріть новий біткойн-гаманець, переведіть всі кошти на новий гаманець із 24 слів і припиніть використання гаманців із мнемонічною фразою з 12 слів. Зробіть це негайно, щоб убезпечити свої кошти.",
@ -683,5 +683,18 @@
"send_to_this_address" : "Надіслати ${currency} ${tag}на цю адресу",
"arrive_in_this_address" : "${currency} ${tag}надійде на цю адресу",
"do_not_send": "Не надсилайте",
"error_dialog_content": "На жаль, ми отримали помилку.\n\nБудь ласка, надішліть звіт про збій нашій команді підтримки, щоб покращити додаток."
"error_dialog_content": "На жаль, ми отримали помилку.\n\nБудь ласка, надішліть звіт про збій нашій команді підтримки, щоб покращити додаток.",
"decimal_places_error": "Забагато знаків після коми",
"edit_node": "Редагувати вузол",
"invoice_details": "Реквізити рахунку-фактури",
"donation_link_details": "Деталі посилання для пожертв",
"anonpay_description": "Згенерувати ${type}. Одержувач може ${method} будь-якою підтримуваною криптовалютою, і ви отримаєте кошти на цей гаманець.",
"create_invoice": "Створити рахунок-фактуру",
"create_donation_link": "Створити посилання для пожертв",
"optional_email_hint": "Додаткова електронна адреса для сповіщення одержувача",
"optional_description": "Додатковий опис",
"optional_name": "Додаткове ім'я одержувача",
"clearnet_link": "Посилання Clearnet",
"onion_link": "Посилання на цибулю",
"sell_monero_com_alert_content": "Продаж Monero ще не підтримується"
}

View file

@ -487,7 +487,7 @@
"submit_request" : "درخواست بھیج دو",
"buy_alert_content" : "فی الحال ہم صرف Bitcoin اور Litecoin کی خریداری کی حمایت کرتے ہیں۔ Bitcoin یا Litecoin خریدنے کے لیے، براہ کرم اپنا Bitcoin یا Litecoin والیٹ بنائیں یا اس میں سوئچ کریں۔",
"buy_alert_content" : "فی الحال ہم صرف Bitcoin، Litecoin، اور Monero کی خریداری کی حمایت کرتے ہیں۔ براہ کرم اپنا Bitcoin، Litecoin، یا Monero والیٹ بنائیں یا اس پر سوئچ کریں۔",
"sell_alert_content" : "ہم فی الحال صرف Bitcoin کی فروخت کی حمایت کرتے ہیں۔ Bitcoin فروخت کرنے کے لیے، براہ کرم اپنا Bitcoin والیٹ بنائیں یا اس میں سوئچ کریں۔",
"outdated_electrum_wallet_description" : "Cake میں بنائے گئے نئے Bitcoin بٹوے میں اب 24 الفاظ کا بیج ہے۔ یہ لازمی ہے کہ آپ ایک نیا Bitcoin والیٹ بنائیں اور اپنے تمام فنڈز کو نئے 24 الفاظ والے والیٹ میں منتقل کریں، اور 12 الفاظ کے بیج والے بٹوے کا استعمال بند کریں۔ براہ کرم اپنے فنڈز کو محفوظ بنانے کے لیے فوری طور پر ایسا کریں۔",
@ -685,5 +685,18 @@
"send_to_this_address" : "اس پتے پر ${currency} ${tag} بھیجیں۔",
"arrive_in_this_address" : "${currency} ${tag}اس پتے پر پہنچے گا۔",
"do_not_send" : "مت بھیجیں۔",
"error_dialog_content" : "افوہ، ہمیں کچھ خرابی ملی۔\n\nایپلی کیشن کو بہتر بنانے کے لیے براہ کرم کریش رپورٹ ہماری سپورٹ ٹیم کو بھیجیں۔"
"error_dialog_content" : "افوہ، ہمیں کچھ خرابی ملی۔\n\nایپلی کیشن کو بہتر بنانے کے لیے براہ کرم کریش رپورٹ ہماری سپورٹ ٹیم کو بھیجیں۔",
"decimal_places_error": "بہت زیادہ اعشاریہ جگہیں۔",
"edit_node": "نوڈ میں ترمیم کریں۔",
"invoice_details": "رسید کی تفصیلات",
"donation_link_details": "عطیہ کے لنک کی تفصیلات",
"anonpay_description": "${type} بنائیں۔ وصول کنندہ کسی بھی تعاون یافتہ کرپٹو کرنسی کے ساتھ ${method} کرسکتا ہے، اور آپ کو اس بٹوے میں فنڈز موصول ہوں گے۔",
"create_invoice": "انوائس بنائیں",
"create_donation_link": "عطیہ کا لنک بنائیں",
"optional_email_hint": "اختیاری وصول کنندہ کی اطلاع کا ای میل",
"optional_description": "اختیاری تفصیل",
"optional_name": "اختیاری وصول کنندہ کا نام",
"clearnet_link": "کلیرنیٹ لنک",
"onion_link": "پیاز کا لنک",
"sell_monero_com_alert_content": "Monero فروخت کرنا ابھی تک تعاون یافتہ نہیں ہے۔"
}

View file

@ -484,7 +484,8 @@
"submit_request" : "提交请求",
"buy_alert_content" : "目前我们只支持购买比特币和莱特币。 要购买比特币或莱特币,请创建或切换到您的比特币或莱特币钱包。",
"buy_alert_content" : "目前我们只支持购买比特币、莱特币和门罗币。 请创建或切换到您的比特币、莱特币或门罗币钱包。",
"sell_alert_content" : "我们目前只支持比特币的销售。 要出售比特币,请创建或切换到您的比特币钱包。",
"outdated_electrum_wallet_description" : "在Cake创建的新比特币钱包现在有一个24字的种子。你必须创建一个新的比特币钱包并将你所有的资金转移到新的24字钱包并停止使用12字种子的钱包。请立即这样做以保证你的资金安全。",
"understand" : "我已知晓",
@ -682,5 +683,18 @@
"send_to_this_address" : "发送 ${currency} ${tag}到这个地址",
"arrive_in_this_address" : "${currency} ${tag}将到达此地址",
"do_not_send": "不要发送",
"error_dialog_content": "糟糕,我们遇到了一些错误。\n\n请将崩溃报告发送给我们的支持团队以改进应用程序。"
"error_dialog_content": "糟糕,我们遇到了一些错误。\n\n请将崩溃报告发送给我们的支持团队以改进应用程序。",
"decimal_places_error": "小数位太多",
"edit_node": "编辑节点",
"invoice_details": "发票明细",
"donation_link_details": "捐赠链接详情",
"anonpay_description": "生成 ${type}。收款人可以使用任何受支持的加密货币 ${method},您将在此钱包中收到资金。",
"create_invoice": "创建发票",
"create_donation_link": "创建捐赠链接",
"optional_email_hint": "可选的收款人通知电子邮件",
"optional_description": "可选说明",
"optional_name": "可选收件人姓名",
"clearnet_link": "明网链接",
"onion_link": "洋葱链接",
"sell_monero_com_alert_content": "尚不支持出售门罗币"
}

View file

@ -14,14 +14,14 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.3.0"
MONERO_COM_BUILD_NUMBER=40
MONERO_COM_VERSION="1.3.2"
MONERO_COM_BUILD_NUMBER=42
MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.6.0"
CAKEWALLET_BUILD_NUMBER=146
CAKEWALLET_VERSION="4.6.2"
CAKEWALLET_BUILD_NUMBER=151
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"

View file

@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_IOS_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.3.0"
MONERO_COM_BUILD_NUMBER=38
MONERO_COM_VERSION="1.3.2"
MONERO_COM_BUILD_NUMBER=40
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.6.0"
CAKEWALLET_BUILD_NUMBER=142
CAKEWALLET_VERSION="4.6.2"
CAKEWALLET_BUILD_NUMBER=145
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
HAVEN_NAME="Haven"

Some files were not shown because too many files have changed in this diff Show more