CW-782: Show error report popup without cooldown (#1739)
Some checks are pending
Cache Dependencies / test (push) Waiting to run

* improve exception throwing on broken wallets
- put _lastOpenedWallet to avoid issues on windows (file is currently open by)
- don't throw corruptedWalletsSeed - instead store it inside of secureStorage
- await ExceptionHandler.onError calls where possible to makse sure that popup won't be canceled by some UI element
- adjust BaseAlertDialog to be scrollable if the text is too long
- add ExceptionHandler.resetLastPopupDate - that can be called when we want to show error report screen (bypassing cooldown)

* fix: HiveError: Box has already been closed.

* await the alerts to be sure that each one of them is being shown
fix typo in secure storage

* Update lib/core/backup_service.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* address comments on github

* don't store seeds in secure storage

* fix wallet password

* update monero_c
update corrupted seeds UI
prevent app from crashing when wallet is corrupted

* show alert with seeds

* Update corrupted wallet UI
Fix wallet opening cache

* remove unused code

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
cyan 2024-11-28 20:28:31 +01:00 committed by GitHub
parent d8d4190608
commit 17d34beae9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 205 additions and 87 deletions

View file

@ -81,6 +81,7 @@ void createWalletSync(
wptr = newWptr;
monero.Wallet_store(wptr!, path: path);
openedWalletsByPath[path] = wptr!;
_lastOpenedWallet = path;
// is the line below needed?
// setupNodeSync(address: "node.moneroworld.com:18089");
@ -116,6 +117,7 @@ void restoreWalletFromSeedSync(
wptr = newWptr;
openedWalletsByPath[path] = wptr!;
_lastOpenedWallet = path;
}
void restoreWalletFromKeysSync(
@ -183,6 +185,7 @@ void restoreWalletFromKeysSync(
wptr = newWptr;
openedWalletsByPath[path] = wptr!;
_lastOpenedWallet = path;
}
void restoreWalletFromSpendKeySync(
@ -231,6 +234,7 @@ void restoreWalletFromSpendKeySync(
storeSync();
openedWalletsByPath[path] = wptr!;
_lastOpenedWallet = path;
}
String _lastOpenedWallet = "";
@ -260,7 +264,7 @@ Future<void> restoreWalletFromHardwareWallet(
throw WalletRestoreFromSeedException(message: error);
}
wptr = newWptr;
_lastOpenedWallet = path;
openedWalletsByPath[path] = wptr!;
}
@ -295,6 +299,11 @@ Future<void> loadWallet(
password: password,
kdfRounds: 1,
);
final status = monero.WalletManager_errorString(wmPtr);
if (status != "") {
print("loadWallet:"+status);
throw WalletOpeningException(message: status);
}
} else {
deviceType = 0;
}
@ -314,15 +323,15 @@ Future<void> loadWallet(
final newWptr = Pointer<Void>.fromAddress(newWptrAddr);
_lastOpenedWallet = path;
final status = monero.Wallet_status(newWptr);
if (status != 0) {
final err = monero.Wallet_errorString(newWptr);
print(err);
print("loadWallet:"+err);
throw WalletOpeningException(message: err);
}
wptr = newWptr;
_lastOpenedWallet = path;
openedWalletsByPath[path] = wptr!;
}
}

View file

@ -168,7 +168,7 @@ class MoneroWalletService extends WalletService<
}
await restoreOrResetWalletFiles(name);
return openWallet(name, password, retryOnFailure: false);
return await openWallet(name, password, retryOnFailure: false);
}
}

View file

@ -503,8 +503,8 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
resolved-ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
ref: c41c4dad9aa5003a914cfb2c528c76386f952665
resolved-ref: c41c4dad9aa5003a914cfb2c528c76386f952665
url: "https://github.com/mrcyjanek/monero_c"
source: git
version: "0.0.0"

View file

@ -25,7 +25,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
ref: c41c4dad9aa5003a914cfb2c528c76386f952665
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
path: impls/monero.dart
mutex: ^3.1.0

View file

@ -463,8 +463,8 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
resolved-ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
ref: c41c4dad9aa5003a914cfb2c528c76386f952665
resolved-ref: c41c4dad9aa5003a914cfb2c528c76386f952665
url: "https://github.com/mrcyjanek/monero_c"
source: git
version: "0.0.0"

View file

@ -25,7 +25,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
ref: d72c15f4339791a7bbdf17e9d827b7b56ca144e4
ref: c41c4dad9aa5003a914cfb2c528c76386f952665
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
path: impls/monero.dart
mutex: ^3.1.0

View file

@ -56,7 +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));
await ExceptionHandler.onError(FlutterErrorDetails(exception: response));
throw Exception('Unexpected response http code: ${response.statusCode}');
}

View file

@ -230,17 +230,15 @@ class BackupService {
json.decode(transactionDescriptionFile.readAsStringSync()) as Map<String, dynamic>;
final descriptionsMap = jsonData.map((key, value) =>
MapEntry(key, TransactionDescription.fromJson(value as Map<String, dynamic>)));
if (!_transactionDescriptionBox.isOpen) {
final transactionDescriptionsBoxKey = await getEncryptionKey(secureStorage: secureStorageShared, forKey: TransactionDescription.boxKey);
final transactionDescriptionBox = await CakeHive.openBox<TransactionDescription>(
var box = _transactionDescriptionBox;
if (!box.isOpen) {
final transactionDescriptionsBoxKey =
await getEncryptionKey(secureStorage: _secureStorage, forKey: TransactionDescription.boxKey);
box = await CakeHive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey,
);
await transactionDescriptionBox.putAll(descriptionsMap);
return;
}
await _transactionDescriptionBox.putAll(descriptionsMap);
encryptionKey: transactionDescriptionsBoxKey);
}
await box.putAll(descriptionsMap);
}
Future<void> _importPreferencesDump() async {

View file

@ -2,15 +2,22 @@ import 'dart:async';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WalletLoadingService {
@ -58,24 +65,25 @@ class WalletLoadingService {
return wallet;
} catch (error, stack) {
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
await ExceptionHandler.resetLastPopupDate();
await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
// try fetching the seeds of the corrupted wallet to show it to the user
String corruptedWalletsSeeds = "Corrupted wallets seeds (if retrievable, empty otherwise):";
try {
corruptedWalletsSeeds += await _getCorruptedWalletSeeds(name, type);
} catch (e) {
corruptedWalletsSeeds += "\nFailed to fetch $name seeds: $e";
corruptedWalletsSeeds += "\nFailed to fetch $name seeds: $e";
}
// try opening another wallet that is not corrupted to give user access to the app
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
WalletBase? wallet;
for (var walletInfo in walletInfoSource.values) {
try {
final walletService = walletServiceFactory.call(walletInfo.type);
final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
final wallet = await walletService.openWallet(walletInfo.name, walletPassword);
final walletPassword = await keyService.getWalletPassword(walletName: walletInfo.name);
wallet = await walletService.openWallet(walletInfo.name, walletPassword);
if (walletInfo.type == WalletType.monero) {
await updateMoneroWalletPassword(wallet);
@ -88,8 +96,6 @@ class WalletLoadingService {
// if found a wallet that is not corrupted, then still display the seeds of the corrupted ones
authenticatedErrorStreamController.add(corruptedWalletsSeeds);
return wallet;
} catch (e) {
print(e);
// save seeds and show corrupted wallets' seeds to the user
@ -99,16 +105,56 @@ class WalletLoadingService {
corruptedWalletsSeeds += seeds;
}
} catch (e) {
corruptedWalletsSeeds += "\nFailed to fetch $name seeds: $e";
corruptedWalletsSeeds += "\nFailed to fetch $name seeds: $e";
}
}
}
// if all user's wallets are corrupted throw exception
throw error.toString() + "\n\n" + corruptedWalletsSeeds;
final msg = error.toString() + "\n" + corruptedWalletsSeeds;
if (navigatorKey.currentContext != null) {
await showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: "Corrupted seeds",
alertContent: S.of(context).corrupted_seed_notice,
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).show_seed,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () => showSeedsPopup(context, msg),
);
});
} else {
throw msg;
}
if (wallet == null) {
throw Exception("Wallet is null");
}
return wallet;
}
}
Future<void> showSeedsPopup(BuildContext context, String message) async {
Navigator.of(context).pop();
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: "Corrupted seeds",
alertContent: message,
leftButtonText: S.of(context).copy,
rightButtonText: S.of(context).ok,
actionLeftButton: () async {
await Clipboard.setData(ClipboardData(text: message));
},
actionRightButton: () async {
Navigator.of(context).pop();
},
);
});
}
Future<void> updateMoneroWalletPassword(WalletBase wallet) async {
final key = PreferencesKey.moneroWalletUpdateV1Key(wallet.name);
var isPasswordUpdated = sharedPreferences.getBool(key) ?? false;

View file

@ -575,7 +575,7 @@ Future<void> setup({
totpAuthPageState.changeProcessText('Loading the wallet');
if (loginError != null) {
totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}');
totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}'.trim());
}
ReactionDisposer? _reaction;
@ -604,7 +604,7 @@ Future<void> setup({
authPageState.changeProcessText('Loading the wallet');
if (loginError != null) {
authPageState.changeProcessText('ERROR: ${loginError.toString()}');
authPageState.changeProcessText('ERROR: ${loginError.toString()}'.trim());
loginError = null;
}
@ -624,7 +624,7 @@ Future<void> setup({
}
if (loginError != null) {
authPageState.changeProcessText('ERROR: ${loginError.toString()}');
authPageState.changeProcessText('ERROR: ${loginError.toString()}'.trim());
timer.cancel();
}
});

View file

@ -89,7 +89,7 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
);
}
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
});
}

View file

@ -43,8 +43,8 @@ void startAuthenticationStateChange(
if (!requireHardwareWalletConnection()) await loadCurrentWallet();
} catch (error, stack) {
loginError = error;
ExceptionHandler.onError(
FlutterErrorDetails(exception: error, stack: stack));
await ExceptionHandler.resetLastPopupDate();
await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
}
return;
}
@ -81,7 +81,7 @@ void startAuthenticationStateChange(
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
}
if (!(await authenticatedErrorStreamController.stream.isEmpty)) {
ExceptionHandler.showError(
await ExceptionHandler.showError(
(await authenticatedErrorStreamController.stream.first).toString());
authenticatedErrorStreamController.stream.drain();
}

View file

@ -1,5 +1,6 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
@ -9,6 +10,8 @@ import 'package:cake_wallet/view_model/auth_view_model.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:flutter/services.dart';
typedef OnAuthenticationFinished = void Function(bool, AuthPageState);
@ -66,7 +69,6 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
dismissFlushBar(_authBar);
showBar<void>(
context, S.of(context).failed_authentication(state.error));
widget.onAuthenticationFinished(false, this);
});
}
@ -77,12 +79,12 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
dismissFlushBar(_authBar);
showBar<void>(
context, S.of(context).failed_authentication(state.error));
widget.onAuthenticationFinished(false, this);
});
}
});
if (widget.authViewModel.isBiometricalAuthenticationAllowed) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future<void>.delayed(Duration(milliseconds: 100));
@ -93,6 +95,23 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
super.initState();
}
Future<void> _showSeedsPopup(BuildContext context, String message) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: "Corrupted seeds",
alertContent: message,
leftButtonText: S.of(context).copy,
rightButtonText: S.of(context).ok,
actionLeftButton: () async {
await Clipboard.setData(ClipboardData(text: message));
},
actionRightButton: () => Navigator.of(context).pop(),
);
});
}
@override
void dispose() {
_reaction?.reaction.dispose();

View file

@ -154,7 +154,7 @@ class BackupPage extends BasePage {
File returnedFile = File(outputFile!);
await returnedFile.writeAsBytes(backup.content);
} catch (exception, stackTrace) {
ExceptionHandler.onError(FlutterErrorDetails(
await ExceptionHandler.onError(FlutterErrorDetails(
exception: exception,
stack: stackTrace,
library: "Export Backup",

View file

@ -11,6 +11,7 @@ import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.da
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';

View file

@ -1,6 +1,7 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
import 'package:cake_wallet/entities/wallet_list_order_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -20,6 +21,7 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@ -459,6 +461,9 @@ class WalletListBodyState extends State<WalletListBody> {
});
}
} catch (e) {
await ExceptionHandler.resetLastPopupDate();
final err = e.toString();
await ExceptionHandler.onError(FlutterErrorDetails(exception: err));
if (this.mounted) {
changeProcessText(S
.of(context)

View file

@ -73,15 +73,22 @@ class BaseAlertDialog extends StatelessWidget {
}
Widget content(BuildContext context) {
return Text(
contentText,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
contentText,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
],
),
);
}

View file

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
@ -20,7 +21,7 @@ class ExceptionHandler {
static const _coolDownDurationInDays = 7;
static File? _file;
static void _saveException(String? error, StackTrace? stackTrace, {String? library}) async {
static Future<void> _saveException(String? error, StackTrace? stackTrace, {String? library}) async {
final appDocDir = await getAppDir();
if (_file == null) {
@ -90,7 +91,12 @@ class ExceptionHandler {
}
}
static void onError(FlutterErrorDetails errorDetails) async {
static Future<void> resetLastPopupDate() async {
final sharedPrefs = await SharedPreferences.getInstance();
await sharedPrefs.setString(PreferencesKey.lastPopupDate, DateTime(1971).toString());
}
static Future<void> onError(FlutterErrorDetails errorDetails) async {
if (kDebugMode || kProfileMode) {
FlutterError.presentError(errorDetails);
debugPrint(errorDetails.toString());
@ -124,35 +130,40 @@ class ExceptionHandler {
}
_hasError = true;
sharedPrefs.setString(PreferencesKey.lastPopupDate, DateTime.now().toString());
await sharedPrefs.setString(PreferencesKey.lastPopupDate, DateTime.now().toString());
WidgetsBinding.instance.addPostFrameCallback(
(timeStamp) async {
if (navigatorKey.currentContext != null) {
await showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (context) {
return AlertWithTwoActions(
isDividerExist: true,
alertTitle: S.of(context).error,
alertContent: S.of(context).error_dialog_content,
rightButtonText: S.of(context).send,
leftButtonText: S.of(context).do_not_send,
actionRightButton: () {
Navigator.of(context).pop();
_sendExceptionFile();
},
actionLeftButton: () {
Navigator.of(context).pop();
},
);
// Instead of using WidgetsBinding.instance.addPostFrameCallback we
// await Future.delayed(Duration.zero), which does essentially the same (
// but doesn't wait for actual frame to be rendered), but it allows us to
// properly await the execution - which is what we want, without awaiting
// other code may call functions like Navigator.pop(), and close the alert
// instead of the intended UI.
// WidgetsBinding.instance.addPostFrameCallback(
// (timeStamp) async {
await Future.delayed(Duration.zero);
if (navigatorKey.currentContext != null) {
await showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (context) {
return AlertWithTwoActions(
isDividerExist: true,
alertTitle: S.of(context).error,
alertContent: S.of(context).error_dialog_content,
rightButtonText: S.of(context).send,
leftButtonText: S.of(context).do_not_send,
actionRightButton: () {
Navigator.of(context).pop();
_sendExceptionFile();
},
actionLeftButton: () {
Navigator.of(context).pop();
},
);
}
},
);
}
_hasError = false;
},
);
_hasError = false;
}
/// Ignore User related errors or system errors
@ -272,20 +283,18 @@ class ExceptionHandler {
};
}
static void showError(String error, {int? delayInSeconds}) async {
static Future<void> showError(String error, {int? delayInSeconds}) async {
if (_hasError) {
return;
}
_hasError = true;
if (delayInSeconds != null) {
Future.delayed(Duration(seconds: delayInSeconds), () => _showCopyPopup(error));
return;
}
WidgetsBinding.instance.addPostFrameCallback(
(_) async => _showCopyPopup(error),
);
await Future.delayed(Duration.zero);
await _showCopyPopup(error);
}
static Future<void> _showCopyPopup(String content) async {

View file

@ -68,7 +68,7 @@ abstract class RestoreFromBackupViewModelBase with Store {
if (msg.toLowerCase().contains("message authentication code (mac)")) {
msg = 'Incorrect backup password';
} else {
ExceptionHandler.onError(FlutterErrorDetails(
await ExceptionHandler.onError(FlutterErrorDetails(
exception: e,
stack: s,
library: this.toString(),

View file

@ -10,7 +10,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
sp_scanner
)
set(PLUGIN_BUNDLED_LIBRARIES)

View file

@ -6,7 +6,6 @@ import FlutterMacOS
import Foundation
import connectivity_plus
import cw_mweb
import device_info_plus
import devicelocale
import fast_scanner
@ -24,7 +23,6 @@ import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
CwMwebPlugin.register(with: registry.registrar(forPlugin: "CwMwebPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))

View file

@ -177,6 +177,7 @@
"copy_address": "نسخ العنوان",
"copy_id": "نسخ معرف العملية",
"copyWalletConnectLink": "ﺎﻨﻫ ﻪﻘﺼﻟﺍﻭ dApp ﻦﻣ WalletConnect ﻂﺑﺍﺭ ﺦﺴﻧﺍ",
"corrupted_seed_notice": "تالف ملفات هذه المحفظة ولا يمكن فتحها. يرجى الاطلاع على عبارة البذور وحفظها واستعادة المحفظة.\n\nإذا كانت القيمة فارغة ، لم تتمكن البذور من استردادها بشكل صحيح.",
"countries": "بلدان",
"create_account": "إنشاء حساب",
"create_backup": "انشئ نسخة احتياطية",

View file

@ -177,6 +177,7 @@
"copy_address": "Copy Address",
"copy_id": "Копиране на ID",
"copyWalletConnectLink": "Копирайте връзката WalletConnect от dApp и я поставете тук",
"corrupted_seed_notice": "Файловете за този портфейл са повредени и не могат да бъдат отворени. Моля, прегледайте фразата за семена, запазете я и възстановете портфейла.\n\nАко стойността е празна, тогава семето не успя да бъде правилно възстановено.",
"countries": "Държави",
"create_account": "Създаване на профил",
"create_backup": "Създаване на резервно копие",

View file

@ -177,6 +177,7 @@
"copy_address": "Zkopírovat adresu",
"copy_id": "Kopírovat ID",
"copyWalletConnectLink": "Zkopírujte odkaz WalletConnect z dApp a vložte jej sem",
"corrupted_seed_notice": "Soubory pro tuto peněženku jsou poškozeny a nemohou být otevřeny. Podívejte se prosím na osivo, uložte ji a obnovte peněženku.\n\nPokud je hodnota prázdná, pak semeno nebylo možné správně obnovit.",
"countries": "Země",
"create_account": "Vytvořit účet",
"create_backup": "Vytvořit zálohu",

View file

@ -177,6 +177,7 @@
"copy_address": "Adresse kopieren",
"copy_id": "ID kopieren",
"copyWalletConnectLink": "Kopieren Sie den WalletConnect-Link von dApp und fügen Sie ihn hier ein",
"corrupted_seed_notice": "Die Dateien für diese Brieftasche sind beschädigt und können nicht geöffnet werden. Bitte sehen Sie sich die Saatgutphrase an, speichern Sie sie und stellen Sie die Brieftasche wieder her.\n\nWenn der Wert leer ist, konnte der Samen nicht korrekt wiederhergestellt werden.",
"countries": "Länder",
"create_account": "Konto erstellen",
"create_backup": "Backup erstellen",

View file

@ -177,6 +177,7 @@
"copy_address": "Copy Address",
"copy_id": "Copy ID",
"copyWalletConnectLink": "Copy the WalletConnect link from dApp and paste here",
"corrupted_seed_notice": "The files for this wallet are corrupted and are unable to be opened. Please view the seed phrase, save it, and restore the wallet.\n\nIf the value is empty, then the seed was unable to be correctly recovered.",
"countries": "Countries",
"create_account": "Create Account",
"create_backup": "Create backup",

View file

@ -177,6 +177,7 @@
"copy_address": "Copiar dirección ",
"copy_id": "Copiar ID",
"copyWalletConnectLink": "Copie el enlace de WalletConnect de dApp y péguelo aquí",
"corrupted_seed_notice": "Los archivos para esta billetera están dañados y no pueden abrirse. Vea la frase de semillas, guárdela y restaura la billetera.\n\nSi el valor está vacío, entonces la semilla no pudo recuperarse correctamente.",
"countries": "Países",
"create_account": "Crear Cuenta",
"create_backup": "Crear copia de seguridad",

View file

@ -177,6 +177,7 @@
"copy_address": "Copier l'Adresse",
"copy_id": "Copier l'ID",
"copyWalletConnectLink": "Copiez le lien WalletConnect depuis l'application décentralisée (dApp) et collez-le ici",
"corrupted_seed_notice": "Les fichiers de ce portefeuille sont corrompus et ne peuvent pas être ouverts. Veuillez consulter la phrase de graines, sauver et restaurer le portefeuille.\n\nSi la valeur est vide, la graine n'a pas pu être correctement récupérée.",
"countries": "Des pays",
"create_account": "Créer un compte",
"create_backup": "Créer une sauvegarde",

View file

@ -177,6 +177,7 @@
"copy_address": "Kwafi Adireshin",
"copy_id": "Kwafi ID",
"copyWalletConnectLink": "Kwafi hanyar haɗin WalletConnect daga dApp kuma liƙa a nan",
"corrupted_seed_notice": "Fayilolin don wannan walat ɗin sun lalata kuma ba za a iya buɗe su ba. Da fatan za a duba kalmar iri, adana shi, da dawo da walat.\n\nIdan darajar ta kasance fanko, to sai zuriyar da ba ta iya murmurewa daidai ba.",
"countries": "Kasashe",
"create_account": "Kirkira ajiya",
"create_backup": "Ƙirƙiri madadin",

View file

@ -177,6 +177,7 @@
"copy_address": "पता कॉपी करें",
"copy_id": "प्रतिलिपि ID",
"copyWalletConnectLink": "dApp से वॉलेटकनेक्ट लिंक को कॉपी करें और यहां पेस्ट करें",
"corrupted_seed_notice": "इस वॉलेट की फाइलें दूषित हैं और उन्हें खोलने में असमर्थ हैं। कृपया बीज वाक्यांश देखें, इसे बचाएं, और बटुए को पुनर्स्थापित करें।\n\nयदि मूल्य खाली है, तो बीज सही ढंग से पुनर्प्राप्त करने में असमर्थ था।",
"countries": "देशों",
"create_account": "खाता बनाएं",
"create_backup": "बैकअप बनाएँ",

View file

@ -177,6 +177,7 @@
"copy_address": "Kopiraj adresu",
"copy_id": "Kopirati ID",
"copyWalletConnectLink": "Kopirajte vezu WalletConnect iz dApp-a i zalijepite je ovdje",
"corrupted_seed_notice": "Datoteke za ovaj novčanik su oštećene i nisu u mogućnosti otvoriti. Molimo pogledajte sjemensku frazu, spremite je i vratite novčanik.\n\nAko je vrijednost prazna, tada sjeme nije bilo u stanju ispravno oporaviti.",
"countries": "Zemalja",
"create_account": "Stvori račun",
"create_backup": "Stvori sigurnosnu kopiju",

View file

@ -177,6 +177,7 @@
"copy_address": "Պատճենել հասցեն",
"copy_id": "Պատճենել ID",
"copyWalletConnectLink": "Պատճենել WalletConnect հղումը dApp-ից և տեղադրել այստեղ",
"corrupted_seed_notice": "Այս դրամապանակի համար ֆայլերը կոռումպացված են եւ չեն կարողանում բացվել: Խնդրում ենք դիտել սերմերի արտահայտությունը, պահպանել այն եւ վերականգնել դրամապանակը:\n\nԵթե ​​արժեքը դատարկ է, ապա սերմը չկարողացավ ճիշտ վերականգնվել:",
"countries": "Երկրներ",
"create_account": "Ստեղծել հաշիվ",
"create_backup": "Ստեղծել կրկնօրինակ",

View file

@ -177,6 +177,7 @@
"copy_address": "Salin Alamat",
"copy_id": "Salin ID",
"copyWalletConnectLink": "Salin tautan WalletConnect dari dApp dan tempel di sini",
"corrupted_seed_notice": "File untuk dompet ini rusak dan tidak dapat dibuka. Silakan lihat frasa benih, simpan, dan kembalikan dompet.\n\nJika nilainya kosong, maka benih tidak dapat dipulihkan dengan benar.",
"countries": "Negara",
"create_account": "Buat Akun",
"create_backup": "Buat cadangan",

View file

@ -178,6 +178,7 @@
"copy_address": "Copia Indirizzo",
"copy_id": "Copia ID",
"copyWalletConnectLink": "Copia il collegamento WalletConnect dalla dApp e incollalo qui",
"corrupted_seed_notice": "I file per questo portafoglio sono corrotti e non sono in grado di essere aperti. Visualizza la frase del seme, salvala e ripristina il portafoglio.\n\nSe il valore è vuoto, il seme non è stato in grado di essere recuperato correttamente.",
"countries": "Paesi",
"create_account": "Crea account",
"create_backup": "Crea backup",

View file

@ -177,6 +177,7 @@
"copy_address": "住所をコピー",
"copy_id": "IDをコピー",
"copyWalletConnectLink": "dApp から WalletConnect リンクをコピーし、ここに貼り付けます",
"corrupted_seed_notice": "このウォレットのファイルは破損しており、開くことができません。シードフレーズを表示し、保存し、財布を復元してください。\n\n値が空の場合、種子を正しく回復することができませんでした。",
"countries": "国",
"create_account": "アカウントの作成",
"create_backup": "バックアップを作成",

View file

@ -177,6 +177,7 @@
"copy_address": "주소 복사",
"copy_id": "부 ID",
"copyWalletConnectLink": "dApp에서 WalletConnect 링크를 복사하여 여기에 붙여넣으세요.",
"corrupted_seed_notice": "이 지갑의 파일은 손상되어 열 수 없습니다. 씨앗 문구를보고 저장하고 지갑을 복원하십시오.\n\n값이 비어 있으면 씨앗을 올바르게 회수 할 수 없었습니다.",
"countries": "국가",
"create_account": "계정 만들기",
"create_backup": "백업 생성",

View file

@ -177,6 +177,7 @@
"copy_address": "လိပ်စာကို ကူးယူပါ။",
"copy_id": "ID ကူးယူပါ။",
"copyWalletConnectLink": "dApp မှ WalletConnect လင့်ခ်ကို ကူးယူပြီး ဤနေရာတွင် ကူးထည့်ပါ။",
"corrupted_seed_notice": "ဤပိုက်ဆံအိတ်အတွက်ဖိုင်များသည်အကျင့်ပျက်ခြစားမှုများနှင့်မဖွင့်နိုင်ပါ။ ကျေးဇူးပြု. မျိုးစေ့များကိုကြည့်ပါ, ၎င်းကိုသိမ်းဆည်းပါ, ပိုက်ဆံအိတ်ကိုပြန်ယူပါ။\n\nအကယ်. တန်ဖိုးသည်အချည်းနှီးဖြစ်ပါကမျိုးစေ့ကိုမှန်ကန်စွာပြန်လည်ကောင်းမွန်မရရှိနိုင်ပါ။",
"countries": "နိုင်ငံများ",
"create_account": "အကောင့်ပြုလုပ်ပါ",
"create_backup": "အရန်သိမ်းခြင်းကို ဖန်တီးပါ။",

View file

@ -177,6 +177,7 @@
"copy_address": "Adres kopiëren",
"copy_id": "ID kopiëren",
"copyWalletConnectLink": "Kopieer de WalletConnect-link van dApp en plak deze hier",
"corrupted_seed_notice": "De bestanden voor deze portemonnee zijn beschadigd en kunnen niet worden geopend. Bekijk de zaadzin, bewaar deze en herstel de portemonnee.\n\nAls de waarde leeg is, kon het zaad niet correct worden hersteld.",
"countries": "Landen",
"create_account": "Account aanmaken",
"create_backup": "Maak een back-up",

View file

@ -177,6 +177,7 @@
"copy_address": "Skopiuj adress",
"copy_id": "skopiuj ID",
"copyWalletConnectLink": "Skopiuj link do WalletConnect z dApp i wklej tutaj",
"corrupted_seed_notice": "Pliki dla tego portfela są uszkodzone i nie można ich otworzyć. Zobacz wyrażenie nasion, zapisz je i przywróć portfel.\n\nJeśli wartość jest pusta, ziarno nie można było poprawnie odzyskać.",
"countries": "Kraje",
"create_account": "Utwórz konto",
"create_backup": "Utwórz kopię zapasową",

View file

@ -177,6 +177,7 @@
"copy_address": "Copiar endereço",
"copy_id": "Copiar ID",
"copyWalletConnectLink": "Copie o link WalletConnect do dApp e cole aqui",
"corrupted_seed_notice": "Os arquivos para esta carteira estão corrompidos e não podem ser abertos. Veja a frase das sementes, salve -a e restaure a carteira.\n\nSe o valor estiver vazio, a semente não pôde ser recuperada corretamente.",
"countries": "Países",
"create_account": "Criar conta",
"create_backup": "Criar backup",

View file

@ -177,6 +177,7 @@
"copy_address": "Cкопировать адрес",
"copy_id": "Скопировать ID",
"copyWalletConnectLink": "Скопируйте ссылку WalletConnect из dApp и вставьте сюда.",
"corrupted_seed_notice": "Файлы для этого кошелька повреждены и не могут быть открыты. Пожалуйста, просмотрите семенную фразу, сохраните ее и восстановите кошелек.\n\nЕсли значение пустое, то семя не смог правильно восстановить.",
"countries": "Страны",
"create_account": "Создать аккаунт",
"create_backup": "Создать резервную копию",

View file

@ -177,6 +177,7 @@
"copy_address": "คัดลอกที่อยู่",
"copy_id": "คัดลอก ID",
"copyWalletConnectLink": "คัดลอกลิงก์ WalletConnect จาก dApp แล้ววางที่นี่",
"corrupted_seed_notice": "ไฟล์สำหรับกระเป๋าเงินนี้เสียหายและไม่สามารถเปิดได้ โปรดดูวลีเมล็ดบันทึกและกู้คืนกระเป๋าเงิน\n\nหากค่าว่างเปล่าเมล็ดก็ไม่สามารถกู้คืนได้อย่างถูกต้อง",
"countries": "ประเทศ",
"create_account": "สร้างบัญชี",
"create_backup": "สร้างการสำรองข้อมูล",

View file

@ -177,6 +177,7 @@
"copy_address": "Kopyahin ang Address",
"copy_id": "Kopyahin ang ID",
"copyWalletConnectLink": "Kopyahin ang link ng WalletConnect mula sa dApp at i-paste dito",
"corrupted_seed_notice": "Ang mga file para sa pitaka na ito ay nasira at hindi mabubuksan. Mangyaring tingnan ang parirala ng binhi, i -save ito, at ibalik ang pitaka.\n\nKung ang halaga ay walang laman, kung gayon ang binhi ay hindi ma -recover nang tama.",
"countries": "Mga bansa",
"create_account": "Lumikha ng Account",
"create_backup": "Lumikha ng backup",

View file

@ -177,6 +177,7 @@
"copy_address": "Adresi kopyala",
"copy_id": "ID'yi kopyala",
"copyWalletConnectLink": "WalletConnect bağlantısını dApp'ten kopyalayıp buraya yapıştırın",
"corrupted_seed_notice": "Bu cüzdanın dosyaları bozuk ve açılamıyor. Lütfen tohum ifadesini görüntüleyin, kaydedin ve cüzdanı geri yükleyin.\n\nDeğer boşsa, tohum doğru bir şekilde geri kazanılamadı.",
"countries": "Ülkeler",
"create_account": "Hesap oluştur",
"create_backup": "Yedek oluştur",

View file

@ -177,6 +177,7 @@
"copy_address": "Cкопіювати адресу",
"copy_id": "Скопіювати ID",
"copyWalletConnectLink": "Скопіюйте посилання WalletConnect із dApp і вставте сюди",
"corrupted_seed_notice": "Файли для цього гаманця пошкоджені і не можуть бути відкриті. Перегляньте насіннєву фразу, збережіть її та відновіть гаманець.\n\nЯкщо значення порожнє, то насіння не могло бути правильно відновленим.",
"countries": "Країни",
"create_account": "Створити обліковий запис",
"create_backup": "Створити резервну копію",

View file

@ -177,6 +177,7 @@
"copy_address": "ایڈریس کاپی کریں۔",
"copy_id": "کاپی ID",
"copyWalletConnectLink": "dApp ﮯﺳ WalletConnect ۔ﮟﯾﺮﮐ ﭧﺴﯿﭘ ﮞﺎﮩﯾ ﺭﻭﺍ ﮟﯾﺮﮐ ﯽﭘﺎﮐ ﻮﮐ ﮏﻨﻟ",
"corrupted_seed_notice": "اس پرس کے لئے فائلیں خراب ہیں اور کھولنے سے قاصر ہیں۔ براہ کرم بیج کے فقرے کو دیکھیں ، اسے بچائیں ، اور بٹوے کو بحال کریں۔\n\nاگر قیمت خالی ہے ، تو بیج صحیح طور پر بازیافت کرنے سے قاصر تھا۔",
"countries": "ممالک",
"create_account": "اکاؤنٹ بنائیں",
"create_backup": "بیک اپ بنائیں",

View file

@ -176,6 +176,7 @@
"copy_address": "Sao chép Địa chỉ",
"copy_id": "Sao chép ID",
"copyWalletConnectLink": "Sao chép liên kết WalletConnect từ dApp và dán vào đây",
"corrupted_seed_notice": "Các tệp cho ví này bị hỏng và không thể mở. Vui lòng xem cụm từ hạt giống, lưu nó và khôi phục ví.\n\nNếu giá trị trống, thì hạt giống không thể được phục hồi chính xác.",
"countries": "Quốc gia",
"create_account": "Tạo tài khoản",
"create_backup": "Tạo sao lưu",

View file

@ -177,6 +177,7 @@
"copy_address": "Ṣẹ̀dà àdírẹ́sì",
"copy_id": "Ṣẹ̀dà àmì ìdánimọ̀",
"copyWalletConnectLink": "Daakọ ọna asopọ WalletConnect lati dApp ki o si lẹẹmọ nibi",
"corrupted_seed_notice": "Awọn faili fun apamọwọ yii jẹ ibajẹ ati pe ko lagbara lati ṣii. Jọwọ wo ọrọ iseda, fipamọ rẹ, ki o mu apamọwọ naa pada.\n\nTi iye ba ṣofo, lẹhinna irugbin naa ko lagbara lati gba pada ni deede.",
"countries": "Awọn orilẹ-ede",
"create_account": "Dá àkáǹtì",
"create_backup": "Ṣẹ̀dà nípamọ́",

View file

@ -177,6 +177,7 @@
"copy_address": "复制地址",
"copy_id": "复制ID",
"copyWalletConnectLink": "从 dApp 复制 WalletConnect 链接并粘贴到此处",
"corrupted_seed_notice": "该钱包的文件被损坏,无法打开。请查看种子短语,保存并恢复钱包。\n\n如果该值为空则种子无法正确恢复。",
"countries": "国家",
"create_account": "创建账户",
"create_backup": "创建备份",

View file

@ -8,7 +8,7 @@ if [[ ! -d "monero_c" ]];
then
git clone https://github.com/mrcyjanek/monero_c --branch master
cd monero_c
git checkout d72c15f4339791a7bbdf17e9d827b7b56ca144e4
git checkout c41c4dad9aa5003a914cfb2c528c76386f952665
git reset --hard
git submodule update --init --force --recursive
./apply_patches.sh monero

View file

@ -14,7 +14,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
sp_scanner
)
set(PLUGIN_BUNDLED_LIBRARIES)