diff --git a/.metadata b/.metadata new file mode 100644 index 000000000..e0236519d --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 20e59316b8b8474554b38493b8ca888794b0234a + channel: stable + +project_type: app diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..ef51a8e6b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Cake Technologies LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..e5cc260ba --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Cake Wallet + +The project description, motivation, build scripts, instructions, tests will be added soon (Jan-Feb 2020); + +Copyright (c) 2020 Cake Technologies LLC. \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 000000000..4c0b97735 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,64 @@ +include: package:pedantic/analysis_options.yaml + +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + exclude: + - **/*.yaml + - build/** + - **/*.g.dart + - lib/generated/*.dart + +linter: + rules: + - always_declare_return_types + - always_specify_types + - annotate_overrides + - avoid_as + - avoid_empty_else + - avoid_init_to_null + - avoid_return_types_on_setters + - await_only_futures + - camel_case_types + - cancel_subscriptions + - close_sinks + - comment_references + - constant_identifier_names + - control_flow_in_finally + - empty_catches + - empty_constructor_bodies + - empty_statements + - hash_and_equals + - implementation_imports + - invariant_booleans + - iterable_contains_unrelated_type + - library_names + - library_prefixes + - list_remove_unrelated_type + - literal_only_boolean_expressions + - non_constant_identifier_names + - one_member_abstracts + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_final_fields + - prefer_final_locals + - prefer_is_not_empty + - public_member_api_docs + - slash_for_doc_comments + - sort_constructors_first + - sort_unnamed_constructors_first + - super_goes_last + - test_types_in_equals + - throw_in_finally + - type_annotate_public_apis + - type_init_formals + - unawaited_futures + - unnecessary_brace_in_string_interp + - unnecessary_getters_setters + - unrelated_type_equality_checks + - valid_regexps \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 000000000..bc2100d8f --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 000000000..878eb27cf --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,86 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.cakewallet.cake_wallet" + minSdkVersion 21 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + externalNativeBuild { + cmake { + cppFlags "-std=c++11" + arguments '-DANDROID_STL=c++_shared', '-DBUILD_TESTING=OFF' + version "3.10.2" + } + } + } + + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + buildTypes { + release { + signingConfig signingConfigs.release + + minifyEnabled true + useProguard true + + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 000000000..d24d7f10a --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,8 @@ +## Flutter wrapper +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } +-keep class io.flutter.view.** { *; } +-keep class io.flutter.** { *; } +-keep class io.flutter.plugins.** { *; } +-dontwarn io.flutter.embedding.** \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..dc767a55d --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..86893fd1b --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java new file mode 100644 index 000000000..69c9866af --- /dev/null +++ b/android/app/src/main/java/com/cakewallet/cake_wallet/MainActivity.java @@ -0,0 +1,13 @@ +package com.cakewallet.cake_wallet; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..5d9200d0a Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..83a98b56d Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..c8cbe84dd Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..c4352f579 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..73fe04234 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..00fa4417c --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..dc767a55d --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 000000000..e0d7ae2c1 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 000000000..38c8d4544 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..296b146b7 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 000000000..5a2f14fb1 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/assets/faq/faq_de.json b/assets/faq/faq_de.json new file mode 100644 index 000000000..5caeaf422 --- /dev/null +++ b/assets/faq/faq_de.json @@ -0,0 +1,58 @@ +[ + { + "question" : "Was ist der Unterschied zwischen verfügbarem Guthaben und vollständigem Guthaben?", + "answer" : "Nachdem Sie eine Transaktion getätigt oder Monero erhalten haben, muss die Transaktion noch bestätigt werden. In ungefähr 20 Minuten sollte Ihr \"verfügbares Guthaben\" aktualisiert werden!\nWenn Sie Monero senden, verringert sich manchmal Ihr verfügbares Guthaben um mehr als den Betrag, den Sie gesendet haben. Dies ist normal und zum Schutz Ihrer Privatsphäre erforderlich. Ihr \"vollständiges Gleichgewicht\" sollte in 20 Minuten wieder normal sein.\n" + }, + { + "question" : "Wie sende ich Monero an eine Börse, für die eine Zahlungs-ID erforderlich ist?", + "answer" : "Tippen Sie auf dem Wallet-Bildschirm auf die Schaltfläche \"Senden\". Kopieren Sie anschließend die Einzahlungsadresse der Börse und fügen Sie sie in das Feld \"Adresse\" ein. Kopieren Sie dann die vom Umtausch bereitgestellte Zahlungs-ID und fügen Sie sie in das Feld Zahlungs-ID ein. Geben Sie zum Schluss den Betrag ein, den Sie senden möchten, und los geht's!\n" + }, + { + "question" : "Was kann ich tun, wenn ich beim Senden von Monero an eine Börse vergessen habe, die Zahlungs-ID einzugeben?", + "answer" : "Obwohl unser Support Sie bei diesem Problem nicht direkt unterstützen kann, ist es ein sehr häufiges Problem, mit dem die meisten Börsen vertraut sind. Wenden Sie sich einfach an den Support der Börse, erklären Sie, dass Sie vergessen haben, Ihre Zahlungs-ID anzugeben, und senden Sie ihnen dann Ihre Transaktions-ID als Nachweis. Sie finden die Transaktions-ID, indem Sie auf die Transaktion in Ihrem Wallet-Bildschirm tippen.\n" + }, + { + "question" : "Was bedeuten \"Samen\" und \"Schlüssel\"?", + "answer" : "Ihre Schlüssel verschlüsseln die privaten Informationen in Ihrer Brieftasche und ermöglichen es Ihnen, Münzen auszugeben und eingehende Transaktionen anzuzeigen.\nIhr Startwert ist nur eine Version Ihres privaten Schlüssels, die so geschrieben wurde, dass Sie sie leichter notieren können. Ihr Same und Schlüssel sind tatsächlich dasselbe, nur in verschiedenen Formen!\nGeben Sie niemals Ihren Samen oder Schlüssel an jemanden weiter. Ihr Geld wird gestohlen, wenn Sie Ihren Samen oder Schlüssel herausgeben. Bitte notieren Sie sich jedoch Ihren Samen und bewahren Sie ihn an einem sicheren Ort auf (so können Sie Ihre Brieftasche wiederherstellen, wenn Sie Ihr Telefon verlieren.)\n" + }, + { + "question" : "Wie viele Geldbörsen kann ich erstellen?", + "answer" : "Es gibt keine Grenzen! Sie können so viele Brieftaschen erstellen, wie Sie möchten.\n" + }, + { + "question" : "Wie kann ich meine Brieftasche wiederherstellen?", + "answer" : "Tippen Sie auf das Menü •••, wählen Sie „Brieftaschen“ und dann „Brieftasche wiederherstellen“. Geben Sie dann Ihren Startwert (oder Ihre Schlüssel) und optional ein Datum vor der ersten Transaktion in Ihrer Brieftasche ein (dies beschleunigt den Synchronisierungsvorgang) .) Möglicherweise müssen Sie die App 15 bis 30 Minuten geöffnet lassen, um Ihr Portemonnaie vollständig wiederherzustellen.\n" + }, + { + "question" : "Was kann ich tun, wenn ich meinen Samen verliere?", + "answer" : "Wenn Sie Ihren Samen vergessen haben, haben Sie ihn wahrscheinlich irgendwo aufgeschrieben. Bitte überprüfen Sie Ihre Notizen und schauen Sie sich auf Ihrem Computer um. Wenn Sie es nirgendwo finden, haben Sie möglicherweise Cake Wallet gesichert (in diesem Fall können Sie es aus diesem Backup wiederherstellen.) Wenn keines dieser Probleme auftritt, können wir leider nichts tun.\n" + }, + { + "question" : "Sammeln Sie Informationen zu meiner Brieftasche?", + "answer" : "Cake Wallet sammelt oder zeichnet keine Informationen über Ihre Brieftasche auf. Ihre Privatsphäre ist uns wichtig.\n" + }, + { + "question" : "Kann ich eine Transaktion stornieren?", + "answer" : "Sobald eine Transaktion an die Blockchain gesendet wurde, kann sie leider nicht mehr rückgängig gemacht werden. Sie können die Transaktion jederzeit abbrechen, bevor sie gesendet wird. Überprüfen Sie die Adresse daher immer, bevor Sie eine Transaktion senden.\n" + }, + { + "question" : "Was sind Subadressen und wie verwende ich sie?", + "answer" : "Eine Unteradresse ist im Grunde eine eindeutige Adresse, die Sie jederzeit generieren können. An sie gesendete Münzen landen weiterhin in Ihrer Hauptbrieftasche, aber die Person, die die Münzen sendet, kann Ihre Hauptadresse nicht ermitteln. Unteradressen beginnen immer mit „8“.\nSie können eine neue Unteradresse im Empfangsbildschirm erstellen, indem Sie auf das „+“ neben der Schaltfläche Unteradressen tippen. Geben Sie einen Namen für die Unteradresse ein und tippen Sie auf \"Hinzufügen\". Dann tippen Sie einfach auf den Namen der Subadresse, wenn Sie ihn verwenden möchten!\nWenn Sie paranoid sind, sollten Sie wahrscheinlich jedes Mal, wenn Sie Monero erhalten, eine neue Unteradresse erstellen.\n" + }, + { + "question" : "Was ist eine Transaktions-ID?", + "answer" : "Ein Transaktions-Hash oder eine Transaktions-ID ist eine eindeutige Methode, um eine Transaktion zu identifizieren. Jede Transaktion hat einen eigenen Hash. Wenn Sie jemandem einen Transaktions-Hash zur Verfügung stellen müssen, gehen Sie einfach zum Hauptbildschirm der Brieftasche, tippen Sie auf die Transaktion, halten Sie im oberen Bereich die Taste gedrückt und wählen Sie Kopieren.\n" + }, + { + "question" : "Ich habe meine XMR nicht erhalten! Was kann ich tun?", + "answer" : "Wenn Sie Ihren Monero nicht erhalten haben, möchten Sie möglicherweise auf das Menü ••• tippen und auf Reconnect (Neu verbinden) klicken. Wenn dies nicht funktioniert, gehen Sie in das Einstellungsmenü, tippen Sie auf das Feld \"Aktueller Knoten\" und wählen Sie einen Knoten mit einem grünen Punkt daneben aus.\n" + }, + { + "question" : "Ich habe in der App keine Münzen aus dem Umtausch erhalten. Was kann ich tun?", + "answer" : "Wenn Sie Probleme mit einem Austausch haben, wenden Sie sich am besten an den Austausch. Wir sind eine Partnerschaft mit XMR.TO, Morph und ChangeNow eingegangen. Rufen Sie daher am besten http://xmr.to, http://changenow.io oder http://morphtoken.com auf und wenden Sie sich an deren Support.\n" + }, + { + "question" : "Wie kontaktiere ich den Cake Wallet-Support?", + "answer" : "Senden Sie eine E-Mail an support@cakewallet.io, schließen Sie sich dem Telegramm unter @cake_wallet an oder twittern Sie @CakeWalletXMR!\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_en.json b/assets/faq/faq_en.json new file mode 100644 index 000000000..8624b6ee9 --- /dev/null +++ b/assets/faq/faq_en.json @@ -0,0 +1,58 @@ +[ + { + "question" : "What’s the difference between Available Balance and Full Balance?", + "answer" : "After you make a transaction or receive some Monero, the transaction still needs to be confirmed. In about 20 minutes your available balance should update!\nSometimes, when you send Monero, your available balance will decrease by more than the amount you’ve sent. This is normal, and it’s necessary in order to protect your privacy. Your full balance should be back to normal in 20 minutes.\n" + }, + { + "question" : "How do I send Monero to an exchange that requires a Payment ID?", + "answer" : "Tap the send button on the Wallet screen. Next, copy the exchange deposit address and paste it into the address box. Then, copy the Payment ID provided by the exchange and paste it into the Payment ID box. Finally, enter the amount you’d like to send, and you’re good to go!\n" + }, + { + "question" : "What do I do if I forgot to enter the Payment ID when sending Monero to an exchange?", + "answer" : "While our support can’t directly help you with this issue, it’s a very common problem that most exchanges are used to dealing with. Just contact the exchange support, explain that you forgot to include your Payment ID, and then send them your Transaction ID as proof. You can find the Transaction ID by tapping on the transaction in your Wallet screen.\n" + }, + { + "question" : "What do \"seed\" and \"keys\" mean?", + "answer" : "Your keys encode the private information in your wallet, and are what allow you to spend coins and see incoming transactions.\nYour seed is just a version of your private key written in a way that’s easier for you to write down. Your seed and keys are actually the same thing, just in different forms!\nDO NOT ever give your seed or keys to anyone. Your funds will be stolen if you give out your seed or keys. Please write down your seed, however, and store it in a safe place (this will allow you to restore your wallet if you lose your phone.)\n" + }, + { + "question" : "How many wallets can I create?", + "answer" : "There is no limit! You can create as many wallets as you want.\n" + }, + { + "question" : "How can I restore my wallet?", + "answer" : "Tap the ••• menu, select Wallets, and then choose Restore Wallet. Then enter your seed (or your keys), and optionally enter a date before the first transaction in your wallet (this will speed up the syncing process.) You may need to keep the app open for 15-30 minutes in order to completely restore your wallet.\n" + }, + { + "question" : "What can I do if I lose my seed?", + "answer" : "If you forgot your seed, you likely wrote it down somewhere. Please check your notes and look around on your computer. If you can’t find it anywhere, you may have backed up Cake Wallet (in which case you would be able to restore from that backup.) If none of these work, there is unfortunately nothing that we can do.\n" + }, + { + "question" : "Do you collect any information about my wallet?", + "answer" : "Cake Wallet DOES NOT collect or record any information about your wallet. We care about your privacy.\n" + }, + { + "question" : "Is it possible to reverse a transaction?", + "answer" : "Unfortunately, as soon as a transaction has been submitted to the blockchain, there is no way to undo it. You can always cancel the transaction before it’s sent though, so always double-check the address before you send a transaction.\n" + }, + { + "question" : "What are subaddresses, and how do I use them?", + "answer" : "A subaddress is basically a unique address that you can generate at any time. Coins sent to it will still arrive in your main wallet, but the person sending the coins can’t tell what your main address is. Subaddresses always start with 8.\nYou can make a new subaddress in the Receive screen by tapping the + next to the Subaddresses button. Enter a name for the subaddress and tap Add. Then just tap on the subaddress name when you want to use it!\nIf you’re paranoid, you should probably create a new subaddress every time you receive Monero.\n" + }, + { + "question" : "What is a transaction ID?", + "answer" : "A transaction hash, or transaction ID, is a unique way to identify any transaction. Each transaction has its own hash. If you need to provide a transaction hash to somebody, just go to the main Wallet screen, tap on the transaction, long-press on the top section, and select Copy.\n" + }, + { + "question" : "I didn't receive my XMR! What can I do?", + "answer" : "If you didn't receive your Monero, you might want to tap the ••• menu and hit Reconnect. If that doesn't work, go into the settings menu, tap the 'Current Node' box, and select a node with a green dot next to it.\n" + }, + { + "question" : "I didn't receive my coins from the exchange in the app. What can I do?", + "answer" : "If you're having issues with an exchange, the best option is to contact the exchange itself. We're partnered with XMR.TO, Morph and ChangeNow, so your best bet is to go to http://xmr.to, http://changenow.io, or http://morphtoken.com and contact their support.\n" + }, + { + "question" : "How do I contact Cake Wallet support?", + "answer" : "Email support@cakewallet.io, join the Telegram at @cake_wallet, or tweet @CakeWalletXMR!\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_es.json b/assets/faq/faq_es.json new file mode 100644 index 000000000..438592839 --- /dev/null +++ b/assets/faq/faq_es.json @@ -0,0 +1,58 @@ +[ + { + "question" : "¿Cuál es la diferencia entre el saldo disponible y el saldo total?", + "answer" : "Después de realizar una transacción o recibir algo de Monero, la transacción aún debe confirmarse. ¡En unos 20 minutos su \"saldo disponible\" debería actualizarse!\nA veces, cuando envía Monero, su saldo disponible disminuirá en más de la cantidad que envió. Esto es normal y es necesario para proteger su privacidad. Su \"saldo total\" debería volver a la normalidad en 20 minutos.\n" + }, + { + "question" : "¿Cómo envío a Monero a un intercambio que requiere una ID de pago?", + "answer" : "Toque el botón \"enviar\" en la pantalla de Wallet. A continuación, copie la dirección de depósito del intercambio y péguela en el cuadro \"dirección\". Luego, copie la ID de pago proporcionada por el intercambio y péguela en el cuadro de ID de pago. Finalmente, ingrese la cantidad que desea enviar, ¡y listo!\n" + }, + { + "question" : "¿Qué debo hacer si olvidé ingresar la ID de pago al enviar Monero a un intercambio?", + "answer" : "Si bien nuestro soporte no puede ayudarlo directamente con este problema, es un problema muy común que la mayoría de los intercambios están acostumbrados a resolver. Simplemente comuníquese con el soporte del intercambio, explique que olvidó incluir su ID de pago y luego envíeles su ID de transacción como prueba. Puede encontrar la ID de la transacción tocando la transacción en la pantalla de su billetera.\n" + }, + { + "question" : "¿Qué significan \"semilla\" y \"claves\"?", + "answer" : "Sus claves codifican la información privada en su billetera y son las que le permiten gastar monedas y ver las transacciones entrantes.\nSu semilla es solo una versión de su clave privada escrita de una manera que le resulta más fácil de anotar. ¡Tu semilla y tus llaves son en realidad la misma cosa, solo que en diferentes formas!\nNunca le des tu semilla o llaves a nadie. Sus fondos serán robados si entrega su semilla o llaves. Sin embargo, escriba su semilla y guárdela en un lugar seguro (esto le permitirá restaurar su billetera si pierde su teléfono).\n" + }, + { + "question" : "¿Cuántas billeteras puedo crear?", + "answer" : "¡No hay límite! Puede crear tantas billeteras como desee.\n" + }, + { + "question" : "¿Cómo puedo restaurar mi billetera?", + "answer" : "Toque el menú •••, seleccione \"Carteras\", y luego elija \"Restaurar billetera\". Luego ingrese su semilla (o sus claves), y opcionalmente ingrese una fecha antes de la primera transacción en su billetera (esto acelerará el proceso de sincronización .) Es posible que deba mantener la aplicación abierta durante 15-30 minutos para restaurar completamente su billetera.\n" + }, + { + "question" : "¿Qué puedo hacer si pierdo mi semilla?", + "answer" : "Si olvidó su semilla, probablemente la escribió en alguna parte. Verifique sus notas y mire a su alrededor en su computadora. Si no puede encontrarlo en ninguna parte, es posible que haya realizado una copia de seguridad de Cake Wallet (en cuyo caso podría restaurar desde esa copia de seguridad). Si ninguno de estos funciona, desafortunadamente no hay nada que podamos hacer.\n" + }, + { + "question" : "¿Recopila información sobre mi billetera?", + "answer" : "Cake Wallet NO recopila ni registra ninguna información sobre su billetera. Nos preocupamos por su privacidad.\n" + }, + { + "question" : "¿Es posible revertir una transacción?", + "answer" : "Desafortunadamente, tan pronto como se haya enviado una transacción a la cadena de bloques, no hay forma de deshacerla. Sin embargo, siempre puede cancelar la transacción antes de enviarla, así que siempre verifique la dirección antes de enviar una transacción.\n" + }, + { + "question" : "¿Qué son las \"subdirecciones\" y cómo las uso?", + "answer" : "Una subdirección es básicamente una dirección única que puede generar en cualquier momento. Las monedas que se le envíen seguirán llegando a su billetera principal, pero la persona que envía las monedas no puede decir cuál es su dirección principal. Las subdirecciones siempre comienzan con \"8\".\nPuede hacer una nueva subdirección en la pantalla Recibir tocando el \"+\" al lado del botón Subdirecciones. Ingrese un nombre para la subdirección y toque “Agregar”. Luego, solo toca el nombre de la subdirección cuando quieras usarlo.\nSi eres paranoico, probablemente deberías crear una nueva subdirección cada vez que recibas Monero.\n" + }, + { + "question" : "¿Qué es una ID de transacción?", + "answer" : "Un hash de transacción, o ID de transacción, es una forma única de identificar cualquier transacción. Cada transacción tiene su propio hash. Si necesita proporcionar un hash de transacción a alguien, simplemente vaya a la pantalla principal de Wallet, toque la transacción, mantenga presionada la sección superior y seleccione Copiar.\n" + }, + { + "question" : "¡No recibí mi XMR! ¿Que puedo hacer?", + "answer" : "Si no recibió su Monero, puede tocar el menú ••• y presionar Volver a conectar. Si eso no funciona, vaya al menú de configuración, toque el cuadro 'Nodo actual' y seleccione un nodo con un punto verde al lado.\n" + }, + { + "question" : "No recibí mis monedas del intercambio en la aplicación. ¿Que puedo hacer?", + "answer" : "Si tiene problemas con un intercambio, la mejor opción es ponerse en contacto con el intercambio en sí. Estamos asociados con XMR.TO, Morph y ChangeNow, por lo que su mejor opción es ir a http://xmr.to, http://changenow.io o http://morphtoken.com y contactar a su soporte.\n" + }, + { + "question" : "¿Cómo contacto al soporte de Cake Wallet?", + "answer" : "¡Envíe un correo electrónico a support@cakewallet.io, únase al Telegram en @cake_wallet o envíe un tweet a @CakeWalletXMR!\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_hi.json b/assets/faq/faq_hi.json new file mode 100644 index 000000000..e9b87083d --- /dev/null +++ b/assets/faq/faq_hi.json @@ -0,0 +1,58 @@ +[ + { + "question" : "उपलब्ध शेष और पूर्ण शेष के बीच क्या अंतर है?", + "answer" : "आपके द्वारा लेन-देन करने या कुछ मोनरो प्राप्त करने के बाद, लेन-देन की पुष्टि करने की आवश्यकता है। लगभग 20 मिनट में आपका \"उपलब्ध शेष\" अपडेट होना चाहिए!\nकभी-कभी, जब आप Monero भेजते हैं, तो आपका उपलब्ध शेष राशि आपके द्वारा भेजी गई राशि से अधिक घट जाएगी। यह सामान्य है, और आपकी गोपनीयता की सुरक्षा के लिए यह आवश्यक है। आपका \"पूर्ण संतुलन\" 20 मिनट में वापस सामान्य हो जाना चाहिए।\n" + }, + { + "question" : "मैं एक एक्सचेंज को एक भुगतान आईडी के लिए मोनेरो को कैसे भेजूं?", + "answer" : "वॉलेट स्क्रीन पर \"भेजें\" बटन पर टैप करें। इसके बाद, एक्सचेंज के जमा पते को कॉपी करें और इसे \"एड्रेस\" बॉक्स में पेस्ट करें। फिर, एक्सचेंज द्वारा प्रदान की गई भुगतान आईडी को कॉपी करें और भुगतान आईडी बॉक्स में पेस्ट करें। अंत में, वह राशि दर्ज करें जिसे आप भेजना चाहते हैं, और आप जाना चाहते हैं!\n" + }, + { + "question" : "यदि मैं एक मुद्रा में मोनरो भेजते समय भुगतान आईडी दर्ज करना भूल गया तो मैं क्या करूँ?", + "answer" : "जबकि हमारा समर्थन सीधे इस मुद्दे पर आपकी सहायता नहीं कर सकता है, यह एक बहुत ही आम समस्या है जिससे निपटने के लिए ज्यादातर एक्सचेंजों का उपयोग किया जाता है। एक्सचेंज के समर्थन से संपर्क करें, समझाएं कि आप अपनी भुगतान आईडी को शामिल करना भूल गए हैं, और फिर उन्हें प्रमाण के रूप में अपनी लेन-देन आईडी भेजें। आप अपने वॉलेट स्क्रीन में लेनदेन पर टैप करके ट्रांजेक्शन आईडी पा सकते हैं।\n" + }, + { + "question" : "\"बीज\" और \"कुंजियाँ\" का क्या अर्थ है?", + "answer" : "आपकी कुंजी आपके बटुए में निजी जानकारी को कूटबद्ध करती है, और जो आपको सिक्के खर्च करने और आने वाले लेनदेन को देखने की अनुमति देती है।\nआपका बीज आपकी निजी कुंजी का एक संस्करण है जो इस तरह से लिखा गया है कि आपको लिखना आसान है। आपके बीज और चाबियाँ वास्तव में एक ही चीज़ हैं, बस अलग-अलग रूपों में!\nकभी भी अपना बीज या चाबी किसी को न दें। यदि आप अपना बीज या चाबियां देते हैं तो आपके धन की चोरी हो जाएगी। कृपया, अपने बीज को नीचे लिखें, और इसे एक सुरक्षित स्थान पर संग्रहीत करें (यदि आप अपना फोन खो देते हैं तो यह आपको अपने बटुए को पुनर्स्थापित करने की अनुमति देगा।)\n" + }, + { + "question" : "मैं कितने वॉलेट बना सकता हूं?", + "answer" : "कोई सीमा नही है! आप जितने चाहें उतने पर्स बना सकते हैं।\n" + }, + { + "question" : "मैं अपना बटुआ कैसे पुनर्स्थापित कर सकता हूं?", + "answer" : "••• मेनू पर टैप करें, \"वॉलेट्स\" चुनें, और फिर \"रिस्टोर वॉलेट\" चुनें। फिर अपना बीज (या अपनी चाबियाँ) दर्ज करें, और वैकल्पिक रूप से अपने वॉलेट में पहले लेनदेन से पहले एक तिथि दर्ज करें (यह सिंकिंग प्रक्रिया को गति देगा। ।) आपको अपने वॉलेट को पूरी तरह से बहाल करने के लिए ऐप को 15-30 मिनट तक खुला रखने की आवश्यकता हो सकती है।\n" + }, + { + "question" : "अगर मैं अपना बीज खो देता हूं तो मैं क्या कर सकता हूं?", + "answer" : "यदि आप अपना बीज भूल गए हैं, तो आप इसे कहीं न कहीं लिख सकते हैं। कृपया अपने नोट्स देखें और अपने कंप्यूटर पर देखें। यदि आप इसे कहीं भी नहीं पा सकते हैं, तो आपने केक वॉलेट का बैकअप ले लिया होगा (जिस स्थिति में आप उस बैकअप से पुनर्स्थापित कर पाएंगे।) यदि इनमें से कोई भी काम नहीं है, तो दुर्भाग्य से ऐसा कुछ भी नहीं है जो हम कर सकते हैं।\n" + }, + { + "question" : "क्या आप मेरे बटुए के बारे में कोई जानकारी एकत्र करते हैं?", + "answer" : "केक वॉलेट आपके बटुए के बारे में कोई जानकारी एकत्र या रिकॉर्ड नहीं करता है। हमें आपकी गोपनीयता का ख्याल है।\n" + }, + { + "question" : "क्या किसी लेनदेन को उल्टा करना संभव है?", + "answer" : "दुर्भाग्य से, जैसे ही एक लेन-देन ब्लॉकचैन को सबमिट किया गया है, इसे पूर्ववत करने का कोई तरीका नहीं है। आप इसे भेजे जाने से पहले लेनदेन को हमेशा रद्द कर सकते हैं, इसलिए लेन-देन भेजने से पहले हमेशा पते की दोबारा जांच करें।\n" + }, + { + "question" : "\"उप-प्रजातियां\" क्या हैं, और मैं उनका उपयोग कैसे करूं?", + "answer" : "एक सबड्रेस मूल रूप से एक अनूठा पता है जिसे आप किसी भी समय उत्पन्न कर सकते हैं। इसके लिए भेजे गए सिक्के अभी भी आपके मुख्य वॉलेट में आएंगे, लेकिन सिक्के भेजने वाला व्यक्ति यह नहीं बता सकता है कि आपका मुख्य पता क्या है। Subaddresses हमेशा \"8\" से शुरू होता है।\nआप Subaddresses बटन के बगल में \"+\" टैप करके प्राप्त स्क्रीन में एक नई उप-प्रसंग बना सकते हैं। सबड्रेस के लिए एक नाम दर्ज करें और \"जोड़ें\" पर टैप करें। फिर जब आप इसका उपयोग करना चाहते हैं तो बस सबड्रेस नाम पर टैप करें!\nयदि आप अपाहिज हैं, तो आपको हर बार मोनरो प्राप्त करने के लिए एक नया सबड्रेस बनाना चाहिए।\n" + }, + { + "question" : "लेनदेन आईडी क्या है?", + "answer" : "लेन-देन हैश, या लेनदेन आईडी, किसी भी लेनदेन की पहचान करने का एक अनूठा तरीका है। प्रत्येक लेनदेन का अपना हैश होता है। यदि आपको किसी को लेन-देन हैश प्रदान करने की आवश्यकता है, तो बस मुख्य वॉलेट स्क्रीन पर जाएं, लेन-देन पर टैप करें, शीर्ष अनुभाग पर लंबे समय तक दबाएं और प्रतिलिपि का चयन करें।\n" + }, + { + "question" : "मैं अपने XMR प्राप्त नहीं किया! मैं क्या कर सकता हूँ?", + "answer" : "यदि आपको अपना मोनरो प्राप्त नहीं हुआ, तो आप ••• मेनू पर टैप कर सकते हैं और रीकनेक्ट को हिट कर सकते हैं। यदि वह काम नहीं करता है, तो सेटिंग मेनू में जाएं, 'करंट नोड' बॉक्स पर टैप करें, और उसके बगल में हरे रंग की डॉट के साथ एक नोड चुनें।\n" + }, + { + "question" : "मुझे ऐप में एक्सचेंज से मेरे सिक्के नहीं मिले। मैं क्या कर सकता हूँ?", + "answer" : "यदि आप एक एक्सचेंज के साथ समस्या कर रहे हैं, तो सबसे अच्छा विकल्प एक्सचेंज से संपर्क करना है। हम XMR.TO, Morph और ChangeNow के साथ भागीदारी कर रहे हैं, इसलिए आपका सबसे अच्छा दांव http://xmr.to, http://changenow.io, या http://morphtoken.com पर जाना है और उनके समर्थन से संपर्क करना है।\n" + }, + { + "question" : "मैं केक वॉलेट से कैसे संपर्क करूं?", + "answer" : "ईमेल support@cakewallet.io, @cake_wallet पर टेलीग्राम में शामिल हों, या @CakeWalletXMR पर ट्वीट करें!\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_ja.json b/assets/faq/faq_ja.json new file mode 100644 index 000000000..cdae6aea6 --- /dev/null +++ b/assets/faq/faq_ja.json @@ -0,0 +1,58 @@ +[ + { + "question" : "Available BalanceとFull Balanceの違いは何ですか?", + "answer" : "取引を行うか、Moneroを受け取った後でも、取引を確認する必要があります。 約20分で「利用可能残高」が更新されます!\nMoneroを送信すると、利用可能な残高が送信した金額よりも少なくなる場合があります。 これは正常な動作であり、プライバシーを保護するために必要です。 「フルバランス」は20分で通常に戻ります。\n" + }, + { + "question" : "支払いIDが必要な取引所にMoneroを送信するにはどうすればよいですか?", + "answer" : "ウォレット画面の「送信」ボタンをタップします。 次に、取引所のデポジットアドレスをコピーして、「アドレス」ボックスに貼り付けます。 次に、取引所から提供された支払いIDをコピーして、[支払いID]ボックスに貼り付けます。 最後に、送信したい金額を入力すると、準備完了です!\n" + }, + { + "question" : "Moneroを取引所に送信するときに支払いIDの入力を忘れた場合はどうすればよいですか?", + "answer" : "のサポートはこの問題を直接支援することはできませんが、ほとんどの取引所が対処に慣れている非常に一般的な問題です。交換サポートに連絡し、支払いIDを含めるのを忘れたことを説明してから、証拠としてトランザクションIDを送信してください。ウォレット画面でトランザクションをタップすると、トランザクションIDを見つけることができます。\n" + }, + { + "question" : "「シード」と「キー」はどういう意味ですか?", + "answer" : "キーはウォレットの個人情報をエンコードし、コインを使って着信トランザクションを確認できるようにします。\nシードは、書きやすいように書かれた秘密鍵の単なるバージョンです。 シードとキーは実際には同じもので、異なる形式になっています!\nシードやキーを誰にも渡さないでください。 シードまたはキーを渡すと、資金が盗まれます。 ただし、シードを書き留めて安全な場所に保管してください(これにより、携帯電話を紛失した場合にウォレットを復元できます。\n" + }, + { + "question" : "いくつのウォレットを作成できますか?", + "answer" : "制限はありません! ウォレットはいくつでも作成できます。\n" + }, + { + "question" : "ウォレットを復元するにはどうすればよいですか?", + "answer" : "•••メニューをタップし、「ウォレット」を選択し、「ウォレットの復元」を選択します。次に、シード(またはキー)を入力し、オプションでウォレットの最初のトランザクションの前に日付を入力します(これにより同期プロセスが高速化されます) 。)ウォレットを完全に復元するには、アプリを15〜30分間開いたままにする必要がある場合があります。\n" + }, + { + "question" : "種を失ったらどうすればいいですか?", + "answer" : "種を忘れた場合は、どこかに書き留めている可能性があります。 のメモを確認して、コンピューターを見てください。 どこにも見つからない場合は、Cake Walletをバックアップしている可能性があります(この場合、そのバックアップから復元できます)。これらのいずれも機能しない場合、残念ながら何もできません。\n" + }, + { + "question" : "ウォレットに関する情報を収集しますか?", + "answer" : "Cake Walletは、ウォレットに関する情報を収集または記録しません。 私たちはあなたのプライバシーを大切にします。\n" + }, + { + "question" : "トランザクションを取り消すことは可能ですか?", + "answer" : "残念ながら、トランザクションがブロックチェーンに送信されるとすぐに、元に戻す方法はありません。 ただし、トランザクションは送信前にいつでもキャンセルできます。したがって、トランザクションを送信する前に必ずアドレスを再確認してください。\n" + }, + { + "question" : "「サブアドレス」とは何ですか。どのように使用しますか?", + "answer" : "サブアドレスは基本的に、いつでも生成できる一意のアドレスです。 送られたコインはメインのウォレットに届きますが、コインを送った人はメインの住所を知ることができません。 サブアドレスは常に「8」で始まります。\n[サブアドレス]ボタンの横にある[+]をタップすると、受信画面で新しいサブアドレスを作成できます。 サブアドレスの名前を入力し、「追加」をタップします。 次に、使用したいサブアドレス名をタップします!\n妄想している場合は、Moneroを受信するたびに新しいサブアドレスを作成する必要があります。\n" + }, + { + "question" : "トランザクションIDとは何ですか?", + "answer" : "トランザクションハッシュ、またはトランザクションIDは、トランザクションを識別するユニークな方法です。 各トランザクションには独自のハッシュがあります。 誰かにトランザクションハッシュを提供する必要がある場合は、ウォレットのメイン画面に移動し、トランザクションをタップして、上部セクションを長押しし、[コピー]を選択します。\n" + }, + { + "question" : "XMRを受け取りませんでした! 私に何ができる?", + "answer" : "Moneroを受け取っていない場合は、•••メニューをタップして、[再接続]を押します。 それでもうまくいかない場合は、設定メニューに移動し、「現在のノード」ボックスをタップして、横に緑色の点があるノードを選択します。\n" + }, + { + "question" : "アプリの取引所からコインを受け取りませんでした。 私に何ができる?", + "answer" : "取引所に問題がある場合、最良の選択肢は取引所自体に連絡することです。 XMR.TO、Morph、ChangeNowと提携しているため、最善の策はhttp://xmr.to、http://changenow.io、またはhttp://morphtoken.comにアクセスしてサポートに連絡することです。\n" + }, + { + "question" : "Cake Walletサポートに連絡するにはどうすればよいですか?", + "answer" : "support@cakewallet.ioにメールを送信するか、@ cake_walletで電報に参加するか、@ CakeWalletXMRにツイートしてください。\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_ko.json b/assets/faq/faq_ko.json new file mode 100644 index 000000000..1e2c39b9f --- /dev/null +++ b/assets/faq/faq_ko.json @@ -0,0 +1,58 @@ +[ + { + "question" : "가용 잔액과 전체 잔액의 차이점은 무엇입니까?", + "answer" : "거래를하거나 일부 Monero를받은 후에도 거래를 확인해야합니다. 약 20 분 후에“사용 가능한 잔액”이 업데이트됩니다!\n때로는 Monero를 보낼 때 사용 가능한 잔액이 보낸 금액보다 많이 줄어 듭니다. 이는 정상적인 현상이며 개인 정보를 보호하기 위해 필요합니다. \"풀 밸런스\"는 20 분 후에 정상으로 돌아옵니다.\n" + }, + { + "question" : "지불 ID가 필요한 거래소에 Monero를 보내려면 어떻게해야합니까?", + "answer" : "월렛 화면에서 \"보내기\"버튼을 누릅니다. 그런 다음 교환기 예금 주소를 복사하여 \"주소\"상자에 붙여 넣습니다. 그런 다음 교환에서 제공 한 지불 ID를 복사하여 지불 ID 상자에 붙여 넣으십시오. 마지막으로 송금 할 금액을 입력하면 좋습니다.\n" + }, + { + "question" : "Monero를 교환소에 보낼 때 지불 ID를 입력하지 않은 경우 어떻게해야합니까?", + "answer" : "의 지원으로이 문제를 직접 해결할 수는 없지만 대부분의 거래소에서 처리하는 데 매우 일반적인 문제입니다. 거래소의 지원팀에 연락하여 결제 ID를 잊어 버렸다고 설명하고 거래 ID를 증거로 보내십시오. 월렛 화면에서 거래를 탭하여 거래 ID를 찾을 수 있습니다.\n" + }, + { + "question" : "\"씨\"와 \"키\"는 무엇을 의미합니까?", + "answer" : "열쇠는 지갑에 개인 정보를 암호화하여 동전을 사용하고 들어오는 거래를 볼 수있게 해줍니다.\n귀하의 시드는 귀하가 쉽게 기록 할 수있는 방식으로 작성된 개인 키 버전입니다. 당신의 씨앗과 열쇠는 실제로는 다른 형태로 동일합니다!\n씨앗이나 열쇠를 다른 사람에게주지 마십시오. 씨앗이나 열쇠를 내면 자금이 도용됩니다. 그러나 씨앗을 기록하여 안전한 곳에 보관하십시오 (휴대 전화를 분실 한 경우 지갑을 복원 할 수 있습니다).\n" + }, + { + "question" : "몇 개의 지갑을 만들 수 있습니까?", + "answer" : "제한이 없습니다! 원하는만큼 지갑을 만들 수 있습니다.\n" + }, + { + "question" : "지갑을 어떻게 복원 할 수 있습니까?", + "answer" : "••• 메뉴를 탭하고 \"월렛\"을 선택한 다음 \"지갑 복원\"을 선택하십시오. 그런 다음 시드 (또는 키)를 입력하고 선택적으로 지갑의 첫 거래 전에 날짜를 입력하십시오 (동기화 속도가 빨라집니다) .) 지갑을 완전히 복원하려면 앱을 15-30 분 동안 열어 두어야합니다.\n" + }, + { + "question" : "씨앗을 잃어 버리면 어떻게해야합니까?", + "answer" : "씨앗을 잊어 버렸다면 아마 어딘가에 썼을 것입니다. 메모를 확인하고 컴퓨터를 둘러보십시오. 어디에서나 찾을 수 없다면 Cake Wallet을 백업했을 수 있습니다 (이 경우 해당 백업에서 복원 할 수 있습니다). 이러한 작업이 없으면 불행히도 우리가 할 수있는 일이 없습니다.\n" + }, + { + "question" : "내 지갑에 대한 정보를 수집합니까?", + "answer" : "케이크 지갑은 지갑에 대한 정보를 수집하거나 기록하지 않습니다. 우리는 당신의 프라이버시를 걱정합니다.\n" + }, + { + "question" : "거래를 취소 할 수 있습니까?", + "answer" : "불행히도, 거래가 블록 체인에 제출 되 자마자 취소 할 수있는 방법이 없습니다. 거래가 전송되기 전에 언제든지 취소 할 수 있으므로 거래를 보내기 전에 항상 주소를 다시 확인하십시오.\n" + }, + { + "question" : "하위 주소 란 무엇이며 어떻게 사용합니까?", + "answer" : "하위 주소는 기본적으로 언제든지 생성 할 수있는 고유 한 주소입니다. 동전은 여전히 주 지갑에 도착하지만 동전을 보낸 사람은 자신의 주 주소가 무엇인지 알 수 없습니다. 하위 주소는 항상 \"8\"로 시작합니다.\n하위 주소 버튼 옆에있는 \"+\"를 누르면 수신 화면에서 새 하위 주소를 만들 수 있습니다. 하위 주소의 이름을 입력하고 \"추가\"를 누릅니다. 그런 다음 사용하려는 하위 주소 이름을 탭하십시오!\n편집증이라면 Monero를받을 때마다 새 하위 주소를 만들어야합니다.\n" + }, + { + "question" : "거래 ID는 무엇입니까?", + "answer" : "트랜잭션 해시 또는 트랜잭션 ID는 트랜잭션을 식별하는 고유 한 방법입니다. 각 트랜잭션에는 자체 해시가 있습니다. 누군가에게 거래 해시를 제공해야하는 경우 주 지갑 화면으로 이동하여 거래를 탭하고 상단 섹션을 길게 누른 다음 복사를 선택하십시오.\n" + }, + { + "question" : "XMR을받지 못했습니다! 내가 무엇을 할 수 있을지?", + "answer" : "Monero를받지 못했다면 ••• 메뉴를 누르고 다시 연결을 누르십시오. 그래도 작동하지 않으면 설정 메뉴로 이동하여 '현재 노드'상자를 누르고 옆에 녹색 점이있는 노드를 선택하십시오.\n" + }, + { + "question" : "앱의 거래소에서 동전을받지 못했습니다. 내가 무엇을 할 수 있을지?", + "answer" : "교환에 문제가있는 경우 교환기에 연락하는 것이 가장 좋습니다. 우리는 XMR.TO, Morph 및 ChangeNow와 파트너 관계를 맺고 있으므로 가장 좋은 방법은 http://xmr.to, http://changenow.io 또는 http://morphtoken.com으로 이동하여 지원 부서에 문의하는 것입니다.\n" + }, + { + "question" : "Cake Wallet 지원팀에 연락하려면 어떻게해야합니까?", + "answer" : "support@cakewallet.io로 이메일을 보내거나 @cake_wallet에서 전보에 가입하거나 @CakeWalletXMR을 트윗하십시오!\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_nl.json b/assets/faq/faq_nl.json new file mode 100644 index 000000000..a080a9bbc --- /dev/null +++ b/assets/faq/faq_nl.json @@ -0,0 +1,58 @@ +[ + { + "question" : "Wat is het verschil tussen beschikbaar saldo en volledig saldo?", + "answer" : "Nadat u een transactie heeft uitgevoerd of wat Monero heeft ontvangen, moet de transactie nog worden bevestigd. Over ongeveer 20 minuten zou uw \"beschikbare saldo\" moeten zijn bijgewerkt!\nWanneer u Monero verzendt, daalt uw beschikbare saldo soms met meer dan het bedrag dat u heeft verzonden. Dit is normaal en dit is noodzakelijk om uw privacy te beschermen. Uw \"volledige balans\" zou binnen 20 minuten weer normaal moeten zijn.\n" + }, + { + "question" : "Hoe stuur ik Monero naar een beurs die een betalings-ID vereist?", + "answer" : "Tik op de knop \"Verzenden\" op het Wallet-scherm. Kopieer vervolgens het stortingsadres van de uitwisseling en plak het in het vak \"adres\". Kopieer vervolgens de betalings-ID van de centrale en plak deze in het vak Betalings-ID. Voer ten slotte het bedrag in dat u wilt verzenden en u bent klaar om te gaan!\n" + }, + { + "question" : "Wat moet ik doen als ik ben vergeten de betalings-ID in te voeren wanneer ik Monero naar een beurs stuur?", + "answer" : "Hoewel onze ondersteuning u niet rechtstreeks met dit probleem kan helpen, is het een veel voorkomend probleem dat de meeste beurzen gewend zijn aan te pakken. Neem contact op met de ondersteuning van de beurs, leg uit dat u bent vergeten uw betalings-ID op te nemen en stuur ze vervolgens uw transactie-ID als bewijs. U kunt de transactie-ID vinden door op de transactie in uw Wallet-scherm te tikken.\n" + }, + { + "question" : "Wat betekenen zaad en sleutels?", + "answer" : "Uw sleutels coderen de privé-informatie in uw portemonnee en stellen u in staat munten uit te geven en inkomende transacties te bekijken.\nUw seed is slechts een versie van uw privésleutel die zo is geschreven dat u deze gemakkelijker kunt opschrijven. Je zaad en sleutels zijn eigenlijk hetzelfde, alleen in verschillende vormen!\nGeef NOOIT uw zaad of sleutels aan iemand. Je geld wordt gestolen als je je zaad of sleutels weggeeft. Schrijf uw zaad echter op en bewaar het op een veilige plaats (hiermee kunt u uw portemonnee herstellen als u uw telefoon verliest.)\n" + }, + { + "question" : "Hoeveel portefeuilles kan ik maken?", + "answer" : "Er is geen limiet! U kunt zoveel portefeuilles maken als u wilt.\n" + }, + { + "question" : "Hoe kan ik mijn portemonnee herstellen?", + "answer" : "Tik op het ••• menu, selecteer \"Portefeuilles\" en kies vervolgens \"Portemonnee herstellen\". Voer vervolgens uw seed (of uw sleutels) in en voer optioneel een datum in vóór de eerste transactie in uw portefeuille (dit versnelt het synchronisatieproces .) Mogelijk moet u de app 15-30 minuten open houden om uw portemonnee volledig te herstellen.\n" + }, + { + "question" : "Wat kan ik doen als ik mijn zaad verlies?", + "answer" : "Als je je zaad bent vergeten, heb je het waarschijnlijk ergens opgeschreven. Controleer uw aantekeningen en kijk rond op uw computer. Als je het nergens kunt vinden, heb je misschien een back-up gemaakt van Cake Wallet (in welk geval je in staat zou zijn om vanuit die back-up te herstellen.) Als geen van deze werken, kunnen we helaas niets doen.\n" + }, + { + "question" : "Verzamel je informatie over mijn portemonnee?", + "answer" : "Cake Wallet verzamelt of registreert GEEN informatie over uw portemonnee. Wij geven om uw privacy.\n" + }, + { + "question" : "Is het mogelijk om een transactie ongedaan te maken?", + "answer" : "Helaas is het niet mogelijk om een transactie ongedaan te maken zodra een transactie bij de blockchain is ingediend. U kunt de transactie echter altijd annuleren voordat deze wordt verzonden, dus controleer altijd het adres voordat u een transactie verzendt.\n" + }, + { + "question" : "Wat zijn \"subadressen\" en hoe gebruik ik deze?", + "answer" : "Een subadres is eigenlijk een uniek adres dat u op elk gewenst moment kunt genereren. Munten die er naartoe worden verzonden, komen nog steeds in uw hoofdportemonnee, maar de persoon die de munten verzendt, kan niet zien wat uw hoofdadres is. Subadressen beginnen altijd met \"8\".\nU kunt een nieuw subadres maken in het scherm Ontvangen door te tikken op de \"+\" naast de knop Subadressen. Voer een naam in voor het subadres en tik op \"Toevoegen\". Tik vervolgens op de naam van het subadres wanneer u deze wilt gebruiken!\nAls u paranoïde bent, moet u waarschijnlijk elke keer dat u Monero ontvangt een nieuw subadres maken.\n" + }, + { + "question" : "Wat is een transactie-ID?", + "answer" : "Een transactie-hash of transactie-ID is een unieke manier om een transactie te identificeren. Elke transactie heeft zijn eigen hash. Als u iemand een transactie-hash moet geven, gaat u gewoon naar het hoofdscherm van Wallet, tikt u op de transactie, drukt u lang op het bovenste gedeelte en selecteert u Kopiëren.\n" + }, + { + "question" : "Ik heb mijn XMR niet ontvangen! Wat kan ik doen?", + "answer" : "Als u uw Monero niet hebt ontvangen, wilt u misschien op het ••• menu tikken en op Opnieuw verbinden klikken. Als dat niet werkt, gaat u naar het instellingenmenu, tikt u op het vak 'Huidige knooppunt' en selecteert u een knooppunt met een groene stip ernaast.\n" + }, + { + "question" : "Ik heb mijn munten niet ontvangen van de beurs in de app. Wat kan ik doen?", + "answer" : "Als u problemen ondervindt met een uitwisseling, kunt u het beste contact opnemen met de uitwisseling zelf. We werken samen met XMR.TO, Morph en ChangeNow, dus u kunt het beste naar http://xmr.to, http://changenow.io of http://morphtoken.com gaan en contact opnemen met hun ondersteuning.\n" + }, + { + "question" : "Hoe neem ik contact op met Cake Wallet-ondersteuning?", + "answer" : "E-mail support@cakewallet.io, word lid van het Telegram op @cake_wallet of tweet @CakeWalletXMR!\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_pl.json b/assets/faq/faq_pl.json new file mode 100644 index 000000000..a3af8deb6 --- /dev/null +++ b/assets/faq/faq_pl.json @@ -0,0 +1,58 @@ +[ + { + "question" : "Jaka jest różnica między dostępnym saldem a pełnym saldem?", + "answer" : "Po dokonaniu transakcji lub otrzymaniu Monero transakcja nadal musi zostać potwierdzona. Za około 20 minut Twoje „dostępne saldo” powinno się zaktualizować!\nCzasami po wysłaniu Monero dostępne saldo zmniejsza się o więcej niż kwota wysłana. Jest to normalne i konieczne w celu ochrony Twojej prywatności. Twoja „pełna równowaga” powinna wrócić do normy za 20 minut.\n" + }, + { + "question" : "Jak wysłać Monero na giełdę, która wymaga płatności ID?", + "answer" : "Naciśnij przycisk „wyślij” na ekranie portfela. Następnie skopiuj adres depozytu giełdy i wklej go w polu „adres”. Następnie skopiuj identyfikator płatności podany przez giełdę i wklej go w polu ID płatności. Na koniec wprowadź kwotę, którą chcesz wysłać, i możesz zacząć!\n" + }, + { + "question" : "Co mam zrobić, jeśli zapomniałem wprowadzić ID płatności podczas wysyłania Monero na giełdę?", + "answer" : "Chociaż nasze wsparcie nie może bezpośrednio pomóc w rozwiązaniu tego problemu, jest to bardzo częsty problem, z którym korzysta większość giełd. Wystarczy skontaktować się z obsługą giełdy, wyjaśnić, że zapomniałeś podać ID płatności, a następnie przesłać im dowód transakcji. Możesz znaleźć ID transakcji, dotykając transakcji na ekranie portfela.\n" + }, + { + "question" : "Co oznaczają słowa „seed” i „keys”?", + "answer" : "Twoje klucze kodują prywatne informacje w twoim portfelu i pozwalają wydać monety i zobaczyć przychodzące transakcje.\nTwoje ziarno to tylko wersja twojego klucza prywatnego napisana w sposób, który łatwiej Ci zapisać. Wasze nasiona i klucze są w rzeczywistości takie same, tylko w różnych formach!\nNigdy nie dawaj nikomu swojego ziarna ani kluczy. Twoje fundusze zostaną skradzione, jeśli wydasz swoje nasiona lub klucze. Zapisz jednak swoje ziarno i przechowuj je w bezpiecznym miejscu (pozwoli to przywrócić portfel, jeśli zgubisz telefon).\n" + }, + { + "question" : "Ile portfeli mogę utworzyć?", + "answer" : "Tu nie ma limitu! Możesz utworzyć dowolną liczbę portfeli.\n" + }, + { + "question" : "Jak mogę przywrócić mój portfel?", + "answer" : "Stuknij menu •••, wybierz „Portfele”, a następnie „Przywróć portfel”. Następnie wprowadź dane początkowe (lub klucze) i opcjonalnie wprowadź datę przed pierwszą transakcją w portfelu (przyspieszy to proces synchronizacji .) Może być konieczne pozostawienie aplikacji otwartej przez 15-30 minut, aby całkowicie przywrócić portfel.\n" + }, + { + "question" : "Co mogę zrobić, jeśli stracę nasiona?", + "answer" : "Jeśli zapomniałeś o nasieniu, prawdopodobnie gdzieś je zapisałeś. Sprawdź swoje notatki i rozejrzyj się po komputerze. Jeśli nie możesz go nigdzie znaleźć, być może utworzono kopię zapasową Cake Wallet (w takim przypadku będziesz mógł przywrócić dane z tej kopii zapasowej). Jeśli żadna z tych czynności nie działa, niestety nic nie możemy zrobić.\n" + }, + { + "question" : "Czy zbierasz jakieś informacje o moim portfelu?", + "answer" : "Portfel Cake NIE gromadzi ani nie rejestruje żadnych informacji o Twoim portfelu. Dbamy o Twoją prywatność.\n" + }, + { + "question" : "Czy można cofnąć transakcję?", + "answer" : "Niestety, jak tylko transakcja zostanie przesłana do blockchain, nie ma możliwości jej cofnięcia. Zawsze możesz jednak anulować transakcję przed jej wysłaniem, dlatego zawsze dokładnie sprawdź adres przed wysłaniem transakcji.\n" + }, + { + "question" : "Co to są „podadresy” i jak z nich korzystać?", + "answer" : "Podadres jest w zasadzie unikalnym adresem, który można wygenerować w dowolnym momencie. Monety wysłane do niego nadal będą pojawiać się w głównym portfelu, ale osoba wysyłająca monety nie może podać Twojego głównego adresu. Podadresy zawsze zaczynają się od „8”.\nMożesz utworzyć nowy podadres na ekranie Odbieranie, dotykając „+” obok przycisku Podadresy. Wprowadź nazwę podadresu i dotknij „Dodaj”. Następnie dotknij nazwy podadresu, gdy chcesz go użyć!\nJeśli jesteś paranoikiem, prawdopodobnie za każdym razem, gdy otrzymasz Monero, powinieneś utworzyć nowy podadres.\n" + }, + { + "question" : "Co to jest ID transakcji?", + "answer" : "Skrót transakcji lub ID transakcji to unikalny sposób identyfikowania każdej transakcji. Każda transakcja ma swój własny skrót. Jeśli chcesz podać komuś skrót transakcji, po prostu przejdź do głównego ekranu Portfela, dotknij transakcji, naciśnij długo w górnej części i wybierz Kopiuj.\n" + }, + { + "question" : "Nie otrzymałem mojego XMR! Co mogę zrobić?", + "answer" : "Jeśli nie otrzymałeś Monero, możesz dotknąć menu ••• i nacisnąć Połącz ponownie. Jeśli to nie zadziała, przejdź do menu ustawień, dotknij pola „Bieżący węzeł” i wybierz węzeł z zieloną kropką obok niego.\n" + }, + { + "question" : "Nie otrzymałem moich monet z wymiany w aplikacji. Co mogę zrobić?", + "answer" : "Jeśli masz problemy z wymianą, najlepszym rozwiązaniem jest skontaktowanie się z samą giełdą. Współpracujemy z XMR.TO, Morph i ChangeNow, więc najlepiej postawić się na stronie http://xmr.to, http://changenow.io lub http://morphtoken.com i skontaktować się z ich wsparciem.\n" + }, + { + "question" : "Jak skontaktować się z obsługą Cake Wallet?", + "answer" : "Wyślij e-mail na adres support@cakewallet.io, dołącz do telegramu na @cake_wallet lub tweet @CakeWalletXMR!\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_pt.json b/assets/faq/faq_pt.json new file mode 100644 index 000000000..210ec5896 --- /dev/null +++ b/assets/faq/faq_pt.json @@ -0,0 +1,58 @@ +[ + { + "question" : "Qual é a diferença entre Saldo disponível e Saldo total?", + "answer" : "Depois de fazer uma transação ou receber alguns Monero, a transação ainda precisa ser confirmada. Em cerca de 20 minutos, seu \"saldo disponível\" deve ser atualizado!\nÀs vezes, quando você envia o Monero, seu saldo disponível diminui em mais do que o valor enviado. Isso é normal e é necessário para proteger sua privacidade. Seu \"equilíbrio total\" deve voltar ao normal em 20 minutos.\n" + }, + { + "question" : "Como envio o Monero para uma troca que exige um ID de pagamento?", + "answer" : "Toque no botão \"enviar\" na tela da Carteira virtual. Em seguida, copie o endereço de depósito da bolsa e cole-o na caixa \"endereço\". Em seguida, copie o ID do pagamento fornecido pela central e cole-o na caixa ID do pagamento. Por fim, insira a quantia que você deseja enviar e pronto!\n" + }, + { + "question" : "O que devo fazer se me esquecer do código de pagamento ao enviar o Monero para uma troca?", + "answer" : "Embora nosso suporte não possa ajudá-lo diretamente com esse problema, é um problema muito comum com o qual a maioria das trocas está acostumada. Basta entrar em contato com o suporte da bolsa, explicar que você esqueceu de incluir seu ID de pagamento e enviar a ele seu ID de transação como prova. Você pode encontrar o ID da transação tocando na transação na tela da Carteira virtual.\n" + }, + { + "question" : "O que significam \"semente\" e \"chaves\"?", + "answer" : "Suas chaves codificam as informações privadas da sua carteira e são o que permite gastar moedas e ver as transações recebidas.\nSua semente é apenas uma versão da sua chave privada escrita de uma maneira mais fácil para você escrever. Suas sementes e chaves são na verdade a mesma coisa, apenas em formas diferentes!\nNunca dê sua semente ou chave a ninguém. Seus fundos serão roubados se você der suas sementes ou chaves. No entanto, anote sua semente e guarde-a em um local seguro (isso permitirá que você restaure sua carteira se perder o telefone.)\n" + }, + { + "question" : "Quantas carteiras posso criar?", + "answer" : "Não há limite! Você pode criar quantas carteiras quiser.\n" + }, + { + "question" : "Como posso restaurar minha carteira?", + "answer" : "Toque no menu •••, selecione \"Carteiras\" e escolha \"Restaurar carteira\". Em seguida, insira sua semente (ou suas chaves) e, opcionalmente, insira uma data antes da primeira transação na sua carteira (isso acelerará o processo de sincronização .) Pode ser necessário manter o aplicativo aberto por 15 a 30 minutos para restaurar completamente sua carteira.\n" + }, + { + "question" : "O que posso fazer se perder minha semente?", + "answer" : "Se você esqueceu sua semente, provavelmente a anotou em algum lugar. Por favor, verifique suas anotações e olhe em volta no seu computador. Se você não conseguir encontrá-lo em nenhum lugar, pode ter feito o backup da Cake Wallet (nesse caso, poderá restaurar a partir desse backup.) Se nada disso funcionar, infelizmente não há nada que possamos fazer.\n" + }, + { + "question" : "Você coleta alguma informação sobre minha carteira?", + "answer" : "Cake Wallet NÃO coleta nem registra nenhuma informação sobre sua carteira. Nós nos preocupamos com sua privacidade.\n" + }, + { + "question" : "É possível reverter uma transação?", + "answer" : "Infelizmente, assim que uma transação foi enviada ao blockchain, não há como desfazê-lo. No entanto, você sempre pode cancelar a transação antes de ser enviada. Verifique sempre o endereço antes de enviar uma transação.\n" + }, + { + "question" : "O que são \"sub-endereços\" e como os usamos?", + "answer" : "Um sub-endereço é basicamente um endereço exclusivo que você pode gerar a qualquer momento. As moedas enviadas ainda chegarão à sua carteira principal, mas a pessoa que está enviando as moedas não sabe qual é o seu endereço principal. Os sub-endereços sempre começam com \"8\".\nVocê pode criar um novo sub-endereço na tela Receber, tocando no botão \"+\" ao lado do botão Sub-endereços. Digite um nome para o sub-endereço e toque em \"Adicionar\". Em seguida, basta tocar no nome do sub-endereço quando quiser usá-lo!\nSe você é paranóico, provavelmente deve criar um novo sub-endereço toda vez que receber o Monero.\n" + }, + { + "question" : "O que é um ID de transação?", + "answer" : "Um hash de transação, ou ID da transação, é uma maneira exclusiva de identificar qualquer transação. Cada transação possui seu próprio hash. Se você precisar fornecer um hash de transação a alguém, vá para a tela principal da Carteira virtual, toque na transação, pressione e segure na seção superior e selecione Copiar.\n" + }, + { + "question" : "Não recebi meu XMR! O que eu posso fazer?", + "answer" : "Se você não recebeu seu Monero, toque no menu ••• e clique em Reconectar. Se isso não funcionar, entre no menu de configurações, toque na caixa 'Nó atual' e selecione um nó com um ponto verde próximo a ele.\n" + }, + { + "question" : "Não recebi minhas moedas da troca no aplicativo. O que eu posso fazer?", + "answer" : "Se você estiver tendo problemas com uma troca, a melhor opção é entrar em contato com a troca. Somos parceiros do XMR.TO, Morph e ChangeNow, portanto, sua melhor aposta é ir para http://xmr.to, http://changenow.io ou http://morphtoken.com e entrar em contato com o suporte deles.\n" + }, + { + "question" : "Como entro em contato com o suporte da Cake Wallet?", + "answer" : "Envie um e-mail para support@cakewallet.io, participe do Telegram em @cake_wallet ou envie um tweet para @CakeWalletXMR!\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_ru.json b/assets/faq/faq_ru.json new file mode 100644 index 000000000..77b1bf53e --- /dev/null +++ b/assets/faq/faq_ru.json @@ -0,0 +1,58 @@ +[ + { + "question" : "В чем разница между доступным балансом и полным балансом?", + "answer" : "После того, как вы совершите транзакцию или получите какой-либо Monero, транзакция все еще нуждается в подтверждении. Примерно через 20 минут ваш «доступный баланс» должен обновиться.\nИногда, когда вы отправляете Monero, ваш доступный баланс уменьшается более чем на сумму, которую вы отправили. Это нормально и необходимо для защиты вашей конфиденциальности. Ваш «полный баланс» должен вернуться к норме через 20 минут.\n" + }, + { + "question" : "Как отправить Monero на обмен, для которого требуется идентификатор платежа?", + "answer" : "Нажмите кнопку «Отправить» на экране кошелька. Затем скопируйте адрес депозита биржи и вставьте его в поле «адрес». Затем скопируйте идентификатор платежа, предоставленный провайдером обмена и вставьте его в поле идентификатора платежа. Затем ведите сумму, которую вы хотите отправить и все готово!\n" + }, + { + "question" : "Что мне делать, если я забыл ввести идентификатор платежа при отправке Monero на обмен?", + "answer" : "Хотя наша поддержка не может напрямую помочь вам в этом вопросе, это очень распространенная проблема, с которой большинство бирж привыкли иметь дело. Просто свяжитесь со службой поддержки провайдера обмена и объясните, что вы забыли указать свой идентификатор платежа, а затем отправьте им свой идентификатор транзакции в качестве доказательства. Вы можете найти идентификатор транзакции, нажав на транзакцию на экране своего кошелька.\n" + }, + { + "question" : "Что означают «семена» и «ключи»?", + "answer" : "Ваши ключи кодируют личную информацию в вашем кошельке и позволяют вам тратить монеты и видеть входящие транзакции.Ваше семя (код-фраза) - это просто версия вашего личного ключа, написанная так, чтобы вам было легче ее записать. Ваше семя и ключи на самом деле одно и то же, только в разных формах! Никому не передавайте свою код-фразу или ключи иначе ваши средства могут быть украдены. Однако, пожалуйста, запишите их и храните в безопасном месте (это позволит вам восстановить свой кошелек, если вы потеряете свой телефон.)\n" + }, + { + "question" : "Сколько кошельков я могу создать?", + "answer" : "Там нет предела! Вы можете создать столько кошельков, сколько захотите.\n" + }, + { + "question" : "Как я могу восстановить свой кошелек?", + "answer" : "Коснитесь ••• меню, выберите «Кошельки», а затем выберите «Восстановить кошелек». Затем введите свою код-фразу (или ключи) и при необходимости введите дату до первой транзакции в вашем кошельке (это ускорит процесс синхронизации .) Вам может понадобиться держать приложение открытым в течение 15-30 минут, чтобы полностью восстановить свой кошелек.\n" + }, + { + "question" : "Что мне делать, если я потеряю свою код-фразу?", + "answer" : "Если вы забыли свою код-фразу, вы вероятно, где-то записали ее. Пожалуйста, проверьте свои заметки и осмотрите свой компьютер. Если вы нигде не можете найти ее, возможно, вы создали резервную копию Cake Wallet (в этом случае вы сможете восстановить данные из этой резервной копии.) К сожалению если вышеуказанные способы вам не помогли, мы ничего не можем сделать.\n" + }, + { + "question" : "Собираете ли вы какую-либо информацию о моем кошельке?", + "answer" : "Cake Wallet не собирает и не записывает какую-либо информацию о вашем кошельке. Мы заботимся о вашей конфиденциальности.\n" + }, + { + "question" : "Возможно ли отменить транзакцию?", + "answer" : "К сожалению, как только транзакция была передана в блокчейн, отменить ее невозможно. Вы всегда можете отменить транзакцию до ее отправки, поэтому всегда дважды проверяйте адрес перед отправкой транзакции.\n" + }, + { + "question" : "Что такое «сабадреса» и как их использовать?", + "answer" : "Сабадрес - это уникальный адрес, который вы можете сгенерировать в любое время. Монеты, отправленные на него, все равно поступят на ваш основной кошелек, но человек отправляющий монеты, не может узнать ваш основной адрес. Сабадреса всегда начинаются с «8».\nВы можете создать новый сабадрес на экране получения, нажав «+» рядом с кнопкой «Сабадреса». Введите имя для дополнительного адреса и нажмите «Добавить». Затем просто нажмите на имя сабадреса, когда вы хотите его использовать!\nЕсли вы переживаете за конфединциальность, вероятно вам следует создавать новый сабадрес каждый раз, когда вы хотите получить Monero.\n" + }, + { + "question" : "Что такое ID транзакции?", + "answer" : "Хэш транзакции (или ID транзакции), является уникальным способом идентификации любой транзакции. Каждая транзакция имеет свой ID. Если вам нужно предоставить ID транзакции кому-то, просто перейдите на главный экран кошелька, нажмите на транзакцию, нажмите и удерживайте поле с ID транзакции для копирования.\n" + }, + { + "question" : "Я не получил свои XMR! Что я могу сделать?", + "answer" : "Если вы не получили Monero, вы можете нажать ••• меню и нажать «Переподключиться». Если это не сработает, перейдите в меню настроек, коснитесь поля «Текущий узел» и выберите узел с зеленой точкой рядом с ним.\n" + }, + { + "question" : "Я не получил свои монеты после обмена в приложении. Что я могу сделать?", + "answer" : "Если у вас возникли проблемы с обменом, лучше всего связаться с провайдером обмена. Мы сотрудничаем с XMR.TO, Morph и ChangeNow, поэтому вам лучше всего зайти на http://xmr.to, http://changenow.io или http://morphtoken.com и связаться с их поддержкой.\n" + }, + { + "question" : "Как мне связаться со службой поддержки Cake Wallet?", + "answer" : "По электронной почте support@cakewallet.io, присоединитесь к Telegram по адресу @cake_wallet или отправьте твит @CakeWalletXMR!\n" + } +] \ No newline at end of file diff --git a/assets/faq/faq_zh.json b/assets/faq/faq_zh.json new file mode 100644 index 000000000..1f9d3242a --- /dev/null +++ b/assets/faq/faq_zh.json @@ -0,0 +1,58 @@ +[ + { + "question" : "可用余额和全额余额有什么区别?", + "answer" : "在您进行交易或收到一些门罗币后,仍然需要确认交易。 在大约20分钟内,您的“可用余额”应该会更新!\n有时,当您发送Monero时,可用余额会减少,超过所发送的金额。 这是正常现象,因此有必要保护您的隐私。 您的“全部余额”应在20分钟内恢复正常。\n" + }, + { + "question" : "如何将Monero发送到需要付款ID的交易所?", + "answer" : "点击电子钱包屏幕上的“发送”按钮。 接下来,复制交易所的存款地址,并将其粘贴到“地址”框中。 然后,复制交易所提供的付款ID,并将其粘贴到“付款ID”框中。 最后,输入您要发送的金额,一切顺利!\n" + }, + { + "question" : "如果将Monero发送到交易所时忘记输入付款ID,该怎么办?", + "answer" : "尽管我们的支持无法直接为您解决此问题,但大多数交易所都习惯于处理这个非常普遍的问题。 只需联系交易所的支持人员,说明您忘记包含付款ID,然后将其交易ID发送给他们即可作为证明。 您可以通过在电子钱包屏幕中点击交易找到交易ID。\n" + }, + { + "question" : "“种子”和“钥匙”是什么意思?", + "answer" : "您的密钥对钱包中的私人信息进行编码,这些密钥使您可以花费硬币并查看传入的交易。\n您的种子只是私钥的一种版本,以更容易记下来的方式编写。 您的种子和密钥实际上是同一件事,只是形式不同!\n永远不要将您的种子或钥匙交给任何人。 如果您提供种子或钥匙,您的资金将被盗。 但是,请写下您的种子,并将其存储在安全的地方(如果您丢失手机,这将使您可以恢复钱包。)\n" + }, + { + "question" : "我可以创建多少个钱包?", + "answer" : "没有限制! 您可以根据需要创建任意数量的钱包。\n" + }, + { + "question" : "如何恢复我的钱包?", + "answer" : "点击•••菜单,选择“钱包”,然后选择“还原钱包”。然后输入您的种子(或密钥),还可以选择在您的钱包中进行第一笔交易之前输入日期(这将加快同步过程) 。)您可能需要将应用保持打开状态15-30分钟,以完全恢复您的钱包。\n" + }, + { + "question" : "如果我种下种子了怎么办?", + "answer" : "如果您忘记了种子,则可能将其写下来。 请检查笔记并在计算机上四处张望。 如果您在任何地方都找不到它,则可能已经备份了Cake Wallet(在这种情况下,您可以从该备份中还原。)如果这些都不起作用,那么很遗憾,我们无能为力。\n" + }, + { + "question" : "您是否收集有关我的钱包的任何信息?", + "answer" : "Cake Wallet不会收集或记录有关您钱包的任何信息。 我们关心您的隐私。\n" + }, + { + "question" : "是否可以撤消交易?", + "answer" : "不幸的是,一旦将交易提交到区块链,就无法撤消它。 不过,您始终可以在发送交易之前取消交易,因此请务必在发送交易之前仔细检查地址。\n" + }, + { + "question" : "什么是“子地址”,我该如何使用它们?", + "answer" : "子地址基本上是您可以随时生成的唯一地址。 发送给它的硬币仍然会到达您的主钱包中,但是发送硬币的人无法告知您的主要地址是什么。 子地址始终以“ 8”开头。\n您可以通过点击“子地址”按钮旁边的“ +”在“接收”屏幕中创建一个新的子地址。 输入子地址的名称,然后点击“添加”。 然后,当您要使用子地址名称时,只需点击它即可!\n如果您偏执狂,您可能应该在每次收到门罗币时创建一个新的子地址。\n" + }, + { + "question" : "什么是交易编号?", + "answer" : "交易哈希或交易ID是识别任何交易的唯一方式。 每个交易都有自己的哈希。 如果您需要向某人提供交易哈希,只需转到电子钱包主屏幕,点击交易,长按顶部,然后选择复制。\n" + }, + { + "question" : "我没有收到我的XMR! 我能做什么?", + "answer" : "如果您未收到门罗币,则可能需要点击•••菜单,然后点击重新连接。 如果这不起作用,请进入设置菜单,点击“当前节点”框,然后选择旁边带有绿点的节点。\n" + }, + { + "question" : "我没有从应用程序中的交易所收到硬币。 我能做什么?", + "answer" : "如果您对交易所有疑问,最好的选择是与交易所本身联系。 我们与XMR.TO,Morph和ChangeNow合作,因此最好的选择是访问http://xmr.to、http://changenow.io或http://morphtoken.com,并与他们的支持部门联系。\n" + }, + { + "question" : "如何联系蛋糕钱包支持?", + "answer" : "电子邮件support@cakewallet.io,通过@cake_wallet加入电报,或在@CakeWalletXMR上发布推文!\n" + } +] \ No newline at end of file diff --git a/assets/fonts/Lato-Bold.ttf b/assets/fonts/Lato-Bold.ttf new file mode 100644 index 000000000..ef5ae3b43 Binary files /dev/null and b/assets/fonts/Lato-Bold.ttf differ diff --git a/assets/fonts/Lato-Regular.ttf b/assets/fonts/Lato-Regular.ttf new file mode 100644 index 000000000..adbfc467d Binary files /dev/null and b/assets/fonts/Lato-Regular.ttf differ diff --git a/assets/fonts/Lato-Semibold.ttf b/assets/fonts/Lato-Semibold.ttf new file mode 100644 index 000000000..60ac82d65 Binary files /dev/null and b/assets/fonts/Lato-Semibold.ttf differ diff --git a/assets/images/2.0x/Telegram.png b/assets/images/2.0x/Telegram.png new file mode 100644 index 000000000..b8f8cce3d Binary files /dev/null and b/assets/images/2.0x/Telegram.png differ diff --git a/assets/images/2.0x/Twitter.png b/assets/images/2.0x/Twitter.png new file mode 100644 index 000000000..642e8eb72 Binary files /dev/null and b/assets/images/2.0x/Twitter.png differ diff --git a/assets/images/2.0x/arrow_bottom_cake_green.png b/assets/images/2.0x/arrow_bottom_cake_green.png new file mode 100644 index 000000000..67c523951 Binary files /dev/null and b/assets/images/2.0x/arrow_bottom_cake_green.png differ diff --git a/assets/images/2.0x/back_arrow.png b/assets/images/2.0x/back_arrow.png new file mode 100644 index 000000000..065eecf7f Binary files /dev/null and b/assets/images/2.0x/back_arrow.png differ diff --git a/assets/images/2.0x/back_arrow_dark_theme.png b/assets/images/2.0x/back_arrow_dark_theme.png new file mode 100644 index 000000000..f1e790fcd Binary files /dev/null and b/assets/images/2.0x/back_arrow_dark_theme.png differ diff --git a/assets/images/2.0x/bitmap.png b/assets/images/2.0x/bitmap.png new file mode 100644 index 000000000..b08091847 Binary files /dev/null and b/assets/images/2.0x/bitmap.png differ diff --git a/assets/images/2.0x/cake_arrow.png b/assets/images/2.0x/cake_arrow.png new file mode 100644 index 000000000..cb72693ce Binary files /dev/null and b/assets/images/2.0x/cake_arrow.png differ diff --git a/assets/images/2.0x/cake_logo.png b/assets/images/2.0x/cake_logo.png new file mode 100644 index 000000000..c464cdcd4 Binary files /dev/null and b/assets/images/2.0x/cake_logo.png differ diff --git a/assets/images/2.0x/change_now.png b/assets/images/2.0x/change_now.png new file mode 100644 index 000000000..37593c419 Binary files /dev/null and b/assets/images/2.0x/change_now.png differ diff --git a/assets/images/2.0x/close_button.png b/assets/images/2.0x/close_button.png new file mode 100644 index 000000000..8e74c751f Binary files /dev/null and b/assets/images/2.0x/close_button.png differ diff --git a/assets/images/2.0x/close_button_dark_theme.png b/assets/images/2.0x/close_button_dark_theme.png new file mode 100644 index 000000000..9fd5cb080 Binary files /dev/null and b/assets/images/2.0x/close_button_dark_theme.png differ diff --git a/assets/images/2.0x/delete_icon.png b/assets/images/2.0x/delete_icon.png new file mode 100644 index 000000000..8d5889f8a Binary files /dev/null and b/assets/images/2.0x/delete_icon.png differ diff --git a/assets/images/2.0x/keysIco.png b/assets/images/2.0x/keysIco.png new file mode 100644 index 000000000..00c2879a6 Binary files /dev/null and b/assets/images/2.0x/keysIco.png differ diff --git a/assets/images/2.0x/qr_code_icon.png b/assets/images/2.0x/qr_code_icon.png new file mode 100644 index 000000000..c8b121d10 Binary files /dev/null and b/assets/images/2.0x/qr_code_icon.png differ diff --git a/assets/images/2.0x/qr_icon.png b/assets/images/2.0x/qr_icon.png new file mode 100644 index 000000000..6c448bcc2 Binary files /dev/null and b/assets/images/2.0x/qr_icon.png differ diff --git a/assets/images/2.0x/refresh_icon.png b/assets/images/2.0x/refresh_icon.png new file mode 100644 index 000000000..b7cf225a6 Binary files /dev/null and b/assets/images/2.0x/refresh_icon.png differ diff --git a/assets/images/2.0x/restoreSeed.png b/assets/images/2.0x/restoreSeed.png new file mode 100644 index 000000000..c25928f6c Binary files /dev/null and b/assets/images/2.0x/restoreSeed.png differ diff --git a/assets/images/2.0x/seedIco.png b/assets/images/2.0x/seedIco.png new file mode 100644 index 000000000..650f2fe05 Binary files /dev/null and b/assets/images/2.0x/seedIco.png differ diff --git a/assets/images/2.0x/seedKeys.png b/assets/images/2.0x/seedKeys.png new file mode 100644 index 000000000..10bee8b37 Binary files /dev/null and b/assets/images/2.0x/seedKeys.png differ diff --git a/assets/images/2.0x/seed_image.png b/assets/images/2.0x/seed_image.png new file mode 100644 index 000000000..795b0aba5 Binary files /dev/null and b/assets/images/2.0x/seed_image.png differ diff --git a/assets/images/2.0x/welcomeImg.png b/assets/images/2.0x/welcomeImg.png new file mode 100644 index 000000000..d4c76fc4f Binary files /dev/null and b/assets/images/2.0x/welcomeImg.png differ diff --git a/assets/images/2.0x/xmr_btc.png b/assets/images/2.0x/xmr_btc.png new file mode 100644 index 000000000..1a68e24cf Binary files /dev/null and b/assets/images/2.0x/xmr_btc.png differ diff --git a/assets/images/3.0x/Telegram.png b/assets/images/3.0x/Telegram.png new file mode 100644 index 000000000..a5d85fd53 Binary files /dev/null and b/assets/images/3.0x/Telegram.png differ diff --git a/assets/images/3.0x/Twitter.png b/assets/images/3.0x/Twitter.png new file mode 100644 index 000000000..6a6b5f3a8 Binary files /dev/null and b/assets/images/3.0x/Twitter.png differ diff --git a/assets/images/3.0x/arrow_bottom_cake_green.png b/assets/images/3.0x/arrow_bottom_cake_green.png new file mode 100644 index 000000000..d37dccf71 Binary files /dev/null and b/assets/images/3.0x/arrow_bottom_cake_green.png differ diff --git a/assets/images/3.0x/back_arrow.png b/assets/images/3.0x/back_arrow.png new file mode 100644 index 000000000..b5b7fde8e Binary files /dev/null and b/assets/images/3.0x/back_arrow.png differ diff --git a/assets/images/3.0x/back_arrow_dark_theme.png b/assets/images/3.0x/back_arrow_dark_theme.png new file mode 100644 index 000000000..724ed23f2 Binary files /dev/null and b/assets/images/3.0x/back_arrow_dark_theme.png differ diff --git a/assets/images/3.0x/bitmap.png b/assets/images/3.0x/bitmap.png new file mode 100644 index 000000000..337bcf027 Binary files /dev/null and b/assets/images/3.0x/bitmap.png differ diff --git a/assets/images/3.0x/cake_arrow.png b/assets/images/3.0x/cake_arrow.png new file mode 100644 index 000000000..b92ec35c0 Binary files /dev/null and b/assets/images/3.0x/cake_arrow.png differ diff --git a/assets/images/3.0x/cake_logo.png b/assets/images/3.0x/cake_logo.png new file mode 100644 index 000000000..5da47a539 Binary files /dev/null and b/assets/images/3.0x/cake_logo.png differ diff --git a/assets/images/3.0x/change_now.png b/assets/images/3.0x/change_now.png new file mode 100644 index 000000000..ac2d564e3 Binary files /dev/null and b/assets/images/3.0x/change_now.png differ diff --git a/assets/images/3.0x/close_button.png b/assets/images/3.0x/close_button.png new file mode 100644 index 000000000..ad9586348 Binary files /dev/null and b/assets/images/3.0x/close_button.png differ diff --git a/assets/images/3.0x/close_button_dark_theme.png b/assets/images/3.0x/close_button_dark_theme.png new file mode 100644 index 000000000..98482a870 Binary files /dev/null and b/assets/images/3.0x/close_button_dark_theme.png differ diff --git a/assets/images/3.0x/delete_icon.png b/assets/images/3.0x/delete_icon.png new file mode 100644 index 000000000..c3d8b990b Binary files /dev/null and b/assets/images/3.0x/delete_icon.png differ diff --git a/assets/images/3.0x/keysIco.png b/assets/images/3.0x/keysIco.png new file mode 100644 index 000000000..a44186dcc Binary files /dev/null and b/assets/images/3.0x/keysIco.png differ diff --git a/assets/images/3.0x/qr_code_icon.png b/assets/images/3.0x/qr_code_icon.png new file mode 100644 index 000000000..918041c04 Binary files /dev/null and b/assets/images/3.0x/qr_code_icon.png differ diff --git a/assets/images/3.0x/qr_icon.png b/assets/images/3.0x/qr_icon.png new file mode 100644 index 000000000..a9e8e604a Binary files /dev/null and b/assets/images/3.0x/qr_icon.png differ diff --git a/assets/images/3.0x/refresh_icon.png b/assets/images/3.0x/refresh_icon.png new file mode 100644 index 000000000..9ccb8f171 Binary files /dev/null and b/assets/images/3.0x/refresh_icon.png differ diff --git a/assets/images/3.0x/restoreSeed.png b/assets/images/3.0x/restoreSeed.png new file mode 100644 index 000000000..8fc5b1f14 Binary files /dev/null and b/assets/images/3.0x/restoreSeed.png differ diff --git a/assets/images/3.0x/seedIco.png b/assets/images/3.0x/seedIco.png new file mode 100644 index 000000000..174a943b6 Binary files /dev/null and b/assets/images/3.0x/seedIco.png differ diff --git a/assets/images/3.0x/seedKeys.png b/assets/images/3.0x/seedKeys.png new file mode 100644 index 000000000..50b31c2e8 Binary files /dev/null and b/assets/images/3.0x/seedKeys.png differ diff --git a/assets/images/3.0x/seed_image.png b/assets/images/3.0x/seed_image.png new file mode 100644 index 000000000..0431af18d Binary files /dev/null and b/assets/images/3.0x/seed_image.png differ diff --git a/assets/images/3.0x/welcomeImg.png b/assets/images/3.0x/welcomeImg.png new file mode 100644 index 000000000..d78387d67 Binary files /dev/null and b/assets/images/3.0x/welcomeImg.png differ diff --git a/assets/images/3.0x/xmr_btc.png b/assets/images/3.0x/xmr_btc.png new file mode 100644 index 000000000..241831594 Binary files /dev/null and b/assets/images/3.0x/xmr_btc.png differ diff --git a/assets/images/Telegram.png b/assets/images/Telegram.png new file mode 100644 index 000000000..e734ab987 Binary files /dev/null and b/assets/images/Telegram.png differ diff --git a/assets/images/Twitter.png b/assets/images/Twitter.png new file mode 100644 index 000000000..4821abfd3 Binary files /dev/null and b/assets/images/Twitter.png differ diff --git a/assets/images/address_book_icon.png b/assets/images/address_book_icon.png new file mode 100644 index 000000000..0e9219814 Binary files /dev/null and b/assets/images/address_book_icon.png differ diff --git a/assets/images/app_logo.png b/assets/images/app_logo.png new file mode 100644 index 000000000..4540659cf Binary files /dev/null and b/assets/images/app_logo.png differ diff --git a/assets/images/arrow_bottom_cake_green.png b/assets/images/arrow_bottom_cake_green.png new file mode 100644 index 000000000..594eb7de2 Binary files /dev/null and b/assets/images/arrow_bottom_cake_green.png differ diff --git a/assets/images/arrow_bottom_purple_icon.png b/assets/images/arrow_bottom_purple_icon.png new file mode 100644 index 000000000..5a0d5918d Binary files /dev/null and b/assets/images/arrow_bottom_purple_icon.png differ diff --git a/assets/images/back_arrow.png b/assets/images/back_arrow.png new file mode 100644 index 000000000..30e599b3f Binary files /dev/null and b/assets/images/back_arrow.png differ diff --git a/assets/images/back_arrow_dark_theme.png b/assets/images/back_arrow_dark_theme.png new file mode 100644 index 000000000..e78cf34c8 Binary files /dev/null and b/assets/images/back_arrow_dark_theme.png differ diff --git a/assets/images/bitmap.png b/assets/images/bitmap.png new file mode 100644 index 000000000..525522b3f Binary files /dev/null and b/assets/images/bitmap.png differ diff --git a/assets/images/cake_arrow.png b/assets/images/cake_arrow.png new file mode 100644 index 000000000..26648d373 Binary files /dev/null and b/assets/images/cake_arrow.png differ diff --git a/assets/images/cake_logo.png b/assets/images/cake_logo.png new file mode 100644 index 000000000..fb5267a79 Binary files /dev/null and b/assets/images/cake_logo.png differ diff --git a/assets/images/change_now.png b/assets/images/change_now.png new file mode 100644 index 000000000..132525743 Binary files /dev/null and b/assets/images/change_now.png differ diff --git a/assets/images/close_button.png b/assets/images/close_button.png new file mode 100644 index 000000000..50d31ed5e Binary files /dev/null and b/assets/images/close_button.png differ diff --git a/assets/images/close_button_dark_theme.png b/assets/images/close_button_dark_theme.png new file mode 100644 index 000000000..a5334a9a2 Binary files /dev/null and b/assets/images/close_button_dark_theme.png differ diff --git a/assets/images/delete_icon.png b/assets/images/delete_icon.png new file mode 100644 index 000000000..0abb30292 Binary files /dev/null and b/assets/images/delete_icon.png differ diff --git a/assets/images/exchange_icon.png b/assets/images/exchange_icon.png new file mode 100644 index 000000000..47db4c585 Binary files /dev/null and b/assets/images/exchange_icon.png differ diff --git a/assets/images/keysIco.png b/assets/images/keysIco.png new file mode 100644 index 000000000..16e73a4b8 Binary files /dev/null and b/assets/images/keysIco.png differ diff --git a/assets/images/more.png b/assets/images/more.png new file mode 100644 index 000000000..6b1a916ff Binary files /dev/null and b/assets/images/more.png differ diff --git a/assets/images/paste_button.png b/assets/images/paste_button.png new file mode 100644 index 000000000..c856c8e72 Binary files /dev/null and b/assets/images/paste_button.png differ diff --git a/assets/images/qr_code_icon.png b/assets/images/qr_code_icon.png new file mode 100644 index 000000000..ad1b3631e Binary files /dev/null and b/assets/images/qr_code_icon.png differ diff --git a/assets/images/qr_icon.png b/assets/images/qr_icon.png new file mode 100644 index 000000000..ea0c1eea6 Binary files /dev/null and b/assets/images/qr_icon.png differ diff --git a/assets/images/receive_icon.png b/assets/images/receive_icon.png new file mode 100755 index 000000000..8701cc643 Binary files /dev/null and b/assets/images/receive_icon.png differ diff --git a/assets/images/receive_icon_raw.png b/assets/images/receive_icon_raw.png new file mode 100644 index 000000000..741ed4e4f Binary files /dev/null and b/assets/images/receive_icon_raw.png differ diff --git a/assets/images/refresh_icon.png b/assets/images/refresh_icon.png new file mode 100644 index 000000000..65e9e06ee Binary files /dev/null and b/assets/images/refresh_icon.png differ diff --git a/assets/images/restoreSeed.png b/assets/images/restoreSeed.png new file mode 100644 index 000000000..a51ea1193 Binary files /dev/null and b/assets/images/restoreSeed.png differ diff --git a/assets/images/seedIco.png b/assets/images/seedIco.png new file mode 100644 index 000000000..d40fe1f2f Binary files /dev/null and b/assets/images/seedIco.png differ diff --git a/assets/images/seedKeys.png b/assets/images/seedKeys.png new file mode 100644 index 000000000..7cdc42d3f Binary files /dev/null and b/assets/images/seedKeys.png differ diff --git a/assets/images/seed_image.png b/assets/images/seed_image.png new file mode 100644 index 000000000..7399c38cc Binary files /dev/null and b/assets/images/seed_image.png differ diff --git a/assets/images/send_icon.png b/assets/images/send_icon.png new file mode 100755 index 000000000..e9774c4a7 Binary files /dev/null and b/assets/images/send_icon.png differ diff --git a/assets/images/settings_icon.png b/assets/images/settings_icon.png new file mode 100644 index 000000000..cf6f55053 Binary files /dev/null and b/assets/images/settings_icon.png differ diff --git a/assets/images/transaction_incoming.png b/assets/images/transaction_incoming.png new file mode 100644 index 000000000..76356e161 Binary files /dev/null and b/assets/images/transaction_incoming.png differ diff --git a/assets/images/transaction_outgoing.png b/assets/images/transaction_outgoing.png new file mode 100644 index 000000000..8a28ae6fc Binary files /dev/null and b/assets/images/transaction_outgoing.png differ diff --git a/assets/images/wallet_icon.png b/assets/images/wallet_icon.png new file mode 100644 index 000000000..793ac22d0 Binary files /dev/null and b/assets/images/wallet_icon.png differ diff --git a/assets/images/welcomeImg.png b/assets/images/welcomeImg.png new file mode 100644 index 000000000..419f61a80 Binary files /dev/null and b/assets/images/welcomeImg.png differ diff --git a/assets/images/x.png b/assets/images/x.png new file mode 100755 index 000000000..2c89f94c8 Binary files /dev/null and b/assets/images/x.png differ diff --git a/assets/images/xmr_btc.png b/assets/images/xmr_btc.png new file mode 100644 index 000000000..858664265 Binary files /dev/null and b/assets/images/xmr_btc.png differ diff --git a/assets/node_list.yml b/assets/node_list.yml new file mode 100644 index 000000000..70c114f57 --- /dev/null +++ b/assets/node_list.yml @@ -0,0 +1,25 @@ +- + uri: xmr-node-uk.cakewallet.com:18081 + is_default: true +- + uri: eu-node.cakewallet.io:18081 + login: cake + password: public_node + is_default: false +- + uri: node.cakewallet.io:18081 + login: cake + password: public_node + is_default: false +- + uri: node.moneroworld.com:18089 + is_default: false +- + uri: node.xmr.pt:18081 + is_default: false +- + uri: node.xmr.ru:13666 + is_default: false +- + uri: opennode.xmr-tw.org:18089 + is_default: false \ No newline at end of file diff --git a/assets/text/Terms_of_Use.txt b/assets/text/Terms_of_Use.txt new file mode 100644 index 000000000..b5cd5a4a5 --- /dev/null +++ b/assets/text/Terms_of_Use.txt @@ -0,0 +1,25 @@ +1. Use of Cake Wallet +The Cake Wallet app (hereinafter, referred to as the "App") allows the use of accessing the Monero Blockchain/network. You are not authorized, and nor should you rely on the App for legal advice, business advice, or advice of any kind. You act at your own risk in reliance on the contents of the App. Should you make a decision to act or not act you should contact a licensed attorney in the relevant jurisdiction in which you want or need help. In no way are the owners of, or contributors to, the App responsible for the actions, decisions, or other behavior taken or not taken by you in reliance upon the App. +2. Translations +The App may contain translations of the English version of the content available on the App. These translations are provided only as a convenience. In the event of any conflict between the English language version and the translated version, the English language version shall take precedence. If you notice any inconsistency, please report them on GitHub. +3. Risks related to the use of Cake Wallet. +The App, the App’s owner Cake Technologies LLC and Cake Technologies owners, partners, employees, contributors, and any affiliates will not be responsible for any losses, damages or claims arising from events falling within the scope of the following five categories: +1. (1) Mistakes made by the user of any Monero-related software or service, e.g., forgotten passwords, payments sent to wrong Monero addresses, and accidental deletion of wallets. +2. (2) Software problems of the App and/or any Monero-related software or service, e.g., corrupted wallet file, incorrectly constructed transactions, unsafe cryptographic libraries, malware affecting the App and/or any Monero-related software or service. +3. (3) Technical failures in the hardware of the user of any Monero-related software or service, e.g., data loss due to a faulty or damaged storage device. +4. (4) Security problems experienced by the user of any Monero-related software or service, e.g., unauthorized access to users' wallets and/or accounts. +5. (5) Actions or inactions of third parties and/or events experienced by third parties, e.g., bankruptcy of service providers, information security attacks on service providers, and fraud conducted by third parties. +4. Investment risks +The investment in Monero can lead to loss of money over short or even long periods. The investors in Monero should expect prices to have large range fluctuations. +5. Compliance with tax obligations +The users of the App are solely responsible to determinate what, if any, taxes apply to their Monero transactions. The owners of, or contributors to, the App are NOT responsible for determining the taxes that apply to Monero transactions. +6. The App does not store, send, or receive Moneros +The App does not store, send or receive Monero. This is because Monero exists only by virtue of the ownership record maintained in the Monero network. Any transfer of title in Moneros occurs within a decentralized Monero network, and not on the App. +7. No warranties +The App is provided on an "as is" basis without any warranties of any kind regarding the App and/or any content, data, materials and/or services provided on the App. +8. Limitation of liability +Unless otherwise required by law, in no event shall the owners of, or contributors to, the App be liable for any damages of any kind, including, but not limited to, loss of use, loss of profits, or loss of data arising out of or in any way connected with the use of the App. +9. Arbitration +The user of the App agrees to arbitrate any dispute arising from or in connection with the App or this disclaimer, except for disputes related to copyrights, logos, trademarks, trade names, trade secrets or patents. +10. Last amendment +This disclaimer was amended for the last time on January 15, 2018. \ No newline at end of file diff --git a/cw_monero/.gitignore b/cw_monero/.gitignore new file mode 100644 index 000000000..c8bb78494 --- /dev/null +++ b/cw_monero/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ + +ios/External/ +android/.externalNativeBuild/ +android/.cxx/ \ No newline at end of file diff --git a/cw_monero/.metadata b/cw_monero/.metadata new file mode 100644 index 000000000..36ba765ff --- /dev/null +++ b/cw_monero/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 798e4272a2e43d7daab75f225a13442e384ee0cd + channel: dev + +project_type: plugin diff --git a/cw_monero/CHANGELOG.md b/cw_monero/CHANGELOG.md new file mode 100644 index 000000000..41cc7d819 --- /dev/null +++ b/cw_monero/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/cw_monero/LICENSE b/cw_monero/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_monero/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_monero/README.md b/cw_monero/README.md new file mode 100644 index 000000000..8a79e0360 --- /dev/null +++ b/cw_monero/README.md @@ -0,0 +1,14 @@ +# cw_monero + +A new flutter plugin project. + +## Getting Started + +This project is a starting point for a Flutter +[plug-in package](https://flutter.dev/developing-packages/), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/cw_monero/android/.gitignore b/cw_monero/android/.gitignore new file mode 100644 index 000000000..c6cbe562a --- /dev/null +++ b/cw_monero/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/cw_monero/android/CMakeLists.txt b/cw_monero/android/CMakeLists.txt new file mode 100644 index 000000000..0b812409b --- /dev/null +++ b/cw_monero/android/CMakeLists.txt @@ -0,0 +1,218 @@ +cmake_minimum_required(VERSION 3.4.1) + +add_library( cw_monero + SHARED + ./jni/monero_jni.cpp + ../ios/Classes/monero_api.cpp) + + find_library( log-lib log ) + +set(CMAKE_BUILD_TYPE Debug) + +set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../ios/External/android) + +############ +# libsodium +############ + +add_library(sodium STATIC IMPORTED) +set_target_properties(sodium PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/libsodium/lib/${ANDROID_ABI}/libsodium.a) + +############ +# OpenSSL +############ + +add_library(crypto STATIC IMPORTED) +set_target_properties(crypto PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a) + +add_library(ssl STATIC IMPORTED) +set_target_properties(ssl PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a) + +############ +# Boost +############ + +add_library(boost_chrono STATIC IMPORTED) +set_target_properties(boost_chrono PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_chrono.a) + +add_library(boost_date_time STATIC IMPORTED) +set_target_properties(boost_date_time PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_date_time.a) + +add_library(boost_filesystem STATIC IMPORTED) +set_target_properties(boost_filesystem PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_filesystem.a) + +add_library(boost_program_options STATIC IMPORTED) +set_target_properties(boost_program_options PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_program_options.a) + +add_library(boost_regex STATIC IMPORTED) +set_target_properties(boost_regex PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_regex.a) + +add_library(boost_serialization STATIC IMPORTED) +set_target_properties(boost_serialization PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_serialization.a) + +add_library(boost_system STATIC IMPORTED) +set_target_properties(boost_system PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_system.a) + +add_library(boost_thread STATIC IMPORTED) +set_target_properties(boost_thread PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_thread.a) + +add_library(boost_wserialization STATIC IMPORTED) +set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_wserialization.a) + +############# +# Monero +############# + +add_library(wallet_api STATIC IMPORTED) +set_target_properties(wallet_api PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet_api.a) + +add_library(wallet STATIC IMPORTED) +set_target_properties(wallet PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet.a) + +add_library(cryptonote_core STATIC IMPORTED) +set_target_properties(cryptonote_core PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_core.a) + +add_library(cryptonote_basic STATIC IMPORTED) +set_target_properties(cryptonote_basic PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_basic.a) + +add_library(mnemonics STATIC IMPORTED) +set_target_properties(mnemonics PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmnemonics.a) + +add_library(common STATIC IMPORTED) +set_target_properties(common PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcommon.a) + +add_library(cncrypto STATIC IMPORTED) +set_target_properties(cncrypto PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcncrypto.a) + +add_library(ringct STATIC IMPORTED) +set_target_properties(ringct PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct.a) + +add_library(ringct_basic STATIC IMPORTED) +set_target_properties(ringct_basic PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct_basic.a) + +add_library(blockchain_db STATIC IMPORTED) +set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblockchain_db.a) + +add_library(lmdb STATIC IMPORTED) +set_target_properties(lmdb PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/liblmdb.a) + +add_library(easylogging STATIC IMPORTED) +set_target_properties(easylogging PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libeasylogging.a) + +add_library(unbound STATIC IMPORTED) +set_target_properties(unbound PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libunbound.a) + +add_library(epee STATIC IMPORTED) +set_target_properties(epee PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libepee.a) + +add_library(blocks STATIC IMPORTED) +set_target_properties(blocks PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblocks.a) + +add_library(checkpoints STATIC IMPORTED) +set_target_properties(checkpoints PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcheckpoints.a) + +add_library(device STATIC IMPORTED) +set_target_properties(device PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libdevice.a) + +add_library(device_trezor STATIC IMPORTED) +set_target_properties(device_trezor PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libdevice_trezor.a) + +add_library(multisig STATIC IMPORTED) +set_target_properties(multisig PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmultisig.a) + +add_library(version STATIC IMPORTED) +set_target_properties(version PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libversion.a) + +add_library(net STATIC IMPORTED) +set_target_properties(net PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libnet.a) + +add_library(hardforks STATIC IMPORTED) +set_target_properties(hardforks PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libhardforks.a) + +add_library(randomx STATIC IMPORTED) +set_target_properties(randomx PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librandomx.a) + +add_library(rpc_base STATIC IMPORTED) +set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librpc_base.a) + +include_directories( ${EXTERNAL_LIBS_DIR}/monero/include ) + +target_link_libraries( cw_monero + + wallet_api + wallet + cryptonote_core + cryptonote_basic + mnemonics + ringct + ringct_basic + net + common + cncrypto + blockchain_db + lmdb + easylogging + unbound + epee + blocks + checkpoints + device + device_trezor + multisig + version + randomx + hardforks + rpc_base + + boost_chrono + boost_date_time + boost_filesystem + boost_program_options + boost_regex + boost_serialization + boost_system + boost_thread + boost_wserialization + + ssl + crypto + + sodium + + ${log-lib} ) \ No newline at end of file diff --git a/cw_monero/android/build.gradle b/cw_monero/android/build.gradle new file mode 100644 index 000000000..ed8c40213 --- /dev/null +++ b/cw_monero/android/build.gradle @@ -0,0 +1,51 @@ +group 'com.cakewallet.monero' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { + minSdkVersion 21 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } + externalNativeBuild { + // Encapsulates your CMake build configurations. + cmake { + // Provides a relative path to your CMake build script. + path "CMakeLists.txt" + } + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/cw_monero/android/gradle.properties b/cw_monero/android/gradle.properties new file mode 100644 index 000000000..38c8d4544 --- /dev/null +++ b/cw_monero/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/cw_monero/android/gradle/wrapper/gradle-wrapper.properties b/cw_monero/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..019065d1d --- /dev/null +++ b/cw_monero/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/cw_monero/android/jni/monero_jni.cpp b/cw_monero/android/jni/monero_jni.cpp new file mode 100644 index 000000000..83e06a41f --- /dev/null +++ b/cw_monero/android/jni/monero_jni.cpp @@ -0,0 +1,74 @@ +#include +#include +#include "../../ios/Classes/monero_api.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT void JNICALL +Java_com_cakewallet_monero_MoneroApi_setNodeAddressJNI( + JNIEnv *env, + jobject inst, + jstring uri, + jstring login, + jstring password, + jboolean use_ssl, + jboolean is_light_wallet) { + const char *_uri = env->GetStringUTFChars(uri, 0); + const char *_login = ""; + const char *_password = ""; + char *error; + + if (login != NULL) { + _login = env->GetStringUTFChars(login, 0); + } + + if (password != NULL) { + _password = env->GetStringUTFChars(password, 0); + } + char *__uri = (char*) _uri; + char *__login = (char*) _login; + char *__password = (char*) _password; + bool inited = setup_node(__uri, __login, __password, false, false, error); + + if (!inited) { + env->ThrowNew(env->FindClass("java/lang/Exception"), error); + } +} + +JNIEXPORT void JNICALL +Java_com_cakewallet_monero_MoneroApi_connectToNodeJNI( + JNIEnv *env, + jobject inst) { + char *error; + bool is_connected = connect_to_node(error); + + if (!is_connected) { + env->ThrowNew(env->FindClass("java/lang/Exception"), error); + } +} + +JNIEXPORT void JNICALL +Java_com_cakewallet_monero_MoneroApi_startSyncJNI( + JNIEnv *env, + jobject inst) { + start_refresh(); +} + +JNIEXPORT void JNICALL +Java_com_cakewallet_monero_MoneroApi_loadWalletJNI( + JNIEnv *env, + jobject inst, + jstring path, + jstring password) { + char *_path = (char *) env->GetStringUTFChars(path, 0); + char *_password = (char *) env->GetStringUTFChars(password, 0); + + load_wallet(_path, _password, 0); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/cw_monero/android/settings.gradle b/cw_monero/android/settings.gradle new file mode 100644 index 000000000..1f9e2a39d --- /dev/null +++ b/cw_monero/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'cw_monero' diff --git a/cw_monero/android/src/main/AndroidManifest.xml b/cw_monero/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8152415a2 --- /dev/null +++ b/cw_monero/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt b/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt new file mode 100644 index 000000000..593627078 --- /dev/null +++ b/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt @@ -0,0 +1,74 @@ +package com.cakewallet.monero + +import android.app.Activity +import android.os.AsyncTask +import android.os.Looper +import android.os.Handler +import android.os.Process + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry.Registrar + +class doAsync(val handler: () -> Unit) : AsyncTask() { + override fun doInBackground(vararg params: Void?): Void? { + Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO); + handler() + return null + } +} + +class CwMoneroPlugin: MethodCallHandler { + companion object { + val moneroApi = MoneroApi() + val main = Handler(Looper.getMainLooper()); + + init { + System.loadLibrary("cw_monero") + } + + @JvmStatic + fun registerWith(registrar: Registrar) { + val channel = MethodChannel(registrar.messenger(), "cw_monero") + channel.setMethodCallHandler(CwMoneroPlugin()) + } + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "setupNode") { + val uri = call.argument("address") ?: "" + val login = call.argument("login") ?: "" + val password = call.argument("password") ?: "" + val useSSL = false + val isLightWallet = false + doAsync { + try { + moneroApi.setNodeAddressJNI(uri, login, password, useSSL, isLightWallet) + main.post({ + result.success(true) + }); + } catch(e: Throwable) { + main.post({ + result.error("CONNECTION_ERROR", e.message, null) + }); + } + }.execute() + } + if (call.method == "startSync") { + doAsync { + moneroApi.startSyncJNI() + main.post({ + result.success(true) + }); + }.execute() + } + if (call.method == "loadWallet") { + val path = call.argument("path") ?: "" + val password = call.argument("password") ?: "" + moneroApi.loadWalletJNI(path, password) + result.success(true) + } + } +} diff --git a/cw_monero/android/src/main/kotlin/com/cakewallet/monero/MoneroApi.kt b/cw_monero/android/src/main/kotlin/com/cakewallet/monero/MoneroApi.kt new file mode 100644 index 000000000..071577ad3 --- /dev/null +++ b/cw_monero/android/src/main/kotlin/com/cakewallet/monero/MoneroApi.kt @@ -0,0 +1,14 @@ +package com.cakewallet.monero + +class MoneroApi { + external fun setNodeAddressJNI(uri: String, login: String, password: String, use_ssl: Boolean, is_light_wallet: Boolean) + external fun connectToNodeJNI() + external fun startSyncJNI() + external fun loadWalletJNI(path: String, password: String) + + companion object { + init { + System.loadLibrary("cw_monero") + } + } +} \ No newline at end of file diff --git a/cw_monero/ios/.gitignore b/cw_monero/ios/.gitignore new file mode 100644 index 000000000..aa479fd3c --- /dev/null +++ b/cw_monero/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/cw_monero/ios/Assets/.gitkeep b/cw_monero/ios/Assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cw_monero/ios/Classes/CwMoneroPlugin.h b/cw_monero/ios/Classes/CwMoneroPlugin.h new file mode 100644 index 000000000..a42018098 --- /dev/null +++ b/cw_monero/ios/Classes/CwMoneroPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface CwMoneroPlugin : NSObject +@end diff --git a/cw_monero/ios/Classes/CwMoneroPlugin.m b/cw_monero/ios/Classes/CwMoneroPlugin.m new file mode 100644 index 000000000..eee251212 --- /dev/null +++ b/cw_monero/ios/Classes/CwMoneroPlugin.m @@ -0,0 +1,8 @@ +#import "CwMoneroPlugin.h" +#import + +@implementation CwMoneroPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftCwMoneroPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/cw_monero/ios/Classes/CwWalletListener.h b/cw_monero/ios/Classes/CwWalletListener.h new file mode 100644 index 000000000..cbfcb0c4e --- /dev/null +++ b/cw_monero/ios/Classes/CwWalletListener.h @@ -0,0 +1,23 @@ +#include + +struct CWMoneroWalletListener; + +typedef int8_t (*on_new_block_callback)(uint64_t height); +typedef int8_t (*on_need_to_refresh_callback)(); + +typedef struct CWMoneroWalletListener +{ + // on_money_spent_callback *on_money_spent; + // on_money_received_callback *on_money_received; + // on_unconfirmed_money_received_callback *on_unconfirmed_money_received; + // on_new_block_callback *on_new_block; + // on_updated_callback *on_updated; + // on_refreshed_callback *on_refreshed; + + on_new_block_callback on_new_block; +} CWMoneroWalletListener; + +struct TestListener { + // int8_t x; + on_new_block_callback on_new_block; +}; \ No newline at end of file diff --git a/cw_monero/ios/Classes/SwiftCwMoneroPlugin.swift b/cw_monero/ios/Classes/SwiftCwMoneroPlugin.swift new file mode 100644 index 000000000..4c03a3e44 --- /dev/null +++ b/cw_monero/ios/Classes/SwiftCwMoneroPlugin.swift @@ -0,0 +1,14 @@ +import Flutter +import UIKit + +public class SwiftCwMoneroPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "cw_monero", binaryMessenger: registrar.messenger()) + let instance = SwiftCwMoneroPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + result("iOS " + UIDevice.current.systemVersion) + } +} diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp new file mode 100644 index 000000000..757f05d8d --- /dev/null +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -0,0 +1,671 @@ +#include +#include "cstdlib" +#include +#include +#include +#include +#include "thread" +#include "../External/android/monero/include/wallet2_api.h" +#include "CwWalletListener.h" + +using namespace std::chrono_literals; + +#ifdef __cplusplus +extern "C" +{ +#endif + + struct Utf8Box + { + char *value; + + Utf8Box(char *_value) + { + value = _value; + } + }; + + struct SubaddressRow + { + uint64_t id; + char *address; + char *label; + + SubaddressRow(std::size_t _id, char *_address, char *_label) + { + id = static_cast(_id); + address = _address; + label = _label; + } + }; + + struct AccountRow + { + uint64_t id; + char *label; + + AccountRow(std::size_t _id, char *_label) + { + id = static_cast(_id); + label = _label; + } + }; + + struct MoneroWalletListener : Monero::WalletListener + { + uint64_t m_height; + bool m_need_to_refresh; + bool m_new_transaction; + + MoneroWalletListener() + { + m_height = 0; + m_need_to_refresh = false; + m_new_transaction = false; + } + + void moneySpent(const std::string &txId, uint64_t amount) + { + m_new_transaction = true; + } + + void moneyReceived(const std::string &txId, uint64_t amount) + { + m_new_transaction = true; + } + + void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) + { + m_new_transaction = true; + } + + void newBlock(uint64_t height) + { + m_height = height; + } + + void updated() + { + m_need_to_refresh = true; + } + + void refreshed() + { + m_need_to_refresh = true; + } + + void resetNeedToRefresh() + { + m_need_to_refresh = false; + } + + bool isNeedToRefresh() + { + return m_need_to_refresh; + } + + bool isNewTransactionExist() + { + return m_new_transaction; + } + + void resetIsNewTransactionExist() + { + m_new_transaction = false; + } + + uint64_t height() + { + return m_height; + } + }; + + struct TransactionInfoRow + { + uint64_t amount; + uint64_t fee; + uint64_t blockHeight; + uint64_t confirmations; + uint32_t subaddrAccount; + int8_t direction; + int8_t isPending; + + char *hash; + char *paymentId; + + int64_t datetime; + + TransactionInfoRow(Monero::TransactionInfo *transaction) + { + amount = transaction->amount(); + fee = transaction->fee(); + blockHeight = transaction->blockHeight(); + subaddrAccount = transaction->subaddrAccount(); + confirmations = transaction->confirmations(); + datetime = static_cast(transaction->timestamp()); + direction = transaction->direction(); + isPending = static_cast(transaction->isPending()); + std::string *hash_str = new std::string(transaction->hash()); + hash = strdup(hash_str->c_str()); + paymentId = strdup(transaction->paymentId().c_str()); + } + }; + + struct PendingTransactionRaw + { + uint64_t amount; + uint64_t fee; + char *hash; + Monero::PendingTransaction *transaction; + + PendingTransactionRaw(Monero::PendingTransaction *_transaction) + { + transaction = _transaction; + amount = _transaction->amount(); + fee = _transaction->fee(); + hash = strdup(_transaction->txid()[0].c_str()); + } + }; + + Monero::Wallet *m_wallet; + Monero::TransactionHistory *m_transaction_history; + MoneroWalletListener *m_listener; + Monero::Subaddress *m_subaddress; + Monero::SubaddressAccount *m_account; + uint64_t m_last_known_wallet_height; + + void change_current_wallet(Monero::Wallet *wallet) + { + m_wallet = wallet; + // m_listener = nullptr; + + + if (wallet != nullptr) + { + m_transaction_history = wallet->history(); + } + else + { + m_transaction_history = nullptr; + } + + if (wallet != nullptr) + { + m_account = wallet->subaddressAccount(); + } + else + { + m_account = nullptr; + } + + if (wallet != nullptr) + { + m_subaddress = wallet->subaddress(); + } + else + { + m_subaddress = nullptr; + } + } + + Monero::Wallet *get_current_wallet() + { + return m_wallet; + } + + bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); + Monero::Wallet *wallet = walletManager->createWallet(path, password, language, _networkType); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok) + { + error = strdup(errorString.c_str()); + return false; + } + + change_current_wallet(wallet); + + return true; + } + + bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, uint64_t restoreHeight, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->recoveryWallet( + std::string(path), + std::string(password), + std::string(seed), + _networkType, + (uint64_t)restoreHeight); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok) + { + error = strdup(errorString.c_str()); + return false; + } + + change_current_wallet(wallet); + return true; + } + + bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createWalletFromKeys( + std::string(path), + std::string(password), + std::string(language), + _networkType, + (uint64_t)restoreHeight, + std::string(address), + std::string(viewKey), + std::string(spendKey)); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok) + { + error = strdup(errorString.c_str()); + return false; + } + + change_current_wallet(wallet); + return true; + } + + void load_wallet(char *path, char *password, int32_t nettype) + { + Monero::NetworkType networkType = static_cast(nettype); + Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->openWallet(std::string(path), std::string(password), networkType); + change_current_wallet(wallet); + } + + bool is_wallet_exist(char *path) + { + return Monero::WalletManagerFactory::getWalletManager()->walletExists(std::string(path)); + } + + void close_current_wallet() + { + Monero::WalletManagerFactory::getWalletManager()->closeWallet(get_current_wallet()); + change_current_wallet(nullptr); + } + + char *get_filename() + { + return strdup(get_current_wallet()->filename().c_str()); + } + + char *secret_view_key() + { + return strdup(get_current_wallet()->secretViewKey().c_str()); + } + + char *public_view_key() + { + return strdup(get_current_wallet()->publicViewKey().c_str()); + } + + char *secret_spend_key() + { + return strdup(get_current_wallet()->secretSpendKey().c_str()); + } + + char *public_spend_key() + { + return strdup(get_current_wallet()->publicSpendKey().c_str()); + } + + char *get_address(uint32_t account_index, uint32_t address_index) + { + return strdup(get_current_wallet()->address(account_index, address_index).c_str()); + } + + + const char *seed() + { + return strdup(get_current_wallet()->seed().c_str()); + } + + uint64_t get_full_balance(uint32_t account_index) + { + return get_current_wallet()->balance(account_index); + } + + uint64_t get_unlocked_balance(uint32_t account_index) + { + return get_current_wallet()->unlockedBalance(account_index); + } + + uint64_t get_current_height() + { + return get_current_wallet()->blockChainHeight(); + } + + uint64_t get_node_height() + { + return get_current_wallet()->daemonBlockChainHeight(); + } + + bool connect_to_node(char *error) + { + bool is_connected = get_current_wallet()->connectToDaemon(); + + if (!is_connected) + { + error = strdup(get_current_wallet()->errorString().c_str()); + } + + return is_connected; + } + + bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error) + { + Monero::Wallet *wallet = get_current_wallet(); + + std::string _login = ""; + std::string _password = ""; + + if (login != nullptr) + { + _login = std::string(login); + } + + if (password != nullptr) + { + _password = std::string(password); + } + + bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet); + + if (!inited) + { + error = strdup(wallet->errorString().c_str()); + } else if (!wallet->connectToDaemon()) { + error = strdup(wallet->errorString().c_str()); + } + + return inited; + } + + bool is_connected() + { + return get_current_wallet()->connected(); + } + + void start_refresh() + { + get_current_wallet()->refreshAsync(); + get_current_wallet()->startRefresh(); + } + + void set_refresh_from_block_height(uint64_t height) + { + get_current_wallet()->setRefreshFromBlockHeight(height); + } + + void set_recovering_from_seed(bool is_recovery) + { + get_current_wallet()->setRecoveringFromSeed(is_recovery); + } + + void store(char *path) + { + get_current_wallet()->store(std::string(path)); + } + + bool transaction_create(char *address, char *payment_id, char *amount, + uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) + { + nice(19); + + auto priority = static_cast(priority_raw); + std::string _payment_id; + Monero::PendingTransaction *transaction; + + if (payment_id != nullptr) + { + _payment_id = std::string(payment_id); + } + + if (amount != nullptr) + { + uint64_t _amount = Monero::Wallet::amountFromString(std::string(amount)); + transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account); + } + else + { + transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional(), m_wallet->defaultMixin(), priority, subaddr_account); + } + + int status = transaction->status(); + + if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) + { + error = Utf8Box(strdup(transaction->errorString().c_str())); + return false; + } + + m_listener->m_new_transaction = true; + + pendingTransaction = PendingTransactionRaw(transaction); + return true; + } + + bool transaction_commit(PendingTransactionRaw *transaction, Utf8Box &error) + { + bool committed = transaction->transaction->commit(); + + if (!committed) + { + error = Utf8Box(strdup(transaction->transaction->errorString().c_str())); + } else { + m_listener->m_new_transaction = true; + } + + return committed; + } + + uint64_t get_syncing_height() + { + if (m_listener == nullptr) { + return 0; + } + + uint64_t _height = m_listener->height(); + + if (_height != m_last_known_wallet_height) + { + m_last_known_wallet_height = _height; + } + + return _height; + } + + uint64_t is_needed_to_refresh() + { + if (m_listener == nullptr) { + return false; + } + + bool should_refresh = m_listener->isNeedToRefresh(); + + if (should_refresh) + { + m_listener->resetNeedToRefresh(); + } + + return should_refresh; + } + + uint8_t is_new_transaction_exist() + { + if (m_listener == nullptr) { + return false; + } + + bool is_new_transaction_exist = m_listener->isNewTransactionExist(); + + if (is_new_transaction_exist) + { + m_listener->resetIsNewTransactionExist(); + } + + return is_new_transaction_exist; + } + + void set_listener() + { + m_last_known_wallet_height = 0; + + if (m_listener != nullptr) + { + free(m_listener); + } + + m_listener = new MoneroWalletListener(); + get_current_wallet()->setListener(m_listener); + } + + int64_t *subaddrress_get_all() + { + std::vector _subaddresses = m_subaddress->getAll(); + size_t size = _subaddresses.size(); + int64_t *subaddresses = (int64_t *)malloc(size * sizeof(int64_t)); + + for (int i = 0; i < size; i++) + { + Monero::SubaddressRow *row = _subaddresses[i]; + SubaddressRow *_row = new SubaddressRow(row->getRowId(), strdup(row->getAddress().c_str()), strdup(row->getLabel().c_str())); + subaddresses[i] = reinterpret_cast(_row); + } + + return subaddresses; + } + + int32_t subaddrress_size() + { + std::vector _subaddresses = m_subaddress->getAll(); + return _subaddresses.size(); + } + + void subaddress_add_row(uint32_t accountIndex, char *label) + { + m_subaddress->addRow(accountIndex, std::string(label)); + } + + void subaddress_set_label(uint32_t accountIndex, uint32_t addressIndex, char *label) + { + m_subaddress->setLabel(accountIndex, addressIndex, std::string(label)); + } + + void subaddress_refresh(uint32_t accountIndex) + { + m_subaddress->refresh(accountIndex); + } + + int32_t account_size() + { + std::vector _accocunts = m_account->getAll(); + return _accocunts.size(); + } + + int64_t *account_get_all() + { + std::vector _accocunts = m_account->getAll(); + size_t size = _accocunts.size(); + int64_t *accocunts = (int64_t *)malloc(size * sizeof(int64_t)); + + for (int i = 0; i < size; i++) + { + Monero::SubaddressAccountRow *row = _accocunts[i]; + AccountRow *_row = new AccountRow(row->getRowId(), strdup(row->getLabel().c_str())); + accocunts[i] = reinterpret_cast(_row); + } + + return accocunts; + } + + void account_add_row(char *label) + { + m_account->addRow(std::string(label)); + } + + void account_set_label_row(uint32_t account_index, char *label) + { + m_account->setLabel(account_index, label); + } + + void account_refresh() + { + m_account->refresh(); + } + + int64_t *transactions_get_all() + { + std::vector transactions = m_transaction_history->getAll(); + size_t size = transactions.size(); + int64_t *transactionAddresses = (int64_t *)malloc(size * sizeof(int64_t)); + + for (int i = 0; i < size; i++) + { + Monero::TransactionInfo *row = transactions[i]; + TransactionInfoRow *tx = new TransactionInfoRow(row); + transactionAddresses[i] = reinterpret_cast(tx); + } + + return transactionAddresses; + } + + void transactions_refresh() + { + m_transaction_history->refresh(); + } + + int64_t transactions_count() + { + return m_transaction_history->count(); + } + + int LedgerExchange( + unsigned char *command, + unsigned int cmd_len, + unsigned char *response, + unsigned int max_resp_len) + { + return -1; + } + + int LedgerFind(char *buffer, size_t len) + { + return -1; + } + + void on_startup() + { + Monero::Utils::onStartup(); + } + + void rescan_blockchain() + { + m_wallet->rescanBlockchainAsync(); + } + +#ifdef __cplusplus +} +#endif diff --git a/cw_monero/ios/Classes/monero_api.h b/cw_monero/ios/Classes/monero_api.h new file mode 100644 index 000000000..3e2ebcff2 --- /dev/null +++ b/cw_monero/ios/Classes/monero_api.h @@ -0,0 +1,35 @@ +#include +#include +#include +#include "CwWalletListener.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error); +bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, uint64_t restoreHeight, char *error); +bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error); +void load_wallet(char *path, char *password, int32_t nettype); +bool is_wallet_exist(char *path); + +char *get_filename(); +const char *seed(); +char *get_address(uint32_t account_index, uint32_t address_index); +uint64_t get_full_balance(uint32_t account_index); +uint64_t get_unlocked_balance(uint32_t account_index); +uint64_t get_current_height(); +uint64_t get_node_height(); + +bool is_connected(); + +bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error); +bool connect_to_node(char *error); +void start_refresh(); +void set_refresh_from_block_height(uint64_t height); +void set_recovering_from_seed(bool is_recovery); +void store(char *path); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/cw_monero/ios/cw_monero.podspec b/cw_monero/ios/cw_monero.podspec new file mode 100644 index 000000000..1fd4a89c2 --- /dev/null +++ b/cw_monero/ios/cw_monero.podspec @@ -0,0 +1,51 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint cw_monero.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'cw_monero' + s.version = '0.0.2' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h, Classes/*.h, External/ios/libs/monero/include/src/**/*.h, External/ios/libs/monero/include/contrib/**/*.h, External/ios/libs/monero/include/External/ios/**/*.h' + s.dependency 'Flutter' + s.platform = :ios, '9.0' + s.swift_version = '4.0' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'arm64' } + s.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/Classes/*.h" } + + s.subspec 'OpenSSL' do |openssl| + openssl.preserve_paths = 'External/ios/libs/OpenSSL/include/openssl/*.h', 'External/ios/libs/OpenSSL/include/LICENSE' + openssl.vendored_libraries = 'External/ios/libs/OpenSSL/lib/libcrypto.a', 'External/ios/libs/OpenSSL/lib/libssl.a' + openssl.libraries = 'ssl', 'crypto' + openssl.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/libs/OpenSSL/include/**" } + end + + s.subspec 'Monero' do |monero| + monero.preserve_paths = 'External/ios/libs/monero/include/src/**/*.h', 'External/ios/libs/monero/include/External/ios/**/*.h', 'External/ios/libs/monero/include/contrib/**/*.h' + monero.vendored_libraries = 'External/ios/libs/monero/libs/lib-ios/*.a' + monero.libraries = 'easylogging', 'epee', 'unbound', 'wallet_merged', 'lmdb', 'randomx' + monero.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/libs/monero/include/src/**" } + end + + s.subspec 'Boost' do |boost| + boost.preserve_paths = 'External/ios/libs/boost/include/**/*.h', 'External/ios/libs/boost/include/**/*.h' + boost.vendored_libraries = 'External/ios/libs/boost/build/libs/universal/*.a' + boost.libraries = 'boost', 'boost_wserialization', 'boost_thread', 'boost_system', 'boost_signals', 'boost_serialization', 'boost_regex', 'boost_random', 'boost_program_options', 'boost_locale', 'boost_graph', 'boost_filesystem', 'boost_date_time', 'boost_chrono' + boost.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/libs/boost/include/**" } + end + + s.subspec 'Sodium' do |sodium| + sodium.preserve_paths = 'External/ios/libs/sodium/include/**/*.h' + sodium.vendored_libraries = 'External/ios/libs/sodium/lib/libsodium.a' + sodium.libraries = 'sodium' + sodium.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/libs/sodium/include/**" } + end +end \ No newline at end of file diff --git a/cw_monero/lib/account_list.dart b/cw_monero/lib/account_list.dart new file mode 100644 index 000000000..f72c57ae2 --- /dev/null +++ b/cw_monero/lib/account_list.dart @@ -0,0 +1,61 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:cw_monero/signatures.dart'; +import 'package:cw_monero/types.dart'; +import 'package:cw_monero/monero_api.dart'; +import 'package:cw_monero/structs/account_row.dart'; +import 'package:flutter/foundation.dart'; + +final accountSizeNative = moneroApi + .lookup>('account_size') + .asFunction(); + +final accountRefreshNative = moneroApi + .lookup>('account_refresh') + .asFunction(); + +final accountGetAllNative = moneroApi + .lookup>('account_get_all') + .asFunction(); + +final accountAddNewNative = moneroApi + .lookup>('account_add_row') + .asFunction(); + +final accountSetLabelNative = moneroApi + .lookup>('account_set_label_row') + .asFunction(); + +refreshAccounts() => accountRefreshNative(); + +List getAllAccount() { + final size = accountSizeNative(); + final accountAddressesPointer = accountGetAllNative(); + final accountAddresses = accountAddressesPointer.asTypedList(size); + + return accountAddresses + .map((addr) => Pointer.fromAddress(addr).ref) + .toList(); +} + +addAccountSync({String label}) { + final labelPointer = Utf8.toUtf8(label); + accountAddNewNative(labelPointer); + free(labelPointer); +} + +setLabelForAccountSync({int accountIndex, String label}) { + final labelPointer = Utf8.toUtf8(label); + accountSetLabelNative(accountIndex, labelPointer); + free(labelPointer); +} + +_addAccount(String label) => addAccountSync(label: label); + +_setLabelForAccount(Map args) => setLabelForAccountSync( + label: args['label'], accountIndex: args['accountIndex']); + +Future addAccount({String label}) async => compute(_addAccount, label); + +Future setLabelForAccount({int accountIndex, String label}) async => compute( + _setLabelForAccount, {'accountIndex': accountIndex, 'label': label}); diff --git a/cw_monero/lib/convert_utf8_to_string.dart b/cw_monero/lib/convert_utf8_to_string.dart new file mode 100644 index 000000000..7fa5a68df --- /dev/null +++ b/cw_monero/lib/convert_utf8_to_string.dart @@ -0,0 +1,8 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +String convertUTF8ToString({Pointer pointer}) { + final str = Utf8.fromUtf8(pointer); + free(pointer); + return str; +} \ No newline at end of file diff --git a/cw_monero/lib/exceptions/connection_to_node_exception.dart b/cw_monero/lib/exceptions/connection_to_node_exception.dart new file mode 100644 index 000000000..4a3733f52 --- /dev/null +++ b/cw_monero/lib/exceptions/connection_to_node_exception.dart @@ -0,0 +1,5 @@ +class ConnectionToNodeException implements Exception { + final String message; + + ConnectionToNodeException({this.message}); +} \ No newline at end of file diff --git a/cw_monero/lib/exceptions/creation_transaction_exception.dart b/cw_monero/lib/exceptions/creation_transaction_exception.dart new file mode 100644 index 000000000..94bb2a53f --- /dev/null +++ b/cw_monero/lib/exceptions/creation_transaction_exception.dart @@ -0,0 +1,8 @@ +class CreationTransactionException implements Exception { + final String message; + + CreationTransactionException({this.message}); + + @override + String toString() => message; +} \ No newline at end of file diff --git a/cw_monero/lib/exceptions/setup_wallet_exception.dart b/cw_monero/lib/exceptions/setup_wallet_exception.dart new file mode 100644 index 000000000..0c044fe67 --- /dev/null +++ b/cw_monero/lib/exceptions/setup_wallet_exception.dart @@ -0,0 +1,5 @@ +class SetupWalletException implements Exception { + final String message; + + SetupWalletException({this.message}); +} \ No newline at end of file diff --git a/cw_monero/lib/exceptions/wallet_creation_exception.dart b/cw_monero/lib/exceptions/wallet_creation_exception.dart new file mode 100644 index 000000000..cb1984ab6 --- /dev/null +++ b/cw_monero/lib/exceptions/wallet_creation_exception.dart @@ -0,0 +1,5 @@ +class WalletCreationException implements Exception { + final String message; + + WalletCreationException({this.message}); +} \ No newline at end of file diff --git a/cw_monero/lib/exceptions/wallet_restore_from_keys_exception.dart b/cw_monero/lib/exceptions/wallet_restore_from_keys_exception.dart new file mode 100644 index 000000000..bf16c1cae --- /dev/null +++ b/cw_monero/lib/exceptions/wallet_restore_from_keys_exception.dart @@ -0,0 +1,5 @@ +class WalletRestoreFromKeysException implements Exception { + final String message; + + WalletRestoreFromKeysException({this.message}); +} \ No newline at end of file diff --git a/cw_monero/lib/exceptions/wallet_restore_from_seed_exception.dart b/cw_monero/lib/exceptions/wallet_restore_from_seed_exception.dart new file mode 100644 index 000000000..6d48684ff --- /dev/null +++ b/cw_monero/lib/exceptions/wallet_restore_from_seed_exception.dart @@ -0,0 +1,5 @@ +class WalletRestoreFromSeedException implements Exception { + final String message; + + WalletRestoreFromSeedException({this.message}); +} \ No newline at end of file diff --git a/cw_monero/lib/monero_api.dart b/cw_monero/lib/monero_api.dart new file mode 100644 index 000000000..398d737d1 --- /dev/null +++ b/cw_monero/lib/monero_api.dart @@ -0,0 +1,6 @@ +import 'dart:ffi'; +import 'dart:io'; + +final DynamicLibrary moneroApi = Platform.isAndroid + ? DynamicLibrary.open("libcw_monero.so") + : DynamicLibrary.open("cw_monero.framework/cw_monero"); \ No newline at end of file diff --git a/cw_monero/lib/signatures.dart b/cw_monero/lib/signatures.dart new file mode 100644 index 000000000..c7db41159 --- /dev/null +++ b/cw_monero/lib/signatures.dart @@ -0,0 +1,108 @@ +import 'dart:ffi'; +import 'package:cw_monero/structs/pending_transaction.dart'; +import 'package:cw_monero/structs/ut8_box.dart'; +import 'package:ffi/ffi.dart'; + +typedef create_wallet = Int8 Function( + Pointer, Pointer, Pointer, Int32, Pointer); + +typedef restore_wallet_from_seed = Int8 Function( + Pointer, Pointer, Pointer, Int32, Int64, Pointer); + +typedef restore_wallet_from_keys = Int8 Function(Pointer, Pointer, + Pointer, Pointer, Pointer, Pointer, Int32, Int64, Pointer); + +typedef is_wallet_exist = Int8 Function(Pointer); + +typedef load_wallet = Void Function(Pointer, Pointer, Int8); + +typedef get_filename = Pointer Function(); + +typedef get_seed = Pointer Function(); + +typedef get_address = Pointer Function(Int32, Int32); + +typedef get_full_balanace = Int64 Function(Int32); + +typedef get_unlocked_balanace = Int64 Function(Int32); + +typedef get_current_height = Int64 Function(); + +typedef get_node_height = Int64 Function(); + +typedef is_connected = Int8 Function(); + +typedef setup_node = Int8 Function( + Pointer, Pointer, Pointer, Int8, Int8, Pointer); + +typedef start_refresh = Void Function(); + +typedef connect_to_node = Int8 Function(); + +typedef set_refresh_from_block_height = Void Function(Int64); + +typedef set_recovering_from_seed = Void Function(Int8); + +typedef store_c = Void Function(Pointer); + +typedef set_listener = Void Function(); + +typedef get_syncing_height = Int64 Function(); + +typedef is_needed_to_refresh = Int8 Function(); + +typedef is_new_transaction_exist = Int8 Function(); + +typedef subaddrress_size = Int32 Function(); + +typedef subaddrress_refresh = Void Function(Int32); + +typedef subaddress_get_all = Pointer Function(); + +typedef subaddress_add_new = Void Function( + Int32 accountIndex, Pointer label); + +typedef subaddress_set_label = Void Function( + Int32 accountIndex, Int32 addressIndex, Pointer label); + +typedef account_size = Int32 Function(); + +typedef account_refresh = Void Function(); + +typedef account_get_all = Pointer Function(); + +typedef account_add_new = Void Function(Pointer label); + +typedef account_set_label = Void Function( + Int32 accountIndex, Pointer label); + + typedef transactions_refresh = Void Function(); + +typedef transactions_count = Int64 Function(); + +typedef transactions_get_all = Pointer Function(); + +typedef transaction_create = Int8 Function( + Pointer address, + Pointer paymentId, + Pointer amount, + Int8 priorityRaw, + Int32 subaddrAccount, + Pointer error, + Pointer pendingTransaction); + +typedef transaction_commit = Int8 Function(Pointer, Pointer); + +typedef secret_view_key = Pointer Function(); + +typedef public_view_key = Pointer Function(); + +typedef secret_spend_key = Pointer Function(); + +typedef public_spend_key = Pointer Function(); + +typedef close_current_wallet = Void Function(); + +typedef on_startup = Void Function(); + +typedef rescan_blockchain = Void Function(); diff --git a/cw_monero/lib/structs/account_row.dart b/cw_monero/lib/structs/account_row.dart new file mode 100644 index 000000000..c3fc22de1 --- /dev/null +++ b/cw_monero/lib/structs/account_row.dart @@ -0,0 +1,11 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class AccountRow extends Struct { + @Int64() + int id; + Pointer label; + + String getLabel() => Utf8.fromUtf8(label); + int getId() => id; +} diff --git a/cw_monero/lib/structs/pending_transaction.dart b/cw_monero/lib/structs/pending_transaction.dart new file mode 100644 index 000000000..fa88a9faa --- /dev/null +++ b/cw_monero/lib/structs/pending_transaction.dart @@ -0,0 +1,23 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class PendingTransactionRaw extends Struct { + @Int64() + int amount; + + @Int64() + int fee; + + Pointer hash; + + String getHash() => Utf8.fromUtf8(hash); +} + +class PendingTransactionDescription { + final int amount; + final int fee; + final String hash; + final int pointerAddress; + + PendingTransactionDescription({this.amount, this.fee, this.hash, this.pointerAddress}); +} \ No newline at end of file diff --git a/cw_monero/lib/structs/subaddress_row.dart b/cw_monero/lib/structs/subaddress_row.dart new file mode 100644 index 000000000..1673e00c7 --- /dev/null +++ b/cw_monero/lib/structs/subaddress_row.dart @@ -0,0 +1,13 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class SubaddressRow extends Struct { + @Int64() + int id; + Pointer address; + Pointer label; + + String getLabel() => Utf8.fromUtf8(label); + String getAddress() => Utf8.fromUtf8(address); + int getId() => id; +} \ No newline at end of file diff --git a/cw_monero/lib/structs/transaction_info_row.dart b/cw_monero/lib/structs/transaction_info_row.dart new file mode 100644 index 000000000..0a0613a42 --- /dev/null +++ b/cw_monero/lib/structs/transaction_info_row.dart @@ -0,0 +1,38 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class TransactionInfoRow extends Struct { + @Uint64() + int amount; + + @Uint64() + int fee; + + @Uint64() + int blockHeight; + + @Uint64() + int confirmations; + + @Uint32() + int subaddrAccount; + + @Int8() + int direction; + + @Int8() + int isPending; + + Pointer hash; + + Pointer paymentId; + + @Int64() + int datetime; + + int getDatetime() => datetime; + int getAmount() => amount >= 0 ? amount : amount * -1; + bool getIsPending() => isPending != 0; + String getHash() => Utf8.fromUtf8(hash); + String getPaymentId() => Utf8.fromUtf8(paymentId); +} diff --git a/cw_monero/lib/structs/ut8_box.dart b/cw_monero/lib/structs/ut8_box.dart new file mode 100644 index 000000000..a6f41bc75 --- /dev/null +++ b/cw_monero/lib/structs/ut8_box.dart @@ -0,0 +1,8 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class Utf8Box extends Struct { + Pointer value; + + String getValue() => Utf8.fromUtf8(value); +} diff --git a/cw_monero/lib/subaddress_list.dart b/cw_monero/lib/subaddress_list.dart new file mode 100644 index 000000000..ba93af453 --- /dev/null +++ b/cw_monero/lib/subaddress_list.dart @@ -0,0 +1,72 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cw_monero/signatures.dart'; +import 'package:cw_monero/types.dart'; +import 'package:cw_monero/monero_api.dart'; +import 'package:cw_monero/structs/subaddress_row.dart'; + +final subaddressSizeNative = moneroApi + .lookup>('subaddrress_size') + .asFunction(); + +final subaddressRefreshNative = moneroApi + .lookup>('subaddress_refresh') + .asFunction(); + +final subaddrressGetAllNative = moneroApi + .lookup>('subaddrress_get_all') + .asFunction(); + +final subaddrressAddNewNative = moneroApi + .lookup>('subaddress_add_row') + .asFunction(); + +final subaddrressSetLabelNative = moneroApi + .lookup>('subaddress_set_label') + .asFunction(); + +refreshSubaddresses({int accountIndex}) => + subaddressRefreshNative(accountIndex); + +List getAllSubaddresses() { + refreshSubaddresses(accountIndex: 0); + final size = subaddressSizeNative(); + final subaddressAddressesPointer = subaddrressGetAllNative(); + final subaddressAddresses = subaddressAddressesPointer.asTypedList(size); + + return subaddressAddresses + .map((addr) => Pointer.fromAddress(addr).ref) + .toList(); +} + +addSubaddressSync({int accountIndex, String label}) { + final labelPointer = Utf8.toUtf8(label); + subaddrressAddNewNative(accountIndex, labelPointer); + free(labelPointer); +} + +setLabelForSubaddressSync({int accountIndex, int addressIndex, String label}) { + final labelPointer = Utf8.toUtf8(label); + subaddrressSetLabelNative(accountIndex, addressIndex, labelPointer); + free(labelPointer); +} + +_addSubaddress(Map args) => + addSubaddressSync(accountIndex: args['accountIndex'], label: args['label']); + +_setLabelForSubaddress(Map args) => setLabelForSubaddressSync( + accountIndex: args['accountIndex'], + addressIndex: args['addressIndex'], + label: args['label']); + +Future addSubaddress({int accountIndex, String label}) async => + compute(_addSubaddress, {'accountIndex': accountIndex, 'label': label}); + +Future setLabelForSubaddress( + {int accountIndex, int addressIndex, String label}) => + compute(_setLabelForSubaddress, { + 'accountIndex': accountIndex, + 'addressIndex': addressIndex, + 'label': label + }); \ No newline at end of file diff --git a/cw_monero/lib/transaction_history.dart b/cw_monero/lib/transaction_history.dart new file mode 100644 index 000000000..1b3ce9126 --- /dev/null +++ b/cw_monero/lib/transaction_history.dart @@ -0,0 +1,122 @@ +import 'dart:ffi'; +import 'package:cw_monero/structs/ut8_box.dart'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cw_monero/signatures.dart'; +import 'package:cw_monero/types.dart'; +import 'package:cw_monero/monero_api.dart'; +import 'package:cw_monero/structs/transaction_info_row.dart'; +import 'package:cw_monero/structs/pending_transaction.dart'; +import 'package:cw_monero/exceptions/creation_transaction_exception.dart'; + +final transactionsRefreshNative = moneroApi + .lookup>('transactions_refresh') + .asFunction(); + +final transactionsCountNative = moneroApi + .lookup>('transactions_count') + .asFunction(); + +final transactionsGetAllNative = moneroApi + .lookup>('transactions_get_all') + .asFunction(); + +final transactionCreateNative = moneroApi + .lookup>('transaction_create') + .asFunction(); + +final transactionCommitNative = moneroApi + .lookup>('transaction_commit') + .asFunction(); + +refreshTransactions() => transactionsRefreshNative(); + +int countOfTransactions() => transactionsCountNative(); + +List getAllTransations() { + final size = transactionsCountNative(); + final transactionsPointer = transactionsGetAllNative(); + final transactionsAddresses = transactionsPointer.asTypedList(size); + + return transactionsAddresses + .map((addr) => Pointer.fromAddress(addr).ref) + .toList(); +} + +PendingTransactionDescription createTransactionSync( + {String address, + String paymentId, + String amount, + int priorityRaw, + int accountIndex = 0}) { + final addressPointer = Utf8.toUtf8(address); + final paymentIdPointer = Utf8.toUtf8(paymentId); + final amountPointer = amount != null ? Utf8.toUtf8(amount) : nullptr; + final errorMessagePointer = allocate(); + final pendingTransactionRawPointer = allocate(); + final created = transactionCreateNative( + addressPointer, + paymentIdPointer, + amountPointer, + priorityRaw, + accountIndex, + errorMessagePointer, + pendingTransactionRawPointer) != + 0; + + free(addressPointer); + free(paymentIdPointer); + + if (amountPointer != nullptr) { + free(amountPointer); + } + + if (!created) { + final message = errorMessagePointer.ref.getValue(); + free(errorMessagePointer); + throw CreationTransactionException(message: message); + } + + return PendingTransactionDescription( + amount: pendingTransactionRawPointer.ref.amount, + fee: pendingTransactionRawPointer.ref.fee, + hash: pendingTransactionRawPointer.ref.getHash(), + pointerAddress: pendingTransactionRawPointer.address); +} + +commitTransactionFromPointerAddress({int address}) => commitTransaction( + transactionPointer: Pointer.fromAddress(address)); + +commitTransaction({Pointer transactionPointer}) { + final errorMessagePointer = allocate(); + final isCommited = + transactionCommitNative(transactionPointer, errorMessagePointer) != 0; + + if (!isCommited) { + final message = errorMessagePointer.ref.getValue(); + free(errorMessagePointer); + throw CreationTransactionException(message: message); + } +} + +PendingTransactionDescription _createTransactionSync(Map args) => + createTransactionSync( + address: args['address'], + paymentId: args['paymentId'], + amount: args['amount'], + priorityRaw: args['priorityRaw'], + accountIndex: args['accountIndex']); + +Future createTransaction( + {String address, + String paymentId, + String amount, + int priorityRaw, + int accountIndex = 0}) => + compute(_createTransactionSync, { + 'address': address, + 'paymentId': paymentId, + 'amount': amount, + 'priorityRaw': priorityRaw, + 'accountIndex': accountIndex + }); diff --git a/cw_monero/lib/types.dart b/cw_monero/lib/types.dart new file mode 100644 index 000000000..e2602ce91 --- /dev/null +++ b/cw_monero/lib/types.dart @@ -0,0 +1,106 @@ +import 'dart:ffi'; +import 'package:cw_monero/structs/pending_transaction.dart'; +import 'package:cw_monero/structs/ut8_box.dart'; +import 'package:ffi/ffi.dart'; + +typedef CreateWallet = int Function( + Pointer, Pointer, Pointer, int, Pointer); + +typedef RestoreWalletFromSeed = int Function( + Pointer, Pointer, Pointer, int, int, Pointer); + +typedef RestoreWalletFromKeys = int Function(Pointer, Pointer, + Pointer, Pointer, Pointer, Pointer, int, int, Pointer); + +typedef IsWalletExist = int Function(Pointer); + +typedef LoadWallet = void Function(Pointer, Pointer, int); + +typedef GetFilename = Pointer Function(); + +typedef GetSeed = Pointer Function(); + +typedef GetAddress = Pointer Function(int, int); + +typedef GetFullBalance = int Function(int); + +typedef GetUnlockedBalance = int Function(int); + +typedef GetCurrentHeight = int Function(); + +typedef GetNodeHeight = int Function(); + +typedef IsConnected = int Function(); + +typedef SetupNode = int Function( + Pointer, Pointer, Pointer, int, int, Pointer); + +typedef StartRefresh = void Function(); + +typedef ConnectToNode = int Function(); + +typedef SetRefreshFromBlockHeight = void Function(int); + +typedef SetRecoveringFromSeed = void Function(int); + +typedef Store = void Function(Pointer); + +typedef SetListener = void Function(); + +typedef GetSyncingHeight = int Function(); + +typedef IsNeededToRefresh = int Function(); + +typedef IsNewTransactionExist = int Function(); + +typedef SubaddressSize = int Function(); + +typedef SubaddressRefresh = void Function(int); + +typedef SubaddressGetAll = Pointer Function(); + +typedef SubaddressAddNew = void Function(int accountIndex, Pointer label); + +typedef SubaddressSetLabel = void Function( + int accountIndex, int addressIndex, Pointer label); + +typedef AccountSize = int Function(); + +typedef AccountRefresh = void Function(); + +typedef AccountGetAll = Pointer Function(); + +typedef AccountAddNew = void Function(Pointer label); + +typedef AccountSetLabel = void Function(int accountIndex, Pointer label); + +typedef TransactionsRefresh = void Function(); + +typedef TransactionsCount = int Function(); + +typedef TransactionsGetAll = Pointer Function(); + +typedef TransactionCreate = int Function( + Pointer address, + Pointer paymentId, + Pointer amount, + int priorityRaw, + int subaddrAccount, + Pointer error, + Pointer pendingTransaction); + +typedef TransactionCommit = int Function(Pointer, Pointer); + +typedef SecretViewKey = Pointer Function(); + +typedef PublicViewKey = Pointer Function(); + +typedef SecretSpendKey = Pointer Function(); + +typedef PublicSpendKey = Pointer Function(); + +typedef CloseCurrentWallet = void Function(); + +typedef OnStartup = void Function(); + +typedef RescanBlockchainAsync = void Function(); \ No newline at end of file diff --git a/cw_monero/lib/wallet.dart b/cw_monero/lib/wallet.dart new file mode 100644 index 000000000..560d6a79c --- /dev/null +++ b/cw_monero/lib/wallet.dart @@ -0,0 +1,283 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:cw_monero/convert_utf8_to_string.dart'; +import 'package:cw_monero/signatures.dart'; +import 'package:cw_monero/types.dart'; +import 'package:cw_monero/monero_api.dart'; +import 'package:cw_monero/exceptions/setup_wallet_exception.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +int _boolToInt(bool value) => value ? 1 : 0; + +final moneroAPIChannel = const MethodChannel('cw_monero'); + +final getFileNameNative = moneroApi + .lookup>('get_filename') + .asFunction(); + +final getSeedNative = + moneroApi.lookup>('seed').asFunction(); + +final getAddressNative = moneroApi + .lookup>('get_address') + .asFunction(); + +final getFullBalanceNative = moneroApi + .lookup>('get_full_balance') + .asFunction(); + +final getUnlockedBalanceNative = moneroApi + .lookup>('get_unlocked_balance') + .asFunction(); + +final getCurrentHeightNative = moneroApi + .lookup>('get_current_height') + .asFunction(); + +final getNodeHeightNative = moneroApi + .lookup>('get_node_height') + .asFunction(); + +final isConnectedNative = moneroApi + .lookup>('is_connected') + .asFunction(); + +final setupNodeNative = moneroApi + .lookup>('setup_node') + .asFunction(); + +final startRefreshNative = moneroApi + .lookup>('start_refresh') + .asFunction(); + +final connecToNodeNative = moneroApi + .lookup>('connect_to_node') + .asFunction(); + +final setRefreshFromBlockHeightNative = moneroApi + .lookup>( + 'set_refresh_from_block_height') + .asFunction(); + +final setRecoveringFromSeedNative = moneroApi + .lookup>( + 'set_recovering_from_seed') + .asFunction(); + +final storeNative = + moneroApi.lookup>('store').asFunction(); + +final setListenerNative = moneroApi + .lookup>('set_listener') + .asFunction(); + +final getSyncingHeightNative = moneroApi + .lookup>('get_syncing_height') + .asFunction(); + +final isNeededToRefreshNative = moneroApi + .lookup>('is_needed_to_refresh') + .asFunction(); + +final isNewTransactionExistNative = moneroApi + .lookup>( + 'is_new_transaction_exist') + .asFunction(); + +final getSecretViewKeyNative = moneroApi + .lookup>('secret_view_key') + .asFunction(); + +final getPublicViewKeyNative = moneroApi + .lookup>('public_view_key') + .asFunction(); + +final getSecretSpendKeyNative = moneroApi + .lookup>('secret_spend_key') + .asFunction(); + +final getPublicSpendKeyNative = moneroApi + .lookup>('public_spend_key') + .asFunction(); + +final closeCurrentWalletNative = moneroApi + .lookup>('close_current_wallet') + .asFunction(); + +final onStartupNative = moneroApi + .lookup>('on_startup') + .asFunction(); + +final rescanBlockchainAsyncNative = moneroApi + .lookup>('rescan_blockchain') + .asFunction(); + +int getSyncingHeight() => getSyncingHeightNative(); + +bool isNeededToRefresh() => isNeededToRefreshNative() != 0; + +bool isNewTransactionExist() => isNewTransactionExistNative() != 0; + +String getFilename() => convertUTF8ToString(pointer: getFileNameNative()); + +String getSeed() => convertUTF8ToString(pointer: getSeedNative()); + +String getAddress({int accountIndex = 0, int addressIndex = 0}) => + convertUTF8ToString(pointer: getAddressNative(accountIndex, addressIndex)); + +int getFullBalance({int accountIndex = 0}) => + getFullBalanceNative(accountIndex); + +int getUnlockedBalance({int accountIndex = 0}) => + getUnlockedBalanceNative(accountIndex); + +int getCurrentHeight() => getCurrentHeightNative(); + +int getNodeHeightSync() => getNodeHeightNative(); + +bool isConnectedSync() => isConnectedNative() != 0; + +bool setupNodeSync( + {String address, + String login, + String password, + bool useSSL = false, + bool isLightWallet = false}) { + final addressPointer = Utf8.toUtf8(address); + Pointer loginPointer; + Pointer passwordPointer; + + if (login != null) { + loginPointer = Utf8.toUtf8(login); + } + + if (password != null) { + passwordPointer = Utf8.toUtf8(password); + } + + final errorMessagePointer = allocate(); + final isSetupNode = setupNodeNative( + addressPointer, + loginPointer, + passwordPointer, + _boolToInt(useSSL), + _boolToInt(isLightWallet), + errorMessagePointer) != + 0; + + free(addressPointer); + free(loginPointer); + free(passwordPointer); + + if (!isSetupNode) { + throw SetupWalletException( + message: convertUTF8ToString(pointer: errorMessagePointer)); + } + + return isSetupNode; +} + +startRefreshSync() => startRefreshNative(); + +Future connectToNode() async => connecToNodeNative() != 0; + +setRefreshFromBlockHeight({int height}) => + setRefreshFromBlockHeightNative(height); + +setRecoveringFromSeed({bool isRecovery}) => + setRecoveringFromSeedNative(_boolToInt(isRecovery)); + +storeSync() { + final pathPointer = Utf8.toUtf8(''); + storeNative(pathPointer); + free(pathPointer); +} + +closeCurrentWallet() => closeCurrentWalletNative(); + +String getSecretViewKey() => + convertUTF8ToString(pointer: getSecretViewKeyNative()); + +String getPublicViewKey() => + convertUTF8ToString(pointer: getPublicViewKeyNative()); + +String getSecretSpendKey() => + convertUTF8ToString(pointer: getSecretSpendKeyNative()); + +String getPublicSpendKey() => + convertUTF8ToString(pointer: getPublicSpendKeyNative()); + +Timer _updateSyncInfoTimer; + +int _lastKnownBlockHeight = 0; + +setListeners(Future Function(int) onNewBlock, Future Function() onNeedToRefresh, + Future Function() onNewTransaction) { + if (_updateSyncInfoTimer != null) { + _updateSyncInfoTimer.cancel(); + } + + _updateSyncInfoTimer = Timer.periodic(Duration(milliseconds: 200), (_) async { + final syncHeight = getSyncingHeight(); + final needToRefresh = isNeededToRefresh(); + final newTransactionExist = isNewTransactionExist(); + + if (_lastKnownBlockHeight != syncHeight && syncHeight != null) { + _lastKnownBlockHeight = syncHeight; + await onNewBlock(syncHeight); + } + + if (newTransactionExist && onNewTransaction != null) { + await onNewTransaction(); + } + + if (needToRefresh && onNeedToRefresh != null) { + await onNeedToRefresh(); + } + }); + setListenerNative(); +} + +closeListeners() { + if (_updateSyncInfoTimer != null) { + _updateSyncInfoTimer.cancel(); + } +} + +onStartup() => onStartupNative(); + +_storeSync(_) => storeSync(); +bool _setupNodeSync(Map args) => setupNodeSync( + address: args['address'], + login: args['login'] ?? '', + password: args['password'] ?? '', + useSSL: args['useSSL'], + isLightWallet: args['isLightWallet']); +bool _isConnected(_) => isConnectedSync(); +int _getNodeHeight(_) => getNodeHeightSync(); + +startRefresh() => startRefreshSync(); + +Future setupNode( + {String address, + String login, + String password, + bool useSSL = false, + bool isLightWallet = false}) => + compute(_setupNodeSync, { + 'address': address, + 'login': login, + 'password': password, + 'useSSL': useSSL, + 'isLightWallet': isLightWallet + }); + +Future store() => compute(_storeSync, 0); + +Future isConnected() => compute(_isConnected, 0); + +Future getNodeHeight() => compute(_getNodeHeight, 0); + +rescanBlockchainAsync() => rescanBlockchainAsyncNative(); \ No newline at end of file diff --git a/cw_monero/lib/wallet_manager.dart b/cw_monero/lib/wallet_manager.dart new file mode 100644 index 000000000..4c6e2ef54 --- /dev/null +++ b/cw_monero/lib/wallet_manager.dart @@ -0,0 +1,218 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cw_monero/convert_utf8_to_string.dart'; +import 'package:cw_monero/signatures.dart'; +import 'package:cw_monero/types.dart'; +import 'package:cw_monero/monero_api.dart'; +import 'package:cw_monero/exceptions/wallet_creation_exception.dart'; +import 'package:cw_monero/exceptions/wallet_restore_from_keys_exception.dart'; +import 'package:cw_monero/exceptions/wallet_restore_from_seed_exception.dart'; + +final createWalletNative = moneroApi + .lookup>('create_wallet') + .asFunction(); + +final restoreWalletFromSeedNative = moneroApi + .lookup>( + 'restore_wallet_from_seed') + .asFunction(); + +final restoreWalletFromKeysNative = moneroApi + .lookup>( + 'restore_wallet_from_keys') + .asFunction(); + +final isWalletExistNative = moneroApi + .lookup>('is_wallet_exist') + .asFunction(); + +final loadWalletNative = moneroApi + .lookup>('load_wallet') + .asFunction(); + +createWalletSync( + {String path, + String password, + String language = 'English', + int nettype = 0}) { + final pathPointer = Utf8.toUtf8(path); + final passwordPointer = Utf8.toUtf8(password); + final languagePointer = Utf8.toUtf8(language); + final errorMessagePointer = allocate(); + final isWalletCreated = createWalletNative(pathPointer, passwordPointer, + languagePointer, nettype, errorMessagePointer) != + 0; + + free(pathPointer); + free(passwordPointer); + free(languagePointer); + + if (!isWalletCreated) { + throw WalletCreationException( + message: convertUTF8ToString(pointer: errorMessagePointer)); + } +} + +bool isWalletExistSync({String path}) { + final pathPointer = Utf8.toUtf8(path); + final isExist = isWalletExistNative(pathPointer) != 0; + + free(pathPointer); + + return isExist; +} + +restoreWalletFromSeedSync( + {String path, + String password, + String seed, + int nettype = 0, + int restoreHeight = 0}) { + final pathPointer = Utf8.toUtf8(path); + final passwordPointer = Utf8.toUtf8(password); + final seedPointer = Utf8.toUtf8(seed); + final errorMessagePointer = allocate(); + final isWalletRestored = restoreWalletFromSeedNative( + pathPointer, + passwordPointer, + seedPointer, + nettype, + restoreHeight, + errorMessagePointer) != + 0; + + free(pathPointer); + free(passwordPointer); + free(seedPointer); + + if (!isWalletRestored) { + throw WalletRestoreFromSeedException( + message: convertUTF8ToString(pointer: errorMessagePointer)); + } +} + +restoreWalletFromKeysSync( + {String path, + String password, + String language = 'English', + String address, + String viewKey, + String spendKey, + int nettype = 0, + int restoreHeight = 0}) { + final pathPointer = Utf8.toUtf8(path); + final passwordPointer = Utf8.toUtf8(password); + final languagePointer = Utf8.toUtf8(language); + final addressPointer = Utf8.toUtf8(address); + final viewKeyPointer = Utf8.toUtf8(viewKey); + final spendKeyPointer = Utf8.toUtf8(spendKey); + final errorMessagePointer = allocate(); + final isWalletRestored = restoreWalletFromKeysNative( + pathPointer, + passwordPointer, + languagePointer, + addressPointer, + viewKeyPointer, + spendKeyPointer, + nettype, + restoreHeight, + errorMessagePointer) != + 0; + + free(pathPointer); + free(passwordPointer); + free(languagePointer); + free(addressPointer); + free(viewKeyPointer); + free(spendKeyPointer); + + if (!isWalletRestored) { + throw WalletRestoreFromKeysException( + message: convertUTF8ToString(pointer: errorMessagePointer)); + } +} + +loadWallet({String path, String password, int nettype = 0}) { + final pathPointer = Utf8.toUtf8(path); + final passwordPointer = Utf8.toUtf8(password); + + loadWalletNative(pathPointer, passwordPointer, nettype); + free(pathPointer); + free(passwordPointer); +} + +_createWallet(args) => + createWalletSync(path: args['path'], password: args['password']); + +_restoreFromSeed(args) => restoreWalletFromSeedSync( + path: args['path'], + password: args['password'], + seed: args['seed'], + restoreHeight: args['restoreHeight']); + +_restoreFromKeys(args) => restoreWalletFromKeysSync( + path: args['path'], + password: args['password'], + restoreHeight: args['restoreHeight'], + address: args['address'], + viewKey: args['viewKey'], + spendKey: args['spendKey']); + +_openWallet(Map args) async => + loadWallet(path: args['path'], password: args['password']); + +bool _isWalletExist(String path) => isWalletExistSync(path: path); + +openWallet({String path, String password, int nettype = 0}) async => + loadWallet(path: path, password: password); + +Future openWalletAsync(Map args) async => compute(_openWallet, args); + +Future createWallet( + {String path, + String password, + String language = 'English', + int nettype = 0}) async => + compute(_createWallet, { + 'path': path, + 'password': password, + 'language': language, + 'nettype': nettype + }); + +Future restoreFromSeed( + {String path, + String password, + String seed, + int nettype = 0, + int restoreHeight = 0}) async => + compute(_restoreFromSeed, { + 'path': path, + 'password': password, + 'seed': seed, + 'nettype': nettype, + 'restoreHeight': restoreHeight + }); + +Future restoreFromKeys( + {String path, + String password, + String language = 'English', + String address, + String viewKey, + String spendKey, + int nettype = 0, + int restoreHeight = 0}) async => + compute(_restoreFromKeys, { + 'path': path, + 'password': password, + 'language': language, + 'address': address, + 'viewKey': viewKey, + 'spendKey': spendKey, + 'nettype': nettype, + 'restoreHeight': restoreHeight + }); + +Future isWalletExist({String path}) => compute(_isWalletExist, path); \ No newline at end of file diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock new file mode 100644 index 000000000..f462e288d --- /dev/null +++ b/cw_monero/pubspec.lock @@ -0,0 +1,210 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + ffi: + dependency: "direct main" + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.6" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.8" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0+1" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.11" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" +sdks: + dart: ">=2.6.0 <3.0.0" + flutter: ">=0.1.4 <2.0.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml new file mode 100644 index 000000000..79229759c --- /dev/null +++ b/cw_monero/pubspec.yaml @@ -0,0 +1,62 @@ +name: cw_monero +description: A new flutter plugin project. +version: 0.0.1 +author: +homepage: + +environment: + sdk: ">=2.6.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + ffi: ^0.1.3 + path_provider: ^1.4.0 + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The androidPackage and pluginClass identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + androidPackage: com.cakewallet.monero + pluginClass: CwMoneroPlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 000000000..e96ef602b --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..6b4c0f78a --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..e8efba114 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..399e9340e --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 000000000..78d39756e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,74 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) { |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + pods_ary.push({:name => podname, :path => podpath}); + else + puts "Invalid plugin specification: #{line}" + end + } + return pods_ary +end + +target 'Runner' do + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + use_frameworks! + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + + # Flutter Pods + generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') + if generated_xcode_build_settings.empty? + puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." + end + generated_xcode_build_settings.map { |p| + if p[:name] == 'FLUTTER_FRAMEWORK_DIR' + symlink = File.join('.symlinks', 'flutter') + File.symlink(File.dirname(p[:path]), symlink) + pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + end + } + + # Plugin Pods + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.map { |p| + symlink = File.join('.symlinks', 'plugins', p[:name]) + File.symlink(p[:path], symlink) + pod p[:name], :path => File.join(symlink, 'ios') + } +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['SWIFT_VERSION'] = '4.0' # required by simple_permission + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 000000000..e7f54fa8f --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,89 @@ +PODS: + - barcode_scan (0.0.1): + - Flutter + - MTBBarcodeScanner + - cw_monero (0.0.2): + - cw_monero/Boost (= 0.0.2) + - cw_monero/Monero (= 0.0.2) + - cw_monero/OpenSSL (= 0.0.2) + - cw_monero/Sodium (= 0.0.2) + - Flutter + - cw_monero/Boost (0.0.2): + - Flutter + - cw_monero/Monero (0.0.2): + - Flutter + - cw_monero/OpenSSL (0.0.2): + - Flutter + - cw_monero/Sodium (0.0.2): + - Flutter + - esys_flutter_share (0.0.1): + - Flutter + - Flutter (1.0.0) + - flutter_secure_storage (3.3.1): + - Flutter + - MTBBarcodeScanner (5.0.11) + - path_provider (0.0.1): + - Flutter + - share (0.5.2): + - Flutter + - shared_preferences (0.0.1): + - Flutter + - url_launcher (0.0.1): + - Flutter + - url_launcher_web (0.0.1): + - Flutter + +DEPENDENCIES: + - barcode_scan (from `.symlinks/plugins/barcode_scan/ios`) + - cw_monero (from `.symlinks/plugins/cw_monero/ios`) + - esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`) + - Flutter (from `.symlinks/flutter/ios-release`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - path_provider (from `.symlinks/plugins/path_provider/ios`) + - share (from `.symlinks/plugins/share/ios`) + - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) + +SPEC REPOS: + trunk: + - MTBBarcodeScanner + +EXTERNAL SOURCES: + barcode_scan: + :path: ".symlinks/plugins/barcode_scan/ios" + cw_monero: + :path: ".symlinks/plugins/cw_monero/ios" + esys_flutter_share: + :path: ".symlinks/plugins/esys_flutter_share/ios" + Flutter: + :path: ".symlinks/flutter/ios-release" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" + path_provider: + :path: ".symlinks/plugins/path_provider/ios" + share: + :path: ".symlinks/plugins/share/ios" + shared_preferences: + :path: ".symlinks/plugins/shared_preferences/ios" + url_launcher: + :path: ".symlinks/plugins/url_launcher/ios" + url_launcher_web: + :path: ".symlinks/plugins/url_launcher_web/ios" + +SPEC CHECKSUMS: + barcode_scan: 33f586d02270046fc6559135038b34b5754eaa4f + cw_monero: ca40a57b99f7753ed93d3b50af671a637277eb89 + esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4 + Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec + MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb + path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d + share: bae0a282aab4483288913fc4dc0b935d4b491f2e + shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01 + url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 + url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c + +PODFILE CHECKSUM: f1916a43bb28badbd408be80e8e4b8652a74e93e + +COCOAPODS: 1.8.4 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..ef5c8ce46 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,602 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 02AA3DBD66A19A0A1732294D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B973AD352B2957AC387EA9 /* Pods_Runner.framework */; }; + 0C0DB1F8237DB1AE00BD32F9 /* A.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0DB1F7237DB1AE00BD32F9 /* A.swift */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 00B973AD352B2957AC387EA9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0C0DB1F6237DB1AD00BD32F9 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 0C0DB1F7237DB1AE00BD32F9 /* A.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = A.swift; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 4B157CEA62824A43D7DD4C38 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A64AD09573DE1F9B50A18F2A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + B39AAE9F098686B29374F51D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 02AA3DBD66A19A0A1732294D /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + BB72635863665F0C75A8EBE5 /* Pods */, + D7C5668014A527FFA32576AF /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 0C0DB1F7237DB1AE00BD32F9 /* A.swift */, + 0C0DB1F6237DB1AD00BD32F9 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + BB72635863665F0C75A8EBE5 /* Pods */ = { + isa = PBXGroup; + children = ( + 4B157CEA62824A43D7DD4C38 /* Pods-Runner.debug.xcconfig */, + A64AD09573DE1F9B50A18F2A /* Pods-Runner.release.xcconfig */, + B39AAE9F098686B29374F51D /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D7C5668014A527FFA32576AF /* Frameworks */ = { + isa = PBXGroup; + children = ( + 00B973AD352B2957AC387EA9 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 95C66705E4338EEA09DF7047 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + A51ED02C4D7DF0F1E59D04DE /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = 32J6BB6VUS; + LastSwiftMigration = 1120; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 95C66705E4338EEA09DF7047 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + A51ED02C4D7DF0F1E59D04DE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 0C0DB1F8237DB1AE00BD32F9 /* A.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 32J6BB6VUS; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VALID_ARCHS = "arm64 arm64e"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 32J6BB6VUS; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VALID_ARCHS = "arm64 arm64e"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 32J6BB6VUS; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VALID_ARCHS = "arm64 arm64e"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..fb2dffc49 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner/A.swift b/ios/Runner/A.swift new file mode 100644 index 000000000..fecc4ab44 --- /dev/null +++ b/ios/Runner/A.swift @@ -0,0 +1 @@ +import Foundation diff --git a/ios/Runner/AppDelegate.h b/ios/Runner/AppDelegate.h new file mode 100644 index 000000000..36e21bbf9 --- /dev/null +++ b/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/ios/Runner/AppDelegate.m b/ios/Runner/AppDelegate.m new file mode 100644 index 000000000..595bf5960 --- /dev/null +++ b/ios/Runner/AppDelegate.m @@ -0,0 +1,25 @@ +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +- (void)applicationDidEnterBackground:(UIApplication *)application +{ + UIViewController *blankViewController = [UIViewController new]; + blankViewController.view.backgroundColor = [UIColor blackColor]; + [self.window.rootViewController presentViewController:blankViewController animated:NO completion:NULL]; +} + +- (void)applicationWillEnterForeground:(UIApplication *)application +{ + [self.window.rootViewController dismissViewControllerAnimated:NO completion:NO]; +} + +@end diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..1357abdc1 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,40 @@ +import UIKit +import Flutter +import CWMonero + +class MoneroWalletManagerHandler { +// static let moneroWalletManager = MoneroWalletGateway() +} + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + let controller : FlutterViewController = window?.rootViewController as! FlutterViewController + let moneroWalletManagerChannel = FlutterMethodChannel(name: "com.cakewallet.wallet/monero-wallet-manager", + binaryMessenger: controller.binaryMessenger) + moneroWalletManagerChannel.setMethodCallHandler({ + (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + + }) + + moneroWalletManagerChannel.setMethodCallHandler({ + [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in + switch call.method { + case "createWallet": + result(1) + case "isWalletExist": + result(false) + default: + result(FlutterMethodNotImplemented) + } + + result(1); + }) + + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..21cc86669 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..73c1fec11 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..dd8ffc81b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..32c62fba2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..c5fc4b8ad Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..7cf1fe989 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..3e0f16134 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..dd8ffc81b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..bbbeef026 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..c415d4e12 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 000000000..33bcc2a98 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 000000000..a13155108 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 000000000..a8365f725 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 000000000..191cc8150 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..c415d4e12 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..c95fec9cc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 000000000..5d9200d0a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 000000000..c4352f579 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..3f5ffb78f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..d987f1bc7 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..dec6b12ca Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 000000000..31d748e9a --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + cake_wallet + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..7335fdf90 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/ios/Runner/main.m b/ios/Runner/main.m new file mode 100644 index 000000000..dff6597e4 --- /dev/null +++ b/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/lib/generated/i18n.dart b/lib/generated/i18n.dart new file mode 100644 index 000000000..69bcf717d --- /dev/null +++ b/lib/generated/i18n.dart @@ -0,0 +1,5808 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +// ignore_for_file: non_constant_identifier_names +// ignore_for_file: camel_case_types +// ignore_for_file: prefer_single_quotes + +// This file is automatically generated. DO NOT EDIT, all your changes would be lost. +class S implements WidgetsLocalizations { + const S(); + + static S current; + + static const GeneratedLocalizationsDelegate delegate = + GeneratedLocalizationsDelegate(); + + static S of(BuildContext context) => Localizations.of(context, S); + + @override + TextDirection get textDirection => TextDirection.ltr; + + String get account => "Account"; + String get accounts => "Accounts"; + String get add => "Add"; + String get address_book => "Address Book"; + String get address_book_menu => "Address book"; + String get all => "ALL"; + String get amount => "Amount: "; + String get amount_is_estimate => "The receive amount is an estimate"; + String get amount_is_guaranteed => "The receive amount is guaranteed"; + String get auth_store_ban_timeout => "ban_timeout"; + String get auth_store_banned_for => "Banned for "; + String get auth_store_banned_minutes => " minutes"; + String get auth_store_incorrect_password => "Wrong PIN"; + String get authenticated => "Authenticated"; + String get authentication => "Authentication"; + String get available_balance => "Available Balance"; + String get cancel => "Cancel"; + String get change => "Change"; + String get change_currency => "Change Currency"; + String get change_exchange_provider => "Change Exchange Provider"; + String get change_language => "Change language"; + String get clear => "Clear"; + String get confirm => "Confirm"; + String get send_xmr => "Send XMR"; + String get confirm_sending => "Confirm sending"; + String get contact => "Contact"; + String get contact_name => "Contact Name"; + String get continue_text => "Continue"; + String get copied_to_clipboard => "Copied to Clipboard"; + String get copy => "Copy"; + String get copy_address => "Copy Address"; + String get copy_id => "Copy ID"; + String get create_new => "Create new"; + String get delete => "Delete"; + String get digit_pin => "-digit Pin"; + String get edit => "Edit"; + String get enter_your_pin => "Enter your PIN"; + String get enter_your_pin_again => "Enter your pin again"; + String get error => "Error"; + String get error_text_account_name => "Account name can only contain letters, numbers\nand must be between 1 and 15 characters long"; + String get error_text_address => "Wallet address must correspond to the type\nof cryptocurrency"; + String get error_text_amount => "Amount can only contain numbers"; + String get error_text_contact_name => "Contact name can't contain ` , ' \" symbols\nand must be between 1 and 32 characters long"; + String get error_text_crypto_currency => "The number of fraction digits\nmust be less or equal to 12"; + String get error_text_fiat => "Value of amount can't exceed available balance.\nThe number of fraction digits must be less or equal to 2"; + String get error_text_keys => "Wallet keys can only contain 64 chars in hex"; + String get error_text_node_address => "Please enter a iPv4 address"; + String get error_text_node_port => "Node port can only contain numbers between 0 and 65535"; + String get error_text_payment_id => "Payment ID can only contain from 16 to 64 chars in hex"; + String get error_text_subaddress_name => "Subaddress name can't contain ` , ' \" symbols\nand must be between 1 and 20 characters long"; + String get error_text_wallet_name => "Wallet name can only contain letters, numbers\nand must be between 1 and 15 characters long"; + String get error_text_xmr => "XMR value can't exceed available balance.\nThe number of fraction digits must be less or equal to 12"; + String get estimated => "Estimated"; + String get exchange => "Exchange"; + String get exchange_result_write_down_ID => "*Please copy or write down your ID shown above."; + String get exchange_result_write_down_trade_id => "Please copy or write down the trade ID to continue."; + String get expired => "Expired"; + String get faq => "FAQ"; + String get fetching => "Fetching"; + String get filters => "Filters"; + String get first_wallet_text => "Awesome wallet\nfor Monero"; + String get full_balance => "Full Balance"; + String get hidden_balance => "Hidden Balance"; + String get id => "ID: "; + String get incoming => "Incoming"; + String get loading_your_wallet => "Loading your wallet"; + String get login => "Login"; + String get new_subaddress_create => "Create"; + String get new_subaddress_label_name => "Label name"; + String get new_subaddress_title => "New subaddress"; + String get new_wallet => "New Wallet"; + String get node_address => "Node Address"; + String get node_new => "New Node"; + String get node_port => "Node port"; + String get node_reset_settings_title => "Reset settings"; + String get nodes => "Nodes"; + String get nodes_list_reset_to_default_message => "Are you sure that you want to reset settings to default?"; + String get offer_expires_in => "Offer expires in: "; + String get ok => "OK"; + String get outgoing => "Outgoing"; + String get password => "Password"; + String get payment_id => "Payment ID: "; + String get pending => " (pending)"; + String get pin_is_incorrect => "PIN is incorrect"; + String get please_make_selection => "Please make selection below to\ncreate or recover your wallet."; + String get please_select => "Please select:"; + String get please_try_to_connect_to_another_node => "Please try to connect to another node"; + String get receive => "Receive"; + String get receive_amount => "Amount"; + String get received => "Received"; + String get reconnect => "Reconnect"; + String get reconnect_alert_text => "Are you sure to reconnect?"; + String get reconnection => "Reconnection"; + String get remove => "Remove"; + String get remove_node => "Remove node"; + String get remove_node_message => "Are you sure that you want to remove selected node?"; + String get rescan => "Rescan"; + String get reset => "Reset"; + String get restore_address => "Address"; + String get restore_description_from_backup => "You can restore the whole Cake Wallet app from\nyour back-up file"; + String get restore_description_from_keys => "Restore your wallet from generated\nkeystrokes saved from your private keys"; + String get restore_description_from_seed => "Restore your wallet from either the 25 word\nor 13 word combination code"; + String get restore_description_from_seed_keys => "Get back your wallet from seed/keys that you've saved to secure place"; + String get restore_next => "Next"; + String get restore_recover => "Restore"; + String get restore_restore_wallet => "Restore Wallet"; + String get restore_seed_keys_restore => "Seed/Keys Restore"; + String get restore_spend_key_private => "Spend key (private)"; + String get restore_title_from_backup => "Restore from a back-up file"; + String get restore_title_from_keys => "Restore from keys"; + String get restore_title_from_seed => "Restore from seed"; + String get restore_title_from_seed_keys => "Restore from seed/keys"; + String get restore_view_key_private => "View key (private)"; + String get restore_wallet => "Restore wallet"; + String get restore_wallet_name => "Wallet name"; + String get restore_wallet_restore_description => "Wallet restore description"; + String get save => "Save"; + String get saved_the_trade_id => "I've saved the trade ID"; + String get seed_alert_first_text => "The next page will show\nyou a seed."; + String get seed_alert_menu => " menu."; + String get seed_alert_second_text => "Please write these down just in\ncase you lose or wipe your phone."; + String get seed_alert_settings => "settings"; + String get seed_alert_third_text => "You can also see the seed again\nin the "; + String get seed_alert_understand => "I understand"; + String get seed_share => "Share seed"; + String get seed_title => "Seed"; + String get send => "Send"; + String get send_creating_transaction => "Creating transaction"; + String get send_error_currency => "Currency can only contain numbers"; + String get send_error_minimum_value => "Minimum value of amount is 0.01"; + String get send_estimated_fee => "Estimated fee:"; + String get send_monero_address => "Monero address"; + String get send_payment_id => "Payment ID (optional)"; + String get send_title => "Send Monero"; + String get send_your_wallet => "Your wallet"; + String get sending => "Sending"; + String get sent => "Sent"; + String get settings_all => "ALL"; + String get settings_allow_biometrical_authentication => "Allow biometrical authentication"; + String get settings_change_language => "Change language"; + String get settings_change_pin => "Change PIN"; + String get settings_currency => "Currency"; + String get settings_current_node => "Current node"; + String get settings_dark_mode => "Dark mode"; + String get settings_display_balance_as => "Display balance as"; + String get settings_display_on_dashboard_list => "Display on dashboard list"; + String get settings_fee_priority => "Fee priority"; + String get settings_nodes => "Nodes"; + String get settings_none => "None"; + String get settings_only_trades => "Only trades"; + String get settings_only_transactions => "Only transactions"; + String get settings_personal => "Personal"; + String get settings_save_recipient_address => "Save recipient address"; + String get settings_support => "Support"; + String get settings_terms_and_conditions => "Terms and conditions"; + String get settings_title => "Settings"; + String get settings_trades => "Trades"; + String get settings_transactions => "Transactions"; + String get settings_wallets => "Wallets"; + String get setup_pin => "Setup PIN"; + String get setup_successful => "Your PIN has been set up successfully!"; + String get share_address => "Share address"; + String get show_keys => "Show keys"; + String get show_seed => "Show seed"; + String get spend_key_private => "Spend key (private)"; + String get spend_key_public => "Spend key (public)"; + String get status => "Status: "; + String get subaddress_title => "Subaddress list"; + String get subaddresses => "Subaddresses"; + String get sync_status_connected => "CONNECTED"; + String get sync_status_connecting => "CONNECTING"; + String get sync_status_failed_connect => "FAILED CONNECT TO THE NODE"; + String get sync_status_not_connected => "NOT CONNECTED"; + String get sync_status_starting_sync => "STARTING SYNC"; + String get sync_status_syncronized => "SYNCHRONIZED"; + String get sync_status_syncronizing => "SYNCRONIZING"; + String get today => "Today"; + String get trade_details_created_at => "Created at"; + String get trade_details_fetching => "Fetching"; + String get trade_details_id => "ID"; + String get trade_details_pair => "Pair"; + String get trade_details_provider => "Provider"; + String get trade_details_state => "State"; + String get trade_details_title => "Trade Details"; + String get trade_history_title => "Trade history"; + String get trade_not_created => "Trade not created."; + String get trade_not_found => "Trade not found."; + String get trade_state_btc_sent => "Btc sent"; + String get trade_state_complete => "Complete"; + String get trade_state_confirming => "Confirming"; + String get trade_state_created => "Created"; + String get trade_state_finished => "Finished"; + String get trade_state_paid => "Paid"; + String get trade_state_paid_unconfirmed => "Paid unconfirmed"; + String get trade_state_pending => "Pending"; + String get trade_state_timeout => "Timeout"; + String get trade_state_to_be_created => "To be created"; + String get trade_state_traded => "Traded"; + String get trade_state_trading => "Trading"; + String get trade_state_underpaid => "Underpaid"; + String get trade_state_unpaid => "Unpaid"; + String get trades => "Trades"; + String get transaction_details_amount => "Amount"; + String get transaction_details_date => "Date"; + String get transaction_details_height => "Height"; + String get transaction_details_recipient_address => "Recipient address"; + String get transaction_details_title => "Transaction Details"; + String get transaction_details_transaction_id => "Transaction ID"; + String get transaction_priority_fast => "Fast"; + String get transaction_priority_fastest => "Fastest"; + String get transaction_priority_medium => "Medium"; + String get transaction_priority_regular => "Regular"; + String get transaction_priority_slow => "Slow"; + String get transaction_sent => "Transaction sent!"; + String get transactions => "Transactions"; + String get transactions_by_date => "Transactions by date"; + String get use => "Use "; + String get view_key_private => "View key (private)"; + String get view_key_public => "View key (public)"; + String get wallet_keys => "Wallet keys"; + String get wallet_list_create_new_wallet => "Create New Wallet"; + String get wallet_list_load_wallet => "Load wallet"; + String get wallet_list_restore_wallet => "Restore Wallet"; + String get wallet_list_title => "Monero Wallet"; + String get wallet_menu => "Menu"; + String get wallet_name => "Wallet name"; + String get wallet_restoration_store_incorrect_seed_length => "Incorrect seed length"; + String get wallet_store_monero_wallet => "Monero Wallet"; + String get wallets => "Wallets"; + String get welcome => "WELCOME\nTO CAKE WALLET"; + String get widgets_address => "Address"; + String get widgets_or => "or"; + String get widgets_restore_from_blockheight => "Restore from blockheight"; + String get widgets_restore_from_date => "Restore from date"; + String get widgets_seed => "Seed"; + String get xmr_available_balance => "XMR Available Balance"; + String get xmr_full_balance => "XMR Full Balance"; + String get xmr_hidden => "XMR Hidden"; + String get yesterday => "Yesterday"; + String get you_will_get => "You will get"; + String get you_will_send => "You will send"; + String Blocks_remaining(String status) => "${status} Blocks Remaining"; + String change_current_node(String node) => "Are you sure to change current node to ${node}?"; + String change_language_to(String language) => "Change language to ${language}?"; + String commit_transaction_amount_fee(String amount, String fee) => "Commit transaction\nAmount: ${amount}\nFee: ${fee}"; + String copied_key_to_clipboard(String key) => "Copied ${key} to Clipboard"; + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "By pressing confirm, you will be sending ${fetchingLabel} ${from} from your wallet called ${walletName} to the address shown above. Or you can send from your external wallet to the above address/QR code.\n\nPlease press confirm to continue or go back to change the amounts.\n\n"; + String exchange_result_description(String fetchingLabel, String from) => "Please send ${fetchingLabel} ${from} to the address shown above.\n\n"; + String failed_authentication(String state_error) => "Failed authentication. ${state_error}"; + String max_value(String value, String currency) => "Max: ${value} ${currency}"; + String min_value(String value, String currency) => "Min: ${value} ${currency}"; + String powered_by(String title) => "Powered by ${title}"; + String router_no_route(String name) => "No route defined for ${name}"; + String send_priority(String transactionPriority) => "Currently the fee is set at ${transactionPriority} priority.\nTransaction priority can be adjusted in the settings"; + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + String trade_details_copied(String title) => "${title} copied to Clipboard"; + String trade_for_not_created(String title) => "Trade for ${title} is not created."; + String trade_id(String id) => "Trade ID:\n${id}"; + String trade_id_not_found(String tradeId, String title) => "Trade ${tradeId} of ${title} not found."; + String trade_is_powered_by(String provider) => "This trade is powered by ${provider}"; + String transaction_details_copied(String title) => "${title} copied to Clipboard"; + String wallet_list_failed_to_load(String wallet_name, String error) => "Failed to load ${wallet_name} wallet. ${error}"; + String wallet_list_failed_to_remove(String wallet_name, String error) => "Failed to remove ${wallet_name} wallet. ${error}"; + String wallet_list_loading_wallet(String wallet_name) => "Loading ${wallet_name} wallet"; + String wallet_list_removing_wallet(String wallet_name) => "Removing ${wallet_name} wallet"; +} + +class $de extends S { + const $de(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "Abbrechen"; + @override + String get trade_state_unpaid => "Unbezahlt"; + @override + String get authenticated => "Authentifiziert"; + @override + String get new_subaddress_create => "Erstellen"; + @override + String get restore_view_key_private => "Schlüssel anzeigen(geheim)"; + @override + String get transaction_priority_fast => "Schnell"; + @override + String get error_text_amount => "Betrag kann nur Zahlen enthalten"; + @override + String get hidden_balance => "Verstecktes Gleichgewicht"; + @override + String get transaction_sent => "Transaktion gesendet!"; + @override + String get password => "Passwort"; + @override + String get you_will_get => "Sie erhalten"; + @override + String get restore_description_from_seed => "Stellen Sie Ihr Wallet aus den 25 Wörtern wieder her\noder 13-Wort-Kombinationscode"; + @override + String get trade_state_underpaid => "Unterbezahlt"; + @override + String get welcome => "Willkommen\nzu CAKE WALLET"; + @override + String get share_address => "Adresse teilen "; + @override + String get please_try_to_connect_to_another_node => "Bitte versuchen Sie, eine Verbindung zu einem anderen Knoten herzustellen"; + @override + String get trade_state_traded => "Handeln"; + @override + String get edit => "Bearbeiten"; + @override + String get spend_key_public => "Schlüssel ausgeben (Öffentlichkeit)"; + @override + String get sync_status_syncronizing => "SYNCHRONISIERUNG"; + @override + String get received => "Empfangen"; + @override + String get auth_store_banned_minutes => " Protokoll"; + @override + String get restore_title_from_seed_keys => "Vom Seed / Schlüssel wiederherstellen"; + @override + String get settings_none => "Keiner"; + @override + String get wallet_list_create_new_wallet => "Neue Wallet erstellen"; + @override + String get seed_alert_third_text => "Sie können den Seed auch wieder sehen\nin dem "; + @override + String get setup_pin => "PIN einrichten"; + @override + String get transaction_details_recipient_address => "Empfängeradresse"; + @override + String get receive_amount => "Menge"; + @override + String get settings_allow_biometrical_authentication => "Biometrische Authentifizierung zulassen"; + @override + String get view_key_private => "Schlüssel anzeigen (eheim)"; + @override + String get restore_next => "Nächster"; + @override + String get trade_state_trading => "Handel"; + @override + String get error_text_address => "Die Walletadresse muss dem Typ entsprechen\nder Kryptowährung"; + @override + String get send_your_wallet => "Deine Geldbörse"; + @override + String get remove_node_message => "Möchten Sie den ausgewählten Knoten wirklich entfernen?"; + @override + String get error_text_account_name => "Der Kontoname darf nur Wallet und Zahlen enthalten\nund muss zwischen 1 und 15 Zeichen lang sein"; + @override + String get reconnection => "Wiederverbindung"; + @override + String get available_balance => "Verfügbares Guthaben"; + @override + String get restore_title_from_keys => "Wiederherstellen von Schlüsseln"; + @override + String get wallet_keys => "Wallet schlüssel"; + @override + String get reconnect => "Erneut verbinden"; + @override + String get reconnect_alert_text => "Sind Sie sicher, dass Sie die Verbindung wiederherstellen möchten?"; + @override + String get send_error_minimum_value => "Der Mindestbetrag beträgt 0,01"; + @override + String get copy_address => "Adresse kopieren"; + @override + String get error_text_subaddress_name => "Der Name der Unteradresse darf nicht enthalten sein ` , ' \" symbole\nund muss zwischen 1 und 20 Zeichen lang sein"; + @override + String get settings_all => "ALLE"; + @override + String get nodes_list_reset_to_default_message => "Möchten Sie die Einstellungen wirklich auf die Standardeinstellungen zurücksetzen?"; + @override + String get receive => "Erhalten"; + @override + String get amount_is_guaranteed => "Der Empfangsbetrag ist garantiert"; + @override + String get auth_store_banned_for => "Gebannt für "; + @override + String get trade_details_pair => "Paar"; + @override + String get confirm => "Bestätigen"; + String get send_xmr => "Senden XMR"; + @override + String get settings_display_balance_as => "Kontostand anzeigen als"; + @override + String get trade_details_provider => "Anbieter"; + @override + String get you_will_send => "Du wirst senden"; + @override + String get create_new => "Erstelle neu"; + @override + String get trade_details_fetching => "Holen"; + @override + String get seed_alert_menu => " Speisekarte."; + @override + String get confirm_sending => "Bestätigen Sie das Senden"; + @override + String get settings_title => "die Einstellungen"; + @override + String get address_book_menu => "Adressbuch"; + @override + String get wallet_restoration_store_incorrect_seed_length => "Falsche Samenlänge"; + @override + String get contact => "Kontakt"; + @override + String get auth_store_incorrect_password => "Falsches PIN"; + @override + String get transaction_priority_slow => "Schleppend"; + @override + String get add => "Hinzufügen"; + @override + String get remove_node => "Knoten entfernen"; + @override + String get trade_state_paid_unconfirmed => "Unbestätigt bezahlt"; + @override + String get please_select => "Bitte auswählen:"; + @override + String get restore_title_from_backup => "Aus einer Sicherungsdatei wiederherstellen"; + @override + String get full_balance => "Volle Balance"; + @override + String get sync_status_not_connected => "NICHT VERBUNDEN"; + @override + String get error_text_crypto_currency => "Die Anzahl der Nachkommastellen\nmuss kleiner oder gleich 12 sein."; + @override + String get error_text_contact_name => "Kontaktname darf nicht enthalten sein ` , ' \" Symbole\nund muss zwischen 1 und 32 Zeichen lang sein"; + @override + String get restore_seed_keys_restore => "Seed / Schlüssel wiederherstellen"; + @override + String get xmr_hidden => "XMR versteckt"; + @override + String get exchange => "Austausch"; + @override + String get sync_status_failed_connect => "Verbindung zum Knoten fehlgeschlagen"; + @override + String get send_estimated_fee => "Geschätzte Gebühr:"; + @override + String get outgoing => "Ausgehend"; + @override + String get sync_status_connected => "IN VERBINDUNG GEBRACHT"; + @override + String get trade_state_pending => "Steht aus"; + @override + String get pending => " (steht aus)"; + @override + String get setup_successful => "Ihre PIN wurde erfolgreich eingerichtet!"; + @override + String get fetching => "holen"; + @override + String get settings_nodes => "Knoten"; + @override + String get widgets_or => "oder"; + @override + String get remove => "Löschen"; + @override + String get yesterday => "Gestern"; + @override + String get expired => "Abgelaufen"; + @override + String get transaction_priority_regular => "Regulär"; + @override + String get trade_details_created_at => "Hergestellt in"; + @override + String get settings_wallets => "Brieftaschen"; + @override + String get settings_only_transactions => "Nur Transaktionen"; + @override + String get estimated => "Geschätzt"; + @override + String get filters => "Filter"; + @override + String get settings_current_node => "Aktueller Knoten"; + @override + String get copy_id => "ID kopieren"; + @override + String get please_make_selection => "Bitte treffen Sie unten eine Auswahl zu\nErstellen oder Wiederherstellen Ihrer Brieftasche."; + @override + String get loading_your_wallet => "Laden Sie Ihre Brieftasche"; + @override + String get subaddresses => "Unteradressen"; + @override + String get trade_state_timeout => "Auszeit"; + @override + String get nodes => "Knoten"; + @override + String get seed_share => "Teilen Sie Seed"; + @override + String get widgets_restore_from_blockheight => "Aus Blockhöhe wiederherstellen"; + @override + String get new_subaddress_label_name => "Markenname"; + @override + String get trade_not_found => "Handel nicht gefunden."; + @override + String get transaction_details_date => "Datum"; + @override + String get show_seed => "Seed zeigen"; + @override + String get send_error_currency => "Die Währung kann nur Zahlen enthalten"; + @override + String get subaddress_title => "Unteradressenliste"; + @override + String get wallet_store_monero_wallet => "Monero Wallet"; + @override + String get send_creating_transaction => "Transaktion erstellen"; + @override + String get trade_state_complete => "Komplett"; + @override + String get error_text_xmr => "Der XMR-Wert kann das verfügbare Guthaben nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 12 sein"; + @override + String get xmr_available_balance => "XMR verfügbares Guthaben"; + @override + String get trade_state_paid => "Bezahlt"; + @override + String get node_new => "Neuer Knoten"; + @override + String get trade_state_created => "Erstellt"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "Ich habe die Geschäfts-ID gespeichert"; + @override + String get settings_currency => "Währung"; + @override + String get change_language => "Sprache ändern"; + @override + String get wallet_name => "Walletname"; + @override + String get error_text_payment_id => "Die Zahlungs-ID kann nur 16 bis 64 hexadezimale Zeichen enthalten"; + @override + String get seed_alert_second_text => "Bitte schreiben Sie diese nur in\nFalls Sie Ihr Telefon verlieren oder abwischen."; + @override + String get node_reset_settings_title => "Einstellungen zurücksetzen"; + @override + String get wallets => "Wallets"; + @override + String get enter_your_pin_again => "Geben Sie Ihre PIN erneut ein"; + @override + String get transactions => "Transaktionen"; + @override + String get amount_is_estimate => "Der empfangene Betrag ist eine Schätzung"; + @override + String get error_text_node_address => "Bitte geben Sie eine iPv4-Adresse ein"; + @override + String get widgets_restore_from_date => "Vom Datum wiederherstellen"; + @override + String get restore_recover => "Genesen"; + @override + String get reset => "Zurücksetzen"; + @override + String get new_wallet => "Neues Wallet"; + @override + String get settings_display_on_dashboard_list => "Anzeige in der Dashboard-Liste"; + @override + String get transaction_details_title => "Transaktionsdetails"; + @override + String get status => "Status: "; + @override + String get restore_spend_key_private => "Schlüssel ausgeben (geheim)"; + @override + String get sync_status_syncronized => "SYNCHRONISIERT"; + @override + String get transaction_priority_medium => "Mittel"; + @override + String get transaction_details_transaction_id => "Transaktions-ID"; + @override + String get save => "speichern"; + @override + String get login => "Einloggen"; + @override + String get wallet_list_load_wallet => "Wallet einlegen"; + @override + String get sending => "Senden"; + @override + String get restore_restore_wallet => "Wallet wiederherstellen"; + @override + String get restore_description_from_seed_keys => "Holen Sie sich Ihr Wallet von Seed / Schlüsseln zurück, die Sie an einem sicheren Ort aufbewahrt haben"; + @override + String get copy => "Kopieren"; + @override + String get node_port => "Knotenport"; + @override + String get change_currency => "Währung ändern"; + @override + String get transaction_details_amount => "Menge"; + @override + String get widgets_address => "Adresse"; + @override + String get contact_name => "Name des Ansprechpartners"; + @override + String get exchange_result_write_down_ID => "*Bitte kopieren oder notieren Sie Ihren oben gezeigten Ausweis."; + @override + String get exchange_result_write_down_trade_id => "Bitte kopieren oder notieren Sie die Handel-ID, um fortzufahren."; + @override + String get new_subaddress_title => "Neue Unteradresse"; + @override + String get change => "Veränderung"; + @override + String get seed_alert_first_text => "Die nächste Seite wird angezeigt\nDu bist ein Seed."; + @override + String get trade_state_finished => "Fertig"; + @override + String get pin_is_incorrect => "PIN ist falsch"; + @override + String get trade_not_created => "Handel nicht angelegt."; + @override + String get restore_wallet_name => "Walletname"; + @override + String get widgets_seed => "Seed"; + @override + String get settings_fee_priority => "Gebührenpriorität"; + @override + String get settings_personal => "persönlich"; + @override + String get seed_title => "Seed"; + @override + String get accounts => "Konten"; + @override + String get rescan => "Erneut scannen"; + @override + String get seed_alert_understand => "ich verstehe"; + @override + String get show_keys => "Schlüssel anzeigen"; + @override + String get error_text_fiat => "Der Wert des Betrags darf den verfügbaren Kontostand nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 2 sein"; + @override + String get transactions_by_date => "Transaktionen nach Datum"; + @override + String get use => "Verwenden "; + @override + String get trade_details_state => "Zustand"; + @override + String get spend_key_private => "Schlüssel ausgeben (geheim)"; + @override + String get settings_only_trades => "Nur Trades"; + @override + String get trade_state_to_be_created => "Geschaffen werden"; + @override + String get all => "ALLE"; + @override + String get xmr_full_balance => "XMR Volle Balance"; + @override + String get incoming => "Eingehend"; + @override + String get trade_history_title => "Handelsgeschichte"; + @override + String get error_text_wallet_name => "Der Wallet darf nur Buchstaben und Zahlen enthalten\nund muss zwischen 1 und 15 Zeichen lang sein"; + @override + String get restore_description_from_keys => "Stellen Sie Ihr Wallet von generiert wieder her\nTastenanschläge, die von Ihren privaten Schlüsseln gespeichert wurden"; + @override + String get sent => "Geschickt"; + @override + String get view_key_public => "Schlüssel anzeigen (Öffentlichkeit)"; + @override + String get trade_details_id => "ID"; + @override + String get trade_state_confirming => "Bestätigung"; + @override + String get send => "Senden"; + @override + String get send_title => "Senden Sie Monero"; + @override + String get error_text_keys => "Walletschlüssel können nur 64 hexadezimale Zeichen enthalten"; + @override + String get settings_save_recipient_address => "Empfängeradresse speichern"; + @override + String get seed_alert_settings => "die Einstellungen"; + @override + String get change_exchange_provider => "Wechseln Sie den Exchange-Anbieter"; + @override + String get send_payment_id => "Zahlungs ID (wahlweise)"; + @override + String get trade_details_title => "Handel Einzelheiten"; + @override + String get settings_terms_and_conditions => "Geschäftsbedingungen"; + @override + String get auth_store_ban_timeout => "Auszeit verbieten"; + @override + String get sync_status_connecting => "ANSCHLUSS"; + @override + String get settings_transactions => "Transaktionen"; + @override + String get transaction_priority_fastest => "Am schnellsten"; + @override + String get error => "Error"; + @override + String get delete => "Löschen"; + @override + String get sync_status_starting_sync => "STARTEN DER SYNCHRONISIERUNG"; + @override + String get copied_to_clipboard => "In die Zwischenablage kopiert"; + @override + String get offer_expires_in => "Angebot läuft ab in: "; + @override + String get continue_text => "Fortsetzen"; + @override + String get transaction_details_height => "Höhe"; + @override + String get wallet_menu => "Brieftaschen-Menü"; + @override + String get settings_dark_mode => "Dunkler Modus"; + @override + String get payment_id => "Zahlungs ID: "; + @override + String get restore_address => "Adresse"; + @override + String get restore_wallet_restore_description => "Beschreibung zur Wiederherstellung der Brieftasche"; + @override + String get today => "Heute"; + @override + String get settings_support => "Unterstützung"; + @override + String get restore_wallet => "Wallet wiederherstellen"; + @override + String get ok => "OK"; + @override + String get wallet_list_title => "Monero Wallet"; + @override + String get authentication => "Authentifizierung"; + @override + String get amount => "Menge: "; + @override + String get node_address => "Knotenadresse"; + @override + String get settings_change_language => "Sprache ändern"; + @override + String get clear => "klar"; + @override + String get settings_change_pin => "PIN ändern"; + @override + String get trades => "Handel"; + @override + String get trade_state_btc_sent => "geschickt"; + @override + String get address_book => "Adressbuch"; + @override + String get enter_your_pin => "PIN eingeben"; + @override + String get wallet_list_restore_wallet => "Wallet wiederherstellen"; + @override + String get restore_title_from_seed => "Aus Seed wiederherstellen"; + @override + String get restore_description_from_backup => "Sie können die gesamte Cake Wallet-App von wiederherstellen \nIhre Sicherungsdatei"; + @override + String get send_monero_address => "Monero-Adresse"; + @override + String get error_text_node_port => "Der Knotenport kann nur Nummern zwischen 0 und 65535 enthalten"; + @override + String get digit_pin => "-stelliger Pin"; + @override + String get first_wallet_text => "tolle Brieftasche\nzum Monero"; + @override + String get settings_trades => "Handel"; + @override + String get account => "Konto"; + @override + String change_language_to(String language) => "Ändern Sie die Sprache in ${language}?"; + @override + String change_current_node(String node) => "Möchten Sie den aktuellen Knoten wirklich auf ändern? ${node}?"; + @override + String trade_id(String id) => "Handel-ID:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "Wird geladen ${wallet_name} Wallet"; + @override + String router_no_route(String name) => "Keine Route definiert für ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "Handel ${tradeId} von ${title} nicht gefunden."; + @override + String transaction_details_copied(String title) => "${title} in die Zwischenablage kopiert"; + @override + String trade_details_copied(String title) => "${title} in die Zwischenablage kopiert"; + @override + String powered_by(String title) => "Unterstützt von ${title}"; + @override + String send_priority(String transactionPriority) => "Derzeit ist die Gebühr auf festgelegt ${transactionPriority} priorität.\nDie Transaktionspriorität kann in den Einstellungen angepasst werden"; + @override + String trade_for_not_created(String title) => "Handel für ${title} wird nicht erstellt."; + @override + String trade_is_powered_by(String provider) => "Dieser Handel wird betrieben von ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + @override + String max_value(String value, String currency) => "Max: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "Mindest: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "Authentifizierung fehlgeschlagen. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} Verbleibende Blöcke"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Durch Drücken von Bestätigen wird gesendet ${fetchingLabel} ${from} von Ihrer Brieftasche aus angerufen ${walletName} an die oben angegebene Adresse. Oder Sie können von Ihrem externen Portemonnaie an die oben angegebene Adresse / QR-Code senden.\n\nBitte bestätigen Sie, um fortzufahren, oder gehen Sie zurück, um die Beträge zu änderns.\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "Bitte senden ${fetchingLabel} ${from} an die oben angegebene Adresse.\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "Transaktion festschreiben\nMenge: ${amount}\nGebühr: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "Fehler beim Entfernen ${wallet_name} Wallet. ${error}"; + @override + String copied_key_to_clipboard(String key) => "Kopiert ${key} Zur Zwischenablage"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "Laden fehlgeschlagen ${wallet_name} Wallet. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "Entfernen ${wallet_name} Wallet"; +} + +class $hi extends S { + const $hi(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "रद्द करना"; + @override + String get trade_state_unpaid => "अवैतनिक"; + @override + String get authenticated => "प्रमाणीकृत"; + @override + String get new_subaddress_create => "सर्जन करना"; + @override + String get restore_view_key_private => "कुंजी देखें (निजी)"; + @override + String get transaction_priority_fast => "उपवास"; + @override + String get error_text_amount => "राशि में केवल संख्याएँ हो सकती हैं"; + @override + String get hidden_balance => "छिपा हुआ संतुलन"; + @override + String get transaction_sent => "भेजा गया लेन-देन"; + @override + String get password => "पारण शब्द"; + @override + String get you_will_get => "आपको मिल जायेगा"; + @override + String get restore_description_from_seed => "या तो 25 शब्द से अपने वॉलेट को पुनर्स्थापित करें\nया 13 शब्द संयोजन कोड"; + @override + String get trade_state_underpaid => "के तहत भुगतान किया"; + @override + String get welcome => "स्वागत हे\nसेवा मेरे CAKE WALLET"; + @override + String get share_address => "पता साझा करें"; + @override + String get please_try_to_connect_to_another_node => "कृपया दूसरे नोड से कनेक्ट करने का प्रयास करें"; + @override + String get trade_state_traded => "ट्रेडेड"; + @override + String get edit => "संपादित करें"; + @override + String get spend_key_public => "खर्च करना (जनता)"; + @override + String get sync_status_syncronizing => "सिंक्रनाइज़ करने"; + @override + String get received => "प्राप्त किया"; + @override + String get auth_store_banned_minutes => " मिनट"; + @override + String get restore_title_from_seed_keys => "बीज / कुंजियों से पुनर्स्थापित करें"; + @override + String get settings_none => "कोई नहीं"; + @override + String get wallet_list_create_new_wallet => "नया बटुआ बनाएँ"; + @override + String get seed_alert_third_text => "आप बीज को फिर से देख सकते हैं\nमें "; + @override + String get setup_pin => "पिन सेट करें"; + @override + String get transaction_details_recipient_address => "प्राप्तकर्ता का पता"; + @override + String get receive_amount => "रकम"; + @override + String get settings_allow_biometrical_authentication => "बायोमेट्रिक प्रमाणीकरण की अनुमति दें"; + @override + String get view_key_private => "कुंजी देखें(निजी)"; + @override + String get restore_next => "आगामी"; + @override + String get trade_state_trading => "व्यापार"; + @override + String get error_text_address => "वॉलेट पता प्रकार के अनुरूप होना चाहिए\nक्रिप्टोकरेंसी का"; + @override + String get send_your_wallet => "आपका बटुआ"; + @override + String get remove_node_message => "क्या आप वाकई चयनित नोड को निकालना चाहते हैं?"; + @override + String get error_text_account_name => "खाता नाम में केवल अक्षर, संख्याएं हो सकती हैं\nऔर 1 और 15 वर्णों के बीच लंबा होना चाहिए"; + @override + String get reconnection => "पुनर्संयोजन"; + @override + String get available_balance => "उपलब्ध शेष राशि"; + @override + String get restore_title_from_keys => "कुंजी से पुनर्स्थापित करें"; + @override + String get wallet_keys => "बटुआ की"; + @override + String get reconnect => "रिकनेक्ट"; + @override + String get reconnect_alert_text => "क्या आप पुन: कनेक्ट होना सुनिश्चित करते हैं?"; + @override + String get send_error_minimum_value => "राशि का न्यूनतम मूल्य 0.01 है"; + @override + String get copy_address => "पता कॉपी करें"; + @override + String get error_text_subaddress_name => "सबड्रेस नाम नहीं हो सकता` , ' \" प्रतीकों\nऔर 1 और 20 वर्णों के बीच लंबा होना चाहिए"; + @override + String get settings_all => "सब"; + @override + String get nodes_list_reset_to_default_message => "क्या आप वाकई सेटिंग को डिफ़ॉल्ट पर रीसेट करना चाहते हैं?"; + @override + String get receive => "प्राप्त करना"; + @override + String get amount_is_guaranteed => "प्राप्त राशि की गारंटी है"; + @override + String get auth_store_banned_for => "के लिए प्रतिबंधित है "; + @override + String get trade_details_pair => "जोड़ा"; + @override + String get confirm => "की पुष्टि करें"; + String get send_xmr => "संदेश XMR"; + @override + String get settings_display_balance_as => "के रूप में संतुलन प्रदर्शित करें"; + @override + String get trade_details_provider => "प्रदाता"; + @override + String get you_will_send => "तुम भेजोगे"; + @override + String get create_new => "नया बनाओ"; + @override + String get trade_details_fetching => "ला रहा है"; + @override + String get seed_alert_menu => " मेन्यू."; + @override + String get confirm_sending => "भेजने की पुष्टि करें"; + @override + String get settings_title => "सेटिंग्स"; + @override + String get address_book_menu => "पता पुस्तिका"; + @override + String get wallet_restoration_store_incorrect_seed_length => "गलत बीज की लंबाई"; + @override + String get contact => "संपर्क करें"; + @override + String get auth_store_incorrect_password => "गलत पिन"; + @override + String get transaction_priority_slow => "धीरे"; + @override + String get add => "जोड़ना"; + @override + String get remove_node => "नोड निकालें"; + @override + String get trade_state_paid_unconfirmed => "अपुष्ट भुगतान किया"; + @override + String get please_select => "कृपया चुने:"; + @override + String get restore_title_from_backup => "बैक-अप फ़ाइल से पुनर्स्थापित करें"; + @override + String get full_balance => "पूर्ण संतुलन"; + @override + String get sync_status_not_connected => "जुड़े नहीं हैं"; + @override + String get error_text_crypto_currency => "अंश अंकों की संख्या\n12 से कम या इसके बराबर होना चाहिए"; + @override + String get error_text_contact_name => "संपर्क नाम शामिल नहीं हो सकता ` , ' \" प्रतीकों\nऔर 1 और 32 वर्णों के बीच लंबा होना चाहिए"; + @override + String get restore_seed_keys_restore => "बीज / कुंजी पुनर्स्थापित करें"; + @override + String get xmr_hidden => "XMR छिपा हुआ"; + @override + String get exchange => "अदला बदली"; + @override + String get sync_status_failed_connect => "फेल हुआ कनेक्ट नोड"; + @override + String get send_estimated_fee => "अनुमानित शुल्क:"; + @override + String get outgoing => "निवर्तमान"; + @override + String get sync_status_connected => "जुड़े हुए"; + @override + String get trade_state_pending => "विचाराधीन"; + @override + String get pending => " (अपूर्ण)"; + @override + String get setup_successful => "आपका पिन सफलतापूर्वक सेट हो गया है"; + @override + String get fetching => "ला रहा है"; + @override + String get settings_nodes => "नोड्स"; + @override + String get widgets_or => "या"; + @override + String get remove => "हटाना"; + @override + String get yesterday => "बिता कल"; + @override + String get expired => "समय सीमा समाप्त"; + @override + String get transaction_priority_regular => "नियमित"; + @override + String get trade_details_created_at => "पर बनाया गया"; + @override + String get settings_wallets => "पर्स"; + @override + String get settings_only_transactions => "केवल लेन-देन"; + @override + String get estimated => "अनुमानित"; + @override + String get filters => "फिल्टर"; + @override + String get settings_current_node => "वर्तमान नोड"; + @override + String get copy_id => "प्रतिलिपि ID"; + @override + String get please_make_selection => "कृपया नीचे चयन करें\nअपना बटुआ बनाएं या पुनर्प्राप्त करें."; + @override + String get loading_your_wallet => "अपना बटुआ लोड कर रहा है"; + @override + String get subaddresses => "उप पते"; + @override + String get trade_state_timeout => "समय समाप्त"; + @override + String get nodes => "नोड्स"; + @override + String get seed_share => "बीज साझा करें"; + @override + String get widgets_restore_from_blockheight => "ब्लॉकचेन से पुनर्स्थापित करें"; + @override + String get new_subaddress_label_name => "लेबल का नाम"; + @override + String get trade_not_found => "व्यापार नहीं मिला"; + @override + String get transaction_details_date => "तारीख"; + @override + String get show_seed => "बीज दिखाओ"; + @override + String get send_error_currency => "मुद्रा में केवल संख्याएँ हो सकती हैं"; + @override + String get subaddress_title => "उपखंड सूची"; + @override + String get wallet_store_monero_wallet => "मोनरो वॉलेट"; + @override + String get send_creating_transaction => "लेन-देन बनाना"; + @override + String get trade_state_complete => "पूर्ण"; + @override + String get error_text_xmr => "एक्सएमआर मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या 12 से कम या इसके बराबर होनी चाहिए"; + @override + String get xmr_available_balance => "XMR उपलब्ध शेष राशि"; + @override + String get trade_state_paid => "भुगतान किया है"; + @override + String get node_new => "नया नोड"; + @override + String get trade_state_created => "बनाया था"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "मैंने व्यापार बचा लिया है ID"; + @override + String get settings_currency => "मुद्रा"; + @override + String get change_language => "भाषा बदलो"; + @override + String get wallet_name => "बटुए का नाम"; + @override + String get error_text_payment_id => "पेमेंट आईडी केवल हेक्स में 16 से 64 चार्ट तक हो सकती है"; + @override + String get seed_alert_second_text => "कृपया इन्हें केवल मामले में लिखें\nआप अपना फोन खो देते हैं या मिटा देते हैं."; + @override + String get node_reset_settings_title => "सेटिंग्स को दुबारा करें"; + @override + String get wallets => "पर्स"; + @override + String get enter_your_pin_again => "फिर से अपना पिन डालें"; + @override + String get transactions => "लेन-देन"; + @override + String get amount_is_estimate => "प्राप्त राशि एक अनुमान है"; + @override + String get error_text_node_address => "कृपया एक IPv4 पता दर्ज करें"; + @override + String get widgets_restore_from_date => "दिनांक से पुनर्स्थापित करें"; + @override + String get restore_recover => "वसूली"; + @override + String get reset => "रीसेट"; + @override + String get new_wallet => "नया बटुआ"; + @override + String get settings_display_on_dashboard_list => "डैशबोर्ड सूची पर प्रदर्शित करें"; + @override + String get transaction_details_title => "लेनदेन का विवरण"; + @override + String get status => "स्थिति: "; + @override + String get restore_spend_key_private => "कुंजी खर्च करें (निजीe)"; + @override + String get sync_status_syncronized => "सिंक्रनाइज़"; + @override + String get transaction_priority_medium => "मध्यम"; + @override + String get transaction_details_transaction_id => "लेनदेन आईडी"; + @override + String get save => "बचाना"; + @override + String get login => "लॉग इन करें"; + @override + String get wallet_list_load_wallet => "वॉलेट लोड करें"; + @override + String get sending => "भेजना"; + @override + String get restore_restore_wallet => "वॉलेट को पुनर्स्थापित करें"; + @override + String get restore_description_from_seed_keys => "अपने बटुए को बीज से वापस लें/वे कुंजियाँ जिन्हें आपने सुरक्षित स्थान पर सहेजा है"; + @override + String get copy => "प्रतिलिपि"; + @override + String get node_port => "नोड पोर्ट"; + @override + String get change_currency => "मुद्रा परिवर्तन करें"; + @override + String get transaction_details_amount => "रकम"; + @override + String get widgets_address => "पता"; + @override + String get contact_name => "संपर्क नाम"; + @override + String get exchange_result_write_down_ID => "*कृपया ऊपर दिखाए गए अपने ID को कॉपी या लिख लें."; + @override + String get exchange_result_write_down_trade_id => "जारी रखने के लिए कृपया ट्रेड ID की प्रतिलिपि बनाएँ या लिखें."; + @override + String get new_subaddress_title => "नई उपशादी"; + @override + String get change => "परिवर्तन"; + @override + String get seed_alert_first_text => "अगला पेज दिखाएगा\nतुम एक बीज हो."; + @override + String get trade_state_finished => "ख़त्म होना"; + @override + String get pin_is_incorrect => "पिन गलत है"; + @override + String get trade_not_created => "व्यापार नहीं बनाया गया."; + @override + String get restore_wallet_name => "बटुए का नाम"; + @override + String get widgets_seed => "बीज"; + @override + String get settings_fee_priority => "शुल्क प्राथमिकता"; + @override + String get settings_personal => "निजी"; + @override + String get seed_title => "बीज"; + @override + String get accounts => "हिसाब किताब"; + @override + String get rescan => "पुन: स्कैन"; + @override + String get seed_alert_understand => "मै समझता हुँ"; + @override + String get show_keys => "चाबी दिखाओ"; + @override + String get error_text_fiat => "राशि का मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या कम या 2 के बराबर होनी चाहिए"; + @override + String get transactions_by_date => "तारीख से लेन-देन"; + @override + String get use => "उपयोग "; + @override + String get trade_details_state => "राज्य"; + @override + String get spend_key_private => "खर्च करना (निजी)"; + @override + String get settings_only_trades => "केवल ट्रेड करता है"; + @override + String get trade_state_to_be_created => "बनाए जाने के लिए"; + @override + String get all => "सब"; + @override + String get xmr_full_balance => "XMR पूर्ण संतुलन"; + @override + String get incoming => "आने वाली"; + @override + String get trade_history_title => "व्यापार का इतिहास"; + @override + String get error_text_wallet_name => "वॉलेट नाम में केवल अक्षर, संख्याएं हो सकती हैं\nऔर 1 और 15 वर्णों के बीच लंबा होना चाहिए"; + @override + String get restore_description_from_keys => "अपने वॉलेट को जेनरेट से पुनर्स्थापित करें\nआपकी निजी कुंजी से कीस्ट्रोक्स सहेजे गए"; + @override + String get sent => "भेज दिया"; + @override + String get view_key_public => "कुंजी देखें (जनता)"; + @override + String get trade_details_id => "आईडी"; + @override + String get trade_state_confirming => "पुष्टि"; + @override + String get send => "संदेश"; + @override + String get send_title => "संदेश Monero"; + @override + String get error_text_keys => "वॉलेट कीज़ में हेक्स में केवल 64 वर्ण हो सकते हैं"; + @override + String get settings_save_recipient_address => "प्राप्तकर्ता का पता सहेजें"; + @override + String get seed_alert_settings => "सेटिंग्स"; + @override + String get change_exchange_provider => "एक्सचेंज प्रदाता बदलें"; + @override + String get send_payment_id => "भुगतान ID (ऐच्छिक)"; + @override + String get trade_details_title => "व्यापार विवरण"; + @override + String get settings_terms_and_conditions => "नियम और शर्तें"; + @override + String get auth_store_ban_timeout => "समय की पाबंदी"; + @override + String get sync_status_connecting => "कनेक्ट"; + @override + String get settings_transactions => "लेन-देन"; + @override + String get transaction_priority_fastest => "सबसे तेजी से"; + @override + String get error => "त्रुटि"; + @override + String get delete => "हटाएं"; + @override + String get sync_status_starting_sync => "सिताज़ा करना"; + @override + String get copied_to_clipboard => "क्लिपबोर्ड पर नकल"; + @override + String get offer_expires_in => "में ऑफर समाप्त हो रहा है: "; + @override + String get continue_text => "जारी रहना"; + @override + String get transaction_details_height => "ऊंचाई"; + @override + String get wallet_menu => "बटुआ मेनू"; + @override + String get settings_dark_mode => "डार्क मोड"; + @override + String get payment_id => "भुगतान ID: "; + @override + String get restore_address => "पता"; + @override + String get restore_wallet_restore_description => "बटुआ विवरण पुनर्स्थापित करें"; + @override + String get today => "आज"; + @override + String get settings_support => "समर्थन"; + @override + String get restore_wallet => "वॉलेट को पुनर्स्थापित करें"; + @override + String get ok => "ठीक है"; + @override + String get wallet_list_title => "Monero बटुआ"; + @override + String get authentication => "प्रमाणीकरण"; + @override + String get amount => "रकम: "; + @override + String get node_address => "नोड पता"; + @override + String get settings_change_language => "भाषा बदलो"; + @override + String get clear => "स्पष्ट"; + @override + String get settings_change_pin => "पिन बदलें"; + @override + String get trades => "ट्रेडों"; + @override + String get trade_state_btc_sent => "भेज दिया"; + @override + String get address_book => "पता पुस्तिका"; + @override + String get enter_your_pin => "अपना पिन दर्ज करो"; + @override + String get wallet_list_restore_wallet => "वॉलेट को पुनर्स्थापित करें"; + @override + String get restore_title_from_seed => "बीज से पुनर्स्थापित करें"; + @override + String get restore_description_from_backup => "आप से पूरे केक वॉलेट एप्लिकेशन को पुनर्स्थापित कर सकते हैं\nआपकी बैक-अप फ़ाइल"; + @override + String get send_monero_address => "मोनरो पता"; + @override + String get error_text_node_port => "नोड पोर्ट में केवल 0 और 65535 के बीच संख्याएँ हो सकती हैं"; + @override + String get digit_pin => "-अंक पिन"; + @override + String get first_wallet_text => "बहुत बढ़िया बटुआ\nके लिये Monero"; + @override + String get settings_trades => "ट्रेडों"; + @override + String get account => "लेखा"; + @override + String change_language_to(String language) => "को भाषा बदलें ${language}?"; + @override + String change_current_node(String node) => "क्या आप वर्तमान नोड को बदलना सुनिश्चित करते हैं ${node}?"; + @override + String trade_id(String id) => "व्यापार ID:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "लोड हो रहा है ${wallet_name} बटुआ"; + @override + String router_no_route(String name) => "के लिए कोई मार्ग निर्धारित नहीं है ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "व्यापार ${tradeId} of ${title} नहीं मिला."; + @override + String transaction_details_copied(String title) => "${title} क्लिपबोर्ड पर नकल"; + @override + String trade_details_copied(String title) => "${title} क्लिपबोर्ड पर नकल"; + @override + String powered_by(String title) => "द्वारा संचालित ${title}"; + @override + String send_priority(String transactionPriority) => "वर्तमान में शुल्क निर्धारित है ${transactionPriority} प्राथमिकता.\nलेन-देन की प्राथमिकता को सेटिंग्स में समायोजित किया जा सकता है"; + @override + String trade_for_not_created(String title) => "के लिए व्यापार ${title} निर्मित नहीं है."; + @override + String trade_is_powered_by(String provider) => "यह व्यापार द्वारा संचालित है ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + @override + String max_value(String value, String currency) => "मैक्स: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "मिन: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "प्रमाणीकरण विफल. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} शेष रहते हैं"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "पुष्टि दबाकर, आप भेज रहे होंगे ${fetchingLabel} ${from} अपने बटुए से ${walletName} ऊपर दिखाए गए पते पर। या आप अपने बाहरी वॉलेट से उपरोक्त पते / क्यूआर कोड पर भेज सकते हैं।\n\nकृपया जारी रखने या राशि बदलने के लिए वापस जाने के लिए पुष्टि करें दबाएं.\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "कृपया भेजें ${fetchingLabel} ${from} ऊपर दिखाए गए पते पर\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "लेन-देन करें\nरकम: ${amount}\nशुल्क: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "निकालने में विफल ${wallet_name} बटुआ. ${error}"; + @override + String copied_key_to_clipboard(String key) => "की नकल की ${key} क्लिपबोर्ड पर"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "लोड करने में विफल ${wallet_name} बटुआ. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "निकाला जा रहा है ${wallet_name} बटुआ"; +} + +class $ru extends S { + const $ru(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "Отменить"; + @override + String get trade_state_unpaid => "Неоплаченно"; + @override + String get authenticated => "Аутентифицировано"; + @override + String get new_subaddress_create => "Создать"; + @override + String get restore_view_key_private => "View ключ (приватный)"; + @override + String get transaction_priority_fast => "Быстрый"; + @override + String get error_text_amount => "Баланс может включать только цифры"; + @override + String get hidden_balance => "Скрытый баланс"; + @override + String get transaction_sent => "Tранзакция отправлена!"; + @override + String get password => "Password"; + @override + String get you_will_get => "Вы получите"; + @override + String get restore_description_from_seed => "Вы можете восстановить кошелек используя 25-ти значную код фразу"; + @override + String get trade_state_underpaid => "Недоплаченно"; + @override + String get welcome => "Приветствуем в CAKE WALLET"; + @override + String get share_address => "Поделиться адресом"; + @override + String get please_try_to_connect_to_another_node => "Пожалуйста, попробуйте подключиться к другой ноде"; + @override + String get trade_state_traded => "Сделка завершена"; + @override + String get edit => "Редактировать"; + @override + String get spend_key_public => "Spend key (публичный)"; + @override + String get sync_status_syncronizing => "СИНХРОНИЗАЦИЯ"; + @override + String get received => "Полученные"; + @override + String get auth_store_banned_minutes => " минут"; + @override + String get restore_title_from_seed_keys => "Восстановить из код-фразы/ключей"; + @override + String get settings_none => "Ничего"; + @override + String get wallet_list_create_new_wallet => "Создать новый кошелек"; + @override + String get seed_alert_third_text => "Вы так же можете посмотреть Вашу код-фразу\nв"; + @override + String get setup_pin => "Настроить PIN"; + @override + String get transaction_details_recipient_address => "Адрес получателя"; + @override + String get receive_amount => "Баланс"; + @override + String get settings_allow_biometrical_authentication => "Включить биометрическую аутентификацию"; + @override + String get view_key_private => "View key (приватный)"; + @override + String get restore_next => "Продолжить"; + @override + String get trade_state_trading => "Совершение сделки"; + @override + String get error_text_address => "Адрес кошелька должен соответствовать типу\nкриптовалюты"; + @override + String get send_your_wallet => "Ваш кошелек"; + @override + String get remove_node_message => "Вы уверены, что хотите удалить текущую ноду?"; + @override + String get error_text_account_name => "Имя аккаунта может включать только буквы, цифры\nи может быть от 1 до 15 символов в длину"; + @override + String get reconnection => "Переподключение"; + @override + String get available_balance => "Доступный баланс"; + @override + String get restore_title_from_keys => "Восстановить с помощью ключей"; + @override + String get wallet_keys => "Ключи кошелька"; + @override + String get reconnect => "Переподключиться"; + @override + String get reconnect_alert_text => "Вы хотите переподключиться?"; + @override + String get send_error_minimum_value => "Mинимальная сумма 0.01"; + @override + String get copy_address => "Cкопировать адрес"; + @override + String get error_text_subaddress_name => "Subaddress имя не может включать ` , ' \" символы\nи может быть от 1 до 32 символов в длину"; + @override + String get settings_all => "ВСЕ"; + @override + String get nodes_list_reset_to_default_message => "Вы уверены, что хотите сбросить до настроек по умолчанию?"; + @override + String get receive => "Получить"; + @override + String get amount_is_guaranteed => "Сумма получения гарантирована"; + @override + String get auth_store_banned_for => "Заблокированно на "; + @override + String get trade_details_pair => "Пара"; + @override + String get confirm => "Подтвердить"; + String get send_xmr => "Отправить XMR"; + @override + String get settings_display_balance_as => "Отображать баланс"; + @override + String get trade_details_provider => "Провайдер"; + @override + String get you_will_send => "Вы отправите"; + @override + String get create_new => "Создать новый"; + @override + String get trade_details_fetching => "Получение"; + @override + String get seed_alert_menu => " меню."; + @override + String get confirm_sending => "Подтвердить отправку"; + @override + String get settings_title => "Настройки"; + @override + String get address_book_menu => "Адресная книга"; + @override + String get wallet_restoration_store_incorrect_seed_length => "Неверная длина код-фразы"; + @override + String get contact => "Контакт"; + @override + String get auth_store_incorrect_password => "Некорректный пин"; + @override + String get transaction_priority_slow => "Медленный"; + @override + String get add => "Добавить"; + @override + String get remove_node => "Удалить ноду"; + @override + String get trade_state_paid_unconfirmed => "Оплата неподтвержденная"; + @override + String get please_select => "Пожалуйста, выберите:"; + @override + String get restore_title_from_backup => "Восстановить из back-up файла"; + @override + String get full_balance => "Полный баланс"; + @override + String get sync_status_not_connected => "НЕТ ПОДКЛЮЧЕНИЯ"; + @override + String get error_text_crypto_currency => "Количество цифр после запятой\nдолжно быть меньше или равно 12"; + @override + String get error_text_contact_name => "Имя контакта не может включать ` , ' \" символы\n и может быть от 1 до 32 символов в длину"; + @override + String get restore_seed_keys_restore => "Восстановить с помощью код-фразы/ключей"; + @override + String get xmr_hidden => "XMR Скрытый"; + @override + String get exchange => "Обмен"; + @override + String get sync_status_failed_connect => "ОШИБКА ПОДКЛЮЧЕНИЯ К НОДЕ"; + @override + String get send_estimated_fee => "Предполагаемый сбор:"; + @override + String get outgoing => "Исходящие"; + @override + String get sync_status_connected => "ПОДКЛЮЧЕНО"; + @override + String get trade_state_pending => "Ожидание"; + @override + String get pending => " (в ожидании)"; + @override + String get setup_successful => "PIN был успешно изменен!"; + @override + String get fetching => "Загрузка"; + @override + String get settings_nodes => "Ноды"; + @override + String get widgets_or => "или"; + @override + String get remove => "Удалить"; + @override + String get yesterday => "Вчера"; + @override + String get expired => "Истекает"; + @override + String get transaction_priority_regular => "Обычный"; + @override + String get trade_details_created_at => "Создано"; + @override + String get settings_wallets => "Кошельки"; + @override + String get settings_only_transactions => "Транзакции"; + @override + String get estimated => "Примерно "; + @override + String get filters => "Фильтры"; + @override + String get settings_current_node => "Текущая нода"; + @override + String get copy_id => "Скопировать ID"; + @override + String get please_make_selection => "Выберите способ создания кошелька: создать новый или восстановить Ваш существующий."; + @override + String get loading_your_wallet => "Загрузка кошелька"; + @override + String get subaddresses => "Subaddresses"; + @override + String get trade_state_timeout => "Timeout"; + @override + String get nodes => "Ноды"; + @override + String get seed_share => "Поделиться код-фразой"; + @override + String get widgets_restore_from_blockheight => "Восстановить по высоте"; + @override + String get new_subaddress_label_name => "Имя"; + @override + String get trade_not_found => "Trade not found."; + @override + String get transaction_details_date => "Дата"; + @override + String get show_seed => "Показать код-фразу"; + @override + String get send_error_currency => "Валюта может включать только цифры"; + @override + String get subaddress_title => "Subaddress список"; + @override + String get wallet_store_monero_wallet => "Monero кошелек"; + @override + String get send_creating_transaction => "Создать транзакцию"; + @override + String get trade_state_complete => "Полный"; + @override + String get error_text_xmr => "XMR баланс не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 12"; + @override + String get xmr_available_balance => "XMR Доступный баланс"; + @override + String get trade_state_paid => "Оплачено"; + @override + String get node_new => "Новая нода"; + @override + String get trade_state_created => "Создано"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "Я сохранил ID сделки"; + @override + String get settings_currency => "Валюта"; + @override + String get change_language => "Изменить язык"; + @override + String get wallet_name => "Имя кошелька"; + @override + String get error_text_payment_id => "ID транзакции может включать от 16 до 64 символа в hex"; + @override + String get seed_alert_second_text => "Пожалуйста, запишите на \nслучай, если Вы потеряете или сотрете Ваш телефон."; + @override + String get node_reset_settings_title => "Сбросить настройки"; + @override + String get wallets => "Кошельки"; + @override + String get enter_your_pin_again => "Введите pin еще раз"; + @override + String get transactions => "Транзакции"; + @override + String get amount_is_estimate => "Полученная сумма является приблизительной"; + @override + String get error_text_node_address => "Пожалуйста, введите iPv4 адрес"; + @override + String get widgets_restore_from_date => "Восстановить по дате"; + @override + String get restore_recover => "Восстановить"; + @override + String get reset => "Сброс"; + @override + String get new_wallet => "Новый кошелек"; + @override + String get settings_display_on_dashboard_list => "Показывать в списке транзакций"; + @override + String get transaction_details_title => "Детали транзакции"; + @override + String get status => "Статус: "; + @override + String get restore_spend_key_private => "Spend ключ (приватный)"; + @override + String get sync_status_syncronized => "СИНХРОНИЗИРОВАНО"; + @override + String get transaction_priority_medium => "Средний"; + @override + String get transaction_details_transaction_id => "ID транзакции"; + @override + String get save => "Сохранить"; + @override + String get login => "Login"; + @override + String get wallet_list_load_wallet => "Загрузка кошелька"; + @override + String get sending => "Отправка"; + @override + String get restore_restore_wallet => "Восстановить кошелек"; + @override + String get restore_description_from_seed_keys => " Вы можете восстановить кошелек из код-фразы/ключей которые Вы сохранили ранее"; + @override + String get copy => "Скопировать"; + @override + String get node_port => "Порт ноды"; + @override + String get change_currency => "Изменить валюту"; + @override + String get transaction_details_amount => "Сумма"; + @override + String get widgets_address => "Адрес"; + @override + String get contact_name => "Имя контакта"; + @override + String get exchange_result_write_down_ID => "*Пожалуйста, скопируйте или запишите ID, указанный выше."; + @override + String get exchange_result_write_down_trade_id => "Пожалуйста, скопируйте или запишите ID сделки."; + @override + String get new_subaddress_title => "Новый subaddress"; + @override + String get change => "Изменить"; + @override + String get seed_alert_first_text => "Следущая страница содержит\nВашу код-фразу."; + @override + String get trade_state_finished => "Окончено"; + @override + String get pin_is_incorrect => "Некорректный пин"; + @override + String get trade_not_created => "Сделка не создана."; + @override + String get restore_wallet_name => "Имя кошелька"; + @override + String get widgets_seed => "Код-фраза"; + @override + String get settings_fee_priority => "Приоритет сбора"; + @override + String get settings_personal => "Персональные"; + @override + String get seed_title => "Код-фраза"; + @override + String get accounts => "Аккаунты"; + @override + String get rescan => "Пересканировать"; + @override + String get seed_alert_understand => "Я понимаю"; + @override + String get show_keys => "Показать ключи"; + @override + String get error_text_fiat => "Значение суммы не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 2"; + @override + String get transactions_by_date => "Сортировать по дате"; + @override + String get use => "Использовать "; + @override + String get trade_details_state => "Статус"; + @override + String get spend_key_private => "Spend key (приватный)"; + @override + String get settings_only_trades => "Сделки"; + @override + String get trade_state_to_be_created => "Будет создана"; + @override + String get all => "ALL"; + @override + String get xmr_full_balance => "XMR Полный баланс"; + @override + String get incoming => "Входящие"; + @override + String get trade_history_title => "История сделок"; + @override + String get error_text_wallet_name => "Имя кошелька может содержать только буквы, цифры\nи может быть от 1 до 15 символов в длину"; + @override + String get restore_description_from_keys => "Вы можете восстановить кошелек с помощью приватных ключей"; + @override + String get sent => "Отправленные"; + @override + String get view_key_public => "View key (публичный)"; + @override + String get trade_details_id => "ID"; + @override + String get trade_state_confirming => "Подтверждение"; + @override + String get send => "Отправить"; + @override + String get send_title => "Отправить Monero"; + @override + String get error_text_keys => "Ключи кошелька могут содержать только 64 символа в hex"; + @override + String get settings_save_recipient_address => "Сохранять адрес получателя"; + @override + String get seed_alert_settings => "настройках"; + @override + String get change_exchange_provider => "Изменить провайдер обмена"; + @override + String get send_payment_id => "Payment ID (optional)"; + @override + String get trade_details_title => "Детали сделок"; + @override + String get settings_terms_and_conditions => "Условия и положения"; + @override + String get auth_store_ban_timeout => "ban_timeout"; + @override + String get sync_status_connecting => "ПОДКЛЮЧЕНИЕ"; + @override + String get settings_transactions => "Транзакции"; + @override + String get transaction_priority_fastest => "Самый быстрый"; + @override + String get error => "Ошибка"; + @override + String get delete => "Удалить"; + @override + String get sync_status_starting_sync => "НАЧАЛО СИНХРОНИЗАЦИИ"; + @override + String get copied_to_clipboard => "Скопировано в буфер обмена"; + @override + String get offer_expires_in => "Предложение истекает через: "; + @override + String get continue_text => "Продолжить"; + @override + String get transaction_details_height => "Высота"; + @override + String get wallet_menu => "Меню кошелька"; + @override + String get settings_dark_mode => "Темный режим"; + @override + String get payment_id => "ID транзакции: "; + @override + String get restore_address => "Адрес"; + @override + String get restore_wallet_restore_description => "Wallet restore description"; + @override + String get today => "Сегодня"; + @override + String get settings_support => "Поддержка"; + @override + String get restore_wallet => "Восстановить"; + @override + String get ok => "OK"; + @override + String get wallet_list_title => "Monero кошелек"; + @override + String get authentication => "Аутентификация"; + @override + String get amount => "Сумма: "; + @override + String get node_address => "Адрес ноды"; + @override + String get settings_change_language => "Изменить язык"; + @override + String get clear => "Очистить"; + @override + String get settings_change_pin => "Изменить PIN"; + @override + String get trades => "Сделки"; + @override + String get trade_state_btc_sent => "Btc отправлен"; + @override + String get address_book => "Адресная книга"; + @override + String get enter_your_pin => "Введите Ваш PIN"; + @override + String get wallet_list_restore_wallet => "Восстановить"; + @override + String get restore_title_from_seed => "Восстановить из код-фразы"; + @override + String get restore_description_from_backup => "Вы можете восстановить Cake Wallet из\nВашего back-up файла"; + @override + String get send_monero_address => "Monero адрес"; + @override + String get error_text_node_port => "Порт ноды может включать только цифры от 0 до 65535"; + @override + String get digit_pin => "-значный Pin"; + @override + String get first_wallet_text => "В самом удобном кошельке для Monero"; + @override + String get settings_trades => "Сделки"; + @override + String get account => "Аккаунт"; + @override + String change_language_to(String language) => "Изменить язык на ${language}?"; + @override + String change_current_node(String node) => "Вы уверены, что хотите изменить текущую ноду на ${node}?"; + @override + String trade_id(String id) => "ID сделки:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "Загрузка ${wallet_name} кошелька"; + @override + String router_no_route(String name) => "Экран не найден ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "Сделка ${tradeId} ${title} не найдена."; + @override + String transaction_details_copied(String title) => "${title} скопировано в буфер обмена"; + @override + String trade_details_copied(String title) => "${title} скопировано в буфер обмена"; + @override + String powered_by(String title) => "Используя ${title}"; + @override + String send_priority(String transactionPriority) => "Сбор установлен в зависимости от приоритета: ${transactionPriority}.\nПриоритет транзакции может быть изменен в настройках"; + @override + String trade_for_not_created(String title) => "Сделка для ${title} не создана."; + @override + String trade_is_powered_by(String provider) => "Сделка выполнена ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}мин ${seconds}сек"; + @override + String max_value(String value, String currency) => "Макс: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "Мин: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "Ошибка аутентификации. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} Осталось блоков"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Нажимая подтвердить, Вы отправите ${fetchingLabel} ${from} с Вашего кошелька ${walletName} на адрес указанный выше. Или Вы можете отправить со своего внешнего кошелька на вышеуказанный адрес / QR-код.\n\nПожалуйста, нажмите подтвердить для продолжения или вернитесь назад для изменения суммы.\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "Пожалуйста отправьте ${fetchingLabel} ${from} на адрес, указанный выше.\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "Подтвердить транзакцию \nСумма: ${amount}\nСбор: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "Ошибка при удалении ${wallet_name} кошелька. ${error}"; + @override + String copied_key_to_clipboard(String key) => "Скопировано ${key} в буфер обмена"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "Ошибка при загрузке ${wallet_name} кошелька. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "Удаление ${wallet_name} кошелька"; +} + +class $ko extends S { + const $ko(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "취소"; + @override + String get trade_state_unpaid => "미지급"; + @override + String get authenticated => "인증"; + @override + String get new_subaddress_create => "몹시 떠들어 대다"; + @override + String get restore_view_key_private => "키보기 (은밀한)"; + @override + String get transaction_priority_fast => "빠른"; + @override + String get error_text_amount => "금액은 숫자 만 포함 할 수 있습니다"; + @override + String get hidden_balance => "숨겨진 균형"; + @override + String get transaction_sent => "거래가 전송되었습니다!"; + @override + String get password => "암호"; + @override + String get you_will_get => "당신은 얻을 것이다"; + @override + String get restore_description_from_seed => "25 단어 또는 13 단어 조합 코드에서 지갑을 복원하십시오."; + @override + String get trade_state_underpaid => "미지급"; + @override + String get welcome => "환영\n에 CAKE WALLET"; + @override + String get share_address => "주소 공유"; + @override + String get please_try_to_connect_to_another_node => "다른 노드에 연결을 시도하십시오"; + @override + String get trade_state_traded => "거래"; + @override + String get edit => "편집하다"; + @override + String get spend_key_public => "지출 키 (공공의)"; + @override + String get sync_status_syncronizing => "동기화"; + @override + String get received => "받았습니다"; + @override + String get auth_store_banned_minutes => " 의사록"; + @override + String get restore_title_from_seed_keys => "시드 / 키에서 복원"; + @override + String get settings_none => "없음"; + @override + String get wallet_list_create_new_wallet => "새 월렛 만들기"; + @override + String get seed_alert_third_text => "당신은 또한 씨앗을 다시 볼 수 있습니다\n에서 "; + @override + String get setup_pin => "설정 PIN"; + @override + String get transaction_details_recipient_address => "받는 사람 주소"; + @override + String get receive_amount => "양"; + @override + String get settings_allow_biometrical_authentication => "생체 인증 허용"; + @override + String get view_key_private => "키보기(은밀한)"; + @override + String get restore_next => "다음 것"; + @override + String get trade_state_trading => "거래"; + @override + String get error_text_address => "지갑 주소는 유형과 일치해야합니다\n암호 화폐"; + @override + String get send_your_wallet => "지갑"; + @override + String get remove_node_message => "선택한 노드를 제거 하시겠습니까?"; + @override + String get error_text_account_name => "계정 이름은 문자, 숫자 만 포함 할 수 있습니다\n1 ~ 15 자 사이 여야합니다"; + @override + String get reconnection => "재 연결"; + @override + String get available_balance => "사용 가능한 잔액"; + @override + String get restore_title_from_keys => "키에서 복원"; + @override + String get wallet_keys => "지갑 키"; + @override + String get reconnect => "다시 연결"; + @override + String get reconnect_alert_text => "다시 연결 하시겠습니까?"; + @override + String get send_error_minimum_value => "금액의 최소값은 0.01입니다"; + @override + String get copy_address => "주소 복사"; + @override + String get error_text_subaddress_name => "하위 주소 이름은 포함 할 수 없습니다 ` , ' \" 기호 \n1 ~ 20 자 사이 여야합니다"; + @override + String get settings_all => "모든"; + @override + String get nodes_list_reset_to_default_message => "설정을 기본값으로 재설정 하시겠습니까?"; + @override + String get receive => "받다"; + @override + String get amount_is_guaranteed => "수신 금액이 보장됩니다"; + @override + String get auth_store_banned_for => "금지"; + @override + String get trade_details_pair => "쌍"; + @override + String get confirm => "확인"; + @override + String get send_xmr => "보내다 XMR"; + @override + String get settings_display_balance_as => "잔액 표시"; + @override + String get trade_details_provider => "공급자"; + @override + String get you_will_send => "보내드립니다"; + @override + String get create_new => "새로 만들기"; + @override + String get trade_details_fetching => "가져 오는 중"; + @override + String get seed_alert_menu => " 메뉴."; + @override + String get confirm_sending => "전송 확인"; + @override + String get settings_title => "설정"; + @override + String get address_book_menu => "주소록"; + @override + String get wallet_restoration_store_incorrect_seed_length => "시드 길이가 잘못되었습니다"; + @override + String get contact => "접촉"; + @override + String get auth_store_incorrect_password => "잘못된 PIN"; + @override + String get transaction_priority_slow => "느린"; + @override + String get add => "더하다"; + @override + String get remove_node => "노드 제거"; + @override + String get trade_state_paid_unconfirmed => "미확인 유료"; + @override + String get please_select => "선택 해주세요:"; + @override + String get restore_title_from_backup => "백업 파일에서 복원"; + @override + String get full_balance => "풀 밸런스"; + @override + String get sync_status_not_connected => "연결되지 않은"; + @override + String get error_text_crypto_currency => "소수 자릿수\n12 이하 여야합니다"; + @override + String get error_text_contact_name => "담당자 이름은 포함 할 수 없습니다 ` , ' \" 기호\n1 자에서 32 자 사이 여야합니다"; + @override + String get restore_seed_keys_restore => "종자 / 키 복원"; + @override + String get xmr_hidden => "XMR 숨김"; + @override + String get exchange => "교환"; + @override + String get sync_status_failed_connect => "노드에 연결하지 못했습니다"; + @override + String get send_estimated_fee => "예상 수수료:"; + @override + String get outgoing => "나가는"; + @override + String get sync_status_connected => "연결됨"; + @override + String get trade_state_pending => "대기 중"; + @override + String get pending => " (보류 중)"; + @override + String get setup_successful => "PIN이 성공적으로 설정되었습니다!"; + @override + String get fetching => "가져 오는 중"; + @override + String get settings_nodes => "노드"; + @override + String get widgets_or => "또는"; + @override + String get remove => "없애다"; + @override + String get yesterday => "어제"; + @override + String get expired => "만료"; + @override + String get transaction_priority_regular => "정규병"; + @override + String get trade_details_created_at => "에 작성"; + @override + String get settings_wallets => "지갑"; + @override + String get settings_only_transactions => "거래 만"; + @override + String get estimated => "예상"; + @override + String get filters => "필터"; + @override + String get settings_current_node => "현재 노드"; + @override + String get copy_id => "부 ID"; + @override + String get please_make_selection => "아래에서 선택하십시오\n지갑 만들기 또는 복구."; + @override + String get loading_your_wallet => "지갑 넣기"; + @override + String get subaddresses => "하위 주소"; + @override + String get trade_state_timeout => "타임 아웃"; + @override + String get nodes => "노드"; + @override + String get seed_share => "시드 공유"; + @override + String get widgets_restore_from_blockheight => "블록 높이에서 복원"; + @override + String get new_subaddress_label_name => "라벨 이름"; + @override + String get trade_not_found => "거래를 찾을 수 없습니다."; + @override + String get transaction_details_date => "날짜"; + @override + String get show_seed => "종자 표시"; + @override + String get send_error_currency => "통화는 숫자 만 포함 할 수 있습니다"; + @override + String get subaddress_title => "하위 주소 목록"; + @override + String get wallet_store_monero_wallet => "모네로 월렛"; + @override + String get send_creating_transaction => "거래 생성"; + @override + String get trade_state_complete => "완전한"; + @override + String get error_text_xmr => "XMR 값은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 12 이하 여야합니다"; + @override + String get xmr_available_balance => "XMR 사용 가능한 잔액"; + @override + String get trade_state_paid => "유료"; + @override + String get node_new => "새로운 노드"; + @override + String get trade_state_created => "만들어진"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "거래 ID를 저장했습니다"; + @override + String get settings_currency => "통화"; + @override + String get change_language => "언어 변경"; + @override + String get wallet_name => "지갑 이름"; + @override + String get error_text_payment_id => "지불 ID는 16 ~ 64 자의 16 진 문자 만 포함 할 수 있습니다"; + @override + String get seed_alert_second_text => "아래에 적어주세요\n휴대 전화를 분실하거나 닦을 경우."; + @override + String get node_reset_settings_title => "설정 초기화"; + @override + String get wallets => "지갑"; + @override + String get enter_your_pin_again => "다시 핀을 입력"; + @override + String get transactions => "업무"; + @override + String get amount_is_estimate => "수신 금액은 견적입니다"; + @override + String get error_text_node_address => "iPv4 주소를 입력하십시오"; + @override + String get widgets_restore_from_date => "날짜에서 복원"; + @override + String get restore_recover => "다시 덮다"; + @override + String get reset => "다시 놓기"; + @override + String get new_wallet => "새 월렛"; + @override + String get settings_display_on_dashboard_list => "대시 보드 목록에 표시"; + @override + String get transaction_details_title => "상세 거래 내역"; + @override + String get status => "지위: "; + @override + String get restore_spend_key_private => "지출 키 (은밀한)"; + @override + String get sync_status_syncronized => "동기화"; + @override + String get transaction_priority_medium => "매질"; + @override + String get transaction_details_transaction_id => "트랜잭션 ID"; + @override + String get save => "구하다"; + @override + String get login => "로그인"; + @override + String get wallet_list_load_wallet => "지갑로드"; + @override + String get sending => "배상"; + @override + String get restore_restore_wallet => "월렛 복원"; + @override + String get restore_description_from_seed_keys => "안전한 장소에 저장 한 종자 / 키로 지갑을 되 찾으십시오."; + @override + String get copy => "부"; + @override + String get node_port => "노드 포트"; + @override + String get change_currency => "통화 변경"; + @override + String get transaction_details_amount => "양"; + @override + String get widgets_address => "주소"; + @override + String get contact_name => "담당자 이름"; + @override + String get exchange_result_write_down_ID => "*위에 표시된 ID를 복사하거나 적어 두십시오."; + @override + String get exchange_result_write_down_trade_id => "계속하려면 거래 ID를 복사하거나 적어 두십시오.."; + @override + String get new_subaddress_title => "새로운 하위 주소"; + @override + String get change => "변화"; + @override + String get seed_alert_first_text => "다음 페이지가 표시됩니다\n당신은 씨앗."; + @override + String get trade_state_finished => "끝마친"; + @override + String get pin_is_incorrect => "PIN이 잘못되었습니다"; + @override + String get trade_not_created => "거래가 생성되지 않았습니다."; + @override + String get restore_wallet_name => "지갑 이름"; + @override + String get widgets_seed => "씨"; + @override + String get settings_fee_priority => "수수료 우선"; + @override + String get settings_personal => "개인적인"; + @override + String get seed_title => "씨"; + @override + String get accounts => "계정"; + @override + String get rescan => "재검색"; + @override + String get seed_alert_understand => "이해 했어"; + @override + String get show_keys => "키 표시"; + @override + String get error_text_fiat => "금액은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 2보다 작거나 같아야합니다"; + @override + String get transactions_by_date => "날짜 별 거래"; + @override + String get use => "용도 "; + @override + String get trade_details_state => "상태"; + @override + String get spend_key_private => "지출 키 (은밀한)"; + @override + String get settings_only_trades => "거래 만"; + @override + String get trade_state_to_be_created => "만들려면"; + @override + String get all => "모든"; + @override + String get xmr_full_balance => "XMR 풀 밸런스"; + @override + String get incoming => "들어오는"; + @override + String get trade_history_title => "무역 역사"; + @override + String get error_text_wallet_name => "지갑 이름은 문자, 숫자 만 포함 할 수 있습니다\n1 ~ 15 자 사이 여야합니다"; + @override + String get restore_description_from_keys => "개인 키에서 저장된 생성 된\n키 스트로크에서 월렛 복원"; + @override + String get sent => "보냄"; + @override + String get view_key_public => "키보기 (공공의)"; + @override + String get trade_details_id => "ID"; + @override + String get trade_state_confirming => "확인 중"; + @override + String get send => "보내다"; + @override + String get send_title => "모네로 보내기"; + @override + String get error_text_keys => "지갑 키는 16 진수로 64 자만 포함 할 수 있습니다"; + @override + String get settings_save_recipient_address => "수신자 주소 저장"; + @override + String get seed_alert_settings => "설정"; + @override + String get change_exchange_provider => "교환 공급자 변경"; + @override + String get send_payment_id => "지불 ID (optional)"; + @override + String get trade_details_title => "거래 세부 사항"; + @override + String get settings_terms_and_conditions => "이용 약관"; + @override + String get auth_store_ban_timeout => "타임 아웃 금지"; + @override + String get sync_status_connecting => "연결 중"; + @override + String get settings_transactions => "업무"; + @override + String get transaction_priority_fastest => "가장 빠른"; + @override + String get error => "오류"; + @override + String get delete => "지우다"; + @override + String get sync_status_starting_sync => "동기화 시작"; + @override + String get copied_to_clipboard => "클립 보드에 복사"; + @override + String get offer_expires_in => "쿠폰 만료일: "; + @override + String get continue_text => "잇다"; + @override + String get transaction_details_height => "신장"; + @override + String get wallet_menu => "월렛 메뉴"; + @override + String get settings_dark_mode => "다크 모드"; + @override + String get payment_id => "지불 ID: "; + @override + String get restore_address => "주소"; + @override + String get restore_wallet_restore_description => "월렛 복원 설명"; + @override + String get today => "오늘"; + @override + String get settings_support => "지원하다"; + @override + String get restore_wallet => "지갑 복원"; + @override + String get ok => "승인"; + @override + String get wallet_list_title => "모네로 월렛"; + @override + String get authentication => "입증"; + @override + String get amount => "양: "; + @override + String get node_address => "노드 주소"; + @override + String get settings_change_language => "언어 변경"; + @override + String get clear => "명확한"; + @override + String get settings_change_pin => "PIN 변경"; + @override + String get trades => "거래"; + @override + String get trade_state_btc_sent => "보냄"; + @override + String get address_book => "주소록"; + @override + String get enter_your_pin => "PIN을 입력하십시오"; + @override + String get wallet_list_restore_wallet => "월렛 복원"; + @override + String get restore_title_from_seed => "종자에서 복원"; + @override + String get restore_description_from_backup => "백업 파일에서 전체 Cake Wallet 앱을 복원 할 수 있습니다."; + @override + String get send_monero_address => "모네로 주소"; + @override + String get error_text_node_port => "노드 포트는 0에서 65535 사이의 숫자 만 포함 할 수 있습니다"; + @override + String get digit_pin => "숫자 PIN"; + @override + String get first_wallet_text => "멋진 지갑\n에 대한 Monero"; + @override + String get settings_trades => "거래"; + @override + String get account => "계정"; + @override + String change_language_to(String language) => "언어를로 변경 ${language}?"; + @override + String change_current_node(String node) => "현재 노드를 다음으로 변경 하시겠습니까 ${node}?"; + @override + String trade_id(String id) => "무역 ID:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "로딩 ${wallet_name} 지갑"; + @override + String router_no_route(String name) => "에 정의 된 경로가 없습니다 ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "무역 ${tradeId} 의 ${title} 찾을 수 없습니다."; + @override + String transaction_details_copied(String title) => "${title} 클립 보드에 복사"; + @override + String trade_details_copied(String title) => "${title} 클립 보드에 복사"; + @override + String powered_by(String title) => "에 의해 구동 ${title}"; + @override + String send_priority(String transactionPriority) => "현재 수수료는 ${transactionPriority} 우선 순위.\n거래 우선 순위는 설정에서 조정할 수 있습니다"; + @override + String trade_for_not_created(String title) => "거래 ${title} 생성되지 않습니다."; + @override + String trade_is_powered_by(String provider) => "이 거래는 ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + @override + String max_value(String value, String currency) => "맥스: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "최소: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "인증 실패. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} 남은 블록"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "확인을 누르면 전송됩니다 ${fetchingLabel} ${from} 지갑에서 ${walletName} 위에 표시된 주소로. 또는 외부 지갑에서 위의 주소 / QR 코드로 보낼 수 있습니다.\n\n확인을 눌러 계속하거나 금액을 변경하려면 돌아가십시오.\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "보내주세요 ${fetchingLabel} ${from} 위에 표시된 주소로.\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "커밋 거래\n양: ${amount}\n보수: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "제거하지 못했습니다 ${wallet_name} 지갑. ${error}"; + @override + String copied_key_to_clipboard(String key) => "복사 ${key} 클립 보드로"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "불러 오지 못했습니다 ${wallet_name} 지갑. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "풀이 ${wallet_name} 지갑"; +} + +class $pt extends S { + const $pt(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "Cancelar"; + @override + String get trade_state_unpaid => "Não remunerado"; + @override + String get authenticated => "Autenticado"; + @override + String get new_subaddress_create => "Crio"; + @override + String get restore_view_key_private => "Tecla Ver (privado)"; + @override + String get transaction_priority_fast => "Rápido"; + @override + String get error_text_amount => "O valor pode conter apenas números"; + @override + String get hidden_balance => "Saldo Oculto"; + @override + String get transaction_sent => "Transação enviada!"; + @override + String get password => "Senha"; + @override + String get you_will_get => "Você vai ter"; + @override + String get restore_description_from_seed => "Restaure sua carteira a partir das 25 palavras\noou código de combinação de 13 palavras"; + @override + String get trade_state_underpaid => "Mal pago"; + @override + String get welcome => "BEM-VINDO\nAO CAKE WALLET"; + @override + String get share_address => "Compartilhar endereço"; + @override + String get please_try_to_connect_to_another_node => "Por favor, tente conectar-se a outro nó"; + @override + String get trade_state_traded => "Negociados"; + @override + String get edit => "Editar"; + @override + String get spend_key_public => "Chave de gastos (público)"; + @override + String get sync_status_syncronizing => "SINCRONIZANDO"; + @override + String get received => "Recebido"; + @override + String get auth_store_banned_minutes => " minutes"; + @override + String get restore_title_from_seed_keys => "Restaurar a partir de sementes / chaves"; + @override + String get settings_none => "Nenhum"; + @override + String get wallet_list_create_new_wallet => "Criar nova carteira"; + @override + String get seed_alert_third_text => "Você também pode ver a semente novamente\nno "; + @override + String get setup_pin => "PIN de configuração"; + @override + String get transaction_details_recipient_address => "Endereço do destinatário"; + @override + String get receive_amount => "Montante"; + @override + String get settings_allow_biometrical_authentication => "Permitir autenticação biométrica"; + @override + String get view_key_private => "Tecla Ver (privado)"; + @override + String get restore_next => "Próximo"; + @override + String get trade_state_trading => "Negociação"; + @override + String get error_text_address => "O endereço da carteira deve corresponder ao tipo\nde criptomoeda"; + @override + String get send_your_wallet => "Enviar Monero"; + @override + String get remove_node_message => "Tem certeza de que deseja remover o nó selecionado?"; + @override + String get error_text_account_name => "O nome da conta pode conter apenas letras, números\ne deve ter entre 1 e 15 caracteres"; + @override + String get reconnection => "Reconexão"; + @override + String get available_balance => "Saldo disponível"; + @override + String get restore_title_from_keys => "Restaurar a partir de chaves"; + @override + String get wallet_keys => "Chaves da carteira"; + @override + String get reconnect => "Reconectar"; + @override + String get reconnect_alert_text => "Você tem certeza de reconectar?"; + @override + String get send_error_minimum_value => "O valor mínimo da quantia é 0,01"; + @override + String get copy_address => "Copiar endereço"; + @override + String get error_text_subaddress_name => "O nome do sub-endereço não pode conter ` , ' \" símbolos\ne deve ter entre 1 e 20 caracteres"; + @override + String get settings_all => "TODOS"; + @override + String get nodes_list_reset_to_default_message => "Tem certeza de que deseja redefinir as configurações para o padrão?"; + @override + String get receive => "Receber"; + @override + String get amount_is_guaranteed => "O valor do recebimento é garantido"; + @override + String get auth_store_banned_for => "Banido por"; + @override + String get trade_details_pair => "Par"; + @override + String get confirm => "Confirme"; + @override + String get send_xmr => "Mandar XMR"; + @override + String get settings_display_balance_as => "Exibir saldo como"; + @override + String get trade_details_provider => "Fornecedor"; + @override + String get you_will_send => "Você enviará"; + @override + String get create_new => "Crie um novo"; + @override + String get trade_details_fetching => "Buscando"; + @override + String get seed_alert_menu => "cardápio."; + @override + String get confirm_sending => "Confirme o envio"; + @override + String get settings_title => "Configurações"; + @override + String get address_book_menu => "Livro de endereços"; + @override + String get wallet_restoration_store_incorrect_seed_length => "Comprimento de semente incorreto"; + @override + String get contact => "Contato"; + @override + String get auth_store_incorrect_password => "PIN incorreto"; + @override + String get transaction_priority_slow => "Lento"; + @override + String get add => "Adicionar"; + @override + String get remove_node => "Remover nó"; + @override + String get trade_state_paid_unconfirmed => "Pago não confirmado"; + @override + String get please_select => "Por favor selecione:"; + @override + String get restore_title_from_backup => "Restaurar a partir de um arquivo de backup"; + @override + String get full_balance => "Saldo total"; + @override + String get sync_status_not_connected => "NÃO CONECTADO"; + @override + String get error_text_crypto_currency => "O número de dígitos da fração\ndeve ser menor ou igual a 12"; + @override + String get error_text_contact_name => "O nome do contato não pode conter ` , ' \" símbolos\ne deve ter entre 1 e 32 caracteres"; + @override + String get restore_seed_keys_restore => "Restauração de sementes / chaves"; + @override + String get xmr_hidden => "XMR Oculto"; + @override + String get exchange => "Troca"; + @override + String get sync_status_failed_connect => "Falha na conexão com o nó"; + @override + String get send_estimated_fee => "Taxa estimada:"; + @override + String get outgoing => "Extrovertido"; + @override + String get sync_status_connected => "CONECTADO"; + @override + String get trade_state_pending => "Pendente"; + @override + String get pending => " (pendente)"; + @override + String get setup_successful => "Seu PIN foi configurado com sucesso!"; + @override + String get fetching => "Buscando"; + @override + String get settings_nodes => "Nós"; + @override + String get widgets_or => "ou"; + @override + String get remove => "Remover"; + @override + String get yesterday => "Ontem"; + @override + String get expired => "Expirado"; + @override + String get transaction_priority_regular => "Regular"; + @override + String get trade_details_created_at => "Criado em"; + @override + String get settings_wallets => "Carteiras"; + @override + String get settings_only_transactions => "Somente transações"; + @override + String get estimated => "Estimado"; + @override + String get filters => "Filtros"; + @override + String get settings_current_node => "Nó atual"; + @override + String get copy_id => "Cópia de ID"; + @override + String get please_make_selection => "Faça a seleção abaixo para\ncrie ou recupere sua carteira."; + @override + String get loading_your_wallet => "Carregando sua carteira"; + @override + String get subaddresses => "Sub-endereços"; + @override + String get trade_state_timeout => "Tempo esgotado"; + @override + String get nodes => "Nós"; + @override + String get seed_share => "Compartilhar sementes"; + @override + String get widgets_restore_from_blockheight => "Restaurar da altura do bloco"; + @override + String get new_subaddress_label_name => "Nome do rótulo"; + @override + String get trade_not_found => "Comércio não encontrado."; + @override + String get transaction_details_date => "Encontro"; + @override + String get show_seed => "Mostrar semente"; + @override + String get send_error_currency => "A moeda pode conter apenas números"; + @override + String get subaddress_title => "Lista de sub-endereços"; + @override + String get wallet_store_monero_wallet => "Carteira Monero"; + @override + String get send_creating_transaction => "Criando transação"; + @override + String get trade_state_complete => "Completo"; + @override + String get error_text_xmr => "O valor XMR não pode exceder o saldo disponível.\nTO número de dígitos da fração deve ser menor ou igual a 12"; + @override + String get xmr_available_balance => "Saldo disponível do XMR"; + @override + String get trade_state_paid => "Pago"; + @override + String get node_new => "Novo nó"; + @override + String get trade_state_created => "Criado"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "Salvei o ID comercial"; + @override + String get settings_currency => "Moeda"; + @override + String get change_language => "Mudar idioma"; + @override + String get wallet_name => "Nome da carteira"; + @override + String get error_text_payment_id => "O código de pagamento pode conter apenas de 16 a 64 caracteres em hexadecimal"; + @override + String get seed_alert_second_text => "Por favor, escreva-as apenas em\ncaso você perca ou limpe seu telefone."; + @override + String get node_reset_settings_title => "Redefinir configurações"; + @override + String get wallets => "Carteiras"; + @override + String get enter_your_pin_again => "Insira seu pin novamente"; + @override + String get transactions => "Transações"; + @override + String get amount_is_estimate => "O valor recebido é uma estimativa"; + @override + String get error_text_node_address => "Digite um endereço iPv4"; + @override + String get widgets_restore_from_date => "Restaurar a partir da data"; + @override + String get restore_recover => "Recuperar"; + @override + String get reset => "Restabelecer"; + @override + String get new_wallet => "Nova carteira"; + @override + String get settings_display_on_dashboard_list => "Exibir na lista do painel"; + @override + String get transaction_details_title => "Detalhes da transação"; + @override + String get status => "Status: "; + @override + String get restore_spend_key_private => "Chave de gastos (privado)"; + @override + String get sync_status_syncronized => "SINCRONIZADO"; + @override + String get transaction_priority_medium => "Médio"; + @override + String get transaction_details_transaction_id => "ID da transação"; + @override + String get save => "Salve"; + @override + String get login => "Entrar"; + @override + String get wallet_list_load_wallet => "Carregar carteira"; + @override + String get sending => "Enviando"; + @override + String get restore_restore_wallet => "Restaurar carteira"; + @override + String get restore_description_from_seed_keys => "Volte sua carteira a partir de sementes / chaves que você salvou em um local seguro"; + @override + String get copy => "Cópia de"; + @override + String get node_port => "Porta do nó"; + @override + String get change_currency => "Alteração de moeda"; + @override + String get transaction_details_amount => "Montante"; + @override + String get widgets_address => "Endereço"; + @override + String get contact_name => "Nome de contato"; + @override + String get exchange_result_write_down_ID => "*Copie ou anote seu ID mostrado acima."; + @override + String get exchange_result_write_down_trade_id => "Copie ou anote o ID comercial para continuar."; + @override + String get new_subaddress_title => "Novo sub-endereço"; + @override + String get change => "Mudança"; + @override + String get seed_alert_first_text => "A próxima página mostrará\nvocê é uma semente."; + @override + String get trade_state_finished => "Acabado"; + @override + String get pin_is_incorrect => "PIN incorreto"; + @override + String get trade_not_created => "Comércio não criado."; + @override + String get restore_wallet_name => "Nome da carteira"; + @override + String get widgets_seed => "Semente"; + @override + String get settings_fee_priority => "Prioridade da taxa"; + @override + String get settings_personal => "Pessoal"; + @override + String get seed_title => "Semente"; + @override + String get accounts => "Contas"; + @override + String get rescan => "Verificar novamente"; + @override + String get seed_alert_understand => "Compreendo"; + @override + String get show_keys => "Mostrar chaves"; + @override + String get error_text_fiat => "O valor do valor não pode exceder o saldo disponível.\nO número de dígitos da fração deve ser menor ou igual a 2"; + @override + String get transactions_by_date => "Transações por data"; + @override + String get use => "Usar "; + @override + String get trade_details_state => "Estado"; + @override + String get spend_key_private => "Chave de gastos (privado)"; + @override + String get settings_only_trades => "Somente negociações"; + @override + String get trade_state_to_be_created => "Para ser criado"; + @override + String get all => "TODOS"; + @override + String get xmr_full_balance => "Saldo total do XMR"; + @override + String get incoming => "Entrada"; + @override + String get trade_history_title => "Histórico comercial"; + @override + String get error_text_wallet_name => "O nome da carteira só pode conter letras, números\ne deve ter entre 1 e 15 caracteres"; + @override + String get restore_description_from_keys => "Restaure sua carteira da geração\npressionamentos de tecla salvos em suas chaves privadas"; + @override + String get sent => "Enviei"; + @override + String get view_key_public => "Tecla Ver (público)"; + @override + String get trade_details_id => "ID"; + @override + String get trade_state_confirming => "Confirmando"; + @override + String get send => "Mandar"; + @override + String get send_title => "Enviar Monero"; + @override + String get error_text_keys => "As chaves da carteira podem conter apenas 64 caracteres em hexadecimal"; + @override + String get settings_save_recipient_address => "Salvar endereço do destinatário"; + @override + String get seed_alert_settings => "configurações"; + @override + String get change_exchange_provider => "Alterar provedor de câmbio"; + @override + String get send_payment_id => "ID de pagamento (opcional)"; + @override + String get trade_details_title => "Detalhes do comércio"; + @override + String get settings_terms_and_conditions => "Termos e Condições"; + @override + String get auth_store_ban_timeout => "proibir timeout"; + @override + String get sync_status_connecting => "CONECTANDO"; + @override + String get settings_transactions => "Transações"; + @override + String get transaction_priority_fastest => "O mais rápido"; + @override + String get error => "Erro"; + @override + String get delete => "Excluir"; + @override + String get sync_status_starting_sync => "INÍCIO DE SINCRONIZAÇÃO"; + @override + String get copied_to_clipboard => "Copiado para a área de transferência"; + @override + String get offer_expires_in => "A oferta expira em: "; + @override + String get continue_text => "Continuar"; + @override + String get transaction_details_height => "Altura"; + @override + String get wallet_menu => "Menu da Carteira"; + @override + String get settings_dark_mode => "Modo escuro"; + @override + String get payment_id => "ID de pagamento: "; + @override + String get restore_address => "Endereço"; + @override + String get restore_wallet_restore_description => "Restauração de carteira"; + @override + String get today => "Hoje"; + @override + String get settings_support => "Suporte"; + @override + String get restore_wallet => "Restaurar carteira"; + @override + String get ok => "Está bem"; + @override + String get wallet_list_title => "Monero Carteira"; + @override + String get authentication => "Autenticação"; + @override + String get amount => "Montante: "; + @override + String get node_address => "Endereço do nó"; + @override + String get settings_change_language => "Mudar idioma"; + @override + String get clear => "Claro"; + @override + String get settings_change_pin => "Alterar PIN"; + @override + String get trades => "Comércios"; + @override + String get trade_state_btc_sent => "Enviei"; + @override + String get address_book => "Livro de endereços"; + @override + String get enter_your_pin => "Insira seu PIN"; + @override + String get wallet_list_restore_wallet => "Restaurar carteira"; + @override + String get restore_title_from_seed => "Restaurar a partir da semente"; + @override + String get restore_description_from_backup => "Você pode restaurar todo o aplicativo Cake Wallet em\nseu arquivo de backup"; + @override + String get send_monero_address => "Endereço Monero"; + @override + String get error_text_node_port => "A porta do nó pode conter apenas números entre 0 e 65535"; + @override + String get digit_pin => "de dois dígitos"; + @override + String get first_wallet_text => "Carteira impressionante\npara Monero"; + @override + String get settings_trades => "Comércios"; + @override + String get account => "Conta"; + @override + String change_language_to(String language) => "Alterar idioma para ${language}?"; + @override + String change_current_node(String node) => "Você tem certeza de alterar o nó atual para ${node}?"; + @override + String trade_id(String id) => "ID comercial:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "Carregando ${wallet_name} carteira"; + @override + String router_no_route(String name) => "Nenhuma rota definida para ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "Comércio${tradeId} of ${title} não encontrado."; + @override + String transaction_details_copied(String title) => "${title} Copiado para a área de transferência"; + @override + String trade_details_copied(String title) => "${title} Copiado para a área de transferência"; + @override + String powered_by(String title) => "Distribuído por ${title}"; + @override + String send_priority(String transactionPriority) => "Atualmente, a taxa está definida em ${transactionPriority} prioridade.\nA prioridade da transação pode ser ajustada nas configurações"; + @override + String trade_for_not_created(String title) => "Comércio por ${title} não é criado."; + @override + String trade_is_powered_by(String provider) => "Este comércio é alimentado por ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + @override + String max_value(String value, String currency) => "Máx: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "Mín: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "Falha na autenticação. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} Blocos restantes"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Ao pressionar confirmar, você estará enviando ${fetchingLabel} ${from} da sua carteira ${walletName} para o endereço mostrado acima. Ou você pode enviar da sua carteira externa para o endereço / código QR acima.\n\nPressione confirmar para continuar ou voltar para alterar os valores.\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "Por favor envie ${fetchingLabel} ${from} para o endereço mostrado acima.\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "Confirmar transação\nMontante: ${amount}\nTaxa: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "Falha ao remover ${wallet_name} carteira. ${error}"; + @override + String copied_key_to_clipboard(String key) => "Copiado ${key} para a área de transferência"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "Falha ao carregar ${wallet_name} carteira. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "Removendo ${wallet_name} carteira"; +} + +class $ja extends S { + const $ja(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "キャンセル"; + @override + String get trade_state_unpaid => "未払い"; + @override + String get authenticated => "認証済み"; + @override + String get new_subaddress_create => "作成する"; + @override + String get restore_view_key_private => "ビューキー (プライベート)"; + @override + String get transaction_priority_fast => "速い"; + @override + String get error_text_amount => "金額には数字のみを含めることができます"; + @override + String get hidden_balance => "隠れたバランス"; + @override + String get transaction_sent => "トランザクションが送信されました!"; + @override + String get password => "パスワード"; + @override + String get you_will_get => "あなたが取得します"; + @override + String get restore_description_from_seed => "25ワードからウォレットを復元します\nまたは13ワードの組み合わせコード"; + @override + String get trade_state_underpaid => "支払不足"; + @override + String get welcome => "ようこそ\nに CAKE WALLET"; + @override + String get share_address => "住所を共有する"; + @override + String get please_try_to_connect_to_another_node => "別のノードに接続してみてください"; + @override + String get trade_state_traded => "取引済み"; + @override + String get edit => "編集"; + @override + String get spend_key_public => "キーを使う (パブリック)"; + @override + String get sync_status_syncronizing => "同期"; + @override + String get received => "受け取った"; + @override + String get auth_store_banned_minutes => " 数分"; + @override + String get restore_title_from_seed_keys => "シード/キーから復元"; + @override + String get settings_none => "なし"; + @override + String get wallet_list_create_new_wallet => "新しいウォレットを作成"; + @override + String get seed_alert_third_text => "また、種子を見ることができます\nの中に "; + @override + String get setup_pin => "PINのセットアップ"; + @override + String get transaction_details_recipient_address => "受取人の住所"; + @override + String get receive_amount => "量"; + @override + String get settings_allow_biometrical_authentication => "生体認証を許可する"; + @override + String get view_key_private => "ビューキー (プライベート)"; + @override + String get restore_next => "次"; + @override + String get trade_state_trading => "トレーディング"; + @override + String get error_text_address => "ウォレットアドレスは、\n暗号通貨"; + @override + String get send_your_wallet => "あなたの財布"; + @override + String get remove_node_message => "選択したノードを削除してもよろしいですか?"; + @override + String get error_text_account_name => "アカウント名には文字のみを含めることができます \n1〜15文字である必要があります"; + @override + String get reconnection => "再接続"; + @override + String get available_balance => "利用可能残高"; + @override + String get restore_title_from_keys => "キーから復元する"; + @override + String get wallet_keys => "ウォレットキー"; + @override + String get reconnect => "再接続"; + @override + String get reconnect_alert_text => "再接続しますか?"; + @override + String get send_error_minimum_value => "金額の最小値は0.01です"; + @override + String get copy_address => "住所をコピー"; + @override + String get error_text_subaddress_name => "サブアドレス名に含めることはできません` , ' \" シンボル\n1〜20文字の長さである必要があります"; + @override + String get settings_all => "すべて"; + @override + String get nodes_list_reset_to_default_message => "設定をデフォルトにリセットしてもよろしいですか?"; + @override + String get receive => "受け取る"; + @override + String get amount_is_guaranteed => "受信金額は保証されています"; + @override + String get auth_store_banned_for => "禁止されています "; + @override + String get trade_details_pair => "ペア"; + @override + String get confirm => "確認する"; + @override + String get send_xmr => "送る XMR"; + @override + String get settings_display_balance_as => "残高を表示"; + @override + String get trade_details_provider => "プロバイダー"; + @override + String get you_will_send => "送ります"; + @override + String get create_new => "新しく作る"; + @override + String get trade_details_fetching => "フェッチング"; + @override + String get seed_alert_menu => " メニュー."; + @override + String get confirm_sending => "送信を確認"; + @override + String get settings_title => "設定"; + @override + String get address_book_menu => "住所録"; + @override + String get wallet_restoration_store_incorrect_seed_length => "誤ったシード長s"; + @override + String get contact => "接触"; + @override + String get auth_store_incorrect_password => "間違ったPIN"; + @override + String get transaction_priority_slow => "スロー"; + @override + String get add => "加える"; + @override + String get remove_node => "ノードを削除"; + @override + String get trade_state_paid_unconfirmed => "未確認の支払い"; + @override + String get please_select => "選んでください:"; + @override + String get restore_title_from_backup => "バックアップファイルから復元する"; + @override + String get full_balance => "フルバランス"; + @override + String get sync_status_not_connected => "接続されていません"; + @override + String get error_text_crypto_currency => "小数桁数\n12以下でなければなりません"; + @override + String get error_text_contact_name => "連絡先名に含めることはできません ` , ' \" シンボル\n長さは1〜32文字でなければなりません"; + @override + String get restore_seed_keys_restore => "シード/キーの復元"; + @override + String get xmr_hidden => "XMR非表示"; + @override + String get exchange => "交換する"; + @override + String get sync_status_failed_connect => "ノードへの接続に失敗しました"; + @override + String get send_estimated_fee => "見積手数料:"; + @override + String get outgoing => "発信"; + @override + String get sync_status_connected => "接続済み"; + @override + String get trade_state_pending => "保留中"; + @override + String get pending => " (保留中)"; + @override + String get setup_successful => "PINは正常に設定されました!"; + @override + String get fetching => "フェッチング"; + @override + String get settings_nodes => "ノード"; + @override + String get widgets_or => "または"; + @override + String get remove => "削除する"; + @override + String get yesterday => "昨日"; + @override + String get expired => "期限切れ"; + @override + String get transaction_priority_regular => "レギュラー"; + @override + String get trade_details_created_at => "で作成"; + @override + String get settings_wallets => "財布"; + @override + String get settings_only_transactions => "トランザクションのみ"; + @override + String get estimated => "推定"; + @override + String get filters => "フィルター"; + @override + String get settings_current_node => "現在のノード"; + @override + String get copy_id => "IDをコピー"; + @override + String get please_make_selection => "以下を選択してください\nウォレットを作成または回復する."; + @override + String get loading_your_wallet => "ウォレットをロードしています"; + @override + String get subaddresses => "サブアドレス"; + @override + String get trade_state_timeout => "タイムアウト"; + @override + String get nodes => "ノード"; + @override + String get seed_share => "シードを共有する"; + @override + String get widgets_restore_from_blockheight => "ブロックの高さから復元"; + @override + String get new_subaddress_label_name => "ラベル名"; + @override + String get trade_not_found => "取引が見つかりません"; + @override + String get transaction_details_date => "日付"; + @override + String get show_seed => "シードを表示"; + @override + String get send_error_currency => "通貨には数字のみを含めることができます"; + @override + String get subaddress_title => "サブアドレス一覧"; + @override + String get wallet_store_monero_wallet => "Monero 財布"; + @override + String get send_creating_transaction => "トランザクションを作成する"; + @override + String get trade_state_complete => "コンプリート"; + @override + String get error_text_xmr => "XMR値は利用可能な残高を超えることはできません.\n小数桁数は12以下でなければなりません"; + @override + String get xmr_available_balance => "XMR利用可能残高"; + @override + String get trade_state_paid => "有料"; + @override + String get node_new => "新しいノード"; + @override + String get trade_state_created => "作成した"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "取引IDを保存しました"; + @override + String get settings_currency => "通貨"; + @override + String get change_language => "言語を変えてください"; + @override + String get wallet_name => "ウォレット名"; + @override + String get error_text_payment_id => "支払いIDには、16進数で16〜64文字しか含めることができません"; + @override + String get seed_alert_second_text => "これらを書き留めてください \n電話を紛失した場合や拭いた場合."; + @override + String get node_reset_settings_title => "設定をリセット"; + @override + String get wallets => "財布"; + @override + String get enter_your_pin_again => "ピンをもう一度入力してください"; + @override + String get transactions => "取引"; + @override + String get amount_is_estimate => "受け取り金額は見積もりです"; + @override + String get error_text_node_address => "iPv4アドレスを入力してください"; + @override + String get widgets_restore_from_date => "日付から復元"; + @override + String get restore_recover => "回復します"; + @override + String get reset => "リセットする"; + @override + String get new_wallet => "新しいウォレット"; + @override + String get settings_display_on_dashboard_list => "ダッシュボードリストに表示"; + @override + String get transaction_details_title => "取引の詳細"; + @override + String get status => "状態: "; + @override + String get restore_spend_key_private => "キーを使う (プライベート)"; + @override + String get sync_status_syncronized => "同期された"; + @override + String get transaction_priority_medium => "中"; + @override + String get transaction_details_transaction_id => "トランザクションID"; + @override + String get save => "セーブ"; + @override + String get login => "ログイン"; + @override + String get wallet_list_load_wallet => "ウォレットをロード"; + @override + String get sending => "送信"; + @override + String get restore_restore_wallet => "ウォレットを復元"; + @override + String get restore_description_from_seed_keys => "安全な場所に保存したシード/キーから財布を取り戻す"; + @override + String get copy => "コピー"; + @override + String get node_port => "ノードポート"; + @override + String get change_currency => "通貨を変更する"; + @override + String get transaction_details_amount => "量"; + @override + String get widgets_address => "住所"; + @override + String get contact_name => "連絡先"; + @override + String get exchange_result_write_down_ID => "*上記のIDをコピーまたは書き留めてください."; + @override + String get exchange_result_write_down_trade_id => "続行するには、取引IDをコピーまたは書き留めてください."; + @override + String get new_subaddress_title => "新しいサブアドレス"; + @override + String get change => "変化する"; + @override + String get seed_alert_first_text => "次のページが表示されます\nあなたは種."; + @override + String get trade_state_finished => "完成した"; + @override + String get pin_is_incorrect => "PINが間違っています"; + @override + String get trade_not_created => "作成されていない取引"; + @override + String get restore_wallet_name => "ウォレット名"; + @override + String get widgets_seed => "シード"; + @override + String get settings_fee_priority => "料金優先"; + @override + String get settings_personal => "パーソナル"; + @override + String get seed_title => "シード"; + @override + String get accounts => "アカウント"; + @override + String get rescan => "再スキャン"; + @override + String get seed_alert_understand => "わかります"; + @override + String get show_keys => "キーを表示"; + @override + String get error_text_fiat => "金額は利用可能な残高を超えることはできません.\n小数桁の数は2以下でなければなりません"; + @override + String get transactions_by_date => "日付ごとの取引"; + @override + String get use => "つかいます "; + @override + String get trade_details_state => "状態"; + @override + String get spend_key_private => "キーを使う (プライベート)"; + @override + String get settings_only_trades => "取引のみ"; + @override + String get trade_state_to_be_created => "作成される"; + @override + String get all => "すべて"; + @override + String get xmr_full_balance => "XMRフルバランス"; + @override + String get incoming => "着信"; + @override + String get trade_history_title => "取引履歴"; + @override + String get error_text_wallet_name => "ウォレット名には文字のみを含めることができます\n1〜15文字である必要があります"; + @override + String get restore_description_from_keys => "生成されたウォレットを復元します\n秘密鍵から保存されたキーストローク"; + @override + String get sent => "送信済み"; + @override + String get view_key_public => "ビューキー (パブリック)"; + @override + String get trade_details_id => "ID"; + @override + String get trade_state_confirming => "確認中"; + @override + String get send => "送る"; + @override + String get send_title => "Moneroを送信"; + @override + String get error_text_keys => "ウォレットキーには、16進数で64文字しか含めることができません"; + @override + String get settings_save_recipient_address => "受信者のアドレスを保存"; + @override + String get seed_alert_settings => "設定"; + @override + String get change_exchange_provider => "Exchangeプロバイダーの変更"; + @override + String get send_payment_id => "支払いID (オプショナル)"; + @override + String get trade_details_title => "取引の詳細"; + @override + String get settings_terms_and_conditions => "規約と条件"; + @override + String get auth_store_ban_timeout => "禁止タイムアウト"; + @override + String get sync_status_connecting => "接続中"; + @override + String get settings_transactions => "取引"; + @override + String get transaction_priority_fastest => "最速"; + @override + String get error => "エラー"; + @override + String get delete => "削除する"; + @override + String get sync_status_starting_sync => "同期の開始"; + @override + String get copied_to_clipboard => "クリップボードにコピーしました"; + @override + String get offer_expires_in => "で有効期限が切れます: "; + @override + String get continue_text => "持続する"; + @override + String get transaction_details_height => "高さ"; + @override + String get wallet_menu => "ウォレットメニュー"; + @override + String get settings_dark_mode => "ダークモード"; + @override + String get payment_id => "支払いID: "; + @override + String get restore_address => "住所"; + @override + String get restore_wallet_restore_description => "ウォレットの復元"; + @override + String get today => "今日"; + @override + String get settings_support => "サポート"; + @override + String get restore_wallet => "ウォレットを復元する"; + @override + String get ok => "OK"; + @override + String get wallet_list_title => "Monero 財布"; + @override + String get authentication => "認証"; + @override + String get amount => "量: "; + @override + String get node_address => "ノードアドレス"; + @override + String get settings_change_language => "言語を変えてください"; + @override + String get clear => "クリア"; + @override + String get settings_change_pin => "PINを変更"; + @override + String get trades => "取引"; + @override + String get trade_state_btc_sent => "送った"; + @override + String get address_book => "住所録"; + @override + String get enter_your_pin => "PINを入力してください"; + @override + String get wallet_list_restore_wallet => "ウォレットを復元"; + @override + String get restore_title_from_seed => "シードから復元"; + @override + String get restore_description_from_backup => "Cake Walletアプリ全体を復元できます\nバックアップファイル"; + @override + String get send_monero_address => "Monero 住所"; + @override + String get error_text_node_port => "ノードポートには、0〜65535の数字のみを含めることができます"; + @override + String get digit_pin => "桁ピン"; + @override + String get first_wallet_text => "素晴らしい財布\nために Monero"; + @override + String get settings_trades => "取引"; + @override + String get account => "アカウント"; + @override + String change_language_to(String language) => "言語を変更 ${language}?"; + @override + String change_current_node(String node) => "現在のノードを変更してよろしいですか ${node}?"; + @override + String trade_id(String id) => "取引ID:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "読み込み中 ${wallet_name} 財布"; + @override + String router_no_route(String name) => "ルートが定義されていません ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "トレード ${tradeId} of ${title} 見つかりません"; + @override + String transaction_details_copied(String title) => "${title} クリップボードにコピーしました"; + @override + String trade_details_copied(String title) => "${title} クリップボードにコピーしました"; + @override + String powered_by(String title) => "搭載 ${title}"; + @override + String send_priority(String transactionPriority) => "現在、料金は ${transactionPriority} 優先度.\nトランザクションの優先度は設定で調整できます"; + @override + String trade_for_not_created(String title) => "取引 ${title} 作成されません"; + @override + String trade_is_powered_by(String provider) => "この取引は ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + @override + String max_value(String value, String currency) => "マックス: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "分: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "認証失敗. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} 残りのブロック"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "確認を押すと、送信されます ${fetchingLabel} ${from} と呼ばれるあなたの財布から ${walletName} 上記のアドレスへ. または、外部ウォレットから上記のアドレス/ QRコードに送信できます.\n\n確認を押して続行するか、戻って金額を変更してください.\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "送信してください ${fetchingLabel} ${from} 上記のアドレスへ.\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "トランザクションをコミット\n量: ${amount}\n費用: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "削除できませんでした ${wallet_name} 財布. ${error}"; + @override + String copied_key_to_clipboard(String key) => "コピー済み ${key} クリップボードへ"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "読み込みに失敗しました ${wallet_name} 財布. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "取りはずし ${wallet_name} 財布"; +} + +class $en extends S { + const $en(); +} + +class $pl extends S { + const $pl(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "Anulować"; + @override + String get trade_state_unpaid => "Nie zapłacony"; + @override + String get authenticated => "Zalegalizowany"; + @override + String get new_subaddress_create => "Stwórz"; + @override + String get restore_view_key_private => "Wyświetl klucz (prywatny)"; + @override + String get transaction_priority_fast => "Szybki"; + @override + String get error_text_amount => "Kwota może zawierać tylko liczby"; + @override + String get hidden_balance => "Ukryta równowaga"; + @override + String get transaction_sent => "Transakcja wysłana!"; + @override + String get password => "Hasło"; + @override + String get you_will_get => "Dostaniesz"; + @override + String get restore_description_from_seed => "Przywróć swój portfel z 25 słów\nlub 13-słowny kod kombinacji"; + @override + String get trade_state_underpaid => "Niedopłacone"; + @override + String get welcome => "WITAMY\nW CAKE WALLET"; + @override + String get share_address => "Udostępnij adres"; + @override + String get please_try_to_connect_to_another_node => "Spróbuj połączyć się z innym węzłem"; + @override + String get trade_state_traded => "Handlowane"; + @override + String get edit => "Edytować"; + @override + String get spend_key_public => "Wydaj klucz (publiczny)"; + @override + String get sync_status_syncronizing => "SYNCHRONIZACJA"; + @override + String get received => "Odebrane"; + @override + String get auth_store_banned_minutes => " minuty"; + @override + String get restore_title_from_seed_keys => "Przywróć z nasion / kluczy"; + @override + String get settings_none => "Żaden"; + @override + String get wallet_list_create_new_wallet => "Utwórz nowy portfel"; + @override + String get seed_alert_third_text => "Możesz także ponownie zobaczyć ziarno\nw "; + @override + String get setup_pin => "Ustaw PIN"; + @override + String get transaction_details_recipient_address => "Adres odbiorcy"; + @override + String get receive_amount => "Ilość"; + @override + String get settings_allow_biometrical_authentication => "Zezwalaj na uwierzytelnianie biometryczne"; + @override + String get view_key_private => "Wyświetl klucz (prywatny)"; + @override + String get restore_next => "Kolejny"; + @override + String get trade_state_trading => "Handlowy"; + @override + String get error_text_address => "Wallet address must correspond to the type\nof cryptocurrency"; + @override + String get send_your_wallet => "Twój portfel"; + @override + String get remove_node_message => "Czy na pewno chcesz usunąć wybrany węzeł?"; + @override + String get error_text_account_name => "Nazwa konta może zawierać tylko litery, cyfry\ni musi mieć od 1 do 15 znaków"; + @override + String get reconnection => "Ponowne połączenie"; + @override + String get available_balance => "Dostępne saldo"; + @override + String get restore_title_from_keys => "Przywróć z kluczy"; + @override + String get wallet_keys => "Klucze portfela"; + @override + String get reconnect => "Na nowo połączyć"; + @override + String get reconnect_alert_text => "Czy na pewno ponownie się połączysz?"; + @override + String get send_error_minimum_value => "Minimalna wartość kwoty to 0,01"; + @override + String get copy_address => "Skopiuj adress"; + @override + String get error_text_subaddress_name => "Nazwa podadresu nie może zawierać ` , ' \" symbolika\ni musi mieć od 1 do 20 znaków"; + @override + String get settings_all => "Cały"; + @override + String get nodes_list_reset_to_default_message => "Czy na pewno chcesz przywrócić ustawienia domyślne?"; + @override + String get receive => "Otrzymać"; + @override + String get amount_is_guaranteed => "Otrzymana kwota jest gwarantowana"; + @override + String get auth_store_banned_for => "Bzbanowany za "; + @override + String get trade_details_pair => "Para"; + @override + String get confirm => "Potwierdzać"; + @override + String get send_xmr => "Wysłać XMR"; + @override + String get settings_display_balance_as => "Wyświetl saldo jako"; + @override + String get trade_details_provider => "Dostawca"; + @override + String get you_will_send => "Wyślesz"; + @override + String get create_new => "Tworzyć nowe"; + @override + String get trade_details_fetching => "Ujmujący"; + @override + String get seed_alert_menu => " menu."; + @override + String get confirm_sending => "Potwierdź wysłanie"; + @override + String get settings_title => "Ustawienia"; + @override + String get address_book_menu => "Książka adresowa"; + @override + String get wallet_restoration_store_incorrect_seed_length => "Nieprawidłowa długość nasion"; + @override + String get contact => "Kontakt"; + @override + String get auth_store_incorrect_password => "Niepoprawny PIN"; + @override + String get transaction_priority_slow => "Powolny"; + @override + String get add => "Dodaj"; + @override + String get remove_node => "Usuń węzeł"; + @override + String get trade_state_paid_unconfirmed => "Płatne niepotwierdzone"; + @override + String get please_select => "Proszę wybrać:"; + @override + String get restore_title_from_backup => "Przywróć z pliku kopii zapasowej"; + @override + String get full_balance => "Pełna równowaga"; + @override + String get sync_status_not_connected => "NIE POŁĄCZONY"; + @override + String get error_text_crypto_currency => "Liczba cyfr ułamkowych\nmusi być mniejsza lub równa 12"; + @override + String get error_text_contact_name => "Nazwa kontaktu nie może zawierać` , ' \" symbolika\ni musi mieć od 1 do 32 znaków "; + @override + String get restore_seed_keys_restore => "Przywracanie nasion / kluczy"; + @override + String get xmr_hidden => "XMR Ukryty"; + @override + String get exchange => "Wymieniać się"; + @override + String get sync_status_failed_connect => "NIE MOŻNA PODŁĄCZYĆ DO WĘZŁA"; + @override + String get send_estimated_fee => "Szacowana opłata:"; + @override + String get outgoing => "Towarzyski"; + @override + String get sync_status_connected => "POŁĄCZONY"; + @override + String get trade_state_pending => "W oczekiwaniu"; + @override + String get pending => " (w oczekiwaniu)"; + @override + String get setup_successful => "Twój kod PIN został pomyślnie skonfigurowany!"; + @override + String get fetching => "Ujmujący"; + @override + String get settings_nodes => "Węzły"; + @override + String get widgets_or => "lub"; + @override + String get remove => "Usunąć"; + @override + String get yesterday => "Wczoraj"; + @override + String get expired => "Przedawniony"; + @override + String get transaction_priority_regular => "Regularny"; + @override + String get trade_details_created_at => "Utworzono w"; + @override + String get settings_wallets => "Portfele"; + @override + String get settings_only_transactions => "Tylko transakcje"; + @override + String get estimated => "Oszacowano"; + @override + String get filters => "Filtry"; + @override + String get settings_current_node => "Bieżący węzeł"; + @override + String get copy_id => "ID kopii"; + @override + String get please_make_selection => "Wybierz poniżej, aby\ncutwórz lub odzyskaj swój portfel."; + @override + String get loading_your_wallet => "Ładowanie portfela"; + @override + String get subaddresses => "Podadresy"; + @override + String get trade_state_timeout => "Koniec czasu"; + @override + String get nodes => "Węzły"; + @override + String get seed_share => "Udostępnij ziarno"; + @override + String get widgets_restore_from_blockheight => "Przywróć z wysokości bloku"; + @override + String get new_subaddress_label_name => "Nazwa etykiety"; + @override + String get trade_not_found => "Nie znaleziono handlu."; + @override + String get transaction_details_date => "Data"; + @override + String get show_seed => "Pokaż nasiona"; + @override + String get send_error_currency => "Waluta może zawierać tylko cyfry"; + @override + String get subaddress_title => "Lista podadresów"; + @override + String get wallet_store_monero_wallet => "Portfel Monero"; + @override + String get send_creating_transaction => "Tworzenie transakcji"; + @override + String get trade_state_complete => "Kompletny"; + @override + String get error_text_xmr => "Wartość XMR nie może przekraczać dostępnego salda.\nLiczba cyfr ułamkowych musi być mniejsza lub równa 12"; + @override + String get xmr_available_balance => "XMR Dostępne saldo"; + @override + String get trade_state_paid => "Płatny"; + @override + String get node_new => "Nowy węzeł"; + @override + String get trade_state_created => "Stworzony"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "Zapisałem ID"; + @override + String get settings_currency => "Waluta"; + @override + String get change_language => "Zmień język"; + @override + String get wallet_name => "Nazwa portfela"; + @override + String get error_text_payment_id => "ID może zawierać od 16 do 64 znaków w formacie szesnastkowym"; + @override + String get seed_alert_second_text => "Zapisz je na wszelki wypadek\nzgubisz lub wyczyścisz telefon."; + @override + String get node_reset_settings_title => "Resetowanie ustawień"; + @override + String get wallets => "Portfele"; + @override + String get enter_your_pin_again => "Wprowadź ponownie swój kod PIN"; + @override + String get transactions => "Transakcje"; + @override + String get amount_is_estimate => "Otrzymana kwota jest wartością szacunkową"; + @override + String get error_text_node_address => "Wpisz adres iPv4"; + @override + String get widgets_restore_from_date => "Przywróć od daty"; + @override + String get restore_recover => "Wyzdrowieć"; + @override + String get reset => "Nastawić"; + @override + String get new_wallet => "Nowy portfel"; + @override + String get settings_display_on_dashboard_list => "Wyświetl na liście kokpitu"; + @override + String get transaction_details_title => "Szczegóły transakcji"; + @override + String get status => "Status: "; + @override + String get restore_spend_key_private => "Wydaj klucz (prywatny)"; + @override + String get sync_status_syncronized => "SYNCHRONIZOWANY"; + @override + String get transaction_priority_medium => "Średni"; + @override + String get transaction_details_transaction_id => "Transakcja ID"; + @override + String get save => "Zapisać"; + @override + String get login => "Zaloguj Się"; + @override + String get wallet_list_load_wallet => "Załaduj portfel"; + @override + String get sending => "Wysyłanie"; + @override + String get restore_restore_wallet => "Przywróć portfel"; + @override + String get restore_description_from_seed_keys => "Odzyskaj swój portfel z nasion / kluczy, które zapisałeś w bezpiecznym miejscu"; + @override + String get copy => "Kopiuj"; + @override + String get node_port => "Port węzła"; + @override + String get change_currency => "Change Currency"; + @override + String get transaction_details_amount => "Ilość"; + @override + String get widgets_address => "Adres"; + @override + String get contact_name => "Nazwa Kontaktu"; + @override + String get exchange_result_write_down_ID => "*Skopiuj lub zanotuj swój identyfikator pokazany powyżej."; + @override + String get exchange_result_write_down_trade_id => "Skopiuj lub zanotuj identyfikator transakcji, aby kontynuować."; + @override + String get new_subaddress_title => "Nowy podadres"; + @override + String get change => "Zmiana"; + @override + String get seed_alert_first_text => "Pojawi się następna strona\nziarno."; + @override + String get trade_state_finished => "Skończone"; + @override + String get pin_is_incorrect => "PPIN jest niepoprawny"; + @override + String get trade_not_created => "Handel nie utworzony."; + @override + String get restore_wallet_name => "Nazwa portfela"; + @override + String get widgets_seed => "Ziarno"; + @override + String get settings_fee_priority => "Priorytet opłaty"; + @override + String get settings_personal => "Osobisty"; + @override + String get seed_title => "Ziarno"; + @override + String get accounts => "Konta"; + @override + String get rescan => "Skanuj ponownie"; + @override + String get seed_alert_understand => "rozumiem"; + @override + String get show_keys => "Pokaż klucze"; + @override + String get error_text_fiat => "Wartość kwoty nie może przekroczyć dostępnego salda.\nLiczba cyfr ułamkowych musi być mniejsza lub równa 2"; + @override + String get transactions_by_date => "Transakcje według daty"; + @override + String get use => "Posługiwać się "; + @override + String get trade_details_state => "Stan"; + @override + String get spend_key_private => "Wydaj klucz (prywatny)"; + @override + String get settings_only_trades => "Tylko transakcje"; + @override + String get trade_state_to_be_created => "Zostać stworzonym"; + @override + String get all => "WSZYSTKO"; + @override + String get xmr_full_balance => "XMR Pełna równowaga"; + @override + String get incoming => "Przychodzące"; + @override + String get trade_history_title => "Historia handlu"; + @override + String get error_text_wallet_name => "Nazwa portfela może zawierać tylko litery i cyfry\ni musi mieć od 1 do 15 znaków"; + @override + String get restore_description_from_keys => "Przywróć swój portfel z wygenerowanego\nnaciśnięcia klawiszy zapisane z kluczy prywatnych"; + @override + String get sent => "Wysłano"; + @override + String get view_key_public => "Wyświetl klucz (publiczny)"; + @override + String get trade_details_id => "ID"; + @override + String get trade_state_confirming => "Potwierdzam"; + @override + String get send => "Wysłać"; + @override + String get send_title => "Wyślij Monero"; + @override + String get error_text_keys => "Klucze portfela mogą zawierać tylko 64 znaki w systemie szesnastkowym"; + @override + String get settings_save_recipient_address => "Zapisz adres odbiorcy"; + @override + String get seed_alert_settings => "ustawienia"; + @override + String get change_exchange_provider => "Zmień dostawcę programu Exchange"; + @override + String get send_payment_id => "Identyfikator płatności (opcjonalny)"; + @override + String get trade_details_title => "Szczegóły handlu"; + @override + String get settings_terms_and_conditions => "Zasady i warunki"; + @override + String get auth_store_ban_timeout => "przekroczenie limitu czasu"; + @override + String get sync_status_connecting => "ZŁĄCZONY"; + @override + String get settings_transactions => "Transakcje"; + @override + String get transaction_priority_fastest => "Najszybszy"; + @override + String get error => "Błąd"; + @override + String get delete => "Kasować"; + @override + String get sync_status_starting_sync => "ROZPOCZĘCIE SYNCHRONIZACJI"; + @override + String get copied_to_clipboard => "Skopiowane do schowka"; + @override + String get offer_expires_in => "Oferta wygasa za "; + @override + String get continue_text => "Dalej"; + @override + String get transaction_details_height => "Wysokość"; + @override + String get wallet_menu => "Menu portfela"; + @override + String get settings_dark_mode => "Tryb ciemny"; + @override + String get payment_id => "Płatności ID: "; + @override + String get restore_address => "Adres"; + @override + String get restore_wallet_restore_description => "Opis przywracania portfela"; + @override + String get today => "Dzisiaj"; + @override + String get settings_support => "Wsparcie"; + @override + String get restore_wallet => "Przywróć portfel"; + @override + String get ok => "Dobrze"; + @override + String get wallet_list_title => "Portfel Monero"; + @override + String get authentication => "Poświadczenie"; + @override + String get amount => "Ilość: "; + @override + String get node_address => "Adres węzła"; + @override + String get settings_change_language => "Zmień język"; + @override + String get clear => "Jasny"; + @override + String get settings_change_pin => "Zmień PIN"; + @override + String get trades => "Transakcje"; + @override + String get trade_state_btc_sent => "Wysłane"; + @override + String get address_book => "Książka adresowa"; + @override + String get enter_your_pin => "Wpisz Twój kod PIN"; + @override + String get wallet_list_restore_wallet => "Przywróć portfel"; + @override + String get restore_title_from_seed => "Przywróć z nasion"; + @override + String get restore_description_from_backup => "Możesz przywrócić całą aplikację Cake Wallet z\nplik kopii zapasowej"; + @override + String get send_monero_address => "Adres Monero"; + @override + String get error_text_node_port => "Port węzła może zawierać tylko liczby od 0 do 65535"; + @override + String get digit_pin => "- znak PIN"; + @override + String get first_wallet_text => "Niesamowity portfel\nfdla Monero"; + @override + String get settings_trades => "Transakcje"; + @override + String get account => "Konto"; + @override + String change_language_to(String language) => "Zmień język na ${language}?"; + @override + String change_current_node(String node) => "Czy na pewno chcesz przywrócić ustawienia domyślne? ${node}?"; + @override + String trade_id(String id) => "Identyfikator handlu:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "Ładuję ${wallet_name} portfel"; + @override + String router_no_route(String name) => "Brak zdefiniowanej trasy dla ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "Handel ${tradeId} of ${title} nie znaleziono."; + @override + String transaction_details_copied(String title) => "${title} skopiowane do schowka"; + @override + String trade_details_copied(String title) => "${title} skopiowane do schowka"; + @override + String powered_by(String title) => "Zasilany przez ${title}"; + @override + String send_priority(String transactionPriority) => "Obecnie opłata ustalona jest na ${transactionPriority} priorytet.\nPriorytet transakcji można zmienić w ustawieniach"; + @override + String trade_for_not_created(String title) => "Zamienić się za ${title} nie jest tworzony."; + @override + String trade_is_powered_by(String provider) => "Ten handel jest zasilany przez ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + @override + String max_value(String value, String currency) => "Max: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "Min: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "Nieudane uwierzytelnienie. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} Bloki pozostałe"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Naciskając Potwierdź, wyślesz ${fetchingLabel} ${from} z twojego portfela ${walletName} z twojego portfela. Lub możesz wysłać z zewnętrznego portfela na powyższy adres / kod QR.\n\nNaciśnij Potwierdź, aby kontynuować lub wróć, aby zmienić kwoty.\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "Proszę wyślij ${fetchingLabel} ${from} na adres podany powyżej.\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "Zatwierdź transakcję\nIlość: ${amount}\nOpłata: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "Nie udało się usunąć ${wallet_name} portfel. ${error}"; + @override + String copied_key_to_clipboard(String key) => "Skopiowane ${key} do schowka"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "Nie udało się załadować ${wallet_name} portfel. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "Usuwanie ${wallet_name} portfel"; +} + +class $es extends S { + const $es(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "Cancelar"; + @override + String get trade_state_unpaid => "No pagado"; + @override + String get authenticated => "Autenticados"; + @override + String get new_subaddress_create => "Crear"; + @override + String get restore_view_key_private => "View clave (privado)"; + @override + String get transaction_priority_fast => "Rápido"; + @override + String get error_text_amount => "La cantidad solo puede contener números"; + @override + String get hidden_balance => "Balance oculto"; + @override + String get transaction_sent => "Transacción enviada!"; + @override + String get password => "Contraseña"; + @override + String get you_will_get => "Conseguirás"; + @override + String get restore_description_from_seed => "Restaure su billetera desde el código de combinación de 25 palabras \ni de 13 palabras"; + @override + String get trade_state_underpaid => "Poco pagado"; + @override + String get welcome => "Bienvenido\n CAKE WALLET"; + @override + String get share_address => "Compartir dirección"; + @override + String get please_try_to_connect_to_another_node => "Intenta conectarte a otro nodo"; + @override + String get trade_state_traded => "Negociado"; + @override + String get edit => "Editar"; + @override + String get spend_key_public => "Spend clave (público)"; + @override + String get sync_status_syncronizing => "SINCRONIZANDO"; + @override + String get received => "Recibido"; + @override + String get auth_store_banned_minutes => " minutos"; + @override + String get restore_title_from_seed_keys => "Restaurar desde semilla/claves"; + @override + String get settings_none => "Ninguno"; + @override + String get wallet_list_create_new_wallet => "Crear nueva billetera"; + @override + String get seed_alert_third_text => "También puede ver la semilla nuevamente \nen el "; + @override + String get setup_pin => "PIN de configuración"; + @override + String get transaction_details_recipient_address => "Dirección del receptor"; + @override + String get receive_amount => "Cantidad"; + @override + String get settings_allow_biometrical_authentication => "Permitir autenticación biométrica"; + @override + String get view_key_private => "View clave (privado)"; + @override + String get restore_next => "Próximo"; + @override + String get trade_state_trading => "Comercio"; + @override + String get error_text_address => "La dirección de la billetera debe corresponder al tipo \nde criptomoneda"; + @override + String get send_your_wallet => "Tu billetera"; + @override + String get remove_node_message => "¿Está seguro de que desea eliminar el nodo seleccionado?"; + @override + String get error_text_account_name => "El nombre de la cuenta solo puede contener letras, números \ny debe tener entre 1 y 15 caracteres de longitud"; + @override + String get reconnection => "Reconexión"; + @override + String get available_balance => "Balance disponible"; + @override + String get restore_title_from_keys => "De las claves"; + @override + String get wallet_keys => "Billetera clave"; + @override + String get reconnect => "Volver a conectar"; + @override + String get reconnect_alert_text => "¿Estás seguro de reconectar?"; + @override + String get send_error_minimum_value => "El valor mínimo de la cantidad es 0.01"; + @override + String get copy_address => "Copiar dirección "; + @override + String get error_text_subaddress_name => "El nombre de la subdirección no puede contener símbolos `, '\" \ny debe tener entre 1 y 20 caracteres de longitud"; + @override + String get settings_all => "TODOS"; + @override + String get nodes_list_reset_to_default_message => "¿Está seguro de que desea restablecer la configuración predeterminada?"; + @override + String get receive => "Recibir"; + @override + String get amount_is_guaranteed => "El monto recibido está garantizado"; + @override + String get auth_store_banned_for => "Prohibido para "; + @override + String get trade_details_pair => "Par"; + @override + String get confirm => "Confirmar"; + @override + String get send_xmr => "Enviar XMR"; + @override + String get settings_display_balance_as => "Mostrar saldo como"; + @override + String get trade_details_provider => "Proveedor"; + @override + String get you_will_send => "Enviarás"; + @override + String get create_new => "Crear nuevo"; + @override + String get trade_details_fetching => "Cargando"; + @override + String get seed_alert_menu => " menú ."; + @override + String get confirm_sending => "Confirmar envío"; + @override + String get settings_title => "Configuraciones"; + @override + String get address_book_menu => "Libreta de direcciones"; + @override + String get wallet_restoration_store_incorrect_seed_length => "Longitud de semilla incorrecta"; + @override + String get contact => "Contacto"; + @override + String get auth_store_incorrect_password => "Contraseña PIN"; + @override + String get transaction_priority_slow => "Lento"; + @override + String get add => "Añadir"; + @override + String get remove_node => "Eliminar nodo"; + @override + String get trade_state_paid_unconfirmed => "Pagado sin confirmar"; + @override + String get please_select => "Por favor seleccione:"; + @override + String get restore_title_from_backup => "Restaurar desde un archivo de respaldo"; + @override + String get full_balance => "Balance completo"; + @override + String get sync_status_not_connected => "NO CONECTADO"; + @override + String get error_text_crypto_currency => "El número de dígitos fraccionarios \ndebe ser menor o igual a 12"; + @override + String get error_text_contact_name => "El nombre del contacto no puede contener símbolos `, '\" \ny debe tener entre 1 y 32 caracteres de longitud"; + @override + String get restore_seed_keys_restore => "Restauración de semillas / llaves"; + @override + String get xmr_hidden => "XMR Oculto"; + @override + String get exchange => "Intercambiar"; + @override + String get sync_status_failed_connect => "CONEXIÓN FALLIDA AL NODO"; + @override + String get send_estimated_fee => "Tarifa estimada:"; + @override + String get outgoing => "Saliente"; + @override + String get sync_status_connected => "CONECTADO"; + @override + String get trade_state_pending => "Pendiente"; + @override + String get pending => " (pendiente)"; + @override + String get setup_successful => "Su PIN se ha configurado correctamente!"; + @override + String get fetching => "Cargando"; + @override + String get settings_nodes => "Nodos"; + @override + String get widgets_or => "o"; + @override + String get remove => "Retirar"; + @override + String get yesterday => "Ayer"; + @override + String get expired => "Muerto"; + @override + String get transaction_priority_regular => "Regular"; + @override + String get trade_details_created_at => "Creado en"; + @override + String get settings_wallets => "Carteras"; + @override + String get settings_only_transactions => "Solo transacciones"; + @override + String get estimated => "Estimado"; + @override + String get filters => "Filtros"; + @override + String get settings_current_node => "Nodo actual"; + @override + String get copy_id => "Copiar ID"; + @override + String get please_make_selection => "Seleccione a continuación para\ncrear o recuperar su billetera."; + @override + String get loading_your_wallet => "Cargando tu billetera"; + @override + String get subaddresses => "Subdirecciones"; + @override + String get trade_state_timeout => "Se acabó el tiempo"; + @override + String get nodes => "Nodos"; + @override + String get seed_share => "Compartir semillas"; + @override + String get widgets_restore_from_blockheight => "Restaurar desde blockheight"; + @override + String get new_subaddress_label_name => "Nombre de etiqueta"; + @override + String get trade_not_found => "Comercio no encontrado."; + @override + String get transaction_details_date => "Fecha"; + @override + String get show_seed => "Mostrar semilla"; + @override + String get send_error_currency => "La moneda solo puede contener números"; + @override + String get subaddress_title => "Lista de subdirecciones"; + @override + String get wallet_store_monero_wallet => "Monedero Monero"; + @override + String get send_creating_transaction => "Creando transacción"; + @override + String get trade_state_complete => "Completar"; + @override + String get error_text_xmr => "El valor XMR no puede exceder el saldo disponible.\nTEl número de dígitos de fracción debe ser menor o igual a 12"; + @override + String get xmr_available_balance => "XMR Available Balance"; + @override + String get trade_state_paid => "Pagado"; + @override + String get node_new => "Nuevo nodo"; + @override + String get trade_state_created => "Creado"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "He salvado comercial ID"; + @override + String get settings_currency => "Moneda"; + @override + String get change_language => "Cambiar idioma"; + @override + String get wallet_name => "Nombre de la billetera"; + @override + String get error_text_payment_id => "La ID de pago solo puede contener de 16 a 64 caracteres en hexadecimal"; + @override + String get seed_alert_second_text => "Anótelos solo en caso de que pierda\no limpie su teléfono."; + @override + String get node_reset_settings_title => "Reiniciar ajustes"; + @override + String get wallets => "Carteras"; + @override + String get enter_your_pin_again => "Ingrese su PIN nuevamente"; + @override + String get transactions => "Actas"; + @override + String get amount_is_estimate => "El monto recibido es un estimado"; + @override + String get error_text_node_address => "Por favor, introduzca una dirección iPv4"; + @override + String get widgets_restore_from_date => "Restaurar desde fecha"; + @override + String get restore_recover => "Recuperar"; + @override + String get reset => "Reiniciar"; + @override + String get new_wallet => "Nueva billetera"; + @override + String get settings_display_on_dashboard_list => "Mostrar en la lista del tablero"; + @override + String get transaction_details_title => "Detalles de la transacción"; + @override + String get status => "Estado: "; + @override + String get restore_spend_key_private => "Spend clave (privado)"; + @override + String get sync_status_syncronized => "SINCRONIZADO"; + @override + String get transaction_priority_medium => "Medio"; + @override + String get transaction_details_transaction_id => "ID de transacción"; + @override + String get save => "Salvar"; + @override + String get login => "Iniciar sesión"; + @override + String get wallet_list_load_wallet => "Billetera de carga"; + @override + String get sending => "Enviando"; + @override + String get restore_restore_wallet => "Recuperar Cartera"; + @override + String get restore_description_from_seed_keys => "Recupere su billetera de las semillas/claves que ha guardado en un lugar seguro"; + @override + String get copy => "Dupdo"; + @override + String get node_port => "Puerto de nodo"; + @override + String get change_currency => "Cambiar moneda"; + @override + String get transaction_details_amount => "Cantidad"; + @override + String get widgets_address => "Dirección"; + @override + String get contact_name => "Nombre de contacto"; + @override + String get exchange_result_write_down_ID => "*Copie o escriba su identificación que se muestra arriba."; + @override + String get exchange_result_write_down_trade_id => "Por favor, copia o escribe el ID."; + @override + String get new_subaddress_title => "Nueva subdirección"; + @override + String get change => "Cambio"; + @override + String get seed_alert_first_text => "La siguiente página te mostrará \nuna semilla."; + @override + String get trade_state_finished => "Terminado"; + @override + String get pin_is_incorrect => "PIN es incorrecto"; + @override + String get trade_not_created => "Comercio no se crea."; + @override + String get restore_wallet_name => "Nombre de la billetera"; + @override + String get widgets_seed => "Semilla"; + @override + String get settings_fee_priority => "Prioridad de tasa"; + @override + String get settings_personal => "Personal"; + @override + String get seed_title => "Semilla"; + @override + String get accounts => "Cuentas"; + @override + String get rescan => "Reescanear"; + @override + String get seed_alert_understand => "Entiendo"; + @override + String get show_keys => "Mostrar llaves"; + @override + String get error_text_fiat => "El valor de la cantidad no puede exceder el saldo disponible.\nEl número de dígitos de fracción debe ser menor o igual a 2"; + @override + String get transactions_by_date => "Transacciones por fecha"; + @override + String get use => "Utilizar "; + @override + String get trade_details_state => "Estado"; + @override + String get spend_key_private => "Spend clave (privado)"; + @override + String get settings_only_trades => "Solo comercia"; + @override + String get trade_state_to_be_created => "Ser creado"; + @override + String get all => "TODOS"; + @override + String get xmr_full_balance => "XMR Saldo disponible"; + @override + String get incoming => "Entrante"; + @override + String get trade_history_title => "Historia del comercio"; + @override + String get error_text_wallet_name => "El nombre de la billetera solo puede contener letras, números \ny debe tener entre 1 y 15 caracteres de longitud"; + @override + String get restore_description_from_keys => "Restaure su billetera de las pulsaciones de teclas generadas\nguardadas de sus claves privadas"; + @override + String get sent => "Expedido"; + @override + String get view_key_public => "View clave (público)"; + @override + String get trade_details_id => "ID"; + @override + String get trade_state_confirming => "Confirmando"; + @override + String get send => "Enviar"; + @override + String get send_title => "Enviar Monero"; + @override + String get error_text_keys => "Las llaves de billetera solo pueden contener 64 caracteres en hexadecimal"; + @override + String get settings_save_recipient_address => "Guardar dirección del destinatario"; + @override + String get seed_alert_settings => "de configuración"; + @override + String get change_exchange_provider => "Cambiar proveedor de intercambio"; + @override + String get send_payment_id => "ID de pago (opcional)"; + @override + String get trade_details_title => "Detalles comerciales"; + @override + String get settings_terms_and_conditions => "Términos y Condiciones"; + @override + String get auth_store_ban_timeout => "prohibición de tiempo de espera"; + @override + String get sync_status_connecting => "CONECTANDO"; + @override + String get settings_transactions => "Transacciones"; + @override + String get transaction_priority_fastest => "Lo más rápido"; + @override + String get error => "Error"; + @override + String get delete => "Borrar"; + @override + String get sync_status_starting_sync => "EMPEZANDO A SINCRONIZAR"; + @override + String get copied_to_clipboard => "Copiado al portapapeles"; + @override + String get offer_expires_in => "Oferta expira en: "; + @override + String get continue_text => "Continuar"; + @override + String get transaction_details_height => "Altura"; + @override + String get wallet_menu => "Menú de billetera"; + @override + String get settings_dark_mode => "Modo oscuro"; + @override + String get payment_id => "ID de pago: "; + @override + String get restore_address => "Dirección"; + @override + String get restore_wallet_restore_description => "Restaurar billetera"; + @override + String get today => "Hoy"; + @override + String get settings_support => "Apoyo"; + @override + String get restore_wallet => "Restaurar billetera"; + @override + String get ok => "OK"; + @override + String get wallet_list_title => "Monedero Monero"; + @override + String get authentication => "Autenticación"; + @override + String get amount => "Cantidad: "; + @override + String get node_address => "Dirección de nodo"; + @override + String get settings_change_language => "Cambiar idioma"; + @override + String get clear => "Claro"; + @override + String get settings_change_pin => "Cambiar PIN"; + @override + String get trades => "Cambios"; + @override + String get trade_state_btc_sent => "Btc expedido"; + @override + String get address_book => "Libreta de direcciones"; + @override + String get enter_your_pin => "Introduce tu PIN"; + @override + String get wallet_list_restore_wallet => "Restaurar billetera"; + @override + String get restore_title_from_seed => "De la semilla"; + @override + String get restore_description_from_backup => "Puede restaurar toda la aplicación Cake Wallet desde \nysu archivo de respaldo"; + @override + String get send_monero_address => "Dirección de Monero"; + @override + String get error_text_node_port => "El puerto de nodo solo puede contener números entre 0 y 65535"; + @override + String get digit_pin => "-dígito Pin"; + @override + String get first_wallet_text => "Impresionante billetera\npara Monero"; + @override + String get settings_trades => "Comercia"; + @override + String get account => "Cuenta"; + @override + String change_language_to(String language) => "Cambiar el idioma a ${language}?"; + @override + String change_current_node(String node) => "¿Está seguro de cambiar el nodo actual a ${node}?"; + @override + String trade_id(String id) => "Comercial ID:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "Billetera ${wallet_name} de carga"; + @override + String router_no_route(String name) => "No hay ruta definida para ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "Comercio ${tradeId} de ${title} no encontrado."; + @override + String transaction_details_copied(String title) => "${title} Copiado al portapapeles"; + @override + String trade_details_copied(String title) => "${title} Copiado al portapapeles"; + @override + String powered_by(String title) => "Energizado por ${title}"; + @override + String send_priority(String transactionPriority) => "Actualmente la tarifa se establece en ${transactionPriority} prioridad.\nLa prioridad de la transacción se puede ajustar en la configuración"; + @override + String trade_for_not_created(String title) => "Comercio por ${title} no se crea."; + @override + String trade_is_powered_by(String provider) => "Este comercio es impulsado por ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + @override + String max_value(String value, String currency) => "Max: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "Min: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "Autenticación fallida. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} Bloques restantes"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Al presionar confirmar, enviará ${fetchingLabel} ${from} desde su billetera llamada ${walletName} a la dirección que se muestra arriba. O puede enviar desde su billetera externa a la dirección / código QR anterior.\n\nPresione confirmar para continuar o regrese para cambiar los montos.\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "Envíe ${fetchingLabel} ${from} a la dirección que se muestra arriba.\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "Error al elimina ${wallet_name} billetera. ${error}"; + @override + String copied_key_to_clipboard(String key) => "Copiado ${key} al portapapeles"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "No se pudo cargar ${wallet_name} la billetera. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "Retirar ${wallet_name} billetera"; +} + +class $nl extends S { + const $nl(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "Annuleer"; + @override + String get trade_state_unpaid => "Onbetaald"; + @override + String get authenticated => "Authenticated"; + @override + String get new_subaddress_create => "Creëren"; + @override + String get restore_view_key_private => "Bekijk sleutel (privaat)"; + @override + String get transaction_priority_fast => "Snel"; + @override + String get error_text_amount => "Bedrag kan alleen cijfers bevatten"; + @override + String get hidden_balance => "Verborgen balans"; + @override + String get transaction_sent => "Transactie verzonden!"; + @override + String get password => "Wachtwoord"; + @override + String get you_will_get => "Je zult krijgen"; + @override + String get restore_description_from_seed => "Herstel uw portemonnee van het 25 woord\nof 13 woord combinatiecode"; + @override + String get trade_state_underpaid => "Slecht betaald"; + @override + String get welcome => "WELKOM\nBIJ CAKE WALLET"; + @override + String get share_address => "Deel adres"; + @override + String get please_try_to_connect_to_another_node => "Probeer verbinding te maken met een ander knooppunt"; + @override + String get trade_state_traded => "Traded"; + @override + String get edit => "Bewerk"; + @override + String get spend_key_public => "Sleutel uitgeven (openbaar)"; + @override + String get sync_status_syncronizing => "SYNCHRONISEREN"; + @override + String get received => "Ontvangen"; + @override + String get auth_store_banned_minutes => " notulen"; + @override + String get restore_title_from_seed_keys => "Herstel van zaad / sleutels"; + @override + String get settings_none => "Geen"; + @override + String get wallet_list_create_new_wallet => "Maak een nieuwe portemonnee"; + @override + String get seed_alert_third_text => "Je kunt het zaad ook weer zien\nin de "; + @override + String get setup_pin => "PIN instellen"; + @override + String get transaction_details_recipient_address => "Adres van de ontvanger"; + @override + String get receive_amount => "Bedrag"; + @override + String get settings_allow_biometrical_authentication => "Biometrische authenticatie toestaan"; + @override + String get view_key_private => "Bekijk sleutel (privaat)"; + @override + String get restore_next => "Volgende"; + @override + String get trade_state_trading => "Handel"; + @override + String get error_text_address => "Portemonnee-adres moet overeenkomen met het type\nvan cryptocurrency"; + @override + String get send_your_wallet => "Uw portemonnee"; + @override + String get remove_node_message => "Weet u zeker dat u het geselecteerde knooppunt wilt verwijderen?"; + @override + String get error_text_account_name => "Accountnaam mag alleen letters, cijfers bevatten\nen moet tussen de 1 en 15 tekens lang zijn"; + @override + String get reconnection => "Reconnection"; + @override + String get available_balance => "Beschikbaar saldo"; + @override + String get restore_title_from_keys => "Herstel van sleutels"; + @override + String get wallet_keys => "Portemonnee sleutels"; + @override + String get reconnect => "Sluit"; + @override + String get reconnect_alert_text => "Weet u zeker dat u opnieuw verbinding wilt maken?"; + @override + String get send_error_minimum_value => "Minimale waarde van bedrag is 0,01"; + @override + String get copy_address => "Adres kopiëren"; + @override + String get error_text_subaddress_name => "Naam subadres mag niet bevatten ` , ' \" symbolen\nen moet tussen de 1 en 20 tekens lang zijn"; + @override + String get settings_all => "ALLE"; + @override + String get nodes_list_reset_to_default_message => "Weet u zeker dat u de standaardinstellingen wilt herstellen?"; + @override + String get receive => "Krijgen"; + @override + String get amount_is_guaranteed => "Het ontvangen bedrag is gegarandeerd"; + @override + String get auth_store_banned_for => "Verboden voor "; + @override + String get trade_details_pair => "Paar"; + @override + String get confirm => "Bevestigen"; + @override + String get send_xmr => "Sturen XMR"; + @override + String get settings_display_balance_as => "Toon saldo als"; + @override + String get trade_details_provider => "Leverancier"; + @override + String get you_will_send => "Je zal versturen"; + @override + String get create_new => "Maak nieuw"; + @override + String get trade_details_fetching => "Ophalen"; + @override + String get seed_alert_menu => " menu."; + @override + String get confirm_sending => "Bevestig verzending"; + @override + String get settings_title => "Instellingen"; + @override + String get address_book_menu => "Adresboek"; + @override + String get wallet_restoration_store_incorrect_seed_length => "Onjuiste zaadlengte"; + @override + String get contact => "Contact"; + @override + String get auth_store_incorrect_password => "Incorrect PIN"; + @override + String get transaction_priority_slow => "Langzaam"; + @override + String get add => "Toevoegen"; + @override + String get remove_node => "Knoop verwijderen"; + @override + String get trade_state_paid_unconfirmed => "Niet bevestigd"; + @override + String get please_select => "Selecteer alstublieft:"; + @override + String get restore_title_from_backup => "Herstellen vanuit een back-upbestand"; + @override + String get full_balance => "Volledig saldo"; + @override + String get sync_status_not_connected => "NIET VERBONDEN"; + @override + String get error_text_crypto_currency => "Het aantal breukcijfers\nmoet kleiner zijn dan of gelijk zijn aan 12"; + @override + String get error_text_contact_name => "Naam contactpersoon kan niet bevatten ` , ' \" symbolen\nen moet tussen de 1 en 32 tekens lang zijn"; + @override + String get restore_seed_keys_restore => "Zaad / sleutels herstellen"; + @override + String get xmr_hidden => "XMR Verborgen"; + @override + String get exchange => "Uitwisseling"; + @override + String get sync_status_failed_connect => "MISLUKT VERBINDING MET DE NODE"; + @override + String get send_estimated_fee => "Geschatte vergoeding:"; + @override + String get outgoing => "Uitgaande"; + @override + String get sync_status_connected => "VERBONDEN"; + @override + String get trade_state_pending => "In afwachting"; + @override + String get pending => " (in afwachting)"; + @override + String get setup_successful => "Uw PIN is succesvol ingesteld!"; + @override + String get fetching => "Ophalen"; + @override + String get settings_nodes => "knooppunten"; + @override + String get widgets_or => "of"; + @override + String get remove => "Verwijderen"; + @override + String get yesterday => "Gisteren"; + @override + String get expired => "Verlopen"; + @override + String get transaction_priority_regular => "Regelmatig"; + @override + String get trade_details_created_at => "Gemaakt bij"; + @override + String get settings_wallets => "Portemonnee"; + @override + String get settings_only_transactions => "Alleen transacties"; + @override + String get estimated => "Geschatte"; + @override + String get filters => "Filters"; + @override + String get settings_current_node => "Huidige knooppunt"; + @override + String get copy_id => "ID kopiëren"; + @override + String get please_make_selection => "Maak hieronder uw keuze tot\nmaak of herstel je portemonnee."; + @override + String get loading_your_wallet => "Uw portemonnee laden"; + @override + String get subaddresses => "Subadressen"; + @override + String get trade_state_timeout => "Time-out"; + @override + String get nodes => "Knooppunten"; + @override + String get seed_share => "Deel zaad"; + @override + String get widgets_restore_from_blockheight => "Herstel vanaf blockheight"; + @override + String get new_subaddress_label_name => "Label naam"; + @override + String get trade_not_found => "Handel niet gevonden."; + @override + String get transaction_details_date => "Datum"; + @override + String get show_seed => "Toon zaad"; + @override + String get send_error_currency => "Valuta kan alleen cijfers bevatten"; + @override + String get subaddress_title => "Subadreslijst"; + @override + String get wallet_store_monero_wallet => "Monero portemonnee"; + @override + String get send_creating_transaction => "Transactie maken"; + @override + String get trade_state_complete => "Compleet"; + @override + String get error_text_xmr => "XMR-waarde kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 12"; + @override + String get xmr_available_balance => "XMR Beschikbaar saldo"; + @override + String get trade_state_paid => "Betaald"; + @override + String get node_new => "Nieuw knooppunt"; + @override + String get trade_state_created => "Gemaakt"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "Ik heb de ruil-ID opgeslagen"; + @override + String get settings_currency => "Valuta"; + @override + String get change_language => "Verander de taal"; + @override + String get wallet_name => "Portemonnee naam"; + @override + String get error_text_payment_id => "Betalings-ID kan alleen 16 tot 64 tekens bevatten in hexadecimale volgorde"; + @override + String get seed_alert_second_text => "Noteer deze voor het geval dat\nje verliest of veegt je telefoon."; + @override + String get node_reset_settings_title => "Reset instellingen"; + @override + String get wallets => "Portefeuilles"; + @override + String get enter_your_pin_again => "Voer uw PIN opnieuw in"; + @override + String get transactions => "Transacties"; + @override + String get amount_is_estimate => "Het ontvangen bedrag is een schatting"; + @override + String get error_text_node_address => "Voer een iPv4-adres in"; + @override + String get widgets_restore_from_date => "Herstel vanaf datum"; + @override + String get restore_recover => "Herstellen"; + @override + String get reset => "Reset"; + @override + String get new_wallet => "Nieuwe portemonnee"; + @override + String get settings_display_on_dashboard_list => "Weergeven op dashboardlijst"; + @override + String get transaction_details_title => "Transactie details"; + @override + String get status => "Staat: "; + @override + String get restore_spend_key_private => "Sleutel uitgeven (privaat)"; + @override + String get sync_status_syncronized => "SYNCHRONIZED"; + @override + String get transaction_priority_medium => "Medium"; + @override + String get transaction_details_transaction_id => "Transactie ID"; + @override + String get save => "Opslaan"; + @override + String get login => "Log in"; + @override + String get wallet_list_load_wallet => "Portemonnee laden"; + @override + String get sending => "Bezig met verzenden"; + @override + String get restore_restore_wallet => "Portemonnee herstellen"; + @override + String get restore_description_from_seed_keys => "Ontvang uw portemonnee terug uit seed / keys die u hebt opgeslagen op een veilige plaats"; + @override + String get copy => "Kopiëren"; + @override + String get node_port => "Knooppunt poort"; + @override + String get change_currency => "Verander valuta"; + @override + String get transaction_details_amount => "Bedrag"; + @override + String get widgets_address => "Adres"; + @override + String get contact_name => "Contactnaam"; + @override + String get exchange_result_write_down_ID => "*Kopieer of noteer uw hierboven getoonde ID."; + @override + String get exchange_result_write_down_trade_id => "Kopieer of noteer de handels-ID om door te gaan."; + @override + String get new_subaddress_title => "Nieuw subadres"; + @override + String get change => "Verandering"; + @override + String get seed_alert_first_text => "De volgende pagina wordt getoond\njij een zaadje."; + @override + String get trade_state_finished => "Afgewerkt"; + @override + String get pin_is_incorrect => "PIN is onjuist"; + @override + String get trade_not_created => "Handel niet gecreëerd."; + @override + String get restore_wallet_name => "Portemonnee naam"; + @override + String get widgets_seed => "Zaad"; + @override + String get settings_fee_priority => "Tariefprioriteit"; + @override + String get settings_personal => "Persoonlijk"; + @override + String get seed_title => "Zaad"; + @override + String get accounts => "Accounts"; + @override + String get rescan => "Opnieuw scannen"; + @override + String get seed_alert_understand => "Ik begrijp het"; + @override + String get show_keys => "Toon sleutels"; + @override + String get error_text_fiat => "Waarde van bedrag kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 2"; + @override + String get transactions_by_date => "Transacties op datum"; + @override + String get use => "Gebruik "; + @override + String get trade_details_state => "Staat"; + @override + String get spend_key_private => "Sleutel uitgeven (privaat)"; + @override + String get settings_only_trades => "Alleen handel"; + @override + String get trade_state_to_be_created => "Om gecreëerd te worden"; + @override + String get all => "ALLE"; + @override + String get xmr_full_balance => "XMR Volledig saldo"; + @override + String get incoming => "inkomend"; + @override + String get trade_history_title => "Handelsgeschiedenis"; + @override + String get error_text_wallet_name => "Naam portemonnee kan alleen letters, cijfers bevatten\nen moet tussen de 1 en 15 tekens lang zijn"; + @override + String get restore_description_from_keys => "Herstel uw portemonnee van gegenereerd\ntoetsaanslagen opgeslagen van uw privésleutels"; + @override + String get sent => "Verzonden"; + @override + String get view_key_public => "Bekijk sleutel (openbaar)"; + @override + String get trade_details_id => "ID"; + @override + String get trade_state_confirming => "Bevestiging"; + @override + String get send => "Sturen"; + @override + String get send_title => "Stuur Monero"; + @override + String get error_text_keys => "Portefeuillesleutels kunnen maximaal 64 tekens bevatten in hexadecimale volgorde"; + @override + String get settings_save_recipient_address => "Adres ontvanger opslaan"; + @override + String get seed_alert_settings => "instellingen"; + @override + String get change_exchange_provider => "Wijzig Exchange Provider"; + @override + String get send_payment_id => "Betaling ID (facultatief)"; + @override + String get trade_details_title => "Handelsgegevens"; + @override + String get settings_terms_and_conditions => "Voorwaarden"; + @override + String get auth_store_ban_timeout => "time-out verbieden"; + @override + String get sync_status_connecting => "AANSLUITING"; + @override + String get settings_transactions => "Transacties"; + @override + String get transaction_priority_fastest => "Snelste"; + @override + String get error => "Fout"; + @override + String get delete => "Delete"; + @override + String get sync_status_starting_sync => "BEGINNEN MET SYNCHRONISEREN"; + @override + String get copied_to_clipboard => "Gekopieerd naar het klembord"; + @override + String get offer_expires_in => "Aanbieding verloopt over: "; + @override + String get continue_text => "Doorgaan met"; + @override + String get transaction_details_height => "Hoogte"; + @override + String get wallet_menu => "Portemonnee-menu"; + @override + String get settings_dark_mode => "Donkere modus"; + @override + String get payment_id => "Betaling ID: "; + @override + String get restore_address => "Adres"; + @override + String get restore_wallet_restore_description => "Portemonnee-herstelbeschrijving"; + @override + String get today => "Vandaag"; + @override + String get settings_support => "Ondersteuning"; + @override + String get restore_wallet => "Portemonnee herstellen"; + @override + String get ok => "OK"; + @override + String get wallet_list_title => "Monero portemonnee"; + @override + String get authentication => "Authenticatie"; + @override + String get amount => "Bedrag: "; + @override + String get node_address => "Knooppunt adres"; + @override + String get settings_change_language => "Verander de taal"; + @override + String get clear => "Duidelijk"; + @override + String get settings_change_pin => "Verander pincode"; + @override + String get trades => "Trades"; + @override + String get trade_state_btc_sent => "Verzonden"; + @override + String get address_book => "Adresboek"; + @override + String get enter_your_pin => "Voer uw pincode in"; + @override + String get wallet_list_restore_wallet => "Portemonnee herstellen"; + @override + String get restore_title_from_seed => "Herstel van zaad"; + @override + String get restore_description_from_backup => "Je kunt de hele Cake Wallet-app herstellen van\nuw back-upbestand"; + @override + String get send_monero_address => "Monero-adres"; + @override + String get error_text_node_port => "Knooppuntpoort kan alleen nummers tussen 0 en 65535 bevatten"; + @override + String get digit_pin => "-cijferige PIN"; + @override + String get first_wallet_text => "Geweldige portemonnee\nfvoor Monero"; + @override + String get settings_trades => "Trades"; + @override + String get account => "Account"; + @override + String change_language_to(String language) => "Verander de taal in ${language}?"; + @override + String change_current_node(String node) => "Weet u zeker dat u het huidige knooppunt wilt wijzigen in ${node}?"; + @override + String trade_id(String id) => "Trade ID:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "Bezig met laden ${wallet_name} portemonnee"; + @override + String router_no_route(String name) => "Geen route gedefinieerd voor ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "Handel ${tradeId} van ${title} niet gevonden."; + @override + String transaction_details_copied(String title) => "${title} gekopieerd naar het klembord"; + @override + String trade_details_copied(String title) => "${title} gekopieerd naar het klembord"; + @override + String powered_by(String title) => "Aangedreven door ${title}"; + @override + String send_priority(String transactionPriority) => "Momenteel is de vergoeding vastgesteld op ${transactionPriority} prioriteit.\nTransactieprioriteit kan worden aangepast in de instellingen"; + @override + String trade_for_not_created(String title) => "Ruilen voor ${title} is niet gemaakt."; + @override + String trade_is_powered_by(String provider) => "Deze transactie wordt mogelijk gemaakt door ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + @override + String max_value(String value, String currency) => "Max: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "Min: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "Mislukte authenticatie. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} Resterende blokken"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "Door op bevestigen te drukken, wordt u verzonden ${fetchingLabel} ${from} uit je portemonnee genoemd ${walletName} naar bovenstaand adres. Of u kunt uw externe portemonnee naar bovenstaand adres / QR-code sturen.\n\nDruk op bevestigen om door te gaan of terug te gaan om de bedragen te wijzigen.\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "Zend alstublieft ${fetchingLabel} ${from} naar bovenstaand adres.\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "Verricht transactie\nBedrag: ${amount}\nhonorarium: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "Verwijderen mislukt ${wallet_name} portemonnee. ${error}"; + @override + String copied_key_to_clipboard(String key) => "Gekopieerd ${key} naar het klembord"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "Laden mislukt ${wallet_name} portemonnee. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "Verwijderen ${wallet_name} portemonnee"; +} + +class $zh extends S { + const $zh(); + + @override + TextDirection get textDirection => TextDirection.ltr; + + @override + String get cancel => "取消"; + @override + String get trade_state_unpaid => "未付"; + @override + String get authenticated => "已认证"; + @override + String get new_subaddress_create => "创建"; + @override + String get restore_view_key_private => "查看金钥 (私人的)"; + @override + String get transaction_priority_fast => "快速"; + @override + String get error_text_amount => "金额只能包含数字"; + @override + String get hidden_balance => "隐藏余额"; + @override + String get transaction_sent => "交易已发送"; + @override + String get password => "密码"; + @override + String get you_will_get => "你会得到"; + @override + String get restore_description_from_seed => "从25个字中恢复您的钱包\n或13个字的组合码"; + @override + String get trade_state_underpaid => "支付不足"; + @override + String get welcome => "欢迎\nTO CAKE WALLET"; + @override + String get share_address => "分享地址"; + @override + String get please_try_to_connect_to_another_node => "请尝试连接到另一个节点"; + @override + String get trade_state_traded => "交易"; + @override + String get edit => "编辑"; + @override + String get spend_key_public => "支出金钥 (public)"; + @override + String get sync_status_syncronizing => "同步化"; + @override + String get received => "已收到"; + @override + String get auth_store_banned_minutes => " 分钟"; + @override + String get restore_title_from_seed_keys => "从种子/密钥还原"; + @override + String get settings_none => "没有"; + @override + String get wallet_list_create_new_wallet => "创建新钱包"; + @override + String get seed_alert_third_text => "您也可以再次看到种子 \n在里面"; + @override + String get setup_pin => "设定PIN码"; + @override + String get transaction_details_recipient_address => "收件人地址"; + @override + String get receive_amount => "量"; + @override + String get settings_allow_biometrical_authentication => "允许生物特征认证"; + @override + String get view_key_private => "查看金钥 (私人的)"; + @override + String get restore_next => "下一个"; + @override + String get trade_state_trading => "贸易"; + @override + String get error_text_address => "钱包地址必须与类型对应\n加密货币"; + @override + String get send_your_wallet => "你的钱包"; + @override + String get remove_node_message => "您确定要删除所选节点吗?"; + @override + String get error_text_account_name => "帐户名称只能包含字母数字\n且必须介于1到15个字符之间"; + @override + String get reconnection => "重新连线"; + @override + String get available_balance => "可用余额"; + @override + String get restore_title_from_keys => "从密钥还原"; + @override + String get wallet_keys => "钱包钥匙"; + @override + String get reconnect => "重新连接"; + @override + String get reconnect_alert_text => "您确定要重新连接吗?"; + @override + String get send_error_minimum_value => "最小金额为0.01"; + @override + String get copy_address => "复制地址"; + @override + String get error_text_subaddress_name => "子地址名称不能包含`,' \" 符号\n并且必须在1到20个字符之间"; + @override + String get settings_all => "所有"; + @override + String get nodes_list_reset_to_default_message => "您确定要将设置重设为默认值吗?"; + @override + String get receive => "接收"; + @override + String get amount_is_guaranteed => "接收金额有保证"; + @override + String get auth_store_banned_for => "禁止 "; + @override + String get trade_details_pair => "对"; + @override + String get confirm => "确认"; + @override + String get send_xmr => "发送 XMR"; + @override + String get settings_display_balance_as => "将余额显示为"; + @override + String get trade_details_provider => "提供者"; + @override + String get you_will_send => "您将发送"; + @override + String get create_new => "创建新的"; + @override + String get trade_details_fetching => "正在取得"; + @override + String get seed_alert_menu => " 菜单."; + @override + String get confirm_sending => "确认发送"; + @override + String get settings_title => "设定值"; + @override + String get address_book_menu => "地址簿"; + @override + String get wallet_restoration_store_incorrect_seed_length => "种子长度错误"; + @override + String get contact => "联系"; + @override + String get auth_store_incorrect_password => "PIN码错误"; + @override + String get transaction_priority_slow => "慢"; + @override + String get add => "加"; + @override + String get remove_node => "删除节点"; + @override + String get trade_state_paid_unconfirmed => "付费未确认"; + @override + String get please_select => "请选择:"; + @override + String get restore_title_from_backup => "从备份文件还原"; + @override + String get full_balance => "全部余额"; + @override + String get sync_status_not_connected => "未连接"; + @override + String get error_text_crypto_currency => "小数位数\n必须小于或等于12"; + @override + String get error_text_contact_name => "联系人姓名不能包含`,' \" 符号\n并且必须介于1到32个字符之间"; + @override + String get restore_seed_keys_restore => "种子/密钥还原"; + @override + String get xmr_hidden => "XMR Hidden"; + @override + String get exchange => "交换"; + @override + String get sync_status_failed_connect => "无法连接到节点"; + @override + String get send_estimated_fee => "预估费用:"; + @override + String get outgoing => "外向"; + @override + String get sync_status_connected => "连接的"; + @override + String get trade_state_pending => "待定"; + @override + String get pending => " (待定)"; + @override + String get setup_successful => "您的PIN码已成功设置!"; + @override + String get fetching => "正在取得"; + @override + String get settings_nodes => "节点数"; + @override + String get widgets_or => "要么"; + @override + String get remove => "去掉"; + @override + String get yesterday => "昨天"; + @override + String get expired => "已过期"; + @override + String get transaction_priority_regular => "定期"; + @override + String get trade_details_created_at => "创建于"; + @override + String get settings_wallets => "皮夹"; + @override + String get settings_only_transactions => "仅交易"; + @override + String get estimated => "估计的"; + @override + String get filters => "筛选器"; + @override + String get settings_current_node => "当前节点"; + @override + String get copy_id => "复印ID"; + @override + String get please_make_selection => "请在下面进行选择\nc创建或恢复您的钱包."; + @override + String get loading_your_wallet => "装钱包"; + @override + String get subaddresses => "子地址"; + @override + String get trade_state_timeout => "超时"; + @override + String get nodes => "节点数"; + @override + String get seed_share => "分享种子"; + @override + String get widgets_restore_from_blockheight => "从块高还原"; + @override + String get new_subaddress_label_name => "标签名称"; + @override + String get trade_not_found => "找不到交易."; + @override + String get transaction_details_date => "日期"; + @override + String get show_seed => "显示种子"; + @override + String get send_error_currency => "货币只能包含数字"; + @override + String get subaddress_title => "子地址清单"; + @override + String get wallet_store_monero_wallet => "Monero 钱包"; + @override + String get send_creating_transaction => "创建交易"; + @override + String get trade_state_complete => "完成"; + @override + String get error_text_xmr => "XMR值不能超过可用余额.\n小数位数必须小于或等于12"; + @override + String get xmr_available_balance => "XMR 可用余额 "; + @override + String get trade_state_paid => "已付费"; + @override + String get node_new => "新节点"; + @override + String get trade_state_created => "已建立"; + @override + String get faq => "FAQ"; + @override + String get id => "ID: "; + @override + String get saved_the_trade_id => "我已经保存了交易ID"; + @override + String get settings_currency => "货币"; + @override + String get change_language => "改變語言"; + @override + String get wallet_name => "钱包名称"; + @override + String get error_text_payment_id => "付款ID只能包含16到64个字符(十六进制)"; + @override + String get seed_alert_second_text => "请写下来以防万一\n万一您丢失或擦拭手机."; + @override + String get node_reset_settings_title => "重新设置"; + @override + String get wallets => "皮夹"; + @override + String get enter_your_pin_again => "再次输入您的PIN码"; + @override + String get transactions => "交易次数"; + @override + String get amount_is_estimate => "收款金额为估算值"; + @override + String get error_text_node_address => "请输入一个iPv4地址"; + @override + String get widgets_restore_from_date => "从日期还原"; + @override + String get restore_recover => "恢复"; + @override + String get reset => "重启"; + @override + String get new_wallet => "新钱包"; + @override + String get settings_display_on_dashboard_list => "显示在仪表板上"; + @override + String get transaction_details_title => "交易明细"; + @override + String get status => "状态: "; + @override + String get restore_spend_key_private => "支出金钥 (私人的)"; + @override + String get sync_status_syncronized => "已同步"; + @override + String get transaction_priority_medium => "介质"; + @override + String get transaction_details_transaction_id => "交易编号"; + @override + String get save => "保存"; + @override + String get login => "登录"; + @override + String get wallet_list_load_wallet => "装入钱包"; + @override + String get sending => "正在发送"; + @override + String get restore_restore_wallet => "恢复钱包"; + @override + String get restore_description_from_seed_keys => "从保存到安全地方的种子/钥匙取回钱包"; + @override + String get copy => "复制"; + @override + String get node_port => "节点端口"; + @override + String get change_currency => "更改币种"; + @override + String get transaction_details_amount => "量"; + @override + String get widgets_address => "地址"; + @override + String get contact_name => "联系人姓名"; + @override + String get exchange_result_write_down_ID => "*请复制或写下您上面显示的ID."; + @override + String get exchange_result_write_down_trade_id => "请复制或写下交易编号以继续."; + @override + String get new_subaddress_title => "新子地址"; + @override + String get change => "更改"; + @override + String get seed_alert_first_text => "下一页将显示\n你是种子."; + @override + String get trade_state_finished => "已完成"; + @override + String get pin_is_incorrect => "PIN码不正确"; + @override + String get trade_not_created => "未建立交易."; + @override + String get restore_wallet_name => "钱包名称"; + @override + String get widgets_seed => "种子"; + @override + String get settings_fee_priority => "费用优先"; + @override + String get settings_personal => "个人"; + @override + String get seed_title => "种子"; + @override + String get accounts => "帐目"; + @override + String get rescan => "重新扫描"; + @override + String get seed_alert_understand => "我明白"; + @override + String get show_keys => "显示按键"; + @override + String get error_text_fiat => "金额不能超过可用余额.\n小数位数必须小于或等于2"; + @override + String get transactions_by_date => "按日期交易"; + @override + String get use => "使用 "; + @override + String get trade_details_state => "条件"; + @override + String get spend_key_private => "支出金钥 (私人的)"; + @override + String get settings_only_trades => "只交易"; + @override + String get trade_state_to_be_created => "待创建"; + @override + String get all => "所有"; + @override + String get xmr_full_balance => "XMR 全部余额"; + @override + String get incoming => "传入"; + @override + String get trade_history_title => "交易历史"; + @override + String get error_text_wallet_name => "钱包名称只能包含字母,数字\n且必须介于1到15个字符之间"; + @override + String get restore_description_from_keys => "R从生成的电子钱包\n从您的私钥中保存的击键"; + @override + String get sent => "已发送"; + @override + String get view_key_public => "查看金钥 (public)"; + @override + String get trade_details_id => "ID"; + @override + String get trade_state_confirming => "确认中"; + @override + String get send => "发送"; + @override + String get send_title => "发送门罗币"; + @override + String get error_text_keys => "钱包密钥只能包含16个字符的十六进制字符"; + @override + String get settings_save_recipient_address => "保存收件人地址"; + @override + String get seed_alert_settings => "设定"; + @override + String get change_exchange_provider => "更改交易所提供商"; + @override + String get send_payment_id => "付款编号 (可选的)"; + @override + String get trade_details_title => "交易明细"; + @override + String get settings_terms_and_conditions => "条款和条件"; + @override + String get auth_store_ban_timeout => "禁止超时"; + @override + String get sync_status_connecting => "连接中"; + @override + String get settings_transactions => "交易次数"; + @override + String get transaction_priority_fastest => "最快的"; + @override + String get error => "错误"; + @override + String get delete => "删除"; + @override + String get sync_status_starting_sync => "开始同步"; + @override + String get copied_to_clipboard => "复制到剪贴板"; + @override + String get offer_expires_in => "优惠有效期至 "; + @override + String get continue_text => "继续"; + @override + String get transaction_details_height => "高度"; + @override + String get wallet_menu => "钱包菜单"; + @override + String get settings_dark_mode => "暗模式"; + @override + String get payment_id => "付款 ID: "; + @override + String get restore_address => "地址"; + @override + String get restore_wallet_restore_description => "钱包还原说明"; + @override + String get today => "今天"; + @override + String get settings_support => "支持"; + @override + String get restore_wallet => "恢复钱包"; + @override + String get ok => "好"; + @override + String get wallet_list_title => "Monero 钱包"; + @override + String get authentication => "认证方式"; + @override + String get amount => "量: "; + @override + String get node_address => "节点地址"; + @override + String get settings_change_language => "改变语言"; + @override + String get clear => "明确"; + @override + String get settings_change_pin => "更改密码"; + @override + String get trades => "交易"; + @override + String get trade_state_btc_sent => "已发送"; + @override + String get address_book => "地址簿"; + @override + String get enter_your_pin => "输入密码"; + @override + String get wallet_list_restore_wallet => "恢复钱包"; + @override + String get restore_title_from_seed => "从种子还原"; + @override + String get restore_description_from_backup => "您可以从还原整个Cake Wallet应用\n您的备份文件"; + @override + String get send_monero_address => "门罗地址"; + @override + String get error_text_node_port => "节点端口只能包含0到65535之间的数字"; + @override + String get digit_pin => "数字别针"; + @override + String get first_wallet_text => "很棒的钱包\n对于 Monero"; + @override + String get settings_trades => "交易"; + @override + String get account => "帐户"; + @override + String change_language_to(String language) => "將語言更改為 ${language}?"; + @override + String change_current_node(String node) => "您确定将当前节点更改为 ${node}?"; + @override + String trade_id(String id) => "贸易编号:\n${id}"; + @override + String wallet_list_loading_wallet(String wallet_name) => "载入中 ${wallet_name} 钱包"; + @override + String router_no_route(String name) => "未定义路线 ${name}"; + @override + String trade_id_not_found(String tradeId, String title) => "贸易方式 ${tradeId} 的 ${title} 未找到."; + @override + String transaction_details_copied(String title) => "${title} 复制到剪贴板"; + @override + String trade_details_copied(String title) => "${title} 复制到剪贴板"; + @override + String powered_by(String title) => "供电 ${title}"; + @override + String send_priority(String transactionPriority) => "目前,费用设置为 ${transactionPriority} 优先.\n交易优先级可以在设置中进行调整"; + @override + String trade_for_not_created(String title) => "交易 ${title} 未创建."; + @override + String trade_is_powered_by(String provider) => "该交易由 ${provider}"; + @override + String time(String minutes, String seconds) => "${minutes}m ${seconds}s"; + @override + String max_value(String value, String currency) => "最高: ${value} ${currency}"; + @override + String min_value(String value, String currency) => "敏: ${value} ${currency}"; + @override + String failed_authentication(String state_error) => "身份验证失败. ${state_error}"; + @override + String Blocks_remaining(String status) => "${status} 剩余的块"; + @override + String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "点击确认 您将发送 ${fetchingLabel} ${from} 从你的钱包里 ${walletName} 到上面显示的地址. 或者,您也可以从外部钱包发送上述地址/ QR码。\n\n请按确认继续或返回以更改金额\n\n"; + @override + String exchange_result_description(String fetchingLabel, String from) => "请发送 ${fetchingLabel} ${from} 到上面显示的地址.\n\n'"; + @override + String commit_transaction_amount_fee(String amount, String fee) => "提交交易\n量: ${amount}\nFee: ${fee}"; + @override + String wallet_list_failed_to_remove(String wallet_name, String error) => "删除失败 ${wallet_name} 钱包. ${error}"; + @override + String copied_key_to_clipboard(String key) => "复制 ${key} 到剪贴板"; + @override + String wallet_list_failed_to_load(String wallet_name, String error) => "加载失败 ${wallet_name} 钱包. ${error}"; + @override + String wallet_list_removing_wallet(String wallet_name) => "拆下 ${wallet_name} 钱包"; +} + +class GeneratedLocalizationsDelegate extends LocalizationsDelegate { + const GeneratedLocalizationsDelegate(); + + List get supportedLocales { + return const [ + Locale("de", ""), + Locale("hi", ""), + Locale("ru", ""), + Locale("ko", ""), + Locale("pt", ""), + Locale("ja", ""), + Locale("en", ""), + Locale("pl", ""), + Locale("es", ""), + Locale("nl", ""), + Locale("zh", ""), + ]; + } + + LocaleListResolutionCallback listResolution({Locale fallback, bool withCountry = true}) { + return (List locales, Iterable supported) { + if (locales == null || locales.isEmpty) { + return fallback ?? supported.first; + } else { + return _resolve(locales.first, fallback, supported, withCountry); + } + }; + } + + LocaleResolutionCallback resolution({Locale fallback, bool withCountry = true}) { + return (Locale locale, Iterable supported) { + return _resolve(locale, fallback, supported, withCountry); + }; + } + + @override + Future load(Locale locale) { + final String lang = getLang(locale); + if (lang != null) { + switch (lang) { + case "de": + S.current = const $de(); + return SynchronousFuture(S.current); + case "hi": + S.current = const $hi(); + return SynchronousFuture(S.current); + case "ru": + S.current = const $ru(); + return SynchronousFuture(S.current); + case "ko": + S.current = const $ko(); + return SynchronousFuture(S.current); + case "pt": + S.current = const $pt(); + return SynchronousFuture(S.current); + case "ja": + S.current = const $ja(); + return SynchronousFuture(S.current); + case "en": + S.current = const $en(); + return SynchronousFuture(S.current); + case "pl": + S.current = const $pl(); + return SynchronousFuture(S.current); + case "es": + S.current = const $es(); + return SynchronousFuture(S.current); + case "nl": + S.current = const $nl(); + return SynchronousFuture(S.current); + case "zh": + S.current = const $zh(); + return SynchronousFuture(S.current); + default: + // NO-OP. + } + } + S.current = const S(); + return SynchronousFuture(S.current); + } + + @override + bool isSupported(Locale locale) => _isSupported(locale, true); + + @override + bool shouldReload(GeneratedLocalizationsDelegate old) => false; + + /// + /// Internal method to resolve a locale from a list of locales. + /// + Locale _resolve(Locale locale, Locale fallback, Iterable supported, bool withCountry) { + if (locale == null || !_isSupported(locale, withCountry)) { + return fallback ?? supported.first; + } + + final Locale languageLocale = Locale(locale.languageCode, ""); + if (supported.contains(locale)) { + return locale; + } else if (supported.contains(languageLocale)) { + return languageLocale; + } else { + final Locale fallbackLocale = fallback ?? supported.first; + return fallbackLocale; + } + } + + /// + /// Returns true if the specified locale is supported, false otherwise. + /// + bool _isSupported(Locale locale, bool withCountry) { + if (locale != null) { + for (Locale supportedLocale in supportedLocales) { + // Language must always match both locales. + if (supportedLocale.languageCode != locale.languageCode) { + continue; + } + + // If country code matches, return this locale. + if (supportedLocale.countryCode == locale.countryCode) { + return true; + } + + // If no country requirement is requested, check if this locale has no country. + if (true != withCountry && (supportedLocale.countryCode == null || supportedLocale.countryCode.isEmpty)) { + return true; + } + } + } + return false; + } +} + +String getLang(Locale l) => l == null + ? null + : l.countryCode != null && l.countryCode.isEmpty + ? l.languageCode + : l.toString(); diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 000000000..16ffa4c0e --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,215 @@ +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hive/hive.dart'; +import 'package:cw_monero/wallet.dart' as moneroWallet; +import 'package:cake_wallet/router.dart'; +import 'theme_changer.dart'; +import 'themes.dart'; +import 'package:cake_wallet/src/domain/common/get_encryption_key.dart'; +import 'package:cake_wallet/src/domain/common/contact.dart'; +import 'package:cake_wallet/src/domain/common/node.dart'; +import 'package:cake_wallet/src/domain/common/wallet_info.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/domain/monero/transaction_description.dart'; +import 'package:cake_wallet/src/reactions/set_reactions.dart'; +import 'package:cake_wallet/src/stores/login/login_store.dart'; +import 'package:cake_wallet/src/stores/balance/balance_store.dart'; +import 'package:cake_wallet/src/stores/sync/sync_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/screens/root/root.dart'; +import 'package:cake_wallet/src/stores/authentication/authentication_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/price/price_store.dart'; +import 'package:cake_wallet/src/domain/services/user_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/domain/common/balance_display_mode.dart'; +import 'package:cake_wallet/src/domain/common/default_settings_migration.dart'; +import 'package:cake_wallet/src/domain/common/fiat_currency.dart'; +import 'package:cake_wallet/src/domain/common/transaction_priority.dart'; +import 'package:cake_wallet/src/domain/common/wallet_type.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/language.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + var appDir = await getApplicationDocumentsDirectory(); + Hive.init(appDir.path); + Hive.registerAdapter(ContactAdapter(), 0); + Hive.registerAdapter(NodeAdapter(), 1); + Hive.registerAdapter(TransactionDescriptionAdapter(), 2); + Hive.registerAdapter(TradeAdapter(), 3); + Hive.registerAdapter(WalletInfoAdapter(), 4); + Hive.registerAdapter(WalletTypeAdapter(), 5); + + final secureStorage = FlutterSecureStorage(); + final transactionDescriptionsBoxKey = await getEncryptionKey( + secureStorage: secureStorage, forKey: 'transactionDescriptionsBoxKey'); // FIXME: Unnamed constant + final tradesBoxKey = await getEncryptionKey( + secureStorage: secureStorage, forKey: 'tradesBoxKey'); // FIXME: Unnamed constant + + var contacts = await Hive.openBox(Contact.boxName); + var nodes = await Hive.openBox(Node.boxName); + var transactionDescriptions = await Hive.openBox( + TransactionDescription.boxName, + encryptionKey: transactionDescriptionsBoxKey); + var trades = + await Hive.openBox(Trade.boxName, encryptionKey: tradesBoxKey); + var walletInfoSource = await Hive.openBox(WalletInfo.boxName); + + final sharedPreferences = await SharedPreferences.getInstance(); + final walletService = WalletService(); + final walletListService = WalletListService( + secureStorage: secureStorage, + walletInfoSource: walletInfoSource, + walletService: walletService, + sharedPreferences: sharedPreferences); + final userService = UserService( + sharedPreferences: sharedPreferences, secureStorage: secureStorage); + final authenticationStore = AuthenticationStore(userService: userService); + + await initialSetup( + sharedPreferences: sharedPreferences, + walletListService: walletListService, + nodes: nodes, + authStore: authenticationStore); + + final settingsStore = await SettingsStoreBase.load( + nodes: nodes, + sharedPreferences: sharedPreferences, + initialFiatCurrency: FiatCurrency.usd, + initialTransactionPriority: TransactionPriority.slow, + initialBalanceDisplayMode: BalanceDisplayMode.availableBalance); + final priceStore = PriceStore(); + final walletStore = + WalletStore(walletService: walletService, settingsStore: settingsStore); + final syncStore = SyncStore(walletService: walletService); + final balanceStore = BalanceStore( + walletService: walletService, + settingsStore: settingsStore, + priceStore: priceStore); + final loginStore = LoginStore( + sharedPreferences: sharedPreferences, walletsService: walletListService); + + setReactions( + settingsStore: settingsStore, + priceStore: priceStore, + syncStore: syncStore, + walletStore: walletStore, + walletService: walletService, + authenticationStore: authenticationStore, + loginStore: loginStore); + + runApp(MultiProvider(providers: [ + Provider(create: (_) => sharedPreferences), + Provider(create: (_) => walletService), + Provider(create: (_) => walletListService), + Provider(create: (_) => userService), + Provider(create: (_) => settingsStore), + Provider(create: (_) => priceStore), + Provider(create: (_) => walletStore), + Provider(create: (_) => syncStore), + Provider(create: (_) => balanceStore), + Provider(create: (_) => authenticationStore), + Provider(create: (_) => contacts), + Provider(create: (_) => nodes), + Provider(create: (_) => transactionDescriptions), + Provider(create: (_) => trades) + ], child: CakeWalletApp())); +} + +initialSetup( + {WalletListService walletListService, + SharedPreferences sharedPreferences, + Box nodes, + AuthenticationStore authStore, + int initialMigrationVersion = 1, + WalletType initialWalletType = WalletType.monero}) async { + await walletListService.changeWalletManger(walletType: initialWalletType); + await defaultSettingsMigration( + version: initialMigrationVersion, + sharedPreferences: sharedPreferences, + nodes: nodes); + await authStore.started(); + moneroWallet.onStartup(); +} + +class CakeWalletApp extends StatelessWidget { + CakeWalletApp() { + SystemChrome.setPreferredOrientations( + [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + } + + @override + Widget build(BuildContext context) { + final settingsStore = Provider.of(context); + + return ChangeNotifierProvider( + create: (_) => ThemeChanger( + settingsStore.isDarkTheme ? Themes.darkTheme : Themes.lightTheme), + child: ChangeNotifierProvider( + create: (_) => Language(settingsStore.languageCode), + child: MaterialAppWithTheme())); + } +} + +class MaterialAppWithTheme extends StatelessWidget { + @override + Widget build(BuildContext context) { + final sharedPreferences = Provider.of(context); + final walletService = Provider.of(context); + final walletListService = Provider.of(context); + final userService = Provider.of(context); + final settingsStore = Provider.of(context); + final priceStore = Provider.of(context); + final walletStore = Provider.of(context); + final syncStore = Provider.of(context); + final balanceStore = Provider.of(context); + final theme = Provider.of(context); + final statusBarColor = + settingsStore.isDarkTheme ? Colors.black : Colors.white; + final currentLanguage = Provider.of(context); + final contacts = Provider.of>(context); + final nodes = Provider.of>(context); + final trades = Provider.of>(context); + final transactionDescriptions = + Provider.of>(context); + + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle(statusBarColor: statusBarColor)); + + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: theme.getTheme(), + localizationsDelegates: [ + S.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + locale: Locale(currentLanguage.getCurrentLanguage()), + onGenerateRoute: (settings) => Router.generateRoute( + sharedPreferences: sharedPreferences, + walletListService: walletListService, + walletService: walletService, + userService: userService, + settings: settings, + priceStore: priceStore, + walletStore: walletStore, + syncStore: syncStore, + balanceStore: balanceStore, + settingsStore: settingsStore, + contacts: contacts, + nodes: nodes, + trades: trades, + transactionDescriptions: transactionDescriptions), + home: Root()); + } +} diff --git a/lib/palette.dart b/lib/palette.dart new file mode 100644 index 000000000..d4d915a02 --- /dev/null +++ b/lib/palette.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; + +class Palette { + static const Color lightBlue = Color.fromRGBO(126, 147, 177, 1.0); + static const Color lightBlueWithOpacity = Color.fromRGBO(126, 147, 177, 0.4); + static const Color lightGrey = Color.fromRGBO(224, 233, 246, 1.0); + static const Color purple = Color.fromRGBO(243, 229, 245, 1.0); + static const Color deepPink = Color.fromRGBO(225, 190, 231, 1.0); + static const Color indigo = Color.fromRGBO(232, 234, 246, 1.0); + static const Color deepIndigo = Color.fromRGBO(197, 202, 233, 1.0); + static const Color creamyGrey = Color.fromRGBO(245, 246, 249, 1.0); + static const Color deepPurple = Color.fromRGBO(124, 83, 240, 1.0); + static const Color wildDarkBlue = Color.fromRGBO(155, 172, 197, 1.0); + static const Color wildDarkBlueWithOpacity = Color.fromRGBO(155, 172, 197, 0.1); + static const Color lightGreen = Color.fromRGBO(52, 186, 204, 1.0); + static const Color darkPurple = Color.fromRGBO(124, 83, 240, 1.0); + static const Color brightBlue = Color.fromRGBO(151, 226, 255, 0.6); + static const Color cloudySky = Color.fromRGBO(121, 201, 233, 0.8); + static const Color darkGrey = Color.fromRGBO(136, 155, 183, 0.21); + static const Color shadowGrey = Color.fromRGBO(132, 141, 198, 0.14); + static const Color shadowGreyWithOpacity = Color.fromRGBO(132, 141, 198, 0.05); + static const Color blueGrey = Color.fromRGBO(103, 107, 141, 1.0); + static const Color lightGrey2 = Color.fromRGBO(249, 249, 249, 1.0); + static const Color violet = Color.fromRGBO(138, 80, 255, 1.0); + static const Color lightViolet = Color.fromRGBO(131, 87, 255, 1); + static const Color cakeGreen = Color.fromRGBO(41, 187, 244, 1.0); + static const Color cakeGreenWithOpacity = Color.fromRGBO(41, 187, 244, 0.2); + static const Color switchBackground = Color.fromRGBO(228, 231, 240, 1.0); + static const Color switchBorder = Color.fromRGBO(211, 222, 238, 1.0); + static const Color green = Color.fromRGBO(39, 206, 80, 1.0); + static const Color red = Color.fromRGBO(255, 51, 51, 1.0); + static const Color nightBlue = Color.fromRGBO(34, 40, 75, 1.0); + static const Color lavender = Color.fromRGBO(249, 250, 253, 1); + static const Color lightLavender = Color.fromRGBO(242, 244, 247, 1); + static const Color cadetBlue = Color.fromRGBO(191, 201, 215, 1); + static const Color manatee = Color.fromRGBO(138, 153, 175, 1); + static const Color separator = Color.fromRGBO(240, 241, 244, 1); + static const Color containerLavender = Color.fromRGBO(226, 235, 238, 0.4); + static const Color purpleBlue = Color.fromRGBO(84, 92, 139, 1); + static const Color floatingActionButton = Color.fromRGBO(213, 56, 99, 1); + static const Color failure = Color.fromRGBO(226, 35, 35, 1); + static const Color powered = Color.fromRGBO(191, 201, 215, 1); + static const Color buttonShadow = Color.fromRGBO(23, 46, 77, 0.129207); +} + +class PaletteDark { + static const Color darkThemeTitle = Color.fromRGBO(132, 154, 186, 1.0); + static const Color darkThemeTitleViolet = Color.fromRGBO(121, 92, 190, 1.0); + static const Color darkThemeGrey = Color.fromRGBO(100, 115, 137, 1.0); + static const Color darkThemeGreyWithOpacity = Color.fromRGBO(100, 115, 137, 0.5); + static const Color darkThemeMidGrey = Color.fromRGBO(20, 26, 38, 1.0); + static const Color darkThemePurpleButton = Color.fromRGBO(182, 143, 255, 0.1); + static const Color darkThemePurpleButtonBorder = Color.fromRGBO(161, 120, 255, 0.7); + static const Color darkThemeBackground = Color.fromRGBO(39, 41, 50, 1.0); + static const Color darkThemeBackgroundDark = Color.fromRGBO(9, 12, 18, 1.0); + static const Color darkThemeDarkGrey = Color.fromRGBO(218, 228, 243, 0.06); + static const Color darkGrey = Color.fromRGBO(218, 228, 243, 0.4); + static const Color darkThemeBlackWithOpacity = Color.fromRGBO(0, 0, 0, 0.2); + static const Color darkThemeBlack = Color.fromRGBO(7, 10, 14, 1.0); + static const Color darkThemeAppBarBlack = Color.fromRGBO(4, 5, 7, 1.0); + static const Color darkThemeViolet = Color.fromRGBO(131, 87, 255, 0.2); + static const Color darkThemeIndigoButton = Color.fromRGBO(216, 223, 246, 0.1); + static const Color darkThemeIndigoButtonBorder = Color.fromRGBO(196, 206, 237, 0.4); + static const Color darkThemeBlueButton = Color.fromRGBO(151, 226, 255, 0.1); + static const Color darkThemeBlueButtonBorder = Color.fromRGBO(62, 190, 240, 0.6); + static const Color darkThemeCloseButton = Color.fromRGBO(34, 40, 74, 1.0); + static const Color darkThemePinButton = Color.fromRGBO(136, 155, 183, 0.1); + static const Color darkThemePinDigitButton = Color.fromRGBO(100, 115, 137, 0.34); + static const Color switchBackground = Color.fromRGBO(100, 115, 137, 0.4); + static const Color wildDarkBlueWithOpacity = Color.fromRGBO(155, 172, 197, 0.4); + static const Color wildDarkBlue = Color.fromRGBO(155, 172, 197, 0.8); +} \ No newline at end of file diff --git a/lib/router.dart b/lib/router.dart new file mode 100644 index 000000000..c7bdc5753 --- /dev/null +++ b/lib/router.dart @@ -0,0 +1,477 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:provider/provider.dart'; +import 'package:hive/hive.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +// MARK: Import domains + +import 'package:cake_wallet/src/domain/common/contact.dart'; +import 'package:cake_wallet/src/domain/services/user_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/changenow/changenow_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/common/node.dart'; +import 'package:cake_wallet/src/domain/monero/transaction_description.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; + +// MARK: Import stores + +import 'package:cake_wallet/src/stores/authentication/authentication_store.dart'; +import 'package:cake_wallet/src/stores/node_list/node_list_store.dart'; +import 'package:cake_wallet/src/stores/auth/auth_store.dart'; +import 'package:cake_wallet/src/stores/balance/balance_store.dart'; +import 'package:cake_wallet/src/stores/send/send_store.dart'; +import 'package:cake_wallet/src/stores/subaddress_creation/subaddress_creation_store.dart'; +import 'package:cake_wallet/src/stores/subaddress_list/subaddress_list_store.dart'; +import 'package:cake_wallet/src/stores/sync/sync_store.dart'; +import 'package:cake_wallet/src/stores/user/user_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_store.dart'; +import 'package:cake_wallet/src/stores/wallet_list/wallet_list_store.dart'; +import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_store.dart'; +import 'package:cake_wallet/src/stores/account_list/account_list_store.dart'; +import 'package:cake_wallet/src/stores/address_book/address_book_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_keys_store.dart'; +import 'package:cake_wallet/src/stores/exchange_trade/exchange_trade_store.dart'; +import 'package:cake_wallet/src/stores/exchange/exchange_store.dart'; +import 'package:cake_wallet/src/stores/rescan/rescan_wallet_store.dart'; +import 'package:cake_wallet/src/stores/price/price_store.dart'; + +// MARK: Import screens + +import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/src/screens/nodes/new_node_page.dart'; +import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart'; +import 'package:cake_wallet/src/screens/receive/receive_page.dart'; +import 'package:cake_wallet/src/screens/subaddress/new_subaddress_page.dart'; +import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart'; +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart'; +import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; +import 'package:cake_wallet/src/screens/restore/restore_options_page.dart'; +import 'package:cake_wallet/src/screens/restore/restore_wallet_options_page.dart'; +import 'package:cake_wallet/src/screens/restore/restore_wallet_from_seed_page.dart'; +import 'package:cake_wallet/src/screens/restore/restore_wallet_from_keys_page.dart'; +import 'package:cake_wallet/src/screens/send/send_page.dart'; +import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; +import 'package:cake_wallet/src/screens/seed_alert/seed_alert.dart'; +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart'; +import 'package:cake_wallet/src/screens/accounts/account_page.dart'; +import 'package:cake_wallet/src/screens/accounts/account_list_page.dart'; +import 'package:cake_wallet/src/screens/address_book/address_book_page.dart'; +import 'package:cake_wallet/src/screens/address_book/contact_page.dart'; +import 'package:cake_wallet/src/screens/show_keys/show_keys_page.dart'; +import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart'; +import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart'; +import 'package:cake_wallet/src/screens/subaddress/subaddress_list_page.dart'; +import 'package:cake_wallet/src/screens/settings/change_language.dart'; +import 'package:cake_wallet/src/screens/restore/restore_wallet_from_seed_details.dart'; +import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; +import 'package:cake_wallet/src/screens/settings/settings.dart'; +import 'package:cake_wallet/src/screens/rescan/rescan_page.dart'; +import 'package:cake_wallet/src/screens/faq/faq_page.dart'; +import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart'; +import 'package:cake_wallet/src/screens/auth/create_unlock_page.dart'; +import 'package:cake_wallet/src/screens/auth/create_login_page.dart'; +import 'package:cake_wallet/src/screens/seed/create_seed_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/create_dashboard_page.dart'; +import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart'; + +class Router { + static Route generateRoute( + {SharedPreferences sharedPreferences, + WalletListService walletListService, + WalletService walletService, + UserService userService, + RouteSettings settings, + PriceStore priceStore, + WalletStore walletStore, + SyncStore syncStore, + BalanceStore balanceStore, + SettingsStore settingsStore, + Box contacts, + Box nodes, + Box transactionDescriptions, + Box trades}) { + switch (settings.name) { + case Routes.welcome: + return MaterialPageRoute(builder: (_) => createWelcomePage()); + + case Routes.newWalletFromWelcome: + return CupertinoPageRoute( + builder: (_) => Provider( + create: (_) => UserStore( + accountService: UserService( + secureStorage: FlutterSecureStorage(), + sharedPreferences: sharedPreferences)), + child: SetupPinCodePage( + onPinCodeSetup: (context, _) => + Navigator.pushNamed(context, Routes.newWallet)))); + + case Routes.newWallet: + return CupertinoPageRoute( + builder: + (_) => + ProxyProvider( + update: (_, authStore, __) => WalletCreationStore( + authStore: authStore, + sharedPreferences: sharedPreferences, + walletListService: walletListService), + child: NewWalletPage( + walletsService: walletListService, + walletService: walletService, + sharedPreferences: sharedPreferences))); + + case Routes.setupPin: + Function(BuildContext, String) callback; + + if (settings.arguments is Function(BuildContext, String)) { + callback = settings.arguments; + } + + return CupertinoPageRoute( + builder: (_) => Provider( + create: (_) => UserStore( + accountService: UserService( + secureStorage: FlutterSecureStorage(), + sharedPreferences: sharedPreferences)), + child: SetupPinCodePage( + onPinCodeSetup: (context, pin) => + callback == null ? null : callback(context, pin))), + fullscreenDialog: true); + + case Routes.restoreOptions: + return CupertinoPageRoute(builder: (_) => RestoreOptionsPage()); + + case Routes.restoreWalletOptions: + return CupertinoPageRoute(builder: (_) => RestoreWalletOptionsPage()); + + case Routes.restoreWalletOptionsFromWelcome: + return CupertinoPageRoute( + builder: (_) => Provider( + create: (_) => UserStore( + accountService: UserService( + secureStorage: FlutterSecureStorage(), + sharedPreferences: sharedPreferences)), + child: SetupPinCodePage( + onPinCodeSetup: (context, _) => Navigator.pushNamed( + context, Routes.restoreWalletOptions)))); + + case Routes.seed: + return MaterialPageRoute( + builder: (_) => createSeedPage( + settingsStore: settingsStore, + walletService: walletService, + callback: settings.arguments)); + + case Routes.restoreWalletFromSeed: + return CupertinoPageRoute( + builder: (_) => + ProxyProvider( + update: (_, authStore, __) => WalletRestorationStore( + authStore: authStore, + sharedPreferences: sharedPreferences, + walletListService: walletListService), + child: RestoreWalletFromSeedPage( + walletsService: walletListService, + walletService: walletService, + sharedPreferences: sharedPreferences))); + + case Routes.restoreWalletFromKeys: + return CupertinoPageRoute( + builder: (_) => + ProxyProvider( + update: (_, authStore, __) => WalletRestorationStore( + authStore: authStore, + sharedPreferences: sharedPreferences, + walletListService: walletListService), + child: RestoreWalletFromKeysPage( + walletsService: walletListService, + walletService: walletService, + sharedPreferences: sharedPreferences))); + + case Routes.dashboard: + return CupertinoPageRoute( + builder: (_) => createDashboardPage( + walletService: walletService, + priceStore: priceStore, + settingsStore: settingsStore, + trades: trades, + transactionDescriptions: transactionDescriptions, + walletStore: walletStore)); + + case Routes.send: + return CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => MultiProvider(providers: [ + ProxyProvider( + update: (_, settingsStore, __) => BalanceStore( + walletService: walletService, + settingsStore: settingsStore, + priceStore: priceStore), + ), + Provider( + create: (_) => SyncStore(walletService: walletService), + ), + Provider( + create: (_) => SendStore( + walletService: walletService, + priceStore: priceStore, + transactionDescriptions: transactionDescriptions)), + ], child: SendPage())); + + case Routes.receive: + return CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => MultiProvider(providers: [ + Provider( + create: (_) => + SubaddressListStore(walletService: walletService)) + ], child: ReceivePage())); + + case Routes.transactionDetails: + return CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => + TransactionDetailsPage(transactionInfo: settings.arguments)); + + case Routes.newSubaddress: + return CupertinoPageRoute( + builder: (_) => Provider( + create: (_) => + SubadrressCreationStore(walletService: walletService), + child: NewSubaddressPage())); + + case Routes.disclaimer: + return CupertinoPageRoute(builder: (_) => DisclaimerPage()); + + case Routes.readDisclaimer: + return CupertinoPageRoute( + builder: (_) => DisclaimerPage(isReadOnly: true)); + + case Routes.seedAlert: + return CupertinoPageRoute(builder: (_) => SeedAlert()); + + case Routes.walletList: + return MaterialPageRoute( + fullscreenDialog: true, + builder: (_) => Provider( + create: (_) => WalletListStore( + walletListService: walletListService, + walletService: walletService), + child: WalletListPage())); + + case Routes.auth: + return MaterialPageRoute( + fullscreenDialog: true, + builder: (_) => Provider( + create: (_) => AuthStore( + sharedPreferences: sharedPreferences, + userService: userService, + walletService: walletService), + child: AuthPage(onAuthenticationFinished: settings.arguments), + )); + + case Routes.unlock: + return MaterialPageRoute( + fullscreenDialog: true, + builder: (_) => createUnlockPage( + sharedPreferences: sharedPreferences, + userService: userService, + walletService: walletService, + onAuthenticationFinished: settings.arguments)); + + case Routes.nodeList: + return CupertinoPageRoute(builder: (context) { + return Provider( + create: (_) => NodeListStore(nodesSource: nodes), + child: NodeListPage()); + }); + + case Routes.newNode: + return CupertinoPageRoute( + builder: (_) => Provider( + create: (_) => NodeListStore(nodesSource: nodes), + child: NewNodePage())); + + case Routes.login: + return CupertinoPageRoute(builder: (context) { + final authenticationStore = Provider.of(context); + + return createLoginPage( + sharedPreferences: sharedPreferences, + userService: userService, + walletService: walletService, + walletListService: walletListService, + authenticationStore: authenticationStore); + }); + + case Routes.accountList: + return MaterialPageRoute( + builder: (context) { + return MultiProvider(providers: [ + Provider( + create: (_) => + AccountListStore(walletService: walletService)), + ], child: AccountListPage()); + }, + fullscreenDialog: true); + + case Routes.accountCreation: + return CupertinoPageRoute(builder: (context) { + return Provider( + create: (_) => AccountListStore(walletService: walletService), + child: AccountPage(account: settings.arguments)); + }); + + case Routes.addressBook: + return MaterialPageRoute(builder: (context) { + return MultiProvider( + providers: [ + Provider( + create: (_) => + AccountListStore(walletService: walletService)), + Provider(create: (_) => AddressBookStore(contacts: contacts)) + ], + child: AddressBookPage(), + ); + }); + + case Routes.pickerAddressBook: + return MaterialPageRoute(builder: (context) { + return MultiProvider( + providers: [ + Provider( + create: (_) => + AccountListStore(walletService: walletService)), + Provider(create: (_) => AddressBookStore(contacts: contacts)) + ], + child: AddressBookPage(isEditable: false), + ); + }); + + case Routes.addressBookAddContact: + return CupertinoPageRoute(builder: (context) { + return MultiProvider( + providers: [ + Provider( + create: (_) => + AccountListStore(walletService: walletService)), + Provider(create: (_) => AddressBookStore(contacts: contacts)) + ], + child: ContactPage(contact: settings.arguments), + ); + }); + + case Routes.showKeys: + return MaterialPageRoute( + builder: (context) { + return Provider( + create: (_) => WalletKeysStore(walletService: walletService), + child: ShowKeysPage(), + ); + }, + fullscreenDialog: true); + + case Routes.exchangeTrade: + return CupertinoPageRoute( + builder: (_) => MultiProvider( + providers: [ + ProxyProvider( + update: (_, settingsStore, __) => ExchangeTradeStore( + trade: settings.arguments, walletStore: walletStore), + ), + ProxyProvider( + update: (_, settingsStore, __) => SendStore( + transactionDescriptions: transactionDescriptions, + walletService: walletService, + settingsStore: settingsStore, + priceStore: priceStore)), + ], + child: ExchangeTradePage(), + )); + + case Routes.exchangeConfirm: + return MaterialPageRoute( + builder: (_) => ExchangeConfirmPage(trade: settings.arguments)); + + case Routes.tradeDetails: + return MaterialPageRoute(builder: (context) { + return MultiProvider(providers: [ + ProxyProvider( + update: (_, settingsStore, __) => ExchangeTradeStore( + trade: settings.arguments, walletStore: walletStore), + ) + ], child: TradeDetailsPage()); + }); + + case Routes.subaddressList: + return MaterialPageRoute( + builder: (_) => MultiProvider(providers: [ + Provider( + create: (_) => + SubaddressListStore(walletService: walletService)) + ], child: SubaddressListPage())); + + case Routes.restoreWalletFromSeedDetails: + return CupertinoPageRoute( + builder: (_) => + ProxyProvider( + update: (_, authStore, __) => WalletRestorationStore( + authStore: authStore, + sharedPreferences: sharedPreferences, + walletListService: walletListService, + seed: settings.arguments), + child: RestoreWalletFromSeedDetailsPage())); + case Routes.exchange: + return MaterialPageRoute( + builder: (_) => MultiProvider(providers: [ + Provider(create: (_) { + final xmrtoprovider = XMRTOExchangeProvider(); + + return ExchangeStore( + initialProvider: xmrtoprovider, + initialDepositCurrency: CryptoCurrency.xmr, + initialReceiveCurrency: CryptoCurrency.btc, + trades: trades, + providerList: [ + xmrtoprovider, + ChangeNowExchangeProvider() + ], + walletStore: walletStore); + }), + ], child: ExchangePage())); + + case Routes.settings: + return MaterialPageRoute( + builder: (_) => Provider( + create: (_) => NodeListStore(nodesSource: nodes), + child: SettingsPage())); + + case Routes.rescan: + return MaterialPageRoute( + builder: (_) => Provider( + create: (_) => RescanWalletStore(walletService: walletService), + child: RescanPage())); + + case Routes.faq: + return MaterialPageRoute(builder: (_) => FaqPage()); + + case Routes.changeLanguage: + return MaterialPageRoute(builder: (_) => ChangeLanguage()); + + default: + return MaterialPageRoute( + builder: (_) => Scaffold( + body: Center( + child: Text(S.current.router_no_route(settings.name))), + )); + } + } +} diff --git a/lib/routes.dart b/lib/routes.dart new file mode 100644 index 000000000..c9046e956 --- /dev/null +++ b/lib/routes.dart @@ -0,0 +1,47 @@ +class Routes { + static const welcome = '/welcome'; + static const newWallet = '/new_wallet'; + static const setupPin = '/setup_pin_code'; + static const newWalletFromWelcome = '/new_wallet_from_welcome'; + static const restoreFromWelcome = '/restore_from_welcome'; + static const seed = '/seed'; + static const restoreOptions = '/restore_options'; + static const restoreOptionsFromWelcome = '/restore_options_from_welcome'; + static const restoreWalletOptions = '/restore_seed_keys'; + static const restoreWalletOptionsFromWelcome = '/restore_wallet_options_from_welcome'; + static const restoreWalletFromSeed = '/restore_wallet_from_seed'; + static const restoreWalletFromKeys = '/restore_wallet_from_keys'; + static const dashboard = '/dashboard'; + static const send = '/send'; + static const transactionDetails = '/transaction_info'; + static const receive = '/receive'; + static const newSubaddress = '/new_subaddress'; + static const disclaimer = '/disclaimer'; + static const readDisclaimer = '/read_disclaimer'; + static const seedAlert = '/seed_alert'; + static const walletList = '/wallet_list'; + static const auth = '/auth'; + static const nodeList = '/node_list'; + static const newNode = '/new_node_list'; + static const login = '/login'; + static const splash = '/splash'; + static const accountList = '/account_list'; + static const accountCreation = '/account_new'; + static const addressBook = '/address_book'; + static const pickerAddressBook = '/picker_address_book'; + static const addressBookAddContact = '/address_book_add_contact'; + static const showKeys = '/show_keys'; + static const exchangeConfirm = '/exchange_confirm'; + static const tradeHistory = '/trade_history'; + static const tradeDetails = '/trade_details'; + static const exchangeFunds = '/exchange_funds'; + static const exchangeTrade = '/exchange_trade'; + static const subaddressList = '/subaddress_list'; + static const restoreWalletFromSeedDetails = '/restore_from_seed_details'; + static const exchange = '/exchange'; + static const settings = '/settings'; + static const unlock = '/auth_not_closable'; + static const rescan = '/rescan'; + static const faq = '/faq'; + static const changeLanguage = '/change_language'; +} \ No newline at end of file diff --git a/lib/src/domain/common/balance.dart b/lib/src/domain/common/balance.dart new file mode 100644 index 000000000..2f53e248a --- /dev/null +++ b/lib/src/domain/common/balance.dart @@ -0,0 +1 @@ +abstract class Balance {} \ No newline at end of file diff --git a/lib/src/domain/common/balance_display_mode.dart b/lib/src/domain/common/balance_display_mode.dart new file mode 100644 index 000000000..590daf5f3 --- /dev/null +++ b/lib/src/domain/common/balance_display_mode.dart @@ -0,0 +1,46 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/enumerable_item.dart'; + +class BalanceDisplayMode extends EnumerableItem with Serializable { + static const all = [ + BalanceDisplayMode.fullBalance, + BalanceDisplayMode.availableBalance, + BalanceDisplayMode.hiddenBalance + ]; + static const fullBalance = BalanceDisplayMode(raw: 0, title: 'Full Balance'); + static const availableBalance = + BalanceDisplayMode(raw: 1, title: 'Available Balance'); + static const hiddenBalance = + BalanceDisplayMode(raw: 2, title: 'Hidden Balance'); + + static BalanceDisplayMode deserialize({int raw}) { + switch (raw) { + case 0: + return fullBalance; + case 1: + return availableBalance; + case 2: + return hiddenBalance; + default: + return null; + } + } + + const BalanceDisplayMode({@required String title, @required int raw}) + : super(title: title, raw: raw); + + @override + String toString() { + switch (this) { + case BalanceDisplayMode.fullBalance: + return S.current.xmr_full_balance; + case BalanceDisplayMode.availableBalance: + return S.current.xmr_available_balance; + case BalanceDisplayMode.hiddenBalance: + return S.current.xmr_hidden; + default: + return ''; + } + } +} diff --git a/lib/src/domain/common/calculate_estimated_fee.dart b/lib/src/domain/common/calculate_estimated_fee.dart new file mode 100644 index 000000000..8d6aa6bc6 --- /dev/null +++ b/lib/src/domain/common/calculate_estimated_fee.dart @@ -0,0 +1,25 @@ +import 'package:cake_wallet/src/domain/common/transaction_priority.dart'; + +double calculateEstimatedFee({TransactionPriority priority}) { + if (priority == TransactionPriority.slow) { + return 0.00002459; + } + + if (priority == TransactionPriority.regular) { + return 0.00012305; + } + + if (priority == TransactionPriority.medium) { + return 0.00024503; + } + + if (priority == TransactionPriority.fast) { + return 0.00061453; + } + + if (priority == TransactionPriority.fastest) { + return 0.0260216; + } + + return 0; +} \ No newline at end of file diff --git a/lib/src/domain/common/calculate_fiat_amount.dart b/lib/src/domain/common/calculate_fiat_amount.dart new file mode 100644 index 000000000..6302c23a0 --- /dev/null +++ b/lib/src/domain/common/calculate_fiat_amount.dart @@ -0,0 +1,14 @@ +String calculateFiatAmount({double price, String cryptoAmount}) { + if (price == null || cryptoAmount == null) { + return '0.00'; + } + + final _amount = double.parse(cryptoAmount); + final result = price * _amount; + + if (result == 0.0) { + return '0.00'; + } + + return result > 0.01 ? result.toStringAsFixed(2) : '< 0.01'; +} \ No newline at end of file diff --git a/lib/src/domain/common/calculate_fiat_amount_raw.dart b/lib/src/domain/common/calculate_fiat_amount_raw.dart new file mode 100644 index 000000000..30db2af6f --- /dev/null +++ b/lib/src/domain/common/calculate_fiat_amount_raw.dart @@ -0,0 +1,13 @@ +String calculateFiatAmountRaw({double price, double cryptoAmount}) { + if (price == null) { + return '0.00'; + } + + final result = price * cryptoAmount; + + if (result == 0.0) { + return '0.00'; + } + + return result > 0.01 ? result.toStringAsFixed(2) : '< 0.01'; +} \ No newline at end of file diff --git a/lib/src/domain/common/contact.dart b/lib/src/domain/common/contact.dart new file mode 100644 index 000000000..11f851253 --- /dev/null +++ b/lib/src/domain/common/contact.dart @@ -0,0 +1,30 @@ +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; + +part 'contact.g.dart'; + +@HiveType() +class Contact extends HiveObject { + static const boxName = 'Contacts'; + + @HiveField(0) + String name; + + @HiveField(1) + String address; + + @HiveField(2) + int raw; + + CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw); + + Contact( + {@required this.name, + @required this.address, + CryptoCurrency type}) + : raw = type?.raw; + + updateCryptoCurrency({@required CryptoCurrency currency}) => + raw = currency.raw; +} diff --git a/lib/src/domain/common/crypto_currency.dart b/lib/src/domain/common/crypto_currency.dart new file mode 100644 index 000000000..aa8220d4e --- /dev/null +++ b/lib/src/domain/common/crypto_currency.dart @@ -0,0 +1,66 @@ +import 'package:cake_wallet/src/domain/common/enumerable_item.dart'; +import 'package:hive/hive.dart'; + +part 'crypto_currency.g.dart'; + +@HiveType() +class CryptoCurrency extends EnumerableItem with Serializable { + static const all = [ + CryptoCurrency.xmr, + CryptoCurrency.btc, + CryptoCurrency.eth, + CryptoCurrency.ltc, + CryptoCurrency.bch, + CryptoCurrency.dash + ]; + static const xmr = CryptoCurrency(title: 'XMR', raw: 0); + static const btc = CryptoCurrency(title: 'BTC', raw: 1); + static const eth = CryptoCurrency(title: 'ETH', raw: 2); + static const ltc = CryptoCurrency(title: 'LTC', raw: 3); + static const bch = CryptoCurrency(title: 'BCH', raw: 4); + static const dash = CryptoCurrency(title: 'DASH', raw: 5); + + static CryptoCurrency deserialize({int raw}) { + switch (raw) { + case 0: + return xmr; + case 1: + return btc; + case 2: + return eth; + case 3: + return ltc; + case 4: + return bch; + case 5: + return dash; + default: + return null; + } + } + + static CryptoCurrency fromString(String raw) { + switch (raw.toLowerCase()) { + case 'xmr': + return xmr; + case 'btc': + return btc; + case 'eth': + return eth; + case 'ltc': + return ltc; + case 'bch': + return bch; + case 'dash': + return dash; + default: + return null; + } + } + + const CryptoCurrency({final String title, final int raw}) + : super(title: title, raw: raw); + + @override + String toString() => title; +} diff --git a/lib/src/domain/common/currency_formatter.dart b/lib/src/domain/common/currency_formatter.dart new file mode 100644 index 000000000..0fa142301 --- /dev/null +++ b/lib/src/domain/common/currency_formatter.dart @@ -0,0 +1,10 @@ +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; + +String cryptoToString(CryptoCurrency crypto) { + switch (crypto) { + case CryptoCurrency.xmr: + return 'XMR'; + default: + return ''; + } +} \ No newline at end of file diff --git a/lib/src/domain/common/default_settings_migration.dart b/lib/src/domain/common/default_settings_migration.dart new file mode 100644 index 000000000..64ff94898 --- /dev/null +++ b/lib/src/domain/common/default_settings_migration.dart @@ -0,0 +1,46 @@ +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/src/domain/common/node.dart'; +import 'package:cake_wallet/src/domain/common/balance_display_mode.dart'; +import 'package:cake_wallet/src/domain/common/fiat_currency.dart'; +import 'package:cake_wallet/src/domain/common/node_list.dart'; +import 'package:cake_wallet/src/domain/common/transaction_priority.dart'; + +Future defaultSettingsMigration( + {@required int version, + @required SharedPreferences sharedPreferences, + @required Box nodes}) async { + int currentVersion = + sharedPreferences.getInt('current_default_settings_migration_version') ?? + 0; + + if (currentVersion >= version) { + return; + } + + try { + switch (version) { + case 1: + sharedPreferences.setString( + 'current_fiat_currency', FiatCurrency.usd.toString()); + sharedPreferences.setInt( + 'current_fee_priority', TransactionPriority.standart.raw); + sharedPreferences.setInt('current_balance_display_mode', + BalanceDisplayMode.availableBalance.raw); + sharedPreferences.setInt( + 'current_default_settings_migration_version', 1); + sharedPreferences.setBool('save_recipient_address', false); + await resetToDefault(nodes); + sharedPreferences.setInt('current_node_id', 0); + break; + default: + break; + } + } catch (e) { + print('Migration error: ${e.toString()}'); + } + + sharedPreferences.setInt( + 'current_default_settings_migration_version', version); +} diff --git a/lib/src/domain/common/digest_request.dart b/lib/src/domain/common/digest_request.dart new file mode 100644 index 000000000..a32bde983 --- /dev/null +++ b/lib/src/domain/common/digest_request.dart @@ -0,0 +1,102 @@ +import 'dart:convert'; +import 'package:dio/dio.dart' as Dio; +import 'package:crypto/crypto.dart' as crypto; +import 'dart:math' as math; + +class DigestRequest { + final md5 = crypto.md5; + + String generateCnonce() { + final rnd = math.Random.secure(); + var values = List.generate(32, (i) => rnd.nextInt(256)); + return base64Url.encode(values).substring(0, 8); + } + + String generateHA1({String realm, String username, String password}) { + final ha1CredentialsData = + Utf8Encoder().convert('$username:$realm:$password'); + final ha1 = md5.convert(ha1CredentialsData).toString(); + + return ha1; + } + + String generateHA2({String method, String uri}) { + final ha2Data = Utf8Encoder().convert('$method:$uri'); + final ha2 = md5.convert(ha2Data).toString(); + + return ha2; + } + + String generateResponseString( + {String ha1, + String ha2, + String nonce, + String nonceCount, + String cnonce, + String qop}) { + final responseData = + Utf8Encoder().convert('$ha1:$nonce:$nonceCount:$cnonce:$qop:$ha2'); + final response = md5.convert(responseData).toString(); + + return response; + } + + Map parsetAuthorizationHeader({String source}) { + final authHeaderParts = + source.substring(7).split(',').map((item) => item.trim()); + var authenticate = Map(); + + for (final part in authHeaderParts) { + final kv = part.split('='); + authenticate[kv[0]] = + kv.getRange(1, kv.length).join('=').replaceAll('"', ''); + } + + return authenticate; + } + + Future request( + {String uri, String login, String password}) async { + final path = '/json_rpc'; + final method = 'POST'; + final url = Uri.http(uri, path); + final dio = Dio.Dio(); + + Map headers = {'Content-type': 'application/json'}; + String body = + json.encode({"jsonrpc": "2.0", "id": "0", "method": "get_info"}); + + var credentialsResponse = await dio.post(url.toString(), + options: Dio.Options(headers: headers, validateStatus: (_) => true)); + var resHeaeders = credentialsResponse.headers; + final authenticate = + parsetAuthorizationHeader(source: resHeaeders['www-authenticate'].first); + final qop = authenticate['qop']; + final algorithm = 'MD5'; + final realm = 'monero-rpc'; + final nonce = authenticate['nonce']; + final cnonce = generateCnonce(); + + var nonceCount = '00000001'; + + final ha1 = generateHA1(realm: realm, username: login, password: password); + final ha2 = generateHA2(method: method, uri: path); + final response = generateResponseString( + ha1: ha1, + ha2: ha2, + nonce: nonce, + nonceCount: nonceCount, + cnonce: cnonce, + qop: qop); + + final authorizationHeaders = { + 'Content-type': 'application/json', + 'Authorization': + 'Digest username="$login",realm="$realm",nonce="$nonce",uri="$path",algorithm="$algorithm",qop=$qop,nc=$nonceCount,cnonce="$cnonce",response="$response"' + }; + + final res = await dio.post(url.toString(), + options: Dio.Options(headers: authorizationHeaders), data: body); + return res; + } +} \ No newline at end of file diff --git a/lib/src/domain/common/encrypt.dart b/lib/src/domain/common/encrypt.dart new file mode 100644 index 000000000..b30bbb7a3 --- /dev/null +++ b/lib/src/domain/common/encrypt.dart @@ -0,0 +1,54 @@ +import 'package:encrypt/encrypt.dart'; +import 'package:password/password.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; + +String encrypt({String source, String key, int keyLength = 16}) { + final _key = Key.fromUtf8(key); + final iv = IV.fromLength(keyLength); + final encrypter = Encrypter(AES(_key)); + final encrypted = encrypter.encrypt(source, iv: iv); + + return encrypted.base64; +} + +String decrypt({String source, String key, int keyLength = 16}) { + final _key = Key.fromUtf8(key); + final iv = IV.fromLength(keyLength); + final encrypter = Encrypter(AES(_key)); + final decrypted = encrypter.decrypt64(source, iv: iv); + + return decrypted; +} + +String hash({String source}) { + final algorithm = PBKDF2(); + final hash = Password.hash(source, algorithm); + + return hash; +} + +String encodedPinCode({String pin}) { + final source = '${secrets.salt}$pin'; + + return encrypt(source: source, key: secrets.key); +} + +String decodedPinCode({String pin}) { + final decrypted = decrypt(source: pin, key: secrets.key); + + return decrypted.substring(secrets.key.length, decrypted.length); +} + +String encodeWalletPassword({String password}) { + final source = password; + final _key = secrets.shortKey + secrets.walletSalt; + + return encrypt(source: source, key: _key); +} + +String decodeWalletPassword({String password}) { + final source = password; + final _key = secrets.shortKey + secrets.walletSalt; + + return decrypt(source: source, key: _key); +} diff --git a/lib/src/domain/common/enumerable_item.dart b/lib/src/domain/common/enumerable_item.dart new file mode 100644 index 000000000..01d912a13 --- /dev/null +++ b/lib/src/domain/common/enumerable_item.dart @@ -0,0 +1,16 @@ +import 'package:flutter/foundation.dart'; + +abstract class EnumerableItem { + final T raw; + final String title; + + const EnumerableItem({@required this.title, @required this.raw}); + + @override + String toString() => title; +} + +mixin Serializable on EnumerableItem { + static Serializable deserialize({T raw}) => null; + T serialize() => raw; +} diff --git a/lib/src/domain/common/fetch_price.dart b/lib/src/domain/common/fetch_price.dart new file mode 100644 index 000000000..7c92fefb9 --- /dev/null +++ b/lib/src/domain/common/fetch_price.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/common/fiat_currency.dart'; +import 'package:cake_wallet/src/domain/common/currency_formatter.dart'; + +const fiatApiAuthority = 'fiat-api.cakewallet.com'; +const fiatApiPath = '/v1/rates'; + +Future fetchPriceFor({CryptoCurrency crypto, FiatCurrency fiat}) async { + double price = 0.0; + + try { + final fiatStringified = fiat.toString(); + final uri = + Uri.https(fiatApiAuthority, fiatApiPath, {'convert': fiatStringified}); + final response = await get(uri.toString()); + + if (response.statusCode != 200) { + return 0.0; + } + + final responseJSON = json.decode(response.body); + final data = responseJSON['data']; + + for (final item in data) { + if (item['symbol'] == cryptoToString(crypto)) { + price = item['quote'][fiatStringified]['price']; + break; + } + } + + return price; + } catch (e) { + return price; + } +} diff --git a/lib/src/domain/common/fiat_currency.dart b/lib/src/domain/common/fiat_currency.dart new file mode 100644 index 000000000..4b28e73d1 --- /dev/null +++ b/lib/src/domain/common/fiat_currency.dart @@ -0,0 +1,80 @@ +import 'package:cake_wallet/src/domain/common/enumerable_item.dart'; + +class FiatCurrency extends EnumerableItem with Serializable { + static const all = [ + FiatCurrency.aud, + FiatCurrency.bgn, + FiatCurrency.brl, + FiatCurrency.cad, + FiatCurrency.chf, + FiatCurrency.cny, + FiatCurrency.czk, + FiatCurrency.eur, + FiatCurrency.dkk, + FiatCurrency.gbp, + FiatCurrency.hkd, + FiatCurrency.hrk, + FiatCurrency.huf, + FiatCurrency.idr, + FiatCurrency.ils, + FiatCurrency.inr, + FiatCurrency.isk, + FiatCurrency.jpy, + FiatCurrency.krw, + FiatCurrency.mxn, + FiatCurrency.myr, + FiatCurrency.nok, + FiatCurrency.nzd, + FiatCurrency.php, + FiatCurrency.pln, + FiatCurrency.ron, + FiatCurrency.rub, + FiatCurrency.sek, + FiatCurrency.sgd, + FiatCurrency.thb, + FiatCurrency.usd, + FiatCurrency.zar, + FiatCurrency.vef + ]; + + static const aud = FiatCurrency(symbol: 'AUD'); + static const bgn = FiatCurrency(symbol: 'BGN'); + static const brl = FiatCurrency(symbol: 'BRL'); + static const cad = FiatCurrency(symbol: 'CAD'); + static const chf = FiatCurrency(symbol: 'CHF'); + static const cny = FiatCurrency(symbol: 'CNY'); + static const czk = FiatCurrency(symbol: 'CZK'); + static const eur = FiatCurrency(symbol: 'EUR'); + static const dkk = FiatCurrency(symbol: 'DKK'); + static const gbp = FiatCurrency(symbol: 'GBP'); + static const hkd = FiatCurrency(symbol: 'HKD'); + static const hrk = FiatCurrency(symbol: 'HRK'); + static const huf = FiatCurrency(symbol: 'HUF'); + static const idr = FiatCurrency(symbol: 'IDR'); + static const ils = FiatCurrency(symbol: 'ILS'); + static const inr = FiatCurrency(symbol: 'INR'); + static const isk = FiatCurrency(symbol: 'ISK'); + static const jpy = FiatCurrency(symbol: 'JPY'); + static const krw = FiatCurrency(symbol: 'KRW'); + static const mxn = FiatCurrency(symbol: 'MXN'); + static const myr = FiatCurrency(symbol: 'MYR'); + static const nok = FiatCurrency(symbol: 'NOK'); + static const nzd = FiatCurrency(symbol: 'NZD'); + static const php = FiatCurrency(symbol: 'PHP'); + static const pln = FiatCurrency(symbol: 'PLN'); + static const ron = FiatCurrency(symbol: 'RON'); + static const rub = FiatCurrency(symbol: 'RUB'); + static const sek = FiatCurrency(symbol: 'SEK'); + static const sgd = FiatCurrency(symbol: 'SGD'); + static const thb = FiatCurrency(symbol: 'THB'); + static const usd = FiatCurrency(symbol: 'USD'); + static const zar = FiatCurrency(symbol: 'ZAR'); + static const vef = FiatCurrency(symbol: 'VEF'); + + const FiatCurrency({String symbol}) : super(title: symbol, raw: symbol); + + operator ==(o) => o is FiatCurrency && o.raw == raw; + + @override + int get hashCode => raw.hashCode ^ title.hashCode; +} diff --git a/lib/src/domain/common/get_encryption_key.dart b/lib/src/domain/common/get_encryption_key.dart new file mode 100644 index 000000000..1e532083d --- /dev/null +++ b/lib/src/domain/common/get_encryption_key.dart @@ -0,0 +1,23 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:hive/hive.dart'; + +Future> getEncryptionKey( + {String forKey, FlutterSecureStorage secureStorage}) async { + final stringifiedKey = + await secureStorage.read(key: 'transactionDescriptionsBoxKey'); + List key; + + if (stringifiedKey == null) { + key = Hive.generateSecureKey(); + final keyStringified = key.join(','); + await secureStorage.write( + key: 'transactionDescriptionsBoxKey', value: keyStringified); + } else { + key = stringifiedKey + .split(',') + .map((i) => int.parse(i)) + .toList(); + } + + return key; +} \ No newline at end of file diff --git a/lib/src/domain/common/language.dart b/lib/src/domain/common/language.dart new file mode 100644 index 000000000..8af3c571d --- /dev/null +++ b/lib/src/domain/common/language.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class Language with ChangeNotifier { + + String _currentLanguage; + + Language(this._currentLanguage); + + getCurrentLanguage() => _currentLanguage; + + setCurrentLanguage(String language){ + _currentLanguage = language; + notifyListeners(); + } + +} \ No newline at end of file diff --git a/lib/src/domain/common/mnemotic_item.dart b/lib/src/domain/common/mnemotic_item.dart new file mode 100644 index 000000000..01521c28a --- /dev/null +++ b/lib/src/domain/common/mnemotic_item.dart @@ -0,0 +1,19 @@ +class MnemoticItem { + String get text => _text; + final List dic; + + String _text; + + MnemoticItem({String text, this.dic}) { + _text = text; + } + + bool isCorrect() => dic.contains(text); + + void changeText(String text) { + _text = text; + } + + @override + String toString() => text; +} \ No newline at end of file diff --git a/lib/src/domain/common/node.dart b/lib/src/domain/common/node.dart new file mode 100644 index 000000000..7855cfe89 --- /dev/null +++ b/lib/src/domain/common/node.dart @@ -0,0 +1,49 @@ +import 'package:flutter/foundation.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:hive/hive.dart'; +import 'package:cake_wallet/src/domain/common/digest_request.dart'; + +part 'node.g.dart'; + +@HiveType() +class Node extends HiveObject { + static const boxName = 'Nodes'; + + @HiveField(0) + String uri; + + @HiveField(1) + String login; + + @HiveField(2) + String password; + + Node({@required this.uri, this.login, this.password}); + + Node.fromMap(Map map) + : uri = map['uri'] ?? '', + login = map['login'], + password = map['password']; + + Future requestNode(String uri, {String login, String password}) async { + var resBody; + + if (login != null && password != null) { + final digestRequest = DigestRequest(); + var response = await digestRequest.request( + uri: uri, login: login, password: password); + resBody = response.data; + } else { + final url = Uri.http(uri, '/json_rpc'); + Map headers = {'Content-type': 'application/json'}; + String body = + json.encode({"jsonrpc": "2.0", "id": "0", "method": "get_info"}); + var response = + await http.post(url.toString(), headers: headers, body: body); + resBody = json.decode(response.body); + } + + return !resBody["result"]["offline"]; + } +} diff --git a/lib/src/domain/common/node_list.dart b/lib/src/domain/common/node_list.dart new file mode 100644 index 000000000..6d19a8916 --- /dev/null +++ b/lib/src/domain/common/node_list.dart @@ -0,0 +1,23 @@ +import 'package:flutter/services.dart'; +import 'package:hive/hive.dart'; +import "package:yaml/yaml.dart"; +import 'package:cake_wallet/src/domain/common/node.dart'; + +Future> loadDefaultNodes() async { + String nodesRaw = await rootBundle.loadString('assets/node_list.yml'); + List nodes = loadYaml(nodesRaw); + return nodes.map((raw) => Node.fromMap(raw)).toList(); +} + +Future resetToDefault(Box nodeSource) async { + final nodes = await loadDefaultNodes(); + await nodeSource.clear(); + + Map enteties = {}; + + for (var i = 0; i < nodes.length; i++) { + enteties[i] = nodes[i]; + } + + await nodeSource.putAll(enteties); +} \ No newline at end of file diff --git a/lib/src/domain/common/parseBoolFromString.dart b/lib/src/domain/common/parseBoolFromString.dart new file mode 100644 index 000000000..dbcbdedd4 --- /dev/null +++ b/lib/src/domain/common/parseBoolFromString.dart @@ -0,0 +1,3 @@ +bool parseBoolFromString(String string) { + return string.toString() == 'true'; +} \ No newline at end of file diff --git a/lib/src/domain/common/pending_transaction.dart b/lib/src/domain/common/pending_transaction.dart new file mode 100644 index 000000000..bbf069f30 --- /dev/null +++ b/lib/src/domain/common/pending_transaction.dart @@ -0,0 +1,25 @@ +import 'package:flutter/foundation.dart'; +import 'package:cw_monero/transaction_history.dart' as transactionHistory; +import 'package:cw_monero/structs/pending_transaction.dart'; +import 'package:cake_wallet/src/domain/monero/monero_amount_format.dart'; + +class PendingTransaction { + final String amount; + final String fee; + final String hash; + + int _pointerAddress; + + PendingTransaction( + {@required this.amount, @required this.fee, @required this.hash}); + + PendingTransaction.fromTransactionDescription( + PendingTransactionDescription transactionDescription) + : amount = moneroAmountToString(amount: transactionDescription.amount), + fee = moneroAmountToString(amount: transactionDescription.fee), + hash = transactionDescription.hash, + _pointerAddress = transactionDescription.pointerAddress; + + Future commit() async => transactionHistory + .commitTransactionFromPointerAddress(address: _pointerAddress); +} diff --git a/lib/src/domain/common/qr_scanner.dart b/lib/src/domain/common/qr_scanner.dart new file mode 100644 index 000000000..b3e8eee84 --- /dev/null +++ b/lib/src/domain/common/qr_scanner.dart @@ -0,0 +1,15 @@ +import 'package:barcode_scan/barcode_scan.dart'; + +var isQrScannerShown = false; + +Future presentQRScanner() async { + isQrScannerShown = true; + try { + final result = await BarcodeScanner.scan(); + isQrScannerShown = false; + return result; + } catch (e) { + isQrScannerShown = false; + throw e; + } +} diff --git a/lib/src/domain/common/secret_store_key.dart b/lib/src/domain/common/secret_store_key.dart new file mode 100644 index 000000000..3a25a7132 --- /dev/null +++ b/lib/src/domain/common/secret_store_key.dart @@ -0,0 +1,27 @@ +enum SecretStoreKey { moneroWalletPassword, pinCodePassword } + +const moneroWalletPassword = "MONERO_WALLET_PASSWORD"; +const pinCodePassword = "PIN_CODE_PASSWORD"; + +String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) { + var _key = ""; + + switch (key) { + case SecretStoreKey.moneroWalletPassword: + { + _key = moneroWalletPassword + "_" + walletName.toUpperCase(); + } + break; + + case SecretStoreKey.pinCodePassword: + { + _key = pinCodePassword; + } + break; + + default: + {} + } + + return _key; +} \ No newline at end of file diff --git a/lib/src/domain/common/sync_status.dart b/lib/src/domain/common/sync_status.dart new file mode 100644 index 000000000..beef7b782 --- /dev/null +++ b/lib/src/domain/common/sync_status.dart @@ -0,0 +1,66 @@ +import 'package:cake_wallet/generated/i18n.dart'; + +abstract class SyncStatus { + const SyncStatus(); + + double progress(); + + String title(); +} + +class SyncingSyncStatus extends SyncStatus { + final int height; + final int blockchainHeight; + final int refreshHeight; + + SyncingSyncStatus(this.height, this.blockchainHeight, this.refreshHeight); + + double progress() { + final line = blockchainHeight - refreshHeight; + final diff = line - (blockchainHeight - height); + return diff <= 0 ? 0.0 : diff / line; + } + + String title() => S.current.sync_status_syncronizing; + + @override + String toString() => '${blockchainHeight - height}'; +} + +class SyncedSyncStatus extends SyncStatus { + double progress() => 1.0; + + String title() => S.current.sync_status_syncronized; +} + +class NotConnectedSyncStatus extends SyncStatus { + const NotConnectedSyncStatus(); + + double progress() => 0.0; + + String title() => S.current.sync_status_not_connected; +} + +class StartingSyncStatus extends SyncStatus { + double progress() => 0.0; + + String title() => S.current.sync_status_starting_sync; +} + +class FailedSyncStatus extends SyncStatus { + double progress() => 1.0; + + String title() => S.current.sync_status_failed_connect; +} + +class ConnectingSyncStatus extends SyncStatus { + double progress() => 0.0; + + String title() => S.current.sync_status_connecting; +} + +class ConnectedSyncStatus extends SyncStatus { + double progress() => 0.0; + + String title() => S.current.sync_status_connected; +} \ No newline at end of file diff --git a/lib/src/domain/common/transaction_creation_credentials.dart b/lib/src/domain/common/transaction_creation_credentials.dart new file mode 100644 index 000000000..6437cc81e --- /dev/null +++ b/lib/src/domain/common/transaction_creation_credentials.dart @@ -0,0 +1 @@ +abstract class TransactionCreationCredentials {} \ No newline at end of file diff --git a/lib/src/domain/common/transaction_direction.dart b/lib/src/domain/common/transaction_direction.dart new file mode 100644 index 000000000..a82420e2c --- /dev/null +++ b/lib/src/domain/common/transaction_direction.dart @@ -0,0 +1,17 @@ +enum TransactionDirection { incoming, outgoing } + +TransactionDirection parseTransactionDirectionFromInt(int raw) { + switch (raw) { + case 0: return TransactionDirection.incoming; + case 1: return TransactionDirection.outgoing; + default: return null; + } +} + +TransactionDirection parseTransactionDirectionFromNumber(String raw) { + switch (raw) { + case "0": return TransactionDirection.incoming; + case "1": return TransactionDirection.outgoing; + default: return null; + } +} \ No newline at end of file diff --git a/lib/src/domain/common/transaction_history.dart b/lib/src/domain/common/transaction_history.dart new file mode 100644 index 000000000..ed449f555 --- /dev/null +++ b/lib/src/domain/common/transaction_history.dart @@ -0,0 +1,10 @@ +import 'package:rxdart/rxdart.dart'; +import 'package:cake_wallet/src/domain/common/transaction_info.dart'; + +abstract class TransactionHistory { + Observable> transactions; + Future> getAll(); + Future count(); + Future refresh(); + Future update(); +} diff --git a/lib/src/domain/common/transaction_info.dart b/lib/src/domain/common/transaction_info.dart new file mode 100644 index 000000000..2ad5c58a0 --- /dev/null +++ b/lib/src/domain/common/transaction_info.dart @@ -0,0 +1,46 @@ +import 'package:cake_wallet/src/domain/monero/monero_amount_format.dart'; +import 'package:cw_monero/structs/transaction_info_row.dart'; +import 'package:cake_wallet/src/domain/common/parseBoolFromString.dart'; +import 'package:cake_wallet/src/domain/common/transaction_direction.dart'; + +class TransactionInfo { + final String id; + final int height; + final TransactionDirection direction; + final DateTime date; + final int accountIndex; + final bool isPending; + final int amount; + String recipientAddress; + String _fiatAmount; + + TransactionInfo.fromMap(Map map) + : id = map['hash'] ?? '', + height = map['height'] ?? '', + direction = parseTransactionDirectionFromNumber(map['direction']) ?? + TransactionDirection.incoming, + date = DateTime.fromMillisecondsSinceEpoch( + (int.parse(map['timestamp']) ?? 0) * 1000), + isPending = parseBoolFromString(map['isPending']), + amount = map['amount'], + accountIndex = int.parse(map['accountIndex']); + + TransactionInfo.fromRow(TransactionInfoRow row) + : id = row.getHash(), + height = row.blockHeight, + direction = parseTransactionDirectionFromInt(row.direction) ?? + TransactionDirection.incoming, + date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000), + isPending = row.isPending != 0, + amount = row.getAmount(), + accountIndex = row.subaddrAccount; + + TransactionInfo(this.id, this.height, this.direction, this.date, + this.isPending, this.amount, this.accountIndex); + + String amountFormatted() => '${moneroAmountToString(amount: amount)} XMR'; + + String fiatAmount() => _fiatAmount ?? ''; + + changeFiatAmount(String amount) => _fiatAmount = amount; +} diff --git a/lib/src/domain/common/transaction_priority.dart b/lib/src/domain/common/transaction_priority.dart new file mode 100644 index 000000000..f00058b7e --- /dev/null +++ b/lib/src/domain/common/transaction_priority.dart @@ -0,0 +1,56 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/enumerable_item.dart'; + +class TransactionPriority extends EnumerableItem with Serializable { + static const all = [ + TransactionPriority.slow, + TransactionPriority.regular, + TransactionPriority.medium, + TransactionPriority.fast, + TransactionPriority.fastest + ]; + static const slow = TransactionPriority(title: 'Slow', raw: 0); + static const regular = TransactionPriority(title: 'Regular', raw: 1); + static const medium = TransactionPriority(title: 'Medium', raw: 2); + static const fast = TransactionPriority(title: 'Fast', raw: 3); + static const fastest = TransactionPriority(title: 'Fastest', raw: 4); + static const standart = slow; + + static TransactionPriority deserialize({int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return regular; + case 2: + return medium; + case 3: + return fast; + case 4: + return fastest; + default: + return null; + } + } + + const TransactionPriority({String title, int raw}) + : super(title: title, raw: raw); + + @override + String toString() { + switch (this) { + case TransactionPriority.slow: + return S.current.transaction_priority_slow; + case TransactionPriority.regular: + return S.current.transaction_priority_regular; + case TransactionPriority.medium: + return S.current.transaction_priority_medium; + case TransactionPriority.fast: + return S.current.transaction_priority_fast; + case TransactionPriority.fastest: + return S.current.transaction_priority_fastest; + default: + return ''; + } + } +} diff --git a/lib/src/domain/common/wallet.dart b/lib/src/domain/common/wallet.dart new file mode 100644 index 000000000..e2cca5e58 --- /dev/null +++ b/lib/src/domain/common/wallet.dart @@ -0,0 +1,61 @@ +import 'package:rxdart/rxdart.dart'; +import 'package:cake_wallet/src/domain/common/sync_status.dart'; +import 'package:cake_wallet/src/domain/common/transaction_history.dart'; +import 'package:cake_wallet/src/domain/common/wallet_type.dart'; +import 'package:cake_wallet/src/domain/common/transaction_creation_credentials.dart'; +import 'package:cake_wallet/src/domain/common/pending_transaction.dart'; +import 'package:cake_wallet/src/domain/common/balance.dart'; +import 'package:cake_wallet/src/domain/common/node.dart'; + +abstract class Wallet { + WalletType getType(); + + WalletType walletType; + + Observable onBalanceChange; + + Observable syncStatus; + + Observable get onNameChange; + + Observable get onAddressChange; + + String get name; + + String get address; + + Future updateInfo(); + + Future getFilename(); + + Future getName(); + + Future getAddress(); + + Future getSeed(); + + Future> getKeys(); + + Future getFullBalance(); + + Future getUnlockedBalance(); + + Future getCurrentHeight(); + + Future getNodeHeight(); + + Future isConnected(); + + Future close(); + + TransactionHistory getHistory(); + + Future connectToNode({Node node, bool useSSL = false, bool isLightWallet = false}); + + Future startSync(); + + Future createTransaction( + TransactionCreationCredentials credentials); + + Future rescan({int restoreHeight = 0}); +} diff --git a/lib/src/domain/common/wallet_description.dart b/lib/src/domain/common/wallet_description.dart new file mode 100644 index 000000000..d62f37970 --- /dev/null +++ b/lib/src/domain/common/wallet_description.dart @@ -0,0 +1,8 @@ +import 'package:cake_wallet/src/domain/common/wallet_type.dart'; + +class WalletDescription { + final String name; + final WalletType type; + + WalletDescription({this.name, this.type}); +} \ No newline at end of file diff --git a/lib/src/domain/common/wallet_info.dart b/lib/src/domain/common/wallet_info.dart new file mode 100644 index 000000000..4bf382d11 --- /dev/null +++ b/lib/src/domain/common/wallet_info.dart @@ -0,0 +1,27 @@ +import 'package:cake_wallet/src/domain/common/wallet_type.dart'; +import 'package:hive/hive.dart'; + +part 'wallet_info.g.dart'; + +@HiveType() +class WalletInfo extends HiveObject { + static const boxName = 'WalletInfo'; + + @HiveField(0) + String id; + + @HiveField(1) + String name; + + @HiveField(2) + WalletType type; + + @HiveField(3) + bool isRecovery; + + @HiveField(4) + int restoreHeight; + + WalletInfo( + {this.id, this.name, this.type, this.isRecovery, this.restoreHeight}); +} diff --git a/lib/src/domain/common/wallet_type.dart b/lib/src/domain/common/wallet_type.dart new file mode 100644 index 000000000..e66799413 --- /dev/null +++ b/lib/src/domain/common/wallet_type.dart @@ -0,0 +1,39 @@ +import 'package:hive/hive.dart'; + +part 'wallet_type.g.dart'; + +@HiveType() +enum WalletType { + @HiveField(0) + monero, + + @HiveField(1) + none +} + +int serializeToInt(WalletType type) { + switch (type) { + case WalletType.monero: + return 0; + default: + return -1; + } +} + +WalletType deserializeToInt(int raw) { + switch (raw) { + case 0: + return WalletType.monero; + default: + return null; + } +} + +String walletTypeToString(WalletType type) { + switch (type) { + case WalletType.monero: + return 'Monero'; + default: + return ''; + } +} \ No newline at end of file diff --git a/lib/src/domain/common/wallets_manager.dart b/lib/src/domain/common/wallets_manager.dart new file mode 100644 index 000000000..27eb12307 --- /dev/null +++ b/lib/src/domain/common/wallets_manager.dart @@ -0,0 +1,18 @@ +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/common/wallet_description.dart'; + +abstract class WalletsManager { + Future create(String name, String password); + + Future restoreFromSeed( + String name, String password, String seed, int restoreHeight); + + Future restoreFromKeys(String name, String password, + int restoreHeight, String address, String viewKey, String spendKey); + + Future openWallet(String name, String password); + + Future isWalletExit(String name); + + Future remove(WalletDescription wallet); +} \ No newline at end of file diff --git a/lib/src/domain/exchange/changenow/changenow_exchange_provider.dart b/lib/src/domain/exchange/changenow/changenow_exchange_provider.dart new file mode 100644 index 000000000..e2c5dcc28 --- /dev/null +++ b/lib/src/domain/exchange/changenow/changenow_exchange_provider.dart @@ -0,0 +1,139 @@ +import 'dart:convert'; +import 'package:cake_wallet/src/domain/exchange/trade_not_found_exeption.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_pair.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/limits.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_state.dart'; +import 'package:cake_wallet/src/domain/exchange/changenow/changenow_request.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_not_created_exeption.dart'; + +class ChangeNowExchangeProvider extends ExchangeProvider { + static const apiUri = 'https://changenow.io/api/v1'; + static const apiKey = secrets.change_now_api_key; + static const _exchangeAmountUriSufix = '/exchange-amount/'; + static const _transactionsUriSufix = '/transactions/'; + static const _minAmountUriSufix = '/min-amount/'; + + String get title => 'ChangeNOW'; + ExchangeProviderDescription get description => + ExchangeProviderDescription.changeNow; + + ChangeNowExchangeProvider() { + pairList = CryptoCurrency.all + .map((i) { + return CryptoCurrency.all.map((k) { + if (i == CryptoCurrency.btc && k == CryptoCurrency.xmr) { + return ExchangePair(from: i, to: k, reverse: false); + } + + if (i == CryptoCurrency.xmr && k == CryptoCurrency.btc) { + return null; + } + + return ExchangePair(from: i, to: k, reverse: true); + }).where((c) => c != null); + }) + .expand((i) => i) + .toList(); + } + + Future fetchLimits({CryptoCurrency from, CryptoCurrency to}) async { + final symbol = from.toString() + '_' + to.toString(); + final url = apiUri + _minAmountUriSufix + symbol; + final response = await get(url); + final responseJSON = json.decode(response.body); + final double min = responseJSON['minAmount']; + + return Limits(min: min, max: null); + } + + Future createTrade({TradeRequest request}) async { + const url = apiUri + _transactionsUriSufix + apiKey; + final _request = request as ChangeNowRequest; + final body = { + 'from': _request.from.toString(), + 'to': _request.to.toString(), + 'address': _request.address, + 'amount': _request.amount, + 'refundAddress': _request.refundAddress + }; + + final response = await post(url, + headers: {'Content-Type': 'application/json'}, body: json.encode(body)); + + if (response.statusCode != 200) { + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body); + final error = responseJSON['message']; + throw TradeNotCreatedException(description, description: error); + } + + throw TradeNotCreatedException(description); + } + + final responseJSON = json.decode(response.body); + + return Trade( + id: responseJSON['id'], + from: _request.from, + to: _request.to, + provider: description, + inputAddress: responseJSON['payinAddress'], + refundAddress: responseJSON['refundAddress'], + extraId: responseJSON["payinExtraId"], + createdAt: DateTime.now(), + amount: _request.amount, + state: TradeState.created); + } + + Future findTradeById({@required String id}) async { + final url = apiUri + _transactionsUriSufix + id + '/' + apiKey; + final response = await get(url); + + if (response.statusCode != 200) { + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body); + final error = responseJSON['message']; + throw TradeNotFoundException(id, + provider: description, description: error); + } + + throw TradeNotFoundException(id, provider: description); + } + + final responseJSON = json.decode(response.body); + + return Trade( + id: id, + from: CryptoCurrency.fromString(responseJSON['fromCurrency']), + to: CryptoCurrency.fromString(responseJSON['toCurrency']), + provider: description, + inputAddress: responseJSON['payinAddress'], + amount: responseJSON['expectedSendAmount'].toString(), + state: TradeState.deserialize(raw: responseJSON['status']), + extraId: responseJSON['payinExtraId'], + outputTransaction: responseJSON['payoutHash']); + } + + Future calculateAmount( + {CryptoCurrency from, CryptoCurrency to, double amount}) async { + final url = apiUri + + _exchangeAmountUriSufix + + amount.toString() + + '/' + + from.toString() + + '_' + + to.toString(); + final response = await get(url); + final responseJSON = json.decode(response.body); + + return responseJSON['estimatedAmount']; + } +} diff --git a/lib/src/domain/exchange/changenow/changenow_request.dart b/lib/src/domain/exchange/changenow/changenow_request.dart new file mode 100644 index 000000000..827074ad6 --- /dev/null +++ b/lib/src/domain/exchange/changenow/changenow_request.dart @@ -0,0 +1,18 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; + +class ChangeNowRequest extends TradeRequest { + CryptoCurrency from; + CryptoCurrency to; + String address; + String amount; + String refundAddress; + + ChangeNowRequest( + {@required this.from, + @required this.to, + @required this.address, + @required this.amount, + @required this.refundAddress}); +} diff --git a/lib/src/domain/exchange/exchange_pair.dart b/lib/src/domain/exchange/exchange_pair.dart new file mode 100644 index 000000000..1b200a2a2 --- /dev/null +++ b/lib/src/domain/exchange/exchange_pair.dart @@ -0,0 +1,9 @@ +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; + +class ExchangePair { + final CryptoCurrency from; + final CryptoCurrency to; + final bool reverse; + + ExchangePair({this.from, this.to, this.reverse = true}); +} \ No newline at end of file diff --git a/lib/src/domain/exchange/exchange_provider.dart b/lib/src/domain/exchange/exchange_provider.dart new file mode 100644 index 000000000..5051de18d --- /dev/null +++ b/lib/src/domain/exchange/exchange_provider.dart @@ -0,0 +1,22 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_pair.dart'; +import 'package:cake_wallet/src/domain/exchange/limits.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; + +abstract class ExchangeProvider { + String get title; + List pairList; + ExchangeProviderDescription description; + + @override + String toString() => title; + + Future fetchLimits({CryptoCurrency from, CryptoCurrency to}); + Future createTrade({TradeRequest request}); + Future findTradeById({@required String id}); + Future calculateAmount( + {CryptoCurrency from, CryptoCurrency to, double amount}); +} diff --git a/lib/src/domain/exchange/exchange_provider_description.dart b/lib/src/domain/exchange/exchange_provider_description.dart new file mode 100644 index 000000000..eaed56a97 --- /dev/null +++ b/lib/src/domain/exchange/exchange_provider_description.dart @@ -0,0 +1,24 @@ +import 'package:cake_wallet/src/domain/common/enumerable_item.dart'; + +class ExchangeProviderDescription extends EnumerableItem + with Serializable { + static const xmrto = ExchangeProviderDescription(title: 'XMR.TO', raw: 0); + static const changeNow = + ExchangeProviderDescription(title: 'ChangeNOW', raw: 1); + + static ExchangeProviderDescription deserialize({int raw}) { + switch (raw) { + case 0: + return xmrto; + case 1: + return changeNow; + default: + return null; + } + } + + final String title; + + const ExchangeProviderDescription({this.title, int raw}) + : super(title: title, raw: raw); +} diff --git a/lib/src/domain/exchange/limits.dart b/lib/src/domain/exchange/limits.dart new file mode 100644 index 000000000..a005282ca --- /dev/null +++ b/lib/src/domain/exchange/limits.dart @@ -0,0 +1,6 @@ +class Limits { + final double min; + final double max; + + const Limits({this.min, this.max}); +} \ No newline at end of file diff --git a/lib/src/domain/exchange/trade.dart b/lib/src/domain/exchange/trade.dart new file mode 100644 index 000000000..177deb3ce --- /dev/null +++ b/lib/src/domain/exchange/trade.dart @@ -0,0 +1,103 @@ +import 'package:hive/hive.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_state.dart'; + +part 'trade.g.dart'; + +@HiveType() +class Trade extends HiveObject { + static const boxName = 'Trades'; + + @HiveField(0) + String id; + + @HiveField(1) + int providerRaw; + + ExchangeProviderDescription get provider => + ExchangeProviderDescription.deserialize(raw: providerRaw); + + @HiveField(2) + int fromRaw; + + CryptoCurrency get from => CryptoCurrency.deserialize(raw: fromRaw); + + @HiveField(3) + int toRaw; + + CryptoCurrency get to => CryptoCurrency.deserialize(raw: toRaw); + + @HiveField(4) + String stateRaw; + + TradeState get state => TradeState.deserialize(raw: stateRaw); + + @HiveField(5) + DateTime createdAt; + + @HiveField(6) + DateTime expiredAt; + + @HiveField(7) + String amount; + + @HiveField(8) + String inputAddress; + + @HiveField(9) + String extraId; + + @HiveField(10) + String outputTransaction; + + @HiveField(11) + String refundAddress; + + @HiveField(12) + String walletId; + + static Trade fromMap(Map map) { + return Trade( + id: map['id'], + provider: ExchangeProviderDescription.deserialize(raw: map['provider']), + from: CryptoCurrency.deserialize(raw: map['input']), + to: CryptoCurrency.deserialize(raw: map['output']), + createdAt: map['date'] != null + ? DateTime.fromMillisecondsSinceEpoch(map['date']) + : null, + amount: map['amount'], + walletId: map['wallet_id']); + } + + Trade( + {this.id, + ExchangeProviderDescription provider, + CryptoCurrency from, + CryptoCurrency to, + TradeState state, + this.createdAt, + this.expiredAt, + this.amount, + this.inputAddress, + this.extraId, + this.outputTransaction, + this.refundAddress, + this.walletId}) + : providerRaw = provider?.raw, + fromRaw = from?.raw, + toRaw = to?.raw, + stateRaw = state?.raw; + + Map toMap() { + return { + 'id': id, + 'provider': provider.serialize(), + 'input': from.serialize(), + 'output': to.serialize(), + 'date': createdAt != null ? createdAt.millisecondsSinceEpoch : null, + 'amount': amount, + 'wallet_id': walletId + }; + } +} diff --git a/lib/src/domain/exchange/trade_not_created_exeption.dart b/lib/src/domain/exchange/trade_not_created_exeption.dart new file mode 100644 index 000000000..ea0dcda7e --- /dev/null +++ b/lib/src/domain/exchange/trade_not_created_exeption.dart @@ -0,0 +1,19 @@ +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class TradeNotCreatedException implements Exception { + ExchangeProviderDescription provider; + String description; + + TradeNotCreatedException(this.provider, {this.description = ''}); + + @override + String toString() { + var text = provider != null + ? S.current.trade_for_not_created(provider.title) + : S.current.trade_not_created; + text += ' $description'; + + return text; + } +} diff --git a/lib/src/domain/exchange/trade_not_found_exeption.dart b/lib/src/domain/exchange/trade_not_found_exeption.dart new file mode 100644 index 000000000..b00d75657 --- /dev/null +++ b/lib/src/domain/exchange/trade_not_found_exeption.dart @@ -0,0 +1,20 @@ +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class TradeNotFoundException implements Exception { + String tradeId; + ExchangeProviderDescription provider; + String description; + + TradeNotFoundException(this.tradeId, {this.provider, this.description = ''}); + + @override + String toString() { + var text = tradeId != null && provider != null + ? S.current.trade_id_not_found(tradeId, provider.title) + : S.current.trade_not_found; + text += ' $description'; + + return text; + } +} diff --git a/lib/src/domain/exchange/trade_request.dart b/lib/src/domain/exchange/trade_request.dart new file mode 100644 index 000000000..fb75ef14c --- /dev/null +++ b/lib/src/domain/exchange/trade_request.dart @@ -0,0 +1 @@ +abstract class TradeRequest {} \ No newline at end of file diff --git a/lib/src/domain/exchange/trade_state.dart b/lib/src/domain/exchange/trade_state.dart new file mode 100644 index 000000000..6121bfb45 --- /dev/null +++ b/lib/src/domain/exchange/trade_state.dart @@ -0,0 +1,68 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/common/enumerable_item.dart'; + +class TradeState extends EnumerableItem with Serializable { + static const pending = TradeState(raw: 'pending', title: 'Pending'); + static const confirming = TradeState(raw: 'confirming', title: 'Confirming'); + static const trading = TradeState(raw: 'trading', title: 'Trading'); + static const traded = TradeState(raw: 'traded', title: 'Traded'); + static const complete = TradeState(raw: 'complete', title: 'Complete'); + static const toBeCreated = + TradeState(raw: 'TO_BE_CREATED', title: 'To be created'); + static const unpaid = TradeState(raw: 'UNPAID', title: 'Unpaid'); + static const underpaid = TradeState(raw: 'UNDERPAID', title: 'Underpaid'); + static const paidUnconfirmed = + TradeState(raw: 'PAID_UNCONFIRMED', title: 'Paid unconfirmed'); + static const paid = TradeState(raw: 'PAID', title: 'Paid'); + static const btcSent = TradeState(raw: 'BTC_SENT', title: 'Btc sent'); + static const timeout = TradeState(raw: 'TIMED_OUT', title: 'Timeout'); + static const notFound = TradeState(raw: 'NOT_FOUND', title: 'Not found'); + static const created = TradeState(raw: 'created', title: 'Created'); + static const finished = TradeState(raw: 'finished', title: 'Finished'); + static const waiting = TradeState(raw: 'waiting', title: 'Waiting'); + + static TradeState deserialize({String raw}) { + switch (raw) { + case 'pending': + return pending; + case 'confirming': + return confirming; + case 'trading': + return trading; + case 'traded': + return traded; + case 'complete': + return complete; + case 'TO_BE_CREATED': + return toBeCreated; + case 'UNPAID': + return unpaid; + case 'UNDERPAID': + return underpaid; + case 'PAID_UNCONFIRMED': + return paidUnconfirmed; + case 'PAID': + return paid; + case 'BTC_SENT': + return btcSent; + case 'TIMED_OUT': + return timeout; + case 'created': + return created; + case 'finished': + return finished; + case 'waiting': + return waiting; + default: + return null; + } + } + + const TradeState({@required String raw, @required String title}) + : super(raw: raw, title: title); + + operator ==(o) => o is TradeState && o.raw == raw; + + @override + int get hashCode => raw.hashCode ^ title.hashCode; +} diff --git a/lib/src/domain/exchange/xmrto/xmrto_exchange_provider.dart b/lib/src/domain/exchange/xmrto/xmrto_exchange_provider.dart new file mode 100644 index 000000000..9cf75983a --- /dev/null +++ b/lib/src/domain/exchange/xmrto/xmrto_exchange_provider.dart @@ -0,0 +1,170 @@ +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_pair.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/limits.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_state.dart'; +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_not_created_exeption.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_not_found_exeption.dart'; + +class XMRTOExchangeProvider extends ExchangeProvider { + static const userAgent = 'CakeWallet/XMR iOS'; + static const originalApiUri = 'https://xmr.to/api/v2/xmr2btc'; + static const proxyApiUri = 'https://xmrproxy.net/api/v2/xmr2btc'; + static const _orderParameterUriSufix = '/order_parameter_query'; + static const _orderStatusUriSufix = '/order_status_query/'; + static const _orderCreateUriSufix = '/order_create/'; + static String _apiUri = ''; + + static Future getApiUri() async { + if (_apiUri != null && _apiUri.isNotEmpty) { + return _apiUri; + } + + const url = originalApiUri + _orderParameterUriSufix; + final response = + await get(url, headers: {'Content-Type': 'application/json'}); + _apiUri = response.statusCode == 403 ? proxyApiUri : originalApiUri; + + return _apiUri; + } + + String get title => 'XMR.TO'; + + ExchangeProviderDescription get description => + ExchangeProviderDescription.xmrto; + + List pairList = [ + ExchangePair( + from: CryptoCurrency.xmr, to: CryptoCurrency.btc, reverse: false) + ]; + + double _rate = 0; + + Future fetchLimits({CryptoCurrency from, CryptoCurrency to}) async { + final url = await getApiUri() + _orderParameterUriSufix; + final response = await get(url); + + if (response.statusCode != 200) { + return Limits(min: 0, max: 0); + } + + final responseJSON = json.decode(response.body); + final double min = responseJSON['lower_limit']; + final double max = responseJSON['upper_limit']; + + return Limits(min: min, max: max); + } + + Future createTrade({TradeRequest request}) async { + final _request = request as XMRTOTradeRequest; + final url = await getApiUri() + _orderCreateUriSufix; + final body = { + 'btc_amount': _request.amount, + 'btc_dest_address': _request.address + }; + final response = await post(url, + headers: {'Content-Type': 'application/json'}, body: json.encode(body)); + + if (response.statusCode != 201) { + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body); + throw TradeNotCreatedException(description, + description: responseJSON['error_msg']); + } + + throw TradeNotCreatedException(description); + } + + final responseJSON = json.decode(response.body); + final uuid = responseJSON["uuid"]; + + return Trade( + id: uuid, + provider: description, + from: _request.from, + to: _request.to, + state: TradeState.created, + amount: _request.amount, + createdAt: DateTime.now()); + } + + Future findTradeById({@required String id}) async { + const headers = { + 'Content-Type': 'application/json', + 'User-Agent': userAgent + }; + final url = await getApiUri() + _orderStatusUriSufix; + final body = {'uuid': id}; + final response = await post(url, headers: headers, body: json.encode(body)); + + if (response.statusCode != 200) { + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body); + final error = responseJSON['error_msg']; + throw TradeNotFoundException(id, + provider: description, description: error); + } + + throw TradeNotFoundException(id, provider: description); + } + + final responseJSON = json.decode(response.body); + final address = responseJSON['xmr_receiving_integrated_address']; + final paymentId = responseJSON['xmr_required_payment_id_short']; + final amount = responseJSON['xmr_amount_total'].toString(); + final stateRaw = responseJSON['state']; + final expiredAtRaw = responseJSON['expires_at']; + final expiredAt = DateTime.parse(expiredAtRaw).toLocal(); + final outputTransaction = responseJSON['btc_transaction_id']; + final state = TradeState.deserialize(raw: stateRaw); + + return Trade( + id: id, + provider: description, + from: CryptoCurrency.xmr, + to: CryptoCurrency.btc, + inputAddress: address, + extraId: paymentId, + expiredAt: expiredAt, + amount: amount, + state: state, + outputTransaction: outputTransaction); + } + + Future calculateAmount( + {CryptoCurrency from, CryptoCurrency to, double amount}) async { + if (from != CryptoCurrency.xmr && to != CryptoCurrency.btc) { + return 0; + } + + if (_rate == null || _rate == 0) { + _rate = await _fetchRates(); + } + + final double result = _rate * amount; + + return double.parse(result.toStringAsFixed(12)); + } + + Future _fetchRates() async { + try { + final url = await getApiUri() + _orderParameterUriSufix; + final response = + await get(url, headers: {'Content-Type': 'application/json'}); + final responseJSON = json.decode(response.body); + double btcprice = responseJSON['price']; + double price = 1 / btcprice; + return price; + } catch (e) { + print(e.toString()); + return 0.0; + } + } +} diff --git a/lib/src/domain/exchange/xmrto/xmrto_trade_request.dart b/lib/src/domain/exchange/xmrto/xmrto_trade_request.dart new file mode 100644 index 000000000..426a78d46 --- /dev/null +++ b/lib/src/domain/exchange/xmrto/xmrto_trade_request.dart @@ -0,0 +1,18 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; + +class XMRTOTradeRequest extends TradeRequest { + final CryptoCurrency from; + final CryptoCurrency to; + final String amount; + final String address; + final String refundAddress; + + XMRTOTradeRequest( + {@required this.from, + @required this.to, + @required this.amount, + @required this.address, + @required this.refundAddress}); +} diff --git a/lib/src/domain/monero/account.dart b/lib/src/domain/monero/account.dart new file mode 100644 index 000000000..3e7c5204b --- /dev/null +++ b/lib/src/domain/monero/account.dart @@ -0,0 +1,16 @@ +import 'package:cw_monero/structs/account_row.dart'; + +class Account { + final int id; + final String label; + + Account({this.id, this.label}); + + Account.fromMap(Map map) + : this.id = map['id'] == null ? 0 : int.parse(map['id']), + this.label = map['label'] ?? ''; + + Account.fromRow(AccountRow row) + : this.id = row.getId(), + this.label = row.getLabel(); +} diff --git a/lib/src/domain/monero/account_list.dart b/lib/src/domain/monero/account_list.dart new file mode 100644 index 000000000..52ce739ff --- /dev/null +++ b/lib/src/domain/monero/account_list.dart @@ -0,0 +1,67 @@ +import 'package:rxdart/rxdart.dart'; +import 'package:cw_monero/account_list.dart' as accountListAPI; +import 'package:cake_wallet/src/domain/monero/account.dart'; + +class AccountList { + get accounts => _accounts.stream; + BehaviorSubject> _accounts; + + bool _isRefreshing; + bool _isUpdating; + + AccountList() { + _isRefreshing = false; + _isUpdating = false; + _accounts = BehaviorSubject>(); + } + + Future update() async { + if (_isUpdating) { + return; + } + + try { + _isUpdating = true; + await refresh(); + final accounts = getAll(); + _accounts.add(accounts); + _isUpdating = false; + } catch (e) { + _isUpdating = false; + throw e; + } + } + + List getAll() { + return accountListAPI + .getAllAccount() + .map((accountRow) => Account.fromRow(accountRow)) + .toList(); + } + + Future addAccount({String label}) async { + await accountListAPI.addAccount(label: label); + await update(); + } + + Future setLabelSubaddress({int accountIndex, String label}) async { + await accountListAPI.setLabelForAccount(accountIndex: accountIndex, label: label); + await update(); + } + + refresh() { + if (_isRefreshing) { + return; + } + + try { + _isRefreshing = true; + accountListAPI.refreshAccounts(); + _isRefreshing = false; + } catch (e) { + _isRefreshing = false; + print(e); + throw e; + } + } +} diff --git a/lib/src/domain/monero/get_height_by_date.dart b/lib/src/domain/monero/get_height_by_date.dart new file mode 100644 index 000000000..41406c794 --- /dev/null +++ b/lib/src/domain/monero/get_height_by_date.dart @@ -0,0 +1,101 @@ +import 'package:intl/intl.dart'; + +final dateFormat = DateFormat('yyyy-MM'); +final dates = { + "2014-5": 18844, + "2014-6": 65406, + "2014-7": 108882, + "2014-8": 153594, + "2014-9": 198072, + "2014-10": 241088, + "2014-11": 285305, + "2014-12": 328069, + "2015-1": 372369, + "2015-2": 416505, + "2015-3": 456631, + "2015-4": 501084, + "2015-5": 543973, + "2015-6": 588326, + "2015-7": 631187, + "2015-8": 675484, + "2015-9": 719725, + "2015-10": 762463, + "2015-11": 806528, + "2015-12": 849041, + "2016-1": 892866, + "2016-2": 936736, + "2016-3": 977691, + "2016-4": 1015848, + "2016-5": 1037417, + "2016-6": 1059651, + "2016-7": 1081269, + "2016-8": 1103630, + "2016-9": 1125983, + "2016-10": 1147617, + "2016-11": 1169779, + "2016-12": 1191402, + "2017-1": 1213861, + "2017-2": 1236197, + "2017-3": 1256358, + "2017-4": 1278622, + "2017-5": 1300239, + "2017-6": 1322564, + "2017-7": 1344225, + "2017-8": 1366664, + "2017-9": 1389113, + "2017-10": 1410738, + "2017-11": 1433039, + "2017-12": 1454639, + "2018-1": 1477201, + "2018-2": 1499599, + "2018-3": 1519796, + "2018-4": 1542067, + "2018-5": 1562861, + "2018-6": 1585135, + "2018-7": 1606715, + "2018-8": 1629017, + "2018-9": 1651347, + "2018-10": 1673031, + "2018-11": 1695128, + "2018-12": 1716687, + "2019-1": 1738923, + "2019-2": 1761435, + "2019-3": 1781681, + "2019-4": 1803081, + "2019-5": 1824671, + "2019-6": 1847005, + "2019-7": 1868590, + "2019-8": 1888590, + "2019-9": 1898590, +}; + +int getHeigthByDate({DateTime date}) { + final raw = '${date.year}' + '-' + '${date.month}'; + var endHeight = dates[raw] ?? 0; + int preLastYear = date.year; + int preLastMonth = date.month - 1; + + if (endHeight <= 0) { + endHeight = dates.values.toList()[dates.length - 1]; + final preLastDate = dateFormat.parse(dates.keys.elementAt(dates.keys.length - 2)); + preLastYear = preLastDate.year; + preLastMonth = preLastDate.month; + } else { + preLastYear = date.year; + preLastMonth = date.month - 1; + } + + if (preLastMonth <= 0) { + preLastMonth = 12; + preLastYear -= 1; + } + + var startRaw = '$preLastYear' + '-' + '$preLastMonth'; + var startHeight = dates[startRaw]; + var diff = endHeight - startHeight; + var heightPerDay = diff / 30; + var daysHeight = date.day * heightPerDay.round(); + var height = endHeight + daysHeight; + + return height; +} diff --git a/lib/src/domain/monero/mnemonics/english.dart b/lib/src/domain/monero/mnemonics/english.dart new file mode 100644 index 000000000..fb464d04e --- /dev/null +++ b/lib/src/domain/monero/mnemonics/english.dart @@ -0,0 +1,1630 @@ +class EnglishMnemonics { + static const words = [ + "abbey", + "abducts", + "ability", + "ablaze", + "abnormal", + "abort", + "abrasive", + "absorb", + "abyss", + "academy", + "aces", + "aching", + "acidic", + "acoustic", + "acquire", + "across", + "actress", + "acumen", + "adapt", + "addicted", + "adept", + "adhesive", + "adjust", + "adopt", + "adrenalin", + "adult", + "adventure", + "aerial", + "afar", + "affair", + "afield", + "afloat", + "afoot", + "afraid", + "after", + "against", + "agenda", + "aggravate", + "agile", + "aglow", + "agnostic", + "agony", + "agreed", + "ahead", + "aided", + "ailments", + "aimless", + "airport", + "aisle", + "ajar", + "akin", + "alarms", + "album", + "alchemy", + "alerts", + "algebra", + "alkaline", + "alley", + "almost", + "aloof", + "alpine", + "already", + "also", + "altitude", + "alumni", + "always", + "amaze", + "ambush", + "amended", + "amidst", + "ammo", + "amnesty", + "among", + "amply", + "amused", + "anchor", + "android", + "anecdote", + "angled", + "ankle", + "annoyed", + "answers", + "antics", + "anvil", + "anxiety", + "anybody", + "apart", + "apex", + "aphid", + "aplomb", + "apology", + "apply", + "apricot", + "aptitude", + "aquarium", + "arbitrary", + "archer", + "ardent", + "arena", + "argue", + "arises", + "army", + "around", + "arrow", + "arsenic", + "artistic", + "ascend", + "ashtray", + "aside", + "asked", + "asleep", + "aspire", + "assorted", + "asylum", + "athlete", + "atlas", + "atom", + "atrium", + "attire", + "auburn", + "auctions", + "audio", + "august", + "aunt", + "austere", + "autumn", + "avatar", + "avidly", + "avoid", + "awakened", + "awesome", + "awful", + "awkward", + "awning", + "awoken", + "axes", + "axis", + "axle", + "aztec", + "azure", + "baby", + "bacon", + "badge", + "baffles", + "bagpipe", + "bailed", + "bakery", + "balding", + "bamboo", + "banjo", + "baptism", + "basin", + "batch", + "bawled", + "bays", + "because", + "beer", + "befit", + "begun", + "behind", + "being", + "below", + "bemused", + "benches", + "berries", + "bested", + "betting", + "bevel", + "beware", + "beyond", + "bias", + "bicycle", + "bids", + "bifocals", + "biggest", + "bikini", + "bimonthly", + "binocular", + "biology", + "biplane", + "birth", + "biscuit", + "bite", + "biweekly", + "blender", + "blip", + "bluntly", + "boat", + "bobsled", + "bodies", + "bogeys", + "boil", + "boldly", + "bomb", + "border", + "boss", + "both", + "bounced", + "bovine", + "bowling", + "boxes", + "boyfriend", + "broken", + "brunt", + "bubble", + "buckets", + "budget", + "buffet", + "bugs", + "building", + "bulb", + "bumper", + "bunch", + "business", + "butter", + "buying", + "buzzer", + "bygones", + "byline", + "bypass", + "cabin", + "cactus", + "cadets", + "cafe", + "cage", + "cajun", + "cake", + "calamity", + "camp", + "candy", + "casket", + "catch", + "cause", + "cavernous", + "cease", + "cedar", + "ceiling", + "cell", + "cement", + "cent", + "certain", + "chlorine", + "chrome", + "cider", + "cigar", + "cinema", + "circle", + "cistern", + "citadel", + "civilian", + "claim", + "click", + "clue", + "coal", + "cobra", + "cocoa", + "code", + "coexist", + "coffee", + "cogs", + "cohesive", + "coils", + "colony", + "comb", + "cool", + "copy", + "corrode", + "costume", + "cottage", + "cousin", + "cowl", + "criminal", + "cube", + "cucumber", + "cuddled", + "cuffs", + "cuisine", + "cunning", + "cupcake", + "custom", + "cycling", + "cylinder", + "cynical", + "dabbing", + "dads", + "daft", + "dagger", + "daily", + "damp", + "dangerous", + "dapper", + "darted", + "dash", + "dating", + "dauntless", + "dawn", + "daytime", + "dazed", + "debut", + "decay", + "dedicated", + "deepest", + "deftly", + "degrees", + "dehydrate", + "deity", + "dejected", + "delayed", + "demonstrate", + "dented", + "deodorant", + "depth", + "desk", + "devoid", + "dewdrop", + "dexterity", + "dialect", + "dice", + "diet", + "different", + "digit", + "dilute", + "dime", + "dinner", + "diode", + "diplomat", + "directed", + "distance", + "ditch", + "divers", + "dizzy", + "doctor", + "dodge", + "does", + "dogs", + "doing", + "dolphin", + "domestic", + "donuts", + "doorway", + "dormant", + "dosage", + "dotted", + "double", + "dove", + "down", + "dozen", + "dreams", + "drinks", + "drowning", + "drunk", + "drying", + "dual", + "dubbed", + "duckling", + "dude", + "duets", + "duke", + "dullness", + "dummy", + "dunes", + "duplex", + "duration", + "dusted", + "duties", + "dwarf", + "dwelt", + "dwindling", + "dying", + "dynamite", + "dyslexic", + "each", + "eagle", + "earth", + "easy", + "eating", + "eavesdrop", + "eccentric", + "echo", + "eclipse", + "economics", + "ecstatic", + "eden", + "edgy", + "edited", + "educated", + "eels", + "efficient", + "eggs", + "egotistic", + "eight", + "either", + "eject", + "elapse", + "elbow", + "eldest", + "eleven", + "elite", + "elope", + "else", + "eluded", + "emails", + "ember", + "emerge", + "emit", + "emotion", + "empty", + "emulate", + "energy", + "enforce", + "enhanced", + "enigma", + "enjoy", + "enlist", + "enmity", + "enough", + "enraged", + "ensign", + "entrance", + "envy", + "epoxy", + "equip", + "erase", + "erected", + "erosion", + "error", + "eskimos", + "espionage", + "essential", + "estate", + "etched", + "eternal", + "ethics", + "etiquette", + "evaluate", + "evenings", + "evicted", + "evolved", + "examine", + "excess", + "exhale", + "exit", + "exotic", + "exquisite", + "extra", + "exult", + "fabrics", + "factual", + "fading", + "fainted", + "faked", + "fall", + "family", + "fancy", + "farming", + "fatal", + "faulty", + "fawns", + "faxed", + "fazed", + "feast", + "february", + "federal", + "feel", + "feline", + "females", + "fences", + "ferry", + "festival", + "fetches", + "fever", + "fewest", + "fiat", + "fibula", + "fictional", + "fidget", + "fierce", + "fifteen", + "fight", + "films", + "firm", + "fishing", + "fitting", + "five", + "fixate", + "fizzle", + "fleet", + "flippant", + "flying", + "foamy", + "focus", + "foes", + "foggy", + "foiled", + "folding", + "fonts", + "foolish", + "fossil", + "fountain", + "fowls", + "foxes", + "foyer", + "framed", + "friendly", + "frown", + "fruit", + "frying", + "fudge", + "fuel", + "fugitive", + "fully", + "fuming", + "fungal", + "furnished", + "fuselage", + "future", + "fuzzy", + "gables", + "gadget", + "gags", + "gained", + "galaxy", + "gambit", + "gang", + "gasp", + "gather", + "gauze", + "gave", + "gawk", + "gaze", + "gearbox", + "gecko", + "geek", + "gels", + "gemstone", + "general", + "geometry", + "germs", + "gesture", + "getting", + "geyser", + "ghetto", + "ghost", + "giant", + "giddy", + "gifts", + "gigantic", + "gills", + "gimmick", + "ginger", + "girth", + "giving", + "glass", + "gleeful", + "glide", + "gnaw", + "gnome", + "goat", + "goblet", + "godfather", + "goes", + "goggles", + "going", + "goldfish", + "gone", + "goodbye", + "gopher", + "gorilla", + "gossip", + "gotten", + "gourmet", + "governing", + "gown", + "greater", + "grunt", + "guarded", + "guest", + "guide", + "gulp", + "gumball", + "guru", + "gusts", + "gutter", + "guys", + "gymnast", + "gypsy", + "gyrate", + "habitat", + "hacksaw", + "haggled", + "hairy", + "hamburger", + "happens", + "hashing", + "hatchet", + "haunted", + "having", + "hawk", + "haystack", + "hazard", + "hectare", + "hedgehog", + "heels", + "hefty", + "height", + "hemlock", + "hence", + "heron", + "hesitate", + "hexagon", + "hickory", + "hiding", + "highway", + "hijack", + "hiker", + "hills", + "himself", + "hinder", + "hippo", + "hire", + "history", + "hitched", + "hive", + "hoax", + "hobby", + "hockey", + "hoisting", + "hold", + "honked", + "hookup", + "hope", + "hornet", + "hospital", + "hotel", + "hounded", + "hover", + "howls", + "hubcaps", + "huddle", + "huge", + "hull", + "humid", + "hunter", + "hurried", + "husband", + "huts", + "hybrid", + "hydrogen", + "hyper", + "iceberg", + "icing", + "icon", + "identity", + "idiom", + "idled", + "idols", + "igloo", + "ignore", + "iguana", + "illness", + "imagine", + "imbalance", + "imitate", + "impel", + "inactive", + "inbound", + "incur", + "industrial", + "inexact", + "inflamed", + "ingested", + "initiate", + "injury", + "inkling", + "inline", + "inmate", + "innocent", + "inorganic", + "input", + "inquest", + "inroads", + "insult", + "intended", + "inundate", + "invoke", + "inwardly", + "ionic", + "irate", + "iris", + "irony", + "irritate", + "island", + "isolated", + "issued", + "italics", + "itches", + "items", + "itinerary", + "itself", + "ivory", + "jabbed", + "jackets", + "jaded", + "jagged", + "jailed", + "jamming", + "january", + "jargon", + "jaunt", + "javelin", + "jaws", + "jazz", + "jeans", + "jeers", + "jellyfish", + "jeopardy", + "jerseys", + "jester", + "jetting", + "jewels", + "jigsaw", + "jingle", + "jittery", + "jive", + "jobs", + "jockey", + "jogger", + "joining", + "joking", + "jolted", + "jostle", + "journal", + "joyous", + "jubilee", + "judge", + "juggled", + "juicy", + "jukebox", + "july", + "jump", + "junk", + "jury", + "justice", + "juvenile", + "kangaroo", + "karate", + "keep", + "kennel", + "kept", + "kernels", + "kettle", + "keyboard", + "kickoff", + "kidneys", + "king", + "kiosk", + "kisses", + "kitchens", + "kiwi", + "knapsack", + "knee", + "knife", + "knowledge", + "knuckle", + "koala", + "laboratory", + "ladder", + "lagoon", + "lair", + "lakes", + "lamb", + "language", + "laptop", + "large", + "last", + "later", + "launching", + "lava", + "lawsuit", + "layout", + "lazy", + "lectures", + "ledge", + "leech", + "left", + "legion", + "leisure", + "lemon", + "lending", + "leopard", + "lesson", + "lettuce", + "lexicon", + "liar", + "library", + "licks", + "lids", + "lied", + "lifestyle", + "light", + "likewise", + "lilac", + "limits", + "linen", + "lion", + "lipstick", + "liquid", + "listen", + "lively", + "loaded", + "lobster", + "locker", + "lodge", + "lofty", + "logic", + "loincloth", + "long", + "looking", + "lopped", + "lordship", + "losing", + "lottery", + "loudly", + "love", + "lower", + "loyal", + "lucky", + "luggage", + "lukewarm", + "lullaby", + "lumber", + "lunar", + "lurk", + "lush", + "luxury", + "lymph", + "lynx", + "lyrics", + "macro", + "madness", + "magically", + "mailed", + "major", + "makeup", + "malady", + "mammal", + "maps", + "masterful", + "match", + "maul", + "maverick", + "maximum", + "mayor", + "maze", + "meant", + "mechanic", + "medicate", + "meeting", + "megabyte", + "melting", + "memoir", + "menu", + "merger", + "mesh", + "metro", + "mews", + "mice", + "midst", + "mighty", + "mime", + "mirror", + "misery", + "mittens", + "mixture", + "moat", + "mobile", + "mocked", + "mohawk", + "moisture", + "molten", + "moment", + "money", + "moon", + "mops", + "morsel", + "mostly", + "motherly", + "mouth", + "movement", + "mowing", + "much", + "muddy", + "muffin", + "mugged", + "mullet", + "mumble", + "mundane", + "muppet", + "mural", + "musical", + "muzzle", + "myriad", + "mystery", + "myth", + "nabbing", + "nagged", + "nail", + "names", + "nanny", + "napkin", + "narrate", + "nasty", + "natural", + "nautical", + "navy", + "nearby", + "necklace", + "needed", + "negative", + "neither", + "neon", + "nephew", + "nerves", + "nestle", + "network", + "neutral", + "never", + "newt", + "nexus", + "nibs", + "niche", + "niece", + "nifty", + "nightly", + "nimbly", + "nineteen", + "nirvana", + "nitrogen", + "nobody", + "nocturnal", + "nodes", + "noises", + "nomad", + "noodles", + "northern", + "nostril", + "noted", + "nouns", + "novelty", + "nowhere", + "nozzle", + "nuance", + "nucleus", + "nudged", + "nugget", + "nuisance", + "null", + "number", + "nuns", + "nurse", + "nutshell", + "nylon", + "oaks", + "oars", + "oasis", + "oatmeal", + "obedient", + "object", + "obliged", + "obnoxious", + "observant", + "obtains", + "obvious", + "occur", + "ocean", + "october", + "odds", + "odometer", + "offend", + "often", + "oilfield", + "ointment", + "okay", + "older", + "olive", + "olympics", + "omega", + "omission", + "omnibus", + "onboard", + "oncoming", + "oneself", + "ongoing", + "onion", + "online", + "onslaught", + "onto", + "onward", + "oozed", + "opacity", + "opened", + "opposite", + "optical", + "opus", + "orange", + "orbit", + "orchid", + "orders", + "organs", + "origin", + "ornament", + "orphans", + "oscar", + "ostrich", + "otherwise", + "otter", + "ouch", + "ought", + "ounce", + "ourselves", + "oust", + "outbreak", + "oval", + "oven", + "owed", + "owls", + "owner", + "oxidant", + "oxygen", + "oyster", + "ozone", + "pact", + "paddles", + "pager", + "pairing", + "palace", + "pamphlet", + "pancakes", + "paper", + "paradise", + "pastry", + "patio", + "pause", + "pavements", + "pawnshop", + "payment", + "peaches", + "pebbles", + "peculiar", + "pedantic", + "peeled", + "pegs", + "pelican", + "pencil", + "people", + "pepper", + "perfect", + "pests", + "petals", + "phase", + "pheasants", + "phone", + "phrases", + "physics", + "piano", + "picked", + "pierce", + "pigment", + "piloted", + "pimple", + "pinched", + "pioneer", + "pipeline", + "pirate", + "pistons", + "pitched", + "pivot", + "pixels", + "pizza", + "playful", + "pledge", + "pliers", + "plotting", + "plus", + "plywood", + "poaching", + "pockets", + "podcast", + "poetry", + "point", + "poker", + "polar", + "ponies", + "pool", + "popular", + "portents", + "possible", + "potato", + "pouch", + "poverty", + "powder", + "pram", + "present", + "pride", + "problems", + "pruned", + "prying", + "psychic", + "public", + "puck", + "puddle", + "puffin", + "pulp", + "pumpkins", + "punch", + "puppy", + "purged", + "push", + "putty", + "puzzled", + "pylons", + "pyramid", + "python", + "queen", + "quick", + "quote", + "rabbits", + "racetrack", + "radar", + "rafts", + "rage", + "railway", + "raking", + "rally", + "ramped", + "randomly", + "rapid", + "rarest", + "rash", + "rated", + "ravine", + "rays", + "razor", + "react", + "rebel", + "recipe", + "reduce", + "reef", + "refer", + "regular", + "reheat", + "reinvest", + "rejoices", + "rekindle", + "relic", + "remedy", + "renting", + "reorder", + "repent", + "request", + "reruns", + "rest", + "return", + "reunion", + "revamp", + "rewind", + "rhino", + "rhythm", + "ribbon", + "richly", + "ridges", + "rift", + "rigid", + "rims", + "ringing", + "riots", + "ripped", + "rising", + "ritual", + "river", + "roared", + "robot", + "rockets", + "rodent", + "rogue", + "roles", + "romance", + "roomy", + "roped", + "roster", + "rotate", + "rounded", + "rover", + "rowboat", + "royal", + "ruby", + "rudely", + "ruffled", + "rugged", + "ruined", + "ruling", + "rumble", + "runway", + "rural", + "rustled", + "ruthless", + "sabotage", + "sack", + "sadness", + "safety", + "saga", + "sailor", + "sake", + "salads", + "sample", + "sanity", + "sapling", + "sarcasm", + "sash", + "satin", + "saucepan", + "saved", + "sawmill", + "saxophone", + "sayings", + "scamper", + "scenic", + "school", + "science", + "scoop", + "scrub", + "scuba", + "seasons", + "second", + "sedan", + "seeded", + "segments", + "seismic", + "selfish", + "semifinal", + "sensible", + "september", + "sequence", + "serving", + "session", + "setup", + "seventh", + "sewage", + "shackles", + "shelter", + "shipped", + "shocking", + "shrugged", + "shuffled", + "shyness", + "siblings", + "sickness", + "sidekick", + "sieve", + "sifting", + "sighting", + "silk", + "simplest", + "sincerely", + "sipped", + "siren", + "situated", + "sixteen", + "sizes", + "skater", + "skew", + "skirting", + "skulls", + "skydive", + "slackens", + "sleepless", + "slid", + "slower", + "slug", + "smash", + "smelting", + "smidgen", + "smog", + "smuggled", + "snake", + "sneeze", + "sniff", + "snout", + "snug", + "soapy", + "sober", + "soccer", + "soda", + "software", + "soggy", + "soil", + "solved", + "somewhere", + "sonic", + "soothe", + "soprano", + "sorry", + "southern", + "sovereign", + "sowed", + "soya", + "space", + "speedy", + "sphere", + "spiders", + "splendid", + "spout", + "sprig", + "spud", + "spying", + "square", + "stacking", + "stellar", + "stick", + "stockpile", + "strained", + "stunning", + "stylishly", + "subtly", + "succeed", + "suddenly", + "suede", + "suffice", + "sugar", + "suitcase", + "sulking", + "summon", + "sunken", + "superior", + "surfer", + "sushi", + "suture", + "swagger", + "swept", + "swiftly", + "sword", + "swung", + "syllabus", + "symptoms", + "syndrome", + "syringe", + "system", + "taboo", + "tacit", + "tadpoles", + "tagged", + "tail", + "taken", + "talent", + "tamper", + "tanks", + "tapestry", + "tarnished", + "tasked", + "tattoo", + "taunts", + "tavern", + "tawny", + "taxi", + "teardrop", + "technical", + "tedious", + "teeming", + "tell", + "template", + "tender", + "tepid", + "tequila", + "terminal", + "testing", + "tether", + "textbook", + "thaw", + "theatrics", + "thirsty", + "thorn", + "threaten", + "thumbs", + "thwart", + "ticket", + "tidy", + "tiers", + "tiger", + "tilt", + "timber", + "tinted", + "tipsy", + "tirade", + "tissue", + "titans", + "toaster", + "tobacco", + "today", + "toenail", + "toffee", + "together", + "toilet", + "token", + "tolerant", + "tomorrow", + "tonic", + "toolbox", + "topic", + "torch", + "tossed", + "total", + "touchy", + "towel", + "toxic", + "toyed", + "trash", + "trendy", + "tribal", + "trolling", + "truth", + "trying", + "tsunami", + "tubes", + "tucks", + "tudor", + "tuesday", + "tufts", + "tugs", + "tuition", + "tulips", + "tumbling", + "tunnel", + "turnip", + "tusks", + "tutor", + "tuxedo", + "twang", + "tweezers", + "twice", + "twofold", + "tycoon", + "typist", + "tyrant", + "ugly", + "ulcers", + "ultimate", + "umbrella", + "umpire", + "unafraid", + "unbending", + "uncle", + "under", + "uneven", + "unfit", + "ungainly", + "unhappy", + "union", + "unjustly", + "unknown", + "unlikely", + "unmask", + "unnoticed", + "unopened", + "unplugs", + "unquoted", + "unrest", + "unsafe", + "until", + "unusual", + "unveil", + "unwind", + "unzip", + "upbeat", + "upcoming", + "update", + "upgrade", + "uphill", + "upkeep", + "upload", + "upon", + "upper", + "upright", + "upstairs", + "uptight", + "upwards", + "urban", + "urchins", + "urgent", + "usage", + "useful", + "usher", + "using", + "usual", + "utensils", + "utility", + "utmost", + "utopia", + "uttered", + "vacation", + "vague", + "vain", + "value", + "vampire", + "vane", + "vapidly", + "vary", + "vastness", + "vats", + "vaults", + "vector", + "veered", + "vegan", + "vehicle", + "vein", + "velvet", + "venomous", + "verification", + "vessel", + "veteran", + "vexed", + "vials", + "vibrate", + "victim", + "video", + "viewpoint", + "vigilant", + "viking", + "village", + "vinegar", + "violin", + "vipers", + "virtual", + "visited", + "vitals", + "vivid", + "vixen", + "vocal", + "vogue", + "voice", + "volcano", + "vortex", + "voted", + "voucher", + "vowels", + "voyage", + "vulture", + "wade", + "waffle", + "wagtail", + "waist", + "waking", + "wallets", + "wanted", + "warped", + "washing", + "water", + "waveform", + "waxing", + "wayside", + "weavers", + "website", + "wedge", + "weekday", + "weird", + "welders", + "went", + "wept", + "were", + "western", + "wetsuit", + "whale", + "when", + "whipped", + "whole", + "wickets", + "width", + "wield", + "wife", + "wiggle", + "wildly", + "winter", + "wipeout", + "wiring", + "wise", + "withdrawn", + "wives", + "wizard", + "wobbly", + "woes", + "woken", + "wolf", + "womanly", + "wonders", + "woozy", + "worry", + "wounded", + "woven", + "wrap", + "wrist", + "wrong", + "yacht", + "yahoo", + "yanks", + "yard", + "yawning", + "yearbook", + "yellow", + "yesterday", + "yeti", + "yields", + "yodel", + "yoga", + "younger", + "yoyo", + "zapped", + "zeal", + "zebra", + "zero", + "zesty", + "zigzags", + "zinger", + "zippers", + "zodiac", + "zombie", + "zones", + "zoom" + ]; +} diff --git a/lib/src/domain/monero/monero_amount_format.dart b/lib/src/domain/monero/monero_amount_format.dart new file mode 100644 index 000000000..3eb54d880 --- /dev/null +++ b/lib/src/domain/monero/monero_amount_format.dart @@ -0,0 +1,12 @@ +import 'package:intl/intl.dart'; + +const moneroAmountLength = 12; +const moneroAmountDivider = 1000000000000; +final moneroAmountFormat = NumberFormat() + ..maximumFractionDigits = moneroAmountLength + ..minimumFractionDigits = 1; + +String moneroAmountToString({int amount}) => + moneroAmountFormat.format(amount / moneroAmountDivider); + +double moneroAmountToDouble({int amount}) => amount / moneroAmountDivider; diff --git a/lib/src/domain/monero/monero_balance.dart b/lib/src/domain/monero/monero_balance.dart new file mode 100644 index 000000000..c49ea8cb9 --- /dev/null +++ b/lib/src/domain/monero/monero_balance.dart @@ -0,0 +1,9 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/common/balance.dart'; + +class MoneroBalance extends Balance { + final String fullBalance; + final String unlockedBalance; + + MoneroBalance({@required this.fullBalance, @required this.unlockedBalance}); +} diff --git a/lib/src/domain/monero/monero_transaction_creation_credentials.dart b/lib/src/domain/monero/monero_transaction_creation_credentials.dart new file mode 100644 index 000000000..c35b7705f --- /dev/null +++ b/lib/src/domain/monero/monero_transaction_creation_credentials.dart @@ -0,0 +1,13 @@ +import 'package:cake_wallet/src/domain/common/transaction_creation_credentials.dart'; +import 'package:cake_wallet/src/domain/common/transaction_priority.dart'; + +class MoneroTransactionCreationCredentials + extends TransactionCreationCredentials { + final String address; + final String paymentId; + final String amount; + final TransactionPriority priority; + + MoneroTransactionCreationCredentials( + {this.address, this.paymentId, this.priority, this.amount}); +} \ No newline at end of file diff --git a/lib/src/domain/monero/monero_transaction_history.dart b/lib/src/domain/monero/monero_transaction_history.dart new file mode 100644 index 000000000..a6c7f1eaa --- /dev/null +++ b/lib/src/domain/monero/monero_transaction_history.dart @@ -0,0 +1,65 @@ +import 'dart:core'; +import 'package:flutter/services.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:cw_monero/transaction_history.dart' as moneroTransactionHistory; +import 'package:cake_wallet/src/domain/common/transaction_history.dart'; +import 'package:cake_wallet/src/domain/common/transaction_info.dart'; + +List _getAllTransactions(_) => moneroTransactionHistory + .getAllTransations() + .map((row) => TransactionInfo.fromRow(row)) + .toList(); + +class MoneroTransactionHistory extends TransactionHistory { + get transactions => _transactions.stream; + BehaviorSubject> _transactions; + + bool _isUpdating = false; + bool _isRefreshing = false; + bool _needToCheckForRefresh = false; + + MoneroTransactionHistory() + : _transactions = BehaviorSubject>.seeded([]); + + Future update() async { + if (_isUpdating) { + return; + } + + try { + _isUpdating = true; + await refresh(); + _transactions.value = await getAll(force: true); + _isUpdating = false; + + if (!_needToCheckForRefresh) { + _needToCheckForRefresh = true; + } + } catch (e) { + _isUpdating = false; + print(e); + throw e; + } + } + + Future> getAll({bool force = false}) async => + _getAllTransactions(null); + + Future count() async => moneroTransactionHistory.countOfTransactions(); + + Future refresh() async { + if (_isRefreshing) { + return; + } + + try { + _isRefreshing = true; + moneroTransactionHistory.refreshTransactions(); + _isRefreshing = false; + } on PlatformException catch (e) { + _isRefreshing = false; + print(e); + throw e; + } + } +} diff --git a/lib/src/domain/monero/monero_wallet.dart b/lib/src/domain/monero/monero_wallet.dart new file mode 100644 index 000000000..fab161580 --- /dev/null +++ b/lib/src/domain/monero/monero_wallet.dart @@ -0,0 +1,396 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:hive/hive.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:cw_monero/wallet.dart' as moneroWallet; +import 'package:cw_monero/transaction_history.dart' as transactionHistory; +import 'package:cake_wallet/src/domain/common/wallet_info.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/common/sync_status.dart'; +import 'package:cake_wallet/src/domain/common/transaction_history.dart'; +import 'package:cake_wallet/src/domain/common/transaction_creation_credentials.dart'; +import 'package:cake_wallet/src/domain/common/pending_transaction.dart'; +import 'package:cake_wallet/src/domain/common/wallet_type.dart'; +import 'package:cake_wallet/src/domain/common/node.dart'; +import 'package:cake_wallet/src/domain/monero/monero_amount_format.dart'; +import 'package:cake_wallet/src/domain/monero/account.dart'; +import 'package:cake_wallet/src/domain/monero/account_list.dart'; +import 'package:cake_wallet/src/domain/monero/subaddress_list.dart'; +import 'package:cake_wallet/src/domain/monero/monero_transaction_creation_credentials.dart'; +import 'package:cake_wallet/src/domain/monero/monero_transaction_history.dart'; +import 'package:cake_wallet/src/domain/monero/subaddress.dart'; +import 'package:cake_wallet/src/domain/common/balance.dart'; +import 'package:cake_wallet/src/domain/monero/monero_balance.dart'; + +const monero_block_size = 1000; + +class MoneroWallet extends Wallet { + static Future createdWallet( + {Box walletInfoSource, + String name, + bool isRecovery = false, + int restoreHeight = 0}) async { + const type = WalletType.monero; + final id = walletTypeToString(type).toLowerCase() + '_' + name; + final walletInfo = WalletInfo( + id: id, + name: name, + type: type, + isRecovery: isRecovery, + restoreHeight: restoreHeight); + await walletInfoSource.add(walletInfo); + + return await configured( + walletInfo: walletInfo, walletInfoSource: walletInfoSource); + } + + static Future load( + Box walletInfoSource, String name, WalletType type) async { + final id = walletTypeToString(type).toLowerCase() + '_' + name; + final walletInfo = + walletInfoSource.values.firstWhere((info) => info.id == id, orElse: () => null); + return await configured( + walletInfoSource: walletInfoSource, walletInfo: walletInfo); + } + + static Future configured( + {@required Box walletInfoSource, + @required WalletInfo walletInfo}) async { + final wallet = MoneroWallet( + walletInfoSource: walletInfoSource, walletInfo: walletInfo); + + if (walletInfo.isRecovery) { + await wallet.setRecoveringFromSeed(); + + if (walletInfo.restoreHeight != null) { + await wallet.setRefreshFromBlockHeight( + height: walletInfo.restoreHeight); + } + } + + return wallet; + } + + WalletType getType() => WalletType.monero; + bool get isRecovery => walletInfo.isRecovery; + Observable get syncStatus => _syncStatus.stream; + Observable get onBalanceChange => _onBalanceChange.stream; + Observable get onAccountChange => _account.stream; + Observable get onNameChange => _name.stream; + Observable get onAddressChange => _address.stream; + Observable get subaddress => _subaddress.stream; + + Account get account => _account.value; + String get address => _address.value; + String get name => _name.value; + + Box walletInfoSource; + WalletInfo walletInfo; + + BehaviorSubject _account; + BehaviorSubject _onBalanceChange; + BehaviorSubject _syncStatus; + BehaviorSubject _name; + BehaviorSubject _address; + BehaviorSubject _subaddress; + int _cachedBlockchainHeight; + bool _isSaving; + int _lastSaveTime; + int _lastRefreshTime; + int _refreshHeight; + int _lastSyncHeight; + + TransactionHistory _cachedTransactionHistory; + SubaddressList _cachedSubaddressList; + AccountList _cachedAccountList; + + MoneroWallet( + {this.walletInfoSource, this.walletInfo}) { + _cachedBlockchainHeight = 0; + _isSaving = false; + _lastSaveTime = 0; + _lastRefreshTime = 0; + _refreshHeight = 0; + _lastSyncHeight = 0; + _name = BehaviorSubject(); + _address = BehaviorSubject(); + _syncStatus = BehaviorSubject(); + _onBalanceChange = BehaviorSubject(); + _account = BehaviorSubject()..add(Account(id: 0)); + _subaddress = BehaviorSubject(); + setListeners(); + } + + Future updateInfo() async { + _name.value = await getName(); + final acccountList = getAccountList(); + await acccountList.refresh(); + _account.value = acccountList.getAll().first; + final subaddressList = getSubaddress(); + await subaddressList.refresh( + accountIndex: _account.value != null ? _account.value.id : 0); + final subaddresses = subaddressList.getAll(); + _subaddress.value = subaddresses.first; + _address.value = await getAddress(); + } + + Future getFilename() async => moneroWallet.getFilename(); + + Future getName() async => getFilename() + .then((filename) => filename.split('/')) + .then((splitted) => splitted.last); + + Future getAddress() async => moneroWallet.getAddress( + accountIndex: _account.value.id, addressIndex: _subaddress.value.id); + + Future getSeed() async => moneroWallet.getSeed(); + + Future getFullBalance() async => moneroAmountToString( + amount: moneroWallet.getFullBalance(accountIndex: _account.value.id)); + + Future getUnlockedBalance() async => moneroAmountToString( + amount: moneroWallet.getUnlockedBalance(accountIndex: _account.value.id)); + + Future getCurrentHeight() async => moneroWallet.getCurrentHeight(); + + Future getNodeHeight() async => moneroWallet.getNodeHeight(); + + Future> getKeys() async => { + 'publicViewKey': moneroWallet.getPublicViewKey(), + 'privateViewKey': moneroWallet.getSecretViewKey(), + 'publicSpendKey': moneroWallet.getPublicSpendKey(), + 'privateSpendKey': moneroWallet.getSecretSpendKey() + }; + + Future close() async { + moneroWallet.closeListeners(); + moneroWallet.closeCurrentWallet(); + } + + Future connectToNode( + {Node node, bool useSSL = false, bool isLightWallet = false}) async { + try { + _syncStatus.value = ConnectingSyncStatus(); + await moneroWallet.setupNode( + address: node.uri, + login: node.login, + password: node.password, + useSSL: useSSL, + isLightWallet: isLightWallet); + _syncStatus.value = ConnectedSyncStatus(); + } catch (e) { + _syncStatus.value = FailedSyncStatus(); + print(e); + } + } + + Future startSync() async { + try { + _syncStatus.value = StartingSyncStatus(); + moneroWallet.startRefresh(); + } on PlatformException catch (e) { + _syncStatus.value = FailedSyncStatus(); + print(e); + throw e; + } + } + + TransactionHistory getHistory() { + if (_cachedTransactionHistory == null) { + _cachedTransactionHistory = MoneroTransactionHistory(); + } + + return _cachedTransactionHistory; + } + + SubaddressList getSubaddress() { + if (_cachedSubaddressList == null) { + _cachedSubaddressList = SubaddressList(); + } + + return _cachedSubaddressList; + } + + AccountList getAccountList() { + if (_cachedAccountList == null) { + _cachedAccountList = AccountList(); + } + + return _cachedAccountList; + } + + Future askForSave() async { + final diff = DateTime.now().millisecondsSinceEpoch - _lastSaveTime; + + if (_lastSaveTime != 0 && diff < 120000) { + return; + } + + await store(); + } + + Future getNodeHeightOrUpdate(int baseHeight) async { + if (_cachedBlockchainHeight < baseHeight) { + _cachedBlockchainHeight = await getNodeHeight(); + } + + return _cachedBlockchainHeight; + } + + Future createTransaction( + TransactionCreationCredentials credentials) async { + MoneroTransactionCreationCredentials _credentials = + credentials as MoneroTransactionCreationCredentials; + + final transactionDescription = await transactionHistory.createTransaction( + address: _credentials.address, + paymentId: _credentials.paymentId, + amount: _credentials.amount, + priorityRaw: _credentials.priority.serialize(), + accountIndex: _account.value.id); + + return PendingTransaction.fromTransactionDescription( + transactionDescription); + } + + setRecoveringFromSeed() => + moneroWallet.setRecoveringFromSeed(isRecovery: true); + + setRefreshFromBlockHeight({int height}) => + moneroWallet.setRefreshFromBlockHeight(height: height); + + Future isConnected() async => moneroWallet.isConnected(); + + Future setAsRecovered() async { + walletInfo.isRecovery = false; + await walletInfo.save(); + } + + Future askForUpdateBalance() async { + final fullBalance = await getFullBalance(); + final unlockedBalance = await getUnlockedBalance(); + final needToChange = _onBalanceChange.value != null + ? _onBalanceChange.value.fullBalance != fullBalance || + _onBalanceChange.value.unlockedBalance != unlockedBalance + : true; + + if (!needToChange) { + return; + } + + _onBalanceChange.add(MoneroBalance( + fullBalance: fullBalance, unlockedBalance: unlockedBalance)); + } + + Future askForUpdateTransactionHistory() async => await getHistory().update(); + + Future rescan({int restoreHeight = 0}) async { + _syncStatus.value = StartingSyncStatus(); + setRefreshFromBlockHeight(height: restoreHeight); + moneroWallet.rescanBlockchainAsync(); + _syncStatus.value = StartingSyncStatus(); + } + + changeCurrentSubaddress(Subaddress subaddress) => + _subaddress.value = subaddress; + + changeAccount(Account account) { + _account.add(account); + + getSubaddress() + .refresh(accountIndex: account.id) + .then((_) => getSubaddress().getAll()) + .then((subaddresses) => _subaddress.value = subaddresses[0]); + } + + Future store() async { + if (_isSaving) { + return; + } + + try { + _isSaving = true; + moneroWallet.store(); + _isSaving = false; + } on PlatformException catch (e) { + print(e); + _isSaving = false; + throw e; + } + } + + setListeners() => moneroWallet.setListeners( + _onNewBlock, _onNeedToRefresh, _onNewTransaction); + + Future _onNewBlock(int height) async { + try { + final nodeHeight = await getNodeHeightOrUpdate(height); + + if (isRecovery && _refreshHeight <= 0) { + _refreshHeight = height; + } + + if (isRecovery && + (_lastSyncHeight == 0 || + (height - _lastSyncHeight) > monero_block_size)) { + _lastSyncHeight = height; + askForUpdateBalance(); + askForUpdateTransactionHistory(); + } + + if (height > 0 && ((nodeHeight - height) < monero_block_size)) { + _syncStatus.add(SyncedSyncStatus()); + } else { + _syncStatus.add(SyncingSyncStatus(height, nodeHeight, _refreshHeight)); + } + } catch (e) { + print(e); + } + } + + Future _onNeedToRefresh() async { + try { + final currentHeight = await getCurrentHeight(); + final nodeHeight = await getNodeHeightOrUpdate(currentHeight); + + // no blocks - maybe we're not connected to the node ? + if (currentHeight <= 1 || nodeHeight == 0) { + return; + } + + if (_syncStatus.value is FailedSyncStatus) { + return; + } + + askForUpdateBalance(); + + _syncStatus.add(SyncedSyncStatus()); + + if (isRecovery) { + askForUpdateTransactionHistory(); + } + + if (isRecovery && (nodeHeight - currentHeight < monero_block_size)) { + await setAsRecovered(); + } + + final now = DateTime.now().millisecondsSinceEpoch; + final diff = now - _lastRefreshTime; + + if (diff >= 0 && diff < 60000) { + return; + } + + await store(); + _lastRefreshTime = now; + } catch (e) { + print(e); + } + } + + Future _onNewTransaction() async { + await askForUpdateBalance(); + await askForUpdateTransactionHistory(); + } +} diff --git a/lib/src/domain/monero/monero_wallets_manager.dart b/lib/src/domain/monero/monero_wallets_manager.dart new file mode 100644 index 000000000..81a034ac7 --- /dev/null +++ b/lib/src/domain/monero/monero_wallets_manager.dart @@ -0,0 +1,160 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:cw_monero/wallet_manager.dart' as moneroWalletManager; +import 'package:cake_wallet/src/domain/common/wallet_info.dart'; +import 'package:cake_wallet/src/domain/common/wallet_type.dart'; +import 'package:cake_wallet/src/domain/common/wallets_manager.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/monero/monero_wallet.dart'; +import 'package:cake_wallet/src/domain/common/wallet_description.dart'; + +Future pathForWallet({String name}) async { + final directory = await getApplicationDocumentsDirectory(); + final pathDir = directory.path + '/$name'; + final dir = Directory(pathDir); + + if (!await dir.exists()) { + await dir.create(); + } + + return pathDir + '/$name'; +} + +class MoneroWalletsManager extends WalletsManager { + static const type = WalletType.monero; + + Box walletInfoSource; + + MoneroWalletsManager({@required this.walletInfoSource}); + + Future create(String name, String password) async { + try { + const isRecovery = false; + final path = await pathForWallet(name: name); + + await moneroWalletManager.createWallet(path: path, password: password); + + final wallet = await MoneroWallet.createdWallet( + walletInfoSource: walletInfoSource, name: name, isRecovery: isRecovery) + ..updateInfo(); + + return wallet; + } catch (e) { + print('MoneroWalletsManager Error: $e'); + throw e; + } + } + + Future restoreFromSeed( + String name, String password, String seed, int restoreHeight) async { + try { + const isRecovery = true; + final path = await pathForWallet(name: name); + + await moneroWalletManager.restoreFromSeed( + path: path, + password: password, + seed: seed, + restoreHeight: restoreHeight); + + return await MoneroWallet.createdWallet( + walletInfoSource: walletInfoSource, + name: name, + isRecovery: isRecovery, + restoreHeight: restoreHeight) + ..updateInfo(); + } catch (e) { + print('MoneroWalletsManager Error: $e'); + throw e; + } + } + + Future restoreFromKeys( + String name, + String password, + int restoreHeight, + String address, + String viewKey, + String spendKey) async { + try { + const isRecovery = true; + final path = await pathForWallet(name: name); + + await moneroWalletManager.restoreFromKeys( + path: path, + password: password, + restoreHeight: restoreHeight, + address: address, + viewKey: viewKey, + spendKey: spendKey); + + final wallet = await MoneroWallet.createdWallet( + walletInfoSource: walletInfoSource, + name: name, + isRecovery: isRecovery, + restoreHeight: restoreHeight) + ..updateInfo(); + + return wallet; + } catch (e) { + print('MoneroWalletsManager Error: $e'); + throw e; + } + } + + Future openWallet(String name, String password) async { + try { + final path = await pathForWallet(name: name); + await moneroWalletManager.openWallet(path: path, password: password); + final wallet = await MoneroWallet.load(walletInfoSource, name, type) + ..updateInfo(); + + return wallet; + } catch (e) { + print('MoneroWalletsManager Error: $e'); + throw e; + } + } + + Future isWalletExit(String name) async { + try { + final path = await pathForWallet(name: name); + return moneroWalletManager.isWalletExist(path: path); + } catch (e) { + print('MoneroWalletsManager Error: $e'); + throw e; + } + } + + Future remove(WalletDescription wallet) async { + final dir = await getApplicationDocumentsDirectory(); + final root = dir.path.replaceAll('app_flutter', 'files'); + final walletFilePath = root + '/cw_monero/' + wallet.name; + final keyPath = walletFilePath + '.keys'; + final addressFilePath = walletFilePath + '.address.txt'; + final walletFile = File(walletFilePath); + final keyFile = File(keyPath); + final addressFile = File(addressFilePath); + + if (await walletFile.exists()) { + await walletFile.delete(); + } + + if (await keyFile.exists()) { + await keyFile.delete(); + } + + if (await addressFile.exists()) { + await addressFile.delete(); + } + + final id = + walletTypeToString(wallet.type).toLowerCase() + '_' + wallet.name; + final info = walletInfoSource.values.firstWhere((info) => info.id == id, orElse: () => null); + + await info?.delete(); + } +} diff --git a/lib/src/domain/monero/subaddress.dart b/lib/src/domain/monero/subaddress.dart new file mode 100644 index 000000000..9294c2ab6 --- /dev/null +++ b/lib/src/domain/monero/subaddress.dart @@ -0,0 +1,19 @@ +import 'package:cw_monero/structs/subaddress_row.dart'; + +class Subaddress { + final int id; + final String address; + final String label; + + Subaddress({this.id, this.address, this.label}); + + Subaddress.fromMap(Map map) + : this.id = map['id'] == null ? 0 : int.parse(map['id']), + this.address = map['address'] ?? '', + this.label = map['label'] ?? ''; + + Subaddress.fromRow(SubaddressRow row) + : this.id = row.getId(), + this.address = row.getAddress(), + this.label = row.getLabel(); +} diff --git a/lib/src/domain/monero/subaddress_list.dart b/lib/src/domain/monero/subaddress_list.dart new file mode 100644 index 000000000..e2d123752 --- /dev/null +++ b/lib/src/domain/monero/subaddress_list.dart @@ -0,0 +1,70 @@ +import 'package:flutter/services.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:cw_monero/subaddress_list.dart' as subaddressListAPI; +import 'package:cake_wallet/src/domain/monero/subaddress.dart'; + +class SubaddressList { + get subaddresses => _subaddress.stream; + BehaviorSubject> _subaddress; + + bool _isRefreshing; + bool _isUpdating; + + SubaddressList() { + _isRefreshing = false; + _isUpdating = false; + _subaddress = BehaviorSubject>(); + } + + Future update({int accountIndex}) async { + if (_isUpdating) { + return; + } + + try { + _isUpdating = true; + await refresh(accountIndex: accountIndex); + final subaddresses = getAll(); + _subaddress.add(subaddresses); + _isUpdating = false; + } catch (e) { + _isUpdating = false; + throw e; + } + } + + List getAll() { + return subaddressListAPI + .getAllSubaddresses() + .map((subaddressRow) => Subaddress.fromRow(subaddressRow)) + .toList(); + } + + Future addSubaddress({int accountIndex, String label}) async { + await subaddressListAPI.addSubaddress(accountIndex: accountIndex, label: label); + await update(accountIndex: accountIndex); + } + + Future setLabelSubaddress( + {int accountIndex, int addressIndex, String label}) async { + await subaddressListAPI.setLabelForSubaddress( + accountIndex: accountIndex, addressIndex: addressIndex, label: label); + await update(); + } + + Future refresh({int accountIndex}) async { + if (_isRefreshing) { + return; + } + + try { + _isRefreshing = true; + subaddressListAPI.refreshSubaddresses(accountIndex: accountIndex); + _isRefreshing = false; + } on PlatformException catch (e) { + _isRefreshing = false; + print(e); + throw e; + } + } +} diff --git a/lib/src/domain/monero/transaction_description.dart b/lib/src/domain/monero/transaction_description.dart new file mode 100644 index 000000000..a0becfd55 --- /dev/null +++ b/lib/src/domain/monero/transaction_description.dart @@ -0,0 +1,16 @@ +import 'package:hive/hive.dart'; + +part 'transaction_description.g.dart'; + +@HiveType() +class TransactionDescription extends HiveObject { + static const boxName = 'TransactionDescriptions'; + + @HiveField(0) + String id; + + @HiveField(1) + String recipientAddress; + + TransactionDescription({this.id, this.recipientAddress}); +} \ No newline at end of file diff --git a/lib/src/domain/services/user_service.dart b/lib/src/domain/services/user_service.dart new file mode 100644 index 000000000..8560eac00 --- /dev/null +++ b/lib/src/domain/services/user_service.dart @@ -0,0 +1,46 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/src/domain/common/secret_store_key.dart'; +import 'package:cake_wallet/src/domain/common/encrypt.dart'; + +class UserService { + final FlutterSecureStorage secureStorage; + final SharedPreferences sharedPreferences; + + UserService({this.sharedPreferences, this.secureStorage}); + + Future setPassword(String password) async { + final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); + + try { + final encodedPassord = encodedPinCode(pin: password); + + await secureStorage.write(key: key, value: encodedPassord); + } catch (e) { + print(e); + } + } + + Future canAuthenticate() async { + final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); + final sharedPreferences = await SharedPreferences.getInstance(); + final walletName = sharedPreferences.getString("current_wallet_name") ?? ""; + var password = ''; + + try { + password = await secureStorage.read(key: key); + } catch (e) { + print(e); + } + + return walletName.isNotEmpty && password.isNotEmpty; + } + + Future authenticate(String pin) async { + final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); + final encodedPin = await secureStorage.read(key: key); + final decodedPin = decodedPinCode(pin: encodedPin); + + return decodedPin == pin; + } +} diff --git a/lib/src/domain/services/wallet_list_service.dart b/lib/src/domain/services/wallet_list_service.dart new file mode 100644 index 000000000..7fe4e1a26 --- /dev/null +++ b/lib/src/domain/services/wallet_list_service.dart @@ -0,0 +1,145 @@ +import 'dart:async'; +import 'package:cake_wallet/src/domain/common/wallet_info.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:uuid/uuid.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/src/domain/common/encrypt.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/common/wallet_description.dart'; +import 'package:cake_wallet/src/domain/common/wallets_manager.dart'; +import 'package:cake_wallet/src/domain/common/secret_store_key.dart'; +import 'package:cake_wallet/src/domain/common/wallet_type.dart'; +import 'package:cake_wallet/src/domain/monero/monero_wallets_manager.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; + +class WalletIsExistException implements Exception { + String name; + + WalletIsExistException(this.name); + + @override + String toString() => "Wallet with name $name is already exist!"; +} + +class WalletListService { + final FlutterSecureStorage secureStorage; + final WalletService walletService; + final Box walletInfoSource; + final SharedPreferences sharedPreferences; + WalletsManager walletsManager; + + WalletListService( + {this.secureStorage, + this.walletInfoSource, + this.walletsManager, + @required this.walletService, + @required this.sharedPreferences}); + + Future> getAll() async => walletInfoSource.values + .map((info) => WalletDescription(name: info.name, type: info.type)) + .toList(); + + Future create(String name) async { + if (await walletsManager.isWalletExit(name)) { + throw WalletIsExistException(name); + } + + if (walletService.currentWallet != null) { + await walletService.close(); + } + + final password = Uuid().v4(); + await saveWalletPassword(password: password, walletName: name); + + final wallet = await walletsManager.create(name, password); + + await onWalletChange(wallet); + } + + Future restoreFromSeed(String name, String seed, int restoreHeight) async { + if (await walletsManager.isWalletExit(name)) { + throw WalletIsExistException(name); + } + + if (walletService.currentWallet != null) { + await walletService.close(); + } + + final password = Uuid().v4(); + await saveWalletPassword(password: password, walletName: name); + + final wallet = await walletsManager.restoreFromSeed( + name, password, seed, restoreHeight); + + await onWalletChange(wallet); + } + + Future restoreFromKeys(String name, int restoreHeight, String address, + String viewKey, String spendKey) async { + if (await walletsManager.isWalletExit(name)) { + throw WalletIsExistException(name); + } + + if (walletService.currentWallet != null) { + await walletService.close(); + } + + final password = Uuid().v4(); + await saveWalletPassword(password: password, walletName: name); + + final wallet = await walletsManager.restoreFromKeys( + name, password, restoreHeight, address, viewKey, spendKey); + + await onWalletChange(wallet); + } + + Future openWallet(String name) async { + if (walletService.currentWallet != null) { + await walletService.close(); + } + + final password = await getWalletPassword(walletName: name); + final wallet = await walletsManager.openWallet(name, password); + + await onWalletChange(wallet); + } + + Future changeWalletManger({WalletType walletType}) async { + switch (walletType) { + case WalletType.monero: + walletsManager = + MoneroWalletsManager(walletInfoSource: walletInfoSource); + break; + case WalletType.none: + walletsManager = null; + break; + } + } + + Future onWalletChange(Wallet wallet) async { + walletService.currentWallet = wallet; + final walletName = await wallet.getName(); + await sharedPreferences.setString('current_wallet_name', walletName); + } + + Future remove(WalletDescription wallet) async => + await walletsManager.remove(wallet); + + Future getWalletPassword({String walletName}) async { + final key = generateStoreKeyFor( + key: SecretStoreKey.moneroWalletPassword, walletName: walletName); + final encodedPassword = await secureStorage.read(key: key); + + return decodeWalletPassword(password: encodedPassword); + } + + Future saveWalletPassword({String walletName, String password}) async { + final key = generateStoreKeyFor( + key: SecretStoreKey.moneroWalletPassword, walletName: walletName); + final encodedPassword = encodeWalletPassword(password: password); + + await secureStorage.write(key: key, value: encodedPassword); + } +} diff --git a/lib/src/domain/services/wallet_service.dart b/lib/src/domain/services/wallet_service.dart new file mode 100644 index 000000000..b805dbb58 --- /dev/null +++ b/lib/src/domain/services/wallet_service.dart @@ -0,0 +1,98 @@ +import 'package:rxdart/rxdart.dart'; +import 'package:cake_wallet/src/domain/common/balance.dart'; +import 'package:cake_wallet/src/domain/common/wallet_description.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/common/transaction_history.dart'; +import 'package:cake_wallet/src/domain/common/wallet_type.dart'; +import 'package:cake_wallet/src/domain/common/sync_status.dart'; +import 'package:cake_wallet/src/domain/common/transaction_creation_credentials.dart'; +import 'package:cake_wallet/src/domain/common/pending_transaction.dart'; +import 'package:cake_wallet/src/domain/common/node.dart'; + +class WalletService extends Wallet { + Observable get onWalletChange => _onWalletChanged.stream; + Observable get onBalanceChange => _onBalanceChange.stream; + Observable get syncStatus => _syncStatus.stream; + Observable get onAddressChange => _currentWallet.onAddressChange; + Observable get onNameChange => _currentWallet.onNameChange; + String get address => _currentWallet.address; + String get name => _currentWallet.name; + SyncStatus get syncStatusValue => _syncStatus.value; + WalletType get walletType => _currentWallet.walletType; + + get currentWallet => _currentWallet; + + set currentWallet(Wallet wallet) { + _currentWallet = wallet; + + if (wallet == null) { + return; + } + + _currentWallet.onBalanceChange + .listen((wallet) => _onBalanceChange.add(wallet)); + _currentWallet.syncStatus.listen((status) => _syncStatus.add(status)); + _onWalletChanged.add(wallet); + + final type = wallet.getType(); + wallet.getName().then( + (name) => description = WalletDescription(name: name, type: type)); + } + + BehaviorSubject _onWalletChanged; + BehaviorSubject _onBalanceChange; + BehaviorSubject _syncStatus; + Wallet _currentWallet; + + WalletService() { + _currentWallet = null; + walletType = WalletType.none; + _syncStatus = BehaviorSubject(); + _onBalanceChange = BehaviorSubject(); + _onWalletChanged = BehaviorSubject(); + } + + WalletDescription description; + + WalletType getType() => WalletType.monero; + + Future getFilename() => _currentWallet.getFilename(); + + Future getName() => _currentWallet.getName(); + + Future getAddress() => _currentWallet.getAddress(); + + Future getSeed() => _currentWallet.getSeed(); + + Future> getKeys() => _currentWallet.getKeys(); + + Future getFullBalance() => _currentWallet.getFullBalance(); + + Future getUnlockedBalance() => _currentWallet.getUnlockedBalance(); + + Future getCurrentHeight() => _currentWallet.getCurrentHeight(); + + Future getNodeHeight() => _currentWallet.getNodeHeight(); + + Future isConnected() => _currentWallet.isConnected(); + + Future close() => _currentWallet.close(); + + Future connectToNode({Node node, bool useSSL = false, bool isLightWallet = false}) => + _currentWallet.connectToNode( + node: node, + useSSL: useSSL, + isLightWallet: isLightWallet); + + Future startSync() => _currentWallet.startSync(); + + TransactionHistory getHistory() => _currentWallet.getHistory(); + + Future createTransaction( + TransactionCreationCredentials credentials) => + _currentWallet.createTransaction(credentials); + + Future updateInfo() async => _currentWallet.updateInfo(); + + Future rescan({int restoreHeight = 0}) async => _currentWallet.rescan(restoreHeight: restoreHeight); +} diff --git a/lib/src/reactions/set_reactions.dart b/lib/src/reactions/set_reactions.dart new file mode 100644 index 000000000..001157524 --- /dev/null +++ b/lib/src/reactions/set_reactions.dart @@ -0,0 +1,86 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/sync_status.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/start_updating_price.dart'; +import 'package:cake_wallet/src/stores/sync/sync_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/price/price_store.dart'; +import 'package:cake_wallet/src/stores/authentication/authentication_store.dart'; +import 'package:cake_wallet/src/stores/login/login_store.dart'; + +setReactions( + {@required SettingsStore settingsStore, + @required PriceStore priceStore, + @required SyncStore syncStore, + @required WalletStore walletStore, + @required WalletService walletService, + @required AuthenticationStore authenticationStore, + @required LoginStore loginStore}) { + connectToNode(settingsStore: settingsStore, walletStore: walletStore); + onSyncStatusChange( + syncStore: syncStore, + walletStore: walletStore, + settingsStore: settingsStore); + onCurrentWalletChange( + walletStore: walletStore, + settingsStore: settingsStore, + priceStore: priceStore); + autorun((_) async { + if (authenticationStore.state == AuthenticationState.allowed) { + await loginStore.loadCurrentWallet(); + authenticationStore.state = AuthenticationState.readyToLogin; + } + }); +} + +connectToNode({SettingsStore settingsStore, WalletStore walletStore}) => + reaction((_) => settingsStore.node, + (node) async => await walletStore.connectToNode(node: node)); + +onSyncStatusChange( + {SyncStore syncStore, + WalletStore walletStore, + SettingsStore settingsStore}) => + reaction((_) => syncStore.status, (status) async { + if (status is ConnectedSyncStatus) { + await walletStore.startSync(); + } + + // Reconnect to the node if the app is not started sync after 30 seconds + if (status is StartingSyncStatus) { + await startReconnectionObserver( + syncStore: syncStore, walletStore: walletStore); + } + }); + +Timer _reconnectionTimer; + +startReconnectionObserver({SyncStore syncStore, WalletStore walletStore}) { + if (_reconnectionTimer != null) { + _reconnectionTimer.cancel(); + } + + _reconnectionTimer = Timer.periodic(Duration(minutes: 1), (_) async { + try { + final isConnected = await walletStore.isConnected(); + + if (!isConnected) { + await walletStore.reconnect(); + } + } catch (e) { + print(e); + } + }); +} + +onCurrentWalletChange( + {WalletStore walletStore, + SettingsStore settingsStore, + PriceStore priceStore}) => + reaction((_) => walletStore.name, (_) { + walletStore.connectToNode(node: settingsStore.node); + startUpdatingPrice(settingsStore: settingsStore, priceStore: priceStore); + }); diff --git a/lib/src/screens/accounts/account_list_page.dart b/lib/src/screens/accounts/account_list_page.dart new file mode 100644 index 000000000..2f54a7718 --- /dev/null +++ b/lib/src/screens/accounts/account_list_page.dart @@ -0,0 +1,120 @@ +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/account_list/account_list_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class AccountListPage extends BasePage { + String get title => S.current.accounts; + + @override + Widget trailing(BuildContext context) { + final accountListStore = Provider.of(context); + + return Container( + width: 28.0, + height: 28.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).selectedRowColor), + child: Stack( + alignment: Alignment.center, + children: [ + Icon(Icons.add, color: Palette.violet, size: 22.0), + ButtonTheme( + minWidth: 28.0, + height: 28.0, + child: FlatButton( + shape: CircleBorder(), + onPressed: () async { + await Navigator.of(context) + .pushNamed(Routes.accountCreation); + await accountListStore.updateAccountList(); + }, + child: Offstage()), + ) + ], + )); + } + + @override + Widget body(BuildContext context) { + final accountListStore = Provider.of(context); + final walletStore = Provider.of(context); + + final currentColor = Theme.of(context).selectedRowColor; + final notCurrentColor = Theme.of(context).backgroundColor; + + return Container( + padding: EdgeInsets.only(top: 10, bottom: 20), + child: Observer( + builder: (_) { + final accounts = accountListStore.accounts; + return ListView.builder( + itemCount: accounts == null + ? 0 + : accounts.length, + itemBuilder: (BuildContext context, int index) { + final account = accounts[index]; + + return Observer(builder: (_) { + final isCurrent = walletStore.account.id == account.id; + + return Slidable( + key: Key(account.id.toString()), + actionPane: SlidableDrawerActionPane(), + child: Container( + color: isCurrent ? currentColor : notCurrentColor, + child: Column( + children: [ + ListTile( + title: Text( + account.label, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.headline.color), + ), + onTap: () { + if (isCurrent) { + return; + } + + walletStore.setAccount(account); + Navigator.of(context).pop(); + }, + ), + Divider( + color: Theme.of(context).dividerTheme.color, + height: 1.0, + ) + ], + ), + ), + secondaryActions: [ + IconSlideAction( + caption: S.of(context).edit, + color: Colors.blue, + icon: Icons.edit, + onTap: () async { + await Navigator.of(context) + .pushNamed(Routes.accountCreation, arguments: account); + // await accountListStore.updateAccountList().then((_) { + // if (isCurrent) walletStore.setAccount(accountListStore.accounts[index]); + // }); + }, + ) + ], + ); + }); + }); + } + ), + ); + } +} diff --git a/lib/src/screens/accounts/account_page.dart b/lib/src/screens/accounts/account_page.dart new file mode 100644 index 000000000..76677939e --- /dev/null +++ b/lib/src/screens/accounts/account_page.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/account_list/account_list_store.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/domain/monero/account.dart'; +import 'package:cake_wallet/palette.dart'; + +class AccountPage extends BasePage { + String get title => 'Account'; + final Account account; + + AccountPage({this.account}); + + @override + Widget body(BuildContext context) => AccountForm(account); +} + +class AccountForm extends StatefulWidget { + final Account account; + + AccountForm(this.account); + + @override + createState() => AccountFormState(); +} + +class AccountFormState extends State { + final _formKey = GlobalKey(); + final _textController = TextEditingController(); + + @override + void initState() { + if (widget.account != null) _textController.text = widget.account.label; + super.initState(); + } + + @override + void dispose() { + _textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final accountListStore = Provider.of(context); + + return Form( + key: _formKey, + child: Container( + padding: EdgeInsets.all(20.0), + child: Column( + children: [ + Expanded( + child: Center( + child: TextFormField( + decoration: InputDecoration( + hintStyle: TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).account, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, width: 1.0))), + controller: _textController, + validator: (value) { + accountListStore.validateAccountName(value); + return accountListStore.errorMessage; + }, + ), + )), + Observer( + builder: (_) => LoadingPrimaryButton( + onPressed: () async { + if (!_formKey.currentState.validate()) { + return; + } + + if (widget.account != null) { + await accountListStore.renameAccount( + index: widget.account.id, + label: _textController.text); + } else { + await accountListStore.addAccount( + label: _textController.text); + } + Navigator.pop(context, _textController.text); + }, + text: + widget.account != null ? 'Rename' : S.of(context).add, + color: Theme.of(context) + .primaryTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .primaryTextTheme + .button + .decorationColor, + isLoading: accountListStore.isAccountCreating, + )) + ], + ), + ), + ); + } +} diff --git a/lib/src/screens/address_book/address_book_page.dart b/lib/src/screens/address_book/address_book_page.dart new file mode 100644 index 000000000..20d849dc6 --- /dev/null +++ b/lib/src/screens/address_book/address_book_page.dart @@ -0,0 +1,261 @@ +import 'package:provider/provider.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:flutter_slidable/flutter_slidable.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/stores/address_book/address_book_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class AddressBookPage extends BasePage { + bool get isModalBackButton => true; + String get title => S.current.address_book; + AppBarStyle get appBarStyle => AppBarStyle.withShadow; + + final bool isEditable; + + AddressBookPage({this.isEditable = true}); + + @override + Widget trailing(BuildContext context) { + if (!isEditable) { + return null; + } + + final addressBookStore = Provider.of(context); + + return Container( + width: 28.0, + height: 28.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).selectedRowColor), + child: Stack( + alignment: Alignment.center, + children: [ + Icon(Icons.add, color: Palette.violet, size: 22.0), + ButtonTheme( + minWidth: 28.0, + height: 28.0, + child: FlatButton( + shape: CircleBorder(), + onPressed: () async { + await Navigator.of(context) + .pushNamed(Routes.addressBookAddContact); + await addressBookStore.updateContactList(); + }, + child: Offstage()), + ) + ], + )); + } + + @override + Widget body(BuildContext context) { + final addressBookStore = Provider.of(context); + + return Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0), + child: Observer( + builder: (_) => ListView.separated( + separatorBuilder: (_, __) => Divider( + color: Theme.of(context).dividerTheme.color, + height: 1.0, + ), + itemCount: addressBookStore.contactList == null + ? 0 + : addressBookStore.contactList.length, + itemBuilder: (BuildContext context, int index) { + final contact = addressBookStore.contactList[index]; + + final content = ListTile( + onTap: () async { + if (!isEditable) { + Navigator.of(context).pop(contact); + return; + } + + bool isCopied = await showNameAndAddressDialog(context, contact.name, contact.address); + if (isCopied) { + Clipboard.setData(ClipboardData(text: contact.address)); + Scaffold.of(context).showSnackBar( + SnackBar( + content: + Text('Copied to Clipboard'), + backgroundColor: Colors.green, + duration: + Duration(milliseconds: 1500), + ), + ); + } + }, + leading: Container( + height: 25.0, + width: 48.0, + alignment: Alignment.center, + decoration: BoxDecoration( + color: _getCurrencyBackgroundColor(contact.type), + borderRadius: BorderRadius.circular(6.0), + ), + child: Text( + contact.type.toString(), + style: TextStyle( + fontSize: 11.0, + color: _getCurrencyTextColor(contact.type), + ), + ), + ), + title: Text( + contact.name, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.title.color), + ), + ); + + return !isEditable ? content + : Slidable( + key: Key('1'),// Key(contact.id.toString()), + actionPane: SlidableDrawerActionPane(), + child: content, + secondaryActions: [ + IconSlideAction( + caption: 'Edit', + color: Colors.blue, + icon: Icons.edit, + onTap: () async { + await Navigator.of(context) + .pushNamed(Routes.addressBookAddContact, arguments: contact); + await addressBookStore.updateContactList(); + }, + ), + IconSlideAction( + caption: 'Delete', + color: Colors.red, + icon: CupertinoIcons.delete, + onTap: () async { + await showAlertDialog(context).then((isDelete) async{ + if (isDelete != null && isDelete) { + await addressBookStore.delete(contact: contact); + await addressBookStore.updateContactList(); + } + }); + }, + ), + ], + dismissal: SlidableDismissal( + child: SlidableDrawerDismissal(), + onDismissed: (actionType) async { + await addressBookStore.delete(contact: contact); + await addressBookStore.updateContactList(); + }, + onWillDismiss: (actionType) async { + return await showAlertDialog(context); + }, + ), + + ); + }), + )); + } + + Color _getCurrencyBackgroundColor(CryptoCurrency currency) { + Color color; + switch (currency) { + case CryptoCurrency.xmr: + color = Palette.cakeGreenWithOpacity; + break; + case CryptoCurrency.btc: + color = Colors.orange; + break; + case CryptoCurrency.eth: + color = Colors.black; + break; + case CryptoCurrency.ltc: + color = Colors.blue[200]; + break; + case CryptoCurrency.bch: + color = Colors.orangeAccent; + break; + case CryptoCurrency.dash: + color = Colors.blue; + break; + default: + color = Colors.white; + } + return color; + } + + Color _getCurrencyTextColor(CryptoCurrency currency) { + Color color; + switch (currency) { + case CryptoCurrency.xmr: + color = Palette.cakeGreen; + break; + case CryptoCurrency.ltc: + color = Palette.lightBlue; + break; + default: + color = Colors.white; + } + return color; + } + + Future showAlertDialog(BuildContext context) async { + return await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text( + 'Remove contact', + textAlign: TextAlign.center, + ), + content: const Text( + 'Are you sure that you want to remove selected contact?', + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + onPressed: () => + Navigator.pop(context, false), + child: const Text('Cancel')), + FlatButton( + onPressed: () => + Navigator.pop(context, true), + child: const Text('Remove')), + ], + ); + }); + } + + showNameAndAddressDialog(BuildContext context, String name, String address) async { + return await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + name, + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + content: Text( + address, + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text('Cancel')), + FlatButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text('Copy')) + ], + ); + } + ); + } +} diff --git a/lib/src/screens/address_book/contact_page.dart b/lib/src/screens/address_book/contact_page.dart new file mode 100644 index 000000000..a42497c8c --- /dev/null +++ b/lib/src/screens/address_book/contact_page.dart @@ -0,0 +1,254 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/common/contact.dart'; +import 'package:cake_wallet/src/stores/address_book/address_book_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/address_text_field.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/palette.dart'; + +class ContactPage extends BasePage { + String get title => S.current.contact; + final Contact contact; + + ContactPage({this.contact}); + + @override + Widget body(BuildContext context) => ContactForm(contact); +} + +class ContactForm extends StatefulWidget { + final Contact contact; + + ContactForm(this.contact); + + @override + createState() => ContactFormState(); +} + +class ContactFormState extends State { + final _formKey = GlobalKey(); + final _contactNameController = TextEditingController(); + final _currencyTypeController = TextEditingController(); + final _addressController = TextEditingController(); + + CryptoCurrency _selectectCrypto = CryptoCurrency.xmr; + + @override + void initState() { + super.initState(); + if (widget.contact == null) { + _currencyTypeController.text = _selectectCrypto.toString(); + } else { + _selectectCrypto = widget.contact.type; + _contactNameController.text = widget.contact.name; + _currencyTypeController.text = _selectectCrypto.toString(); + _addressController.text = widget.contact.address; + } + } + + @override + void dispose() { + _contactNameController.dispose(); + _currencyTypeController.dispose(); + _addressController.dispose(); + super.dispose(); + } + + _setCurrencyType(BuildContext context) async { + String currencyType = CryptoCurrency.all[0].toString(); + CryptoCurrency selectedCurrency = CryptoCurrency.all[0]; + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).please_select), + backgroundColor: Theme.of(context).backgroundColor, + content: Container( + height: 150.0, + child: CupertinoPicker( + backgroundColor: Theme.of(context).backgroundColor, + itemExtent: 45.0, + onSelectedItemChanged: (int index) { + selectedCurrency = CryptoCurrency.all[index]; + currencyType = CryptoCurrency.all[index].toString(); + }, + children: + List.generate(CryptoCurrency.all.length, (int index) { + return Center( + child: Text( + CryptoCurrency.all[index].toString(), + style: TextStyle( + color: Theme.of(context) + .primaryTextTheme + .caption + .color), + ), + ); + })), + ), + actions: [ + FlatButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text(S.of(context).cancel)), + FlatButton( + onPressed: () { + _selectectCrypto = selectedCurrency; + _currencyTypeController.text = currencyType; + Navigator.of(context).pop(); + }, + child: Text(S.of(context).ok)) + ], + ); + }); + } + + @override + Widget build(BuildContext context) { + final addressBookStore = Provider.of(context); + + return ScrollableWithBottomSection( + content: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + style: TextStyle( + fontSize: 14.0, + color: Theme.of(context).primaryTextTheme.headline.color), + decoration: InputDecoration( + hintStyle: TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).contact_name, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, width: 1.0))), + controller: _contactNameController, + validator: (value) { + addressBookStore.validateContactName(value); + return addressBookStore.errorMessage; + }, + ), + SizedBox(height: 14.0), + Container( + child: InkWell( + onTap: () => _setCurrencyType(context), + child: IgnorePointer( + child: TextFormField( + style: TextStyle( + fontSize: 14.0, + color: Theme.of(context) + .primaryTextTheme + .headline + .color), + decoration: InputDecoration( + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, + width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + controller: _currencyTypeController, + ), + ), + ), + ), + SizedBox(height: 14.0), + AddressTextField( + controller: _addressController, + options: [AddressTextFieldOption.qrCode], + validator: (value) { + addressBookStore.validateAddress(value, + cryptoCurrency: _selectectCrypto); + return addressBookStore.errorMessage; + }, + ) + ], + ), + ), + bottomSection: Row( + children: [ + Expanded( + child: PrimaryButton( + onPressed: () { + setState(() { + _selectectCrypto = CryptoCurrency.xmr; + _contactNameController.text = ''; + _currencyTypeController.text = + _selectectCrypto.toString(); + _addressController.text = ''; + }); + }, + text: S.of(context).reset, + color: + Theme.of(context).accentTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).accentTextTheme.button.decorationColor), + ), + SizedBox(width: 20), + Expanded( + child: PrimaryButton( + onPressed: () async { + if (!_formKey.currentState.validate()) { + return; + } + + try { + if (widget.contact == null) { + final newContact = Contact( + name: _contactNameController.text, + address: _addressController.text, + type: _selectectCrypto); + + await addressBookStore.add(contact: newContact); + } else { + widget.contact.name = _contactNameController.text; + widget.contact.address = _addressController.text; + widget.contact.updateCryptoCurrency(currency: _selectectCrypto); + + await addressBookStore.update(contact: widget.contact); + } + Navigator.pop(context); + } catch (e) { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + e.toString(), + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + onPressed: () => + Navigator.of(context).pop(), + child: Text(S.of(context).ok)) + ], + ); + }); + } + }, + text: S.of(context).save, + color: Theme.of(context) + .primaryTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .primaryTextTheme + .button + .decorationColor)) + ], + )); + } +} diff --git a/lib/src/screens/auth/auth_page.dart b/lib/src/screens/auth/auth_page.dart new file mode 100644 index 000000000..085defe2b --- /dev/null +++ b/lib/src/screens/auth/auth_page.dart @@ -0,0 +1,95 @@ +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/auth/auth_state.dart'; +import 'package:cake_wallet/src/stores/auth/auth_store.dart'; +import 'package:cake_wallet/src/screens/pin_code/pin_code.dart'; + + +class AuthPage extends StatefulWidget { + final Function(bool, AuthPageState) onAuthenticationFinished; + final bool closable; + + AuthPage({this.onAuthenticationFinished, this.closable = true}); + + @override + AuthPageState createState() => AuthPageState(); +} + +class AuthPageState extends State { + final _key = GlobalKey(); + final _pinCodeKey = GlobalKey(); + + void changeProcessText(String text) { + _key.currentState.showSnackBar( + SnackBar(content: Text(text), backgroundColor: Colors.green)); + } + + void close() => Navigator.of(_key.currentContext).pop(); + + @override + Widget build(BuildContext context) { + final authStore = Provider.of(context); + + reaction((_) => authStore.state, (state) { + if (state is AuthenticatedSuccessfully) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.onAuthenticationFinished != null) { + widget.onAuthenticationFinished(true, this); + } else { + _key.currentState.showSnackBar( + SnackBar( + content: Text(S.of(context).authenticated), + backgroundColor: Colors.green, + ), + ); + } + }); + } + + if (state is AuthenticationInProgress) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _key.currentState.showSnackBar( + SnackBar( + content: Text(S.of(context).authentication), + backgroundColor: Colors.green, + ), + ); + }); + } + + if (state is AuthenticationFailure || state is AuthenticationBanned) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _pinCodeKey.currentState.clear(); + _key.currentState.hideCurrentSnackBar(); + _key.currentState.showSnackBar( + SnackBar( + content: Text(S.of(context).failed_authentication(state.error)), + backgroundColor: Colors.red, + ), + ); + + if (widget.onAuthenticationFinished != null) { + widget.onAuthenticationFinished(false, this); + } + }); + } + }); + + return Scaffold( + key: _key, + appBar: CupertinoNavigationBar( + leading: widget.closable ? CloseButton() : Container(), + backgroundColor: Theme.of(context).backgroundColor, + border: null, + ), + resizeToAvoidBottomPadding: false, + body: PinCode( + (pin, _) => authStore.auth( + password: pin.fold('', (ac, val) => ac + '$val')), + false, + _pinCodeKey)); + } +} diff --git a/lib/src/screens/auth/create_login_page.dart b/lib/src/screens/auth/create_login_page.dart new file mode 100644 index 000000000..eee2774a5 --- /dev/null +++ b/lib/src/screens/auth/create_login_page.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/src/domain/services/user_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/src/stores/auth/auth_store.dart'; +import 'package:cake_wallet/src/stores/authentication/authentication_store.dart'; + +Widget createLoginPage( + {@required SharedPreferences sharedPreferences, + @required UserService userService, + @required WalletService walletService, + @required WalletListService walletListService, + @required AuthenticationStore authenticationStore}) => + Provider( + create: (_) => AuthStore( + sharedPreferences: sharedPreferences, + userService: userService, + walletService: walletService), + child: AuthPage( + onAuthenticationFinished: (isAuthenticated, state) { + if (isAuthenticated) { + authenticationStore.loggedIn(); + } + }, + closable: false)); diff --git a/lib/src/screens/auth/create_unlock_page.dart b/lib/src/screens/auth/create_unlock_page.dart new file mode 100644 index 000000000..ddde813cb --- /dev/null +++ b/lib/src/screens/auth/create_unlock_page.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/src/domain/services/user_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/src/stores/auth/auth_store.dart'; + +Widget createUnlockPage( + {@required SharedPreferences sharedPreferences, + @required UserService userService, + @required WalletService walletService, + @required Function(bool, AuthPageState) onAuthenticationFinished}) => + WillPopScope( + onWillPop: () async => false, + child: Provider( + create: (_) => AuthStore( + sharedPreferences: sharedPreferences, + userService: userService, + walletService: walletService), + child: AuthPage( + onAuthenticationFinished: onAuthenticationFinished, + closable: false))); \ No newline at end of file diff --git a/lib/src/screens/base_page.dart b/lib/src/screens/base_page.dart new file mode 100644 index 000000000..4b5ef8b01 --- /dev/null +++ b/lib/src/screens/base_page.dart @@ -0,0 +1,125 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/widgets/nav_bar.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/themes.dart'; +import 'package:cake_wallet/theme_changer.dart'; + +enum AppBarStyle { regular, withShadow } + +abstract class BasePage extends StatelessWidget { + String get title => null; + bool get isModalBackButton => false; + Color get backgroundColor => Colors.white; + bool get resizeToAvoidBottomPadding => true; + AppBarStyle get appBarStyle => AppBarStyle.regular; + + final _backArrowImage = Image.asset('assets/images/back_arrow.png'); + final _backArrowImageDarkTheme = + Image.asset('assets/images/back_arrow_dark_theme.png'); + final _closeButtonImage = Image.asset('assets/images/close_button.png'); + final _closeButtonImageDarkTheme = + Image.asset('assets/images/close_button_dark_theme.png'); + + void onClose(BuildContext context) => Navigator.of(context).pop(); + + Widget leading(BuildContext context) { + if (ModalRoute.of(context).isFirst) { + return null; + } + + ThemeChanger _themeChanger = Provider.of(context); + Image _closeButton, _backButton; + + if (_themeChanger.getTheme() == Themes.darkTheme) { + _backButton = _backArrowImageDarkTheme; + _closeButton = _closeButtonImageDarkTheme; + } else { + _backButton = _backArrowImage; + _closeButton = _closeButtonImage; + } + + return SizedBox( + height: 37, + width: isModalBackButton ? 37 : 20, + child: ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + padding: EdgeInsets.all(0), + onPressed: () => onClose(context), + child: isModalBackButton ? _closeButton : _backButton), + ), + ); + } + + Widget middle(BuildContext context) { + return title == null + ? null + : Text( + title, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + color: Theme.of(context).primaryTextTheme.title.color), + ); + } + + Widget trailing(BuildContext context) => null; + + Widget floatingActionButton(BuildContext context) => null; + + Widget appBar(BuildContext context) { + final _themeChanger = Provider.of(context); + final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme; + + switch (appBarStyle) { + case AppBarStyle.regular: + return NavBar( + context: context, + leading: leading(context), + middle: middle(context), + trailing: trailing(context), + backgroundColor: _isDarkTheme + ? Theme.of(context).backgroundColor + : backgroundColor); + + case AppBarStyle.withShadow: + return NavBar.withShadow( + context: context, + leading: leading(context), + middle: middle(context), + trailing: trailing(context), + backgroundColor: _isDarkTheme + ? Theme.of(context).backgroundColor + : backgroundColor); + + default: + return NavBar( + context: context, + leading: leading(context), + middle: middle(context), + trailing: trailing(context), + backgroundColor: _isDarkTheme + ? Theme.of(context).backgroundColor + : backgroundColor); + } + } + + Widget body(BuildContext context); + + @override + Widget build(BuildContext context) { + final _themeChanger = Provider.of(context); + final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme; + + return Scaffold( + backgroundColor: + _isDarkTheme ? Theme.of(context).backgroundColor : backgroundColor, + resizeToAvoidBottomPadding: resizeToAvoidBottomPadding, + appBar: appBar(context), + body: SafeArea(child: body(context)), + floatingActionButton: floatingActionButton(context)); + } +} diff --git a/lib/src/screens/dashboard/create_dashboard_page.dart b/lib/src/screens/dashboard/create_dashboard_page.dart new file mode 100644 index 000000000..b2163c339 --- /dev/null +++ b/lib/src/screens/dashboard/create_dashboard_page.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/domain/monero/transaction_description.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart'; +import 'package:cake_wallet/src/stores/action_list/action_list_store.dart'; +import 'package:cake_wallet/src/stores/action_list/trade_filter_store.dart'; +import 'package:cake_wallet/src/stores/action_list/transaction_filter_store.dart'; +import 'package:cake_wallet/src/stores/price/price_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; + +Widget createDashboardPage( + {@required WalletService walletService, + @required PriceStore priceStore, + @required Box transactionDescriptions, + @required SettingsStore settingsStore, + @required Box trades, + @required WalletStore walletStore}) => + Provider( + create: (_) => ActionListStore( + walletService: walletService, + settingsStore: settingsStore, + priceStore: priceStore, + tradesSource: trades, + transactionFilterStore: TransactionFilterStore(), + tradeFilterStore: TradeFilterStore(walletStore: walletStore), + transactionDescriptions: transactionDescriptions), + child: DashboardPage()); diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart new file mode 100644 index 000000000..2097193c4 --- /dev/null +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -0,0 +1,584 @@ +import 'package:provider/provider.dart'; +import 'package:intl/intl.dart'; +import 'package:date_range_picker/date_range_picker.dart' as DateRagePicker; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/balance_display_mode.dart'; +import 'package:cake_wallet/src/domain/common/sync_status.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/src/stores/action_list/action_list_store.dart'; +import 'package:cake_wallet/src/stores/balance/balance_store.dart'; +import 'package:cake_wallet/src/stores/sync/sync_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/stores/action_list/date_section_item.dart'; +import 'package:cake_wallet/src/stores/action_list/trade_list_item.dart'; +import 'package:cake_wallet/src/stores/action_list/transaction_list_item.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/date_section_raw.dart'; +import 'package:cake_wallet/src/screens/dashboard/trade_row.dart'; +import 'package:cake_wallet/src/screens/dashboard/transaction_raw.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/screens/dashboard/wallet_menu.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; + +class DashboardPage extends BasePage { + final _bodyKey = GlobalKey(); + + @override + Widget leading(BuildContext context) { + return SizedBox( + width: 30, + child: FlatButton( + padding: EdgeInsets.all(0), + onPressed: () => _presentWalletMenu(context), + child: Image.asset('assets/images/more.png', + color: Theme.of(context).primaryTextTheme.caption.color, + width: 30))); + } + + @override + Widget middle(BuildContext context) { + final walletStore = Provider.of(context); + + return Observer(builder: (_) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + walletStore.name, + style: TextStyle( + color: Theme.of(context).primaryTextTheme.title.color), + ), + SizedBox(height: 5), + Text( + walletStore.account != null ? '${walletStore.account.label}' : '', + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 10, + color: Theme.of(context).primaryTextTheme.title.color), + ), + ]); + }); + } + + @override + Widget trailing(BuildContext context) { + return SizedBox( + width: 20, + child: FlatButton( + padding: EdgeInsets.all(0), + onPressed: () => Navigator.of(context).pushNamed(Routes.settings), + child: Image.asset('assets/images/settings_icon.png', + color: Colors.grey, height: 20)), + ); + } + + @override + Widget body(BuildContext context) => DashboardPageBody(key: _bodyKey); + + @override + Widget floatingActionButton(BuildContext context) => FloatingActionButton( + child: Image.asset('assets/images/exchange_icon.png', + color: Colors.white, height: 26, width: 22), + backgroundColor: Palette.floatingActionButton, + onPressed: () async { + final actionListStore = Provider.of(context); + + await Navigator.of(context, rootNavigator: true) + .pushNamed(Routes.exchange); + actionListStore.updateTradeList(); + }); + + void _presentWalletMenu(BuildContext bodyContext) { + final walletMenu = WalletMenu(bodyContext); + + showDialog( + builder: (_) => Picker( + items: walletMenu.items, + selectedAtIndex: -1, + title: S.of(bodyContext).wallet_menu, + pickerHeight: 510, + onItemSelected: (item) => + walletMenu.action(walletMenu.items.indexOf(item))), + context: bodyContext); + } +} + +class DashboardPageBody extends StatefulWidget { + DashboardPageBody({Key key}) : super(key: key); + + @override + DashboardPageBodyState createState() => DashboardPageBodyState(); +} + +class DashboardPageBodyState extends State { + static final transactionDateFormat = DateFormat("MMM d, yyyy HH:mm"); + + final _connectionStatusObserverKey = GlobalKey(); + final _balanceObserverKey = GlobalKey(); + final _balanceTitleObserverKey = GlobalKey(); + final _syncingObserverKey = GlobalKey(); + final _listObserverKey = GlobalKey(); + final _listKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + final balanceStore = Provider.of(context); + final actionListStore = Provider.of(context); + final syncStore = Provider.of(context); + final settingsStore = Provider.of(context); + + return Observer( + key: _listObserverKey, + builder: (_) { + final items = + actionListStore.items == null ? [] : actionListStore.items; + final itemsCount = items.length + 2; + + return ListView.builder( + key: _listKey, + padding: EdgeInsets.only(bottom: 15), + itemCount: itemsCount, + itemBuilder: (context, index) { + if (index == 0) { + return Container( + margin: EdgeInsets.only(bottom: 20), + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + boxShadow: [ + BoxShadow( + color: Palette.shadowGreyWithOpacity, + blurRadius: 10, + offset: Offset(0, 12)) + ]), + child: Column( + children: [ + Observer( + key: _syncingObserverKey, + builder: (_) { + final status = syncStore.status; + final statusText = status.title(); + final progress = syncStore.status.progress(); + final isFialure = status is FailedSyncStatus; + + var descriptionText = ''; + + if (status is SyncingSyncStatus) { + descriptionText = S + .of(context) + .Blocks_remaining( + syncStore.status.toString()); + } + + if (status is FailedSyncStatus) { + descriptionText = S + .of(context) + .please_try_to_connect_to_another_node; + } + + return Container( + child: Column( + children: [ + SizedBox( + height: 3, + child: LinearProgressIndicator( + backgroundColor: Palette.separator, + valueColor: + AlwaysStoppedAnimation( + Palette.cakeGreen), + value: progress, + ), + ), + SizedBox(height: 10), + Text(statusText, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: isFialure + ? Palette.failure + : Palette.cakeGreen)), + Text(descriptionText, + style: TextStyle( + fontSize: 11, + color: Palette.wildDarkBlue, + height: 2.0)) + ], + ), + ); + }), + GestureDetector( + onTapUp: (_) => balanceStore.isReversing = false, + onTapDown: (_) => balanceStore.isReversing = true, + child: Container( + padding: EdgeInsets.only(top: 40, bottom: 40), + color: Colors.transparent, + child: Column( + children: [ + Container(width: double.infinity), + Observer( + key: _balanceTitleObserverKey, + builder: (_) { + final savedDisplayMode = + settingsStore.balanceDisplayMode; + final displayMode = + balanceStore.isReversing + ? (savedDisplayMode == + BalanceDisplayMode + .availableBalance + ? BalanceDisplayMode + .fullBalance + : BalanceDisplayMode + .availableBalance) + : savedDisplayMode; + var title = displayMode.toString(); + + return Text(title, + style: TextStyle( + color: Palette.violet, + fontSize: 16)); + }), + Observer( + key: _connectionStatusObserverKey, + builder: (_) { + final savedDisplayMode = + settingsStore.balanceDisplayMode; + var balance = '---'; + final displayMode = + balanceStore.isReversing + ? (savedDisplayMode == + BalanceDisplayMode + .availableBalance + ? BalanceDisplayMode + .fullBalance + : BalanceDisplayMode + .availableBalance) + : savedDisplayMode; + + if (displayMode == + BalanceDisplayMode.availableBalance) { + balance = + balanceStore.unlockedBalance ?? + '0.0'; + } + + if (displayMode == + BalanceDisplayMode.fullBalance) { + balance = + balanceStore.fullBalance ?? '0.0'; + } + + return Text( + balance, + style: TextStyle( + color: Theme.of(context) + .primaryTextTheme + .caption + .color, + fontSize: 42), + ); + }), + Padding( + padding: EdgeInsets.only(top: 7), + child: Observer( + key: _balanceObserverKey, + builder: (_) { + final savedDisplayMode = + settingsStore.balanceDisplayMode; + final displayMode = + balanceStore.isReversing + ? (savedDisplayMode == + BalanceDisplayMode + .availableBalance + ? BalanceDisplayMode + .fullBalance + : BalanceDisplayMode + .availableBalance) + : savedDisplayMode; + final symbol = settingsStore + .fiatCurrency + .toString(); + var balance = '---'; + + if (displayMode == + BalanceDisplayMode + .availableBalance) { + balance = + '${balanceStore.fiatUnlockedBalance} $symbol'; + } + + if (displayMode == + BalanceDisplayMode.fullBalance) { + balance = + '${balanceStore.fiatFullBalance} $symbol'; + } + + return Text(balance, + style: TextStyle( + color: Palette.wildDarkBlue, + fontSize: 16)); + })) + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 35, right: 35, bottom: 30), + child: Container( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: PrimaryImageButton( + image: Image.asset( + 'assets/images/send_icon.png', + height: 25, + width: 25), + text: S.of(context).send, + onPressed: () => Navigator.of(context, + rootNavigator: true) + .pushNamed(Routes.send), + color: Theme.of(context) + .primaryTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .primaryTextTheme + .button + .decorationColor, + )), + SizedBox(width: 10), + Expanded( + child: PrimaryImageButton( + image: Image.asset( + 'assets/images/receive_icon.png', + height: 25, + width: 25), + text: S.of(context).receive, + onPressed: () => Navigator.of(context, + rootNavigator: true) + .pushNamed(Routes.receive), + color: Theme.of(context) + .accentTextTheme + .caption + .backgroundColor, + borderColor: Theme.of(context) + .accentTextTheme + .caption + .decorationColor, + )) + ], + ), + )), + ], + ), + ); + } + + if (index == 1 && actionListStore.totalCount > 0) { + return Padding( + padding: EdgeInsets.only(right: 20, top: 10, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + enabled: false, + value: -1, + child: Text(S.of(context).transactions, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black))), + PopupMenuItem( + value: 0, + child: Observer( + builder: (_) => Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text(S.of(context).incoming), + Checkbox( + value: actionListStore + .transactionFilterStore + .displayIncoming, + onChanged: (value) => + actionListStore + .transactionFilterStore + .toggleIncoming(), + ) + ]))), + PopupMenuItem( + value: 1, + child: Observer( + builder: (_) => Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text(S.of(context).outgoing), + Checkbox( + value: actionListStore + .transactionFilterStore + .displayOutgoing, + onChanged: (value) => + actionListStore + .transactionFilterStore + .toggleOutgoing(), + ) + ]))), + PopupMenuItem( + value: 2, + child: + Text(S.of(context).transactions_by_date)), + PopupMenuDivider(), + PopupMenuItem( + enabled: false, + value: -1, + child: Text(S.of(context).trades, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black))), + PopupMenuItem( + value: 3, + child: Observer( + builder: (_) => Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text('XMR.TO'), + Checkbox( + value: actionListStore + .tradeFilterStore + .displayXMRTO, + onChanged: (value) => + actionListStore + .tradeFilterStore + .toggleDisplayExchange( + ExchangeProviderDescription + .xmrto), + ) + ]))), + PopupMenuItem( + value: 4, + child: Observer( + builder: (_) => Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text('Change.NOW'), + Checkbox( + value: actionListStore + .tradeFilterStore + .displayChangeNow, + onChanged: (value) => + actionListStore + .tradeFilterStore + .toggleDisplayExchange( + ExchangeProviderDescription + .changeNow), + ) + ]))) + ], + child: Text(S.of(context).filters, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context) + .primaryTextTheme + .subtitle + .color)), + onSelected: (item) async { + if (item == 2) { + final List picked = + await DateRagePicker.showDatePicker( + context: context, + initialFirstDate: DateTime.now() + .subtract(Duration(days: 1)), + initialLastDate: (DateTime.now()), + firstDate: DateTime(2015), + lastDate: DateTime.now() + .add(Duration(days: 1))); + + if (picked != null && picked.length == 2) { + actionListStore.transactionFilterStore + .changeStartDate(picked.first); + actionListStore.transactionFilterStore + .changeEndDate(picked.last); + } + } + }, + ) + ]), + ); + } + + index -= 2; + + if (index < 0 || index >= items.length) { + return Container(); + } + + final item = items[index]; + + if (item is DateSectionItem) { + return DateSectionRaw(date: item.date); + } + + if (item is TransactionListItem) { + final transaction = item.transaction; + final savedDisplayMode = settingsStore.balanceDisplayMode; + final formattedAmount = + savedDisplayMode == BalanceDisplayMode.hiddenBalance + ? '---' + : transaction.amountFormatted(); + final formattedFiatAmount = + savedDisplayMode == BalanceDisplayMode.hiddenBalance + ? '---' + : transaction.fiatAmount(); + + return TransactionRow( + onTap: () => Navigator.of(context).pushNamed( + Routes.transactionDetails, + arguments: transaction), + direction: transaction.direction, + formattedDate: + transactionDateFormat.format(transaction.date), + formattedAmount: formattedAmount, + formattedFiatAmount: formattedFiatAmount, + isPending: transaction.isPending); + } + + if (item is TradeListItem) { + final trade = item.trade; + final savedDisplayMode = settingsStore.balanceDisplayMode; + final formattedAmount = trade.amount != null + ? savedDisplayMode == BalanceDisplayMode.hiddenBalance + ? '---' + : trade.amount + : trade.amount; + + return TradeRow( + onTap: () => Navigator.of(context) + .pushNamed(Routes.tradeDetails, arguments: trade), + provider: trade.provider, + from: trade.from, + to: trade.to, + createdAtFormattedDate: + DateFormat("dd.MM.yyyy, H:m").format(trade.createdAt), + formattedAmount: formattedAmount); + } + + return Container(); + }); + }); + } +} diff --git a/lib/src/screens/dashboard/date_section_raw.dart b/lib/src/screens/dashboard/date_section_raw.dart new file mode 100644 index 000000000..dea6310f4 --- /dev/null +++ b/lib/src/screens/dashboard/date_section_raw.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:intl/intl.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class DateSectionRaw extends StatelessWidget { + static final dateSectionDateFormat = DateFormat("d MMM"); + static final nowDate = DateTime.now(); + + final DateTime date; + + DateSectionRaw({this.date}); + + @override + Widget build(BuildContext context) { + final diffDays = date.difference(nowDate).inDays; + final isToday = nowDate.day == date.day && nowDate.month == date.month && nowDate.year == date.year; + var title = ""; + + if (isToday) { + title = S.of(context).today; + } else if (diffDays == 0) { + title = S.of(context).yesterday; + } else if (diffDays > -7 && diffDays < 0) { + final dateFormat = DateFormat("EEEE"); + title = dateFormat.format(date); + } else { + title = dateSectionDateFormat.format(date); + } + + return Padding( + padding: const EdgeInsets.only(top: 10, bottom: 10), + child: Center( + child: Text(title, + style: TextStyle(fontSize: 16, color: Palette.wildDarkBlue))), + ); + } +} diff --git a/lib/src/screens/dashboard/trade_row.dart b/lib/src/screens/dashboard/trade_row.dart new file mode 100644 index 000000000..668dc05a0 --- /dev/null +++ b/lib/src/screens/dashboard/trade_row.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; + +class TradeRow extends StatelessWidget { + final VoidCallback onTap; + final ExchangeProviderDescription provider; + final CryptoCurrency from; + final CryptoCurrency to; + final String createdAtFormattedDate; + final String formattedAmount; + + TradeRow( + {this.provider, + this.from, + this.to, + this.createdAtFormattedDate, + this.formattedAmount, + @required this.onTap}); + + @override + Widget build(BuildContext context) { + final amountCrypto = provider == ExchangeProviderDescription.xmrto + ? to.toString() + : from.toString(); + + return InkWell( + onTap: onTap, + child: Container( + padding: EdgeInsets.only(top: 14, bottom: 14, left: 20, right: 20), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: PaletteDark.darkGrey, + width: 0.5, + style: BorderStyle.solid))), + child: Row(children: [ + _getPoweredImage(provider), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('${from.toString()} → ${to.toString()}', + style: TextStyle( + fontSize: 16, + color: Theme.of(context).primaryTextTheme.subhead.color)), + formattedAmount != null + ? Text(formattedAmount + ' ' + amountCrypto, + style: const TextStyle( + fontSize: 16, color: Palette.purpleBlue)) + : Container() + ]), + SizedBox(height: 6), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(createdAtFormattedDate, + style: const TextStyle( + fontSize: 13, color: Palette.blueGrey)) + ]), + ], + ), + )) + ]), + )); + } + + Image _getPoweredImage(ExchangeProviderDescription provider) { + Image image; + switch (provider) { + case ExchangeProviderDescription.xmrto: + image = Image.asset('assets/images/xmr_btc.png'); + break; + case ExchangeProviderDescription.changeNow: + image = Image.asset('assets/images/change_now.png'); + break; + default: + image = null; + } + return image; + } +} diff --git a/lib/src/screens/dashboard/transaction_raw.dart b/lib/src/screens/dashboard/transaction_raw.dart new file mode 100644 index 000000000..0f7dc5ef5 --- /dev/null +++ b/lib/src/screens/dashboard/transaction_raw.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/domain/common/transaction_direction.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class TransactionRow extends StatelessWidget { + final VoidCallback onTap; + final TransactionDirection direction; + final String formattedDate; + final String formattedAmount; + final String formattedFiatAmount; + final bool isPending; + + TransactionRow( + {this.direction, + this.formattedDate, + this.formattedAmount, + this.formattedFiatAmount, + this.isPending, + @required this.onTap}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Container( + padding: EdgeInsets.only(top: 14, bottom: 14, left: 20, right: 20), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: PaletteDark.darkGrey, + width: 0.5, + style: BorderStyle.solid))), + child: Row(children: [ + Image.asset( + direction == TransactionDirection.incoming + ? 'assets/images/transaction_incoming.png' + : 'assets/images/transaction_outgoing.png', + height: 25, + width: 25), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + (direction == TransactionDirection.incoming + ? S.of(context).received + : S.of(context).sent) + + (isPending ? S.of(context).pending : ''), + style: TextStyle( + fontSize: 16, + color: Theme.of(context).primaryTextTheme.subhead.color)), + Text(formattedAmount, + style: const TextStyle( + fontSize: 16, color: Palette.purpleBlue)) + ]), + SizedBox(height: 6), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(formattedDate, + style: const TextStyle( + fontSize: 13, color: Palette.blueGrey)), + Text(formattedFiatAmount, + style: const TextStyle( + fontSize: 14, color: Palette.blueGrey)) + ]), + ], + ), + )) + ]), + )); + } +} diff --git a/lib/src/screens/dashboard/wallet_menu.dart b/lib/src/screens/dashboard/wallet_menu.dart new file mode 100644 index 000000000..c4899bbe2 --- /dev/null +++ b/lib/src/screens/dashboard/wallet_menu.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class WalletMenu { + final List items = [ + S.current.reconnect, + S.current.rescan, + S.current.wallets, + S.current.show_seed, + S.current.show_keys, + S.current.accounts, + S.current.address_book_menu + ]; + final BuildContext context; + + WalletMenu(this.context); + + void action(int index) { + switch (index) { + case 0: + _presentReconnectAlert(context); + break; + case 1: + Navigator.of(context).pushNamed(Routes.rescan); + break; + case 2: + Navigator.of(context).pushNamed(Routes.walletList); + + break; + case 3: + Navigator.of(context).pushNamed(Routes.auth, + arguments: (isAuthenticatedSuccessfully, auth) => + isAuthenticatedSuccessfully + ? Navigator.of(auth.context).popAndPushNamed(Routes.seed) + : null); + + break; + case 4: + Navigator.of(context).pushNamed(Routes.auth, + arguments: (isAuthenticatedSuccessfully, auth) => + isAuthenticatedSuccessfully + ? Navigator.of(auth.context) + .popAndPushNamed(Routes.showKeys) + : null); + break; + case 5: + Navigator.of(context).pushNamed(Routes.accountList); + break; + case 6: + Navigator.of(context).pushNamed(Routes.addressBook); + break; + default: + break; + } + } + + Future _presentReconnectAlert(BuildContext context) async { + final walletStore = Provider.of(context); + + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + S.of(context).reconnection, + textAlign: TextAlign.center, + ), + content: Text(S.of(context).reconnect_alert_text), + actions: [ + FlatButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(S.of(context).cancel)), + FlatButton( + onPressed: () { + walletStore.reconnect(); + Navigator.of(context).pop(); + }, + child: Text(S.of(context).ok)) + ], + ); + }); + } +} diff --git a/lib/src/screens/disclaimer/disclaimer_page.dart b/lib/src/screens/disclaimer/disclaimer_page.dart new file mode 100644 index 000000000..7f48c6264 --- /dev/null +++ b/lib/src/screens/disclaimer/disclaimer_page.dart @@ -0,0 +1,305 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; + +class DisclaimerPage extends BasePage { + final bool isReadOnly; + + DisclaimerPage({this.isReadOnly = false}); + + @override + bool get isModalBackButton => false; + + @override + String get title => 'Terms of Use'; + + @override + Widget body(BuildContext context) => DisclaimerPageBody(isReadOnly: true); +} + +class DisclaimerPageBody extends StatefulWidget { + final bool isReadOnly; + + DisclaimerPageBody({this.isReadOnly = true}); + + @override + createState() => DisclaimerBodyState(false); +} + +class DisclaimerBodyState extends State { + static const xmrto_url = 'https://xmr.to/app_static/html/tos.html'; + static const changenow_url = 'https://changenow.io/terms-of-use'; + + bool _isAccepted; + bool _checked = false; + String _fileText = ''; + + DisclaimerBodyState(this._isAccepted); + + launchUrl(String url) async { + if (await canLaunch(url)) await launch(url); + } + + Future getFileLines() async { + _fileText = await rootBundle.loadString('assets/text/Terms_of_Use.txt'); + setState(() {}); + } + + _showAlertDialog(BuildContext context) async { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + 'Terms and conditions', + textAlign: TextAlign.center, + ), + content: Text( + 'By using this app, you agree to the Terms of Agreement set forth to below', + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text('OK')), + ], + ); + }); + } + + _afterLayout(_) { + _showAlertDialog(context); + } + + @override + void initState() { + super.initState(); + getFileLines(); + if (_isAccepted) WidgetsBinding.instance.addPostFrameCallback(_afterLayout); + } + + @override + Widget build(BuildContext context) { + + return Column( + children: [ + SizedBox(height: 10.0), + Expanded( + child: Stack( + children: [ + SingleChildScrollView( + padding: EdgeInsets.only(left: 25.0, right: 25.0), + child: Column( + children: [ + !_isAccepted + ? Row( + children: [ + Expanded( + child: Text( + 'Terms and conditions', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold), + ), + ) + ], + ) + : Offstage(), + !_isAccepted + ? SizedBox( + height: 20.0, + ) + : Offstage(), + Row( + children: [ + Expanded( + child: Text( + 'Legal Disclaimer\nAnd\nTerms of Use', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12.0, fontWeight: FontWeight.bold), + ), + ) + ], + ), + SizedBox( + height: 16.0, + ), + Row( + children: [ + Expanded( + child: Text( + _fileText, + style: TextStyle(fontSize: 12.0), + )) + ], + ), + SizedBox( + height: 16.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Text( + 'Other Terms and Conditions', + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 14.0, fontWeight: FontWeight.bold), + ), + ) + ], + ), + SizedBox( + height: 16.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: GestureDetector( + onTap: () => launchUrl(xmrto_url), + child: Text( + xmrto_url, + textAlign: TextAlign.left, + style: TextStyle( + color: Colors.blue, + fontSize: 14.0, + fontWeight: FontWeight.normal, + decoration: TextDecoration.underline), + ), + )) + ], + ), + SizedBox( + height: 16.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: GestureDetector( + onTap: () => launchUrl(changenow_url), + child: Text( + changenow_url, + textAlign: TextAlign.left, + style: TextStyle( + color: Colors.blue, + fontSize: 14.0, + fontWeight: FontWeight.normal, + decoration: TextDecoration.underline), + ), + )) + ], + ), + SizedBox( + height: 16.0, + ) + ], + ), + ), + Container( + alignment: Alignment.bottomCenter, + child: Container( + height: 12.0, + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 0.7, sigmaY: 0.7), + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).backgroundColor.withOpacity(0.0), + Theme.of(context).backgroundColor, + ], + begin: FractionalOffset.topCenter, + end: FractionalOffset.bottomCenter, + ), + ), + ), + ), + ), + ), + ) + ], + )), + if (!widget.isReadOnly) ...[ + !_isAccepted + ? Row( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 25.0, top: 10.0, right: 25.0, bottom: 10.0), + child: InkWell( + onTap: () { + setState(() { + _checked = !_checked; + }); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + height: 25.0, + width: 25.0, + margin: EdgeInsets.only( + right: 10.0, + ), + decoration: BoxDecoration( + border: Border.all( + color: Palette.lightGrey, width: 1.0), + borderRadius: BorderRadius.all( + Radius.circular(8.0)), + color: Theme.of(context).backgroundColor), + child: _checked + ? Icon( + Icons.check, + color: Colors.blue, + size: 20.0, + ) + : null, + ), + Text( + 'I agree to Terms of Use', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14.0), + ) + ], + ), + )), + ), + ], + ) + : Offstage(), + !_isAccepted + ? Container( + padding: + EdgeInsets.only(left: 25.0, right: 25.0, bottom: 25.0), + child: PrimaryButton( + onPressed: _checked ? () {} : null, + text: 'Accept', + color: Theme.of(context).primaryTextTheme.button.backgroundColor, + borderColor: Theme.of(context).primaryTextTheme.button.decorationColor, + ), + ) + : Offstage(), + _isAccepted + ? SizedBox( + height: 20.0, + ) + : Offstage() + ], + ], + ); + } +} diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart new file mode 100644 index 000000000..82d6604cf --- /dev/null +++ b/lib/src/screens/exchange/exchange_page.dart @@ -0,0 +1,486 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; +import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart'; +import 'package:cake_wallet/src/stores/exchange/limits_state.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/stores/exchange/exchange_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; + +class ExchangePage extends BasePage { + String get title => S.current.exchange; + + @override + bool get isModalBackButton => true; + + final Image arrowBottomPurple = Image.asset( + 'assets/images/arrow_bottom_purple_icon.png', + height: 8, + ); + + @override + Widget middle(BuildContext context) { + final exchangeStore = Provider.of(context); + + return FlatButton( + onPressed: () => _presentProviderPicker(context), + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text(S.of(context).exchange, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w400, + color: Theme.of(context).primaryTextTheme.title.color)), + SizedBox(width: 5), + arrowBottomPurple + ]), + Observer( + builder: (_) => Text('${exchangeStore.provider.title}', + style: TextStyle( + fontSize: 10.0, + fontWeight: FontWeight.w400, + color: + Theme.of(context).primaryTextTheme.headline.color))) + ], + ), + ); + } + + @override + Widget trailing(BuildContext context) { + final exchangeStore = Provider.of(context); + + return ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + padding: EdgeInsets.all(0), + child: Text( + S.of(context).clear, + style: TextStyle( + color: Theme.of(context).accentTextTheme.caption.color, + fontWeight: FontWeight.w500, + fontSize: 16), + ), + onPressed: () => exchangeStore.reset()), + ); + } + + @override + Widget body(BuildContext context) => ExchangeForm(); + + void _presentProviderPicker(BuildContext context) { + final exchangeStore = Provider.of(context); + final items = exchangeStore.providersForCurrentPair(); + final selectedItem = items.indexOf(exchangeStore.provider); + + showDialog( + builder: (_) => Picker( + items: items, + selectedAtIndex: selectedItem, + title: S.of(context).change_exchange_provider, + onItemSelected: (provider) => + exchangeStore.changeProvider(provider: provider)), + context: context); + } +} + +class ExchangeForm extends StatefulWidget { + @override + State createState() => ExchangeFormState(); +} + +class ExchangeFormState extends State { + final depositKey = GlobalKey(); + final receiveKey = GlobalKey(); + final _formKey = GlobalKey(); + var _isReactionsSet = false; + + final Image arrowBottomPurple = Image.asset( + 'assets/images/arrow_bottom_purple_icon.png', + height: 8, + ); + final Image arrowBottomCakeGreen = Image.asset( + 'assets/images/arrow_bottom_cake_green.png', + height: 8, + ); + + @override + Widget build(BuildContext context) { + final exchangeStore = Provider.of(context); + final walletStore = Provider.of(context); + + final depositWalletName = + exchangeStore.depositCurrency == CryptoCurrency.xmr + ? walletStore.name + : null; + final receiveWalletName = + exchangeStore.receiveCurrency == CryptoCurrency.xmr + ? walletStore.name + : null; + + WidgetsBinding.instance.addPostFrameCallback( + (_) => _setReactions(context, exchangeStore, walletStore)); + + return Form( + key: _formKey, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(left: 20, right: 20), + content: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Padding( + padding: EdgeInsets.only(top: 10, bottom: 20), + child: Text( + S.of(context).you_will_send, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + height: 1.1, + color: Theme.of(context).primaryTextTheme.title.color), + ), + ), + ExchangeCard( + key: depositKey, + initialCurrency: exchangeStore.depositCurrency, + initialWalletName: depositWalletName, + initialAddress: + exchangeStore.depositCurrency == walletStore.type + ? walletStore.address + : null, + initialIsAmountEditable: + !(exchangeStore.provider is XMRTOExchangeProvider), + initialIsAddressEditable: + !(exchangeStore.provider is XMRTOExchangeProvider), + isAmountEstimated: + exchangeStore.provider is XMRTOExchangeProvider, + currencies: CryptoCurrency.all, + onCurrencySelected: (currency) => + exchangeStore.changeDepositCurrency(currency: currency), + imageArrow: arrowBottomPurple, + currencyValueValidator: (value) { + exchangeStore.validateCryptoCurrency(value); + return exchangeStore.errorMessage; + }, + addressTextFieldValidator: (value) { + exchangeStore.validateAddress(value, + cryptoCurrency: exchangeStore.depositCurrency); + return exchangeStore.errorMessage; + }, + ), + SizedBox(height: 35), + Padding( + padding: EdgeInsets.only(bottom: 20), + child: Text( + S.of(context).you_will_get, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + height: 1.1, + color: + Theme.of(context).primaryTextTheme.title.color), + )), + Observer( + builder: (_) => ExchangeCard( + key: receiveKey, + initialCurrency: exchangeStore.receiveCurrency, + initialWalletName: receiveWalletName, + initialAddress: + exchangeStore.receiveCurrency == walletStore.type + ? walletStore.address + : null, + initialIsAmountEditable: + exchangeStore.provider is XMRTOExchangeProvider, + initialIsAddressEditable: + exchangeStore.provider is XMRTOExchangeProvider, + isAmountEstimated: !(exchangeStore.provider + is XMRTOExchangeProvider), + currencies: CryptoCurrency.all, + onCurrencySelected: (currency) => exchangeStore + .changeReceiveCurrency(currency: currency), + imageArrow: arrowBottomCakeGreen, + currencyValueValidator: (value) { + exchangeStore.validateCryptoCurrency(value); + return exchangeStore.errorMessage; + }, + addressTextFieldValidator: (value) { + exchangeStore.validateAddress(value, + cryptoCurrency: exchangeStore.receiveCurrency); + return exchangeStore.errorMessage; + }, + )), + ], + ), + ), + bottomSectionPadding: EdgeInsets.only(top: 35, left: 20, right: 20), + bottomSection: Column(children: [ + Padding( + padding: EdgeInsets.only(bottom: 15), + child: Observer(builder: (_) { + final description = + exchangeStore.provider is XMRTOExchangeProvider + ? S.of(context).amount_is_guaranteed + : S.of(context).amount_is_estimate; + return Center( + child: Text( + description, + style: TextStyle(color: Palette.blueGrey, fontSize: 12), + ), + ); + }), + ), + Observer( + builder: (_) => LoadingPrimaryButton( + text: S.of(context).exchange, + onPressed: () { + if (_formKey.currentState.validate()) + exchangeStore.createTrade(); + }, + color: Theme.of(context) + .primaryTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .primaryTextTheme + .button + .decorationColor, + isLoading: exchangeStore.tradeState is TradeIsCreating, + )), + Observer(builder: (_) { + final title = exchangeStore.provider.description.title; + var imageSrc = ''; + + switch (exchangeStore.provider.description) { + case ExchangeProviderDescription.xmrto: + imageSrc = 'assets/images/xmr_btc.png'; + break; + case ExchangeProviderDescription.changeNow: + imageSrc = 'assets/images/change_now.png'; + break; + } + + return Padding( + padding: EdgeInsets.only(top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset(imageSrc), + SizedBox(width: 10), + Text( + S.of(context).powered_by(title), + style: TextStyle(fontSize: 14, color: Palette.powered), + ) + ], + ), + ); + }) + ]), + )); + } + + void _setReactions( + BuildContext context, ExchangeStore store, WalletStore walletStore) { + if (_isReactionsSet) { + return; + } + + final depositAddressController = depositKey.currentState.addressController; + final depositAmountController = depositKey.currentState.amountController; + final receiveAddressController = receiveKey.currentState.addressController; + final receiveAmountController = receiveKey.currentState.amountController; + final limitsState = store.limitsState; + + if (limitsState is LimitsLoadedSuccessfully) { + final min = limitsState.limits.min != null + ? limitsState.limits.min.toString() + : null; + final max = limitsState.limits.max != null + ? limitsState.limits.max.toString() + : null; + final key = + store.provider is XMRTOExchangeProvider ? receiveKey : depositKey; + key.currentState.changeLimits(min: min, max: max); + } + + _onCurrencyChange(store.receiveCurrency, walletStore, receiveKey); + _onCurrencyChange(store.depositCurrency, walletStore, depositKey); + + reaction( + (_) => walletStore.name, + (_) => _onWalletNameChange( + walletStore, store.receiveCurrency, receiveKey)); + + reaction( + (_) => walletStore.name, + (_) => _onWalletNameChange( + walletStore, store.depositCurrency, depositKey)); + + reaction((_) => store.receiveCurrency, + (currency) => _onCurrencyChange(currency, walletStore, receiveKey)); + + reaction((_) => store.depositCurrency, + (currency) => _onCurrencyChange(currency, walletStore, depositKey)); + + reaction((_) => store.depositAmount, (amount) { + if (depositKey.currentState.amountController.text != amount) { + depositKey.currentState.amountController.text = amount; + } + }); + + reaction((_) => store.receiveAmount, (amount) { + if (receiveKey.currentState.amountController.text != + store.receiveAmount) { + receiveKey.currentState.amountController.text = amount; + } + }); + + reaction((_) => store.provider, (provider) { + final isReversed = provider is XMRTOExchangeProvider; + + if (isReversed) { + receiveKey.currentState.isAddressEditable(isEditable: true); + receiveKey.currentState.isAmountEditable(isEditable: true); + depositKey.currentState.isAddressEditable(isEditable: false); + depositKey.currentState.isAmountEditable(isEditable: false); + } else { + receiveKey.currentState.isAddressEditable(isEditable: true); + receiveKey.currentState.isAmountEditable(isEditable: false); + depositKey.currentState.isAddressEditable(isEditable: true); + depositKey.currentState.isAmountEditable(isEditable: true); + } + + depositKey.currentState.changeIsAmountEstimated(isReversed); + receiveKey.currentState.changeIsAmountEstimated(!isReversed); + }); + + reaction((_) => store.tradeState, (state) { + if (state is TradeIsCreatedFailure) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).error), + content: Text(state.error), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () => Navigator.of(context).pop()) + ], + ); + }); + }); + } + if (state is TradeIsCreatedSuccessfully) { + Navigator.of(context) + .pushNamed(Routes.exchangeConfirm, arguments: state.trade); + } + }); + + reaction((_) => store.limitsState, (state) { + final isXMRTO = store.provider is XMRTOExchangeProvider; + String min; + String max; + + if (state is LimitsLoadedSuccessfully) { + min = state.limits.min != null ? state.limits.min.toString() : null; + max = state.limits.max != null ? state.limits.max.toString() : null; + } + + if (state is LimitsLoadedFailure) { + min = '0'; + max = '0'; + } + + if (state is LimitsIsLoading) { + min = '...'; + max = '...'; + } + + final depositMin = isXMRTO ? null : min; + final depositMax = isXMRTO ? null : max; + final receiveMin = isXMRTO ? min : null; + final receiveMax = isXMRTO ? max : null; + + depositKey.currentState.changeLimits(min: depositMin, max: depositMax); + receiveKey.currentState.changeLimits(min: receiveMin, max: receiveMax); + }); + + depositAddressController.addListener( + () => store.depositAddress = depositAddressController.text); + + depositAmountController.addListener(() { + if (depositAmountController.text != store.depositAmount) { + store.changeDepositAmount(amount: depositAmountController.text); + } + }); + + receiveAddressController.addListener( + () => store.receiveAddress = receiveAddressController.text); + + receiveAmountController.addListener(() { + if (receiveAmountController.text != store.receiveAmount) { + store.changeReceiveAmount(amount: receiveAmountController.text); + } + }); + + reaction((_) => walletStore.address, (address) { + if (store.depositCurrency == CryptoCurrency.xmr) { + depositKey.currentState.changeAddress(address: address); + } + + if (store.receiveCurrency == CryptoCurrency.xmr) { + receiveKey.currentState.changeAddress(address: address); + } + }); + + _isReactionsSet = true; + } + + void _onCurrencyChange(CryptoCurrency currency, WalletStore walletStore, + GlobalKey key) { + final isCurrentTypeWallet = currency == walletStore.type; + + key.currentState.changeSelectedCurrency(currency); + key.currentState + .changeWalletName(isCurrentTypeWallet ? walletStore.name : null); + + key.currentState + .changeAddress(address: isCurrentTypeWallet ? walletStore.address : ''); + + key.currentState.changeAmount(amount: ''); + } + + void _onWalletNameChange(WalletStore walletStore, CryptoCurrency currency, + GlobalKey key) { + final isCurrentTypeWallet = currency == walletStore.type; + + if (isCurrentTypeWallet) { + key.currentState.changeWalletName(walletStore.name); + key.currentState.addressController.text = walletStore.address; + } else if (key.currentState.addressController.text == walletStore.address) { + key.currentState.changeWalletName(null); + key.currentState.addressController.text = null; + } + } +} diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart new file mode 100644 index 000000000..acef42a3e --- /dev/null +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -0,0 +1,261 @@ +import 'package:flutter/services.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/src/widgets/address_text_field.dart'; + +class ExchangeCard extends StatefulWidget { + final List currencies; + final Function(CryptoCurrency) onCurrencySelected; + final CryptoCurrency initialCurrency; + final String initialWalletName; + final String initialAddress; + final bool initialIsAmountEditable; + final bool initialIsAddressEditable; + final bool isAmountEstimated; + final Image imageArrow; + final FormFieldValidator currencyValueValidator; + final FormFieldValidator addressTextFieldValidator; + + ExchangeCard( + {Key key, + this.initialCurrency, + this.initialAddress, + this.initialWalletName, + this.initialIsAmountEditable, + this.initialIsAddressEditable, + this.isAmountEstimated, + this.currencies, + this.onCurrencySelected, + this.imageArrow, + this.currencyValueValidator, + this.addressTextFieldValidator}) + : super(key: key); + + @override + createState() => ExchangeCardState(); +} + +class ExchangeCardState extends State { + final addressController = TextEditingController(); + final amountController = TextEditingController(); + + String _min; + String _max; + CryptoCurrency _selectedCurrency; + String _walletName; + bool _isAmountEditable; + bool _isAddressEditable; + bool _isAmountEstimated; + + @override + void initState() { + _isAmountEditable = widget.initialIsAmountEditable; + _isAddressEditable = widget.initialIsAddressEditable; + _walletName = widget.initialWalletName; + _selectedCurrency = widget.initialCurrency; + _isAmountEstimated = widget.isAmountEstimated; + addressController.text = widget.initialAddress; + super.initState(); + } + + void changeLimits({String min, String max}) { + setState(() { + _min = min; + _max = max; + }); + } + + void changeSelectedCurrency(CryptoCurrency currency) { + setState(() => _selectedCurrency = currency); + } + + void changeWalletName(String walletName) { + setState(() => _walletName = walletName); + } + + void changeIsAction(bool isActive) { + setState(() => _isAmountEditable = isActive); + } + + void isAmountEditable({bool isEditable = true}) { + setState(() => _isAmountEditable = isEditable); + } + + void isAddressEditable({bool isEditable = true}) { + setState(() => _isAddressEditable = isEditable); + } + + void changeAddress({String address}) { + setState(() => addressController.text = address); + } + + void changeAmount({String amount}) { + setState(() => amountController.text = amount); + } + + void changeIsAmountEstimated(bool isEstimated) { + setState(() => _isAmountEstimated = isEstimated); + } + + @override + Widget build(BuildContext context) { + + return Container( + padding: EdgeInsets.fromLTRB(22, 15, 22, 30), + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.all(Radius.circular(12))), + child: Column(children: [ + _isAmountEstimated != null && _isAmountEstimated + ? Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + SizedBox( + height: 30, + child: Container( + padding: EdgeInsets.fromLTRB(10, 5, 10, 5), + decoration: BoxDecoration( + color: Palette.lightGrey, + borderRadius: BorderRadius.circular(10.0)), + child: Text( + S.of(context).estimated, + style: TextStyle( + fontSize: 14, + color: Palette.wildDarkBlue, + fontWeight: FontWeight.bold), + ), + ), + ), + ]) + : Container(), + Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 52, + width: 80, + child: InkWell( + onTap: () => _presentPicker(context), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(_selectedCurrency.toString(), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + color: Theme.of(context).primaryTextTheme.title.color)), + widget.imageArrow + ]), + _walletName != null + ? Text(_walletName, + style: TextStyle( + fontSize: 12, + color: Palette.wildDarkBlue)) + : SizedBox(), + ]), + ), + ), + SizedBox(width: 10), + Flexible( + child: Column( + children: [ + TextFormField( + style: TextStyle(fontSize: 23, height: 1.21), + controller: amountController, + enabled: _isAmountEditable, + textAlign: TextAlign.right, + keyboardType: TextInputType.numberWithOptions( + signed: false, decimal: false), + inputFormatters: [BlacklistingTextInputFormatter(new RegExp('[\\-|\\ |\\,]'))], + decoration: InputDecoration( + hintStyle: TextStyle( + color: Theme.of(context).cardTheme.color, + fontSize: 23, + height: 1.21), + hintText: '0.00000000', + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, + width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: _isAmountEditable + ? Palette.deepPurple + : Theme.of(context).focusColor, + width: 1.0))), + validator: widget.currencyValueValidator), + SizedBox(height: 5), + SizedBox( + height: 15, + width: double.infinity, + child: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _min != null + ? Text( + S.of(context).min_value(_min, _selectedCurrency.toString()), + style: TextStyle( + fontSize: 10, + height: 1.2, + color: Theme.of(context).primaryTextTheme.subtitle.color), + ) + : SizedBox(), + _min != null ? SizedBox(width: 10) : SizedBox(), + _max != null + ? Text( + S.of(context).max_value(_max, _selectedCurrency.toString()), + style: TextStyle( + fontSize: 10, + height: 1.2, + color: Theme.of(context).primaryTextTheme.subtitle.color)) + : SizedBox(), + ]), + ), + ), + ], + ), + ), + ]), + ), + SizedBox(height: 10), + AddressTextField( + controller: addressController, + isActive: _isAddressEditable, + options: _isAddressEditable + ? _walletName != null + ? [ + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook, + AddressTextFieldOption.subaddressList + ] + : [ + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook, + ] + : [], + validator: widget.addressTextFieldValidator, + ) + ]), + ); + } + + void _presentPicker(BuildContext context) { + showDialog( + builder: (_) => Picker( + items: widget.currencies, + selectedAtIndex: widget.currencies.indexOf(_selectedCurrency), + title: S.of(context).change_currency, + onItemSelected: (item) => widget.onCurrencySelected != null + ? widget.onCurrencySelected(item) + : null), + context: context); + } +} diff --git a/lib/src/screens/exchange_trade/exchange_confirm_page.dart b/lib/src/screens/exchange_trade/exchange_confirm_page.dart new file mode 100644 index 000000000..10762293d --- /dev/null +++ b/lib/src/screens/exchange_trade/exchange_confirm_page.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; + +class ExchangeConfirmPage extends BasePage { + String get title => S.current.copy_id; + + final Trade trade; + + ExchangeConfirmPage({@required this.trade}); + + @override + Widget body(BuildContext context) { + return Column( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only(left: 80.0, right: 80.0), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + S.of(context).exchange_result_write_down_trade_id, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.0, + color: Theme.of(context) + .primaryTextTheme + .button + .color), + ), + SizedBox( + height: 70.0, + ), + Text( + S.of(context).trade_id(trade.id), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.0, + color: Theme.of(context) + .primaryTextTheme + .button + .color), + ), + SizedBox( + height: 70.0, + ), + PrimaryButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: trade.id)); + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + S.of(context).copied_to_clipboard, + textAlign: TextAlign.center, + style: TextStyle(color: Colors.white), + ), + backgroundColor: Colors.green, + )); + }, + text: S.of(context).copy_id, + color: Theme.of(context) + .accentTextTheme + .caption + .backgroundColor, + borderColor: Theme.of(context) + .accentTextTheme + .caption + .decorationColor) + ], + ), + ))), + Container( + padding: EdgeInsets.only(left: 20.0, right: 20.0, bottom: 40.0), + child: PrimaryButton( + onPressed: () => Navigator.of(context) + .pushReplacementNamed(Routes.exchangeTrade, arguments: trade), + text: S.of(context).saved_the_trade_id, + color: Theme.of(context).primaryTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).primaryTextTheme.button.decorationColor), + ) + ], + ); + } +} diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart new file mode 100644 index 000000000..887d58917 --- /dev/null +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -0,0 +1,373 @@ +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/exchange_trade/exchange_trade_store.dart'; +import 'package:cake_wallet/src/stores/send/send_store.dart'; +import 'package:cake_wallet/src/stores/send/sending_state.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/screens/exchange_trade/widgets/copy_button.dart'; +import 'package:cake_wallet/src/screens/receive/qr_image.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/exchange_trade/widgets/timer_widget.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; + +class ExchangeTradePage extends BasePage { + String get title => S.current.exchange; + + @override + Widget body(BuildContext context) => ExchangeTradeForm(); +} + +class ExchangeTradeForm extends StatefulWidget { + @override + createState() => ExchangeTradeState(); +} + +class ExchangeTradeState extends State { + final fetchingLabel = S.current.fetching; + String get title => S.current.exchange; + + bool _effectsInstalled = false; + + @override + Widget build(BuildContext context) { + final tradeStore = Provider.of(context); + final sendStore = Provider.of(context); + final walletStore = Provider.of(context); + + _setEffects(context); + + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(left: 20, right: 20, top: 20), + content: Observer(builder: (_) { + final trade = tradeStore.trade; + final walletName = walletStore.name; + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + S.of(context).id, + style: TextStyle( + height: 2, + fontWeight: FontWeight.bold, + fontSize: 14.0, + color: Theme.of(context) + .primaryTextTheme + .button + .color), + ), + Text( + '${trade.id ?? fetchingLabel}', + style: TextStyle( + fontSize: 14.0, + height: 2, + color: Theme.of(context) + .primaryTextTheme + .subtitle + .color), + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + S.of(context).amount, + style: TextStyle( + height: 2, + fontWeight: FontWeight.bold, + fontSize: 14.0, + color: Theme.of(context) + .primaryTextTheme + .button + .color), + ), + Text( + '${trade.amount ?? fetchingLabel}', + style: TextStyle( + fontSize: 14.0, + height: 2, + color: Theme.of(context) + .primaryTextTheme + .subtitle + .color), + ) + ], + ), + trade.extraId != null + ? Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + S.of(context).payment_id, + style: TextStyle( + height: 2, + fontWeight: FontWeight.bold, + fontSize: 14.0, + color: Theme.of(context) + .primaryTextTheme + .button + .color), + ), + Text( + '${trade.extraId ?? fetchingLabel}', + style: TextStyle( + fontSize: 14.0, + height: 2, + color: Theme.of(context) + .primaryTextTheme + .subtitle + .color), + ) + ], + ) + : Container(), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + S.of(context).status, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.bold, + color: Theme.of(context) + .primaryTextTheme + .button + .color, + height: 2), + ), + Text( + '${trade.state ?? fetchingLabel}', + style: TextStyle( + fontSize: 14.0, + height: 2, + color: Theme.of(context) + .primaryTextTheme + .subtitle + .color), + ) + ], + ), + trade.expiredAt != null + ? Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + S.of(context).offer_expires_in, + style: TextStyle( + fontSize: 14.0, + color: Theme.of(context) + .primaryTextTheme + .button + .color), + ), + TimerWidget(trade.expiredAt, + color: Theme.of(context) + .primaryTextTheme + .subtitle + .color) + ], + ) + : Container(), + ], + ), + SizedBox(width: 10), + Container( + padding: EdgeInsets.all(5), + color: Colors.white, + constraints: BoxConstraints(minWidth: 65, maxWidth: 105), + child: QrImage( + data: trade.inputAddress ?? fetchingLabel, + backgroundColor: Colors.white)) + ], + ), + ), + SizedBox( + height: 20.0, + ), + Center( + child: Text( + S.of(context).trade_is_powered_by(trade.provider != null + ? trade.provider.title + : fetchingLabel), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryTextTheme.headline.color), + ), + ), + Container( + padding: EdgeInsets.only(top: 20, bottom: 20), + child: Center( + child: Text( + trade.inputAddress ?? fetchingLabel, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14.0, color: Palette.lightViolet), + ), + ), + ), + Container( + padding: EdgeInsets.only(left: 50.0, right: 50.0), + child: Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(right: 5.0), + child: CopyButton( + onPressed: () => Clipboard.setData( + ClipboardData(text: trade.inputAddress)), + text: S.of(context).copy_address, + color: Theme.of(context) + .accentTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .accentTextTheme + .button + .decorationColor), + )), + Flexible( + child: Container( + padding: EdgeInsets.only(left: 5.0), + child: CopyButton( + onPressed: () => + Clipboard.setData(ClipboardData(text: trade.id)), + text: S.of(context).copy_id, + color: Theme.of(context) + .accentTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .accentTextTheme + .button + .decorationColor), + )) + ], + ), + ), + Container( + padding: EdgeInsets.only(top: 20), + child: Text( + tradeStore.isSendable + ? S.of(context).exchange_result_confirm( + trade.amount ?? fetchingLabel, + trade.from.toString(), + walletName) + : S.of(context).exchange_result_description( + trade.amount ?? fetchingLabel, trade.from.toString()), + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 13.0, + color: Theme.of(context).primaryTextTheme.title.color), + ), + ), + Text( + S.of(context).exchange_result_write_down_ID, + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 13.0, + color: Theme.of(context).accentTextTheme.title.color), + ) + ], + ); + }), + bottomSection: Observer( + builder: (_) => tradeStore.trade.from == CryptoCurrency.xmr && + !(sendStore.state is TransactionCreatedSuccessfully) + ? Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0), + child: LoadingPrimaryButton( + isDisabled: tradeStore.trade.inputAddress == null || + tradeStore.trade.inputAddress.isEmpty, + isLoading: sendStore.state is CreatingTransaction || + sendStore.state is TransactionCommitted, + onPressed: () => sendStore.createTransaction( + address: tradeStore.trade.inputAddress, + amount: tradeStore.trade.amount), + text: tradeStore.trade.provider == + ExchangeProviderDescription.xmrto + ? S.of(context).confirm + : S.of(context).send_xmr, + color: Theme.of(context) + .primaryTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .primaryTextTheme + .button + .decorationColor), + ) + : Offstage()), + ); + } + + void _setEffects(BuildContext context) { + if (_effectsInstalled) { + return; + } + + final sendStore = Provider.of(context); + + reaction((_) => sendStore.state, (state) { + if (state is SendingFailed) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).error), + content: Text(state.error), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () => Navigator.of(context).pop()) + ], + ); + }); + }); + } + + if (state is TransactionCreatedSuccessfully) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).sending), + content: Text(S.of(context).transaction_sent), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () => Navigator.of(context).pop()) + ], + ); + }); + }); + } + }); + + _effectsInstalled = true; + } +} diff --git a/lib/src/screens/exchange_trade/widgets/copy_button.dart b/lib/src/screens/exchange_trade/widgets/copy_button.dart new file mode 100644 index 000000000..b152157ef --- /dev/null +++ b/lib/src/screens/exchange_trade/widgets/copy_button.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class CopyButton extends StatelessWidget { + + final VoidCallback onPressed; + final Color color; + final Color borderColor; + final String text; + + const CopyButton({ + @required this.onPressed, + @required this.text, + @required this.color, + @required this.borderColor}); + + @override + Widget build(BuildContext context) { + return ButtonTheme( + minWidth: double.infinity, + height: 44.0, + child: FlatButton( + onPressed: onPressed, + color: color, + shape: RoundedRectangleBorder(side: BorderSide(color: borderColor), borderRadius: BorderRadius.circular(10.0)), + child: Text(text, style: TextStyle(fontSize: 14.0)), + ) + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/exchange_trade/widgets/timer_widget.dart b/lib/src/screens/exchange_trade/widgets/timer_widget.dart new file mode 100644 index 000000000..d3154aaa1 --- /dev/null +++ b/lib/src/screens/exchange_trade/widgets/timer_widget.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; +import 'package:cake_wallet/generated/i18n.dart'; + +class TimerWidget extends StatefulWidget { + final DateTime expiratedAt; + final Color color; + + TimerWidget(this.expiratedAt, {this.color = Colors.black}); + + @override + createState() => TimerWidgetState(); +} + +class TimerWidgetState extends State { + int _leftSeconds; + int _minutes; + int _seconds; + bool _isExpired; + Timer _timer; + + TimerWidgetState(); + + @override + void initState() { + super.initState(); + final start = DateTime.now(); + _isExpired = false; + _leftSeconds = widget.expiratedAt.difference(start).inSeconds; + _recalculate(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _timer = Timer.periodic(Duration(seconds: 1), (timer) { + if (_isExpired) { + timer.cancel(); + } + + _leftSeconds--; + _isExpired = _leftSeconds <= 0; + _recalculate(); + setState(() {}); + }); + }); + } + + @override + void dispose() { + if (_timer != null) _timer.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return _isExpired + ? Text(S.of(context).expired, style: TextStyle(fontSize: 14.0, color: Colors.red)) + : Text( + S.of(context).time(_minutes.toString(), _seconds.toString()), + style: TextStyle(fontSize: 14.0, color: widget.color), + ); + } + + void _recalculate() { + _minutes = _leftSeconds ~/ 60; + _seconds = _leftSeconds % 60; + } +} diff --git a/lib/src/screens/faq/faq_page.dart b/lib/src/screens/faq/faq_page.dart new file mode 100644 index 000000000..121865809 --- /dev/null +++ b/lib/src/screens/faq/faq_page.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class FaqPage extends BasePage { + String get title => S.current.faq; + + @override + Widget body(BuildContext context) { + + return FutureBuilder( + builder: (context, snapshot) { + var faqItems = json.decode(snapshot.data.toString()); + + return ListView.separated( + itemBuilder: (BuildContext context, int index) { + final itemTitle = faqItems[index]["question"]; + final itemChild = faqItems[index]["answer"]; + + return ExpansionTile( + title: Text( + itemTitle + ), + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only( + left: 15.0, + right: 15.0 + ), + child: Text( + itemChild, + ), + ) + ) + ], + ) + ], + ); + }, + separatorBuilder: (_, __) => Divider( + color: Theme.of(context).dividerTheme.color, + height: 1.0, + ), + itemCount: faqItems == null ? 0 : faqItems.length, + ); + }, + future: rootBundle.loadString(getFaqPath(context)), + ); + } + + String getFaqPath(BuildContext context) { + final settingsStore = Provider.of(context); + + switch (settingsStore.languageCode) { + case 'en': + return 'assets/faq/faq_en.json'; + case 'ru': + return 'assets/faq/faq_ru.json'; + case 'es': + return 'assets/faq/faq_es.json'; + case 'ja': + return 'assets/faq/faq_ja.json'; + case 'ko': + return 'assets/faq/faq_ko.json'; + case 'hi': + return 'assets/faq/faq_hi.json'; + case 'de': + return 'assets/faq/faq_de.json'; + case 'zh': + return 'assets/faq/faq_zh.json'; + case 'pt': + return 'assets/faq/faq_pt.json'; + case 'pl': + return 'assets/faq/faq_pl.json'; + case 'nl': + return 'assets/faq/faq_nl.json'; + default: + return 'assets/faq/faq_en.json'; + } + } + +} \ No newline at end of file diff --git a/lib/src/screens/login/login_page.dart b/lib/src/screens/login/login_page.dart new file mode 100644 index 000000000..684a35d70 --- /dev/null +++ b/lib/src/screens/login/login_page.dart @@ -0,0 +1,114 @@ +// import 'package:cake_wallet/src/stores/auth/auth_state.dart'; +// import 'package:cake_wallet/src/stores/login/login_store.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter/cupertino.dart'; +// import 'package:mobx/mobx.dart'; +// import 'package:provider/provider.dart'; +// import 'package:cake_wallet/src/screens/pin_code/pin_code.dart'; +// import 'package:cake_wallet/src/screens/base_page.dart'; + +// class LoginPage extends BasePage { +// @override +// Widget leading(BuildContext context) => Container(); + +// @override +// Widget body(BuildContext context) => _LoginPinCode(); +// } + +// class _LoginPinCode extends PinCode { +// _LoginPinCode([Key key]) : super((_, __) => null, false, key); + +// @override +// _LoginPinCodeState createState() => _LoginPinCodeState(); +// } + +// class _LoginPinCodeState extends PinCodeState<_LoginPinCode> { +// LoginStore _loginStore; +// String title = 'Enter your PIN'; + +// @override +// Future onPinCodeEntered(PinCodeState state) async { +// final password = pin.fold("", (ac, val) => ac + '$val'); + +// await _loginStore.authStore.auth(password: password); +// super.onPinCodeEntered(state); +// } + +// @override +// Widget build(BuildContext context) { +// _setLoginStore(store: Provider.of(context)); +// return body(context); +// } + +// void _setLoginStore({LoginStore store}) { +// if (_loginStore != null) { +// return; +// } + +// _loginStore = store; + +// reaction((_) => _loginStore.authStore.state, (state) { +// if (state is AuthenticationFailure) { +// WidgetsBinding.instance.addPostFrameCallback((_) { +// clear(); +// Scaffold.of(context).showSnackBar( +// SnackBar( +// content: Text(state.error), +// backgroundColor: Colors.red, +// ), +// ); +// }); +// } + +// if (state is AuthenticationBanned) { +// WidgetsBinding.instance.addPostFrameCallback((_) { +// clear(); +// Scaffold.of(context).showSnackBar( +// SnackBar( +// content: Text(state.error), +// backgroundColor: Colors.red, +// ), +// ); +// }); +// } + +// if (state is AuthenticationInProgress) { +// WidgetsBinding.instance.addPostFrameCallback((_) { +// Scaffold.of(context).hideCurrentSnackBar(); +// Scaffold.of(context).showSnackBar( +// SnackBar( +// content: Text('Authentication'), +// backgroundColor: Colors.green, +// ), +// ); +// }); +// } +// }); + +// reaction((_) => _loginStore.state, (state) { +// if (state is LoadedCurrentWalletFailure) { +// WidgetsBinding.instance.addPostFrameCallback((_) { +// clear(); +// Scaffold.of(context).showSnackBar( +// SnackBar( +// content: Text(state.errorMessage), +// backgroundColor: Colors.red, +// ), +// ); +// }); +// } + +// if (state is LoadingCurrentWallet) { +// WidgetsBinding.instance.addPostFrameCallback((_) { +// Scaffold.of(context).hideCurrentSnackBar(); +// Scaffold.of(context).showSnackBar( +// SnackBar( +// content: Text('Loading your wallet'), +// backgroundColor: Colors.green, +// ), +// ); +// }); +// } +// }); +// } +// } diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart new file mode 100644 index 000000000..9b713e5bf --- /dev/null +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -0,0 +1,121 @@ +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_store.dart'; +import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_state.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/palette.dart'; + +class NewWalletPage extends BasePage { + final WalletListService walletsService; + final WalletService walletService; + final SharedPreferences sharedPreferences; + + String get title => S.current.new_wallet; + + NewWalletPage( + {@required this.walletsService, + @required this.walletService, + @required this.sharedPreferences}); + + @override + Widget body(BuildContext context) => WalletNameForm(); +} + +class WalletNameForm extends StatefulWidget { + @override + createState() => _WalletNameFormState(); +} + +class _WalletNameFormState extends State { + final _formKey = GlobalKey(); + final nameController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final walletCreationStore = Provider.of(context); + + reaction((_) => walletCreationStore.state, (state) { + if (state is WalletCreatedSuccessfully) { + Navigator.of(context).popUntil((route) => route.isFirst); + } + + if (state is WalletCreationFailure) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text(state.error), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ); + }); + }); + } + }); + + return ScrollableWithBottomSection( + content: Column(children: [ + Padding( + padding: EdgeInsets.only(bottom: 10), + child: Image.asset('assets/images/bitmap.png', + height: 224, width: 400), + ), + Padding( + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), + child: Form( + key: _formKey, + child: TextFormField( + style: TextStyle( + fontSize: 24.0, + color: Theme.of(context).accentTextTheme.subtitle.color), + controller: nameController, + decoration: InputDecoration( + hintStyle: TextStyle( + fontSize: 24.0, color: Theme.of(context).hintColor), + hintText: S.of(context).wallet_name, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + walletCreationStore.validateWalletName(value); + return walletCreationStore.errorMessage; + }, + )), + ) + ]), + bottomSection: Observer( + builder: (context) { + return LoadingPrimaryButton( + onPressed: () { + if (_formKey.currentState.validate()) { + walletCreationStore.create(name: nameController.text); + } + }, + text: S.of(context).continue_text, + color: Theme.of(context).primaryTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).primaryTextTheme.button.decorationColor, + isLoading: walletCreationStore.state is WalletIsCreating, + ); + }, + )); + } +} diff --git a/lib/src/screens/nodes/new_node_page.dart b/lib/src/screens/nodes/new_node_page.dart new file mode 100644 index 000000000..96a83926d --- /dev/null +++ b/lib/src/screens/nodes/new_node_page.dart @@ -0,0 +1,215 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/node_list/node_list_store.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class NewNodePage extends BasePage { + @override + String get title => S.current.node_new; + + @override + Widget body(BuildContext context) => NewNodePageForm(); +} + +class NewNodePageForm extends StatefulWidget { + @override + createState() => NewNodeFormState(); +} + +class NewNodeFormState extends State { + final _formKey = GlobalKey(); + final _nodeAddressController = TextEditingController(); + final _nodePortController = TextEditingController(); + final _loginController = TextEditingController(); + final _passwordController = TextEditingController(); + + @override + void dispose() { + _nodeAddressController.dispose(); + _nodePortController.dispose(); + _loginController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final nodeList = Provider.of(context); + + return Form( + key: _formKey, + child: Column( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only(left: 38.0, right: 38.0, top: 0), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: TextFormField( + style: TextStyle(fontSize: 14.0), + decoration: InputDecoration( + hintStyle: + TextStyle(color: Palette.wildDarkBlue), + hintText: S.of(context).node_address, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + controller: _nodeAddressController, + validator: (value) { + nodeList.validateNodeAddress(value); + return nodeList.errorMessage; + }, + ), + ) + ], + ), + SizedBox(height: 10.0), + Row( + children: [ + Expanded( + child: TextFormField( + style: TextStyle(fontSize: 14.0), + keyboardType: TextInputType.numberWithOptions( + signed: false, decimal: false), + decoration: InputDecoration( + hintStyle: + TextStyle(color: Palette.wildDarkBlue), + hintText: S.of(context).node_port, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + controller: _nodePortController, + validator: (value) { + nodeList.validateNodePort(value); + return nodeList.errorMessage; + }, + ), + ) + ], + ), + SizedBox(height: 10.0), + Row( + children: [ + Expanded( + child: TextFormField( + style: TextStyle(fontSize: 14.0), + decoration: InputDecoration( + hintStyle: + TextStyle(color: Palette.wildDarkBlue), + hintText: S.of(context).login, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + controller: _loginController, + validator: (value) => null, + ), + ) + ], + ), + SizedBox(height: 10.0), + Row( + children: [ + Expanded( + child: TextFormField( + style: TextStyle(fontSize: 14.0), + decoration: InputDecoration( + hintStyle: + TextStyle(color: Palette.wildDarkBlue), + hintText: S.of(context).password, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + controller: _passwordController, + validator: (value) => null, + ), + ) + ], + ) + ], + ), + ), + )), + Container( + padding: EdgeInsets.only(bottom: 20.0, left: 20.0, right: 20.0), + child: Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(right: 8.0), + child: PrimaryButton( + onPressed: () { + _nodeAddressController.text = ''; + _nodePortController.text = ''; + _loginController.text = ''; + _passwordController.text = ''; + }, + text: S.of(context).reset, + color: Theme.of(context) + .accentTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .accentTextTheme + .button + .decorationColor), + )), + Flexible( + child: Container( + padding: EdgeInsets.only(left: 8.0), + child: PrimaryButton( + onPressed: () async { + if (!_formKey.currentState.validate()) { + return; + } + + await nodeList.addNode( + address: _nodeAddressController.text, + port: _nodePortController.text, + login: _loginController.text, + password: _passwordController.text); + + Navigator.of(context).pop(); + }, + text: S.of(context).save, + color: Theme.of(context) + .primaryTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .primaryTextTheme + .button + .decorationColor, + ), + )), + ], + ), + ) + ], + )); + } +} diff --git a/lib/src/screens/nodes/node_indicator.dart b/lib/src/screens/nodes/node_indicator.dart new file mode 100644 index 000000000..b08139c84 --- /dev/null +++ b/lib/src/screens/nodes/node_indicator.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; + +class NodeIndicator extends StatelessWidget { + final color; + + NodeIndicator({this.color = Palette.red}); + + @override + Widget build(BuildContext context) { + return Container( + width: 10.0, + height: 10.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color), + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/nodes/nodes_list_page.dart b/lib/src/screens/nodes/nodes_list_page.dart new file mode 100644 index 000000000..5ddee1f5f --- /dev/null +++ b/lib/src/screens/nodes/nodes_list_page.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/nodes/node_indicator.dart'; +import 'package:cake_wallet/src/stores/node_list/node_list_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class NodeListPage extends BasePage { + NodeListPage(); + + String get title => S.current.nodes; + + @override + Widget trailing(context) { + final nodeList = Provider.of(context); + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + onPressed: () async { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + S.of(context).node_reset_settings_title, + textAlign: TextAlign.center, + ), + content: Text( + S.of(context).nodes_list_reset_to_default_message, + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text(S.of(context).cancel)), + FlatButton( + onPressed: () async { + Navigator.pop(context); + await nodeList.reset(); + }, + child: Text(S.of(context).reset)) + ], + ); + }); + }, + child: Text( + S.of(context).reset, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.subtitle.color), + )), + ), + Container( + width: 28.0, + height: 28.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).selectedRowColor), + child: Stack( + alignment: Alignment.center, + children: [ + Icon(Icons.add, color: Palette.violet, size: 22.0), + ButtonTheme( + minWidth: 28.0, + height: 28.0, + child: FlatButton( + shape: CircleBorder(), + onPressed: () async { + await Navigator.of(context).pushNamed(Routes.newNode); + nodeList.update(); + }, + child: Offstage()), + ) + ], + )), + ], + ); + } + + @override + Widget body(context) => NodeListPageBody(); +} + +class NodeListPageBody extends StatefulWidget { + @override + createState() => NodeListPageBodyState(); +} + +class NodeListPageBodyState extends State { + @override + Widget build(BuildContext context) { + final nodeList = Provider.of(context); + final settings = Provider.of(context); + + final currentColor = Theme.of(context).selectedRowColor; + final notCurrentColor = Theme.of(context).backgroundColor; + + return Container( + padding: EdgeInsets.only(bottom: 20.0), + child: Column( + children: [ + Expanded(child: Observer(builder: (context) { + return ListView.separated( + separatorBuilder: (_, __) => Divider( + color: Theme.of(context).dividerTheme.color, height: 1), + itemCount: nodeList.nodes.length, + itemBuilder: (BuildContext context, int index) { + final node = nodeList.nodes[index]; + + return Observer(builder: (_) { + final isCurrent = settings.node == null + ? false + : node.key == settings.node.key; + + final content = Container( + color: isCurrent ? currentColor : notCurrentColor, + child: ListTile( + title: Text( + node.uri, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context) + .primaryTextTheme + .title + .color), + ), + trailing: FutureBuilder( + future: nodeList.isNodeOnline(node), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + return NodeIndicator( + color: snapshot.data + ? Palette.green + : Palette.red); + default: + return NodeIndicator(); + } + }), + onTap: () async { + if (!isCurrent) { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text( + S + .of(context) + .change_current_node(node.uri), + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + onPressed: () => + Navigator.pop(context), + child: Text(S.of(context).cancel)), + FlatButton( + onPressed: () async { + Navigator.of(context).pop(); + await settings.setCurrentNode( + node: node); + }, + child: Text(S.of(context).change)), + ], + ); + }); + } + }, + )); + + return isCurrent + ? content + : Dismissible( + key: Key('${node.key}'), + confirmDismiss: (direction) async { + return await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + S.of(context).remove_node, + textAlign: TextAlign.center, + ), + content: Text( + S.of(context).remove_node_message, + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + onPressed: () => + Navigator.pop(context, false), + child: Text(S.of(context).cancel)), + FlatButton( + onPressed: () => + Navigator.pop(context, true), + child: Text(S.of(context).remove)), + ], + ); + }); + }, + onDismissed: (direction) async => + await nodeList.remove(node: node), + direction: DismissDirection.endToStart, + background: Container( + padding: EdgeInsets.only(right: 10.0), + alignment: AlignmentDirectional.centerEnd, + color: Palette.red, + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Icon( + CupertinoIcons.delete, + color: Colors.white, + ), + Text( + S.of(context).delete, + style: TextStyle(color: Colors.white), + ) + ], + )), + child: content); + }); + }); + })) + ], + ), + ); + } +} diff --git a/lib/src/screens/pin_code/pin_code.dart b/lib/src/screens/pin_code/pin_code.dart new file mode 100644 index 000000000..7bc7634b1 --- /dev/null +++ b/lib/src/screens/pin_code/pin_code.dart @@ -0,0 +1,245 @@ +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +abstract class PinCodeWidget extends StatefulWidget { + final Function(List pin, PinCodeState state) onPinCodeEntered; + final bool hasLengthSwitcher; + + PinCodeWidget({Key key, this.onPinCodeEntered, this.hasLengthSwitcher}) + : super(key: key); +} + +class PinCode extends PinCodeWidget { + final Function(List pin, PinCodeState state) onPinCodeEntered; + final bool hasLengthSwitcher; + + PinCode(this.onPinCodeEntered, this.hasLengthSwitcher, Key key) + : super(key: key); + + @override + PinCodeState createState() => PinCodeState(); +} + +class PinCodeState extends State { + static const defaultPinLength = 4; + static const sixPinLength = 6; + static const fourPinLength = 4; + static final deleteIconImage = Image.asset('assets/images/delete_icon.png'); + static final backArrowImage = Image.asset('assets/images/back_arrow.png'); + GlobalKey _gridViewKey = GlobalKey(); + + int pinLength = defaultPinLength; + List pin = List.filled(defaultPinLength, null); + String title = S.current.enter_your_pin; + double _aspectRatio = 0; + + void setTitle(String title) { + setState(() => this.title = title); + } + + void clear() { + setState(() => pin = List.filled(pinLength, null)); + } + + void onPinCodeEntered(PinCodeState state) { + widget.onPinCodeEntered(state.pin, this); + } + + void changePinLength(int length) { + List newPin = List.filled(length, null); + + setState(() { + pinLength = length; + pin = newPin; + }); + } + + setDefaultPinLength() { + final settingsStore = Provider.of(context); + + pinLength = settingsStore.defaultPinLength; + changePinLength(pinLength); + } + + getCurrentAspectRatio() { + final RenderBox renderBox = _gridViewKey.currentContext.findRenderObject(); + + double cellWidth = renderBox.size.width / 3; + double cellHeight = renderBox.size.height / 4; + if (cellWidth > 0 && cellHeight > 0) _aspectRatio = cellWidth / cellHeight; + setState(() {}); + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback(afterLayout); + } + + afterLayout(_) { + setDefaultPinLength(); + getCurrentAspectRatio(); + } + + @override + Widget build(BuildContext context) { + return Scaffold(body: body(context)); + } + + Widget body(BuildContext context) { + return SafeArea( + child: Container( + color: Theme.of(context).backgroundColor, + padding: EdgeInsets.only(left: 40.0, right: 40.0, bottom: 40.0), + child: Column(children: [ + Spacer(flex: 2), + Text(title, + style: TextStyle(fontSize: 24, color: Palette.wildDarkBlue)), + Spacer(flex: 3), + Container( + width: 180, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(pinLength, (index) { + const size = 10.0; + final isFilled = pin[index] != null; + + return Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isFilled ? Palette.deepPurple : Colors.transparent, + border: Border.all(color: Palette.wildDarkBlue), + )); + }), + ), + ), + Spacer(flex: 2), + if (widget.hasLengthSwitcher) ...[ + FlatButton( + onPressed: () { + changePinLength(pinLength == PinCodeState.fourPinLength + ? PinCodeState.sixPinLength + : PinCodeState.fourPinLength); + }, + child: Text( + _changePinLengthText(), + style: TextStyle(fontSize: 16.0, color: Palette.wildDarkBlue), + )) + ], + Spacer(flex: 1), + Flexible( + flex: 24, + child: Container( + key: _gridViewKey, + child: _aspectRatio > 0 + ? GridView.count( + shrinkWrap: true, + crossAxisCount: 3, + childAspectRatio: _aspectRatio, + physics: const NeverScrollableScrollPhysics(), + children: List.generate(12, (index) { + const double marginRight = 15; + const double marginLeft = 15; + + if (index == 9) { + return Container( + margin: EdgeInsets.only( + left: marginLeft, right: marginRight), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).buttonColor, + ), + ); + } else if (index == 10) { + index = 0; + } else if (index == 11) { + return Container( + margin: EdgeInsets.only( + left: marginLeft, right: marginRight), + child: FlatButton( + onPressed: () => _pop(), + color: Theme.of(context).buttonColor, + shape: CircleBorder(), + child: deleteIconImage, + ), + ); + } else { + index++; + } + + return Container( + margin: EdgeInsets.only( + left: marginLeft, right: marginRight), + child: FlatButton( + onPressed: () => _push(index), + color: Theme.of(context) + .accentTextTheme + .title + .backgroundColor, + shape: CircleBorder(), + child: Text('$index', + style: TextStyle( + fontSize: 23.0, color: Palette.blueGrey)), + ), + ); + }), + ) + : null)) + ]), + )); + } + + void _push(int num) { + if (_pinLength() >= pinLength) { + return; + } + + for (var i = 0; i < pin.length; i++) { + if (pin[i] == null) { + setState(() => pin[i] = num); + break; + } + } + + if (_pinLength() == pinLength) { + onPinCodeEntered(this); + } + } + + void _pop() { + if (_pinLength() == 0) { + return; + } + + for (var i = pin.length - 1; i >= 0; i--) { + if (pin[i] != null) { + setState(() => pin[i] = null); + break; + } + } + } + + int _pinLength() { + return pin.fold(0, (v, e) { + if (e != null) { + return v + 1; + } + + return v; + }); + } + + String _changePinLengthText() { + return S.current.use + + (pinLength == PinCodeState.fourPinLength + ? '${PinCodeState.sixPinLength}' + : '${PinCodeState.fourPinLength}') + + S.current.digit_pin; + } +} diff --git a/lib/src/screens/receive/qr_image.dart b/lib/src/screens/receive/qr_image.dart new file mode 100644 index 000000000..45e2793ac --- /dev/null +++ b/lib/src/screens/receive/qr_image.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:qr/qr.dart'; +import 'package:cake_wallet/src/screens/receive/qr_painter.dart'; + +class QrImage extends StatelessWidget { + QrImage({ + @required String data, + this.size = 100.0, + this.backgroundColor, + Color foregroundColor = Colors.black, + int version = 7, + int errorCorrectionLevel = QrErrorCorrectLevel.L, + }) : _painter = new QrPainter(data, foregroundColor, version, errorCorrectionLevel); + + final QrPainter _painter; + final Color backgroundColor; + final double size; + + @override + Widget build(BuildContext context) { + return new Container( + width: size, + height: size, + color: backgroundColor, + child: CustomPaint( + painter: _painter, + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/receive/qr_painter.dart b/lib/src/screens/receive/qr_painter.dart new file mode 100644 index 000000000..c7f229f46 --- /dev/null +++ b/lib/src/screens/receive/qr_painter.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:qr/qr.dart'; + +class QrPainter extends CustomPainter { + QrPainter( + String data, + this.color, + this.version, + this.errorCorrectionLevel, + ) : this._qr = new QrCode(version, errorCorrectionLevel) { + _p.color = this.color; + + _qr.addData(data); + _qr.make(); + } + + final QrCode _qr; + final _p = new Paint()..style = PaintingStyle.fill; + + final int version; + final int errorCorrectionLevel; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + final squareSize = size.shortestSide / _qr.moduleCount; + for (int x = 0; x < _qr.moduleCount; x++) { + for (int y = 0; y < _qr.moduleCount; y++) { + if (_qr.isDark(y, x)) { + final squareRect = new Rect.fromLTWH(x * squareSize, y * squareSize, squareSize, squareSize); + canvas.drawRect(squareRect, _p); + } + } + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + if (oldDelegate is QrPainter) { + return this.color != oldDelegate.color || this.errorCorrectionLevel != oldDelegate.errorCorrectionLevel || + this.version != oldDelegate.version; + } + return false; + } +} \ No newline at end of file diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart new file mode 100644 index 000000000..217bf0ae0 --- /dev/null +++ b/lib/src/screens/receive/receive_page.dart @@ -0,0 +1,273 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:esys_flutter_share/esys_flutter_share.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/subaddress_list/subaddress_list_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/screens/receive/qr_image.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class ReceivePage extends BasePage { + bool get isModalBackButton => true; + String get title => S.current.receive; + + @override + Widget trailing(BuildContext context) { + final walletStore = Provider.of(context); + + return SizedBox( + height: 37.0, + width: 37.0, + child: ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + padding: EdgeInsets.all(0), + onPressed: () => Share.text( + 'Share address', walletStore.subaddress.address, 'text/plain'), + child: Icon(Icons.share, size: 30.0,)), + ), + ); + } + + @override + Widget body(BuildContext context) => + SingleChildScrollView(child: ReceiveBody()); +} + +class ReceiveBody extends StatefulWidget { + @override + createState() => ReceiveBodyState(); +} + +class ReceiveBodyState extends State { + final amountController = TextEditingController(); + final _formKey = GlobalKey(); + + @override + void dispose() { + amountController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final walletStore = Provider.of(context); + final subaddressListStore = Provider.of(context); + + final currentColor = Theme.of(context).selectedRowColor; + final notCurrentColor = Theme.of(context).scaffoldBackgroundColor; + + amountController.addListener(() { + if (_formKey.currentState.validate()) { + walletStore.onChangedAmountValue(amountController.text); + } else + walletStore.onChangedAmountValue(''); + }); + + return SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + Container( + padding: EdgeInsets.all(35.0), + color: Theme.of(context).backgroundColor, + child: Column( + children: [ + Observer(builder: (_) { + return Row( + children: [ + Spacer( + flex: 1, + ), + Flexible( + flex: 2, + child: AspectRatio( + aspectRatio: 1.0, + child: Container( + padding: EdgeInsets.all(5), + color: Colors.white, + child: QrImage( + data: walletStore.subaddress.address + + walletStore.amountValue, + backgroundColor: Colors.transparent, + ), + ), + )), + Spacer( + flex: 1, + ) + ], + ); + }), + Observer(builder: (_) { + return Row( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.all(20.0), + child: Center( + child: GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: walletStore.subaddress.address)); + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + S.of(context).copied_to_clipboard, + style: TextStyle(color: Colors.white), + ), + backgroundColor: Colors.green, + )); + }, + child: Text( + walletStore.subaddress.address, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w600, + color: Theme.of(context) + .primaryTextTheme + .title + .color), + ), + ), + ), + )) + ], + ); + }), + Row( + children: [ + Expanded( + child: Form( + key: _formKey, + child: TextFormField( + keyboardType: + TextInputType.numberWithOptions(decimal: true), + inputFormatters: [ + BlacklistingTextInputFormatter( + new RegExp('[\\-|\\ |\\,]')) + ], + style: TextStyle( + fontSize: 14.0, + ), + decoration: InputDecoration( + hintStyle: TextStyle( + color: Theme.of(context).hintColor), + hintText: S.of(context).amount, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, + width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + walletStore.validateAmount(value); + return walletStore.errorMessage; + }, + autovalidate: true, + controller: amountController, + ))) + ], + ) + ], + ), + ), + Row( + children: [ + Expanded( + child: Container( + color: Theme.of(context).accentTextTheme.headline.color, + child: Column( + children: [ + ListTile( + title: Text( + S.of(context).subaddresses, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context) + .primaryTextTheme + .headline + .color), + ), + trailing: Container( + width: 28.0, + height: 28.0, + decoration: BoxDecoration( + color: Theme.of(context).selectedRowColor, + shape: BoxShape.circle), + child: InkWell( + onTap: () => Navigator.of(context) + .pushNamed(Routes.newSubaddress), + borderRadius: BorderRadius.all(Radius.circular(14.0)), + child: Icon( + Icons.add, + color: Palette.violet, + size: 22.0, + ), + ), + ), + ), + Divider( + color: Theme.of(context).dividerTheme.color, + height: 1.0, + ) + ], + ), + )) + ], + ), + Observer(builder: (_) { + return ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: subaddressListStore.subaddresses.length, + separatorBuilder: (context, i) { + return Divider( + color: Theme.of(context).dividerTheme.color, + height: 1.0, + ); + }, + itemBuilder: (context, i) { + return Observer(builder: (_) { + final subaddress = subaddressListStore.subaddresses[i]; + final isCurrent = + walletStore.subaddress.address == subaddress.address; + final label = subaddress.label.isNotEmpty + ? subaddress.label + : subaddress.address; + + return InkWell( + onTap: () => walletStore.setSubaddress(subaddress), + child: Container( + color: isCurrent ? currentColor : notCurrentColor, + child: Column(children: [ + ListTile( + title: Text( + label, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context) + .primaryTextTheme + .headline + .color), + ), + ) + ]), + ), + ); + }); + }); + }) + ], + ))); + } +} diff --git a/lib/src/screens/rescan/rescan_page.dart b/lib/src/screens/rescan/rescan_page.dart new file mode 100644 index 000000000..a17614df1 --- /dev/null +++ b/lib/src/screens/rescan/rescan_page.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/stores/rescan/rescan_wallet_store.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class RescanPage extends BasePage { + final blockchainKey = GlobalKey(); + @override + String get title => S.current.rescan; + + @override + Widget body(BuildContext context) { + final rescanWalletStore = Provider.of(context); + + return Padding( + padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), + child: + Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + BlockchainHeightWidget(key: blockchainKey), + Observer( + builder: (_) => LoadingPrimaryButton( + isLoading: + rescanWalletStore.state == RescanWalletState.rescaning, + text: S.of(context).rescan, + onPressed: () async { + await rescanWalletStore.rescanCurrentWallet( + restoreHeight: blockchainKey.currentState.height); + Navigator.of(context).pop(); + }, + color: + Theme.of(context).primaryTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).primaryTextTheme.button.decorationColor)) + ]), + ); + } +} diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart new file mode 100644 index 000000000..b2460ba95 --- /dev/null +++ b/lib/src/screens/restore/restore_options_page.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class RestoreOptionsPage extends BasePage { + static const _aspectRatioImage = 2.086; + + String get title => S.current.restore_restore_wallet; + Color get backgroundColor => Palette.creamyGrey; + + final _imageSeedKeys = Image.asset('assets/images/seedKeys.png'); + final _imageRestoreSeed = Image.asset('assets/images/restoreSeed.png'); + + @override + Widget body(BuildContext context) => Container( + padding: EdgeInsets.only( + left: 20.0, + right: 20.0, + ), + child: Column( + children: [ + Flexible( + child: RestoreButton( + onPressed: () { + Navigator.pushNamed(context, Routes.restoreWalletOptionsFromWelcome); + }, + image: _imageSeedKeys, + aspectRatioImage: _aspectRatioImage, + titleColor: Palette.lightViolet, + color: Palette.lightViolet, + title: S.of(context).restore_title_from_seed_keys, + description: S.of(context).restore_description_from_seed_keys, + textButton: S.of(context).restore_next, + ), + ), + Flexible( + child: RestoreButton( + onPressed: () {}, + image: _imageRestoreSeed, + aspectRatioImage: _aspectRatioImage, + titleColor: Palette.cakeGreen, + color: Palette.cakeGreen, + title: S.of(context).restore_title_from_backup, + description: S.of(context).restore_description_from_backup, + textButton: S.of(context).restore_next, + )) + ], + ), + ); +} diff --git a/lib/src/screens/restore/restore_wallet_from_keys_page.dart b/lib/src/screens/restore/restore_wallet_from_keys_page.dart new file mode 100644 index 000000000..ce5c7913c --- /dev/null +++ b/lib/src/screens/restore/restore_wallet_from_keys_page.dart @@ -0,0 +1,225 @@ +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_store.dart'; +import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_state.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/palette.dart'; + +class RestoreWalletFromKeysPage extends BasePage { + final WalletListService walletsService; + final WalletService walletService; + final SharedPreferences sharedPreferences; + + String get title => S.current.restore_title_from_keys; + + RestoreWalletFromKeysPage( + {@required this.walletsService, + @required this.sharedPreferences, + @required this.walletService}); + + @override + Widget body(BuildContext context) => RestoreFromKeysFrom(); +} + +class RestoreFromKeysFrom extends StatefulWidget { + @override + createState() => _RestoreFromKeysFromState(); +} + +class _RestoreFromKeysFromState extends State { + final _formKey = GlobalKey(); + final _blockchainHeightKey = GlobalKey(); + final _nameController = TextEditingController(); + final _addressController = TextEditingController(); + final _viewKeyController = TextEditingController(); + final _spendKeyController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final walletRestorationStore = Provider.of(context); + + reaction((_) => walletRestorationStore.state, (state) { + if (state is WalletRestoredSuccessfully) { + Navigator.of(context).popUntil((route) => route.isFirst); + } + + if (state is WalletRestorationFailure) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text(state.error), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ); + }); + }); + } + }); + + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(left: 20.0, right: 20.0, bottom: 20.0), + content: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(left: 13, right: 13), + child: Column(children: [ + Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(top: 20.0), + child: TextFormField( + style: TextStyle(fontSize: 14.0), + controller: _nameController, + decoration: InputDecoration( + hintStyle: + TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).restore_wallet_name, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, + width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + walletRestorationStore.validateWalletName(value); + return walletRestorationStore.errorMessage; + }, + ), + )) + ], + ), + Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(top: 20.0), + child: TextFormField( + style: TextStyle(fontSize: 14.0), + controller: _addressController, + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: InputDecoration( + hintStyle: + TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).restore_address, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, + width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + walletRestorationStore.validateAddress(value); + return walletRestorationStore.errorMessage; + }, + ), + )) + ], + ), + Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(top: 20.0), + child: TextFormField( + style: TextStyle(fontSize: 14.0), + controller: _viewKeyController, + decoration: InputDecoration( + hintStyle: + TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).restore_view_key_private, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, + width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + walletRestorationStore.validateKeys(value); + return walletRestorationStore.errorMessage; + }, + ), + )) + ], + ), + Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(top: 20.0), + child: TextFormField( + style: TextStyle(fontSize: 14.0), + controller: _spendKeyController, + decoration: InputDecoration( + hintStyle: + TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).restore_spend_key_private, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, + width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + walletRestorationStore.validateKeys(value); + return walletRestorationStore.errorMessage; + }, + ), + )) + ], + ), + BlockchainHeightWidget(key: _blockchainHeightKey), + ]), + ) + ], + ), + ), + bottomSection: Observer(builder: (_) { + return LoadingPrimaryButton( + onPressed: () { + if (_formKey.currentState.validate()) { + walletRestorationStore.restoreFromKeys( + name: _nameController.text, + address: _addressController.text, + viewKey: _viewKeyController.text, + spendKey: _spendKeyController.text, + restoreHeight: _blockchainHeightKey.currentState.height); + } + }, + text: S.of(context).restore_recover, + color: Theme.of(context).primaryTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).primaryTextTheme.button.decorationColor); + }), + ); + } +} diff --git a/lib/src/screens/restore/restore_wallet_from_seed_details.dart b/lib/src/screens/restore/restore_wallet_from_seed_details.dart new file mode 100644 index 000000000..a1061f3d7 --- /dev/null +++ b/lib/src/screens/restore/restore_wallet_from_seed_details.dart @@ -0,0 +1,124 @@ +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_store.dart'; +import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_state.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/palette.dart'; + +class RestoreWalletFromSeedDetailsPage extends BasePage { + String get title => S.current.restore_wallet_restore_description; + + @override + Widget body(BuildContext context) => RestoreFromSeedDetailsForm(); +} + +class RestoreFromSeedDetailsForm extends StatefulWidget { + @override + createState() => _RestoreFromSeedDetailsFormState(); +} + +class _RestoreFromSeedDetailsFormState + extends State { + final _formKey = GlobalKey(); + final _blockchainHeightKey = GlobalKey(); + final _nameController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final walletRestorationStore = Provider.of(context); + + reaction((_) => walletRestorationStore.state, (state) { + if (state is WalletRestoredSuccessfully) { + Navigator.of(context).popUntil((route) => route.isFirst); + } + + if (state is WalletRestorationFailure) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text(state.error), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ); + }); + }); + } + }); + + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(left: 20.0, right: 20.0, bottom: 20.0), + content: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(left: 13, right: 13), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(top: 20.0), + child: TextFormField( + style: TextStyle(fontSize: 14.0), + controller: _nameController, + decoration: InputDecoration( + hintStyle: TextStyle( + color: Theme.of(context).hintColor), + hintText: S.of(context).restore_wallet_name, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, + width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + walletRestorationStore + .validateWalletName(value); + return walletRestorationStore.errorMessage; + }, + ), + )) + ], + ), + BlockchainHeightWidget(key: _blockchainHeightKey), + ])) + ], + ), + ), + bottomSection: Observer(builder: (_) { + return LoadingPrimaryButton( + onPressed: () { + if (_formKey.currentState.validate()) { + walletRestorationStore.restoreFromSeed( + name: _nameController.text, + restoreHeight: _blockchainHeightKey.currentState.height); + } + }, + isLoading: walletRestorationStore.state is WalletIsRestoring, + text: S.of(context).restore_recover, + color: Theme.of(context).primaryTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).primaryTextTheme.button.decorationColor); + }), + ); + } +} diff --git a/lib/src/screens/restore/restore_wallet_from_seed_page.dart b/lib/src/screens/restore/restore_wallet_from_seed_page.dart new file mode 100644 index 000000000..3b7e625af --- /dev/null +++ b/lib/src/screens/restore/restore_wallet_from_seed_page.dart @@ -0,0 +1,105 @@ +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_store.dart'; +import 'package:cake_wallet/src/widgets/seed_widget.dart'; + +class RestoreWalletFromSeedPage extends BasePage { + final WalletListService walletsService; + final WalletService walletService; + final SharedPreferences sharedPreferences; + + String get title => S.current.restore_title_from_seed; + + RestoreWalletFromSeedPage( + {@required this.walletsService, + @required this.walletService, + @required this.sharedPreferences}); + + @override + Widget body(BuildContext context) => RestoreFromSeedForm(); +} + +class RestoreFromSeedForm extends StatefulWidget { + @override + createState() => _RestoreFromSeedFormState(); +} + +class _RestoreFromSeedFormState extends State { + final _seedKey = GlobalKey(); + bool _reactionSet = false; + + @override + Widget build(BuildContext context) { + final walletRestorationStore = Provider.of(context); + + WidgetsBinding.instance.addPostFrameCallback( + (_) => _setReactions(context, walletRestorationStore)); + + return GestureDetector( + onTap: () { + SystemChannels.textInput.invokeMethod('TextInput.hide'); + }, + child: Container( + padding: EdgeInsets.only(left: 20.0, right: 20.0, bottom: 20.0), + child: Column( + children: [ + Expanded( + child: SeedWidget( + key: _seedKey, + onMnemoticChange: (seed) => walletRestorationStore.setSeed(seed), + )), + Container( + alignment: Alignment.bottomCenter, + child: PrimaryButton( + onPressed: () { + if (!walletRestorationStore.isValid) { + return; + } + + Navigator.of(context).pushNamed( + Routes.restoreWalletFromSeedDetails, + arguments: _seedKey.currentState.items); + }, + text: S.of(context).restore_next, + color: Theme.of(context) + .primaryTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .primaryTextTheme + .button + .decorationColor)) + ], + ), + ), + ); + } + + void _setReactions(BuildContext context, WalletRestorationStore store) { + if (_reactionSet) { + return; + } + + reaction((_) => store.errorMessage, (errorMessage) { + if (errorMessage == null || errorMessage.isEmpty) { + _seedKey.currentState.validated(); + } else { + _seedKey.currentState.invalidate(); + } + + _seedKey.currentState.setErrorMessage(errorMessage); + }); + + _reactionSet = true; + } +} diff --git a/lib/src/screens/restore/restore_wallet_options_page.dart b/lib/src/screens/restore/restore_wallet_options_page.dart new file mode 100644 index 000000000..296755fb8 --- /dev/null +++ b/lib/src/screens/restore/restore_wallet_options_page.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class RestoreWalletOptionsPage extends BasePage { + static const _aspectRatioImage = 2.086; + + String get title => S.current.restore_seed_keys_restore; + Color get backgroundColor => Palette.creamyGrey; + + final _imageSeed = Image.asset('assets/images/seedIco.png'); + final _imageKeys = Image.asset('assets/images/keysIco.png'); + + @override + Widget body(BuildContext context) => Container( + padding: EdgeInsets.only( + left: 20.0, + right: 20.0, + ), + child: Column( + children: [ + Flexible( + child: RestoreButton( + onPressed: () => + Navigator.pushNamed(context, Routes.restoreWalletFromSeed), + image: _imageSeed, + aspectRatioImage: _aspectRatioImage, + titleColor: Palette.lightViolet, + color: Palette.lightViolet, + title: S.of(context).restore_title_from_seed, + description: S.of(context).restore_description_from_seed, + textButton: S.of(context).restore_next, + )), + Flexible( + child: RestoreButton( + onPressed: () => + Navigator.pushNamed(context, Routes.restoreWalletFromKeys), + image: _imageKeys, + aspectRatioImage: _aspectRatioImage, + titleColor: Palette.cakeGreen, + color: Palette.cakeGreen, + title: S.of(context).restore_title_from_keys, + description: S.of(context).restore_description_from_keys, + textButton: S.of(context).restore_next, + )) + ], + ), + ); +} diff --git a/lib/src/screens/restore/widgets/restore_button.dart b/lib/src/screens/restore/widgets/restore_button.dart new file mode 100644 index 000000000..41f580cc6 --- /dev/null +++ b/lib/src/screens/restore/widgets/restore_button.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; + +class RestoreButton extends StatelessWidget { + final VoidCallback onPressed; + final Image image; + final double aspectRatioImage; + final Color color; + final Color titleColor; + final String title; + final String description; + final String textButton; + + const RestoreButton( + {@required this.onPressed, + @required this.image, + @required this.aspectRatioImage, + @required this.color, + @required this.titleColor, + this.title = '', + this.description = '', + this.textButton = ''}); + + @override + Widget build(BuildContext context) { + + return Container( + margin: EdgeInsets.only(top: 20.0, bottom: 20.0), + decoration: BoxDecoration( + color: Theme.of(context).accentTextTheme.headline.backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(20.0)), + boxShadow: [ + BoxShadow( + color: Palette.buttonShadow, + blurRadius: 10, + offset: Offset( + 0, + 12, + ), + ) + ]), + child: InkWell( + onTap: onPressed, + borderRadius: BorderRadius.all(Radius.circular(20.0)), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Container( + child: AspectRatio( + aspectRatio: aspectRatioImage, + child: FittedBox( + fit: BoxFit.contain, + child: image, + ), + ), + ), + ), + Column( + children: [ + Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + color: titleColor, + fontSize: 18.0, + fontWeight: FontWeight.bold), + ), + Padding( + padding: EdgeInsets.only(left: 50, right: 50, top: 10), + child: Text( + description, + textAlign: TextAlign.center, + style: + TextStyle( + color: Theme.of(context).accentTextTheme.subhead.color, + fontSize: 14.0, + height: 1.4), + ), + ) + ], + ), + SizedBox( + height: 20.0, + ), + Container( + height: 56.0, + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: Theme.of(context).accentTextTheme.headline.decorationColor, + width: 1.15)), + color: Colors.transparent, + ), + child: Center( + child: Text( + textButton, + style: TextStyle( + color: color, + fontSize: 16.0, + fontWeight: FontWeight.bold), + ), + )) + ], + )), + ); + } +} diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart new file mode 100644 index 000000000..9482ea861 --- /dev/null +++ b/lib/src/screens/root/root.dart @@ -0,0 +1,135 @@ +import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:hive/hive.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/router.dart'; +import 'package:cake_wallet/src/stores/authentication/authentication_store.dart'; +import 'package:cake_wallet/src/stores/price/price_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/domain/common/qr_scanner.dart'; +import 'package:cake_wallet/src/domain/services/user_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/domain/monero/transaction_description.dart'; +import 'package:cake_wallet/src/screens/auth/create_login_page.dart'; +import 'package:cake_wallet/src/screens/seed/create_seed_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/create_dashboard_page.dart'; + +class Root extends StatefulWidget { + Root({Key key}) : super(key: key); + + @override + RootState createState() => RootState(); +} + +class RootState extends State with WidgetsBindingObserver { + bool _isInactive; + bool _postFrameCallback; + AuthenticationStore _authenticationStore; + + @override + void initState() { + _isInactive = false; + _postFrameCallback = false; + WidgetsBinding.instance.addObserver(this); + super.initState(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.paused: + if (isQrScannerShown) { + return; + } + + if (!_isInactive && + _authenticationStore.state == + AuthenticationState.authenticated || + _authenticationStore.state == AuthenticationState.active) { + setState(() => _isInactive = true); + } + + break; + default: + break; + } + } + + @override + Widget build(BuildContext context) { + _authenticationStore = Provider.of(context); + final sharedPreferences = Provider.of(context); + final walletListService = Provider.of(context); + final walletService = Provider.of(context); + final userService = Provider.of(context); + final priceStore = Provider.of(context); + final authenticationStore = Provider.of(context); + final trades = Provider.of>(context); + final transactionDescriptions = + Provider.of>(context); + final walletStore = Provider.of(context); + final settingsStore = Provider.of(context); + + if (_isInactive && !_postFrameCallback) { + _postFrameCallback = true; + + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context).pushNamed(Routes.unlock, + arguments: (isAuthenticatedSuccessfully, auth) { + if (!isAuthenticatedSuccessfully) { + return; + } + + setState(() { + _postFrameCallback = false; + _isInactive = false; + }); + auth.close(); + }); + }); + } + + return Observer(builder: (_) { + final state = _authenticationStore.state; + if (state == AuthenticationState.denied) { + return createWelcomePage(); + } + + if (state == AuthenticationState.readyToLogin) { + return createLoginPage( + sharedPreferences: sharedPreferences, + userService: userService, + walletService: walletService, + walletListService: walletListService, + authenticationStore: authenticationStore); + } + + if (state == AuthenticationState.authenticated || + state == AuthenticationState.restored) { + return createDashboardPage( + walletService: walletService, + priceStore: priceStore, + trades: trades, + transactionDescriptions: transactionDescriptions, + walletStore: walletStore, + settingsStore: settingsStore); + } + + if (state == AuthenticationState.created) { + return createSeedPage( + settingsStore: settingsStore, + walletService: walletService, + callback: () => + _authenticationStore.state = AuthenticationState.authenticated); + } + + return Container(color: Colors.white); + }); + } +} diff --git a/lib/src/screens/seed/create_seed_page.dart b/lib/src/screens/seed/create_seed_page.dart new file mode 100644 index 000000000..b841f0324 --- /dev/null +++ b/lib/src/screens/seed/create_seed_page.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/screens/seed/seed_page.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/wallet_seed/wallet_seed_store.dart'; + +Widget createSeedPage( + {@required SettingsStore settingsStore, + @required WalletService walletService, + @required void Function() callback}) => + Provider( + create: (_) => WalletSeedStore(walletService: walletService), + child: SeedPage(onCloseCallback: callback)); \ No newline at end of file diff --git a/lib/src/screens/seed/seed_page.dart b/lib/src/screens/seed/seed_page.dart new file mode 100644 index 000000000..13611587a --- /dev/null +++ b/lib/src/screens/seed/seed_page.dart @@ -0,0 +1,162 @@ +import 'package:provider/provider.dart'; +import 'package:esys_flutter_share/esys_flutter_share.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/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/stores/wallet_seed/wallet_seed_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class SeedPage extends BasePage { + static final image = Image.asset('assets/images/seed_image.png'); + bool get isModalBackButton => true; + String get title => S.current.seed_title; + + final VoidCallback onCloseCallback; + + SeedPage({this.onCloseCallback}); + + void onClose(BuildContext context) => + onCloseCallback != null ? onCloseCallback() : Navigator.of(context).pop(); + + @override + Widget leading(BuildContext context) { + return onCloseCallback != null ? Offstage() : super.leading(context); + } + + @override + Widget body(BuildContext context) { + final walletSeedStore = Provider.of(context); + String _seed; + + return Container( + padding: EdgeInsets.all(30.0), + child: Column( + children: [ + Expanded( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + image, + Container( + margin: EdgeInsets.only(left: 30.0, top: 10.0, right: 30.0), + child: Observer(builder: (_) { + _seed = walletSeedStore.seed; + return Column( + children: [ + Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: Theme.of(context) + .dividerColor))), + padding: EdgeInsets.only(bottom: 20.0), + margin: EdgeInsets.only(bottom: 10.0), + child: Text( + walletSeedStore.name, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.0, + color: Theme.of(context) + .primaryTextTheme + .button + .color), + ), + )) + ], + ), + SizedBox( + height: 10.0, + ), + Text( + walletSeedStore.seed, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.0, + color: Theme.of(context) + .primaryTextTheme + .title + .color), + ) + ], + ); + }), + ), + Container( + margin: EdgeInsets.only(top: 30.0), + child: Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(right: 8.0), + child: PrimaryButton( + onPressed: () => Share.text( + S.of(context).seed_share, + _seed, + 'text/plain'), + color: Theme.of(context) + .primaryTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .primaryTextTheme + .button + .decorationColor, + text: S.of(context).save), + )), + Flexible( + child: Container( + padding: EdgeInsets.only(left: 8.0), + child: Builder( + builder: (context) => PrimaryButton( + onPressed: () { + Clipboard.setData( + ClipboardData(text: _seed)); + Scaffold.of(context).showSnackBar( + SnackBar( + content: Text(S + .of(context) + .copied_to_clipboard), + backgroundColor: Colors.green, + duration: + Duration(milliseconds: 1500), + ), + ); + }, + text: S.of(context).copy, + color: Theme.of(context) + .accentTextTheme + .caption + .backgroundColor, + borderColor: Theme.of(context) + .accentTextTheme + .caption + .decorationColor), + ))) + ], + ), + ) + ], + ), + ), + ), + onCloseCallback != null + ? PrimaryButton( + onPressed: () => onClose(context), + text: S.of(context).restore_next, + color: Palette.darkGrey, + borderColor: Palette.darkGrey) + : Offstage() + ], + ), + ); + } +} diff --git a/lib/src/screens/seed_alert/seed_alert.dart b/lib/src/screens/seed_alert/seed_alert.dart new file mode 100644 index 000000000..cfeeea742 --- /dev/null +++ b/lib/src/screens/seed_alert/seed_alert.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; + +class SeedAlert extends StatelessWidget { + final imageSeed = Image.asset('assets/images/seedIco.png'); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomPadding: false, + backgroundColor: Theme.of(context).backgroundColor, + body: SafeArea( + child: Container( + padding: EdgeInsets.all(20.0), + child: Column( + children: [ + Expanded( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + imageSeed, + Text( + S.of(context).seed_alert_first_text, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 19.0), + ), + SizedBox( + height: 20.0, + ), + Text( + S.of(context).seed_alert_second_text, + textAlign: TextAlign.center, + style: TextStyle( + letterSpacing: 0.2, + fontSize: 16.0, + color: Palette.lightBlue), + ), + SizedBox( + height: 20.0, + ), + RichText( + textAlign: TextAlign.center, + text: TextSpan(children: [ + TextSpan( + text: S.of(context).seed_alert_third_text, + style: TextStyle( + letterSpacing: 0.2, + fontSize: 16.0, + color: Palette.lightBlue)), + TextSpan( + text: S.of(context).seed_alert_settings, + style: TextStyle( + letterSpacing: 0.2, + fontSize: 16.0, + color: Palette.lightGreen)), + TextSpan( + text: S.of(context).seed_alert_menu, + style: TextStyle( + letterSpacing: 0.2, + fontSize: 16.0, + color: Palette.lightBlue)), + ])) + ], + ), + ), + ), + PrimaryButton( + onPressed: () {}, + text: S.of(context).seed_alert_understand, + color: + Theme.of(context).primaryTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).primaryTextTheme.button.decorationColor), + ], + ), + )), + ); + } +} diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart new file mode 100644 index 000000000..274f60795 --- /dev/null +++ b/lib/src/screens/send/send_page.dart @@ -0,0 +1,483 @@ +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/address_text_field.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/balance/balance_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/stores/send/send_store.dart'; +import 'package:cake_wallet/src/stores/send/sending_state.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/common/balance_display_mode.dart'; +import 'package:cake_wallet/src/domain/common/calculate_estimated_fee.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/sync_status.dart'; +import 'package:cake_wallet/src/stores/sync/sync_store.dart'; + +class SendPage extends BasePage { + String get title => S.current.send_title; + bool get isModalBackButton => true; + bool get resizeToAvoidBottomPadding => false; + + @override + Widget body(BuildContext context) => SendForm(); +} + +class SendForm extends StatefulWidget { + @override + State createState() => SendFormState(); +} + +class SendFormState extends State { + final _addressController = TextEditingController(); + final _paymentIdController = TextEditingController(); + final _cryptoAmountController = TextEditingController(); + final _fiatAmountController = TextEditingController(); + + bool _effectsInstalled = false; + + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + final settingsStore = Provider.of(context); + final sendStore = Provider.of(context); + sendStore.settingsStore = settingsStore; + final balanceStore = Provider.of(context); + final walletStore = Provider.of(context); + final syncStore = Provider.of(context); + + _setEffects(context); + + return ScrollableWithBottomSection( + contentPadding: EdgeInsets.all(0), + content: Column( + children: [ + Container( + padding: EdgeInsets.only(left: 38, right: 30), + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + boxShadow: [ + BoxShadow( + color: Palette.shadowGrey, + blurRadius: 10, + offset: Offset(0, 12), + ) + ], + border: Border( + top: BorderSide( + width: 1, + color: Theme.of(context) + .accentTextTheme + .subtitle + .backgroundColor))), + child: SizedBox( + height: 56, + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Observer(builder: (_) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(S.of(context).send_your_wallet, + style: TextStyle( + fontSize: 12, color: Palette.lightViolet)), + Text(walletStore.name, + style: TextStyle( + fontSize: 18, + color: Theme.of(context) + .accentTextTheme + .overline + .color, + height: 1.25)), + ]); + }), + Observer(builder: (context) { + final savedDisplayMode = settingsStore.balanceDisplayMode; + final availableBalance = + savedDisplayMode == BalanceDisplayMode.hiddenBalance + ? '---' + : balanceStore.unlockedBalance; + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(S.of(context).xmr_available_balance, + style: TextStyle( + fontSize: 12, + color: Theme.of(context) + .accentTextTheme + .overline + .backgroundColor, + )), + Text(availableBalance, + style: TextStyle( + fontSize: 22, + color: Theme.of(context) + .accentTextTheme + .overline + .color, + height: 1.1)), + ]); + }) + ], + ), + ), + ), + Form( + key: _formKey, + child: Container( + padding: + EdgeInsets.only(left: 38, right: 33, top: 10, bottom: 30), + child: Column(children: [ + AddressTextField( + controller: _addressController, + placeholder: S.of(context).send_monero_address, + onURIScanned: (uri) { + var address = ''; + var amount = ''; + var paymentId = ''; + + if (uri != null) { + address = uri.path; + amount = uri.queryParameters['tx_amount']; + paymentId = uri.queryParameters['tx_payment_id']; + } else { + address = uri.toString(); + } + + _addressController.text = address; + _cryptoAmountController.text = amount; + _paymentIdController.text = paymentId; + }, + options: [ + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook + ], + validator: (value) { + sendStore.validateAddress(value, + cryptoCurrency: CryptoCurrency.xmr); + return sendStore.errorMessage; + }, + ), + Padding( + padding: const EdgeInsets.only(top: 20), + child: TextFormField( + style: TextStyle( + fontSize: 14.0, + color: Theme.of(context) + .accentTextTheme + .overline + .backgroundColor), + controller: _paymentIdController, + decoration: InputDecoration( + hintStyle: TextStyle( + fontSize: 14.0, + color: Theme.of(context).hintColor), + hintText: S.of(context).send_payment_id, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + sendStore.validatePaymentID(value); + return sendStore.errorMessage; + }), + ), + Padding( + padding: const EdgeInsets.only(top: 20), + child: TextFormField( + style: TextStyle( + fontSize: 18.0, + color: Theme.of(context) + .accentTextTheme + .overline + .color), + controller: _cryptoAmountController, + keyboardType: TextInputType.numberWithOptions( + signed: false, decimal: false), + inputFormatters: [ + BlacklistingTextInputFormatter( + new RegExp('[\\-|\\ |\\,]')) + ], + decoration: InputDecoration( + prefixIcon: Padding( + padding: EdgeInsets.only(top: 12), + child: Text('XMR:', + style: TextStyle( + fontSize: 18, + color: Theme.of(context) + .accentTextTheme + .overline + .color, + )), + ), + suffixIcon: Container( + width: 1, + padding: EdgeInsets.only(top: 0), + child: Center( + child: InkWell( + onTap: () => sendStore.setSendAll(), + child: Text(S.of(context).all, + style: TextStyle( + fontSize: 10, + color: Theme.of(context) + .accentTextTheme + .overline + .decorationColor))), + ), + ), + hintStyle: TextStyle( + fontSize: 18.0, + color: Theme.of(context).hintColor), + hintText: '0.0000', + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + sendStore.validateXMR( + value, balanceStore.unlockedBalance); + return sendStore.errorMessage; + }), + ), + Padding( + padding: const EdgeInsets.only(top: 20), + child: TextFormField( + style: TextStyle( + fontSize: 18.0, + color: Theme.of(context) + .accentTextTheme + .overline + .color), + controller: _fiatAmountController, + keyboardType: TextInputType.numberWithOptions( + signed: false, decimal: false), + inputFormatters: [ + BlacklistingTextInputFormatter( + new RegExp('[\\-|\\ |\\,]')) + ], + decoration: InputDecoration( + prefixIcon: Padding( + padding: EdgeInsets.only(top: 12), + child: Text( + '${settingsStore.fiatCurrency.toString()}:', + style: TextStyle( + fontSize: 18, + color: Theme.of(context) + .accentTextTheme + .overline + .color, + )), + ), + hintStyle: TextStyle( + fontSize: 18.0, + color: Theme.of(context).hintColor), + hintText: '0.00', + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0)))), + ), + Padding( + padding: const EdgeInsets.only(top: 12.0, bottom: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(S.of(context).send_estimated_fee, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .accentTextTheme + .overline + .backgroundColor, + )), + Text( + '${calculateEstimatedFee(priority: settingsStore.transactionPriority)} XMR', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .primaryTextTheme + .overline + .backgroundColor, + )) + ], + ), + ), + SizedBox( + width: double.infinity, + child: Text( + S.of(context).send_priority( + settingsStore.transactionPriority.toString()), + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .primaryTextTheme + .subtitle + .color, + height: 1.3)), + ), + ]), + ), + ) + ], + ), + bottomSection: Observer(builder: (_) { + return LoadingPrimaryButton( + onPressed: syncStore.status is SyncedSyncStatus + ? () async { + // Hack. Don't ask me. + FocusScope.of(context).requestFocus(FocusNode()); + + if (_formKey.currentState.validate()) { + await showDialog( + context: context, + builder: (dialogContext) { + return AlertDialog( + title: Text( + S.of(context).send_creating_transaction), + content: Text(S.of(context).confirm_sending), + actions: [ + FlatButton( + child: Text(S.of(context).send), + onPressed: () async { + Navigator.of(dialogContext) + .popAndPushNamed( + Routes.auth, + arguments: + (isAuthenticatedSuccessfully, + auth) { + if (!isAuthenticatedSuccessfully) { + return; + } + + Navigator.of(auth.context).pop(); + + sendStore.createTransaction( + address: _addressController.text, + paymentId: + _paymentIdController.text); + }); + }), + FlatButton( + child: Text(S.of(context).cancel), + onPressed: () => + Navigator.of(context).pop()) + ], + ); + }); + } + } + : null, + text: S.of(context).send, + color: Theme.of(context).accentTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).accentTextTheme.button.decorationColor, + isLoading: sendStore.state is CreatingTransaction || + sendStore.state is TransactionCommitted); + })); + } + + void _setEffects(BuildContext context) { + if (_effectsInstalled) { + return; + } + + final sendStore = Provider.of(context); + + reaction((_) => sendStore.fiatAmount, (amount) { + if (amount != _fiatAmountController.text) { + _fiatAmountController.text = amount; + } + }); + + reaction((_) => sendStore.cryptoAmount, (amount) { + if (amount != _cryptoAmountController.text) { + _cryptoAmountController.text = amount; + } + }); + + _fiatAmountController.addListener(() { + final fiatAmount = _fiatAmountController.text; + + if (sendStore.fiatAmount != fiatAmount) { + sendStore.changeFiatAmount(fiatAmount); + } + }); + + _cryptoAmountController.addListener(() { + final cryptoAmount = _cryptoAmountController.text; + + if (sendStore.cryptoAmount != cryptoAmount) { + sendStore.changeCryptoAmount(cryptoAmount); + } + }); + + reaction((_) => sendStore.state, (state) { + if (state is SendingFailed) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).error), + content: Text(state.error), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () => Navigator.of(context).pop()) + ], + ); + }); + }); + } + + if (state is TransactionCreatedSuccessfully) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).sending), + content: Text(S.of(context).transaction_sent), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + _addressController.text = ''; + _cryptoAmountController.text = ''; + Navigator.of(context)..pop()..pop(); + }) + ], + ); + }); + }); + } + }); + + _effectsInstalled = true; + } +} diff --git a/lib/src/screens/settings/attributes.dart b/lib/src/screens/settings/attributes.dart new file mode 100644 index 000000000..a70cdbd05 --- /dev/null +++ b/lib/src/screens/settings/attributes.dart @@ -0,0 +1 @@ +enum Attributes {arrow, header, link, switcher, widget, rawWidget} \ No newline at end of file diff --git a/lib/src/screens/settings/change_language.dart b/lib/src/screens/settings/change_language.dart new file mode 100644 index 000000000..8a055f2f6 --- /dev/null +++ b/lib/src/screens/settings/change_language.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/language.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +const Map _languages = { + 'en': 'English', + 'ru': 'Русский (Russian)', + 'es': 'Español (Spanish)', + 'ja': '日本 (Japanese)', + 'ko': '한국어 (Korean)', + 'hi': 'हिंदी (Hindi)', + 'de': 'Deutsch (German)', + 'zh': '中文 (Chinese)', + 'pt': 'Português (Portuguese)', + 'pl': 'Polski (Polish)', + 'nl': 'Nederlands (Dutch)' +}; + +class ChangeLanguage extends BasePage { + get title => S.current.settings_change_language; + + @override + Widget body(BuildContext context) { + final settingsStore = Provider.of(context); + final currentLanguage = Provider.of(context); + + return Container( + padding: EdgeInsets.only(top: 10.0, bottom: 10.0), + child: ListView.builder( + itemCount: _languages.values.length, + itemBuilder: (BuildContext context, int index) { + final isCurrent = settingsStore.languageCode == null + ? false + : _languages.keys.elementAt(index) == + settingsStore.languageCode; + + return Container( + margin: EdgeInsets.only(top: 10.0, bottom: 10.0), + color: Theme.of(context).accentTextTheme.subhead.backgroundColor, + child: ListTile( + title: Text( + _languages.values.elementAt(index), + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.title.color), + ), + onTap: () async { + if (!isCurrent) { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + S.of(context).change_language, + textAlign: TextAlign.center, + ), + content: Text( + S.of(context).change_language_to( + _languages.values.elementAt(index)), + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(S.of(context).cancel)), + FlatButton( + onPressed: () { + settingsStore.saveLanguageCode( + languageCode: + _languages.keys.elementAt(index)); + currentLanguage.setCurrentLanguage( + _languages.keys.elementAt(index)); + Navigator.of(context).pop(); + }, + child: Text(S.of(context).change)), + ], + ); + }); + } + }, + ), + ); + }, + )); + } +} diff --git a/lib/src/screens/settings/enter_pin_code.dart b/lib/src/screens/settings/enter_pin_code.dart new file mode 100644 index 000000000..07c727f8c --- /dev/null +++ b/lib/src/screens/settings/enter_pin_code.dart @@ -0,0 +1,245 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:flutter/foundation.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/theme_changer.dart'; +import 'package:cake_wallet/themes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class EnterPinCode extends StatefulWidget{ + + final int currentPinLength; + final List currentPin; + + const EnterPinCode(this.currentPinLength, this.currentPin); + + @override + createState() => EnterPinCodeState(currentPinLength, currentPin); + +} + +class EnterPinCodeState extends State{ + GlobalKey _gridViewKey = GlobalKey(); + + final _closeButtonImage = Image.asset('assets/images/close_button.png'); + final _closeButtonImageDarkTheme = Image.asset('assets/images/close_button_dark_theme.png'); + static final deleteIconImage = Image.asset('assets/images/delete_icon.png'); + final int pinLength; + final List currentPin; + List pin; + double _aspectRatio = 0; + + EnterPinCodeState(this.pinLength, this.currentPin); + + _getCurrentAspectRatio(){ + final RenderBox renderBox = _gridViewKey.currentContext.findRenderObject(); + + double cellWidth = renderBox.size.width/3; + double cellHeight = renderBox.size.height/4; + if (cellWidth > 0 && cellHeight > 0) _aspectRatio = cellWidth/cellHeight; + setState(() { + }); + } + + @override + void initState() { + super.initState(); + pin = List.filled(pinLength, null); + WidgetsBinding.instance.addPostFrameCallback(_afterLayout); + } + + _afterLayout(_) { + _getCurrentAspectRatio(); + } + + @override + Widget build(BuildContext context) { + + ThemeChanger _themeChanger = Provider.of(context); + bool _isDarkTheme; + + if (_themeChanger.getTheme() == Themes.darkTheme) _isDarkTheme = true; + else _isDarkTheme = false; + + return Scaffold( + backgroundColor: Theme.of(context).backgroundColor, + appBar: CupertinoNavigationBar( + leading: ButtonTheme( + minWidth: double.minPositive, + child: FlatButton( + onPressed: (){ Navigator.pop(context, false); }, + child: _isDarkTheme ? _closeButtonImageDarkTheme : _closeButtonImage + ), + ), + backgroundColor: Theme.of(context).backgroundColor, + border: null, + ), + body: SafeArea( + child: Container( + padding: EdgeInsets.only(left: 40.0, right: 40.0, bottom: 40.0), + child: Column( + children: [ + Spacer(flex: 2), + Text(S.of(context).enter_your_pin, + style: TextStyle( + fontSize: 24, + color: Palette.wildDarkBlue + ) + ), + Spacer(flex: 3), + Container( + width: 180, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(pinLength, (index) { + const size = 10.0; + final isFilled = pin[index] != null; + + return Container( + width: size, + height: size, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isFilled ? Palette.deepPurple : Colors.transparent, + border: Border.all(color: Palette.wildDarkBlue), + )); + }), + ), + ), + Spacer(flex: 3), + Flexible( + flex: 24, + child: Container( + key: _gridViewKey, + child: _aspectRatio > 0 ? GridView.count( + crossAxisCount: 3, + childAspectRatio: _aspectRatio, + physics: const NeverScrollableScrollPhysics(), + children: List.generate(12, (index) { + + if (index == 9) { + return Container( + margin: EdgeInsets.all(5.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _isDarkTheme ? PaletteDark.darkThemePinButton + : Palette.darkGrey, + ), + ); + } else if (index == 10) { + index = 0; + } else if (index == 11) { + return Container( + margin: EdgeInsets.all(5.0), + child: FlatButton( + onPressed: () { _pop(); }, + color: _isDarkTheme ? PaletteDark.darkThemePinButton + : Palette.darkGrey, + shape: CircleBorder(), + child: deleteIconImage, + ), + ); + } else { + index++; + } + + return Container( + margin: EdgeInsets.all(5.0), + child: FlatButton( + onPressed: () { _push(index); }, + color: _isDarkTheme ? PaletteDark.darkThemePinDigitButton + : Palette.creamyGrey, + shape: CircleBorder(), + child: Text( + '$index', + style: TextStyle( + fontSize: 23.0, + color: Palette.blueGrey + ) + ), + ), + ); + }), + ) : null + ) + ) + ], + ), + ) + ), + ); + } + + _showIncorrectPinDialog(BuildContext context) async { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text(S.of(context).pin_is_incorrect), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + } + ); + } + + void _push(int num) { + if (_pinLength() >= pinLength) { + return; + } + + for (var i = 0; i < pin.length; i++) { + if (pin[i] == null) { + setState(() => pin[i] = num); + break; + } + } + + if (_pinLength() == pinLength) { + + if (listEquals(pin, currentPin)){ + + Navigator.pop(context, true); + + } else { + + Navigator.pop(context, false); + + _showIncorrectPinDialog(context); + + } + + } + } + + void _pop() { + if (_pinLength() == 0) { + return; + } + + for (var i = pin.length - 1; i >= 0; i--) { + if (pin[i] != null) { + setState(() => pin[i] = null); + break; + } + } + } + + int _pinLength() { + return pin.fold(0, (v, e) { + if (e != null) { + return v + 1; + } + + return v; + }); + } + +} \ No newline at end of file diff --git a/lib/src/screens/settings/items/item_headers.dart b/lib/src/screens/settings/items/item_headers.dart new file mode 100644 index 000000000..4ab7732c1 --- /dev/null +++ b/lib/src/screens/settings/items/item_headers.dart @@ -0,0 +1,17 @@ +class ItemHeaders { + static const nodes = 'Nodes'; + static const currentNode = 'Current node'; + static const wallets = 'Wallets'; + static const displayBalanceAs = 'Display balance as'; + static const currency = 'Currency'; + static const feePriority = 'Fee priority'; + static const saveRecipientAddress = 'Save recipient address'; + static const personal = 'Personal'; + static const changePIN = 'Change PIN'; + static const changeLanguage = 'Change language'; + static const allowBiometricalAuthentication = 'Allow biometrical authentication'; + static const darkMode = 'Dark mode'; + static const support = 'Support'; + static const termsAndConditions = 'Terms and conditions'; + static const faq = 'FAQ'; +} \ No newline at end of file diff --git a/lib/src/screens/settings/items/settings_item.dart b/lib/src/screens/settings/items/settings_item.dart new file mode 100644 index 000000000..68ae7b04d --- /dev/null +++ b/lib/src/screens/settings/items/settings_item.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/screens/settings/attributes.dart'; + +class SettingsItem { + final VoidCallback onTaped; + final String title; + final String link; + final Image image; + final Widget widget; + final Attributes attribute; + final WidgetBuilder widgetBuilder; + + SettingsItem({this.onTaped, + this.title, + this.link, + this.image, + this.widget, + this.attribute, + this.widgetBuilder}); +} \ No newline at end of file diff --git a/lib/src/screens/settings/settings.dart b/lib/src/screens/settings/settings.dart new file mode 100644 index 000000000..ca6ccfaa2 --- /dev/null +++ b/lib/src/screens/settings/settings.dart @@ -0,0 +1,445 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/balance_display_mode.dart'; +import 'package:cake_wallet/src/domain/common/fiat_currency.dart'; +import 'package:cake_wallet/src/domain/common/transaction_priority.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/action_list/action_list_display_mode.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/settings/attributes.dart'; +import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; +import 'package:cake_wallet/src/screens/settings/items/settings_item.dart'; +import 'package:cake_wallet/src/screens/settings/items/item_headers.dart'; +// Settings widgets +import 'package:cake_wallet/src/screens/settings/widgets/settings_arrow_list_row.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_header_list_row.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_link_list_row.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_switch_list_row.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_text_list_row.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_raw_widget_list_row.dart'; + +class SettingsPage extends BasePage { + String get title => S.current.settings_title; + bool get isModalBackButton => true; + Color get backgroundColor => Palette.lightGrey2; + + @override + Widget body(BuildContext context) { + return SettingsForm(); + } +} + +class SettingsForm extends StatefulWidget { + @override + createState() => SettingsFormState(); +} + +class SettingsFormState extends State { + final _telegramImage = Image.asset('assets/images/Telegram.png'); + final _twitterImage = Image.asset('assets/images/Twitter.png'); + final _changeNowImage = Image.asset('assets/images/change_now.png'); + final _xmrBtcImage = Image.asset('assets/images/xmr_btc.png'); + + final _emailUrl = 'mailto:support@cakewallet.io'; + final _telegramUrl = 'https:t.me/cake_wallet'; + final _twitterUrl = 'https:twitter.com/CakewalletXMR'; + final _changeNowUrl = 'mailto:support@changenow.io'; + final _xmrToUrl = 'mailto:support@xmr.to'; + + List _items = List(); + + _launchUrl(String url) async { + if (await canLaunch(url)) await launch(url); + } + + _setSettingsList() { + final settingsStore = Provider.of(context); + + settingsStore.setItemHeaders(); + + _items.addAll([ + SettingsItem(title: ItemHeaders.nodes, attribute: Attributes.header), + SettingsItem( + onTaped: () => Navigator.of(context).pushNamed(Routes.nodeList), + title: ItemHeaders.currentNode, + widget: Observer( + builder: (_) => Text( + settingsStore.node == null ? '' : settingsStore.node.uri, + style: TextStyle( + fontSize: 16.0, + color: + Theme.of(context).primaryTextTheme.subtitle.color), + )), + attribute: Attributes.widget), + SettingsItem(title: ItemHeaders.wallets, attribute: Attributes.header), + SettingsItem( + onTaped: () => _setBalance(context), + title: ItemHeaders.displayBalanceAs, + widget: Observer( + builder: (_) => Text( + settingsStore.balanceDisplayMode.toString(), + style: TextStyle( + fontSize: 16.0, + color: + Theme.of(context).primaryTextTheme.subtitle.color), + )), + attribute: Attributes.widget), + SettingsItem( + onTaped: () => _setCurrency(context), + title: ItemHeaders.currency, + widget: Observer( + builder: (_) => Text( + settingsStore.fiatCurrency.toString(), + style: TextStyle( + fontSize: 16.0, + color: + Theme.of(context).primaryTextTheme.subtitle.color), + )), + attribute: Attributes.widget), + SettingsItem( + onTaped: () => _setTransactionPriority(context), + title: ItemHeaders.feePriority, + widget: Observer( + builder: (_) => Text( + settingsStore.transactionPriority.toString(), + style: TextStyle( + fontSize: 16.0, + color: + Theme.of(context).primaryTextTheme.subtitle.color), + )), + attribute: Attributes.widget), + SettingsItem( + title: ItemHeaders.saveRecipientAddress, + attribute: Attributes.switcher), + SettingsItem(title: ItemHeaders.personal, attribute: Attributes.header), + SettingsItem( + onTaped: () { + Navigator.of(context).pushNamed(Routes.auth, + arguments: (isAuthenticatedSuccessfully, auth) => + isAuthenticatedSuccessfully + ? Navigator.of(context).popAndPushNamed(Routes.setupPin, + arguments: (setupPinContext, _) => + Navigator.of(context).pop()) + : null); + }, + title: ItemHeaders.changePIN, + attribute: Attributes.arrow), + SettingsItem( + onTaped: () => Navigator.pushNamed(context, Routes.changeLanguage), + title: ItemHeaders.changeLanguage, + attribute: Attributes.arrow), + SettingsItem( + title: ItemHeaders.allowBiometricalAuthentication, + attribute: Attributes.switcher), + SettingsItem(title: ItemHeaders.darkMode, attribute: Attributes.switcher), + SettingsItem( + widgetBuilder: (context) { + return PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + value: ActionListDisplayMode.transactions, + child: Observer( + builder: (_) => Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text(S + .of(context) + .settings_transactions), + Checkbox( + value: settingsStore + .actionlistDisplayMode + .contains(ActionListDisplayMode + .transactions), + onChanged: (value) => settingsStore + .toggleTransactionsDisplay(), + ) + ]))), + PopupMenuItem( + value: ActionListDisplayMode.trades, + child: Observer( + builder: (_) => Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text(S.of(context).settings_trades), + Checkbox( + value: settingsStore + .actionlistDisplayMode + .contains( + ActionListDisplayMode.trades), + onChanged: (value) => settingsStore + .toggleTradesDisplay(), + ) + ]))) + ], + child: Container( + height: 56, + padding: EdgeInsets.only(left: 20, right: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(S.of(context).settings_display_on_dashboard_list, + style: TextStyle( + fontSize: 16, + color: Theme.of(context) + .primaryTextTheme + .title + .color)), + Observer(builder: (_) { + var title = ''; + + if (settingsStore.actionlistDisplayMode.length == + ActionListDisplayMode.values.length) { + title = S.of(context).settings_all; + } + + if (title.isEmpty && + settingsStore.actionlistDisplayMode + .contains(ActionListDisplayMode.trades)) { + title = S.of(context).settings_only_trades; + } + + if (title.isEmpty && + settingsStore.actionlistDisplayMode.contains( + ActionListDisplayMode.transactions)) { + title = S.of(context).settings_only_transactions; + } + + if (title.isEmpty) { + title = S.of(context).settings_none; + } + + return Text(title, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context) + .primaryTextTheme + .subtitle + .color)); + }) + ]), + )); + }, + attribute: Attributes.rawWidget), + SettingsItem(title: ItemHeaders.support, attribute: Attributes.header), + SettingsItem( + onTaped: () => _launchUrl(_emailUrl), + title: 'Email', + link: 'support@cakewallet.io', + image: null, + attribute: Attributes.link), + SettingsItem( + onTaped: () => _launchUrl(_telegramUrl), + title: 'Telegram', + link: 't.me/cake_wallet', + image: _telegramImage, + attribute: Attributes.link), + SettingsItem( + onTaped: () => _launchUrl(_twitterUrl), + title: 'Twitter', + link: 'twitter.com/CakewalletXMR', + image: _twitterImage, + attribute: Attributes.link), + SettingsItem( + onTaped: () => _launchUrl(_changeNowUrl), + title: 'ChangeNow', + link: 'support@changenow.io', + image: _changeNowImage, + attribute: Attributes.link), + SettingsItem( + onTaped: () => _launchUrl(_xmrToUrl), + title: 'XMR.to', + link: 'support@xmr.to', + image: _xmrBtcImage, + attribute: Attributes.link), + SettingsItem( + onTaped: () { + Navigator.push( + context, + CupertinoPageRoute( + builder: (BuildContext context) => DisclaimerPage())); + }, + title: ItemHeaders.termsAndConditions, + attribute: Attributes.arrow), + SettingsItem( + onTaped: () => Navigator.pushNamed(context, Routes.faq), + title: ItemHeaders.faq, + attribute: Attributes.arrow) + ]); + setState(() {}); + } + + _afterLayout(_) => _setSettingsList(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback(_afterLayout); + } + + Widget _getWidget(SettingsItem item) { + switch (item.attribute) { + case Attributes.arrow: + return SettingsArrowListRow( + onTaped: item.onTaped, + title: item.title, + ); + case Attributes.header: + return SettingsHeaderListRow( + title: item.title, + ); + case Attributes.link: + return SettingsLinktListRow( + onTaped: item.onTaped, + title: item.title, + link: item.link, + image: item.image, + ); + case Attributes.switcher: + return SettingsSwitchListRow( + title: item.title, + ); + case Attributes.widget: + return SettingsTextListRow( + onTaped: item.onTaped, + title: item.title, + widget: item.widget, + ); + case Attributes.rawWidget: + return SettingRawWidgetListRow(widgetBuilder: item.widgetBuilder); + default: + return Offstage(); + } + } + + @override + Widget build(BuildContext context) { + final settingsStore = Provider.of(context); + settingsStore.setItemHeaders(); + + return SingleChildScrollView( + child: Column( + children: [ + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: _items.length, + itemBuilder: (context, index) { + final item = _items[index]; + bool _isDrawDivider = true; + + if (item.attribute == Attributes.header) + _isDrawDivider = false; + else if (index < _items.length - 1) { + if (_items[index + 1].attribute == Attributes.header) + _isDrawDivider = false; + } + + return Column( + children: [ + _getWidget(item), + _isDrawDivider + ? Container( + color: Theme.of(context) + .accentTextTheme + .headline + .backgroundColor, + padding: EdgeInsets.only( + left: 20.0, + right: 20.0, + ), + child: Divider( + color: Theme.of(context).dividerColor, + height: 1.0, + ), + ) + : Offstage() + ], + ); + }), + Container( + height: 20.0, + color: Theme.of(context).accentTextTheme.headline.backgroundColor, + ) + ], + )); + } + + Future _presentPicker( + BuildContext context, List list) async { + T _value = list[0]; + + return await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(S.of(context).please_select), + backgroundColor: Theme.of(context).backgroundColor, + content: Container( + height: 150.0, + child: CupertinoPicker( + backgroundColor: Theme.of(context).backgroundColor, + itemExtent: 45.0, + onSelectedItemChanged: (int index) => _value = list[index], + children: List.generate( + list.length, + (index) => Center( + child: Text( + list[index].toString(), + style: TextStyle( + color: Theme.of(context) + .primaryTextTheme + .caption + .color), + ), + ))), + ), + actions: [ + FlatButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(S.of(context).cancel)), + FlatButton( + onPressed: () => Navigator.of(context).pop(_value), + child: Text(S.of(context).ok)) + ], + ); + }); + } + + void _setBalance(BuildContext context) async { + final settingsStore = Provider.of(context); + final selectedDisplayMode = + await _presentPicker(context, BalanceDisplayMode.all); + + if (selectedDisplayMode != null) { + settingsStore.setCurrentBalanceDisplayMode( + balanceDisplayMode: selectedDisplayMode); + } + } + + void _setCurrency(BuildContext context) async { + final settingsStore = Provider.of(context); + final selectedCurrency = await _presentPicker(context, FiatCurrency.all); + + if (selectedCurrency != null) { + settingsStore.setCurrentFiatCurrency(currency: selectedCurrency); + } + } + + void _setTransactionPriority(BuildContext context) async { + final settingsStore = Provider.of(context); + final selectedPriority = + await _presentPicker(context, TransactionPriority.all); + + if (selectedPriority != null) { + settingsStore.setCurrentTransactionPriority(priority: selectedPriority); + } + } +} diff --git a/lib/src/screens/settings/widgets/settings_arrow_list_row.dart b/lib/src/screens/settings/widgets/settings_arrow_list_row.dart new file mode 100644 index 000000000..8532d3bc6 --- /dev/null +++ b/lib/src/screens/settings/widgets/settings_arrow_list_row.dart @@ -0,0 +1,32 @@ +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; + +class SettingsArrowListRow extends StatelessWidget { + final VoidCallback onTaped; + final String title; + final _cakeArrowImage = Image.asset('assets/images/cake_arrow.png'); + + SettingsArrowListRow({@required this.onTaped, this.title}); + + @override + Widget build(BuildContext context) { + final settingsStore = Provider.of(context); + + return Container( + color: Theme.of(context).accentTextTheme.headline.backgroundColor, + child: ListTile( + contentPadding: EdgeInsets.only(left: 20.0, right: 20.0), + title: Observer( + builder: (_) => Text( + settingsStore.itemHeaders[title], + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.title.color), + )), + trailing: _cakeArrowImage, + onTap: onTaped), + ); + } +} diff --git a/lib/src/screens/settings/widgets/settings_header_list_row.dart b/lib/src/screens/settings/widgets/settings_header_list_row.dart new file mode 100644 index 000000000..e64775f8d --- /dev/null +++ b/lib/src/screens/settings/widgets/settings_header_list_row.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:provider/provider.dart'; + +class SettingsHeaderListRow extends StatelessWidget { + final String title; + + SettingsHeaderListRow({this.title}); + + @override + Widget build(BuildContext context) { + final settingsStore = Provider.of(context); + + return Column( + children: [ + SizedBox( + height: 28.0, + ), + Container( + padding: EdgeInsets.only(left: 20.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Observer( + builder: (_) => Text( + settingsStore.itemHeaders[title], + style: TextStyle( + fontSize: 15.0, color: Palette.wildDarkBlue), + )) + ], + ), + ), + SizedBox( + height: 14.0, + ), + ], + ); + } + +} \ No newline at end of file diff --git a/lib/src/screens/settings/widgets/settings_link_list_row.dart b/lib/src/screens/settings/widgets/settings_link_list_row.dart new file mode 100644 index 000000000..d72b01f40 --- /dev/null +++ b/lib/src/screens/settings/widgets/settings_link_list_row.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; + +class SettingsLinktListRow extends StatelessWidget { + final VoidCallback onTaped; + final String title; + final String link; + final Image image; + + SettingsLinktListRow({@required this.onTaped, this.title, this.link, this.image}); + + @override + Widget build(BuildContext context) { + + return Container( + color: Theme.of(context).accentTextTheme.headline.backgroundColor, + child: ListTile( + contentPadding: EdgeInsets.only(left: 20.0, right: 20.0), + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + image != null ? image : Offstage(), + Container( + padding: image != null ? EdgeInsets.only(left: 10) : null, + child: Text( + title, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryTextTheme.title.color + ), + ), + ) + ], + ), + trailing: Text( + link, + style: + TextStyle(fontSize: 14.0, color: Palette.cakeGreen), + ), + onTap: onTaped, + ), + ); + } + +} \ No newline at end of file diff --git a/lib/src/screens/settings/widgets/settings_raw_widget_list_row.dart b/lib/src/screens/settings/widgets/settings_raw_widget_list_row.dart new file mode 100644 index 000000000..e60120123 --- /dev/null +++ b/lib/src/screens/settings/widgets/settings_raw_widget_list_row.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class SettingRawWidgetListRow extends StatelessWidget { + final WidgetBuilder widgetBuilder; + + SettingRawWidgetListRow({@required this.widgetBuilder}); + + @override + Widget build(BuildContext context) { + + return Container( + color: Theme.of(context).accentTextTheme.headline.backgroundColor, + child: widgetBuilder(context) ?? Container(), + ); + } +} diff --git a/lib/src/screens/settings/widgets/settings_switch_list_row.dart b/lib/src/screens/settings/widgets/settings_switch_list_row.dart new file mode 100644 index 000000000..df658bccc --- /dev/null +++ b/lib/src/screens/settings/widgets/settings_switch_list_row.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/theme_changer.dart'; +import 'package:cake_wallet/themes.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/widgets/standart_switch.dart'; + +class SettingsSwitchListRow extends StatelessWidget { + final String title; + + SettingsSwitchListRow({@required this.title}); + + Widget _getSwitch(BuildContext context) { + final settingsStore = Provider.of(context); + ThemeChanger _themeChanger = Provider.of(context); + + if (settingsStore.itemHeaders[title] == + S.of(context).settings_save_recipient_address) { + return Observer( + builder: (_) => StandartSwitch( + value: settingsStore.shouldSaveRecipientAddress, + onTaped: () { + bool _currentValue = !settingsStore.shouldSaveRecipientAddress; + settingsStore.setSaveRecipientAddress( + shouldSaveRecipientAddress: _currentValue); + })); + } + + if (settingsStore.itemHeaders[title] == + S.of(context).settings_allow_biometrical_authentication) { + return Observer( + builder: (_) => StandartSwitch( + value: settingsStore.allowBiometricalAuthentication, + onTaped: () { + bool _currentValue = + !settingsStore.allowBiometricalAuthentication; + settingsStore.setAllowBiometricalAuthentication( + allowBiometricalAuthentication: _currentValue); + })); + } + + if (settingsStore.itemHeaders[title] == S.of(context).settings_dark_mode) { + return Observer( + builder: (_) => StandartSwitch( + value: settingsStore.isDarkTheme, + onTaped: () { + bool _currentValue = !settingsStore.isDarkTheme; + settingsStore.saveDarkTheme(isDarkTheme: _currentValue); + _themeChanger.setTheme( + _currentValue ? Themes.darkTheme : Themes.lightTheme); + })); + } + + return null; + } + + @override + Widget build(BuildContext context) { + final settingsStore = Provider.of(context); + + return Container( + color: Theme.of(context).accentTextTheme.headline.backgroundColor, + child: ListTile( + contentPadding: EdgeInsets.only(left: 20.0, right: 20.0), + title: Observer( + builder: (_) => Text(settingsStore.itemHeaders[title], + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.title.color)), + ), + trailing: _getSwitch(context)), + ); + } +} diff --git a/lib/src/screens/settings/widgets/settings_text_list_row.dart b/lib/src/screens/settings/widgets/settings_text_list_row.dart new file mode 100644 index 000000000..ccda97a4b --- /dev/null +++ b/lib/src/screens/settings/widgets/settings_text_list_row.dart @@ -0,0 +1,33 @@ +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; + +class SettingsTextListRow extends StatelessWidget { + final VoidCallback onTaped; + final String title; + final Widget widget; + + SettingsTextListRow({@required this.onTaped, this.title, this.widget}); + + @override + Widget build(BuildContext context) { + final settingsStore = Provider.of(context); + + return Container( + color: Theme.of(context).accentTextTheme.headline.backgroundColor, + child: ListTile( + contentPadding: EdgeInsets.only(left: 20.0, right: 20.0), + title: Observer( + builder: (_) => Text( + settingsStore.itemHeaders[title], + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.title.color), + )), + trailing: widget, + onTap: onTaped, + ), + ); + } +} diff --git a/lib/src/screens/setup_pin_code/setup_pin_code.dart b/lib/src/screens/setup_pin_code/setup_pin_code.dart new file mode 100644 index 000000000..330a8ee8f --- /dev/null +++ b/lib/src/screens/setup_pin_code/setup_pin_code.dart @@ -0,0 +1,113 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/src/stores/user/user_store.dart'; +import 'package:cake_wallet/src/screens/pin_code/pin_code.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class SetupPinCodePage extends BasePage { + final Function(BuildContext, String) onPinCodeSetup; + + @override + String get title => S.current.setup_pin; + + SetupPinCodePage({this.onPinCodeSetup}); + + @override + Widget body(BuildContext context) => + SetupPinCodeForm(onPinCodeSetup: onPinCodeSetup, hasLengthSwitcher: true); +} + +class SetupPinCodeForm extends PinCodeWidget { + final Function(BuildContext, String) onPinCodeSetup; + final bool hasLengthSwitcher; + + SetupPinCodeForm( + {@required this.onPinCodeSetup, @required this.hasLengthSwitcher}); + + @override + _SetupPinCodeFormState createState() => _SetupPinCodeFormState(); +} + +class _SetupPinCodeFormState + extends PinCodeState { + + bool isEnteredOriginalPin() => !(_originalPin.length == 0); + Function(BuildContext) onPinCodeSetup; + List _originalPin = []; + UserStore _userStore; + SettingsStore _settingsStore; + + _SetupPinCodeFormState() { + title = S.current.enter_your_pin; + } + + @override + void onPinCodeEntered(PinCodeState state) { + if (!isEnteredOriginalPin()) { + _originalPin = state.pin; + state.title = S.current.enter_your_pin_again; + state.clear(); + } else { + if (listEquals(state.pin, _originalPin)) { + final String pin = state.pin.fold("", (ac, val) => ac + '$val'); + _userStore.set(password: pin); + _settingsStore.setDefaultPinLength(pinLength: state.pinLength); + + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + content: Text(S.of(context).setup_successful), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + widget.onPinCodeSetup(context, pin); + reset(); + }, + ), + ], + ); + }); + } else { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text(S.of(context).pin_is_incorrect), + actions: [ + FlatButton( + child: Text(S.of(context).ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }); + + reset(); + } + } + } + + void reset() { + clear(); + setTitle(S.current.enter_your_pin); + _originalPin = []; + } + + @override + Widget build(BuildContext context) { + _userStore = Provider.of(context); + _settingsStore = Provider.of(context); + + return body(context); + } +} diff --git a/lib/src/screens/show_keys/show_keys_page.dart b/lib/src/screens/show_keys/show_keys_page.dart new file mode 100644 index 000000000..4d5a1dd1c --- /dev/null +++ b/lib/src/screens/show_keys/show_keys_page.dart @@ -0,0 +1,68 @@ +import 'package:provider/provider.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/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_keys_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class ShowKeysPage extends BasePage { + bool get isModalBackButton => true; + String get title => S.current.wallet_keys; + + @override + Widget body(BuildContext context) { + final walletKeysStore = Provider.of(context); + + return Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0, left: 20, right: 20), + child: Observer( + builder: (_) { + Map keysMap = { + S.of(context).view_key_public: walletKeysStore.publicViewKey, + S.of(context).view_key_private: walletKeysStore.privateViewKey, + S.of(context).spend_key_public: walletKeysStore.publicSpendKey, + S.of(context).spend_key_private: walletKeysStore.privateSpendKey + }; + + return ListView.separated( + separatorBuilder: (_, __) => Container( + padding: EdgeInsets.only(left: 30.0, right: 20.0), + child: Divider( + color: Theme.of(context).dividerTheme.color, + height: 1.0)), + itemCount: keysMap.length, + itemBuilder: (BuildContext context, int index) { + final key = keysMap.keys.elementAt(index); + final value = keysMap.values.elementAt(index); + + return ListTile( + contentPadding: EdgeInsets.only( + top: 10, bottom: 10, left: 30, right: 20), + onTap: () { + Clipboard.setData(ClipboardData( + text: keysMap.values.elementAt(index))); + Scaffold.of(context).showSnackBar(SnackBar( + content: Text( + S.of(context).copied_key_to_clipboard(key), + textAlign: TextAlign.center, + style: TextStyle(color: Colors.white), + ), + backgroundColor: Colors.green, + duration: Duration(seconds: 1), + )); + }, + title: Text(key + ':', style: TextStyle(fontSize: 16.0)), + subtitle: Container( + padding: EdgeInsets.only(top: 5.0), + child: Text(value, + style: TextStyle( + fontSize: 16.0, color: Palette.wildDarkBlue)), + )); + }); + }, + )); + } +} diff --git a/lib/src/screens/splash/splash_page.dart b/lib/src/screens/splash/splash_page.dart new file mode 100644 index 000000000..07bc1119d --- /dev/null +++ b/lib/src/screens/splash/splash_page.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class SplashPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container() + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/subaddress/new_subaddress_page.dart b/lib/src/screens/subaddress/new_subaddress_page.dart new file mode 100644 index 000000000..06c29a932 --- /dev/null +++ b/lib/src/screens/subaddress/new_subaddress_page.dart @@ -0,0 +1,100 @@ +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/subaddress_creation/subaddress_creation_state.dart'; +import 'package:cake_wallet/src/stores/subaddress_creation/subaddress_creation_store.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/palette.dart'; + +class NewSubaddressPage extends BasePage { + String get title => S.current.new_subaddress_title; + + @override + Widget body(BuildContext context) => NewSubaddressForm(); + + @override + Widget build(BuildContext context) { + final subaddressCreationStore = + Provider.of(context); + + reaction((_) => subaddressCreationStore.state, (state) { + if (state is SubaddressCreatedSuccessfully) { + WidgetsBinding.instance + .addPostFrameCallback((_) => Navigator.of(context).pop()); + } + }); + + return super.build(context); + } +} + +class NewSubaddressForm extends StatefulWidget { + @override + NewSubaddressFormState createState() => NewSubaddressFormState(); +} + +class NewSubaddressFormState extends State { + final _formKey = GlobalKey(); + final _labelController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final subaddressCreationStore = + Provider.of(context); + + return Form( + key: _formKey, + child: Stack(children: [ + Center( + child: Padding( + padding: EdgeInsets.only(left: 35, right: 35), + child: TextFormField( + controller: _labelController, + decoration: InputDecoration( + hintStyle: TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).new_subaddress_label_name, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + validator: (value) { + subaddressCreationStore.validateSubaddressName(value); + return subaddressCreationStore.errorMessage; + }), + ), + ), + Positioned( + bottom: 20, + left: 20, + right: 20, + child: Observer( + builder: (_) => LoadingPrimaryButton( + onPressed: () async { + if (_formKey.currentState.validate()) { + await subaddressCreationStore.add( + label: _labelController.text); + Navigator.of(context).pop(); + } + }, + text: S.of(context).new_subaddress_create, + color: Theme.of(context) + .accentTextTheme + .button + .backgroundColor, + borderColor: Theme.of(context) + .accentTextTheme + .button + .decorationColor, + isLoading: + subaddressCreationStore.state is SubaddressIsCreating), + )) + ])); + } +} diff --git a/lib/src/screens/subaddress/subaddress_list_page.dart b/lib/src/screens/subaddress/subaddress_list_page.dart new file mode 100644 index 000000000..28f3b3f58 --- /dev/null +++ b/lib/src/screens/subaddress/subaddress_list_page.dart @@ -0,0 +1,66 @@ +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/stores/subaddress_list/subaddress_list_store.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class SubaddressListPage extends BasePage { + bool get isModalBackButton => true; + String get title => S.current.subaddress_title; + AppBarStyle get appBarStyle => AppBarStyle.withShadow; + + SubaddressListPage(); + + @override + Widget body(BuildContext context) { + final walletStore = Provider.of(context); + final subaddressListStore = Provider.of(context); + + final currentColor = Theme.of(context).selectedRowColor; + final notCurrentColor = Theme.of(context).backgroundColor; + + return Container( + padding: EdgeInsets.only(top: 20.0, bottom: 20.0), + child: Observer( + builder: (_) => ListView.separated( + separatorBuilder: (_, __) => Divider( + color: Theme.of(context).dividerTheme.color, + height: 1.0, + ), + itemCount: subaddressListStore.subaddresses == null + ? 0 + : subaddressListStore.subaddresses.length, + itemBuilder: (BuildContext context, int index) { + final subaddress = subaddressListStore.subaddresses[index]; + final isCurrent = + walletStore.subaddress.address == subaddress.address; + final label = subaddress.label != null + ? subaddress.label + : subaddress.address; + + return InkWell( + onTap: () => Navigator.of(context).pop(subaddress), + child: Container( + color: isCurrent ? currentColor : notCurrentColor, + child: Column(children: [ + ListTile( + title: Text( + label, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context) + .primaryTextTheme + .headline + .color), + ), + ) + ]), + ), + ); + }), + )); + } +} diff --git a/lib/src/screens/trade_details/trade_details_page.dart b/lib/src/screens/trade_details/trade_details_page.dart new file mode 100644 index 000000000..68c19a285 --- /dev/null +++ b/lib/src/screens/trade_details/trade_details_page.dart @@ -0,0 +1,78 @@ +import 'package:provider/provider.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'; +import 'package:cake_wallet/src/stores/exchange_trade/exchange_trade_store.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/standart_list_row.dart'; + +class TradeDetailsPage extends BasePage { + String get title => S.current.trade_details_title; + bool get isModalBackButton => true; + + @override + Widget body(BuildContext context) { + final exchangeStore = Provider.of(context); + + return Container( + padding: EdgeInsets.only(top: 10.0, bottom: 10.0, left: 20, right: 15), + child: Observer(builder: (_) { + final trade = exchangeStore.trade; + final items = [ + StandartListItem( + title: S.of(context).trade_details_id, value: trade.id), + StandartListItem( + title: S.of(context).trade_details_state, + value: trade.state != null + ? trade.state.toString() + : S.of(context).trade_details_fetching) + ]; + + if (trade.provider != null) { + items.add(StandartListItem( + title: S.of(context).trade_details_provider, + value: trade.provider.toString())); + } + + if (trade.createdAt != null) { + items.add(StandartListItem( + title: S.of(context).trade_details_created_at, + value: trade.createdAt.toString())); + } + + if (trade.from != null && trade.to != null) { + items.add(StandartListItem( + title: S.of(context).trade_details_pair, + value: '${trade.from.toString()} → ${trade.to.toString()}')); + } + + return ListView.separated( + separatorBuilder: (_, __) => Divider( + color: Theme.of(context).dividerTheme.color, + height: 1.0, + ), + padding: EdgeInsets.only(left: 25, top: 10, right: 25, bottom: 15), + itemCount: items.length, + itemBuilder: (BuildContext context, int index) { + final item = items[index]; + return GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: '${item.value}')); + Scaffold.of(context).showSnackBar( + SnackBar( + content: Text( + S.of(context).trade_details_copied(item.title)), + backgroundColor: Colors.green, + duration: Duration(milliseconds: 1500), + ), + ); + }, + child: StandartListRow( + title: '${item.title}', value: '${item.value}')); + }); + })); + } +} diff --git a/lib/src/screens/transaction_details/standart_list_item.dart b/lib/src/screens/transaction_details/standart_list_item.dart new file mode 100644 index 000000000..eb36c3706 --- /dev/null +++ b/lib/src/screens/transaction_details/standart_list_item.dart @@ -0,0 +1,6 @@ +class StandartListItem { + final String title; + final String value; + + StandartListItem({this.title, this.value}); +} \ No newline at end of file diff --git a/lib/src/screens/transaction_details/standart_list_row.dart b/lib/src/screens/transaction_details/standart_list_row.dart new file mode 100644 index 000000000..5d9f0cd6b --- /dev/null +++ b/lib/src/screens/transaction_details/standart_list_row.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; + +class StandartListRow extends StatelessWidget { + final String title; + final String value; + + StandartListRow({this.title, this.value}); + + @override + Widget build(BuildContext context) { + + return Padding( + padding: const EdgeInsets.only(top: 10, bottom: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).primaryTextTheme.overline.color), + textAlign: TextAlign.left), + Padding( + padding: const EdgeInsets.only(top: 5), + child: Text(value, + style: TextStyle( + fontSize: 14, + color: Palette.wildDarkBlue)), + ) + ]), + ); + } +} diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart new file mode 100644 index 000000000..74a70ab0c --- /dev/null +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -0,0 +1,104 @@ +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/transaction_info.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/standart_list_row.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class TransactionDetailsPage extends BasePage { + bool get isModalBackButton => true; + String get title => S.current.transaction_details_title; + + final TransactionInfo transactionInfo; + + TransactionDetailsPage({this.transactionInfo}); + + @override + Widget body(BuildContext context) { + final settingsStore = Provider.of(context); + + return TransactionDetailsForm( + transactionInfo: transactionInfo, settingsStore: settingsStore); + } +} + +class TransactionDetailsForm extends StatefulWidget { + final TransactionInfo transactionInfo; + final SettingsStore settingsStore; + + TransactionDetailsForm( + {@required this.transactionInfo, @required this.settingsStore}); + + @override + createState() => TransactionDetailsFormState(); +} + +class TransactionDetailsFormState extends State { + final DateFormat _dateFormat = DateFormat('dd.MM.yyyy, HH:mm'); + List _items = List(); + + @override + void initState() { + List items = [ + StandartListItem( + title: S.current.transaction_details_transaction_id, + value: widget.transactionInfo.id), + StandartListItem( + title: S.current.transaction_details_date, + value: _dateFormat.format(widget.transactionInfo.date)), + StandartListItem( + title: S.current.transaction_details_height, + value: '${widget.transactionInfo.height}'), + StandartListItem( + title: S.current.transaction_details_amount, + value: widget.transactionInfo.amountFormatted()) + ]; + + if (widget.settingsStore.shouldSaveRecipientAddress && widget.transactionInfo.recipientAddress != null) { + items.add(StandartListItem( + title: S.current.transaction_details_recipient_address, + value: widget.transactionInfo.recipientAddress)); + } + + _items.addAll(items); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(left: 20, right: 15, top: 10, bottom: 10), + child: ListView.separated( + separatorBuilder: (context, index) => Container( + height: 1, + color: Theme.of(context).dividerTheme.color, + ), + padding: EdgeInsets.only(left: 25, top: 10, right: 25, bottom: 15), + itemCount: _items.length, + itemBuilder: (context, index) { + final item = _items[index]; + + return GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: item.value)); + Scaffold.of(context).showSnackBar( + SnackBar( + content: Text( + S.of(context).transaction_details_copied(item.title)), + backgroundColor: Colors.green, + duration: Duration(milliseconds: 1500), + ), + ); + }, + child: + StandartListRow(title: '${item.title}:', value: item.value), + ); + }), + ); + } +} diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart new file mode 100644 index 000000000..7bbadfa46 --- /dev/null +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/domain/common/wallet_description.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/src/stores/wallet_list/wallet_list_store.dart'; +import 'package:cake_wallet/src/screens/wallet_list/wallet_menu.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; + +class WalletListPage extends BasePage { + bool get isModalBackButton => true; + String get title => S.current.wallet_list_title; + AppBarStyle get appBarStyle => AppBarStyle.withShadow; + + @override + Widget body(BuildContext context) => WalletListBody(); +} + +class WalletListBody extends StatefulWidget { + WalletListBodyState createState() => WalletListBodyState(); +} + +class WalletListBodyState extends State { + WalletListStore _walletListStore; + + void presetMenuForWallet(WalletDescription wallet, bool isCurrentWallet, + BuildContext bodyContext) { + final walletMenu = WalletMenu(bodyContext); + List items = walletMenu.generateItemsForWalletMenu(isCurrentWallet); + + showDialog( + context: bodyContext, + builder: (_) => Picker( + items: items, + selectedAtIndex: -1, + title: S.of(context).wallet_menu, + onItemSelected: (item) => walletMenu.action( + walletMenu.listItems.indexOf(item), wallet, isCurrentWallet)), + ); + } + + @override + Widget build(BuildContext context) { + _walletListStore = Provider.of(context); + + return ScrollableWithBottomSection( + content: Container( + padding: EdgeInsets.all(20), + child: Observer( + builder: (_) => ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (_, index) => Divider( + color: Theme.of(context).dividerTheme.color, height: 1.0), + itemCount: _walletListStore.wallets.length, + itemBuilder: (__, index) { + final wallet = _walletListStore.wallets[index]; + final isCurrentWallet = + _walletListStore.isCurrentWallet(wallet); + + return InkWell( + onTap: () => + presetMenuForWallet(wallet, isCurrentWallet, context), + child: Container( + padding: EdgeInsets.only(left: 10.0, right: 10.0), + child: ListTile( + title: Text( + wallet.name, + style: TextStyle( + color: isCurrentWallet + ? Palette.cakeGreen + : Theme.of(context) + .primaryTextTheme + .headline + .color, + fontSize: 18.0, + fontWeight: FontWeight.w600), + ), + trailing: isCurrentWallet + ? Icon( + Icons.check, + color: Palette.cakeGreen, + size: 20.0, + ) + : null))); + }), + ), + ), + bottomSection: Column(children: [ + PrimaryIconButton( + onPressed: () => + Navigator.of(context).pushNamed(Routes.newWallet), + iconData: Icons.add, + color: Theme.of(context).primaryTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).primaryTextTheme.button.decorationColor, + iconColor: Palette.violet, + iconBackgroundColor: Theme.of(context).primaryIconTheme.color, + text: S.of(context).wallet_list_create_new_wallet), + SizedBox(height: 10.0), + PrimaryIconButton( + onPressed: () => + Navigator.of(context).pushNamed(Routes.restoreWalletOptions), + iconData: Icons.refresh, + text: S.of(context).wallet_list_restore_wallet, + color: Theme.of(context).accentTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).accentTextTheme.button.decorationColor, + iconColor: Theme.of(context).primaryTextTheme.caption.color, + iconBackgroundColor: Theme.of(context).accentIconTheme.color) + ])); + } +} diff --git a/lib/src/screens/wallet_list/wallet_menu.dart b/lib/src/screens/wallet_list/wallet_menu.dart new file mode 100644 index 000000000..cd1842ed6 --- /dev/null +++ b/lib/src/screens/wallet_list/wallet_menu.dart @@ -0,0 +1,88 @@ +import 'package:cake_wallet/src/domain/common/wallet_description.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/src/stores/wallet_list/wallet_list_store.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class WalletMenu { + + final BuildContext context; + final List listItems = [ + S.current.wallet_list_load_wallet, + S.current.show_seed, + S.current.remove, + S.current.rescan + ]; + + WalletMenu(this.context); + + ListgenerateItemsForWalletMenu(bool isCurrentWallet) { + List items = new List(); + + if (!isCurrentWallet) items.add(listItems[0]); + if (isCurrentWallet) items.add(listItems[1]); + if (!isCurrentWallet) items.add(listItems[2]); + if (isCurrentWallet) items.add(listItems[3]); + + return items; + } + + void action(int index, WalletDescription wallet, bool isCurrentWallet) { + WalletListStore _walletListStore = Provider.of(context); + + switch (index) { + case 0: + Navigator.of(context).pushNamed(Routes.auth, + arguments: (isAuthenticatedSuccessfully, auth) async { + if (!isAuthenticatedSuccessfully) { + return; + } + + try { + auth.changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); + await _walletListStore.loadWallet(wallet); + auth.close(); + Navigator.of(context).pop(); + } catch (e) { + auth.changeProcessText( + S.of(context).wallet_list_failed_to_load(wallet.name, e.toString())); + } + }); + break; + case 1: + Navigator.of(context).pushNamed(Routes.auth, + arguments: (isAuthenticatedSuccessfully, auth) async { + if (!isAuthenticatedSuccessfully) { + return; + } + auth.close(); + Navigator.of(context).pushNamed(Routes.seed); + }); + break; + case 2: + Navigator.of(context).pushNamed(Routes.auth, + arguments: (isAuthenticatedSuccessfully, auth) async { + if (!isAuthenticatedSuccessfully) { + return; + } + + try { + auth.changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name)); + await _walletListStore.remove(wallet); + auth.close(); + } catch (e) { + auth.changeProcessText( + S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString())); + } + }); + break; + case 3: + Navigator.of(context).pushNamed(Routes.rescan); + break; + default: + break; + } + } + +} \ No newline at end of file diff --git a/lib/src/screens/welcome/create_welcome_page.dart b/lib/src/screens/welcome/create_welcome_page.dart new file mode 100644 index 000000000..2d4b3cc28 --- /dev/null +++ b/lib/src/screens/welcome/create_welcome_page.dart @@ -0,0 +1,4 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/screens/welcome/welcome_page.dart'; + +Widget createWelcomePage() => WelcomePage(); \ No newline at end of file diff --git a/lib/src/screens/welcome/welcome_page.dart b/lib/src/screens/welcome/welcome_page.dart new file mode 100644 index 000000000..30f35afa1 --- /dev/null +++ b/lib/src/screens/welcome/welcome_page.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/theme_changer.dart'; +import 'package:cake_wallet/themes.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class WelcomePage extends BasePage { + static const _aspectRatioImage = 1.26; + static const _baseWidth = 411.43; + final _image = Image.asset('assets/images/welcomeImg.png'); + final _cakeLogo = Image.asset('assets/images/cake_logo.png'); + + @override + Widget build(BuildContext context) { + ThemeChanger _themeChanger = Provider.of(context); + bool _isDarkTheme = (_themeChanger.getTheme() == Themes.darkTheme); + + return Scaffold( + backgroundColor: + _isDarkTheme ? Theme.of(context).backgroundColor : backgroundColor, + resizeToAvoidBottomPadding: false, + body: SafeArea(child: body(context)), + ); + } + + @override + Widget body(BuildContext context) { + final _screenWidth = MediaQuery.of(context).size.width; + final textScaleFactor = _screenWidth < _baseWidth ? 0.76 : 1.0; + + return Column(children: [ + Stack( + alignment: Alignment.center, + children: [ + AspectRatio( + aspectRatio: _aspectRatioImage, + child: FittedBox(child: _image, fit: BoxFit.fill)), + Positioned(bottom: 0.0, child: _cakeLogo) + ], + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + S.of(context).welcome, + style: TextStyle( + fontSize: 30.0, + fontWeight: FontWeight.bold, + ), + textScaleFactor: textScaleFactor, + textAlign: TextAlign.center, + ), + Text( + S.of(context).first_wallet_text, + style: TextStyle( + fontSize: 22.0, + color: Palette.lightBlue, + ), + textScaleFactor: textScaleFactor, + textAlign: TextAlign.center, + ), + Text( + S.of(context).please_make_selection, + style: TextStyle( + fontSize: 16.0, + color: Palette.lightBlue, + ), + textScaleFactor: textScaleFactor, + textAlign: TextAlign.center, + ) + ]), + ), + Container( + padding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 20.0), + child: Column(children: [ + PrimaryButton( + onPressed: () { + Navigator.pushNamed(context, Routes.newWalletFromWelcome); + }, + text: S.of(context).create_new, + color: + Theme.of(context).primaryTextTheme.button.backgroundColor, + borderColor: + Theme.of(context).primaryTextTheme.button.decorationColor), + SizedBox(height: 10), + PrimaryButton( + onPressed: () { + Navigator.pushNamed(context, Routes.restoreOptions); + }, + color: Theme.of(context).accentTextTheme.caption.backgroundColor, + borderColor: + Theme.of(context).accentTextTheme.caption.decorationColor, + text: S.of(context).restore_wallet, + ) + ])) + ]); + } +} diff --git a/lib/src/start_updating_price.dart b/lib/src/start_updating_price.dart new file mode 100644 index 000000000..1f441f084 --- /dev/null +++ b/lib/src/start_updating_price.dart @@ -0,0 +1,34 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/common/fetch_price.dart'; +import 'package:cake_wallet/src/stores/price/price_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; + +bool _startedUpdatingPrice = false; + +_updatePrice(Map args) async => + await fetchPriceFor(fiat: args['fiat'], crypto: args['crypto']); + +updatePrice(Map args) async => compute(_updatePrice, args); + +startUpdatingPrice({SettingsStore settingsStore, PriceStore priceStore}) async { + if (_startedUpdatingPrice) { + return; + } + + const currentCrypto = CryptoCurrency.xmr; + _startedUpdatingPrice = true; + + final price = await updatePrice( + {'fiat': settingsStore.fiatCurrency, 'crypto': currentCrypto}); + priceStore.changePriceForPair( + fiat: settingsStore.fiatCurrency, crypto: currentCrypto, price: price); + + Timer.periodic(Duration(seconds: 30), (_) async { + final price = await updatePrice( + {'fiat': settingsStore.fiatCurrency, 'crypto': currentCrypto}); + priceStore.changePriceForPair( + fiat: settingsStore.fiatCurrency, crypto: currentCrypto, price: price); + }); +} diff --git a/lib/src/stores/account_list/account_list_store.dart b/lib/src/stores/account_list/account_list_store.dart new file mode 100644 index 000000000..e8ea07d5c --- /dev/null +++ b/lib/src/stores/account_list/account_list_store.dart @@ -0,0 +1,99 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/monero/monero_wallet.dart'; +import 'package:cake_wallet/src/domain/monero/account.dart'; +import 'package:cake_wallet/src/domain/monero/account_list.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'account_list_store.g.dart'; + +class AccountListStore = AcountListStoreBase with _$AccountListStore; + +abstract class AcountListStoreBase with Store { + @observable + List accounts; + + @observable + bool isValid; + + @observable + String errorMessage; + + @observable + bool isAccountCreating; + + AccountList _accountList; + StreamSubscription _onWalletChangeSubscription; + StreamSubscription> _onAccountsChangeSubscription; + + AcountListStoreBase({@required WalletService walletService}) { + accounts = []; + isAccountCreating = false; + + if (walletService.currentWallet != null) { + _onWalletChanged(walletService.currentWallet); + } + + _onWalletChangeSubscription = + walletService.onWalletChange.listen(_onWalletChanged); + } + + @override + void dispose() { + _onWalletChangeSubscription.cancel(); + + if (_onAccountsChangeSubscription != null) { + _onAccountsChangeSubscription.cancel(); + } + + super.dispose(); + } + + Future updateAccountList() async { + await _accountList.refresh(); + accounts = _accountList.getAll(); + } + + Future addAccount({String label}) async { + try { + isAccountCreating = true; + await _accountList.addAccount(label: label); + await updateAccountList(); + isAccountCreating = false; + } catch (e) { + isAccountCreating = false; + } + } + + Future renameAccount({int index, String label}) async { + await _accountList.setLabelSubaddress(accountIndex: index, label: label); + await updateAccountList(); + } + + Future _onWalletChanged(Wallet wallet) async { + if (_onAccountsChangeSubscription != null) { + _onAccountsChangeSubscription.cancel(); + } + + if (wallet is MoneroWallet) { + _accountList = wallet.getAccountList(); + _onAccountsChangeSubscription = + _accountList.accounts.listen((accounts) => this.accounts = accounts); + await updateAccountList(); + + return; + } + + print('Incorrect wallet type for this operation (AccountList)'); + } + + void validateAccountName(String value) { + String p = '^[a-zA-Z0-9_]{1,15}\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + errorMessage = isValid ? null : S.current.error_text_account_name; + } +} diff --git a/lib/src/stores/action_list/action_list_display_mode.dart b/lib/src/stores/action_list/action_list_display_mode.dart new file mode 100644 index 000000000..a4ce36cad --- /dev/null +++ b/lib/src/stores/action_list/action_list_display_mode.dart @@ -0,0 +1,32 @@ +enum ActionListDisplayMode { transactions, trades } + +int serializeActionlistDisplayModes(List modes) { + var i = 0; + + for (final mode in modes) { + switch (mode) { + case ActionListDisplayMode.trades: + i += 1; + break; + case ActionListDisplayMode.transactions: + i += 10; + break; + } + } + + return i; +} + +List deserializeActionlistDisplayModes(int raw) { + List modes = []; + + if (raw == 1 || raw - 10 == 1) { + modes.add(ActionListDisplayMode.trades); + } + + if (raw >= 10) { + modes.add(ActionListDisplayMode.transactions); + } + + return modes; +} diff --git a/lib/src/stores/action_list/action_list_item.dart b/lib/src/stores/action_list/action_list_item.dart new file mode 100644 index 000000000..b03bd1bdc --- /dev/null +++ b/lib/src/stores/action_list/action_list_item.dart @@ -0,0 +1,3 @@ +abstract class ActionListItem { + DateTime get date; +} \ No newline at end of file diff --git a/lib/src/stores/action_list/action_list_store.dart b/lib/src/stores/action_list/action_list_store.dart new file mode 100644 index 000000000..0dd1e30f9 --- /dev/null +++ b/lib/src/stores/action_list/action_list_store.dart @@ -0,0 +1,224 @@ +import 'dart:async'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/monero/account.dart'; +import 'package:cake_wallet/src/domain/common/transaction_history.dart'; +import 'package:cake_wallet/src/domain/common/transaction_info.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/domain/monero/monero_wallet.dart'; +import 'package:cake_wallet/src/domain/common/calculate_fiat_amount_raw.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/monero/monero_amount_format.dart'; +import 'package:cake_wallet/src/domain/monero/transaction_description.dart'; +import 'package:cake_wallet/src/stores/price/price_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/src/stores/action_list/action_list_display_mode.dart'; +import 'package:cake_wallet/src/stores/action_list/action_list_item.dart'; +import 'package:cake_wallet/src/stores/action_list/date_section_item.dart'; +import 'package:cake_wallet/src/stores/action_list/trade_filter_store.dart'; +import 'package:cake_wallet/src/stores/action_list/trade_list_item.dart'; +import 'package:cake_wallet/src/stores/action_list/transaction_filter_store.dart'; +import 'package:cake_wallet/src/stores/action_list/transaction_list_item.dart'; + +part 'action_list_store.g.dart'; + +class ActionListStore = ActionListBase with _$ActionListStore; + +abstract class ActionListBase with Store { + static List formattedItemsList(List items) { + var formattedList = List(); + DateTime lastDate; + items.sort((a, b) => b.date.compareTo(a.date)); + + for (int i = 0; i < items.length; i++) { + final transaction = items[i]; + + if (lastDate == null) { + lastDate = transaction.date; + formattedList.add(DateSectionItem(transaction.date)); + formattedList.add(transaction); + continue; + } + + final isCurrentDay = lastDate.year == transaction.date.year && + lastDate.month == transaction.date.month && + lastDate.day == transaction.date.day; + + if (isCurrentDay) { + formattedList.add(transaction); + continue; + } + + lastDate = transaction.date; + formattedList.add(DateSectionItem(transaction.date)); + formattedList.add(transaction); + } + + return formattedList; + } + + @computed + List get transactions { + final symbol = PriceStoreBase.generateSymbolForPair( + fiat: _settingsStore.fiatCurrency, crypto: CryptoCurrency.xmr); + final price = _priceStore.prices[symbol]; + + _transactions.forEach((item) { + final amount = calculateFiatAmountRaw( + cryptoAmount: moneroAmountToDouble(amount: item.transaction.amount), + price: price); + item.transaction.changeFiatAmount(amount); + }); + + return _transactions; + } + + @observable + List _transactions; + + @observable + List trades; + + @computed + List get items { + var _items = List(); + + if (_settingsStore.actionlistDisplayMode + .contains(ActionListDisplayMode.transactions)) { + _items + .addAll(transactionFilterStore.filtered(transactions: transactions)); + } + + if (_settingsStore.actionlistDisplayMode + .contains(ActionListDisplayMode.trades)) { + _items.addAll(tradeFilterStore.filtered(trades: trades)); + } + + return formattedItemsList(_items); + } + + @computed + int get totalCount => transactions.length + trades.length; + + TransactionFilterStore transactionFilterStore; + TradeFilterStore tradeFilterStore; + Box transactionDescriptions; + Box tradesSource; + + WalletService _walletService; + TransactionHistory _history; + SettingsStore _settingsStore; + PriceStore _priceStore; + Account _account; + StreamSubscription _onWalletChangeSubscription; + StreamSubscription> _onTransactionsChangeSubscription; + StreamSubscription _onAccountChangeSubscription; + StreamSubscription _onTransactionDescriptions; + + ActionListBase( + {@required WalletService walletService, + @required SettingsStore settingsStore, + @required PriceStore priceStore, + @required this.transactionFilterStore, + @required this.tradeFilterStore, + @required this.transactionDescriptions, + @required this.tradesSource}) { + trades = List(); + _transactions = List(); + _walletService = walletService; + _settingsStore = settingsStore; + _priceStore = priceStore; + + if (walletService.currentWallet != null) { + _onWalletChanged(walletService.currentWallet); + } + + _onWalletChangeSubscription = + walletService.onWalletChange.listen(_onWalletChanged); + + _onTransactionDescriptions = transactionDescriptions + .watch() + .listen((_) async => await _updateTransactionsList()); + + updateTradeList(); + } + + @override + void dispose() { + if (_onTransactionsChangeSubscription != null) { + _onTransactionsChangeSubscription.cancel(); + } + + if (_onAccountChangeSubscription != null) { + _onAccountChangeSubscription.cancel(); + } + + _onTransactionDescriptions?.cancel(); + + _onWalletChangeSubscription.cancel(); + super.dispose(); + } + + @action + Future updateTradeList() async => this.trades = + tradesSource.values.map((trade) => TradeListItem(trade: trade)).toList(); + + Future _updateTransactionsList() async { + await _history.refresh(); + final _transactions = await _history.getAll(); + await _setTransactions(_transactions); + } + + Future _onWalletChanged(Wallet wallet) async { + if (_onTransactionsChangeSubscription != null) { + _onTransactionsChangeSubscription.cancel(); + } + + if (_onAccountChangeSubscription != null) { + _onAccountChangeSubscription.cancel(); + } + + _history = wallet.getHistory(); + _onTransactionsChangeSubscription = _history.transactions + .listen((transactions) => _setTransactions(transactions)); + + if (wallet is MoneroWallet) { + _account = wallet.account; + _onAccountChangeSubscription = wallet.onAccountChange.listen((account) { + _account = account; + _updateTransactionsList(); + }); + } + + await _updateTransactionsList(); + } + + Future _setTransactions(List transactions) async { + final wallet = _walletService.currentWallet; + List sortedTransactions = transactions.map((transaction) { + if (transactionDescriptions.values.length > 0) { + final description = transactionDescriptions.values.firstWhere( + (desc) => desc.id == transaction.id, + orElse: () => null); + + if (description != null && description.recipientAddress != null) { + transaction.recipientAddress = description.recipientAddress; + } + } + + return transaction; + }).toList(); + + if (wallet is MoneroWallet) { + sortedTransactions = + transactions.where((tx) => tx.accountIndex == _account.id).toList(); + } + + this._transactions = sortedTransactions + .map((transaction) => TransactionListItem(transaction: transaction)) + .toList(); + } +} diff --git a/lib/src/stores/action_list/date_section_item.dart b/lib/src/stores/action_list/date_section_item.dart new file mode 100644 index 000000000..911887907 --- /dev/null +++ b/lib/src/stores/action_list/date_section_item.dart @@ -0,0 +1,7 @@ +import 'package:cake_wallet/src/stores/action_list/action_list_item.dart'; + +class DateSectionItem extends ActionListItem { + final DateTime date; + + DateSectionItem(this.date); +} \ No newline at end of file diff --git a/lib/src/stores/action_list/trade_filter_store.dart b/lib/src/stores/action_list/trade_filter_store.dart new file mode 100644 index 000000000..addaf9015 --- /dev/null +++ b/lib/src/stores/action_list/trade_filter_store.dart @@ -0,0 +1,53 @@ +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/src/stores/action_list/trade_list_item.dart'; + +part 'trade_filter_store.g.dart'; + +class TradeFilterStore = TradeFilterStoreBase with _$TradeFilterStore; + +abstract class TradeFilterStoreBase with Store { + @observable + bool displayXMRTO; + + @observable + bool displayChangeNow; + + WalletStore walletStore; + + TradeFilterStoreBase( + {this.displayXMRTO = true, + this.displayChangeNow = true, + this.walletStore}); + + @action + void toggleDisplayExchange(ExchangeProviderDescription provider) { + switch (provider) { + case ExchangeProviderDescription.changeNow: + displayChangeNow = !displayChangeNow; + break; + case ExchangeProviderDescription.xmrto: + displayXMRTO = !displayXMRTO; + break; + } + } + + List filtered({List trades}) { + List _trades = + trades.where((item) => item.trade.walletId == walletStore.id).toList(); + + final needToFilter = !displayChangeNow || !displayXMRTO; + + return needToFilter + ? trades + .where((item) => + (displayXMRTO && + item.trade.provider == ExchangeProviderDescription.xmrto) || + (displayChangeNow && + item.trade.provider == + ExchangeProviderDescription.changeNow)) + .toList() + : _trades; + } +} diff --git a/lib/src/stores/action_list/trade_list_item.dart b/lib/src/stores/action_list/trade_list_item.dart new file mode 100644 index 000000000..0cb9b3e9d --- /dev/null +++ b/lib/src/stores/action_list/trade_list_item.dart @@ -0,0 +1,10 @@ +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/stores/action_list/action_list_item.dart'; + +class TradeListItem extends ActionListItem { + final Trade trade; + + DateTime get date => trade.createdAt; + + TradeListItem({this.trade}); +} \ No newline at end of file diff --git a/lib/src/stores/action_list/transaction_filter_store.dart b/lib/src/stores/action_list/transaction_filter_store.dart new file mode 100644 index 000000000..9997930bd --- /dev/null +++ b/lib/src/stores/action_list/transaction_filter_store.dart @@ -0,0 +1,69 @@ +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/transaction_direction.dart'; +import 'package:cake_wallet/src/stores/action_list/transaction_list_item.dart'; + +part 'transaction_filter_store.g.dart'; + +class TransactionFilterStore = TransactionFilterStoreBase + with _$TransactionFilterStore; + +abstract class TransactionFilterStoreBase with Store { + @observable + bool displayIncoming; + + @observable + bool displayOutgoing; + + @observable + DateTime startDate; + + @observable + DateTime endDate; + + TransactionFilterStoreBase( + {this.displayIncoming = true, this.displayOutgoing = true}); + + @action + void toggleIncoming() => displayIncoming = !displayIncoming; + + @action + void toggleOutgoing() => displayOutgoing = !displayOutgoing; + + @action + void changeStartDate(DateTime date) => startDate = date; + + @action + void changeEndDate(DateTime date) => endDate = date; + + List filtered({List transactions}) { + List _transactions = []; + final needToFilter = !displayOutgoing || + !displayIncoming || + (startDate != null && endDate != null); + + if (needToFilter) { + _transactions = transactions.where((item) { + var allowed = true; + + if (allowed && startDate != null && endDate != null) { + allowed = startDate.isBefore(item.transaction.date) && + endDate.isAfter(item.transaction.date); + } + + if (allowed && (!displayOutgoing || !displayIncoming)) { + allowed = (displayOutgoing && + item.transaction.direction == + TransactionDirection.outgoing) || + (displayIncoming && + item.transaction.direction == TransactionDirection.incoming); + } + + return allowed; + }).toList(); + } else { + _transactions = transactions; + } + + return _transactions; + } +} diff --git a/lib/src/stores/action_list/transaction_list_item.dart b/lib/src/stores/action_list/transaction_list_item.dart new file mode 100644 index 000000000..c04a57179 --- /dev/null +++ b/lib/src/stores/action_list/transaction_list_item.dart @@ -0,0 +1,10 @@ +import 'package:cake_wallet/src/domain/common/transaction_info.dart'; +import 'package:cake_wallet/src/stores/action_list/action_list_item.dart'; + +class TransactionListItem extends ActionListItem { + final TransactionInfo transaction; + + DateTime get date => transaction.date; + + TransactionListItem({this.transaction}); +} \ No newline at end of file diff --git a/lib/src/stores/address_book/address_book_store.dart b/lib/src/stores/address_book/address_book_store.dart new file mode 100644 index 000000000..4cf33d9a4 --- /dev/null +++ b/lib/src/stores/address_book/address_book_store.dart @@ -0,0 +1,75 @@ +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/contact.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:hive/hive.dart'; + +part 'address_book_store.g.dart'; + +class AddressBookStore = AddressBookStoreBase with _$AddressBookStore; + +abstract class AddressBookStoreBase with Store { + @observable + List contactList; + + @observable + bool isValid; + + @observable + String errorMessage; + + Box contacts; + + AddressBookStoreBase({@required this.contacts}) { + updateContactList(); + } + + @action + Future add({Contact contact}) async => contacts.add(contact); + + @action + Future updateContactList() async => contactList = contacts.values.toList(); + + @action + Future update({Contact contact}) async => contact.save(); + + @action + Future delete({Contact contact}) async => await contact.delete(); + + void validateContactName(String value) { + String p = '''^[^`,'"]{1,32}\$'''; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + errorMessage = isValid ? null : S.current.error_text_contact_name; + } + + void validateAddress(String value, {CryptoCurrency cryptoCurrency}) { + // XMR (95), BTC (34), ETH (42), LTC (34), BCH (42), DASH (34) + String p = '^[0-9a-zA-Z]{95}\$|^[0-9a-zA-Z]{34}\$|^[0-9a-zA-Z]{42}\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + if (isValid && cryptoCurrency != null) { + switch (cryptoCurrency.toString()) { + case 'XMR': + isValid = (value.length == 95); + break; + case 'BTC': + isValid = (value.length == 34); + break; + case 'ETH': + isValid = (value.length == 42); + break; + case 'LTC': + isValid = (value.length == 34); + break; + case 'BCH': + isValid = (value.length == 42); + break; + case 'DASH': + isValid = (value.length == 34); + } + } + errorMessage = isValid ? null : S.current.error_text_address; + } +} diff --git a/lib/src/stores/auth/auth_state.dart b/lib/src/stores/auth/auth_state.dart new file mode 100644 index 000000000..1361fc75e --- /dev/null +++ b/lib/src/stores/auth/auth_state.dart @@ -0,0 +1,20 @@ +abstract class AuthState {} + +class AuthenticationStateInitial extends AuthState {} + +class AuthenticationInProgress extends AuthState {} + +class AuthenticatedSuccessfully extends AuthState {} + +class AuthenticationFailure extends AuthState { + final String error; + + AuthenticationFailure({this.error}); +} + +class AuthenticationBanned extends AuthState { + final String error; + + AuthenticationBanned({this.error}); +} + diff --git a/lib/src/stores/auth/auth_store.dart b/lib/src/stores/auth/auth_store.dart new file mode 100644 index 000000000..1a8ba95be --- /dev/null +++ b/lib/src/stores/auth/auth_store.dart @@ -0,0 +1,94 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/services/user_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/stores/auth/auth_state.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'auth_store.g.dart'; + +class AuthStore = AuthStoreBase with _$AuthStore; + +abstract class AuthStoreBase with Store { + static const maxFailedLogins = 3; + static const banTimeout = 180; // 3 mins + final banTimeoutKey = S.current.auth_store_ban_timeout; + + final UserService userService; + final WalletService walletService; + + final SharedPreferences sharedPreferences; + + @observable + AuthState state; + + @observable + int _failureCounter; + + AuthStoreBase( + {@required this.userService, + @required this.walletService, + @required this.sharedPreferences}) { + state = AuthenticationStateInitial(); + _failureCounter = 0; + } + + @action + Future auth({String password}) async { + state = AuthenticationStateInitial(); + final _banDuration = banDuration(); + + if (_banDuration != null) { + state = AuthenticationBanned( + error: S.current.auth_store_banned_for + '${_banDuration.inMinutes}' + S.current.auth_store_banned_minutes); + return; + } + + state = AuthenticationInProgress(); + final isAuth = await userService.authenticate(password); + + if (isAuth) { + state = AuthenticatedSuccessfully(); + _failureCounter = 0; + } else { + _failureCounter += 1; + + if (_failureCounter >= maxFailedLogins) { + final banDuration = await ban(); + state = AuthenticationBanned( + error: S.current.auth_store_banned_for + '${banDuration.inMinutes}' + S.current.auth_store_banned_minutes); + return; + } + + state = AuthenticationFailure(error: S.current.auth_store_incorrect_password); + } + } + + Duration banDuration() { + final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey); + + if (unbanTimestamp == null) { + return null; + } + + final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp); + final now = DateTime.now(); + + if (now.isAfter(unbanTime)) { + return null; + } + + return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch); + } + + Future ban() async { + final multiplier = _failureCounter - maxFailedLogins + 1; + final timeout = (multiplier * banTimeout) * 1000; + final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout; + await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp); + + return Duration(milliseconds: timeout); + } +} diff --git a/lib/src/stores/authentication/authentication_store.dart b/lib/src/stores/authentication/authentication_store.dart new file mode 100644 index 000000000..fe822cc8f --- /dev/null +++ b/lib/src/stores/authentication/authentication_store.dart @@ -0,0 +1,70 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/services/user_service.dart'; + +part 'authentication_store.g.dart'; + +class AuthenticationStore = AuthenticationStoreBase with _$AuthenticationStore; + +enum AuthenticationState { + uninitialized, + allowed, + denied, + authenticated, + unauthenticated, + active, + loading, + created, + restored, + readyToLogin +} + +abstract class AuthenticationStoreBase with Store { + final UserService userService; + + @observable + AuthenticationState state; + + @observable + String errorMessage; + + AuthenticationStoreBase({@required this.userService}) { + state = AuthenticationState.uninitialized; + } + + Future started() async { + final canAuth = await userService.canAuthenticate(); + state = canAuth ? AuthenticationState.allowed : AuthenticationState.denied; + } + + @action + void created() { + state = AuthenticationState.created; + } + + @action + void restored() { + state = AuthenticationState.restored; + } + + @action + void loggedIn() { + state = AuthenticationState.authenticated; + } + + @action + void inactive() { + state = AuthenticationState.unauthenticated; + } + + @action + void active() { + state = AuthenticationState.active; + } + + @action + void loggedOut() { + state = AuthenticationState.uninitialized; + } +} diff --git a/lib/src/stores/balance/balance_store.dart b/lib/src/stores/balance/balance_store.dart new file mode 100644 index 000000000..da7a4b4e3 --- /dev/null +++ b/lib/src/stores/balance/balance_store.dart @@ -0,0 +1,120 @@ +import 'dart:async'; +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/common/balance.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/domain/monero/monero_balance.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/common/calculate_fiat_amount.dart'; +import 'package:cake_wallet/src/stores/price/price_store.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; + +part 'balance_store.g.dart'; + +class BalanceStore = BalanceStoreBase with _$BalanceStore; + +abstract class BalanceStoreBase with Store { + @observable + String fullBalance; + + @observable + String unlockedBalance; + + @computed + String get fiatFullBalance { + if (fullBalance == null) { + return '0.00'; + } + + final symbol = PriceStoreBase.generateSymbolForPair( + fiat: _settingsStore.fiatCurrency, crypto: CryptoCurrency.xmr); + final price = _priceStore.prices[symbol]; + return calculateFiatAmount(price: price, cryptoAmount: fullBalance); + } + + @computed + String get fiatUnlockedBalance { + if (unlockedBalance == null) { + return '0.00'; + } + + final symbol = PriceStoreBase.generateSymbolForPair( + fiat: _settingsStore.fiatCurrency, crypto: CryptoCurrency.xmr); + final price = _priceStore.prices[symbol]; + return calculateFiatAmount(price: price, cryptoAmount: unlockedBalance); + } + + @observable + bool isReversing; + + WalletService _walletService; + StreamSubscription _onWalletChangeSubscription; + StreamSubscription _onBalanceChangeSubscription; + SettingsStore _settingsStore; + PriceStore _priceStore; + + BalanceStoreBase( + {String fullBalance = '0.0', + String unlockedBalance = '0.0', + @required WalletService walletService, + @required SettingsStore settingsStore, + @required PriceStore priceStore}) { + fullBalance = fullBalance; + unlockedBalance = unlockedBalance; + isReversing = false; + _walletService = walletService; + _settingsStore = settingsStore; + _priceStore = priceStore; + + if (_walletService.currentWallet != null) { + _onWalletChanged(_walletService.currentWallet); + } + + _onWalletChangeSubscription = _walletService.onWalletChange + .listen((wallet) => _onWalletChanged(wallet)); + } + + @override + void dispose() { + _onWalletChangeSubscription.cancel(); + + if (_onBalanceChangeSubscription != null) { + _onBalanceChangeSubscription.cancel(); + } + + super.dispose(); + } + + Future _onBalanceChange(Balance balance) async { + final _balance = balance as MoneroBalance; + + if (this.fullBalance != _balance.fullBalance) { + this.fullBalance = _balance.fullBalance; + } + + if (this.unlockedBalance != _balance.unlockedBalance) { + this.unlockedBalance = _balance.unlockedBalance; + } + } + + Future _onWalletChanged(Wallet wallet) async { + if (_onBalanceChangeSubscription != null) { + _onBalanceChangeSubscription.cancel(); + } + + _onBalanceChangeSubscription = _walletService.onBalanceChange + .listen((balance) async => await _onBalanceChange(balance)); + + await _updateBalances(wallet); + } + + Future _updateBalances(Wallet wallet) async { + if (wallet == null) { + return; + } + + fullBalance = await _walletService.getFullBalance(); + unlockedBalance = await _walletService.getUnlockedBalance(); + } +} diff --git a/lib/src/stores/exchange/exchange_store.dart b/lib/src/stores/exchange/exchange_store.dart new file mode 100644 index 000000000..ead674423 --- /dev/null +++ b/lib/src/stores/exchange/exchange_store.dart @@ -0,0 +1,266 @@ +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/exchange/changenow/changenow_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/changenow/changenow_request.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_trade_request.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; +import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart'; +import 'package:cake_wallet/src/stores/exchange/limits_state.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'exchange_store.g.dart'; + +class ExchangeStore = ExchangeStoreBase with _$ExchangeStore; + +abstract class ExchangeStoreBase with Store { + @observable + ExchangeProvider provider; + + @observable + List providerList; + + @observable + CryptoCurrency depositCurrency; + + @observable + CryptoCurrency receiveCurrency; + + @observable + LimitsState limitsState; + + @observable + ExchangeTradeState tradeState; + + @observable + String depositAmount; + + @observable + String receiveAmount; + + @observable + bool isValid; + + @observable + String errorMessage; + + Box trades; + + String depositAddress; + + String receiveAddress; + + WalletStore walletStore; + + ExchangeStoreBase( + {@required ExchangeProvider initialProvider, + @required CryptoCurrency initialDepositCurrency, + @required CryptoCurrency initialReceiveCurrency, + @required this.providerList, + @required this.trades, + @required this.walletStore}) { + provider = initialProvider; + depositCurrency = initialDepositCurrency; + receiveCurrency = initialReceiveCurrency; + limitsState = LimitsInitialState(); + tradeState = ExchangeTradeStateInitial(); + loadLimits(); + } + + @action + void changeProvider({ExchangeProvider provider}) { + this.provider = provider; + depositAmount = ''; + receiveAmount = ''; + } + + @action + void changeDepositCurrency({CryptoCurrency currency}) { + depositCurrency = currency; + _onPairChange(); + } + + @action + void changeReceiveCurrency({CryptoCurrency currency}) { + receiveCurrency = currency; + _onPairChange(); + } + + @action + void changeReceiveAmount({String amount}) { + receiveAmount = amount; + + if (amount == null || amount.isEmpty) { + depositAmount = ''; + return; + } + + final _amount = double.parse(amount) ?? 0; + + provider + .calculateAmount( + from: depositCurrency, to: receiveCurrency, amount: _amount) + .then((amount) => amount.toString()) + .then((amount) => depositAmount = amount); + } + + @action + void changeDepositAmount({String amount}) { + depositAmount = amount; + + if (amount == null || amount.isEmpty) { + depositAmount = ''; + return; + } + + final _amount = double.parse(amount); + provider + .calculateAmount( + from: depositCurrency, to: receiveCurrency, amount: _amount) + .then((amount) => amount.toString()) + .then((amount) => receiveAmount = amount); + } + + @action + Future loadLimits() async { + limitsState = LimitsIsLoading(); + + try { + final limits = await provider.fetchLimits( + from: depositCurrency, to: receiveCurrency); + limitsState = LimitsLoadedSuccessfully(limits: limits); + } catch (e) { + limitsState = LimitsLoadedFailure(error: e.toString()); + } + } + + @action + Future createTrade() async { + TradeRequest request; + + if (provider is XMRTOExchangeProvider) { + request = XMRTOTradeRequest( + to: receiveCurrency, + from: depositCurrency, + amount: receiveAmount, + address: receiveAddress, + refundAddress: depositAddress); + } + + if (provider is ChangeNowExchangeProvider) { + request = ChangeNowRequest( + from: depositCurrency, + to: receiveCurrency, + amount: depositAmount, + refundAddress: depositAddress, + address: receiveAddress); + } + + try { + tradeState = TradeIsCreating(); + final trade = await provider.createTrade(request: request); + trade.walletId = walletStore.id; + await trades.add(trade); + tradeState = TradeIsCreatedSuccessfully(trade: trade); + } catch (e) { + tradeState = TradeIsCreatedFailure(error: e.toString()); + } + } + + @action + void reset() { + depositAmount = ''; + receiveAmount = ''; + depositAddress = ''; + receiveAddress = ''; + provider = XMRTOExchangeProvider(); + depositCurrency = CryptoCurrency.xmr; + receiveCurrency = CryptoCurrency.btc; + } + + List providersForCurrentPair() { + return _providersForPair(from: depositCurrency, to: receiveCurrency); + } + + List _providersForPair( + {CryptoCurrency from, CryptoCurrency to}) { + final providers = providerList + .where((provider) => + provider.pairList + .where((pair) => + pair.from == depositCurrency && pair.to == receiveCurrency) + .length > + 0) + .toList(); + + return providers; + } + + void _onPairChange() { + final isPairExist = provider.pairList + .where((pair) => + pair.from == depositCurrency && pair.to == receiveCurrency) + .length > + 0; + + if (!isPairExist) { + final provider = + _providerForPair(from: depositCurrency, to: receiveCurrency); + + if (provider != null) { + changeProvider(provider: provider); + } + } + + depositAmount = ''; + receiveAmount = ''; + + loadLimits(); + } + + ExchangeProvider _providerForPair({CryptoCurrency from, CryptoCurrency to}) { + final providers = _providersForPair(from: from, to: to); + return providers.length > 0 ? providers[0] : null; + } + + void validateAddress(String value, {CryptoCurrency cryptoCurrency}) { + // XMR (95), BTC (34), ETH (42), LTC (34), BCH (42), DASH (34) + String p = '^[0-9a-zA-Z]{95}\$|^[0-9a-zA-Z]{34}\$|^[0-9a-zA-Z]{42}\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + if (isValid && cryptoCurrency != null) { + switch (cryptoCurrency.toString()) { + case 'XMR': + isValid = (value.length == 95); + break; + case 'BTC': + isValid = (value.length == 34); + break; + case 'ETH': + isValid = (value.length == 42); + break; + case 'LTC': + isValid = (value.length == 34); + break; + case 'BCH': + isValid = (value.length == 42); + break; + case 'DASH': + isValid = (value.length == 34); + } + } + errorMessage = isValid ? null : S.current.error_text_address; + } + + void validateCryptoCurrency(String value) { + String p = '^([0-9]+([.][0-9]{0,12})?|[.][0-9]{1,12})\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + errorMessage = isValid ? null : S.current.error_text_crypto_currency; + } +} diff --git a/lib/src/stores/exchange/exchange_trade_state.dart b/lib/src/stores/exchange/exchange_trade_state.dart new file mode 100644 index 000000000..eb171ca28 --- /dev/null +++ b/lib/src/stores/exchange/exchange_trade_state.dart @@ -0,0 +1,20 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; + +abstract class ExchangeTradeState {} + +class ExchangeTradeStateInitial extends ExchangeTradeState {} + +class TradeIsCreating extends ExchangeTradeState {} + +class TradeIsCreatedSuccessfully extends ExchangeTradeState { + final Trade trade; + + TradeIsCreatedSuccessfully({@required this.trade}); +} + +class TradeIsCreatedFailure extends ExchangeTradeState { + final String error; + + TradeIsCreatedFailure({@required this.error}); +} \ No newline at end of file diff --git a/lib/src/stores/exchange/limits_state.dart b/lib/src/stores/exchange/limits_state.dart new file mode 100644 index 000000000..3ca340c1b --- /dev/null +++ b/lib/src/stores/exchange/limits_state.dart @@ -0,0 +1,20 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/exchange/limits.dart'; + +abstract class LimitsState {} + +class LimitsInitialState extends LimitsState {} + +class LimitsIsLoading extends LimitsState {} + +class LimitsLoadedSuccessfully extends LimitsState { + final Limits limits; + + LimitsLoadedSuccessfully({@required this.limits}); +} + +class LimitsLoadedFailure extends LimitsState { + final String error; + + LimitsLoadedFailure({@required this.error}); +} diff --git a/lib/src/stores/exchange_trade/exchange_trade_store.dart b/lib/src/stores/exchange_trade/exchange_trade_store.dart new file mode 100644 index 000000000..5876acd7b --- /dev/null +++ b/lib/src/stores/exchange_trade/exchange_trade_store.dart @@ -0,0 +1,65 @@ +import 'dart:async'; +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/exchange/trade.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/changenow/changenow_exchange_provider.dart'; +import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart'; +import 'package:cake_wallet/src/stores/wallet/wallet_store.dart'; + +part 'exchange_trade_store.g.dart'; + +class ExchangeTradeStore = ExchangeTradeStoreBase with _$ExchangeTradeStore; + +abstract class ExchangeTradeStoreBase with Store { + @observable + Trade trade; + + @observable + bool isSendable; + + ExchangeProvider _provider; + + Timer _timer; + + ExchangeTradeStoreBase({@required this.trade, @required WalletStore walletStore}) { + isSendable = trade.from == walletStore.type || trade.provider == ExchangeProviderDescription.xmrto; + + switch (trade.provider) { + case ExchangeProviderDescription.xmrto: + _provider = XMRTOExchangeProvider(); + break; + case ExchangeProviderDescription.changeNow: + _provider = ChangeNowExchangeProvider(); + break; + } + + _updateTrade(); + _timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade()); + } + + @override + void dispose() { + super.dispose(); + + if (_timer != null) { + _timer.cancel(); + } + } + + @action + Future _updateTrade() async { + try { + final updatedTrade = await _provider.findTradeById(id: trade.id); + + if (updatedTrade.createdAt == null && trade.createdAt != null) { + updatedTrade.createdAt = trade.createdAt; + } + + trade = updatedTrade; + } catch (e) { + print(e.toString()); + } + } +} diff --git a/lib/src/stores/login/login_store.dart b/lib/src/stores/login/login_store.dart new file mode 100644 index 000000000..20e26b815 --- /dev/null +++ b/lib/src/stores/login/login_store.dart @@ -0,0 +1,49 @@ +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; + +part 'login_store.g.dart'; + +abstract class LoginState {} + +class InitialLoginState extends LoginState {} + +class LoadingCurrentWallet extends LoginState {} + +class LoadedCurrentWalletSuccessfully extends LoginState {} + +class LoadedCurrentWalletFailure extends LoginState { + final String errorMessage; + + LoadedCurrentWalletFailure({this.errorMessage}); +} + +class LoginStore = LoginStoreBase with _$LoginStore; + +abstract class LoginStoreBase with Store { + final SharedPreferences sharedPreferences; + final WalletListService walletsService; + + @observable + LoginState state; + + LoginStoreBase( + {@required this.sharedPreferences, @required this.walletsService}) { + state = InitialLoginState(); + } + + @action + Future loadCurrentWallet() async { + state = InitialLoginState(); + + try { + state = LoadingCurrentWallet(); + final walletName = sharedPreferences.getString('current_wallet_name'); + await walletsService.openWallet(walletName); + state = LoadedCurrentWalletSuccessfully(); + } catch (e) { + state = LoadedCurrentWalletFailure(errorMessage: e.toString()); + } + } +} diff --git a/lib/src/stores/node_list/node_list_store.dart b/lib/src/stores/node_list/node_list_store.dart new file mode 100644 index 000000000..45b4546ef --- /dev/null +++ b/lib/src/stores/node_list/node_list_store.dart @@ -0,0 +1,95 @@ +import 'dart:async'; +import 'package:mobx/mobx.dart'; +import 'package:hive/hive.dart'; +import 'package:cake_wallet/src/domain/common/node.dart'; +import 'package:cake_wallet/src/domain/common/node_list.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'node_list_store.g.dart'; + +class NodeListStore = NodeListBase with _$NodeListStore; + +abstract class NodeListBase with Store { + @observable + ObservableList nodes; + + @observable + bool isValid; + + @observable + String errorMessage; + + Box nodesSource; + + StreamSubscription _onNodesChangeSubscription; + + NodeListBase({this.nodesSource}) { + nodes = ObservableList(); + _onNodesChangeSubscription = nodesSource.watch().listen((e) => update()); + update(); + } + + @override + void dispose() { + super.dispose(); + + if (_onNodesChangeSubscription != null) { + _onNodesChangeSubscription.cancel(); + } + } + + @action + update() async => + nodes.replaceRange(0, nodes.length, nodesSource.values.toList()); + + @action + Future addNode( + {String address, String port, String login, String password}) async { + var uri = address; + + if (port != null && port.isNotEmpty) { + uri += ':' + port; + } + + final node = Node(uri: uri, login: login, password: password); + await nodesSource.add(node); + } + + @action + Future remove({Node node}) async => await node.delete(); + + @action + Future reset() async => await resetToDefault(nodesSource); + + Future isNodeOnline(Node node) async { + try { + return await node.requestNode(node.uri, + login: node.login, password: node.password); + } catch (e) { + return false; + } + } + + void validateNodeAddress(String value) { + String p = + '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + errorMessage = isValid ? null : S.current.error_text_node_address; + } + + void validateNodePort(String value) { + String p = '^[0-9]{1,5}'; + RegExp regExp = new RegExp(p); + if (regExp.hasMatch(value)) { + try { + int intValue = int.parse(value); + isValid = (intValue >= 0 && intValue <= 65535); + } catch (e) { + isValid = false; + } + } else + isValid = false; + errorMessage = isValid ? null : S.current.error_text_node_port; + } +} diff --git a/lib/src/stores/price/price_store.dart b/lib/src/stores/price/price_store.dart new file mode 100644 index 000000000..631eca42b --- /dev/null +++ b/lib/src/stores/price/price_store.dart @@ -0,0 +1,32 @@ +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/common/fetch_price.dart'; +import 'package:cake_wallet/src/domain/common/fiat_currency.dart'; + +part 'price_store.g.dart'; + +class PriceStore = PriceStoreBase with _$PriceStore; + +abstract class PriceStoreBase with Store { + static String generateSymbolForPair( + {FiatCurrency fiat, CryptoCurrency crypto}) => + crypto.toString().toUpperCase() + fiat.toString().toUpperCase(); + + @observable + ObservableMap prices; + + PriceStoreBase() : prices = ObservableMap(); + + @action + Future updatePrice({FiatCurrency fiat, CryptoCurrency crypto}) async { + final symbol = generateSymbolForPair(fiat: fiat, crypto: crypto); + final price = await fetchPriceFor(fiat: fiat, crypto: crypto); + prices[symbol] = price; + } + + @action + changePriceForPair({FiatCurrency fiat, CryptoCurrency crypto, double price}) { + final symbol = generateSymbolForPair(fiat: fiat, crypto: crypto); + prices[symbol] = price; + } +} diff --git a/lib/src/stores/rescan/rescan_wallet_store.dart b/lib/src/stores/rescan/rescan_wallet_store.dart new file mode 100644 index 000000000..a2c440405 --- /dev/null +++ b/lib/src/stores/rescan/rescan_wallet_store.dart @@ -0,0 +1,30 @@ +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; + +part 'rescan_wallet_store.g.dart'; + +class RescanWalletStore = RescanWalletStoreBase with _$RescanWalletStore; + +enum RescanWalletState { + rescaning, none +} + +abstract class RescanWalletStoreBase with Store { + @observable + RescanWalletState state; + + WalletService _walletService; + + RescanWalletStoreBase({@required WalletService walletService}) { + _walletService = walletService; + state = RescanWalletState.none; + } + + @action + Future rescanCurrentWallet({int restoreHeight}) async { + state = RescanWalletState.rescaning; + await _walletService.rescan(restoreHeight: restoreHeight); + state = RescanWalletState.none; + } +} diff --git a/lib/src/stores/send/send_store.dart b/lib/src/stores/send/send_store.dart new file mode 100644 index 000000000..bb45af0d8 --- /dev/null +++ b/lib/src/stores/send/send_store.dart @@ -0,0 +1,245 @@ +import 'package:hive/hive.dart'; +import 'package:intl/intl.dart'; +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/domain/common/pending_transaction.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/domain/monero/monero_transaction_creation_credentials.dart'; +import 'package:cake_wallet/src/domain/monero/transaction_description.dart'; +import 'package:cake_wallet/src/stores/price/price_store.dart'; +import 'package:cake_wallet/src/stores/send/sending_state.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'send_store.g.dart'; + +class SendStore = SendStoreBase with _$SendStore; + +abstract class SendStoreBase with Store { + WalletService walletService; + SettingsStore settingsStore; + PriceStore priceStore; + Box transactionDescriptions; + + @observable + SendingState state; + + @observable + String fiatAmount; + + @observable + String cryptoAmount; + + @observable + bool isValid; + + @observable + String errorMessage; + + PendingTransaction get pendingTransaction => _pendingTransaction; + PendingTransaction _pendingTransaction; + NumberFormat _cryptoNumberFormat; + NumberFormat _fiatNumberFormat; + String _lastRecipientAddress; + + SendStoreBase( + {@required this.walletService, + this.settingsStore, + this.transactionDescriptions, + this.priceStore}) { + state = SendingStateInitial(); + _pendingTransaction = null; + _cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12; + _fiatNumberFormat = NumberFormat()..maximumFractionDigits = 2; + + reaction((_) => this.state, (state) async { + if (state is TransactionCreatedSuccessfully) { + commitTransaction(); + } + }); + } + + @action + Future createTransaction( + {String address, String paymentId, String amount}) async { + state = CreatingTransaction(); + + try { + final _amount = amount != null + ? amount + : cryptoAmount == S.current.all + ? null + : cryptoAmount.replaceAll(',', '.'); + final credentials = MoneroTransactionCreationCredentials( + address: address, + paymentId: paymentId ?? '', + amount: _amount, + priority: settingsStore.transactionPriority); + + _pendingTransaction = await walletService.createTransaction(credentials); + state = TransactionCreatedSuccessfully(); + _lastRecipientAddress = address; + } catch (e) { + state = SendingFailed(error: e.toString()); + } + } + + @action + Future commitTransaction() async { + try { + final transactionId = _pendingTransaction.hash; + await _pendingTransaction.commit(); + + if (settingsStore.shouldSaveRecipientAddress) { + await transactionDescriptions.add(TransactionDescription( + id: transactionId, recipientAddress: _lastRecipientAddress)); + } + } catch (e) { + state = SendingFailed(error: e.toString()); + } + + _pendingTransaction = null; + } + + @action + void setSendAll() { + cryptoAmount = 'ALL'; + fiatAmount = ''; + } + + @action + void changeCryptoAmount(String amount) { + cryptoAmount = amount; + + if (cryptoAmount != null && cryptoAmount.isNotEmpty) { + _calculateFiatAmount(); + } else { + fiatAmount = ''; + } + } + + @action + void changeFiatAmount(String amount) { + fiatAmount = amount; + + if (fiatAmount != null && fiatAmount.isNotEmpty) { + _calculateCryptoAmount(); + } else { + cryptoAmount = ''; + } + } + + @action + Future _calculateFiatAmount() async { + final symbol = PriceStoreBase.generateSymbolForPair( + fiat: settingsStore.fiatCurrency, crypto: CryptoCurrency.xmr); + final price = priceStore.prices[symbol] ?? 0; + + try { + final amount = double.parse(cryptoAmount) * price; + fiatAmount = _fiatNumberFormat.format(amount); + } catch (e) { + fiatAmount = '0.00'; + } + } + + @action + Future _calculateCryptoAmount() async { + final symbol = PriceStoreBase.generateSymbolForPair( + fiat: settingsStore.fiatCurrency, crypto: CryptoCurrency.xmr); + final price = priceStore.prices[symbol] ?? 0; + + try { + final amount = double.parse(fiatAmount) / price; + cryptoAmount = _cryptoNumberFormat.format(amount); + } catch (e) { + cryptoAmount = '0.00'; + } + } + + void validateAddress(String value, {CryptoCurrency cryptoCurrency}) { + // XMR (95), BTC (34), ETH (42), LTC (34), BCH (42), DASH (34) + String p = '^[0-9a-zA-Z]{95}\$|^[0-9a-zA-Z]{34}\$|^[0-9a-zA-Z]{42}\$'; + RegExp regExp = new RegExp(p); + isValid = value == null ? false : regExp.hasMatch(value); + if (isValid && cryptoCurrency != null) { + switch (cryptoCurrency.toString()) { + case 'XMR': + isValid = (value.length == 95); + break; + case 'BTC': + isValid = (value.length == 34); + break; + case 'ETH': + isValid = (value.length == 42); + break; + case 'LTC': + isValid = (value.length == 34); + break; + case 'BCH': + isValid = (value.length == 42); + break; + case 'DASH': + isValid = (value.length == 34); + } + } + errorMessage = isValid ? null : S.current.error_text_address; + } + + void validatePaymentID(String value) { + if (value.isEmpty) { + isValid = true; + } else { + String p = '^[A-Fa-f0-9]{16,64}\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + } + errorMessage = isValid ? null : S.current.error_text_payment_id; + } + + void validateXMR(String value, String availableBalance) { + const double maxValue = 18446744.073709551616; + String p = '^([0-9]+([.][0-9]{0,12})?|[.][0-9]{1,12})\$|ALL'; + RegExp regExp = new RegExp(p); + if (regExp.hasMatch(value)) { + if (value == 'ALL') + isValid = true; + else { + try { + double dValue = double.parse(value); + double maxAvailable = double.parse(availableBalance); + isValid = + (dValue <= maxAvailable && dValue <= maxValue && dValue > 0); + } catch (e) { + isValid = false; + } + } + } else + isValid = false; + errorMessage = isValid ? null : S.current.error_text_xmr; + } + + void validateFiat(String value, {double maxValue}) { + const double minValue = 0.01; + if (value.isEmpty && cryptoAmount == 'ALL') + isValid = true; + else { + String p = '^([0-9]+([.][0-9]{0,2})?|[.][0-9]{1,2})\$'; + RegExp regExp = new RegExp(p); + if (regExp.hasMatch(value)) { + try { + double dValue = double.parse(value); + isValid = (dValue >= minValue && dValue <= maxValue); + } catch (e) { + isValid = false; + } + } else + isValid = false; + } + errorMessage = isValid + ? null + : "Value of amount can't exceed available balance.\n" + "The number of fraction digits must be less or equal to 2"; + } +} diff --git a/lib/src/stores/send/sending_state.dart b/lib/src/stores/send/sending_state.dart new file mode 100644 index 000000000..f9d121e7e --- /dev/null +++ b/lib/src/stores/send/sending_state.dart @@ -0,0 +1,19 @@ +import 'package:flutter/foundation.dart'; + +abstract class SendingState {} + +class SendingStateInitial extends SendingState {} + +class CreatingTransaction extends SendingState {} + +class TransactionCreatedSuccessfully extends SendingState {} + +class TransactionCommiting extends SendingState {} + +class TransactionCommitted extends SendingState {} + +class SendingFailed extends SendingState { + String error; + + SendingFailed({@required this.error}); +} diff --git a/lib/src/stores/settings/settings_store.dart b/lib/src/stores/settings/settings_store.dart new file mode 100644 index 000000000..06b961e0a --- /dev/null +++ b/lib/src/stores/settings/settings_store.dart @@ -0,0 +1,269 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:hive/hive.dart'; +import 'package:cake_wallet/src/domain/common/node.dart'; +import 'package:cake_wallet/src/domain/common/balance_display_mode.dart'; +import 'package:cake_wallet/src/domain/common/fiat_currency.dart'; +import 'package:cake_wallet/src/domain/common/transaction_priority.dart'; +import 'package:cake_wallet/src/stores/action_list/action_list_display_mode.dart'; +import 'package:cake_wallet/src/screens/settings/items/item_headers.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'settings_store.g.dart'; + +class SettingsStore = SettingsStoreBase with _$SettingsStore; + +abstract class SettingsStoreBase with Store { + static const currentNodeIdKey = 'current_node_id'; + static const currentFiatCurrencyKey = 'current_fiat_currency'; + static const currentTransactionPriorityKey = 'current_fee_priority'; + static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; + static const shouldSaveRecipientAddressKey = 'save_recipient_address'; + static const allowBiometricalAuthenticationKey = + 'allow_biometrical_authentication'; + static const currentDarkTheme = 'dark_theme'; + static const displayActionListModeKey = 'display_list_mode'; + static const currentPinLength = 'current_pin_length'; + static const currentLanguageCode = 'language_code'; + + static Future load( + {@required SharedPreferences sharedPreferences, + @required Box nodes, + @required FiatCurrency initialFiatCurrency, + @required TransactionPriority initialTransactionPriority, + @required BalanceDisplayMode initialBalanceDisplayMode}) async { + final currentFiatCurrency = FiatCurrency( + symbol: sharedPreferences.getString(currentFiatCurrencyKey)); + final currentTransactionPriority = TransactionPriority.deserialize( + raw: sharedPreferences.getInt(currentTransactionPriorityKey)); + final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( + raw: sharedPreferences.getInt(currentBalanceDisplayModeKey)); + final shouldSaveRecipientAddress = + sharedPreferences.getBool(shouldSaveRecipientAddressKey); + final allowBiometricalAuthentication = + sharedPreferences.getBool(allowBiometricalAuthenticationKey) == null + ? false + : sharedPreferences.getBool(allowBiometricalAuthenticationKey); + final savedDarkTheme = sharedPreferences.getBool(currentDarkTheme) == null + ? false + : sharedPreferences.getBool(currentDarkTheme); + final actionlistDisplayMode = ObservableList(); + actionlistDisplayMode.addAll(deserializeActionlistDisplayModes( + sharedPreferences.getInt(displayActionListModeKey) ?? 11)); + final defaultPinLength = sharedPreferences.getInt(currentPinLength) == null + ? 4 + : sharedPreferences.getInt(currentPinLength); + final savedLanguageCode = + sharedPreferences.getString(currentLanguageCode) == null + ? 'en' + : sharedPreferences.getString(currentLanguageCode); + + final store = SettingsStore( + sharedPreferences: sharedPreferences, + nodes: nodes, + initialFiatCurrency: currentFiatCurrency, + initialTransactionPriority: currentTransactionPriority, + initialBalanceDisplayMode: currentBalanceDisplayMode, + initialSaveRecipientAddress: shouldSaveRecipientAddress, + initialAllowBiometricalAuthentication: allowBiometricalAuthentication, + initialDarkTheme: savedDarkTheme, + actionlistDisplayMode: actionlistDisplayMode, + initialPinLength: defaultPinLength, + initialLanguageCode: savedLanguageCode); + + await store.loadSettings(); + + return store; + } + + @observable + Node node; + + @observable + FiatCurrency fiatCurrency; + + @observable + ObservableList actionlistDisplayMode; + + @observable + TransactionPriority transactionPriority; + + @observable + BalanceDisplayMode balanceDisplayMode; + + @observable + bool shouldSaveRecipientAddress; + + @observable + bool allowBiometricalAuthentication; + + @observable + bool isDarkTheme; + + @observable + int defaultPinLength; + String languageCode; + + @observable + Map itemHeaders; + + SharedPreferences _sharedPreferences; + Box _nodes; + + SettingsStoreBase( + {@required SharedPreferences sharedPreferences, + @required Box nodes, + @required FiatCurrency initialFiatCurrency, + @required TransactionPriority initialTransactionPriority, + @required BalanceDisplayMode initialBalanceDisplayMode, + @required bool initialSaveRecipientAddress, + @required bool initialAllowBiometricalAuthentication, + @required bool initialDarkTheme, + this.actionlistDisplayMode, + @required int initialPinLength, + @required String initialLanguageCode}) { + fiatCurrency = initialFiatCurrency; + transactionPriority = initialTransactionPriority; + balanceDisplayMode = initialBalanceDisplayMode; + shouldSaveRecipientAddress = initialSaveRecipientAddress; + _sharedPreferences = sharedPreferences; + _nodes = nodes; + allowBiometricalAuthentication = initialAllowBiometricalAuthentication; + isDarkTheme = initialDarkTheme; + defaultPinLength = initialPinLength; + languageCode = initialLanguageCode; + itemHeaders = Map(); + + actionlistDisplayMode.observe( + (dynamic _) => _sharedPreferences.setInt(displayActionListModeKey, + serializeActionlistDisplayModes(actionlistDisplayMode)), + fireImmediately: false); + } + + @action + Future setAllowBiometricalAuthentication( + {@required bool allowBiometricalAuthentication}) async { + this.allowBiometricalAuthentication = allowBiometricalAuthentication; + await _sharedPreferences.setBool( + allowBiometricalAuthenticationKey, allowBiometricalAuthentication); + } + + @action + Future saveDarkTheme({@required bool isDarkTheme}) async { + this.isDarkTheme = isDarkTheme; + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + statusBarColor: isDarkTheme ? Colors.black : Colors.white)); + await _sharedPreferences.setBool(currentDarkTheme, isDarkTheme); + } + + @action + Future saveLanguageCode({@required String languageCode}) async { + this.languageCode = languageCode; + await _sharedPreferences.setString(currentLanguageCode, languageCode); + } + + @action + Future setCurrentNode({@required Node node}) async { + this.node = node; + await _sharedPreferences.setInt(currentNodeIdKey, node.key); + } + + @action + Future setCurrentFiatCurrency({@required FiatCurrency currency}) async { + this.fiatCurrency = currency; + await _sharedPreferences.setString( + currentFiatCurrencyKey, fiatCurrency.serialize()); + } + + @action + Future setCurrentTransactionPriority( + {@required TransactionPriority priority}) async { + this.transactionPriority = priority; + await _sharedPreferences.setInt( + currentTransactionPriorityKey, priority.serialize()); + } + + @action + Future setCurrentBalanceDisplayMode( + {@required BalanceDisplayMode balanceDisplayMode}) async { + this.balanceDisplayMode = balanceDisplayMode; + await _sharedPreferences.setInt( + currentBalanceDisplayModeKey, balanceDisplayMode.serialize()); + } + + @action + Future setSaveRecipientAddress( + {@required bool shouldSaveRecipientAddress}) async { + this.shouldSaveRecipientAddress = shouldSaveRecipientAddress; + await _sharedPreferences.setBool( + shouldSaveRecipientAddressKey, shouldSaveRecipientAddress); + } + + Future loadSettings() async => node = await _fetchCurrentNode(); + + @action + void toggleTransactionsDisplay() => + actionlistDisplayMode.contains(ActionListDisplayMode.transactions) + ? _hideTransaction() + : _showTransaction(); + + @action + void toggleTradesDisplay() => + actionlistDisplayMode.contains(ActionListDisplayMode.trades) + ? _hideTrades() + : _showTrades(); + + @action + void _hideTransaction() => + actionlistDisplayMode.remove(ActionListDisplayMode.transactions); + + @action + void _hideTrades() => + actionlistDisplayMode.remove(ActionListDisplayMode.trades); + + @action + void _showTransaction() => + actionlistDisplayMode.add(ActionListDisplayMode.transactions); + + @action + void _showTrades() => actionlistDisplayMode.add(ActionListDisplayMode.trades); + + @action + Future setDefaultPinLength({@required int pinLength}) async { + this.defaultPinLength = pinLength; + await _sharedPreferences.setInt(currentPinLength, pinLength); + } + + Future _fetchCurrentNode() async { + final id = _sharedPreferences.getInt(currentNodeIdKey); + + return _nodes.get(id); + } + + @action + void setItemHeaders() { + itemHeaders.clear(); + itemHeaders.addAll({ + ItemHeaders.nodes: S.current.settings_nodes, + ItemHeaders.currentNode: S.current.settings_current_node, + ItemHeaders.wallets: S.current.settings_wallets, + ItemHeaders.displayBalanceAs: S.current.settings_display_balance_as, + ItemHeaders.currency: S.current.settings_currency, + ItemHeaders.feePriority: S.current.settings_fee_priority, + ItemHeaders.saveRecipientAddress: + S.current.settings_save_recipient_address, + ItemHeaders.personal: S.current.settings_personal, + ItemHeaders.changePIN: S.current.settings_change_pin, + ItemHeaders.changeLanguage: S.current.settings_change_language, + ItemHeaders.allowBiometricalAuthentication: + S.current.settings_allow_biometrical_authentication, + ItemHeaders.darkMode: S.current.settings_dark_mode, + ItemHeaders.support: S.current.settings_support, + ItemHeaders.termsAndConditions: S.current.settings_terms_and_conditions, + ItemHeaders.faq: S.current.faq + }); + } +} diff --git a/lib/src/stores/subaddress_creation/subaddress_creation_state.dart b/lib/src/stores/subaddress_creation/subaddress_creation_state.dart new file mode 100644 index 000000000..b0ba84327 --- /dev/null +++ b/lib/src/stores/subaddress_creation/subaddress_creation_state.dart @@ -0,0 +1,13 @@ +abstract class SubaddressCreationState {} + +class SubaddressCreationStateInitial extends SubaddressCreationState {} + +class SubaddressIsCreating extends SubaddressCreationState {} + +class SubaddressCreatedSuccessfully extends SubaddressCreationState {} + +class SubaddressCreationFailure extends SubaddressCreationState { + String error; + + SubaddressCreationFailure({this.error}); +} \ No newline at end of file diff --git a/lib/src/stores/subaddress_creation/subaddress_creation_store.dart b/lib/src/stores/subaddress_creation/subaddress_creation_store.dart new file mode 100644 index 000000000..3edc71de7 --- /dev/null +++ b/lib/src/stores/subaddress_creation/subaddress_creation_store.dart @@ -0,0 +1,85 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/monero/monero_wallet.dart'; +import 'package:cake_wallet/src/domain/monero/subaddress_list.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/stores/subaddress_creation/subaddress_creation_state.dart'; +import 'package:cake_wallet/src/domain/monero/account.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'subaddress_creation_store.g.dart'; + +class SubadrressCreationStore = SubadrressCreationStoreBase + with _$SubadrressCreationStore; + +abstract class SubadrressCreationStoreBase with Store { + SubaddressCreationState state; + + @observable + bool isValid; + + @observable + String errorMessage; + + SubaddressList _subaddressList; + StreamSubscription _onWalletChangeSubscription; + StreamSubscription _onAccountChangeSubscription; + Account _account; + + SubadrressCreationStoreBase({@required WalletService walletService}) { + state = SubaddressCreationStateInitial(); + + if (walletService.currentWallet != null) { + _onWalletChanged(walletService.currentWallet); + } + + _onWalletChangeSubscription = + walletService.onWalletChange.listen(_onWalletChanged); + } + + @override + void dispose() { + _onWalletChangeSubscription.cancel(); + + if (_onAccountChangeSubscription != null) { + _onAccountChangeSubscription.cancel(); + } + + super.dispose(); + } + + Future add({String label}) async { + try { + state = SubaddressIsCreating(); + await _subaddressList.addSubaddress( + accountIndex: _account.id, label: label); + state = SubaddressCreatedSuccessfully(); + } catch (e) { + state = SubaddressCreationFailure(error: e.toString()); + } + } + + Future _onWalletChanged(Wallet wallet) async { + if (wallet is MoneroWallet) { + _account = wallet.account; + _subaddressList = wallet.getSubaddress(); + + _onAccountChangeSubscription = wallet.onAccountChange.listen((account) async { + _account = account; + _subaddressList.update(accountIndex: account.id); + }); + return; + } + + print('Incorrect wallet type for this operation (SubaddressList)'); + } + + void validateSubaddressName(String value) { + String p = '''^[^`,'"]{1,20}\$'''; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + errorMessage = isValid ? null : S.current.error_text_subaddress_name; + } +} diff --git a/lib/src/stores/subaddress_list/subaddress_list_store.dart b/lib/src/stores/subaddress_list/subaddress_list_store.dart new file mode 100644 index 000000000..9a4154226 --- /dev/null +++ b/lib/src/stores/subaddress_list/subaddress_list_store.dart @@ -0,0 +1,77 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/monero/monero_wallet.dart'; +import 'package:cake_wallet/src/domain/monero/subaddress.dart'; +import 'package:cake_wallet/src/domain/monero/subaddress_list.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/domain/monero/account.dart'; + +part 'subaddress_list_store.g.dart'; + +class SubaddressListStore = SubaddressListStoreBase with _$SubaddressListStore; + +abstract class SubaddressListStoreBase with Store { + @observable + ObservableList subaddresses; + + SubaddressList _subaddressList; + StreamSubscription _onWalletChangeSubscription; + StreamSubscription> _onSubaddressesChangeSubscription; + StreamSubscription _onAccountChangeSubscription; + Account _account; + + SubaddressListStoreBase({@required WalletService walletService}) { + subaddresses = ObservableList(); + + if (walletService.currentWallet != null) { + _onWalletChanged(walletService.currentWallet); + } + + _onWalletChangeSubscription = + walletService.onWalletChange.listen(_onWalletChanged); + } + + @override + void dispose() { + if (_onSubaddressesChangeSubscription != null) { + _onSubaddressesChangeSubscription.cancel(); + } + + if (_onAccountChangeSubscription != null) { + _onAccountChangeSubscription.cancel(); + } + + _onWalletChangeSubscription.cancel(); + super.dispose(); + } + + Future _updateSubaddressList({int accountIndex}) async { + await _subaddressList.refresh(accountIndex: accountIndex); + subaddresses = ObservableList.of(_subaddressList.getAll()); + } + + Future _onWalletChanged(Wallet wallet) async { + if (_onSubaddressesChangeSubscription != null) { + _onSubaddressesChangeSubscription.cancel(); + } + + if (wallet is MoneroWallet) { + _account = wallet.account; + _subaddressList = wallet.getSubaddress(); + _onSubaddressesChangeSubscription = _subaddressList.subaddresses + .listen((subaddress) => subaddresses = ObservableList.of(subaddress)); + await _updateSubaddressList(accountIndex: _account.id); + + _onAccountChangeSubscription = wallet.onAccountChange.listen((account) async { + _account = account; + await _updateSubaddressList(accountIndex: account.id); + }); + + return; + } + + print('Incorrect wallet type for this operation (SubaddressList)'); + } +} diff --git a/lib/src/stores/sync/sync_store.dart b/lib/src/stores/sync/sync_store.dart new file mode 100644 index 000000000..6c19b4247 --- /dev/null +++ b/lib/src/stores/sync/sync_store.dart @@ -0,0 +1,50 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/sync_status.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; + +part 'sync_store.g.dart'; + +class SyncStore = SyncStoreBase with _$SyncStore; + +abstract class SyncStoreBase with Store { + @observable + SyncStatus status; + + StreamSubscription _onWalletChangeSubscription; + StreamSubscription _onSyncStatusChangeSubscription; + + SyncStoreBase( + {SyncStatus syncStatus = const NotConnectedSyncStatus(), + @required WalletService walletService}) { + status = syncStatus; + + if (walletService.currentWallet != null) { + _onWalletChanged(walletService.currentWallet); + } + + _onWalletChangeSubscription = + walletService.onWalletChange.listen(_onWalletChanged); + } + + @override + void dispose() { + if (_onSyncStatusChangeSubscription != null) { + _onSyncStatusChangeSubscription.cancel(); + } + + _onWalletChangeSubscription.cancel(); + super.dispose(); + } + + void _onWalletChanged(Wallet wallet) { + if (_onSyncStatusChangeSubscription != null) { + _onSyncStatusChangeSubscription.cancel(); + } + + _onSyncStatusChangeSubscription = + wallet.syncStatus.listen((status) => this.status = status); + } +} diff --git a/lib/src/stores/user/user_store.dart b/lib/src/stores/user/user_store.dart new file mode 100644 index 000000000..42f62e4b0 --- /dev/null +++ b/lib/src/stores/user/user_store.dart @@ -0,0 +1,32 @@ +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/src/domain/services/user_service.dart'; +import 'package:cake_wallet/src/stores/user/user_store_state.dart'; + +part 'user_store.g.dart'; + +class UserStore = UserStoreBase with _$UserStore; + +abstract class UserStoreBase with Store { + UserService accountService; + + @observable + UserStoreState state; + + @observable + String errorMessage; + + UserStoreBase({@required this.accountService}); + + @action + Future set({String password}) async { + state = UserStoreStateInitial(); + + try { + await accountService.setPassword(password); + state = PinCodeSetSuccesfully(); + } catch(e) { + state = PinCodeSetFailed(error: e.toString()); + } + } +} diff --git a/lib/src/stores/user/user_store_state.dart b/lib/src/stores/user/user_store_state.dart new file mode 100644 index 000000000..663c0fd60 --- /dev/null +++ b/lib/src/stores/user/user_store_state.dart @@ -0,0 +1,13 @@ +import 'package:flutter/foundation.dart'; + +abstract class UserStoreState {} + +class UserStoreStateInitial extends UserStoreState {} + +class PinCodeSetSuccesfully extends UserStoreState {} + +class PinCodeSetFailed extends UserStoreState { + String error; + + PinCodeSetFailed({@required this.error}); +} diff --git a/lib/src/stores/wallet/wallet_keys_store.dart b/lib/src/stores/wallet/wallet_keys_store.dart new file mode 100644 index 000000000..4d76dc638 --- /dev/null +++ b/lib/src/stores/wallet/wallet_keys_store.dart @@ -0,0 +1,37 @@ +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; + +part 'wallet_keys_store.g.dart'; + +class WalletKeysStore = WalletKeysStoreBase with _$WalletKeysStore; + +abstract class WalletKeysStoreBase with Store { + @observable + String publicViewKey; + + @observable + String privateViewKey; + + @observable + String publicSpendKey; + + @observable + String privateSpendKey; + + WalletKeysStoreBase({@required WalletService walletService}) { + publicViewKey = ''; + privateViewKey = ''; + publicSpendKey = ''; + privateSpendKey = ''; + + if (walletService.currentWallet != null) { + walletService.getKeys().then((keys) { + publicViewKey = keys['publicViewKey']; + privateViewKey = keys['privateViewKey']; + publicSpendKey = keys['publicSpendKey']; + privateSpendKey = keys['privateSpendKey']; + }); + } + } +} diff --git a/lib/src/stores/wallet/wallet_store.dart b/lib/src/stores/wallet/wallet_store.dart new file mode 100644 index 000000000..054e1545b --- /dev/null +++ b/lib/src/stores/wallet/wallet_store.dart @@ -0,0 +1,159 @@ +import 'dart:async'; +import 'package:cake_wallet/src/domain/common/node.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/wallet.dart'; +import 'package:cake_wallet/src/domain/monero/account.dart'; +import 'package:cake_wallet/src/domain/monero/monero_wallet.dart'; +import 'package:cake_wallet/src/domain/monero/subaddress.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/src/stores/settings/settings_store.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'wallet_store.g.dart'; + +class WalletStore = WalletStoreBase with _$WalletStore; + +abstract class WalletStoreBase with Store { + @observable + String address; + + @observable + String name; + + @observable + Subaddress subaddress; + + @observable + Account account; + + @observable + CryptoCurrency type; + + @observable + String amountValue; + + @observable + bool isValid; + + @observable + String errorMessage; + + String get id => name + type.toString().toLowerCase(); + + WalletService _walletService; + SettingsStore _settingsStore; + StreamSubscription _onWalletChangeSubscription; + StreamSubscription _onAccountChangeSubscription; + StreamSubscription _onSubaddressChangeSubscription; + + + WalletStoreBase({WalletService walletService, SettingsStore settingsStore}) { + _walletService = walletService; + _settingsStore = settingsStore; + name = ''; + type = CryptoCurrency.xmr; + amountValue = ''; + + if (_walletService.currentWallet != null) { + _onWalletChanged(_walletService.currentWallet); + } + + _onWalletChangeSubscription = _walletService.onWalletChange + .listen((wallet) async => await _onWalletChanged(wallet)); + } + + @override + void dispose() { + if (_onWalletChangeSubscription != null) { + _onWalletChangeSubscription.cancel(); + } + + if (_onAccountChangeSubscription != null) { + _onAccountChangeSubscription.cancel(); + } + + if (_onSubaddressChangeSubscription != null) { + _onSubaddressChangeSubscription.cancel(); + } + + super.dispose(); + } + + @action + void setAccount(Account account) { + final wallet = _walletService.currentWallet; + + if (wallet is MoneroWallet) { + this.account = account; + wallet.changeAccount(account); + } + } + + @action + void setSubaddress(Subaddress subaddress) { + final wallet = _walletService.currentWallet; + + if (wallet is MoneroWallet) { + this.subaddress = subaddress; + wallet.changeCurrentSubaddress(subaddress); + } + } + + @action + Future reconnect() async => + await _walletService.connectToNode(node: _settingsStore.node); + + @action + Future rescan({int restoreHeight}) async => + await _walletService.rescan(restoreHeight: restoreHeight); + + @action + Future startSync() async => await _walletService.startSync(); + + @action + Future connectToNode({Node node}) async => await _walletService.connectToNode(node: node); + + Future _onWalletChanged(Wallet wallet) async { + if (this == null) { + return; + } + + wallet.onNameChange.listen((name) => this.name = name); + wallet.onAddressChange.listen((address) => this.address = address); + + if (wallet is MoneroWallet) { + _onAccountChangeSubscription = wallet.onAccountChange.listen((account) => this.account = account); + _onSubaddressChangeSubscription = wallet.subaddress.listen((subaddress) => this.subaddress = subaddress); + } + } + + @action + onChangedAmountValue(String value) { + amountValue = value.isNotEmpty ? '?tx_amount=' + value : ''; + } + + @action + void validateAmount(String value) { + const double maxValue = 18446744.073709551616; + + if (value.isEmpty) { + isValid = true; + } else { + String p = '^([0-9]+([.][0-9]{0,12})?|[.][0-9]{1,12})\$'; + RegExp regExp = new RegExp(p); + if (regExp.hasMatch(value)) { + try { + double dValue = double.parse(value); + isValid = dValue <= maxValue; + } catch (e) { + isValid = false; + } + } else isValid = false; + } + + errorMessage = isValid ? null : S.current.error_text_amount; + } + + Future isConnected() async => await _walletService.isConnected(); +} diff --git a/lib/src/stores/wallet_creation/wallet_creation_state.dart b/lib/src/stores/wallet_creation/wallet_creation_state.dart new file mode 100644 index 000000000..f29c40100 --- /dev/null +++ b/lib/src/stores/wallet_creation/wallet_creation_state.dart @@ -0,0 +1,14 @@ +import 'package:flutter/foundation.dart'; + +abstract class WalletCreationState {} + +class WalletCreationStateInitial extends WalletCreationState {} + +class WalletIsCreating extends WalletCreationState {} + +class WalletCreatedSuccessfully extends WalletCreationState {} + +class WalletCreationFailure extends WalletCreationState { + String error; + WalletCreationFailure({@required this.error}); +} \ No newline at end of file diff --git a/lib/src/stores/wallet_creation/wallet_creation_store.dart b/lib/src/stores/wallet_creation/wallet_creation_store.dart new file mode 100644 index 000000000..3732b5f27 --- /dev/null +++ b/lib/src/stores/wallet_creation/wallet_creation_store.dart @@ -0,0 +1,54 @@ +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_state.dart'; +import 'package:cake_wallet/src/stores/authentication/authentication_store.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'wallet_creation_store.g.dart'; + +class WalletCreationStore = WalletCreationStoreBase with _$WalletCreationStore; + +abstract class WalletCreationStoreBase with Store { + final AuthenticationStore authStore; + final WalletListService walletListService; + final SharedPreferences sharedPreferences; + + @observable + WalletCreationState state; + + @observable + String errorMessage; + + @observable + bool isValid; + + WalletCreationStoreBase( + {@required this.authStore, + @required this.walletListService, + @required this.sharedPreferences}) { + state = WalletCreationStateInitial(); + } + + @action + Future create({String name}) async { + state = WalletCreationStateInitial(); + + try { + state = WalletIsCreating(); + await walletListService.create(name); + authStore.created(); + state = WalletCreatedSuccessfully(); + } catch (e) { + state = WalletCreationFailure(error: e.toString()); + } + } + + void validateWalletName(String value) { + String p = '^[a-zA-Z0-9_]{1,15}\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + errorMessage = isValid ? null : S.current.error_text_wallet_name; + } +} diff --git a/lib/src/stores/wallet_list/wallet_list_store.dart b/lib/src/stores/wallet_list/wallet_list_store.dart new file mode 100644 index 000000000..ee0165d89 --- /dev/null +++ b/lib/src/stores/wallet_list/wallet_list_store.dart @@ -0,0 +1,46 @@ +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/common/wallet_description.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; + +part 'wallet_list_store.g.dart'; + +class WalletListStore = WalletListStoreBase with _$WalletListStore; + +abstract class WalletListStoreBase with Store { + @observable + List wallets; + + WalletListService _walletListService; + WalletService _walletService; + + WalletListStoreBase( + {@required WalletListService walletListService, + @required WalletService walletService}) { + _walletListService = walletListService; + _walletService = walletService; + wallets = []; + walletListService.getAll().then((walletList) => wallets = walletList); + } + + bool isCurrentWallet(WalletDescription wallet) { + return _walletService.description?.name == wallet.name; + } + + @action + Future updateWalletList() async { + wallets = await _walletListService.getAll(); + } + + @action + Future loadWallet(WalletDescription wallet) async { + await _walletListService.openWallet(wallet.name); + } + + @action + Future remove(WalletDescription wallet) async { + await _walletListService.remove(wallet); + await updateWalletList(); + } +} diff --git a/lib/src/stores/wallet_restoration/wallet_restoration_state.dart b/lib/src/stores/wallet_restoration/wallet_restoration_state.dart new file mode 100644 index 000000000..6597574e7 --- /dev/null +++ b/lib/src/stores/wallet_restoration/wallet_restoration_state.dart @@ -0,0 +1,14 @@ +import 'package:flutter/foundation.dart'; + +abstract class WalletRestorationState {} + +class WalletRestorationStateInitial extends WalletRestorationState {} + +class WalletIsRestoring extends WalletRestorationState {} + +class WalletRestoredSuccessfully extends WalletRestorationState {} + +class WalletRestorationFailure extends WalletRestorationState { + String error; + WalletRestorationFailure({@required this.error}); +} \ No newline at end of file diff --git a/lib/src/stores/wallet_restoration/wallet_restoration_store.dart b/lib/src/stores/wallet_restoration/wallet_restoration_store.dart new file mode 100644 index 000000000..a351c22d7 --- /dev/null +++ b/lib/src/stores/wallet_restoration/wallet_restoration_store.dart @@ -0,0 +1,154 @@ +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cake_wallet/src/domain/services/wallet_list_service.dart'; +import 'package:cake_wallet/src/domain/common/mnemotic_item.dart'; +import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_state.dart'; +import 'package:cake_wallet/src/stores/authentication/authentication_store.dart'; +import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +part 'wallet_restoration_store.g.dart'; + +class WalletRestorationStore = WalleRestorationStoreBase + with _$WalletRestorationStore; + +abstract class WalleRestorationStoreBase with Store { + final AuthenticationStore authStore; + final WalletListService walletListService; + final SharedPreferences sharedPreferences; + + @observable + WalletRestorationState state; + + @observable + String errorMessage; + + @observable + bool isValid; + + @observable + List seed; + + WalleRestorationStoreBase( + {this.seed, + @required this.authStore, + @required this.walletListService, + @required this.sharedPreferences}) { + state = WalletRestorationStateInitial(); + } + + @action + Future restoreFromSeed({String name, String seed, int restoreHeight}) async { + state = WalletRestorationStateInitial(); + final _seed = seed ?? _seedText(); + + try { + state = WalletIsRestoring(); + await walletListService.restoreFromSeed(name, _seed, restoreHeight); + authStore.restored(); + state = WalletRestoredSuccessfully(); + } catch (e) { + state = WalletRestorationFailure(error: e.toString()); + } + } + + @action + Future restoreFromKeys( + {String name, + String address, + String viewKey, + String spendKey, + int restoreHeight}) async { + state = WalletRestorationStateInitial(); + + try { + state = WalletIsRestoring(); + await walletListService.restoreFromKeys( + name, restoreHeight, address, viewKey, spendKey); + authStore.restored(); + state = WalletRestoredSuccessfully(); + } catch (e) { + state = WalletRestorationFailure(error: e.toString()); + } + } + + @action + void setSeed(List seed) { + this.seed = seed; + validateSeed(seed); + } + + @action + void validateSeed(List seed) { + final _seed = seed != null ? seed : this.seed; + bool isValid = _seed.length == 25; + + if (!isValid) { + errorMessage = S.current.wallet_restoration_store_incorrect_seed_length; + this.isValid = isValid; + return; + } + + for (final item in _seed) { + if (!item.isCorrect()) { + isValid = false; + break; + } + } + + if (isValid) { + errorMessage = null; + } + + this.isValid = isValid; + return; + } + + String _seedText() { + return seed.fold('', (acc, item) => acc + ' ' + item.toString()); + } + + void validateWalletName(String value) { + String p = '^[a-zA-Z0-9_]{1,15}\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + errorMessage = isValid ? null : S.current.error_text_wallet_name; + } + + void validateAddress(String value, {CryptoCurrency cryptoCurrency}) { + // XMR (95), BTC (34), ETH (42), LTC (34), BCH (42), DASH (34) + String p = '^[0-9a-zA-Z]{95}\$|^[0-9a-zA-Z]{34}\$|^[0-9a-zA-Z]{42}\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + if (isValid && cryptoCurrency != null) { + switch (cryptoCurrency.toString()) { + case 'XMR': + isValid = (value.length == 95); + break; + case 'BTC': + isValid = (value.length == 34); + break; + case 'ETH': + isValid = (value.length == 42); + break; + case 'LTC': + isValid = (value.length == 34); + break; + case 'BCH': + isValid = (value.length == 42); + break; + case 'DASH': + isValid = (value.length == 34); + } + } + errorMessage = isValid ? null : S.current.error_text_address; + } + + void validateKeys(String value) { + String p = '^[A-Fa-f0-9]{64}\$'; + RegExp regExp = new RegExp(p); + isValid = regExp.hasMatch(value); + errorMessage = isValid ? null : S.current.error_text_keys; + } +} diff --git a/lib/src/stores/wallet_seed/wallet_seed_store.dart b/lib/src/stores/wallet_seed/wallet_seed_store.dart new file mode 100644 index 000000000..f3ac71d58 --- /dev/null +++ b/lib/src/stores/wallet_seed/wallet_seed_store.dart @@ -0,0 +1,24 @@ +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/src/domain/services/wallet_service.dart'; + +part 'wallet_seed_store.g.dart'; + +class WalletSeedStore = WalletSeedStoreBase with _$WalletSeedStore; + +abstract class WalletSeedStoreBase with Store { + @observable + String name; + + @observable + String seed; + + WalletSeedStoreBase({@required WalletService walletService}) { + seed = ''; + + if (walletService.currentWallet != null) { + walletService.getSeed().then((seed) => this.seed = seed); + walletService.getName().then((name) => this.name = name); + } + } +} diff --git a/lib/src/util/index.dart b/lib/src/util/index.dart new file mode 100644 index 000000000..e69de29bb diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart new file mode 100644 index 000000000..060b8c760 --- /dev/null +++ b/lib/src/widgets/address_text_field.dart @@ -0,0 +1,157 @@ +import 'package:cake_wallet/routes.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/common/contact.dart'; +import 'package:cake_wallet/src/domain/monero/subaddress.dart'; +import 'package:cake_wallet/src/domain/common/qr_scanner.dart'; + +enum AddressTextFieldOption { qrCode, addressBook, subaddressList } + +class AddressTextField extends StatelessWidget { + static const prefixIconWidth = 34.0; + static const prefixIconHeight = 34.0; + static const spaceBetweenPrefixIcons = 10.0; + + final TextEditingController controller; + final bool isActive; + final String placeholder; + final Function(Uri) onURIScanned; + final List options; + final FormFieldValidator validator; + + AddressTextField( + {@required this.controller, + this.isActive = true, + this.placeholder, + this.options = const [ + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook + ], + this.onURIScanned, + this.validator}); + + @override + Widget build(BuildContext context) { + return TextFormField( + onFieldSubmitted: (_) => FocusScope.of(context).unfocus(), + enabled: isActive, + controller: controller, + decoration: InputDecoration( + suffixIcon: SizedBox( + width: prefixIconWidth * options.length + + (spaceBetweenPrefixIcons * options.length), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 5), + if (this.options.contains(AddressTextFieldOption.qrCode)) ...[ + Container( + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: InkWell( + onTap: () async => _presentQRScanner(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Palette.wildDarkBlueWithOpacity, + borderRadius: + BorderRadius.all(Radius.circular(8))), + child: Image.asset('assets/images/qr_code_icon.png')), + )) + ], + if (this + .options + .contains(AddressTextFieldOption.addressBook)) ...[ + Container( + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: InkWell( + onTap: () async => _presetAddressBookPicker(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Palette.wildDarkBlueWithOpacity, + borderRadius: + BorderRadius.all(Radius.circular(8))), + child: Image.asset( + 'assets/images/address_book_icon.png')), + )) + ], + if (this + .options + .contains(AddressTextFieldOption.subaddressList)) ...[ + Container( + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: InkWell( + onTap: () async => _presetSubaddressListPicker(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Palette.wildDarkBlueWithOpacity, + borderRadius: + BorderRadius.all(Radius.circular(8))), + child: Image.asset( + 'assets/images/receive_icon_raw.png')), + )) + ], + ], + ), + ), + hintStyle: TextStyle(color: Theme.of(context).hintColor), + hintText: placeholder ?? S.current.widgets_address, + focusedBorder: UnderlineInputBorder( + borderSide: + BorderSide(color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: + BorderSide(color: Theme.of(context).focusColor, width: 1.0)), + ), + validator: validator, + ); + } + + Future _presentQRScanner(BuildContext context) async { + try { + String code = await presentQRScanner(); + var uri = Uri.parse(code); + var address = ''; + + if (uri == null) { + controller.text = code; + return; + } + + address = uri.path; + controller.text = address; + + if (onURIScanned != null) { + onURIScanned(uri); + } + } catch (e) { + print('Error $e'); + } + } + + Future _presetAddressBookPicker(BuildContext context) async { + final contact = await Navigator.of(context, rootNavigator: true) + .pushNamed(Routes.pickerAddressBook); + + if (contact is Contact && contact.address != null) { + controller.text = contact.address; + } + } + + Future _presetSubaddressListPicker(BuildContext context) async { + final subaddress = await Navigator.of(context, rootNavigator: true) + .pushNamed(Routes.subaddressList); + + if (subaddress is Subaddress && subaddress.address != null) { + controller.text = subaddress.address; + } + } +} diff --git a/lib/src/widgets/blockchain_height_widget.dart b/lib/src/widgets/blockchain_height_widget.dart new file mode 100644 index 000000000..fcd4cfead --- /dev/null +++ b/lib/src/widgets/blockchain_height_widget.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/domain/monero/get_height_by_date.dart'; +import 'package:cake_wallet/palette.dart'; + +class BlockchainHeightWidget extends StatefulWidget { + BlockchainHeightWidget({GlobalKey key}) : super(key: key); + + @override + State createState() => BlockchainHeightState(); +} + +class BlockchainHeightState extends State { + final dateController = TextEditingController(); + final restoreHeightController = TextEditingController(); + int get height => _height; + int _height = 0; + + @override + void initState() { + restoreHeightController.addListener(() => _height = + restoreHeightController.text != null + ? int.parse(restoreHeightController.text) + : 0); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(top: 20.0, bottom: 10.0), + child: TextFormField( + style: TextStyle(fontSize: 14.0), + controller: restoreHeightController, + keyboardType: TextInputType.numberWithOptions( + signed: false, decimal: false), + decoration: InputDecoration( + hintStyle: TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).widgets_restore_from_blockheight, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, width: 1.0))), + ), + )) + ], + ), + Padding( + padding: EdgeInsets.only(top: 15, bottom: 15), + child: Text( + S.of(context).widgets_or, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryTextTheme.title.color), + ), + ), + Row( + children: [ + Flexible( + child: Container( + child: InkWell( + onTap: () => _selectDate(context), + child: IgnorePointer( + child: TextFormField( + style: TextStyle(fontSize: 14.0), + decoration: InputDecoration( + hintStyle: + TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).widgets_restore_from_date, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, + width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).focusColor, + width: 1.0))), + controller: dateController, + validator: (value) { + return null; + }, + ), + ), + ), + )) + ], + ), + ], + ); + } + + Future _selectDate(BuildContext context) async { + final now = DateTime.now(); + final DateTime date = await showDatePicker( + context: context, + initialDate: now.subtract(Duration(days: 1)), + firstDate: DateTime(2014, DateTime.april), + lastDate: now); + + if (date != null) { + final height = getHeigthByDate(date: date); + + setState(() { + dateController.text = DateFormat('yyyy-MM-dd').format(date); + restoreHeightController.text = '$height'; + _height = height; + }); + } + } +} diff --git a/lib/src/widgets/index.dart b/lib/src/widgets/index.dart new file mode 100644 index 000000000..e69de29bb diff --git a/lib/src/widgets/nav_bar.dart b/lib/src/widgets/nav_bar.dart new file mode 100644 index 000000000..aacedeef8 --- /dev/null +++ b/lib/src/widgets/nav_bar.dart @@ -0,0 +1,100 @@ +import 'package:cake_wallet/theme_changer.dart'; +import 'package:cake_wallet/themes.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget { + static const _originalHeight = 44.0; // iOS nav bar height + static const _height = 60.0; + + factory NavBar.withShadow( + {BuildContext context, + Widget leading, + Widget middle, + Widget trailing, + Color backgroundColor}) { + final _themeChanger = Provider.of(context); + final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme; + + return NavBar._internal( + leading: leading, + middle: middle, + trailing: trailing, + height: 80, + backgroundColor: + _isDarkTheme ? Theme.of(context).backgroundColor : backgroundColor, + decoration: BoxDecoration( + color: _isDarkTheme + ? Theme.of(context).backgroundColor + : backgroundColor, + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(132, 141, 198, 0.11), + blurRadius: 8, + offset: Offset(0, 2)) + ]), + ); + } + + factory NavBar( + {BuildContext context, + Widget leading, + Widget middle, + Widget trailing, + Color backgroundColor}) { + final _themeChanger = Provider.of(context); + final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme; + + return NavBar._internal( + leading: leading, + middle: middle, + trailing: trailing, + height: _height, + backgroundColor: + _isDarkTheme ? Theme.of(context).backgroundColor : backgroundColor); + } + + final Widget leading; + final Widget middle; + final Widget trailing; + final Color backgroundColor; + final BoxDecoration decoration; + final double height; + + NavBar._internal( + {this.leading, + this.middle, + this.trailing, + this.backgroundColor, + this.decoration, + this.height = _height}); + + @override + Widget build(BuildContext context) { + final pad = height - _originalHeight; + final paddingTop = pad / 2; + final _paddingBottom = (pad / 2); + + return Container( + decoration: decoration ?? BoxDecoration(color: backgroundColor), + padding: + EdgeInsetsDirectional.only(bottom: _paddingBottom, top: paddingTop), + child: CupertinoNavigationBar( + leading: leading, + middle: middle, + trailing: trailing, + backgroundColor: backgroundColor, + border: null, + ), + ); + } + + @override + Size get preferredSize => Size.fromHeight(height); + + @override + bool shouldFullyObstruct(BuildContext context) { + return false; + } +} diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart new file mode 100644 index 000000000..f16211194 --- /dev/null +++ b/lib/src/widgets/picker.dart @@ -0,0 +1,105 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; + +class Picker extends StatelessWidget { + final int selectedAtIndex; + final List items; + final String title; + final double pickerHeight; + final Function(Item) onItemSelected; + + Picker( + {@required this.selectedAtIndex, + @required this.items, + @required this.title, + this.pickerHeight = 300, + this.onItemSelected}); + + @override + Widget build(BuildContext context) { + + return GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Container( + color: Colors.transparent, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), + child: Container( + decoration: BoxDecoration(color: Colors.white.withOpacity(0.55)), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GestureDetector( + onTap: () => null, + child: Container( + width: double.infinity, + height: pickerHeight, + color: Theme.of(context).backgroundColor, + child: ListView.separated( + itemCount: items.length + 1, + separatorBuilder: (_, index) => index == 0 + ? SizedBox() + : Divider( + height: 1, + color: Color.fromRGBO(235, 238, 242, 1)), + itemBuilder: (_, index) { + if (index == 0) { + return Container( + height: 100, + width: double.infinity, + color: Theme.of(context).backgroundColor, + child: Center( + child: Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.w600, + fontFamily: 'Lato', + decoration: TextDecoration.none, + color: Theme.of(context).primaryTextTheme.caption.color + ), + ), + ), + ); + } + + index -= 1; + final item = items[index]; + + return GestureDetector( + onTap: () { + if (onItemSelected == null) { + return; + } + Navigator.of(context).pop(); + onItemSelected(item); + }, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(top: 18, bottom: 18), + child: Center( + child: Text( + item.toString(), + style: TextStyle( + decoration: TextDecoration.none, + fontSize: 18, + fontWeight: FontWeight.normal, + fontFamily: 'Lato', + color: index == selectedAtIndex + ? Color.fromRGBO(138, 80, 255, 1) + : Theme.of(context).primaryTextTheme.caption.color), + )), + ), + ); + }, + )), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/widgets/primary_button.dart b/lib/src/widgets/primary_button.dart new file mode 100644 index 000000000..5571f726d --- /dev/null +++ b/lib/src/widgets/primary_button.dart @@ -0,0 +1,188 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/palette.dart'; + +class PrimaryButton extends StatelessWidget { + final VoidCallback onPressed; + final Color color; + final Color borderColor; + final String text; + + const PrimaryButton( + {@required this.onPressed, + @required this.text, + @required this.color, + @required this.borderColor}); + + @override + Widget build(BuildContext context) { + + return ButtonTheme( + minWidth: double.infinity, + height: 56.0, + child: FlatButton( + onPressed: onPressed, + color: color, + shape: RoundedRectangleBorder( + side: BorderSide(color: borderColor), + borderRadius: BorderRadius.circular(10.0)), + child: Text(text, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.button.color)), + )); + } +} + +class LoadingPrimaryButton extends StatelessWidget { + final VoidCallback onPressed; + final Color color; + final Color borderColor; + final bool isLoading; + final bool isDisabled; + final String text; + + const LoadingPrimaryButton( + {@required this.onPressed, + @required this.text, + @required this.color, + @required this.borderColor, + this.isDisabled = false, + this.isLoading = false}); + + @override + Widget build(BuildContext context) { + + return ButtonTheme( + minWidth: double.infinity, + height: 56.0, + child: FlatButton( + onPressed: (isLoading || isDisabled) ? null : onPressed, + color: color, + shape: RoundedRectangleBorder( + side: BorderSide(color: borderColor), + borderRadius: BorderRadius.circular(10.0)), + child: isLoading + ? CupertinoActivityIndicator(animating: true) + : Text(text, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.button.color)), + )); + } +} + +class PrimaryIconButton extends StatelessWidget { + final VoidCallback onPressed; + final IconData iconData; + final Color color; + final Color borderColor; + final Color iconColor; + final Color iconBackgroundColor; + final String text; + + const PrimaryIconButton({ + @required this.onPressed, + @required this.iconData, + @required this.text, + @required this.color, + @required this.borderColor, + @required this.iconColor, + @required this.iconBackgroundColor, + }); + + @override + Widget build(BuildContext context) { + + return ButtonTheme( + minWidth: double.infinity, + height: 56.0, + child: FlatButton( + onPressed: onPressed, + color: color, + shape: RoundedRectangleBorder( + side: BorderSide(color: borderColor), + borderRadius: BorderRadius.circular(10.0)), + child: Stack( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: 28.0, + height: 56.0, + decoration: BoxDecoration( + shape: BoxShape.circle, color: iconBackgroundColor), + child: Icon(iconData, color: iconColor, size: 22.0), + ), + ], + ), + Container( + height: 56.0, + child: Center( + child: Text(text, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryTextTheme.button.color)), + ), + ) + ], + ), + )); + } +} + +class PrimaryImageButton extends StatelessWidget { + final VoidCallback onPressed; + final Image image; + final Color color; + final Color borderColor; + final Color iconColor; + final String text; + + const PrimaryImageButton( + {@required this.onPressed, + @required this.image, + @required this.text, + this.color = Palette.purple, + this.borderColor = Palette.deepPink, + this.iconColor = Colors.black}); + + @override + Widget build(BuildContext context) { + return ButtonTheme( + minWidth: double.infinity, + height: 58.0, + child: FlatButton( + onPressed: onPressed, + color: color, + shape: RoundedRectangleBorder( + side: BorderSide(color: borderColor), + borderRadius: BorderRadius.circular(12.0)), + child: Row( + children: [ + Container( + width: 28.0, + height: 56.0, + decoration: BoxDecoration( + shape: BoxShape.circle, color: Colors.transparent), + child: image, + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 56.0, + child: Center( + child: Text(text, style: TextStyle(fontSize: 18.0, + color: Theme.of(context).primaryTextTheme.button.color + )), + ), + ) + ])) + ], + ), + )); + } +} diff --git a/lib/src/widgets/scollable_with_bottom_section.dart b/lib/src/widgets/scollable_with_bottom_section.dart new file mode 100644 index 000000000..4cd2fffe8 --- /dev/null +++ b/lib/src/widgets/scollable_with_bottom_section.dart @@ -0,0 +1,50 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class ScrollableWithBottomSection extends StatefulWidget { + final Widget content; + final Widget bottomSection; + final EdgeInsets contentPadding; + final EdgeInsets bottomSectionPadding; + + ScrollableWithBottomSection( + {this.content, + this.bottomSection, + this.contentPadding, + this.bottomSectionPadding}); + + @override + ScrollableWithBottomSectionState createState() => + ScrollableWithBottomSectionState(); +} + +class ScrollableWithBottomSectionState + extends State { + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: (context, constraints) { + return SingleChildScrollView( + // physics: + // const AlwaysScrollableScrollPhysics(), // const NeverScrollableScrollPhysics(), // + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.heightConstraints().maxHeight), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: widget.contentPadding ?? + EdgeInsets.only(left: 20, right: 20), + child: widget.content, + ), + Padding( + padding: widget.bottomSectionPadding ?? + EdgeInsets.only(bottom: 20, right: 20, left: 20), + child: widget.bottomSection) + ], + ), + ), + ); + }); + } +} diff --git a/lib/src/widgets/seed_widget.dart b/lib/src/widgets/seed_widget.dart new file mode 100644 index 000000000..3328df858 --- /dev/null +++ b/lib/src/widgets/seed_widget.dart @@ -0,0 +1,251 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/src/domain/monero/mnemonics/english.dart'; +import 'package:cake_wallet/src/domain/common/mnemotic_item.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class SeedWidget extends StatefulWidget { + final Function(List) onMnemoticChange; + + SeedWidget({Key key, this.onMnemoticChange}) : super(key: key); + + SeedWidgetState createState() => SeedWidgetState(); +} + +class SeedWidgetState extends State { + List items = []; + final _seedController = TextEditingController(); + MnemoticItem selectedItem; + bool isValid; + String errorMessage; + + @override + void initState() { + super.initState(); + isValid = true; + _seedController.addListener(() => mnemoticFromText(_seedController.text)); + } + + void addMnemotic(String text) { + setState(() => items.add(MnemoticItem( + text: text.trim().toLowerCase(), dic: EnglishMnemonics.words))); + _seedController.text = ''; + + if (widget.onMnemoticChange != null) { + widget.onMnemoticChange(items); + } + } + + void deleteMnemotic(MnemoticItem item) { + setState(() => items.remove(item)); + + if (widget.onMnemoticChange != null) { + widget.onMnemoticChange(items); + } + } + + void mnemoticFromText(String text) { + final splitted = text.split(' '); + + if (splitted.length >= 2) { + for (final text in splitted) { + if (text == ' ' || text.isEmpty) { + continue; + } + + if (selectedItem != null) { + editTextOfSelectedMnemotic(text); + } else { + addMnemotic(text); + } + } + } + } + + void selectMnemotic(MnemoticItem item) { + setState(() => selectedItem = item); + _seedController + ..text = item.text + ..selection = TextSelection.collapsed(offset: item.text.length); + } + + void onMnemoticTap(MnemoticItem item) { + if (selectedItem == item) { + setState(() => selectedItem = null); + _seedController.text = ''; + return; + } + + selectMnemotic(item); + } + + void editTextOfSelectedMnemotic(String text) { + setState(() => selectedItem.changeText(text)); + selectedItem = null; + _seedController.text = ''; + + if (widget.onMnemoticChange != null) { + widget.onMnemoticChange(items); + } + } + + void onFieldSubmitted(String text) { + if (text.isEmpty || text == null) { + return; + } + + if (selectedItem != null) { + editTextOfSelectedMnemotic(text); + } else { + addMnemotic(text); + } + } + + void clear() { + setState(() { + items = []; + _seedController.text = ''; + + if (widget.onMnemoticChange != null) { + widget.onMnemoticChange(items); + } + }); + } + + void invalidate() { + setState(() => isValid = false); + } + + void validated() { + setState(() => isValid = true); + } + + void setErrorMessage(String errorMessage) { + setState(() => this.errorMessage = errorMessage); + } + + void replaceText(String text) { + setState(() => items = []); + mnemoticFromText(text); + } + + @override + Widget build(BuildContext context) { + return Container( + child: Column(children: [ + TextFormField( + onFieldSubmitted: (text) => onFieldSubmitted(text), + style: TextStyle(fontSize: 14.0), + controller: _seedController, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + suffixIcon: GestureDetector( + behavior: HitTestBehavior.opaque, + child: SizedBox( + width: 65, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + clear(); + FocusScope.of(context).unfocus(); + }, + child: Container( + height: 20, + width: 20, + padding: EdgeInsets.all(3), + decoration: BoxDecoration( + color: Palette.wildDarkBlueWithOpacity, + borderRadius: BorderRadius.circular(10.0)), + child: Image.asset( + 'assets/images/x.png', + color: Palette.wildDarkBlue, + ), + ), + ), + InkWell( + onTap: () async => Clipboard.getData('text/plain') + .then((clipboard) => replaceText(clipboard.text)), + child: Container( + height: 35, + width: 35, + padding: EdgeInsets.all(7), + decoration: BoxDecoration( + color: Palette.wildDarkBlueWithOpacity, + borderRadius: BorderRadius.circular(10.0)), + child: Image.asset('assets/images/paste_button.png', + height: 5, width: 5, color: Palette.wildDarkBlue), + ), + ) + ], + ), + ), + ), + errorText: errorMessage, + hintStyle: TextStyle(color: Theme.of(context).hintColor), + hintText: S.of(context).widgets_seed, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Palette.cakeGreen, width: 2.0)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: + isValid ? Theme.of(context).focusColor : Palette.red, + width: 1.0))), + ), + SizedBox(height: 20), + Expanded( + child: GridView.count( + physics: NeverScrollableScrollPhysics(), + crossAxisCount: 3, + childAspectRatio: 3.3, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + children: List.generate(items.length, (index) { + final item = items[index]; + final isValid = item.isCorrect(); + final isSelected = selectedItem == item; + + return InkWell( + onTap: () => onMnemoticTap(item), + child: Container( + decoration: BoxDecoration( + color: isValid ? Palette.brightBlue : Palette.red, + border: Border.all( + color: + isSelected ? Palette.violet : Colors.transparent), + borderRadius: BorderRadius.circular(15.0)), + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + item.toString(), + style: TextStyle( + color: + isValid ? Palette.blueGrey : Palette.lightGrey, + fontSize: 12, + fontWeight: FontWeight.w600), + ), + InkWell( + onTap: () => deleteMnemotic(item), + child: Image.asset( + 'assets/images/x.png', + height: 15, + width: 15, + color: Palette.wildDarkBlue, + )) + ], + ), + ), + ); + }), + ), + ), + ]), + ); + } +} diff --git a/lib/src/widgets/standart_close_button.dart b/lib/src/widgets/standart_close_button.dart new file mode 100644 index 000000000..a89077450 --- /dev/null +++ b/lib/src/widgets/standart_close_button.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; + +class StandartCloseButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return SizedBox( + height: 37, + width: 37, + child: FlatButton( + padding: EdgeInsets.all(0), + onPressed: () => Navigator.of(context).pop(), + child: Image.asset('assets/images/close_button.png')), + ); + } +} \ No newline at end of file diff --git a/lib/src/widgets/standart_switch.dart b/lib/src/widgets/standart_switch.dart new file mode 100644 index 000000000..21347ee81 --- /dev/null +++ b/lib/src/widgets/standart_switch.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +class StandartSwitch extends StatefulWidget { + + final bool value; + final VoidCallback onTaped; + + const StandartSwitch({ + @required this.value, + @required this.onTaped}); + + @override + createState() => StandartSwitchState(); + +} + +class StandartSwitchState extends State { + + @override + Widget build(BuildContext context) { + + return GestureDetector( + onTap: widget.onTaped, + child: AnimatedContainer( + padding: EdgeInsets.only(left: 4.0, right: 4.0), + alignment: widget.value + ? Alignment.centerRight + : Alignment.centerLeft, + duration: Duration(milliseconds: 250), + width: 55.0, + height: 33.0, + decoration: BoxDecoration( + color: Theme.of(context).toggleButtonsTheme.color, + border: Border.all( + color: Theme.of(context).toggleButtonsTheme.borderColor + ), + borderRadius: + BorderRadius.all(Radius.circular(10.0))), + child: Container( + width: 25.0, + height: 25.0, + decoration: BoxDecoration( + color: widget.value + ? Theme.of(context).toggleButtonsTheme.selectedColor + : Theme.of(context).toggleButtonsTheme.disabledColor, + borderRadius: + BorderRadius.all(Radius.circular(8.0))), + child: Icon(widget.value + ? Icons.check + : Icons.close, + color: Colors.white, + size: 16.0, + ), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/theme_changer.dart b/lib/theme_changer.dart new file mode 100644 index 000000000..f6aa9c69d --- /dev/null +++ b/lib/theme_changer.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class ThemeChanger with ChangeNotifier { + + ThemeData _themeData; + + ThemeChanger(this._themeData); + + getTheme() => _themeData; + + setTheme(ThemeData theme){ + _themeData = theme; + + notifyListeners(); + } + +} \ No newline at end of file diff --git a/lib/themes.dart b/lib/themes.dart new file mode 100644 index 000000000..c7b69f171 --- /dev/null +++ b/lib/themes.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'palette.dart'; + +class Themes { + + static final ThemeData lightTheme = ThemeData( + fontFamily: 'Lato', + brightness: Brightness.light, + backgroundColor: Colors.white, + scaffoldBackgroundColor: Colors.white, + hintColor: Palette.lightBlue, + focusColor: Palette.lightGrey, // focused and enabled border color for text fields + primaryTextTheme: TextTheme( + title: TextStyle( + color: Colors.black + ), + caption: TextStyle( + color: Colors.black, + ), + button: TextStyle( + color: Colors.black, + backgroundColor: Palette.purple, // button purple background color + decorationColor: Palette.deepPink // button pink border color + ), + headline: TextStyle( + color: Colors.black // account list tile, contact page + ), + subtitle: TextStyle( + color: Palette.wildDarkBlue // filters + ), + subhead: TextStyle( + color: Colors.black // transaction raw, trade raw + ), + overline: TextStyle( + color: PaletteDark.darkThemeCloseButton // standart list row, transaction details + ) + ), + toggleButtonsTheme: ToggleButtonsThemeData( + selectedColor: Palette.cakeGreen, + disabledColor: Palette.wildDarkBlue, + color: Palette.switchBackground, + borderColor: Palette.switchBorder + ), + selectedRowColor: Palette.purple, + dividerColor: Palette.lightGrey, + dividerTheme: DividerThemeData( + color: Palette.lightGrey + ), + accentTextTheme: TextTheme( + title: TextStyle( + color: Colors.grey, + backgroundColor: Palette.creamyGrey // pin button color + ), + caption: TextStyle( + color: Palette.wildDarkBlue, // clear button color, exchange page + backgroundColor: Palette.brightBlue, // button blue background color + decorationColor: Palette.cloudySky // button blue border color + ), + button: TextStyle( + backgroundColor: Palette.indigo, // button indigo background color + decorationColor: Palette.deepIndigo // button indigo border color + ), + subtitle: TextStyle( + color: Colors.black, + backgroundColor: Palette.lightLavender // send page border color + ), + headline: TextStyle( + color: Palette.lightGrey2, // receive page + backgroundColor: Colors.white, // restore button background color + decorationColor: Palette.darkGrey, // restore button border color + ), + subhead: TextStyle( + color: Palette.lightBlue, // restore button description + backgroundColor: Palette.lightGrey2 // change language background color + ), + overline: TextStyle( + color: Palette.nightBlue, // send page text + backgroundColor: Palette.nightBlue, // send page text + decorationColor: Palette.manatee // send page text + ) + ), + cardColor: Palette.lavender, + cardTheme: CardTheme( + color: Palette.cadetBlue + ), + buttonColor: Palette.darkGrey, + primaryIconTheme: IconThemeData( + color: Colors.white + ), + accentIconTheme: IconThemeData( + color: Colors.white + ) + ); + + + static final ThemeData darkTheme = ThemeData( + fontFamily: 'Lato', + brightness: Brightness.dark, + backgroundColor: PaletteDark.darkThemeBackgroundDark, + scaffoldBackgroundColor: PaletteDark.darkThemeBlack, + hintColor: PaletteDark.darkThemeGrey, + focusColor: PaletteDark.darkThemeGreyWithOpacity, // focused and enabled border color for text fields + primaryTextTheme: TextTheme( + title: TextStyle( + color: PaletteDark.darkThemeTitle + ), + caption: TextStyle( + color: Colors.white + ), + button: TextStyle( + color: Palette.wildDarkBlue, + backgroundColor: PaletteDark.darkThemePurpleButton, // button purple background color + decorationColor: PaletteDark.darkThemePurpleButtonBorder // button pink border color + ), + headline: TextStyle( + color: PaletteDark.darkThemeGrey // account list tile, contact page + ), + subtitle: TextStyle( + color: PaletteDark.darkThemeGrey // filters + ), + subhead: TextStyle( + color: Palette.blueGrey // transaction raw, trade raw + ), + overline: TextStyle( + color: PaletteDark.darkThemeGrey // standart list row, transaction details + ) + ), + toggleButtonsTheme: ToggleButtonsThemeData( + selectedColor: Palette.cakeGreen, + disabledColor: Palette.wildDarkBlue, + color: PaletteDark.switchBackground, + borderColor: PaletteDark.darkThemeMidGrey + ), + selectedRowColor: PaletteDark.darkThemeViolet, + dividerColor: PaletteDark.darkThemeDarkGrey, + dividerTheme: DividerThemeData( + color: PaletteDark.darkThemeGreyWithOpacity + ), + accentTextTheme: TextTheme( + title: TextStyle( + color: PaletteDark.darkThemeTitle, + backgroundColor: PaletteDark.darkThemePinDigitButton // pin button color + ), + caption: TextStyle( + color: PaletteDark.darkThemeTitleViolet, // clear button color, exchange page + backgroundColor: PaletteDark.darkThemeBlueButton, // button blue background color + decorationColor: PaletteDark.darkThemeBlueButtonBorder // button blue border color + ), + button: TextStyle( + backgroundColor: PaletteDark.darkThemeIndigoButton, // button indigo background color + decorationColor: PaletteDark.darkThemeIndigoButtonBorder // button indigo border color + ), + subtitle: TextStyle( + color: PaletteDark.wildDarkBlueWithOpacity, + backgroundColor: PaletteDark.darkThemeDarkGrey // send page border color + ), + headline: TextStyle( + color: PaletteDark.darkThemeBlack, // receive page + backgroundColor: PaletteDark.darkThemeMidGrey, // restore button background color + decorationColor: PaletteDark.darkThemeDarkGrey, // restore button border color + ), + subhead: TextStyle( + color: Palette.wildDarkBlue, // restore button description + backgroundColor: PaletteDark.darkThemeMidGrey // change language background color + ), + overline: TextStyle( + color: PaletteDark.darkThemeTitle, // send page text + backgroundColor: PaletteDark.darkThemeGrey, // send page text + decorationColor: PaletteDark.darkThemeTitle // send page text + ) + ), + cardColor: PaletteDark.darkThemeMidGrey, + cardTheme: CardTheme( + color: PaletteDark.darkThemeGrey + ), + buttonColor: PaletteDark.darkThemePinButton, + primaryIconTheme: IconThemeData( + color: PaletteDark.darkThemeViolet + ), + accentIconTheme: IconThemeData( + color: PaletteDark.darkThemeIndigoButtonBorder + ) + ); + +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 000000000..7aedeb3a0 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,759 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.36.4" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + asn1lib: + dependency: transitive + description: + name: asn1lib + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.15" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + barcode_scan: + dependency: "direct main" + description: + name: barcode_scan + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.2" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1+1" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.2" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.0" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.0" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "7.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + cw_monero: + dependency: "direct main" + description: + path: cw_monero + relative: true + source: path + version: "0.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.9" + dartx: + dependency: transitive + description: + name: dartx + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + date_range_picker: + dependency: "direct main" + description: + name: date_range_picker + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.6" + dio: + dependency: "direct main" + description: + name: dio + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.7" + encrypt: + dependency: "direct main" + description: + name: encrypt + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + esys_flutter_share: + dependency: "direct main" + description: + name: esys_flutter_share + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.11" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cupertino_localizations: + dependency: "direct main" + description: + name: flutter_cupertino_localizations + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.4" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_mobx: + dependency: "direct main" + description: + name: flutter_mobx + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0+1" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.1+1" + flutter_slidable: + dependency: "direct main" + description: + name: flutter_slidable + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.4" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + front_end: + dependency: transitive + description: + name: front_end + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.19" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + hive: + dependency: "direct main" + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+3" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0+2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.3" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.1+1" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + kernel: + dependency: transitive + description: + name: kernel + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.19" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.3+2" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.6" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.8" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.6+3" + mobx: + dependency: "direct main" + description: + name: mobx + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.9+3" + mobx_codegen: + dependency: "direct dev" + description: + name: mobx_codegen + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3+1" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1+2" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + package_resolver: + dependency: transitive + description: + name: package_resolver + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.10" + password: + dependency: "direct main" + description: + name: password + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0+1" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.2" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + qr: + dependency: "direct main" + description: + name: qr + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + rxdart: + dependency: "direct main" + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.22.6" + share: + dependency: "direct main" + description: + name: share + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3+5" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.4+9" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.5" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.4+4" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.20" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.11" + time: + dependency: transitive + description: + name: time + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+2" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+1" + uuid: + dependency: "direct main" + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+13" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" + yaml: + dependency: "direct main" + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" +sdks: + dart: ">=2.6.0 <3.0.0" + flutter: ">=1.12.8 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 000000000..3ad89668e --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,122 @@ +name: cake_wallet +description: Cake Wallet. + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.2.2 <3.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + flutter_cupertino_localizations: ^1.0.1 + intl: ^0.16.0 + url_launcher: ^5.1.2 + qr: ^1.2.0 + uuid: 2.0.1 + shared_preferences: ^0.5.3+4 + flutter_secure_storage: ^3.2.1+1 + provider: ^3.1.0 + rxdart: ^0.22.2 + yaml: ^2.1.16 + barcode_scan: any + http: ^0.12.0+2 + path_provider: ^1.3.0 + mobx: ^0.3.7 + flutter_mobx: 0.3.0+1 + flutter_slidable: ^0.5.3 + share: ^0.6.2+1 + esys_flutter_share: ^1.0.2 + date_range_picker: ^1.0.6 + dio: 3.0.7 + cw_monero: + path: ./cw_monero + hive: ^1.2.0 + hive_flutter: ^0.2.1 + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^0.1.2 + encrypt: ^4.0.0 + password: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^1.3.1 + mobx_codegen: 0.3.3+1 + hive_generator: ^0.6.0 + flutter_launcher_icons: ^0.7.4 + +flutter_icons: + image_path: "assets/images/app_logo.png" + android: true + ios: true + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + assets: + - assets/images/ + - assets/node_list.yml + - assets/text/ + - assets/faq/ + + fonts: + - family: Lato + fonts: + - asset: assets/fonts/Lato-Regular.ttf + - asset: assets/fonts/Lato-Bold.ttf + - asset: assets/fonts/Lato-Semibold.ttf + + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb new file mode 100644 index 000000000..fe012a1dc --- /dev/null +++ b/res/values/strings_de.arb @@ -0,0 +1,334 @@ +{ + "welcome" : "Willkommen\nzu CAKE WALLET", + "first_wallet_text" : "tolle Brieftasche\nzum Monero", + "please_make_selection" : "Bitte treffen Sie unten eine Auswahl zu\nErstellen oder Wiederherstellen Ihrer Brieftasche.", + "create_new" : "Erstelle neu", + "restore_wallet" : "Wallet wiederherstellen", + + + "accounts" : "Konten", + "edit" : "Bearbeiten", + "account" : "Konto", + "add" : "Hinzufügen", + + + "address_book" : "Adressbuch", + "contact" : "Kontakt", + "please_select" : "Bitte auswählen:", + "cancel" : "Abbrechen", + "ok" : "OK", + "contact_name" : "Name des Ansprechpartners", + "reset" : "Zurücksetzen", + "save" : "speichern", + + + "authenticated" : "Authentifiziert", + "authentication" : "Authentifizierung", + "failed_authentication" : "Authentifizierung fehlgeschlagen. ${state_error}", + + + "wallet_menu" : "Brieftaschen-Menü", + "Blocks_remaining" : "${status} Verbleibende Blöcke", + "please_try_to_connect_to_another_node" : "Bitte versuchen Sie, eine Verbindung zu einem anderen Knoten herzustellen", + "xmr_hidden" : "XMR versteckt", + "xmr_available_balance" : "XMR verfügbares Guthaben", + "xmr_full_balance" : "XMR Volle Balance", + "send" : "Senden", + "receive" : "Erhalten", + "transactions" : "Transaktionen", + "incoming" : "Eingehend", + "outgoing" : "Ausgehend", + "transactions_by_date" : "Transaktionen nach Datum", + "trades" : "Handel", + "filters" : "Filter", + "today" : "Heute", + "yesterday" : "Gestern", + "received" : "Empfangen", + "sent" : "Geschickt", + "pending" : " (steht aus)", + "rescan" : "Erneut scannen", + "reconnect" : "Erneut verbinden", + "wallets" : "Wallets", + "show_seed" : "Seed zeigen", + "show_keys" : "Schlüssel anzeigen", + "address_book_menu" : "Adressbuch", + "reconnection" : "Wiederverbindung", + "reconnect_alert_text" : "Sind Sie sicher, dass Sie die Verbindung wiederherstellen möchten?", + + + "exchange" : "Austausch", + "clear" : "klar", + "change_exchange_provider" : "Wechseln Sie den Exchange-Anbieter", + "you_will_send" : "Du wirst senden", + "you_will_get" : "Sie erhalten", + "amount_is_guaranteed" : "Der Empfangsbetrag ist garantiert", + "amount_is_estimate" : "Der empfangene Betrag ist eine Schätzung", + "powered_by" : "Unterstützt von ${title}", + "error" : "Error", + "estimated" : "Geschätzt", + "min_value" : "Mindest: ${value} ${currency}", + "max_value" : "Max: ${value} ${currency}", + "change_currency" : "Währung ändern", + + + "copy_id" : "ID kopieren", + "exchange_result_write_down_trade_id" : "Bitte kopieren oder notieren Sie die Handel-ID, um fortzufahren.", + "trade_id" : "Handel-ID:\n${id}", + "copied_to_clipboard" : "In die Zwischenablage kopiert", + "saved_the_trade_id" : "Ich habe die Geschäfts-ID gespeichert", + "fetching" : "holen", + "id" : "ID: ", + "amount" : "Menge: ", + "payment_id" : "Zahlungs ID: ", + "status" : "Status: ", + "offer_expires_in" : "Angebot läuft ab in: ", + "trade_is_powered_by" : "Dieser Handel wird betrieben von ${provider}", + "copy_address" : "Adresse kopieren", + "exchange_result_confirm" : "Durch Drücken von Bestätigen wird gesendet ${fetchingLabel} ${from} von Ihrer Brieftasche aus angerufen ${walletName} an die oben angegebene Adresse. Oder Sie können von Ihrem externen Portemonnaie an die oben angegebene Adresse / QR-Code senden.\n\nBitte bestätigen Sie, um fortzufahren, oder gehen Sie zurück, um die Beträge zu änderns.\n\n", + "exchange_result_description" : "Bitte senden ${fetchingLabel} ${from} an die oben angegebene Adresse.\n\n'", + "exchange_result_write_down_ID" : "*Bitte kopieren oder notieren Sie Ihren oben gezeigten Ausweis.", + "confirm" : "Bestätigen", + "confirm_sending" : "Bestätigen Sie das Senden", + "commit_transaction_amount_fee" : "Transaktion festschreiben\nMenge: ${amount}\nGebühr: ${fee}", + "sending" : "Senden", + "transaction_sent" : "Transaktion gesendet!", + "expired" : "Abgelaufen", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "Senden XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "PIN eingeben", + "loading_your_wallet" : "Laden Sie Ihre Brieftasche", + + + "new_wallet" : "Neues Wallet", + "wallet_name" : "Walletname", + "continue_text" : "Fortsetzen", + + + "node_new" : "Neuer Knoten", + "node_address" : "Knotenadresse", + "node_port" : "Knotenport", + "login" : "Einloggen", + "password" : "Passwort", + "nodes" : "Knoten", + "node_reset_settings_title" : "Einstellungen zurücksetzen", + "nodes_list_reset_to_default_message" : "Möchten Sie die Einstellungen wirklich auf die Standardeinstellungen zurücksetzen?", + "change_current_node" : "Möchten Sie den aktuellen Knoten wirklich auf ändern? ${node}?", + "change" : "Veränderung", + "remove_node" : "Knoten entfernen", + "remove_node_message" : "Möchten Sie den ausgewählten Knoten wirklich entfernen?", + "remove" : "Löschen", + "delete" : "Löschen", + + + "use" : "Verwenden ", + "digit_pin" : "-stelliger Pin", + + + "share_address" : "Adresse teilen ", + "receive_amount" : "Menge", + "subaddresses" : "Unteradressen", + + + "restore_restore_wallet" : "Wallet wiederherstellen", + "restore_title_from_seed_keys" : "Vom Seed / Schlüssel wiederherstellen", + "restore_description_from_seed_keys" : "Holen Sie sich Ihr Wallet von Seed / Schlüsseln zurück, die Sie an einem sicheren Ort aufbewahrt haben", + "restore_next" : "Nächster", + "restore_title_from_backup" : "Aus einer Sicherungsdatei wiederherstellen", + "restore_description_from_backup" : "Sie können die gesamte Cake Wallet-App von wiederherstellen \nIhre Sicherungsdatei", + "restore_seed_keys_restore" : "Seed / Schlüssel wiederherstellen", + "restore_title_from_seed" : "Aus Seed wiederherstellen", + "restore_description_from_seed" : "Stellen Sie Ihr Wallet aus den 25 Wörtern wieder her\noder 13-Wort-Kombinationscode", + "restore_title_from_keys" : "Wiederherstellen von Schlüsseln", + "restore_description_from_keys" : "Stellen Sie Ihr Wallet von generiert wieder her\nTastenanschläge, die von Ihren privaten Schlüsseln gespeichert wurden", + "restore_wallet_name" : "Walletname", + "restore_address" : "Adresse", + "restore_view_key_private" : "Schlüssel anzeigen(geheim)", + "restore_spend_key_private" : "Schlüssel ausgeben (geheim)", + "restore_recover" : "Genesen", + "restore_wallet_restore_description" : "Beschreibung zur Wiederherstellung der Brieftasche", + + + "seed_title" : "Seed", + "seed_share" : "Teilen Sie Seed", + "copy" : "Kopieren", + + + "seed_alert_first_text" : "Die nächste Seite wird angezeigt\nDu bist ein Seed.", + "seed_alert_second_text" : "Bitte schreiben Sie diese nur in\nFalls Sie Ihr Telefon verlieren oder abwischen.", + "seed_alert_third_text" : "Sie können den Seed auch wieder sehen\nin dem ", + "seed_alert_settings" : "die Einstellungen", + "seed_alert_menu" : " Speisekarte.", + "seed_alert_understand" : "ich verstehe", + + + "send_title" : "Senden Sie Monero", + "send_your_wallet" : "Deine Geldbörse", + "send_monero_address" : "Monero-Adresse", + "send_payment_id" : "Zahlungs ID (wahlweise)", + "all" : "ALLE", + "send_error_minimum_value" : "Der Mindestbetrag beträgt 0,01", + "send_error_currency" : "Die Währung kann nur Zahlen enthalten", + "send_estimated_fee" : "Geschätzte Gebühr:", + "send_priority" : "Derzeit ist die Gebühr auf festgelegt ${transactionPriority} priorität.\nDie Transaktionspriorität kann in den Einstellungen angepasst werden", + "send_creating_transaction" : "Transaktion erstellen", + + + "settings_title" : "die Einstellungen", + "settings_nodes" : "Knoten", + "settings_current_node" : "Aktueller Knoten", + "settings_wallets" : "Brieftaschen", + "settings_display_balance_as" : "Kontostand anzeigen als", + "settings_currency" : "Währung", + "settings_fee_priority" : "Gebührenpriorität", + "settings_save_recipient_address" : "Empfängeradresse speichern", + "settings_personal" : "persönlich", + "settings_change_pin" : "PIN ändern", + "settings_change_language" : "Sprache ändern", + "settings_allow_biometrical_authentication" : "Biometrische Authentifizierung zulassen", + "settings_dark_mode" : "Dunkler Modus", + "settings_transactions" : "Transaktionen", + "settings_trades" : "Handel", + "settings_display_on_dashboard_list" : "Anzeige in der Dashboard-Liste", + "settings_all" : "ALLE", + "settings_only_trades" : "Nur Trades", + "settings_only_transactions" : "Nur Transaktionen", + "settings_none" : "Keiner", + "settings_support" : "Unterstützung", + "settings_terms_and_conditions" : "Geschäftsbedingungen", + "pin_is_incorrect" : "PIN ist falsch", + + + "setup_pin" : "PIN einrichten", + "enter_your_pin_again" : "Geben Sie Ihre PIN erneut ein", + "setup_successful" : "Ihre PIN wurde erfolgreich eingerichtet!", + + + "wallet_keys" : "Wallet schlüssel", + "view_key_private" : "Schlüssel anzeigen (eheim)", + "view_key_public" : "Schlüssel anzeigen (Öffentlichkeit)", + "spend_key_private" : "Schlüssel ausgeben (geheim)", + "spend_key_public" : "Schlüssel ausgeben (Öffentlichkeit)", + "copied_key_to_clipboard" : "Kopiert ${key} Zur Zwischenablage", + + + "new_subaddress_title" : "Neue Unteradresse", + "new_subaddress_label_name" : "Markenname", + "new_subaddress_create" : "Erstellen", + + + "subaddress_title" : "Unteradressenliste", + + + "trade_details_title" : "Handel Einzelheiten", + "trade_details_id" : "ID", + "trade_details_state" : "Zustand", + "trade_details_fetching" : "Holen", + "trade_details_provider" : "Anbieter", + "trade_details_created_at" : "Hergestellt in", + "trade_details_pair" : "Paar", + "trade_details_copied" : "${title} in die Zwischenablage kopiert", + + + "trade_history_title" : "Handelsgeschichte", + + + "transaction_details_title" : "Transaktionsdetails", + "transaction_details_transaction_id" : "Transaktions-ID", + "transaction_details_date" : "Datum", + "transaction_details_height" : "Höhe", + "transaction_details_amount" : "Menge", + "transaction_details_copied" : "${title} in die Zwischenablage kopiert", + "transaction_details_recipient_address" : "Empfängeradresse", + + + "wallet_list_title" : "Monero Wallet", + "wallet_list_create_new_wallet" : "Neue Wallet erstellen", + "wallet_list_restore_wallet" : "Wallet wiederherstellen", + "wallet_list_load_wallet" : "Wallet einlegen", + "wallet_list_loading_wallet" : "Wird geladen ${wallet_name} Wallet", + "wallet_list_failed_to_load" : "Laden fehlgeschlagen ${wallet_name} Wallet. ${error}", + "wallet_list_removing_wallet" : "Entfernen ${wallet_name} Wallet", + "wallet_list_failed_to_remove" : "Fehler beim Entfernen ${wallet_name} Wallet. ${error}", + + + "widgets_address" : "Adresse", + "widgets_restore_from_blockheight" : "Aus Blockhöhe wiederherstellen", + "widgets_restore_from_date" : "Vom Datum wiederherstellen", + "widgets_or" : "oder", + "widgets_seed" : "Seed", + + + "router_no_route" : "Keine Route definiert für ${name}", + + + "error_text_account_name" : "Der Kontoname darf nur Wallet und Zahlen enthalten\nund muss zwischen 1 und 15 Zeichen lang sein", + "error_text_contact_name" : "Kontaktname darf nicht enthalten sein ` , ' \" Symbole\nund muss zwischen 1 und 32 Zeichen lang sein", + "error_text_address" : "Die Walletadresse muss dem Typ entsprechen\nder Kryptowährung", + "error_text_node_address" : "Bitte geben Sie eine iPv4-Adresse ein", + "error_text_node_port" : "Der Knotenport kann nur Nummern zwischen 0 und 65535 enthalten", + "error_text_payment_id" : "Die Zahlungs-ID kann nur 16 bis 64 hexadezimale Zeichen enthalten", + "error_text_xmr" : "Der XMR-Wert kann das verfügbare Guthaben nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 12 sein", + "error_text_fiat" : "Der Wert des Betrags darf den verfügbaren Kontostand nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 2 sein", + "error_text_subaddress_name" : "Der Name der Unteradresse darf nicht enthalten sein ` , ' \" symbole\nund muss zwischen 1 und 20 Zeichen lang sein", + "error_text_amount" : "Betrag kann nur Zahlen enthalten", + "error_text_wallet_name" : "Der Wallet darf nur Buchstaben und Zahlen enthalten\nund muss zwischen 1 und 15 Zeichen lang sein", + "error_text_keys" : "Walletschlüssel können nur 64 hexadezimale Zeichen enthalten", + "error_text_crypto_currency" : "Die Anzahl der Nachkommastellen\nmuss kleiner oder gleich 12 sein.", + + + "auth_store_ban_timeout" : "Auszeit verbieten", + "auth_store_banned_for" : "Gebannt für ", + "auth_store_banned_minutes" : " Protokoll", + "auth_store_incorrect_password" : "Falsches PIN", + "wallet_store_monero_wallet" : "Monero Wallet", + "wallet_restoration_store_incorrect_seed_length" : "Falsche Samenlänge", + + + "full_balance" : "Volle Balance", + "available_balance" : "Verfügbares Guthaben", + "hidden_balance" : "Verstecktes Gleichgewicht", + + + "sync_status_syncronizing" : "SYNCHRONISIERUNG", + "sync_status_syncronized" : "SYNCHRONISIERT", + "sync_status_not_connected" : "NICHT VERBUNDEN", + "sync_status_starting_sync" : "STARTEN DER SYNCHRONISIERUNG", + "sync_status_failed_connect" : "Verbindung zum Knoten fehlgeschlagen", + "sync_status_connecting" : "ANSCHLUSS", + "sync_status_connected" : "IN VERBINDUNG GEBRACHT", + + + "transaction_priority_slow" : "Schleppend", + "transaction_priority_regular" : "Regulär", + "transaction_priority_medium" : "Mittel", + "transaction_priority_fast" : "Schnell", + "transaction_priority_fastest" : "Am schnellsten", + + + "trade_for_not_created" : "Handel für ${title} wird nicht erstellt.", + "trade_not_created" : "Handel nicht angelegt.", + "trade_id_not_found" : "Handel ${tradeId} von ${title} nicht gefunden.", + "trade_not_found" : "Handel nicht gefunden.", + + + "trade_state_pending" : "Steht aus", + "trade_state_confirming" : "Bestätigung", + "trade_state_trading" : "Handel", + "trade_state_traded" : "Handeln", + "trade_state_complete" : "Komplett", + "trade_state_to_be_created" : "Geschaffen werden", + "trade_state_unpaid" : "Unbezahlt", + "trade_state_underpaid" : "Unterbezahlt", + "trade_state_paid_unconfirmed" : "Unbestätigt bezahlt", + "trade_state_paid" : "Bezahlt", + "trade_state_btc_sent" : "geschickt", + "trade_state_timeout" : "Auszeit", + "trade_state_created" : "Erstellt", + "trade_state_finished" : "Fertig", + + "change_language" : "Sprache ändern", + "change_language_to" : "Ändern Sie die Sprache in ${language}?"} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb new file mode 100644 index 000000000..07c3a49c5 --- /dev/null +++ b/res/values/strings_en.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "WELCOME\nTO CAKE WALLET", + "first_wallet_text" : "Awesome wallet\nfor Monero", + "please_make_selection" : "Please make selection below to\ncreate or recover your wallet.", + "create_new" : "Create new", + "restore_wallet" : "Restore wallet", + + + "accounts" : "Accounts", + "edit" : "Edit", + "account" : "Account", + "add" : "Add", + + + "address_book" : "Address Book", + "contact" : "Contact", + "please_select" : "Please select:", + "cancel" : "Cancel", + "ok" : "OK", + "contact_name" : "Contact Name", + "reset" : "Reset", + "save" : "Save", + + + "authenticated" : "Authenticated", + "authentication" : "Authentication", + "failed_authentication" : "Failed authentication. ${state_error}", + + + "wallet_menu" : "Menu", + "Blocks_remaining" : "${status} Blocks Remaining", + "please_try_to_connect_to_another_node" : "Please try to connect to another node", + "xmr_hidden" : "XMR Hidden", + "xmr_available_balance" : "XMR Available Balance", + "xmr_full_balance" : "XMR Full Balance", + "send" : "Send", + "receive" : "Receive", + "transactions" : "Transactions", + "incoming" : "Incoming", + "outgoing" : "Outgoing", + "transactions_by_date" : "Transactions by date", + "trades" : "Trades", + "filters" : "Filters", + "today" : "Today", + "yesterday" : "Yesterday", + "received" : "Received", + "sent" : "Sent", + "pending" : " (pending)", + "rescan" : "Rescan", + "reconnect" : "Reconnect", + "wallets" : "Wallets", + "show_seed" : "Show seed", + "show_keys" : "Show keys", + "address_book_menu" : "Address book", + "reconnection" : "Reconnection", + "reconnect_alert_text" : "Are you sure to reconnect?", + + + "exchange" : "Exchange", + "clear" : "Clear", + "change_exchange_provider" : "Change Exchange Provider", + "you_will_send" : "You will send", + "you_will_get" : "You will get", + "amount_is_guaranteed" : "The receive amount is guaranteed", + "amount_is_estimate" : "The receive amount is an estimate", + "powered_by" : "Powered by ${title}", + "error" : "Error", + "estimated" : "Estimated", + "min_value" : "Min: ${value} ${currency}", + "max_value" : "Max: ${value} ${currency}", + "change_currency" : "Change Currency", + + + "copy_id" : "Copy ID", + "exchange_result_write_down_trade_id" : "Please copy or write down the trade ID to continue.", + "trade_id" : "Trade ID:\n${id}", + "copied_to_clipboard" : "Copied to Clipboard", + "saved_the_trade_id" : "I've saved the trade ID", + "fetching" : "Fetching", + "id" : "ID: ", + "amount" : "Amount: ", + "payment_id" : "Payment ID: ", + "status" : "Status: ", + "offer_expires_in" : "Offer expires in: ", + "trade_is_powered_by" : "This trade is powered by ${provider}", + "copy_address" : "Copy Address", + "exchange_result_confirm" : "By pressing confirm, you will be sending ${fetchingLabel} ${from} from your wallet called ${walletName} to the address shown above. Or you can send from your external wallet to the above address/QR code.\n\nPlease press confirm to continue or go back to change the amounts.\n\n", + "exchange_result_description" : "Please send ${fetchingLabel} ${from} to the address shown above.\n\n", + "exchange_result_write_down_ID" : "*Please copy or write down your ID shown above.", + "confirm" : "Confirm", + "confirm_sending" : "Confirm sending", + "commit_transaction_amount_fee" : "Commit transaction\nAmount: ${amount}\nFee: ${fee}", + "sending" : "Sending", + "transaction_sent" : "Transaction sent!", + "expired" : "Expired", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "Send XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "Enter your PIN", + "loading_your_wallet" : "Loading your wallet", + + + "new_wallet" : "New Wallet", + "wallet_name" : "Wallet name", + "continue_text" : "Continue", + + + "node_new" : "New Node", + "node_address" : "Node Address", + "node_port" : "Node port", + "login" : "Login", + "password" : "Password", + "nodes" : "Nodes", + "node_reset_settings_title" : "Reset settings", + "nodes_list_reset_to_default_message" : "Are you sure that you want to reset settings to default?", + "change_current_node" : "Are you sure to change current node to ${node}?", + "change" : "Change", + "remove_node" : "Remove node", + "remove_node_message" : "Are you sure that you want to remove selected node?", + "remove" : "Remove", + "delete" : "Delete", + + + "use" : "Use ", + "digit_pin" : "-digit Pin", + + + "share_address" : "Share address", + "receive_amount" : "Amount", + "subaddresses" : "Subaddresses", + + + "restore_restore_wallet" : "Restore Wallet", + "restore_title_from_seed_keys" : "Restore from seed/keys", + "restore_description_from_seed_keys" : "Get back your wallet from seed/keys that you've saved to secure place", + "restore_next" : "Next", + "restore_title_from_backup" : "Restore from a back-up file", + "restore_description_from_backup" : "You can restore the whole Cake Wallet app from\nyour back-up file", + "restore_seed_keys_restore" : "Seed/Keys Restore", + "restore_title_from_seed" : "Restore from seed", + "restore_description_from_seed" : "Restore your wallet from either the 25 word\nor 13 word combination code", + "restore_title_from_keys" : "Restore from keys", + "restore_description_from_keys" : "Restore your wallet from generated\nkeystrokes saved from your private keys", + "restore_wallet_name" : "Wallet name", + "restore_address" : "Address", + "restore_view_key_private" : "View key (private)", + "restore_spend_key_private" : "Spend key (private)", + "restore_recover" : "Restore", + "restore_wallet_restore_description" : "Wallet restore description", + + + "seed_title" : "Seed", + "seed_share" : "Share seed", + "copy" : "Copy", + + + "seed_alert_first_text" : "The next page will show\nyou a seed.", + "seed_alert_second_text" : "Please write these down just in\ncase you lose or wipe your phone.", + "seed_alert_third_text" : "You can also see the seed again\nin the ", + "seed_alert_settings" : "settings", + "seed_alert_menu" : " menu.", + "seed_alert_understand" : "I understand", + + + "send_title" : "Send Monero", + "send_your_wallet" : "Your wallet", + "send_monero_address" : "Monero address", + "send_payment_id" : "Payment ID (optional)", + "all" : "ALL", + "send_error_minimum_value" : "Minimum value of amount is 0.01", + "send_error_currency" : "Currency can only contain numbers", + "send_estimated_fee" : "Estimated fee:", + "send_priority" : "Currently the fee is set at ${transactionPriority} priority.\nTransaction priority can be adjusted in the settings", + "send_creating_transaction" : "Creating transaction", + + + "settings_title" : "Settings", + "settings_nodes" : "Nodes", + "settings_current_node" : "Current node", + "settings_wallets" : "Wallets", + "settings_display_balance_as" : "Display balance as", + "settings_currency" : "Currency", + "settings_fee_priority" : "Fee priority", + "settings_save_recipient_address" : "Save recipient address", + "settings_personal" : "Personal", + "settings_change_pin" : "Change PIN", + "settings_change_language" : "Change language", + "settings_allow_biometrical_authentication" : "Allow biometrical authentication", + "settings_dark_mode" : "Dark mode", + "settings_transactions" : "Transactions", + "settings_trades" : "Trades", + "settings_display_on_dashboard_list" : "Display on dashboard list", + "settings_all" : "ALL", + "settings_only_trades" : "Only trades", + "settings_only_transactions" : "Only transactions", + "settings_none" : "None", + "settings_support" : "Support", + "settings_terms_and_conditions" : "Terms and conditions", + "pin_is_incorrect" : "PIN is incorrect", + + + "setup_pin" : "Setup PIN", + "enter_your_pin_again" : "Enter your pin again", + "setup_successful" : "Your PIN has been set up successfully!", + + + "wallet_keys" : "Wallet keys", + "view_key_private" : "View key (private)", + "view_key_public" : "View key (public)", + "spend_key_private" : "Spend key (private)", + "spend_key_public" : "Spend key (public)", + "copied_key_to_clipboard" : "Copied ${key} to Clipboard", + + + "new_subaddress_title" : "New subaddress", + "new_subaddress_label_name" : "Label name", + "new_subaddress_create" : "Create", + + + "subaddress_title" : "Subaddress list", + + + "trade_details_title" : "Trade Details", + "trade_details_id" : "ID", + "trade_details_state" : "State", + "trade_details_fetching" : "Fetching", + "trade_details_provider" : "Provider", + "trade_details_created_at" : "Created at", + "trade_details_pair" : "Pair", + "trade_details_copied" : "${title} copied to Clipboard", + + + "trade_history_title" : "Trade history", + + + "transaction_details_title" : "Transaction Details", + "transaction_details_transaction_id" : "Transaction ID", + "transaction_details_date" : "Date", + "transaction_details_height" : "Height", + "transaction_details_amount" : "Amount", + "transaction_details_copied" : "${title} copied to Clipboard", + "transaction_details_recipient_address" : "Recipient address", + + + "wallet_list_title" : "Monero Wallet", + "wallet_list_create_new_wallet" : "Create New Wallet", + "wallet_list_restore_wallet" : "Restore Wallet", + "wallet_list_load_wallet" : "Load wallet", + "wallet_list_loading_wallet" : "Loading ${wallet_name} wallet", + "wallet_list_failed_to_load" : "Failed to load ${wallet_name} wallet. ${error}", + "wallet_list_removing_wallet" : "Removing ${wallet_name} wallet", + "wallet_list_failed_to_remove" : "Failed to remove ${wallet_name} wallet. ${error}", + + + "widgets_address" : "Address", + "widgets_restore_from_blockheight" : "Restore from blockheight", + "widgets_restore_from_date" : "Restore from date", + "widgets_or" : "or", + "widgets_seed" : "Seed", + + + "router_no_route" : "No route defined for ${name}", + + + "error_text_account_name" : "Account name can only contain letters, numbers\nand must be between 1 and 15 characters long", + "error_text_contact_name" : "Contact name can't contain ` , ' \" symbols\nand must be between 1 and 32 characters long", + "error_text_address" : "Wallet address must correspond to the type\nof cryptocurrency", + "error_text_node_address" : "Please enter a iPv4 address", + "error_text_node_port" : "Node port can only contain numbers between 0 and 65535", + "error_text_payment_id" : "Payment ID can only contain from 16 to 64 chars in hex", + "error_text_xmr" : "XMR value can't exceed available balance.\nThe number of fraction digits must be less or equal to 12", + "error_text_fiat" : "Value of amount can't exceed available balance.\nThe number of fraction digits must be less or equal to 2", + "error_text_subaddress_name" : "Subaddress name can't contain ` , ' \" symbols\nand must be between 1 and 20 characters long", + "error_text_amount" : "Amount can only contain numbers", + "error_text_wallet_name" : "Wallet name can only contain letters, numbers\nand must be between 1 and 15 characters long", + "error_text_keys" : "Wallet keys can only contain 64 chars in hex", + "error_text_crypto_currency" : "The number of fraction digits\nmust be less or equal to 12", + + + "auth_store_ban_timeout" : "ban_timeout", + "auth_store_banned_for" : "Banned for ", + "auth_store_banned_minutes" : " minutes", + "auth_store_incorrect_password" : "Wrong PIN", + "wallet_store_monero_wallet" : "Monero Wallet", + "wallet_restoration_store_incorrect_seed_length" : "Incorrect seed length", + + + "full_balance" : "Full Balance", + "available_balance" : "Available Balance", + "hidden_balance" : "Hidden Balance", + + + "sync_status_syncronizing" : "SYNCRONIZING", + "sync_status_syncronized" : "SYNCHRONIZED", + "sync_status_not_connected" : "NOT CONNECTED", + "sync_status_starting_sync" : "STARTING SYNC", + "sync_status_failed_connect" : "FAILED CONNECT TO THE NODE", + "sync_status_connecting" : "CONNECTING", + "sync_status_connected" : "CONNECTED", + + + "transaction_priority_slow" : "Slow", + "transaction_priority_regular" : "Regular", + "transaction_priority_medium" : "Medium", + "transaction_priority_fast" : "Fast", + "transaction_priority_fastest" : "Fastest", + + + "trade_for_not_created" : "Trade for ${title} is not created.", + "trade_not_created" : "Trade not created.", + "trade_id_not_found" : "Trade ${tradeId} of ${title} not found.", + "trade_not_found" : "Trade not found.", + + + "trade_state_pending" : "Pending", + "trade_state_confirming" : "Confirming", + "trade_state_trading" : "Trading", + "trade_state_traded" : "Traded", + "trade_state_complete" : "Complete", + "trade_state_to_be_created" : "To be created", + "trade_state_unpaid" : "Unpaid", + "trade_state_underpaid" : "Underpaid", + "trade_state_paid_unconfirmed" : "Paid unconfirmed", + "trade_state_paid" : "Paid", + "trade_state_btc_sent" : "Btc sent", + "trade_state_timeout" : "Timeout", + "trade_state_created" : "Created", + "trade_state_finished" : "Finished", + + "change_language" : "Change language", + "change_language_to" : "Change language to ${language}?" +} \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb new file mode 100644 index 000000000..4051f5e3e --- /dev/null +++ b/res/values/strings_es.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "Bienvenido\n CAKE WALLET", + "first_wallet_text" : "Impresionante billetera\npara Monero", + "please_make_selection" : "Seleccione a continuación para\ncrear o recuperar su billetera.", + "create_new" : "Crear nuevo", + "restore_wallet" : "Restaurar billetera", + + + "accounts" : "Cuentas", + "edit" : "Editar", + "account" : "Cuenta", + "add" : "Añadir", + + + "address_book" : "Libreta de direcciones", + "contact" : "Contacto", + "please_select" : "Por favor seleccione:", + "cancel" : "Cancelar", + "ok" : "OK", + "contact_name" : "Nombre de contacto", + "reset" : "Reiniciar", + "save" : "Salvar", + + + "authenticated" : "Autenticados", + "authentication" : "Autenticación", + "failed_authentication" : "Autenticación fallida. ${state_error}", + + + "wallet_menu" : "Menú de billetera", + "Blocks_remaining" : "${status} Bloques restantes", + "please_try_to_connect_to_another_node" : "Intenta conectarte a otro nodo", + "xmr_hidden" : "XMR Oculto", + "xmr_available_balance" : "XMR Available Balance", + "xmr_full_balance" : "XMR Saldo disponible", + "send" : "Enviar", + "receive" : "Recibir", + "transactions" : "Actas", + "incoming" : "Entrante", + "outgoing" : "Saliente", + "transactions_by_date" : "Transacciones por fecha", + "trades" : "Cambios", + "filters" : "Filtros", + "today" : "Hoy", + "yesterday" : "Ayer", + "received" : "Recibido", + "sent" : "Expedido", + "pending" : " (pendiente)", + "rescan" : "Reescanear", + "reconnect" : "Volver a conectar", + "wallets" : "Carteras", + "show_seed" : "Mostrar semilla", + "show_keys" : "Mostrar llaves", + "address_book_menu" : "Libreta de direcciones", + "reconnection" : "Reconexión", + "reconnect_alert_text" : "¿Estás seguro de reconectar?", + + + "exchange" : "Intercambiar", + "clear" : "Claro", + "change_exchange_provider" : "Cambiar proveedor de intercambio", + "you_will_send" : "Enviarás", + "you_will_get" : "Conseguirás", + "amount_is_guaranteed" : "El monto recibido está garantizado", + "amount_is_estimate" : "El monto recibido es un estimado", + "powered_by" : "Energizado por ${title}", + "error" : "Error", + "estimated" : "Estimado", + "min_value" : "Min: ${value} ${currency}", + "max_value" : "Max: ${value} ${currency}", + "change_currency" : "Cambiar moneda", + + + "copy_id" : "Copiar ID", + "exchange_result_write_down_trade_id" : "Por favor, copia o escribe el ID.", + "trade_id" : "Comercial ID:\n${id}", + "copied_to_clipboard" : "Copiado al portapapeles", + "saved_the_trade_id" : "He salvado comercial ID", + "fetching" : "Cargando", + "id" : "ID: ", + "amount" : "Cantidad: ", + "payment_id" : "ID de pago: ", + "status" : "Estado: ", + "offer_expires_in" : "Oferta expira en: ", + "trade_is_powered_by" : "Este comercio es impulsado por ${provider}", + "copy_address" : "Copiar dirección ", + "exchange_result_confirm" : "Al presionar confirmar, enviará ${fetchingLabel} ${from} desde su billetera llamada ${walletName} a la dirección que se muestra arriba. O puede enviar desde su billetera externa a la dirección / código QR anterior.\n\nPresione confirmar para continuar o regrese para cambiar los montos.\n\n", + "exchange_result_description" : "Envíe ${fetchingLabel} ${from} a la dirección que se muestra arriba.\n\n'", + "exchange_result_write_down_ID" : "*Copie o escriba su identificación que se muestra arriba.", + "confirm" : "Confirmar", + "confirm_sending" : "Confirmar envío", + "commit_transaction_amount_fee" : "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}", + "sending" : "Enviando", + "transaction_sent" : "Transacción enviada!", + "expired" : "Muerto", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "Enviar XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "Introduce tu PIN", + "loading_your_wallet" : "Cargando tu billetera", + + + "new_wallet" : "Nueva billetera", + "wallet_name" : "Nombre de la billetera", + "continue_text" : "Continuar", + + + "node_new" : "Nuevo nodo", + "node_address" : "Dirección de nodo", + "node_port" : "Puerto de nodo", + "login" : "Iniciar sesión", + "password" : "Contraseña", + "nodes" : "Nodos", + "node_reset_settings_title" : "Reiniciar ajustes", + "nodes_list_reset_to_default_message" : "¿Está seguro de que desea restablecer la configuración predeterminada?", + "change_current_node" : "¿Está seguro de cambiar el nodo actual a ${node}?", + "change" : "Cambio", + "remove_node" : "Eliminar nodo", + "remove_node_message" : "¿Está seguro de que desea eliminar el nodo seleccionado?", + "remove" : "Retirar", + "delete" : "Borrar", + + + "use" : "Utilizar ", + "digit_pin" : "-dígito Pin", + + + "share_address" : "Compartir dirección", + "receive_amount" : "Cantidad", + "subaddresses" : "Subdirecciones", + + + "restore_restore_wallet" : "Recuperar Cartera", + "restore_title_from_seed_keys" : "Restaurar desde semilla/claves", + "restore_description_from_seed_keys" : "Recupere su billetera de las semillas/claves que ha guardado en un lugar seguro", + "restore_next" : "Próximo", + "restore_title_from_backup" : "Restaurar desde un archivo de respaldo", + "restore_description_from_backup" : "Puede restaurar toda la aplicación Cake Wallet desde \nysu archivo de respaldo", + "restore_seed_keys_restore" : "Restauración de semillas / llaves", + "restore_title_from_seed" : "De la semilla", + "restore_description_from_seed" : "Restaure su billetera desde el código de combinación de 25 palabras \ni de 13 palabras", + "restore_title_from_keys" : "De las claves", + "restore_description_from_keys" : "Restaure su billetera de las pulsaciones de teclas generadas\nguardadas de sus claves privadas", + "restore_wallet_name" : "Nombre de la billetera", + "restore_address" : "Dirección", + "restore_view_key_private" : "View clave (privado)", + "restore_spend_key_private" : "Spend clave (privado)", + "restore_recover" : "Recuperar", + "restore_wallet_restore_description" : "Restaurar billetera", + + + "seed_title" : "Semilla", + "seed_share" : "Compartir semillas", + "copy" : "Dupdo", + + + "seed_alert_first_text" : "La siguiente página te mostrará \nuna semilla.", + "seed_alert_second_text" : "Anótelos solo en caso de que pierda\no limpie su teléfono.", + "seed_alert_third_text" : "También puede ver la semilla nuevamente \nen el ", + "seed_alert_settings" : "de configuración", + "seed_alert_menu" : " menú .", + "seed_alert_understand" : "Entiendo", + + + "send_title" : "Enviar Monero", + "send_your_wallet" : "Tu billetera", + "send_monero_address" : "Dirección de Monero", + "send_payment_id" : "ID de pago (opcional)", + "all" : "TODOS", + "send_error_minimum_value" : "El valor mínimo de la cantidad es 0.01", + "send_error_currency" : "La moneda solo puede contener números", + "send_estimated_fee" : "Tarifa estimada:", + "send_priority" : "Actualmente la tarifa se establece en ${transactionPriority} prioridad.\nLa prioridad de la transacción se puede ajustar en la configuración", + "send_creating_transaction" : "Creando transacción", + + + "settings_title" : "Configuraciones", + "settings_nodes" : "Nodos", + "settings_current_node" : "Nodo actual", + "settings_wallets" : "Carteras", + "settings_display_balance_as" : "Mostrar saldo como", + "settings_currency" : "Moneda", + "settings_fee_priority" : "Prioridad de tasa", + "settings_save_recipient_address" : "Guardar dirección del destinatario", + "settings_personal" : "Personal", + "settings_change_pin" : "Cambiar PIN", + "settings_change_language" : "Cambiar idioma", + "settings_allow_biometrical_authentication" : "Permitir autenticación biométrica", + "settings_dark_mode" : "Modo oscuro", + "settings_transactions" : "Transacciones", + "settings_trades" : "Comercia", + "settings_display_on_dashboard_list" : "Mostrar en la lista del tablero", + "settings_all" : "TODOS", + "settings_only_trades" : "Solo comercia", + "settings_only_transactions" : "Solo transacciones", + "settings_none" : "Ninguno", + "settings_support" : "Apoyo", + "settings_terms_and_conditions" : "Términos y Condiciones", + "pin_is_incorrect" : "PIN es incorrecto", + + + "setup_pin" : "PIN de configuración", + "enter_your_pin_again" : "Ingrese su PIN nuevamente", + "setup_successful" : "Su PIN se ha configurado correctamente!", + + + "wallet_keys" : "Billetera clave", + "view_key_private" : "View clave (privado)", + "view_key_public" : "View clave (público)", + "spend_key_private" : "Spend clave (privado)", + "spend_key_public" : "Spend clave (público)", + "copied_key_to_clipboard" : "Copiado ${key} al portapapeles", + + + "new_subaddress_title" : "Nueva subdirección", + "new_subaddress_label_name" : "Nombre de etiqueta", + "new_subaddress_create" : "Crear", + + + "subaddress_title" : "Lista de subdirecciones", + + + "trade_details_title" : "Detalles comerciales", + "trade_details_id" : "ID", + "trade_details_state" : "Estado", + "trade_details_fetching" : "Cargando", + "trade_details_provider" : "Proveedor", + "trade_details_created_at" : "Creado en", + "trade_details_pair" : "Par", + "trade_details_copied" : "${title} Copiado al portapapeles", + + + "trade_history_title" : "Historia del comercio", + + + "transaction_details_title" : "Detalles de la transacción", + "transaction_details_transaction_id" : "ID de transacción", + "transaction_details_date" : "Fecha", + "transaction_details_height" : "Altura", + "transaction_details_amount" : "Cantidad", + "transaction_details_copied" : "${title} Copiado al portapapeles", + "transaction_details_recipient_address" : "Dirección del receptor", + + + "wallet_list_title" : "Monedero Monero", + "wallet_list_create_new_wallet" : "Crear nueva billetera", + "wallet_list_restore_wallet" : "Restaurar billetera", + "wallet_list_load_wallet" : "Billetera de carga", + "wallet_list_loading_wallet" : "Billetera ${wallet_name} de carga", + "wallet_list_failed_to_load" : "No se pudo cargar ${wallet_name} la billetera. ${error}", + "wallet_list_removing_wallet" : "Retirar ${wallet_name} billetera", + "wallet_list_failed_to_remove" : "Error al elimina ${wallet_name} billetera. ${error}", + + + "widgets_address" : "Dirección", + "widgets_restore_from_blockheight" : "Restaurar desde blockheight", + "widgets_restore_from_date" : "Restaurar desde fecha", + "widgets_or" : "o", + "widgets_seed" : "Semilla", + + + "router_no_route" : "No hay ruta definida para ${name}", + + + "error_text_account_name" : "El nombre de la cuenta solo puede contener letras, números \ny debe tener entre 1 y 15 caracteres de longitud", + "error_text_contact_name" : "El nombre del contacto no puede contener símbolos `, '\" \ny debe tener entre 1 y 32 caracteres de longitud", + "error_text_address" : "La dirección de la billetera debe corresponder al tipo \nde criptomoneda", + "error_text_node_address" : "Por favor, introduzca una dirección iPv4", + "error_text_node_port" : "El puerto de nodo solo puede contener números entre 0 y 65535", + "error_text_payment_id" : "La ID de pago solo puede contener de 16 a 64 caracteres en hexadecimal", + "error_text_xmr" : "El valor XMR no puede exceder el saldo disponible.\nTEl número de dígitos de fracción debe ser menor o igual a 12", + "error_text_fiat" : "El valor de la cantidad no puede exceder el saldo disponible.\nEl número de dígitos de fracción debe ser menor o igual a 2", + "error_text_subaddress_name" : "El nombre de la subdirección no puede contener símbolos `, '\" \ny debe tener entre 1 y 20 caracteres de longitud", + "error_text_amount" : "La cantidad solo puede contener números", + "error_text_wallet_name" : "El nombre de la billetera solo puede contener letras, números \ny debe tener entre 1 y 15 caracteres de longitud", + "error_text_keys" : "Las llaves de billetera solo pueden contener 64 caracteres en hexadecimal", + "error_text_crypto_currency" : "El número de dígitos fraccionarios \ndebe ser menor o igual a 12", + + + "auth_store_ban_timeout" : "prohibición de tiempo de espera", + "auth_store_banned_for" : "Prohibido para ", + "auth_store_banned_minutes" : " minutos", + "auth_store_incorrect_password" : "Contraseña PIN", + "wallet_store_monero_wallet" : "Monedero Monero", + "wallet_restoration_store_incorrect_seed_length" : "Longitud de semilla incorrecta", + + + "full_balance" : "Balance completo", + "available_balance" : "Balance disponible", + "hidden_balance" : "Balance oculto", + + + "sync_status_syncronizing" : "SINCRONIZANDO", + "sync_status_syncronized" : "SINCRONIZADO", + "sync_status_not_connected" : "NO CONECTADO", + "sync_status_starting_sync" : "EMPEZANDO A SINCRONIZAR", + "sync_status_failed_connect" : "CONEXIÓN FALLIDA AL NODO", + "sync_status_connecting" : "CONECTANDO", + "sync_status_connected" : "CONECTADO", + + + "transaction_priority_slow" : "Lento", + "transaction_priority_regular" : "Regular", + "transaction_priority_medium" : "Medio", + "transaction_priority_fast" : "Rápido", + "transaction_priority_fastest" : "Lo más rápido", + + + "trade_for_not_created" : "Comercio por ${title} no se crea.", + "trade_not_created" : "Comercio no se crea.", + "trade_id_not_found" : "Comercio ${tradeId} de ${title} no encontrado.", + "trade_not_found" : "Comercio no encontrado.", + + + "trade_state_pending" : "Pendiente", + "trade_state_confirming" : "Confirmando", + "trade_state_trading" : "Comercio", + "trade_state_traded" : "Negociado", + "trade_state_complete" : "Completar", + "trade_state_to_be_created" : "Ser creado", + "trade_state_unpaid" : "No pagado", + "trade_state_underpaid" : "Poco pagado", + "trade_state_paid_unconfirmed" : "Pagado sin confirmar", + "trade_state_paid" : "Pagado", + "trade_state_btc_sent" : "Btc expedido", + "trade_state_timeout" : "Se acabó el tiempo", + "trade_state_created" : "Creado", + "trade_state_finished" : "Terminado", + + "change_language" : "Cambiar idioma", + "change_language_to" : "Cambiar el idioma a ${language}?" +} \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb new file mode 100644 index 000000000..304a2cfa9 --- /dev/null +++ b/res/values/strings_hi.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "स्वागत हे\nसेवा मेरे CAKE WALLET", + "first_wallet_text" : "बहुत बढ़िया बटुआ\nके लिये Monero", + "please_make_selection" : "कृपया नीचे चयन करें\nअपना बटुआ बनाएं या पुनर्प्राप्त करें.", + "create_new" : "नया बनाओ", + "restore_wallet" : "वॉलेट को पुनर्स्थापित करें", + + + "accounts" : "हिसाब किताब", + "edit" : "संपादित करें", + "account" : "लेखा", + "add" : "जोड़ना", + + + "address_book" : "पता पुस्तिका", + "contact" : "संपर्क करें", + "please_select" : "कृपया चुने:", + "cancel" : "रद्द करना", + "ok" : "ठीक है", + "contact_name" : "संपर्क नाम", + "reset" : "रीसेट", + "save" : "बचाना", + + + "authenticated" : "प्रमाणीकृत", + "authentication" : "प्रमाणीकरण", + "failed_authentication" : "प्रमाणीकरण विफल. ${state_error}", + + + "wallet_menu" : "बटुआ मेनू", + "Blocks_remaining" : "${status} शेष रहते हैं", + "please_try_to_connect_to_another_node" : "कृपया दूसरे नोड से कनेक्ट करने का प्रयास करें", + "xmr_hidden" : "XMR छिपा हुआ", + "xmr_available_balance" : "XMR उपलब्ध शेष राशि", + "xmr_full_balance" : "XMR पूर्ण संतुलन", + "send" : "संदेश", + "receive" : "प्राप्त करना", + "transactions" : "लेन-देन", + "incoming" : "आने वाली", + "outgoing" : "निवर्तमान", + "transactions_by_date" : "तारीख से लेन-देन", + "trades" : "ट्रेडों", + "filters" : "फिल्टर", + "today" : "आज", + "yesterday" : "बिता कल", + "received" : "प्राप्त किया", + "sent" : "भेज दिया", + "pending" : " (अपूर्ण)", + "rescan" : "पुन: स्कैन", + "reconnect" : "रिकनेक्ट", + "wallets" : "पर्स", + "show_seed" : "बीज दिखाओ", + "show_keys" : "चाबी दिखाओ", + "address_book_menu" : "पता पुस्तिका", + "reconnection" : "पुनर्संयोजन", + "reconnect_alert_text" : "क्या आप पुन: कनेक्ट होना सुनिश्चित करते हैं?", + + + "exchange" : "अदला बदली", + "clear" : "स्पष्ट", + "change_exchange_provider" : "एक्सचेंज प्रदाता बदलें", + "you_will_send" : "तुम भेजोगे", + "you_will_get" : "आपको मिल जायेगा", + "amount_is_guaranteed" : "प्राप्त राशि की गारंटी है", + "amount_is_estimate" : "प्राप्त राशि एक अनुमान है", + "powered_by" : "द्वारा संचालित ${title}", + "error" : "त्रुटि", + "estimated" : "अनुमानित", + "min_value" : "मिन: ${value} ${currency}", + "max_value" : "मैक्स: ${value} ${currency}", + "change_currency" : "मुद्रा परिवर्तन करें", + + + "copy_id" : "प्रतिलिपि ID", + "exchange_result_write_down_trade_id" : "जारी रखने के लिए कृपया ट्रेड ID की प्रतिलिपि बनाएँ या लिखें.", + "trade_id" : "व्यापार ID:\n${id}", + "copied_to_clipboard" : "क्लिपबोर्ड पर नकल", + "saved_the_trade_id" : "मैंने व्यापार बचा लिया है ID", + "fetching" : "ला रहा है", + "id" : "ID: ", + "amount" : "रकम: ", + "payment_id" : "भुगतान ID: ", + "status" : "स्थिति: ", + "offer_expires_in" : "में ऑफर समाप्त हो रहा है: ", + "trade_is_powered_by" : "यह व्यापार द्वारा संचालित है ${provider}", + "copy_address" : "पता कॉपी करें", + "exchange_result_confirm" : "पुष्टि दबाकर, आप भेज रहे होंगे ${fetchingLabel} ${from} अपने बटुए से ${walletName} ऊपर दिखाए गए पते पर। या आप अपने बाहरी वॉलेट से उपरोक्त पते / क्यूआर कोड पर भेज सकते हैं।\n\nकृपया जारी रखने या राशि बदलने के लिए वापस जाने के लिए पुष्टि करें दबाएं.\n\n", + "exchange_result_description" : "कृपया भेजें ${fetchingLabel} ${from} ऊपर दिखाए गए पते पर\n\n'", + "exchange_result_write_down_ID" : "*कृपया ऊपर दिखाए गए अपने ID को कॉपी या लिख लें.", + "confirm" : "की पुष्टि करें", + "confirm_sending" : "भेजने की पुष्टि करें", + "commit_transaction_amount_fee" : "लेन-देन करें\nरकम: ${amount}\nशुल्क: ${fee}", + "sending" : "भेजना", + "transaction_sent" : "भेजा गया लेन-देन", + "expired" : "समय सीमा समाप्त", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "संदेश XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "अपना पिन दर्ज करो", + "loading_your_wallet" : "अपना बटुआ लोड कर रहा है", + + + "new_wallet" : "नया बटुआ", + "wallet_name" : "बटुए का नाम", + "continue_text" : "जारी रहना", + + + "node_new" : "नया नोड", + "node_address" : "नोड पता", + "node_port" : "नोड पोर्ट", + "login" : "लॉग इन करें", + "password" : "पारण शब्द", + "nodes" : "नोड्स", + "node_reset_settings_title" : "सेटिंग्स को दुबारा करें", + "nodes_list_reset_to_default_message" : "क्या आप वाकई सेटिंग को डिफ़ॉल्ट पर रीसेट करना चाहते हैं?", + "change_current_node" : "क्या आप वर्तमान नोड को बदलना सुनिश्चित करते हैं ${node}?", + "change" : "परिवर्तन", + "remove_node" : "नोड निकालें", + "remove_node_message" : "क्या आप वाकई चयनित नोड को निकालना चाहते हैं?", + "remove" : "हटाना", + "delete" : "हटाएं", + + + "use" : "उपयोग ", + "digit_pin" : "-अंक पिन", + + + "share_address" : "पता साझा करें", + "receive_amount" : "रकम", + "subaddresses" : "उप पते", + + + "restore_restore_wallet" : "वॉलेट को पुनर्स्थापित करें", + "restore_title_from_seed_keys" : "बीज / कुंजियों से पुनर्स्थापित करें", + "restore_description_from_seed_keys" : "अपने बटुए को बीज से वापस लें/वे कुंजियाँ जिन्हें आपने सुरक्षित स्थान पर सहेजा है", + "restore_next" : "आगामी", + "restore_title_from_backup" : "बैक-अप फ़ाइल से पुनर्स्थापित करें", + "restore_description_from_backup" : "आप से पूरे केक वॉलेट एप्लिकेशन को पुनर्स्थापित कर सकते हैं\nआपकी बैक-अप फ़ाइल", + "restore_seed_keys_restore" : "बीज / कुंजी पुनर्स्थापित करें", + "restore_title_from_seed" : "बीज से पुनर्स्थापित करें", + "restore_description_from_seed" : "या तो 25 शब्द से अपने वॉलेट को पुनर्स्थापित करें\nया 13 शब्द संयोजन कोड", + "restore_title_from_keys" : "कुंजी से पुनर्स्थापित करें", + "restore_description_from_keys" : "अपने वॉलेट को जेनरेट से पुनर्स्थापित करें\nआपकी निजी कुंजी से कीस्ट्रोक्स सहेजे गए", + "restore_wallet_name" : "बटुए का नाम", + "restore_address" : "पता", + "restore_view_key_private" : "कुंजी देखें (निजी)", + "restore_spend_key_private" : "कुंजी खर्च करें (निजीe)", + "restore_recover" : "वसूली", + "restore_wallet_restore_description" : "बटुआ विवरण पुनर्स्थापित करें", + + + "seed_title" : "बीज", + "seed_share" : "बीज साझा करें", + "copy" : "प्रतिलिपि", + + + "seed_alert_first_text" : "अगला पेज दिखाएगा\nतुम एक बीज हो.", + "seed_alert_second_text" : "कृपया इन्हें केवल मामले में लिखें\nआप अपना फोन खो देते हैं या मिटा देते हैं.", + "seed_alert_third_text" : "आप बीज को फिर से देख सकते हैं\nमें ", + "seed_alert_settings" : "सेटिंग्स", + "seed_alert_menu" : " मेन्यू.", + "seed_alert_understand" : "मै समझता हुँ", + + + "send_title" : "संदेश Monero", + "send_your_wallet" : "आपका बटुआ", + "send_monero_address" : "मोनरो पता", + "send_payment_id" : "भुगतान ID (ऐच्छिक)", + "all" : "सब", + "send_error_minimum_value" : "राशि का न्यूनतम मूल्य 0.01 है", + "send_error_currency" : "मुद्रा में केवल संख्याएँ हो सकती हैं", + "send_estimated_fee" : "अनुमानित शुल्क:", + "send_priority" : "वर्तमान में शुल्क निर्धारित है ${transactionPriority} प्राथमिकता.\nलेन-देन की प्राथमिकता को सेटिंग्स में समायोजित किया जा सकता है", + "send_creating_transaction" : "लेन-देन बनाना", + + + "settings_title" : "सेटिंग्स", + "settings_nodes" : "नोड्स", + "settings_current_node" : "वर्तमान नोड", + "settings_wallets" : "पर्स", + "settings_display_balance_as" : "के रूप में संतुलन प्रदर्शित करें", + "settings_currency" : "मुद्रा", + "settings_fee_priority" : "शुल्क प्राथमिकता", + "settings_save_recipient_address" : "प्राप्तकर्ता का पता सहेजें", + "settings_personal" : "निजी", + "settings_change_pin" : "पिन बदलें", + "settings_change_language" : "भाषा बदलो", + "settings_allow_biometrical_authentication" : "बायोमेट्रिक प्रमाणीकरण की अनुमति दें", + "settings_dark_mode" : "डार्क मोड", + "settings_transactions" : "लेन-देन", + "settings_trades" : "ट्रेडों", + "settings_display_on_dashboard_list" : "डैशबोर्ड सूची पर प्रदर्शित करें", + "settings_all" : "सब", + "settings_only_trades" : "केवल ट्रेड करता है", + "settings_only_transactions" : "केवल लेन-देन", + "settings_none" : "कोई नहीं", + "settings_support" : "समर्थन", + "settings_terms_and_conditions" : "नियम और शर्तें", + "pin_is_incorrect" : "पिन गलत है", + + + "setup_pin" : "पिन सेट करें", + "enter_your_pin_again" : "फिर से अपना पिन डालें", + "setup_successful" : "आपका पिन सफलतापूर्वक सेट हो गया है", + + + "wallet_keys" : "बटुआ की", + "view_key_private" : "कुंजी देखें(निजी)", + "view_key_public" : "कुंजी देखें (जनता)", + "spend_key_private" : "खर्च करना (निजी)", + "spend_key_public" : "खर्च करना (जनता)", + "copied_key_to_clipboard" : "की नकल की ${key} क्लिपबोर्ड पर", + + + "new_subaddress_title" : "नई उपशादी", + "new_subaddress_label_name" : "लेबल का नाम", + "new_subaddress_create" : "सर्जन करना", + + + "subaddress_title" : "उपखंड सूची", + + + "trade_details_title" : "व्यापार विवरण", + "trade_details_id" : "आईडी", + "trade_details_state" : "राज्य", + "trade_details_fetching" : "ला रहा है", + "trade_details_provider" : "प्रदाता", + "trade_details_created_at" : "पर बनाया गया", + "trade_details_pair" : "जोड़ा", + "trade_details_copied" : "${title} क्लिपबोर्ड पर नकल", + + + "trade_history_title" : "व्यापार का इतिहास", + + + "transaction_details_title" : "लेनदेन का विवरण", + "transaction_details_transaction_id" : "लेनदेन आईडी", + "transaction_details_date" : "तारीख", + "transaction_details_height" : "ऊंचाई", + "transaction_details_amount" : "रकम", + "transaction_details_copied" : "${title} क्लिपबोर्ड पर नकल", + "transaction_details_recipient_address" : "प्राप्तकर्ता का पता", + + + "wallet_list_title" : "Monero बटुआ", + "wallet_list_create_new_wallet" : "नया बटुआ बनाएँ", + "wallet_list_restore_wallet" : "वॉलेट को पुनर्स्थापित करें", + "wallet_list_load_wallet" : "वॉलेट लोड करें", + "wallet_list_loading_wallet" : "लोड हो रहा है ${wallet_name} बटुआ", + "wallet_list_failed_to_load" : "लोड करने में विफल ${wallet_name} बटुआ. ${error}", + "wallet_list_removing_wallet" : "निकाला जा रहा है ${wallet_name} बटुआ", + "wallet_list_failed_to_remove" : "निकालने में विफल ${wallet_name} बटुआ. ${error}", + + + "widgets_address" : "पता", + "widgets_restore_from_blockheight" : "ब्लॉकचेन से पुनर्स्थापित करें", + "widgets_restore_from_date" : "दिनांक से पुनर्स्थापित करें", + "widgets_or" : "या", + "widgets_seed" : "बीज", + + + "router_no_route" : "के लिए कोई मार्ग निर्धारित नहीं है ${name}", + + + "error_text_account_name" : "खाता नाम में केवल अक्षर, संख्याएं हो सकती हैं\nऔर 1 और 15 वर्णों के बीच लंबा होना चाहिए", + "error_text_contact_name" : "संपर्क नाम शामिल नहीं हो सकता ` , ' \" प्रतीकों\nऔर 1 और 32 वर्णों के बीच लंबा होना चाहिए", + "error_text_address" : "वॉलेट पता प्रकार के अनुरूप होना चाहिए\nक्रिप्टोकरेंसी का", + "error_text_node_address" : "कृपया एक IPv4 पता दर्ज करें", + "error_text_node_port" : "नोड पोर्ट में केवल 0 और 65535 के बीच संख्याएँ हो सकती हैं", + "error_text_payment_id" : "पेमेंट आईडी केवल हेक्स में 16 से 64 चार्ट तक हो सकती है", + "error_text_xmr" : "एक्सएमआर मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या 12 से कम या इसके बराबर होनी चाहिए", + "error_text_fiat" : "राशि का मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या कम या 2 के बराबर होनी चाहिए", + "error_text_subaddress_name" : "सबड्रेस नाम नहीं हो सकता` , ' \" प्रतीकों\nऔर 1 और 20 वर्णों के बीच लंबा होना चाहिए", + "error_text_amount" : "राशि में केवल संख्याएँ हो सकती हैं", + "error_text_wallet_name" : "वॉलेट नाम में केवल अक्षर, संख्याएं हो सकती हैं\nऔर 1 और 15 वर्णों के बीच लंबा होना चाहिए", + "error_text_keys" : "वॉलेट कीज़ में हेक्स में केवल 64 वर्ण हो सकते हैं", + "error_text_crypto_currency" : "अंश अंकों की संख्या\n12 से कम या इसके बराबर होना चाहिए", + + + "auth_store_ban_timeout" : "समय की पाबंदी", + "auth_store_banned_for" : "के लिए प्रतिबंधित है ", + "auth_store_banned_minutes" : " मिनट", + "auth_store_incorrect_password" : "गलत पिन", + "wallet_store_monero_wallet" : "मोनरो वॉलेट", + "wallet_restoration_store_incorrect_seed_length" : "गलत बीज की लंबाई", + + + "full_balance" : "पूर्ण संतुलन", + "available_balance" : "उपलब्ध शेष राशि", + "hidden_balance" : "छिपा हुआ संतुलन", + + + "sync_status_syncronizing" : "सिंक्रनाइज़ करने", + "sync_status_syncronized" : "सिंक्रनाइज़", + "sync_status_not_connected" : "जुड़े नहीं हैं", + "sync_status_starting_sync" : "सिताज़ा करना", + "sync_status_failed_connect" : "फेल हुआ कनेक्ट नोड", + "sync_status_connecting" : "कनेक्ट", + "sync_status_connected" : "जुड़े हुए", + + + "transaction_priority_slow" : "धीरे", + "transaction_priority_regular" : "नियमित", + "transaction_priority_medium" : "मध्यम", + "transaction_priority_fast" : "उपवास", + "transaction_priority_fastest" : "सबसे तेजी से", + + + "trade_for_not_created" : "के लिए व्यापार ${title} निर्मित नहीं है.", + "trade_not_created" : "व्यापार नहीं बनाया गया.", + "trade_id_not_found" : "व्यापार ${tradeId} of ${title} नहीं मिला.", + "trade_not_found" : "व्यापार नहीं मिला", + + + "trade_state_pending" : "विचाराधीन", + "trade_state_confirming" : "पुष्टि", + "trade_state_trading" : "व्यापार", + "trade_state_traded" : "ट्रेडेड", + "trade_state_complete" : "पूर्ण", + "trade_state_to_be_created" : "बनाए जाने के लिए", + "trade_state_unpaid" : "अवैतनिक", + "trade_state_underpaid" : "के तहत भुगतान किया", + "trade_state_paid_unconfirmed" : "अपुष्ट भुगतान किया", + "trade_state_paid" : "भुगतान किया है", + "trade_state_btc_sent" : "भेज दिया", + "trade_state_timeout" : "समय समाप्त", + "trade_state_created" : "बनाया था", + "trade_state_finished" : "ख़त्म होना", + + "change_language" : "भाषा बदलो", + "change_language_to" : "को भाषा बदलें ${language}?" +} \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb new file mode 100644 index 000000000..1acac03c3 --- /dev/null +++ b/res/values/strings_ja.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "ようこそ\nに CAKE WALLET", + "first_wallet_text" : "素晴らしい財布\nために Monero", + "please_make_selection" : "以下を選択してください\nウォレットを作成または回復する.", + "create_new" : "新しく作る", + "restore_wallet" : "ウォレットを復元する", + + + "accounts" : "アカウント", + "edit" : "編集", + "account" : "アカウント", + "add" : "加える", + + + "address_book" : "住所録", + "contact" : "接触", + "please_select" : "選んでください:", + "cancel" : "キャンセル", + "ok" : "OK", + "contact_name" : "連絡先", + "reset" : "リセットする", + "save" : "セーブ", + + + "authenticated" : "認証済み", + "authentication" : "認証", + "failed_authentication" : "認証失敗. ${state_error}", + + + "wallet_menu" : "ウォレットメニュー", + "Blocks_remaining" : "${status} 残りのブロック", + "please_try_to_connect_to_another_node" : "別のノードに接続してみてください", + "xmr_hidden" : "XMR非表示", + "xmr_available_balance" : "XMR利用可能残高", + "xmr_full_balance" : "XMRフルバランス", + "send" : "送る", + "receive" : "受け取る", + "transactions" : "取引", + "incoming" : "着信", + "outgoing" : "発信", + "transactions_by_date" : "日付ごとの取引", + "trades" : "取引", + "filters" : "フィルター", + "today" : "今日", + "yesterday" : "昨日", + "received" : "受け取った", + "sent" : "送信済み", + "pending" : " (保留中)", + "rescan" : "再スキャン", + "reconnect" : "再接続", + "wallets" : "財布", + "show_seed" : "シードを表示", + "show_keys" : "キーを表示", + "address_book_menu" : "住所録", + "reconnection" : "再接続", + "reconnect_alert_text" : "再接続しますか?", + + + "exchange" : "交換する", + "clear" : "クリア", + "change_exchange_provider" : "Exchangeプロバイダーの変更", + "you_will_send" : "送ります", + "you_will_get" : "あなたが取得します", + "amount_is_guaranteed" : "受信金額は保証されています", + "amount_is_estimate" : "受け取り金額は見積もりです", + "powered_by" : "搭載 ${title}", + "error" : "エラー", + "estimated" : "推定", + "min_value" : "分: ${value} ${currency}", + "max_value" : "マックス: ${value} ${currency}", + "change_currency" : "通貨を変更する", + + + "copy_id" : "IDをコピー", + "exchange_result_write_down_trade_id" : "続行するには、取引IDをコピーまたは書き留めてください.", + "trade_id" : "取引ID:\n${id}", + "copied_to_clipboard" : "クリップボードにコピーしました", + "saved_the_trade_id" : "取引IDを保存しました", + "fetching" : "フェッチング", + "id" : "ID: ", + "amount" : "量: ", + "payment_id" : "支払いID: ", + "status" : "状態: ", + "offer_expires_in" : "で有効期限が切れます: ", + "trade_is_powered_by" : "この取引は ${provider}", + "copy_address" : "住所をコピー", + "exchange_result_confirm" : "確認を押すと、送信されます ${fetchingLabel} ${from} と呼ばれるあなたの財布から ${walletName} 上記のアドレスへ. または、外部ウォレットから上記のアドレス/ QRコードに送信できます.\n\n確認を押して続行するか、戻って金額を変更してください.\n\n", + "exchange_result_description" : "送信してください ${fetchingLabel} ${from} 上記のアドレスへ.\n\n'", + "exchange_result_write_down_ID" : "*上記のIDをコピーまたは書き留めてください.", + "confirm" : "確認する", + "confirm_sending" : "送信を確認", + "commit_transaction_amount_fee" : "トランザクションをコミット\n量: ${amount}\n費用: ${fee}", + "sending" : "送信", + "transaction_sent" : "トランザクションが送信されました!", + "expired" : "期限切れ", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "送る XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "PINを入力してください", + "loading_your_wallet" : "ウォレットをロードしています", + + + "new_wallet" : "新しいウォレット", + "wallet_name" : "ウォレット名", + "continue_text" : "持続する", + + + "node_new" : "新しいノード", + "node_address" : "ノードアドレス", + "node_port" : "ノードポート", + "login" : "ログイン", + "password" : "パスワード", + "nodes" : "ノード", + "node_reset_settings_title" : "設定をリセット", + "nodes_list_reset_to_default_message" : "設定をデフォルトにリセットしてもよろしいですか?", + "change_current_node" : "現在のノードを変更してよろしいですか ${node}?", + "change" : "変化する", + "remove_node" : "ノードを削除", + "remove_node_message" : "選択したノードを削除してもよろしいですか?", + "remove" : "削除する", + "delete" : "削除する", + + + "use" : "つかいます ", + "digit_pin" : "桁ピン", + + + "share_address" : "住所を共有する", + "receive_amount" : "量", + "subaddresses" : "サブアドレス", + + + "restore_restore_wallet" : "ウォレットを復元", + "restore_title_from_seed_keys" : "シード/キーから復元", + "restore_description_from_seed_keys" : "安全な場所に保存したシード/キーから財布を取り戻す", + "restore_next" : "次", + "restore_title_from_backup" : "バックアップファイルから復元する", + "restore_description_from_backup" : "Cake Walletアプリ全体を復元できます\nバックアップファイル", + "restore_seed_keys_restore" : "シード/キーの復元", + "restore_title_from_seed" : "シードから復元", + "restore_description_from_seed" : "25ワードからウォレットを復元します\nまたは13ワードの組み合わせコード", + "restore_title_from_keys" : "キーから復元する", + "restore_description_from_keys" : "生成されたウォレットを復元します\n秘密鍵から保存されたキーストローク", + "restore_wallet_name" : "ウォレット名", + "restore_address" : "住所", + "restore_view_key_private" : "ビューキー (プライベート)", + "restore_spend_key_private" : "キーを使う (プライベート)", + "restore_recover" : "回復します", + "restore_wallet_restore_description" : "ウォレットの復元", + + + "seed_title" : "シード", + "seed_share" : "シードを共有する", + "copy" : "コピー", + + + "seed_alert_first_text" : "次のページが表示されます\nあなたは種.", + "seed_alert_second_text" : "これらを書き留めてください \n電話を紛失した場合や拭いた場合.", + "seed_alert_third_text" : "また、種子を見ることができます\nの中に ", + "seed_alert_settings" : "設定", + "seed_alert_menu" : " メニュー.", + "seed_alert_understand" : "わかります", + + + "send_title" : "Moneroを送信", + "send_your_wallet" : "あなたの財布", + "send_monero_address" : "Monero 住所", + "send_payment_id" : "支払いID (オプショナル)", + "all" : "すべて", + "send_error_minimum_value" : "金額の最小値は0.01です", + "send_error_currency" : "通貨には数字のみを含めることができます", + "send_estimated_fee" : "見積手数料:", + "send_priority" : "現在、料金は ${transactionPriority} 優先度.\nトランザクションの優先度は設定で調整できます", + "send_creating_transaction" : "トランザクションを作成する", + + + "settings_title" : "設定", + "settings_nodes" : "ノード", + "settings_current_node" : "現在のノード", + "settings_wallets" : "財布", + "settings_display_balance_as" : "残高を表示", + "settings_currency" : "通貨", + "settings_fee_priority" : "料金優先", + "settings_save_recipient_address" : "受信者のアドレスを保存", + "settings_personal" : "パーソナル", + "settings_change_pin" : "PINを変更", + "settings_change_language" : "言語を変えてください", + "settings_allow_biometrical_authentication" : "生体認証を許可する", + "settings_dark_mode" : "ダークモード", + "settings_transactions" : "取引", + "settings_trades" : "取引", + "settings_display_on_dashboard_list" : "ダッシュボードリストに表示", + "settings_all" : "すべて", + "settings_only_trades" : "取引のみ", + "settings_only_transactions" : "トランザクションのみ", + "settings_none" : "なし", + "settings_support" : "サポート", + "settings_terms_and_conditions" : "規約と条件", + "pin_is_incorrect" : "PINが間違っています", + + + "setup_pin" : "PINのセットアップ", + "enter_your_pin_again" : "ピンをもう一度入力してください", + "setup_successful" : "PINは正常に設定されました!", + + + "wallet_keys" : "ウォレットキー", + "view_key_private" : "ビューキー (プライベート)", + "view_key_public" : "ビューキー (パブリック)", + "spend_key_private" : "キーを使う (プライベート)", + "spend_key_public" : "キーを使う (パブリック)", + "copied_key_to_clipboard" : "コピー済み ${key} クリップボードへ", + + + "new_subaddress_title" : "新しいサブアドレス", + "new_subaddress_label_name" : "ラベル名", + "new_subaddress_create" : "作成する", + + + "subaddress_title" : "サブアドレス一覧", + + + "trade_details_title" : "取引の詳細", + "trade_details_id" : "ID", + "trade_details_state" : "状態", + "trade_details_fetching" : "フェッチング", + "trade_details_provider" : "プロバイダー", + "trade_details_created_at" : "で作成", + "trade_details_pair" : "ペア", + "trade_details_copied" : "${title} クリップボードにコピーしました", + + + "trade_history_title" : "取引履歴", + + + "transaction_details_title" : "取引の詳細", + "transaction_details_transaction_id" : "トランザクションID", + "transaction_details_date" : "日付", + "transaction_details_height" : "高さ", + "transaction_details_amount" : "量", + "transaction_details_copied" : "${title} クリップボードにコピーしました", + "transaction_details_recipient_address" : "受取人の住所", + + + "wallet_list_title" : "Monero 財布", + "wallet_list_create_new_wallet" : "新しいウォレットを作成", + "wallet_list_restore_wallet" : "ウォレットを復元", + "wallet_list_load_wallet" : "ウォレットをロード", + "wallet_list_loading_wallet" : "読み込み中 ${wallet_name} 財布", + "wallet_list_failed_to_load" : "読み込みに失敗しました ${wallet_name} 財布. ${error}", + "wallet_list_removing_wallet" : "取りはずし ${wallet_name} 財布", + "wallet_list_failed_to_remove" : "削除できませんでした ${wallet_name} 財布. ${error}", + + + "widgets_address" : "住所", + "widgets_restore_from_blockheight" : "ブロックの高さから復元", + "widgets_restore_from_date" : "日付から復元", + "widgets_or" : "または", + "widgets_seed" : "シード", + + + "router_no_route" : "ルートが定義されていません ${name}", + + + "error_text_account_name" : "アカウント名には文字のみを含めることができます \n1〜15文字である必要があります", + "error_text_contact_name" : "連絡先名に含めることはできません ` , ' \" シンボル\n長さは1〜32文字でなければなりません", + "error_text_address" : "ウォレットアドレスは、\n暗号通貨", + "error_text_node_address" : "iPv4アドレスを入力してください", + "error_text_node_port" : "ノードポートには、0〜65535の数字のみを含めることができます", + "error_text_payment_id" : "支払いIDには、16進数で16〜64文字しか含めることができません", + "error_text_xmr" : "XMR値は利用可能な残高を超えることはできません.\n小数桁数は12以下でなければなりません", + "error_text_fiat" : "金額は利用可能な残高を超えることはできません.\n小数桁の数は2以下でなければなりません", + "error_text_subaddress_name" : "サブアドレス名に含めることはできません` , ' \" シンボル\n1〜20文字の長さである必要があります", + "error_text_amount" : "金額には数字のみを含めることができます", + "error_text_wallet_name" : "ウォレット名には文字のみを含めることができます\n1〜15文字である必要があります", + "error_text_keys" : "ウォレットキーには、16進数で64文字しか含めることができません", + "error_text_crypto_currency" : "小数桁数\n12以下でなければなりません", + + + "auth_store_ban_timeout" : "禁止タイムアウト", + "auth_store_banned_for" : "禁止されています ", + "auth_store_banned_minutes" : " 数分", + "auth_store_incorrect_password" : "間違ったPIN", + "wallet_store_monero_wallet" : "Monero 財布", + "wallet_restoration_store_incorrect_seed_length" : "誤ったシード長s", + + + "full_balance" : "フルバランス", + "available_balance" : "利用可能残高", + "hidden_balance" : "隠れたバランス", + + + "sync_status_syncronizing" : "同期", + "sync_status_syncronized" : "同期された", + "sync_status_not_connected" : "接続されていません", + "sync_status_starting_sync" : "同期の開始", + "sync_status_failed_connect" : "ノードへの接続に失敗しました", + "sync_status_connecting" : "接続中", + "sync_status_connected" : "接続済み", + + + "transaction_priority_slow" : "スロー", + "transaction_priority_regular" : "レギュラー", + "transaction_priority_medium" : "中", + "transaction_priority_fast" : "速い", + "transaction_priority_fastest" : "最速", + + + "trade_for_not_created" : "取引 ${title} 作成されません", + "trade_not_created" : "作成されていない取引", + "trade_id_not_found" : "トレード ${tradeId} of ${title} 見つかりません", + "trade_not_found" : "取引が見つかりません", + + + "trade_state_pending" : "保留中", + "trade_state_confirming" : "確認中", + "trade_state_trading" : "トレーディング", + "trade_state_traded" : "取引済み", + "trade_state_complete" : "コンプリート", + "trade_state_to_be_created" : "作成される", + "trade_state_unpaid" : "未払い", + "trade_state_underpaid" : "支払不足", + "trade_state_paid_unconfirmed" : "未確認の支払い", + "trade_state_paid" : "有料", + "trade_state_btc_sent" : "送った", + "trade_state_timeout" : "タイムアウト", + "trade_state_created" : "作成した", + "trade_state_finished" : "完成した", + + "change_language" : "言語を変えてください", + "change_language_to" : "言語を変更 ${language}?" +} \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb new file mode 100644 index 000000000..2384a070e --- /dev/null +++ b/res/values/strings_ko.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "환영\n에 CAKE WALLET", + "first_wallet_text" : "멋진 지갑\n에 대한 Monero", + "please_make_selection" : "아래에서 선택하십시오\n지갑 만들기 또는 복구.", + "create_new" : "새로 만들기", + "restore_wallet" : "지갑 복원", + + + "accounts" : "계정", + "edit" : "편집하다", + "account" : "계정", + "add" : "더하다", + + + "address_book" : "주소록", + "contact" : "접촉", + "please_select" : "선택 해주세요:", + "cancel" : "취소", + "ok" : "승인", + "contact_name" : "담당자 이름", + "reset" : "다시 놓기", + "save" : "구하다", + + + "authenticated" : "인증", + "authentication" : "입증", + "failed_authentication" : "인증 실패. ${state_error}", + + + "wallet_menu" : "월렛 메뉴", + "Blocks_remaining" : "${status} 남은 블록", + "please_try_to_connect_to_another_node" : "다른 노드에 연결을 시도하십시오", + "xmr_hidden" : "XMR 숨김", + "xmr_available_balance" : "XMR 사용 가능한 잔액", + "xmr_full_balance" : "XMR 풀 밸런스", + "send" : "보내다", + "receive" : "받다", + "transactions" : "업무", + "incoming" : "들어오는", + "outgoing" : "나가는", + "transactions_by_date" : "날짜 별 거래", + "trades" : "거래", + "filters" : "필터", + "today" : "오늘", + "yesterday" : "어제", + "received" : "받았습니다", + "sent" : "보냄", + "pending" : " (보류 중)", + "rescan" : "재검색", + "reconnect" : "다시 연결", + "wallets" : "지갑", + "show_seed" : "종자 표시", + "show_keys" : "키 표시", + "address_book_menu" : "주소록", + "reconnection" : "재 연결", + "reconnect_alert_text" : "다시 연결 하시겠습니까?", + + + "exchange" : "교환", + "clear" : "명확한", + "change_exchange_provider" : "교환 공급자 변경", + "you_will_send" : "보내드립니다", + "you_will_get" : "당신은 얻을 것이다", + "amount_is_guaranteed" : "수신 금액이 보장됩니다", + "amount_is_estimate" : "수신 금액은 견적입니다", + "powered_by" : "에 의해 구동 ${title}", + "error" : "오류", + "estimated" : "예상", + "min_value" : "최소: ${value} ${currency}", + "max_value" : "맥스: ${value} ${currency}", + "change_currency" : "통화 변경", + + + "copy_id" : "부 ID", + "exchange_result_write_down_trade_id" : "계속하려면 거래 ID를 복사하거나 적어 두십시오..", + "trade_id" : "무역 ID:\n${id}", + "copied_to_clipboard" : "클립 보드에 복사", + "saved_the_trade_id" : "거래 ID를 저장했습니다", + "fetching" : "가져 오는 중", + "id" : "ID: ", + "amount" : "양: ", + "payment_id" : "지불 ID: ", + "status" : "지위: ", + "offer_expires_in" : "쿠폰 만료일: ", + "trade_is_powered_by" : "이 거래는 ${provider}", + "copy_address" : "주소 복사", + "exchange_result_confirm" : "확인을 누르면 전송됩니다 ${fetchingLabel} ${from} 지갑에서 ${walletName} 위에 표시된 주소로. 또는 외부 지갑에서 위의 주소 / QR 코드로 보낼 수 있습니다.\n\n확인을 눌러 계속하거나 금액을 변경하려면 돌아가십시오.\n\n", + "exchange_result_description" : "보내주세요 ${fetchingLabel} ${from} 위에 표시된 주소로.\n\n'", + "exchange_result_write_down_ID" : "*위에 표시된 ID를 복사하거나 적어 두십시오.", + "confirm" : "확인", + "confirm_sending" : "전송 확인", + "commit_transaction_amount_fee" : "커밋 거래\n양: ${amount}\n보수: ${fee}", + "sending" : "배상", + "transaction_sent" : "거래가 전송되었습니다!", + "expired" : "만료", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "보내다 XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "PIN을 입력하십시오", + "loading_your_wallet" : "지갑 넣기", + + + "new_wallet" : "새 월렛", + "wallet_name" : "지갑 이름", + "continue_text" : "잇다", + + + "node_new" : "새로운 노드", + "node_address" : "노드 주소", + "node_port" : "노드 포트", + "login" : "로그인", + "password" : "암호", + "nodes" : "노드", + "node_reset_settings_title" : "설정 초기화", + "nodes_list_reset_to_default_message" : "설정을 기본값으로 재설정 하시겠습니까?", + "change_current_node" : "현재 노드를 다음으로 변경 하시겠습니까 ${node}?", + "change" : "변화", + "remove_node" : "노드 제거", + "remove_node_message" : "선택한 노드를 제거 하시겠습니까?", + "remove" : "없애다", + "delete" : "지우다", + + + "use" : "용도 ", + "digit_pin" : "숫자 PIN", + + + "share_address" : "주소 공유", + "receive_amount" : "양", + "subaddresses" : "하위 주소", + + + "restore_restore_wallet" : "월렛 복원", + "restore_title_from_seed_keys" : "시드 / 키에서 복원", + "restore_description_from_seed_keys" : "안전한 장소에 저장 한 종자 / 키로 지갑을 되 찾으십시오.", + "restore_next" : "다음 것", + "restore_title_from_backup" : "백업 파일에서 복원", + "restore_description_from_backup" : "백업 파일에서 전체 Cake Wallet 앱을 복원 할 수 있습니다.", + "restore_seed_keys_restore" : "종자 / 키 복원", + "restore_title_from_seed" : "종자에서 복원", + "restore_description_from_seed" : "25 단어 또는 13 단어 조합 코드에서 지갑을 복원하십시오.", + "restore_title_from_keys" : "키에서 복원", + "restore_description_from_keys" : "개인 키에서 저장된 생성 된\n키 스트로크에서 월렛 복원", + "restore_wallet_name" : "지갑 이름", + "restore_address" : "주소", + "restore_view_key_private" : "키보기 (은밀한)", + "restore_spend_key_private" : "지출 키 (은밀한)", + "restore_recover" : "다시 덮다", + "restore_wallet_restore_description" : "월렛 복원 설명", + + + "seed_title" : "씨", + "seed_share" : "시드 공유", + "copy" : "부", + + + "seed_alert_first_text" : "다음 페이지가 표시됩니다\n당신은 씨앗.", + "seed_alert_second_text" : "아래에 적어주세요\n휴대 전화를 분실하거나 닦을 경우.", + "seed_alert_third_text" : "당신은 또한 씨앗을 다시 볼 수 있습니다\n에서 ", + "seed_alert_settings" : "설정", + "seed_alert_menu" : " 메뉴.", + "seed_alert_understand" : "이해 했어", + + + "send_title" : "모네로 보내기", + "send_your_wallet" : "지갑", + "send_monero_address" : "모네로 주소", + "send_payment_id" : "지불 ID (optional)", + "all" : "모든", + "send_error_minimum_value" : "금액의 최소값은 0.01입니다", + "send_error_currency" : "통화는 숫자 만 포함 할 수 있습니다", + "send_estimated_fee" : "예상 수수료:", + "send_priority" : "현재 수수료는 ${transactionPriority} 우선 순위.\n거래 우선 순위는 설정에서 조정할 수 있습니다", + "send_creating_transaction" : "거래 생성", + + + "settings_title" : "설정", + "settings_nodes" : "노드", + "settings_current_node" : "현재 노드", + "settings_wallets" : "지갑", + "settings_display_balance_as" : "잔액 표시", + "settings_currency" : "통화", + "settings_fee_priority" : "수수료 우선", + "settings_save_recipient_address" : "수신자 주소 저장", + "settings_personal" : "개인적인", + "settings_change_pin" : "PIN 변경", + "settings_change_language" : "언어 변경", + "settings_allow_biometrical_authentication" : "생체 인증 허용", + "settings_dark_mode" : "다크 모드", + "settings_transactions" : "업무", + "settings_trades" : "거래", + "settings_display_on_dashboard_list" : "대시 보드 목록에 표시", + "settings_all" : "모든", + "settings_only_trades" : "거래 만", + "settings_only_transactions" : "거래 만", + "settings_none" : "없음", + "settings_support" : "지원하다", + "settings_terms_and_conditions" : "이용 약관", + "pin_is_incorrect" : "PIN이 잘못되었습니다", + + + "setup_pin" : "설정 PIN", + "enter_your_pin_again" : "다시 핀을 입력", + "setup_successful" : "PIN이 성공적으로 설정되었습니다!", + + + "wallet_keys" : "지갑 키", + "view_key_private" : "키보기(은밀한)", + "view_key_public" : "키보기 (공공의)", + "spend_key_private" : "지출 키 (은밀한)", + "spend_key_public" : "지출 키 (공공의)", + "copied_key_to_clipboard" : "복사 ${key} 클립 보드로", + + + "new_subaddress_title" : "새로운 하위 주소", + "new_subaddress_label_name" : "라벨 이름", + "new_subaddress_create" : "몹시 떠들어 대다", + + + "subaddress_title" : "하위 주소 목록", + + + "trade_details_title" : "거래 세부 사항", + "trade_details_id" : "ID", + "trade_details_state" : "상태", + "trade_details_fetching" : "가져 오는 중", + "trade_details_provider" : "공급자", + "trade_details_created_at" : "에 작성", + "trade_details_pair" : "쌍", + "trade_details_copied" : "${title} 클립 보드에 복사", + + + "trade_history_title" : "무역 역사", + + + "transaction_details_title" : "상세 거래 내역", + "transaction_details_transaction_id" : "트랜잭션 ID", + "transaction_details_date" : "날짜", + "transaction_details_height" : "신장", + "transaction_details_amount" : "양", + "transaction_details_copied" : "${title} 클립 보드에 복사", + "transaction_details_recipient_address" : "받는 사람 주소", + + + "wallet_list_title" : "모네로 월렛", + "wallet_list_create_new_wallet" : "새 월렛 만들기", + "wallet_list_restore_wallet" : "월렛 복원", + "wallet_list_load_wallet" : "지갑로드", + "wallet_list_loading_wallet" : "로딩 ${wallet_name} 지갑", + "wallet_list_failed_to_load" : "불러 오지 못했습니다 ${wallet_name} 지갑. ${error}", + "wallet_list_removing_wallet" : "풀이 ${wallet_name} 지갑", + "wallet_list_failed_to_remove" : "제거하지 못했습니다 ${wallet_name} 지갑. ${error}", + + + "widgets_address" : "주소", + "widgets_restore_from_blockheight" : "블록 높이에서 복원", + "widgets_restore_from_date" : "날짜에서 복원", + "widgets_or" : "또는", + "widgets_seed" : "씨", + + + "router_no_route" : "에 정의 된 경로가 없습니다 ${name}", + + + "error_text_account_name" : "계정 이름은 문자, 숫자 만 포함 할 수 있습니다\n1 ~ 15 자 사이 여야합니다", + "error_text_contact_name" : "담당자 이름은 포함 할 수 없습니다 ` , ' \" 기호\n1 자에서 32 자 사이 여야합니다", + "error_text_address" : "지갑 주소는 유형과 일치해야합니다\n암호 화폐", + "error_text_node_address" : "iPv4 주소를 입력하십시오", + "error_text_node_port" : "노드 포트는 0에서 65535 사이의 숫자 만 포함 할 수 있습니다", + "error_text_payment_id" : "지불 ID는 16 ~ 64 자의 16 진 문자 만 포함 할 수 있습니다", + "error_text_xmr" : "XMR 값은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 12 이하 여야합니다", + "error_text_fiat" : "금액은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 2보다 작거나 같아야합니다", + "error_text_subaddress_name" : "하위 주소 이름은 포함 할 수 없습니다 ` , ' \" 기호 \n1 ~ 20 자 사이 여야합니다", + "error_text_amount" : "금액은 숫자 만 포함 할 수 있습니다", + "error_text_wallet_name" : "지갑 이름은 문자, 숫자 만 포함 할 수 있습니다\n1 ~ 15 자 사이 여야합니다", + "error_text_keys" : "지갑 키는 16 진수로 64 자만 포함 할 수 있습니다", + "error_text_crypto_currency" : "소수 자릿수\n12 이하 여야합니다", + + + "auth_store_ban_timeout" : "타임 아웃 금지", + "auth_store_banned_for" : "금지", + "auth_store_banned_minutes" : " 의사록", + "auth_store_incorrect_password" : "잘못된 PIN", + "wallet_store_monero_wallet" : "모네로 월렛", + "wallet_restoration_store_incorrect_seed_length" : "시드 길이가 잘못되었습니다", + + + "full_balance" : "풀 밸런스", + "available_balance" : "사용 가능한 잔액", + "hidden_balance" : "숨겨진 균형", + + + "sync_status_syncronizing" : "동기화", + "sync_status_syncronized" : "동기화", + "sync_status_not_connected" : "연결되지 않은", + "sync_status_starting_sync" : "동기화 시작", + "sync_status_failed_connect" : "노드에 연결하지 못했습니다", + "sync_status_connecting" : "연결 중", + "sync_status_connected" : "연결됨", + + + "transaction_priority_slow" : "느린", + "transaction_priority_regular" : "정규병", + "transaction_priority_medium" : "매질", + "transaction_priority_fast" : "빠른", + "transaction_priority_fastest" : "가장 빠른", + + + "trade_for_not_created" : "거래 ${title} 생성되지 않습니다.", + "trade_not_created" : "거래가 생성되지 않았습니다.", + "trade_id_not_found" : "무역 ${tradeId} 의 ${title} 찾을 수 없습니다.", + "trade_not_found" : "거래를 찾을 수 없습니다.", + + + "trade_state_pending" : "대기 중", + "trade_state_confirming" : "확인 중", + "trade_state_trading" : "거래", + "trade_state_traded" : "거래", + "trade_state_complete" : "완전한", + "trade_state_to_be_created" : "만들려면", + "trade_state_unpaid" : "미지급", + "trade_state_underpaid" : "미지급", + "trade_state_paid_unconfirmed" : "미확인 유료", + "trade_state_paid" : "유료", + "trade_state_btc_sent" : "보냄", + "trade_state_timeout" : "타임 아웃", + "trade_state_created" : "만들어진", + "trade_state_finished" : "끝마친", + + "change_language" : "언어 변경", + "change_language_to" : "언어를로 변경 ${language}?" +} \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb new file mode 100644 index 000000000..1bc2834a9 --- /dev/null +++ b/res/values/strings_nl.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "WELKOM\nBIJ CAKE WALLET", + "first_wallet_text" : "Geweldige portemonnee\nfvoor Monero", + "please_make_selection" : "Maak hieronder uw keuze tot\nmaak of herstel je portemonnee.", + "create_new" : "Maak nieuw", + "restore_wallet" : "Portemonnee herstellen", + + + "accounts" : "Accounts", + "edit" : "Bewerk", + "account" : "Account", + "add" : "Toevoegen", + + + "address_book" : "Adresboek", + "contact" : "Contact", + "please_select" : "Selecteer alstublieft:", + "cancel" : "Annuleer", + "ok" : "OK", + "contact_name" : "Contactnaam", + "reset" : "Reset", + "save" : "Opslaan", + + + "authenticated" : "Authenticated", + "authentication" : "Authenticatie", + "failed_authentication" : "Mislukte authenticatie. ${state_error}", + + + "wallet_menu" : "Portemonnee-menu", + "Blocks_remaining" : "${status} Resterende blokken", + "please_try_to_connect_to_another_node" : "Probeer verbinding te maken met een ander knooppunt", + "xmr_hidden" : "XMR Verborgen", + "xmr_available_balance" : "XMR Beschikbaar saldo", + "xmr_full_balance" : "XMR Volledig saldo", + "send" : "Sturen", + "receive" : "Krijgen", + "transactions" : "Transacties", + "incoming" : "inkomend", + "outgoing" : "Uitgaande", + "transactions_by_date" : "Transacties op datum", + "trades" : "Trades", + "filters" : "Filters", + "today" : "Vandaag", + "yesterday" : "Gisteren", + "received" : "Ontvangen", + "sent" : "Verzonden", + "pending" : " (in afwachting)", + "rescan" : "Opnieuw scannen", + "reconnect" : "Sluit", + "wallets" : "Portefeuilles", + "show_seed" : "Toon zaad", + "show_keys" : "Toon sleutels", + "address_book_menu" : "Adresboek", + "reconnection" : "Reconnection", + "reconnect_alert_text" : "Weet u zeker dat u opnieuw verbinding wilt maken?", + + + "exchange" : "Uitwisseling", + "clear" : "Duidelijk", + "change_exchange_provider" : "Wijzig Exchange Provider", + "you_will_send" : "Je zal versturen", + "you_will_get" : "Je zult krijgen", + "amount_is_guaranteed" : "Het ontvangen bedrag is gegarandeerd", + "amount_is_estimate" : "Het ontvangen bedrag is een schatting", + "powered_by" : "Aangedreven door ${title}", + "error" : "Fout", + "estimated" : "Geschatte", + "min_value" : "Min: ${value} ${currency}", + "max_value" : "Max: ${value} ${currency}", + "change_currency" : "Verander valuta", + + + "copy_id" : "ID kopiëren", + "exchange_result_write_down_trade_id" : "Kopieer of noteer de handels-ID om door te gaan.", + "trade_id" : "Trade ID:\n${id}", + "copied_to_clipboard" : "Gekopieerd naar het klembord", + "saved_the_trade_id" : "Ik heb de ruil-ID opgeslagen", + "fetching" : "Ophalen", + "id" : "ID: ", + "amount" : "Bedrag: ", + "payment_id" : "Betaling ID: ", + "status" : "Staat: ", + "offer_expires_in" : "Aanbieding verloopt over: ", + "trade_is_powered_by" : "Deze transactie wordt mogelijk gemaakt door ${provider}", + "copy_address" : "Adres kopiëren", + "exchange_result_confirm" : "Door op bevestigen te drukken, wordt u verzonden ${fetchingLabel} ${from} uit je portemonnee genoemd ${walletName} naar bovenstaand adres. Of u kunt uw externe portemonnee naar bovenstaand adres / QR-code sturen.\n\nDruk op bevestigen om door te gaan of terug te gaan om de bedragen te wijzigen.\n\n", + "exchange_result_description" : "Zend alstublieft ${fetchingLabel} ${from} naar bovenstaand adres.\n\n'", + "exchange_result_write_down_ID" : "*Kopieer of noteer uw hierboven getoonde ID.", + "confirm" : "Bevestigen", + "confirm_sending" : "Bevestig verzending", + "commit_transaction_amount_fee" : "Verricht transactie\nBedrag: ${amount}\nhonorarium: ${fee}", + "sending" : "Bezig met verzenden", + "transaction_sent" : "Transactie verzonden!", + "expired" : "Verlopen", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "Sturen XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "Voer uw pincode in", + "loading_your_wallet" : "Uw portemonnee laden", + + + "new_wallet" : "Nieuwe portemonnee", + "wallet_name" : "Portemonnee naam", + "continue_text" : "Doorgaan met", + + + "node_new" : "Nieuw knooppunt", + "node_address" : "Knooppunt adres", + "node_port" : "Knooppunt poort", + "login" : "Log in", + "password" : "Wachtwoord", + "nodes" : "Knooppunten", + "node_reset_settings_title" : "Reset instellingen", + "nodes_list_reset_to_default_message" : "Weet u zeker dat u de standaardinstellingen wilt herstellen?", + "change_current_node" : "Weet u zeker dat u het huidige knooppunt wilt wijzigen in ${node}?", + "change" : "Verandering", + "remove_node" : "Knoop verwijderen", + "remove_node_message" : "Weet u zeker dat u het geselecteerde knooppunt wilt verwijderen?", + "remove" : "Verwijderen", + "delete" : "Delete", + + + "use" : "Gebruik ", + "digit_pin" : "-cijferige PIN", + + + "share_address" : "Deel adres", + "receive_amount" : "Bedrag", + "subaddresses" : "Subadressen", + + + "restore_restore_wallet" : "Portemonnee herstellen", + "restore_title_from_seed_keys" : "Herstel van zaad / sleutels", + "restore_description_from_seed_keys" : "Ontvang uw portemonnee terug uit seed / keys die u hebt opgeslagen op een veilige plaats", + "restore_next" : "Volgende", + "restore_title_from_backup" : "Herstellen vanuit een back-upbestand", + "restore_description_from_backup" : "Je kunt de hele Cake Wallet-app herstellen van\nuw back-upbestand", + "restore_seed_keys_restore" : "Zaad / sleutels herstellen", + "restore_title_from_seed" : "Herstel van zaad", + "restore_description_from_seed" : "Herstel uw portemonnee van het 25 woord\nof 13 woord combinatiecode", + "restore_title_from_keys" : "Herstel van sleutels", + "restore_description_from_keys" : "Herstel uw portemonnee van gegenereerd\ntoetsaanslagen opgeslagen van uw privésleutels", + "restore_wallet_name" : "Portemonnee naam", + "restore_address" : "Adres", + "restore_view_key_private" : "Bekijk sleutel (privaat)", + "restore_spend_key_private" : "Sleutel uitgeven (privaat)", + "restore_recover" : "Herstellen", + "restore_wallet_restore_description" : "Portemonnee-herstelbeschrijving", + + + "seed_title" : "Zaad", + "seed_share" : "Deel zaad", + "copy" : "Kopiëren", + + + "seed_alert_first_text" : "De volgende pagina wordt getoond\njij een zaadje.", + "seed_alert_second_text" : "Noteer deze voor het geval dat\nje verliest of veegt je telefoon.", + "seed_alert_third_text" : "Je kunt het zaad ook weer zien\nin de ", + "seed_alert_settings" : "instellingen", + "seed_alert_menu" : " menu.", + "seed_alert_understand" : "Ik begrijp het", + + + "send_title" : "Stuur Monero", + "send_your_wallet" : "Uw portemonnee", + "send_monero_address" : "Monero-adres", + "send_payment_id" : "Betaling ID (facultatief)", + "all" : "ALLE", + "send_error_minimum_value" : "Minimale waarde van bedrag is 0,01", + "send_error_currency" : "Valuta kan alleen cijfers bevatten", + "send_estimated_fee" : "Geschatte vergoeding:", + "send_priority" : "Momenteel is de vergoeding vastgesteld op ${transactionPriority} prioriteit.\nTransactieprioriteit kan worden aangepast in de instellingen", + "send_creating_transaction" : "Transactie maken", + + + "settings_title" : "Instellingen", + "settings_nodes" : "knooppunten", + "settings_current_node" : "Huidige knooppunt", + "settings_wallets" : "Portemonnee", + "settings_display_balance_as" : "Toon saldo als", + "settings_currency" : "Valuta", + "settings_fee_priority" : "Tariefprioriteit", + "settings_save_recipient_address" : "Adres ontvanger opslaan", + "settings_personal" : "Persoonlijk", + "settings_change_pin" : "Verander pincode", + "settings_change_language" : "Verander de taal", + "settings_allow_biometrical_authentication" : "Biometrische authenticatie toestaan", + "settings_dark_mode" : "Donkere modus", + "settings_transactions" : "Transacties", + "settings_trades" : "Trades", + "settings_display_on_dashboard_list" : "Weergeven op dashboardlijst", + "settings_all" : "ALLE", + "settings_only_trades" : "Alleen handel", + "settings_only_transactions" : "Alleen transacties", + "settings_none" : "Geen", + "settings_support" : "Ondersteuning", + "settings_terms_and_conditions" : "Voorwaarden", + "pin_is_incorrect" : "PIN is onjuist", + + + "setup_pin" : "PIN instellen", + "enter_your_pin_again" : "Voer uw PIN opnieuw in", + "setup_successful" : "Uw PIN is succesvol ingesteld!", + + + "wallet_keys" : "Portemonnee sleutels", + "view_key_private" : "Bekijk sleutel (privaat)", + "view_key_public" : "Bekijk sleutel (openbaar)", + "spend_key_private" : "Sleutel uitgeven (privaat)", + "spend_key_public" : "Sleutel uitgeven (openbaar)", + "copied_key_to_clipboard" : "Gekopieerd ${key} naar het klembord", + + + "new_subaddress_title" : "Nieuw subadres", + "new_subaddress_label_name" : "Label naam", + "new_subaddress_create" : "Creëren", + + + "subaddress_title" : "Subadreslijst", + + + "trade_details_title" : "Handelsgegevens", + "trade_details_id" : "ID", + "trade_details_state" : "Staat", + "trade_details_fetching" : "Ophalen", + "trade_details_provider" : "Leverancier", + "trade_details_created_at" : "Gemaakt bij", + "trade_details_pair" : "Paar", + "trade_details_copied" : "${title} gekopieerd naar het klembord", + + + "trade_history_title" : "Handelsgeschiedenis", + + + "transaction_details_title" : "Transactie details", + "transaction_details_transaction_id" : "Transactie ID", + "transaction_details_date" : "Datum", + "transaction_details_height" : "Hoogte", + "transaction_details_amount" : "Bedrag", + "transaction_details_copied" : "${title} gekopieerd naar het klembord", + "transaction_details_recipient_address" : "Adres van de ontvanger", + + + "wallet_list_title" : "Monero portemonnee", + "wallet_list_create_new_wallet" : "Maak een nieuwe portemonnee", + "wallet_list_restore_wallet" : "Portemonnee herstellen", + "wallet_list_load_wallet" : "Portemonnee laden", + "wallet_list_loading_wallet" : "Bezig met laden ${wallet_name} portemonnee", + "wallet_list_failed_to_load" : "Laden mislukt ${wallet_name} portemonnee. ${error}", + "wallet_list_removing_wallet" : "Verwijderen ${wallet_name} portemonnee", + "wallet_list_failed_to_remove" : "Verwijderen mislukt ${wallet_name} portemonnee. ${error}", + + + "widgets_address" : "Adres", + "widgets_restore_from_blockheight" : "Herstel vanaf blockheight", + "widgets_restore_from_date" : "Herstel vanaf datum", + "widgets_or" : "of", + "widgets_seed" : "Zaad", + + + "router_no_route" : "Geen route gedefinieerd voor ${name}", + + + "error_text_account_name" : "Accountnaam mag alleen letters, cijfers bevatten\nen moet tussen de 1 en 15 tekens lang zijn", + "error_text_contact_name" : "Naam contactpersoon kan niet bevatten ` , ' \" symbolen\nen moet tussen de 1 en 32 tekens lang zijn", + "error_text_address" : "Portemonnee-adres moet overeenkomen met het type\nvan cryptocurrency", + "error_text_node_address" : "Voer een iPv4-adres in", + "error_text_node_port" : "Knooppuntpoort kan alleen nummers tussen 0 en 65535 bevatten", + "error_text_payment_id" : "Betalings-ID kan alleen 16 tot 64 tekens bevatten in hexadecimale volgorde", + "error_text_xmr" : "XMR-waarde kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 12", + "error_text_fiat" : "Waarde van bedrag kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 2", + "error_text_subaddress_name" : "Naam subadres mag niet bevatten ` , ' \" symbolen\nen moet tussen de 1 en 20 tekens lang zijn", + "error_text_amount" : "Bedrag kan alleen cijfers bevatten", + "error_text_wallet_name" : "Naam portemonnee kan alleen letters, cijfers bevatten\nen moet tussen de 1 en 15 tekens lang zijn", + "error_text_keys" : "Portefeuillesleutels kunnen maximaal 64 tekens bevatten in hexadecimale volgorde", + "error_text_crypto_currency" : "Het aantal breukcijfers\nmoet kleiner zijn dan of gelijk zijn aan 12", + + + "auth_store_ban_timeout" : "time-out verbieden", + "auth_store_banned_for" : "Verboden voor ", + "auth_store_banned_minutes" : " notulen", + "auth_store_incorrect_password" : "Incorrect PIN", + "wallet_store_monero_wallet" : "Monero portemonnee", + "wallet_restoration_store_incorrect_seed_length" : "Onjuiste zaadlengte", + + + "full_balance" : "Volledig saldo", + "available_balance" : "Beschikbaar saldo", + "hidden_balance" : "Verborgen balans", + + + "sync_status_syncronizing" : "SYNCHRONISEREN", + "sync_status_syncronized" : "SYNCHRONIZED", + "sync_status_not_connected" : "NIET VERBONDEN", + "sync_status_starting_sync" : "BEGINNEN MET SYNCHRONISEREN", + "sync_status_failed_connect" : "MISLUKT VERBINDING MET DE NODE", + "sync_status_connecting" : "AANSLUITING", + "sync_status_connected" : "VERBONDEN", + + + "transaction_priority_slow" : "Langzaam", + "transaction_priority_regular" : "Regelmatig", + "transaction_priority_medium" : "Medium", + "transaction_priority_fast" : "Snel", + "transaction_priority_fastest" : "Snelste", + + + "trade_for_not_created" : "Ruilen voor ${title} is niet gemaakt.", + "trade_not_created" : "Handel niet gecreëerd.", + "trade_id_not_found" : "Handel ${tradeId} van ${title} niet gevonden.", + "trade_not_found" : "Handel niet gevonden.", + + + "trade_state_pending" : "In afwachting", + "trade_state_confirming" : "Bevestiging", + "trade_state_trading" : "Handel", + "trade_state_traded" : "Traded", + "trade_state_complete" : "Compleet", + "trade_state_to_be_created" : "Om gecreëerd te worden", + "trade_state_unpaid" : "Onbetaald", + "trade_state_underpaid" : "Slecht betaald", + "trade_state_paid_unconfirmed" : "Niet bevestigd", + "trade_state_paid" : "Betaald", + "trade_state_btc_sent" : "Verzonden", + "trade_state_timeout" : "Time-out", + "trade_state_created" : "Gemaakt", + "trade_state_finished" : "Afgewerkt", + + "change_language" : "Verander de taal", + "change_language_to" : "Verander de taal in ${language}?" +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb new file mode 100644 index 000000000..9a99ab9a4 --- /dev/null +++ b/res/values/strings_pl.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "WITAMY\nW CAKE WALLET", + "first_wallet_text" : "Niesamowity portfel\nfdla Monero", + "please_make_selection" : "Wybierz poniżej, aby\ncutwórz lub odzyskaj swój portfel.", + "create_new" : "Tworzyć nowe", + "restore_wallet" : "Przywróć portfel", + + + "accounts" : "Konta", + "edit" : "Edytować", + "account" : "Konto", + "add" : "Dodaj", + + + "address_book" : "Książka adresowa", + "contact" : "Kontakt", + "please_select" : "Proszę wybrać:", + "cancel" : "Anulować", + "ok" : "Dobrze", + "contact_name" : "Nazwa Kontaktu", + "reset" : "Nastawić", + "save" : "Zapisać", + + + "authenticated" : "Zalegalizowany", + "authentication" : "Poświadczenie", + "failed_authentication" : "Nieudane uwierzytelnienie. ${state_error}", + + + "wallet_menu" : "Menu portfela", + "Blocks_remaining" : "${status} Bloki pozostałe", + "please_try_to_connect_to_another_node" : "Spróbuj połączyć się z innym węzłem", + "xmr_hidden" : "XMR Ukryty", + "xmr_available_balance" : "XMR Dostępne saldo", + "xmr_full_balance" : "XMR Pełna równowaga", + "send" : "Wysłać", + "receive" : "Otrzymać", + "transactions" : "Transakcje", + "incoming" : "Przychodzące", + "outgoing" : "Towarzyski", + "transactions_by_date" : "Transakcje według daty", + "trades" : "Transakcje", + "filters" : "Filtry", + "today" : "Dzisiaj", + "yesterday" : "Wczoraj", + "received" : "Odebrane", + "sent" : "Wysłano", + "pending" : " (w oczekiwaniu)", + "rescan" : "Skanuj ponownie", + "reconnect" : "Na nowo połączyć", + "wallets" : "Portfele", + "show_seed" : "Pokaż nasiona", + "show_keys" : "Pokaż klucze", + "address_book_menu" : "Książka adresowa", + "reconnection" : "Ponowne połączenie", + "reconnect_alert_text" : "Czy na pewno ponownie się połączysz?", + + + "exchange" : "Wymieniać się", + "clear" : "Jasny", + "change_exchange_provider" : "Zmień dostawcę programu Exchange", + "you_will_send" : "Wyślesz", + "you_will_get" : "Dostaniesz", + "amount_is_guaranteed" : "Otrzymana kwota jest gwarantowana", + "amount_is_estimate" : "Otrzymana kwota jest wartością szacunkową", + "powered_by" : "Zasilany przez ${title}", + "error" : "Błąd", + "estimated" : "Oszacowano", + "min_value" : "Min: ${value} ${currency}", + "max_value" : "Max: ${value} ${currency}", + "change_currency" : "Change Currency", + + + "copy_id" : "ID kopii", + "exchange_result_write_down_trade_id" : "Skopiuj lub zanotuj identyfikator transakcji, aby kontynuować.", + "trade_id" : "Identyfikator handlu:\n${id}", + "copied_to_clipboard" : "Skopiowane do schowka", + "saved_the_trade_id" : "Zapisałem ID", + "fetching" : "Ujmujący", + "id" : "ID: ", + "amount" : "Ilość: ", + "payment_id" : "Płatności ID: ", + "status" : "Status: ", + "offer_expires_in" : "Oferta wygasa za ", + "trade_is_powered_by" : "Ten handel jest zasilany przez ${provider}", + "copy_address" : "Skopiuj adress", + "exchange_result_confirm" : "Naciskając Potwierdź, wyślesz ${fetchingLabel} ${from} z twojego portfela ${walletName} z twojego portfela. Lub możesz wysłać z zewnętrznego portfela na powyższy adres / kod QR.\n\nNaciśnij Potwierdź, aby kontynuować lub wróć, aby zmienić kwoty.\n\n", + "exchange_result_description" : "Proszę wyślij ${fetchingLabel} ${from} na adres podany powyżej.\n\n'", + "exchange_result_write_down_ID" : "*Skopiuj lub zanotuj swój identyfikator pokazany powyżej.", + "confirm" : "Potwierdzać", + "confirm_sending" : "Potwierdź wysłanie", + "commit_transaction_amount_fee" : "Zatwierdź transakcję\nIlość: ${amount}\nOpłata: ${fee}", + "sending" : "Wysyłanie", + "transaction_sent" : "Transakcja wysłana!", + "expired" : "Przedawniony", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "Wysłać XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "Wpisz Twój kod PIN", + "loading_your_wallet" : "Ładowanie portfela", + + + "new_wallet" : "Nowy portfel", + "wallet_name" : "Nazwa portfela", + "continue_text" : "Dalej", + + + "node_new" : "Nowy węzeł", + "node_address" : "Adres węzła", + "node_port" : "Port węzła", + "login" : "Zaloguj Się", + "password" : "Hasło", + "nodes" : "Węzły", + "node_reset_settings_title" : "Resetowanie ustawień", + "nodes_list_reset_to_default_message" : "Czy na pewno chcesz przywrócić ustawienia domyślne?", + "change_current_node" : "Czy na pewno chcesz przywrócić ustawienia domyślne? ${node}?", + "change" : "Zmiana", + "remove_node" : "Usuń węzeł", + "remove_node_message" : "Czy na pewno chcesz usunąć wybrany węzeł?", + "remove" : "Usunąć", + "delete" : "Kasować", + + + "use" : "Posługiwać się ", + "digit_pin" : "- znak PIN", + + + "share_address" : "Udostępnij adres", + "receive_amount" : "Ilość", + "subaddresses" : "Podadresy", + + + "restore_restore_wallet" : "Przywróć portfel", + "restore_title_from_seed_keys" : "Przywróć z nasion / kluczy", + "restore_description_from_seed_keys" : "Odzyskaj swój portfel z nasion / kluczy, które zapisałeś w bezpiecznym miejscu", + "restore_next" : "Kolejny", + "restore_title_from_backup" : "Przywróć z pliku kopii zapasowej", + "restore_description_from_backup" : "Możesz przywrócić całą aplikację Cake Wallet z\nplik kopii zapasowej", + "restore_seed_keys_restore" : "Przywracanie nasion / kluczy", + "restore_title_from_seed" : "Przywróć z nasion", + "restore_description_from_seed" : "Przywróć swój portfel z 25 słów\nlub 13-słowny kod kombinacji", + "restore_title_from_keys" : "Przywróć z kluczy", + "restore_description_from_keys" : "Przywróć swój portfel z wygenerowanego\nnaciśnięcia klawiszy zapisane z kluczy prywatnych", + "restore_wallet_name" : "Nazwa portfela", + "restore_address" : "Adres", + "restore_view_key_private" : "Wyświetl klucz (prywatny)", + "restore_spend_key_private" : "Wydaj klucz (prywatny)", + "restore_recover" : "Wyzdrowieć", + "restore_wallet_restore_description" : "Opis przywracania portfela", + + + "seed_title" : "Ziarno", + "seed_share" : "Udostępnij ziarno", + "copy" : "Kopiuj", + + + "seed_alert_first_text" : "Pojawi się następna strona\nziarno.", + "seed_alert_second_text" : "Zapisz je na wszelki wypadek\nzgubisz lub wyczyścisz telefon.", + "seed_alert_third_text" : "Możesz także ponownie zobaczyć ziarno\nw ", + "seed_alert_settings" : "ustawienia", + "seed_alert_menu" : " menu.", + "seed_alert_understand" : "rozumiem", + + + "send_title" : "Wyślij Monero", + "send_your_wallet" : "Twój portfel", + "send_monero_address" : "Adres Monero", + "send_payment_id" : "Identyfikator płatności (opcjonalny)", + "all" : "WSZYSTKO", + "send_error_minimum_value" : "Minimalna wartość kwoty to 0,01", + "send_error_currency" : "Waluta może zawierać tylko cyfry", + "send_estimated_fee" : "Szacowana opłata:", + "send_priority" : "Obecnie opłata ustalona jest na ${transactionPriority} priorytet.\nPriorytet transakcji można zmienić w ustawieniach", + "send_creating_transaction" : "Tworzenie transakcji", + + + "settings_title" : "Ustawienia", + "settings_nodes" : "Węzły", + "settings_current_node" : "Bieżący węzeł", + "settings_wallets" : "Portfele", + "settings_display_balance_as" : "Wyświetl saldo jako", + "settings_currency" : "Waluta", + "settings_fee_priority" : "Priorytet opłaty", + "settings_save_recipient_address" : "Zapisz adres odbiorcy", + "settings_personal" : "Osobisty", + "settings_change_pin" : "Zmień PIN", + "settings_change_language" : "Zmień język", + "settings_allow_biometrical_authentication" : "Zezwalaj na uwierzytelnianie biometryczne", + "settings_dark_mode" : "Tryb ciemny", + "settings_transactions" : "Transakcje", + "settings_trades" : "Transakcje", + "settings_display_on_dashboard_list" : "Wyświetl na liście kokpitu", + "settings_all" : "Cały", + "settings_only_trades" : "Tylko transakcje", + "settings_only_transactions" : "Tylko transakcje", + "settings_none" : "Żaden", + "settings_support" : "Wsparcie", + "settings_terms_and_conditions" : "Zasady i warunki", + "pin_is_incorrect" : "PPIN jest niepoprawny", + + + "setup_pin" : "Ustaw PIN", + "enter_your_pin_again" : "Wprowadź ponownie swój kod PIN", + "setup_successful" : "Twój kod PIN został pomyślnie skonfigurowany!", + + + "wallet_keys" : "Klucze portfela", + "view_key_private" : "Wyświetl klucz (prywatny)", + "view_key_public" : "Wyświetl klucz (publiczny)", + "spend_key_private" : "Wydaj klucz (prywatny)", + "spend_key_public" : "Wydaj klucz (publiczny)", + "copied_key_to_clipboard" : "Skopiowane ${key} do schowka", + + + "new_subaddress_title" : "Nowy podadres", + "new_subaddress_label_name" : "Nazwa etykiety", + "new_subaddress_create" : "Stwórz", + + + "subaddress_title" : "Lista podadresów", + + + "trade_details_title" : "Szczegóły handlu", + "trade_details_id" : "ID", + "trade_details_state" : "Stan", + "trade_details_fetching" : "Ujmujący", + "trade_details_provider" : "Dostawca", + "trade_details_created_at" : "Utworzono w", + "trade_details_pair" : "Para", + "trade_details_copied" : "${title} skopiowane do schowka", + + + "trade_history_title" : "Historia handlu", + + + "transaction_details_title" : "Szczegóły transakcji", + "transaction_details_transaction_id" : "Transakcja ID", + "transaction_details_date" : "Data", + "transaction_details_height" : "Wysokość", + "transaction_details_amount" : "Ilość", + "transaction_details_copied" : "${title} skopiowane do schowka", + "transaction_details_recipient_address" : "Adres odbiorcy", + + + "wallet_list_title" : "Portfel Monero", + "wallet_list_create_new_wallet" : "Utwórz nowy portfel", + "wallet_list_restore_wallet" : "Przywróć portfel", + "wallet_list_load_wallet" : "Załaduj portfel", + "wallet_list_loading_wallet" : "Ładuję ${wallet_name} portfel", + "wallet_list_failed_to_load" : "Nie udało się załadować ${wallet_name} portfel. ${error}", + "wallet_list_removing_wallet" : "Usuwanie ${wallet_name} portfel", + "wallet_list_failed_to_remove" : "Nie udało się usunąć ${wallet_name} portfel. ${error}", + + + "widgets_address" : "Adres", + "widgets_restore_from_blockheight" : "Przywróć z wysokości bloku", + "widgets_restore_from_date" : "Przywróć od daty", + "widgets_or" : "lub", + "widgets_seed" : "Ziarno", + + + "router_no_route" : "Brak zdefiniowanej trasy dla ${name}", + + + "error_text_account_name" : "Nazwa konta może zawierać tylko litery, cyfry\ni musi mieć od 1 do 15 znaków", + "error_text_contact_name" : "Nazwa kontaktu nie może zawierać` , ' \" symbolika\ni musi mieć od 1 do 32 znaków ", + "error_text_address" : "Wallet address must correspond to the type\nof cryptocurrency", + "error_text_node_address" : "Wpisz adres iPv4", + "error_text_node_port" : "Port węzła może zawierać tylko liczby od 0 do 65535", + "error_text_payment_id" : "ID może zawierać od 16 do 64 znaków w formacie szesnastkowym", + "error_text_xmr" : "Wartość XMR nie może przekraczać dostępnego salda.\nLiczba cyfr ułamkowych musi być mniejsza lub równa 12", + "error_text_fiat" : "Wartość kwoty nie może przekroczyć dostępnego salda.\nLiczba cyfr ułamkowych musi być mniejsza lub równa 2", + "error_text_subaddress_name" : "Nazwa podadresu nie może zawierać ` , ' \" symbolika\ni musi mieć od 1 do 20 znaków", + "error_text_amount" : "Kwota może zawierać tylko liczby", + "error_text_wallet_name" : "Nazwa portfela może zawierać tylko litery i cyfry\ni musi mieć od 1 do 15 znaków", + "error_text_keys" : "Klucze portfela mogą zawierać tylko 64 znaki w systemie szesnastkowym", + "error_text_crypto_currency" : "Liczba cyfr ułamkowych\nmusi być mniejsza lub równa 12", + + + "auth_store_ban_timeout" : "przekroczenie limitu czasu", + "auth_store_banned_for" : "Bzbanowany za ", + "auth_store_banned_minutes" : " minuty", + "auth_store_incorrect_password" : "Niepoprawny PIN", + "wallet_store_monero_wallet" : "Portfel Monero", + "wallet_restoration_store_incorrect_seed_length" : "Nieprawidłowa długość nasion", + + + "full_balance" : "Pełna równowaga", + "available_balance" : "Dostępne saldo", + "hidden_balance" : "Ukryta równowaga", + + + "sync_status_syncronizing" : "SYNCHRONIZACJA", + "sync_status_syncronized" : "SYNCHRONIZOWANY", + "sync_status_not_connected" : "NIE POŁĄCZONY", + "sync_status_starting_sync" : "ROZPOCZĘCIE SYNCHRONIZACJI", + "sync_status_failed_connect" : "NIE MOŻNA PODŁĄCZYĆ DO WĘZŁA", + "sync_status_connecting" : "ZŁĄCZONY", + "sync_status_connected" : "POŁĄCZONY", + + + "transaction_priority_slow" : "Powolny", + "transaction_priority_regular" : "Regularny", + "transaction_priority_medium" : "Średni", + "transaction_priority_fast" : "Szybki", + "transaction_priority_fastest" : "Najszybszy", + + + "trade_for_not_created" : "Zamienić się za ${title} nie jest tworzony.", + "trade_not_created" : "Handel nie utworzony.", + "trade_id_not_found" : "Handel ${tradeId} of ${title} nie znaleziono.", + "trade_not_found" : "Nie znaleziono handlu.", + + + "trade_state_pending" : "W oczekiwaniu", + "trade_state_confirming" : "Potwierdzam", + "trade_state_trading" : "Handlowy", + "trade_state_traded" : "Handlowane", + "trade_state_complete" : "Kompletny", + "trade_state_to_be_created" : "Zostać stworzonym", + "trade_state_unpaid" : "Nie zapłacony", + "trade_state_underpaid" : "Niedopłacone", + "trade_state_paid_unconfirmed" : "Płatne niepotwierdzone", + "trade_state_paid" : "Płatny", + "trade_state_btc_sent" : "Wysłane", + "trade_state_timeout" : "Koniec czasu", + "trade_state_created" : "Stworzony", + "trade_state_finished" : "Skończone", + + "change_language" : "Zmień język", + "change_language_to" : "Zmień język na ${language}?" +} \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb new file mode 100644 index 000000000..8dd4ea97d --- /dev/null +++ b/res/values/strings_pt.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "BEM-VINDO\nAO CAKE WALLET", + "first_wallet_text" : "Carteira impressionante\npara Monero", + "please_make_selection" : "Faça a seleção abaixo para\ncrie ou recupere sua carteira.", + "create_new" : "Crie um novo", + "restore_wallet" : "Restaurar carteira", + + + "accounts" : "Contas", + "edit" : "Editar", + "account" : "Conta", + "add" : "Adicionar", + + + "address_book" : "Livro de endereços", + "contact" : "Contato", + "please_select" : "Por favor selecione:", + "cancel" : "Cancelar", + "ok" : "Está bem", + "contact_name" : "Nome de contato", + "reset" : "Restabelecer", + "save" : "Salve", + + + "authenticated" : "Autenticado", + "authentication" : "Autenticação", + "failed_authentication" : "Falha na autenticação. ${state_error}", + + + "wallet_menu" : "Menu da Carteira", + "Blocks_remaining" : "${status} Blocos restantes", + "please_try_to_connect_to_another_node" : "Por favor, tente conectar-se a outro nó", + "xmr_hidden" : "XMR Oculto", + "xmr_available_balance" : "Saldo disponível do XMR", + "xmr_full_balance" : "Saldo total do XMR", + "send" : "Mandar", + "receive" : "Receber", + "transactions" : "Transações", + "incoming" : "Entrada", + "outgoing" : "Extrovertido", + "transactions_by_date" : "Transações por data", + "trades" : "Comércios", + "filters" : "Filtros", + "today" : "Hoje", + "yesterday" : "Ontem", + "received" : "Recebido", + "sent" : "Enviei", + "pending" : " (pendente)", + "rescan" : "Verificar novamente", + "reconnect" : "Reconectar", + "wallets" : "Carteiras", + "show_seed" : "Mostrar semente", + "show_keys" : "Mostrar chaves", + "address_book_menu" : "Livro de endereços", + "reconnection" : "Reconexão", + "reconnect_alert_text" : "Você tem certeza de reconectar?", + + + "exchange" : "Troca", + "clear" : "Claro", + "change_exchange_provider" : "Alterar provedor de câmbio", + "you_will_send" : "Você enviará", + "you_will_get" : "Você vai ter", + "amount_is_guaranteed" : "O valor do recebimento é garantido", + "amount_is_estimate" : "O valor recebido é uma estimativa", + "powered_by" : "Distribuído por ${title}", + "error" : "Erro", + "estimated" : "Estimado", + "min_value" : "Mín: ${value} ${currency}", + "max_value" : "Máx: ${value} ${currency}", + "change_currency" : "Alteração de moeda", + + + "copy_id" : "Cópia de ID", + "exchange_result_write_down_trade_id" : "Copie ou anote o ID comercial para continuar.", + "trade_id" : "ID comercial:\n${id}", + "copied_to_clipboard" : "Copiado para a área de transferência", + "saved_the_trade_id" : "Salvei o ID comercial", + "fetching" : "Buscando", + "id" : "ID: ", + "amount" : "Montante: ", + "payment_id" : "ID de pagamento: ", + "status" : "Status: ", + "offer_expires_in" : "A oferta expira em: ", + "trade_is_powered_by" : "Este comércio é alimentado por ${provider}", + "copy_address" : "Copiar endereço", + "exchange_result_confirm" : "Ao pressionar confirmar, você estará enviando ${fetchingLabel} ${from} da sua carteira ${walletName} para o endereço mostrado acima. Ou você pode enviar da sua carteira externa para o endereço / código QR acima.\n\nPressione confirmar para continuar ou voltar para alterar os valores.\n\n", + "exchange_result_description" : "Por favor envie ${fetchingLabel} ${from} para o endereço mostrado acima.\n\n'", + "exchange_result_write_down_ID" : "*Copie ou anote seu ID mostrado acima.", + "confirm" : "Confirme", + "confirm_sending" : "Confirme o envio", + "commit_transaction_amount_fee" : "Confirmar transação\nMontante: ${amount}\nTaxa: ${fee}", + "sending" : "Enviando", + "transaction_sent" : "Transação enviada!", + "expired" : "Expirado", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "Mandar XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "Insira seu PIN", + "loading_your_wallet" : "Carregando sua carteira", + + + "new_wallet" : "Nova carteira", + "wallet_name" : "Nome da carteira", + "continue_text" : "Continuar", + + + "node_new" : "Novo nó", + "node_address" : "Endereço do nó", + "node_port" : "Porta do nó", + "login" : "Entrar", + "password" : "Senha", + "nodes" : "Nós", + "node_reset_settings_title" : "Redefinir configurações", + "nodes_list_reset_to_default_message" : "Tem certeza de que deseja redefinir as configurações para o padrão?", + "change_current_node" : "Você tem certeza de alterar o nó atual para ${node}?", + "change" : "Mudança", + "remove_node" : "Remover nó", + "remove_node_message" : "Tem certeza de que deseja remover o nó selecionado?", + "remove" : "Remover", + "delete" : "Excluir", + + + "use" : "Usar ", + "digit_pin" : "de dois dígitos", + + + "share_address" : "Compartilhar endereço", + "receive_amount" : "Montante", + "subaddresses" : "Sub-endereços", + + + "restore_restore_wallet" : "Restaurar carteira", + "restore_title_from_seed_keys" : "Restaurar a partir de sementes / chaves", + "restore_description_from_seed_keys" : "Volte sua carteira a partir de sementes / chaves que você salvou em um local seguro", + "restore_next" : "Próximo", + "restore_title_from_backup" : "Restaurar a partir de um arquivo de backup", + "restore_description_from_backup" : "Você pode restaurar todo o aplicativo Cake Wallet em\nseu arquivo de backup", + "restore_seed_keys_restore" : "Restauração de sementes / chaves", + "restore_title_from_seed" : "Restaurar a partir da semente", + "restore_description_from_seed" : "Restaure sua carteira a partir das 25 palavras\noou código de combinação de 13 palavras", + "restore_title_from_keys" : "Restaurar a partir de chaves", + "restore_description_from_keys" : "Restaure sua carteira da geração\npressionamentos de tecla salvos em suas chaves privadas", + "restore_wallet_name" : "Nome da carteira", + "restore_address" : "Endereço", + "restore_view_key_private" : "Tecla Ver (privado)", + "restore_spend_key_private" : "Chave de gastos (privado)", + "restore_recover" : "Recuperar", + "restore_wallet_restore_description" : "Restauração de carteira", + + + "seed_title" : "Semente", + "seed_share" : "Compartilhar sementes", + "copy" : "Cópia de", + + + "seed_alert_first_text" : "A próxima página mostrará\nvocê é uma semente.", + "seed_alert_second_text" : "Por favor, escreva-as apenas em\ncaso você perca ou limpe seu telefone.", + "seed_alert_third_text" : "Você também pode ver a semente novamente\nno ", + "seed_alert_settings" : "configurações", + "seed_alert_menu" : "cardápio.", + "seed_alert_understand" : "Compreendo", + + + "send_title" : "Enviar Monero", + "send_your_wallet" : "Enviar Monero", + "send_monero_address" : "Endereço Monero", + "send_payment_id" : "ID de pagamento (opcional)", + "all" : "TODOS", + "send_error_minimum_value" : "O valor mínimo da quantia é 0,01", + "send_error_currency" : "A moeda pode conter apenas números", + "send_estimated_fee" : "Taxa estimada:", + "send_priority" : "Atualmente, a taxa está definida em ${transactionPriority} prioridade.\nA prioridade da transação pode ser ajustada nas configurações", + "send_creating_transaction" : "Criando transação", + + + "settings_title" : "Configurações", + "settings_nodes" : "Nós", + "settings_current_node" : "Nó atual", + "settings_wallets" : "Carteiras", + "settings_display_balance_as" : "Exibir saldo como", + "settings_currency" : "Moeda", + "settings_fee_priority" : "Prioridade da taxa", + "settings_save_recipient_address" : "Salvar endereço do destinatário", + "settings_personal" : "Pessoal", + "settings_change_pin" : "Alterar PIN", + "settings_change_language" : "Mudar idioma", + "settings_allow_biometrical_authentication" : "Permitir autenticação biométrica", + "settings_dark_mode" : "Modo escuro", + "settings_transactions" : "Transações", + "settings_trades" : "Comércios", + "settings_display_on_dashboard_list" : "Exibir na lista do painel", + "settings_all" : "TODOS", + "settings_only_trades" : "Somente negociações", + "settings_only_transactions" : "Somente transações", + "settings_none" : "Nenhum", + "settings_support" : "Suporte", + "settings_terms_and_conditions" : "Termos e Condições", + "pin_is_incorrect" : "PIN incorreto", + + + "setup_pin" : "PIN de configuração", + "enter_your_pin_again" : "Insira seu pin novamente", + "setup_successful" : "Seu PIN foi configurado com sucesso!", + + + "wallet_keys" : "Chaves da carteira", + "view_key_private" : "Tecla Ver (privado)", + "view_key_public" : "Tecla Ver (público)", + "spend_key_private" : "Chave de gastos (privado)", + "spend_key_public" : "Chave de gastos (público)", + "copied_key_to_clipboard" : "Copiado ${key} para a área de transferência", + + + "new_subaddress_title" : "Novo sub-endereço", + "new_subaddress_label_name" : "Nome do rótulo", + "new_subaddress_create" : "Crio", + + + "subaddress_title" : "Lista de sub-endereços", + + + "trade_details_title" : "Detalhes do comércio", + "trade_details_id" : "ID", + "trade_details_state" : "Estado", + "trade_details_fetching" : "Buscando", + "trade_details_provider" : "Fornecedor", + "trade_details_created_at" : "Criado em", + "trade_details_pair" : "Par", + "trade_details_copied" : "${title} Copiado para a área de transferência", + + + "trade_history_title" : "Histórico comercial", + + + "transaction_details_title" : "Detalhes da transação", + "transaction_details_transaction_id" : "ID da transação", + "transaction_details_date" : "Encontro", + "transaction_details_height" : "Altura", + "transaction_details_amount" : "Montante", + "transaction_details_copied" : "${title} Copiado para a área de transferência", + "transaction_details_recipient_address" : "Endereço do destinatário", + + + "wallet_list_title" : "Monero Carteira", + "wallet_list_create_new_wallet" : "Criar nova carteira", + "wallet_list_restore_wallet" : "Restaurar carteira", + "wallet_list_load_wallet" : "Carregar carteira", + "wallet_list_loading_wallet" : "Carregando ${wallet_name} carteira", + "wallet_list_failed_to_load" : "Falha ao carregar ${wallet_name} carteira. ${error}", + "wallet_list_removing_wallet" : "Removendo ${wallet_name} carteira", + "wallet_list_failed_to_remove" : "Falha ao remover ${wallet_name} carteira. ${error}", + + + "widgets_address" : "Endereço", + "widgets_restore_from_blockheight" : "Restaurar da altura do bloco", + "widgets_restore_from_date" : "Restaurar a partir da data", + "widgets_or" : "ou", + "widgets_seed" : "Semente", + + + "router_no_route" : "Nenhuma rota definida para ${name}", + + + "error_text_account_name" : "O nome da conta pode conter apenas letras, números\ne deve ter entre 1 e 15 caracteres", + "error_text_contact_name" : "O nome do contato não pode conter ` , ' \" símbolos\ne deve ter entre 1 e 32 caracteres", + "error_text_address" : "O endereço da carteira deve corresponder ao tipo\nde criptomoeda", + "error_text_node_address" : "Digite um endereço iPv4", + "error_text_node_port" : "A porta do nó pode conter apenas números entre 0 e 65535", + "error_text_payment_id" : "O código de pagamento pode conter apenas de 16 a 64 caracteres em hexadecimal", + "error_text_xmr" : "O valor XMR não pode exceder o saldo disponível.\nTO número de dígitos da fração deve ser menor ou igual a 12", + "error_text_fiat" : "O valor do valor não pode exceder o saldo disponível.\nO número de dígitos da fração deve ser menor ou igual a 2", + "error_text_subaddress_name" : "O nome do sub-endereço não pode conter ` , ' \" símbolos\ne deve ter entre 1 e 20 caracteres", + "error_text_amount" : "O valor pode conter apenas números", + "error_text_wallet_name" : "O nome da carteira só pode conter letras, números\ne deve ter entre 1 e 15 caracteres", + "error_text_keys" : "As chaves da carteira podem conter apenas 64 caracteres em hexadecimal", + "error_text_crypto_currency" : "O número de dígitos da fração\ndeve ser menor ou igual a 12", + + + "auth_store_ban_timeout" : "proibir timeout", + "auth_store_banned_for" : "Banido por", + "auth_store_banned_minutes" : " minutes", + "auth_store_incorrect_password" : "PIN incorreto", + "wallet_store_monero_wallet" : "Carteira Monero", + "wallet_restoration_store_incorrect_seed_length" : "Comprimento de semente incorreto", + + + "full_balance" : "Saldo total", + "available_balance" : "Saldo disponível", + "hidden_balance" : "Saldo Oculto", + + + "sync_status_syncronizing" : "SINCRONIZANDO", + "sync_status_syncronized" : "SINCRONIZADO", + "sync_status_not_connected" : "NÃO CONECTADO", + "sync_status_starting_sync" : "INÍCIO DE SINCRONIZAÇÃO", + "sync_status_failed_connect" : "Falha na conexão com o nó", + "sync_status_connecting" : "CONECTANDO", + "sync_status_connected" : "CONECTADO", + + + "transaction_priority_slow" : "Lento", + "transaction_priority_regular" : "Regular", + "transaction_priority_medium" : "Médio", + "transaction_priority_fast" : "Rápido", + "transaction_priority_fastest" : "O mais rápido", + + + "trade_for_not_created" : "Comércio por ${title} não é criado.", + "trade_not_created" : "Comércio não criado.", + "trade_id_not_found" : "Comércio${tradeId} of ${title} não encontrado.", + "trade_not_found" : "Comércio não encontrado.", + + + "trade_state_pending" : "Pendente", + "trade_state_confirming" : "Confirmando", + "trade_state_trading" : "Negociação", + "trade_state_traded" : "Negociados", + "trade_state_complete" : "Completo", + "trade_state_to_be_created" : "Para ser criado", + "trade_state_unpaid" : "Não remunerado", + "trade_state_underpaid" : "Mal pago", + "trade_state_paid_unconfirmed" : "Pago não confirmado", + "trade_state_paid" : "Pago", + "trade_state_btc_sent" : "Enviei", + "trade_state_timeout" : "Tempo esgotado", + "trade_state_created" : "Criado", + "trade_state_finished" : "Acabado", + + "change_language" : "Mudar idioma", + "change_language_to" : "Alterar idioma para ${language}?" +} \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb new file mode 100644 index 000000000..8429f14d2 --- /dev/null +++ b/res/values/strings_ru.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "Приветствуем в CAKE WALLET", + "first_wallet_text" : "В самом удобном кошельке для Monero", + "please_make_selection" : "Выберите способ создания кошелька: создать новый или восстановить Ваш существующий.", + "create_new" : "Создать новый", + "restore_wallet" : "Восстановить", + + + "accounts" : "Аккаунты", + "edit" : "Редактировать", + "account" : "Аккаунт", + "add" : "Добавить", + + + "address_book" : "Адресная книга", + "contact" : "Контакт", + "please_select" : "Пожалуйста, выберите:", + "cancel" : "Отменить", + "ok" : "OK", + "contact_name" : "Имя контакта", + "reset" : "Сброс", + "save" : "Сохранить", + + + "authenticated" : "Аутентифицировано", + "authentication" : "Аутентификация", + "failed_authentication" : "Ошибка аутентификации. ${state_error}", + + + "wallet_menu" : "Меню кошелька", + "Blocks_remaining" : "${status} Осталось блоков", + "please_try_to_connect_to_another_node" : "Пожалуйста, попробуйте подключиться к другой ноде", + "xmr_hidden" : "XMR Скрытый", + "xmr_available_balance" : "XMR Доступный баланс", + "xmr_full_balance" : "XMR Полный баланс", + "send" : "Отправить", + "receive" : "Получить", + "transactions" : "Транзакции", + "incoming" : "Входящие", + "outgoing" : "Исходящие", + "transactions_by_date" : "Сортировать по дате", + "trades" : "Сделки", + "filters" : "Фильтры", + "today" : "Сегодня", + "yesterday" : "Вчера", + "received" : "Полученные", + "sent" : "Отправленные", + "pending" : " (в ожидании)", + "rescan" : "Пересканировать", + "reconnect" : "Переподключиться", + "wallets" : "Кошельки", + "show_seed" : "Показать код-фразу", + "show_keys" : "Показать ключи", + "address_book_menu" : "Адресная книга", + "reconnection" : "Переподключение", + "reconnect_alert_text" : "Вы хотите переподключиться?", + + + "exchange" : "Обмен", + "clear" : "Очистить", + "change_exchange_provider" : "Изменить провайдер обмена", + "you_will_send" : "Вы отправите", + "you_will_get" : "Вы получите", + "amount_is_guaranteed" : "Сумма получения гарантирована", + "amount_is_estimate" : "Полученная сумма является приблизительной", + "powered_by" : "Используя ${title}", + "error" : "Ошибка", + "estimated" : "Примерно ", + "min_value" : "Мин: ${value} ${currency}", + "max_value" : "Макс: ${value} ${currency}", + "change_currency" : "Изменить валюту", + + + "copy_id" : "Скопировать ID", + "exchange_result_write_down_trade_id" : "Пожалуйста, скопируйте или запишите ID сделки.", + "trade_id" : "ID сделки:\n${id}", + "copied_to_clipboard" : "Скопировано в буфер обмена", + "saved_the_trade_id" : "Я сохранил ID сделки", + "fetching" : "Загрузка", + "id" : "ID: ", + "amount" : "Сумма: ", + "payment_id" : "ID транзакции: ", + "status" : "Статус: ", + "offer_expires_in" : "Предложение истекает через: ", + "trade_is_powered_by" : "Сделка выполнена ${provider}", + "copy_address" : "Cкопировать адрес", + "exchange_result_confirm" : "Нажимая подтвердить, Вы отправите ${fetchingLabel} ${from} с Вашего кошелька ${walletName} на адрес указанный выше. Или Вы можете отправить со своего внешнего кошелька на вышеуказанный адрес / QR-код.\n\nПожалуйста, нажмите подтвердить для продолжения или вернитесь назад для изменения суммы.\n\n", + "exchange_result_description" : "Пожалуйста отправьте ${fetchingLabel} ${from} на адрес, указанный выше.\n\n'", + "exchange_result_write_down_ID" : "*Пожалуйста, скопируйте или запишите ID, указанный выше.", + "confirm" : "Подтвердить", + "confirm_sending" : "Подтвердить отправку", + "commit_transaction_amount_fee" : "Подтвердить транзакцию \nСумма: ${amount}\nСбор: ${fee}", + "sending" : "Отправка", + "transaction_sent" : "Tранзакция отправлена!", + "expired" : "Истекает", + "time" : "${minutes}мин ${seconds}сек", + "send_xmr" : "Отправить XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "Введите Ваш PIN", + "loading_your_wallet" : "Загрузка кошелька", + + + "new_wallet" : "Новый кошелек", + "wallet_name" : "Имя кошелька", + "continue_text" : "Продолжить", + + + "node_new" : "Новая нода", + "node_address" : "Адрес ноды", + "node_port" : "Порт ноды", + "login" : "Login", + "password" : "Password", + "nodes" : "Ноды", + "node_reset_settings_title" : "Сбросить настройки", + "nodes_list_reset_to_default_message" : "Вы уверены, что хотите сбросить до настроек по умолчанию?", + "change_current_node" : "Вы уверены, что хотите изменить текущую ноду на ${node}?", + "change" : "Изменить", + "remove_node" : "Удалить ноду", + "remove_node_message" : "Вы уверены, что хотите удалить текущую ноду?", + "remove" : "Удалить", + "delete" : "Удалить", + + + "use" : "Использовать ", + "digit_pin" : "-значный Pin", + + + "share_address" : "Поделиться адресом", + "receive_amount" : "Баланс", + "subaddresses" : "Subaddresses", + + + "restore_restore_wallet" : "Восстановить кошелек", + "restore_title_from_seed_keys" : "Восстановить из код-фразы/ключей", + "restore_description_from_seed_keys" : " Вы можете восстановить кошелек из код-фразы/ключей которые Вы сохранили ранее", + "restore_next" : "Продолжить", + "restore_title_from_backup" : "Восстановить из back-up файла", + "restore_description_from_backup" : "Вы можете восстановить Cake Wallet из\nВашего back-up файла", + "restore_seed_keys_restore" : "Восстановить с помощью код-фразы/ключей", + "restore_title_from_seed" : "Восстановить из код-фразы", + "restore_description_from_seed" : "Вы можете восстановить кошелек используя 25-ти значную код фразу", + "restore_title_from_keys" : "Восстановить с помощью ключей", + "restore_description_from_keys" : "Вы можете восстановить кошелек с помощью приватных ключей", + "restore_wallet_name" : "Имя кошелька", + "restore_address" : "Адрес", + "restore_view_key_private" : "View ключ (приватный)", + "restore_spend_key_private" : "Spend ключ (приватный)", + "restore_recover" : "Восстановить", + "restore_wallet_restore_description" : "Wallet restore description", + + + "seed_title" : "Код-фраза", + "seed_share" : "Поделиться код-фразой", + "copy" : "Скопировать", + + + "seed_alert_first_text" : "Следущая страница содержит\nВашу код-фразу.", + "seed_alert_second_text" : "Пожалуйста, запишите на \nслучай, если Вы потеряете или сотрете Ваш телефон.", + "seed_alert_third_text" : "Вы так же можете посмотреть Вашу код-фразу\nв", + "seed_alert_settings" : "настройках", + "seed_alert_menu" : " меню.", + "seed_alert_understand" : "Я понимаю", + + + "send_title" : "Отправить Monero", + "send_your_wallet" : "Ваш кошелек", + "send_monero_address" : "Monero адрес", + "send_payment_id" : "Payment ID (optional)", + "all" : "ALL", + "send_error_minimum_value" : "Mинимальная сумма 0.01", + "send_error_currency" : "Валюта может включать только цифры", + "send_estimated_fee" : "Предполагаемый сбор:", + "send_priority" : "Сбор установлен в зависимости от приоритета: ${transactionPriority}.\nПриоритет транзакции может быть изменен в настройках", + "send_creating_transaction" : "Создать транзакцию", + + + "settings_title" : "Настройки", + "settings_nodes" : "Ноды", + "settings_current_node" : "Текущая нода", + "settings_wallets" : "Кошельки", + "settings_display_balance_as" : "Отображать баланс", + "settings_currency" : "Валюта", + "settings_fee_priority" : "Приоритет сбора", + "settings_save_recipient_address" : "Сохранять адрес получателя", + "settings_personal" : "Персональные", + "settings_change_pin" : "Изменить PIN", + "settings_change_language" : "Изменить язык", + "settings_allow_biometrical_authentication" : "Включить биометрическую аутентификацию", + "settings_dark_mode" : "Темный режим", + "settings_transactions" : "Транзакции", + "settings_trades" : "Сделки", + "settings_display_on_dashboard_list" : "Показывать в списке транзакций", + "settings_all" : "ВСЕ", + "settings_only_trades" : "Сделки", + "settings_only_transactions" : "Транзакции", + "settings_none" : "Ничего", + "settings_support" : "Поддержка", + "settings_terms_and_conditions" : "Условия и положения", + "pin_is_incorrect" : "Некорректный пин", + + + "setup_pin" : "Настроить PIN", + "enter_your_pin_again" : "Введите pin еще раз", + "setup_successful" : "PIN был успешно изменен!", + + + "wallet_keys" : "Ключи кошелька", + "view_key_private" : "View key (приватный)", + "view_key_public" : "View key (публичный)", + "spend_key_private" : "Spend key (приватный)", + "spend_key_public" : "Spend key (публичный)", + "copied_key_to_clipboard" : "Скопировано ${key} в буфер обмена", + + + "new_subaddress_title" : "Новый subaddress", + "new_subaddress_label_name" : "Имя", + "new_subaddress_create" : "Создать", + + + "subaddress_title" : "Subaddress список", + + + "trade_details_title" : "Детали сделок", + "trade_details_id" : "ID", + "trade_details_state" : "Статус", + "trade_details_fetching" : "Получение", + "trade_details_provider" : "Провайдер", + "trade_details_created_at" : "Создано", + "trade_details_pair" : "Пара", + "trade_details_copied" : "${title} скопировано в буфер обмена", + + + "trade_history_title" : "История сделок", + + + "transaction_details_title" : "Детали транзакции", + "transaction_details_transaction_id" : "ID транзакции", + "transaction_details_date" : "Дата", + "transaction_details_height" : "Высота", + "transaction_details_amount" : "Сумма", + "transaction_details_copied" : "${title} скопировано в буфер обмена", + "transaction_details_recipient_address" : "Адрес получателя", + + + "wallet_list_title" : "Monero кошелек", + "wallet_list_create_new_wallet" : "Создать новый кошелек", + "wallet_list_restore_wallet" : "Восстановить", + "wallet_list_load_wallet" : "Загрузка кошелька", + "wallet_list_loading_wallet" : "Загрузка ${wallet_name} кошелька", + "wallet_list_failed_to_load" : "Ошибка при загрузке ${wallet_name} кошелька. ${error}", + "wallet_list_removing_wallet" : "Удаление ${wallet_name} кошелька", + "wallet_list_failed_to_remove" : "Ошибка при удалении ${wallet_name} кошелька. ${error}", + + + "widgets_address" : "Адрес", + "widgets_restore_from_blockheight" : "Восстановить по высоте", + "widgets_restore_from_date" : "Восстановить по дате", + "widgets_or" : "или", + "widgets_seed" : "Код-фраза", + + + "router_no_route" : "Экран не найден ${name}", + + + "error_text_account_name" : "Имя аккаунта может включать только буквы, цифры\nи может быть от 1 до 15 символов в длину", + "error_text_contact_name" : "Имя контакта не может включать ` , ' \" символы\n и может быть от 1 до 32 символов в длину", + "error_text_address" : "Адрес кошелька должен соответствовать типу\nкриптовалюты", + "error_text_node_address" : "Пожалуйста, введите iPv4 адрес", + "error_text_node_port" : "Порт ноды может включать только цифры от 0 до 65535", + "error_text_payment_id" : "ID транзакции может включать от 16 до 64 символа в hex", + "error_text_xmr" : "XMR баланс не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 12", + "error_text_fiat" : "Значение суммы не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 2", + "error_text_subaddress_name" : "Subaddress имя не может включать ` , ' \" символы\nи может быть от 1 до 32 символов в длину", + "error_text_amount" : "Баланс может включать только цифры", + "error_text_wallet_name" : "Имя кошелька может содержать только буквы, цифры\nи может быть от 1 до 15 символов в длину", + "error_text_keys" : "Ключи кошелька могут содержать только 64 символа в hex", + "error_text_crypto_currency" : "Количество цифр после запятой\nдолжно быть меньше или равно 12", + + + "auth_store_ban_timeout" : "ban_timeout", + "auth_store_banned_for" : "Заблокированно на ", + "auth_store_banned_minutes" : " минут", + "auth_store_incorrect_password" : "Некорректный пин", + "wallet_store_monero_wallet" : "Monero кошелек", + "wallet_restoration_store_incorrect_seed_length" : "Неверная длина код-фразы", + + + "full_balance" : "Полный баланс", + "available_balance" : "Доступный баланс", + "hidden_balance" : "Скрытый баланс", + + + "sync_status_syncronizing" : "СИНХРОНИЗАЦИЯ", + "sync_status_syncronized" : "СИНХРОНИЗИРОВАНО", + "sync_status_not_connected" : "НЕТ ПОДКЛЮЧЕНИЯ", + "sync_status_starting_sync" : "НАЧАЛО СИНХРОНИЗАЦИИ", + "sync_status_failed_connect" : "ОШИБКА ПОДКЛЮЧЕНИЯ К НОДЕ", + "sync_status_connecting" : "ПОДКЛЮЧЕНИЕ", + "sync_status_connected" : "ПОДКЛЮЧЕНО", + + + "transaction_priority_slow" : "Медленный", + "transaction_priority_regular" : "Обычный", + "transaction_priority_medium" : "Средний", + "transaction_priority_fast" : "Быстрый", + "transaction_priority_fastest" : "Самый быстрый", + + + "trade_for_not_created" : "Сделка для ${title} не создана.", + "trade_not_created" : "Сделка не создана.", + "trade_id_not_found" : "Сделка ${tradeId} ${title} не найдена.", + "trade_not_found" : "Trade not found.", + + + "trade_state_pending" : "Ожидание", + "trade_state_confirming" : "Подтверждение", + "trade_state_trading" : "Совершение сделки", + "trade_state_traded" : "Сделка завершена", + "trade_state_complete" : "Полный", + "trade_state_to_be_created" : "Будет создана", + "trade_state_unpaid" : "Неоплаченно", + "trade_state_underpaid" : "Недоплаченно", + "trade_state_paid_unconfirmed" : "Оплата неподтвержденная", + "trade_state_paid" : "Оплачено", + "trade_state_btc_sent" : "Btc отправлен", + "trade_state_timeout" : "Timeout", + "trade_state_created" : "Создано", + "trade_state_finished" : "Окончено", + + "change_language" : "Изменить язык", + "change_language_to" : "Изменить язык на ${language}?" +} \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb new file mode 100644 index 000000000..3a3ce1d81 --- /dev/null +++ b/res/values/strings_zh.arb @@ -0,0 +1,335 @@ +{ + "welcome" : "欢迎\nTO CAKE WALLET", + "first_wallet_text" : "很棒的钱包\n对于 Monero", + "please_make_selection" : "请在下面进行选择\nc创建或恢复您的钱包.", + "create_new" : "创建新的", + "restore_wallet" : "恢复钱包", + + + "accounts" : "帐目", + "edit" : "编辑", + "account" : "帐户", + "add" : "加", + + + "address_book" : "地址簿", + "contact" : "联系", + "please_select" : "请选择:", + "cancel" : "取消", + "ok" : "好", + "contact_name" : "联系人姓名", + "reset" : "重启", + "save" : "保存", + + + "authenticated" : "已认证", + "authentication" : "认证方式", + "failed_authentication" : "身份验证失败. ${state_error}", + + + "wallet_menu" : "钱包菜单", + "Blocks_remaining" : "${status} 剩余的块", + "please_try_to_connect_to_another_node" : "请尝试连接到另一个节点", + "xmr_hidden" : "XMR Hidden", + "xmr_available_balance" : "XMR 可用余额 ", + "xmr_full_balance" : "XMR 全部余额", + "send" : "发送", + "receive" : "接收", + "transactions" : "交易次数", + "incoming" : "传入", + "outgoing" : "外向", + "transactions_by_date" : "按日期交易", + "trades" : "交易", + "filters" : "筛选器", + "today" : "今天", + "yesterday" : "昨天", + "received" : "已收到", + "sent" : "已发送", + "pending" : " (待定)", + "rescan" : "重新扫描", + "reconnect" : "重新连接", + "wallets" : "皮夹", + "show_seed" : "显示种子", + "show_keys" : "显示按键", + "address_book_menu" : "地址簿", + "reconnection" : "重新连线", + "reconnect_alert_text" : "您确定要重新连接吗?", + + + "exchange" : "交换", + "clear" : "明确", + "change_exchange_provider" : "更改交易所提供商", + "you_will_send" : "您将发送", + "you_will_get" : "你会得到", + "amount_is_guaranteed" : "接收金额有保证", + "amount_is_estimate" : "收款金额为估算值", + "powered_by" : "供电 ${title}", + "error" : "错误", + "estimated" : "估计的", + "min_value" : "敏: ${value} ${currency}", + "max_value" : "最高: ${value} ${currency}", + "change_currency" : "更改币种", + + + "copy_id" : "复印ID", + "exchange_result_write_down_trade_id" : "请复制或写下交易编号以继续.", + "trade_id" : "贸易编号:\n${id}", + "copied_to_clipboard" : "复制到剪贴板", + "saved_the_trade_id" : "我已经保存了交易ID", + "fetching" : "正在取得", + "id" : "ID: ", + "amount" : "量: ", + "payment_id" : "付款 ID: ", + "status" : "状态: ", + "offer_expires_in" : "优惠有效期至 ", + "trade_is_powered_by" : "该交易由 ${provider}", + "copy_address" : "复制地址", + "exchange_result_confirm" : "点击确认 您将发送 ${fetchingLabel} ${from} 从你的钱包里 ${walletName} 到上面显示的地址. 或者,您也可以从外部钱包发送上述地址/ QR码。\n\n请按确认继续或返回以更改金额\n\n", + "exchange_result_description" : "请发送 ${fetchingLabel} ${from} 到上面显示的地址.\n\n'", + "exchange_result_write_down_ID" : "*请复制或写下您上面显示的ID.", + "confirm" : "确认", + "confirm_sending" : "确认发送", + "commit_transaction_amount_fee" : "提交交易\n量: ${amount}\nFee: ${fee}", + "sending" : "正在发送", + "transaction_sent" : "交易已发送", + "expired" : "已过期", + "time" : "${minutes}m ${seconds}s", + "send_xmr" : "发送 XMR", + + "faq" : "FAQ", + + + "enter_your_pin" : "输入密码", + "loading_your_wallet" : "装钱包", + + + "new_wallet" : "新钱包", + "wallet_name" : "钱包名称", + "continue_text" : "继续", + + + "node_new" : "新节点", + "node_address" : "节点地址", + "node_port" : "节点端口", + "login" : "登录", + "password" : "密码", + "nodes" : "节点数", + "node_reset_settings_title" : "重新设置", + "nodes_list_reset_to_default_message" : "您确定要将设置重设为默认值吗?", + "change_current_node" : "您确定将当前节点更改为 ${node}?", + "change" : "更改", + "remove_node" : "删除节点", + "remove_node_message" : "您确定要删除所选节点吗?", + "remove" : "去掉", + "delete" : "删除", + + + "use" : "使用 ", + "digit_pin" : "数字别针", + + + "share_address" : "分享地址", + "receive_amount" : "量", + "subaddresses" : "子地址", + + + "restore_restore_wallet" : "恢复钱包", + "restore_title_from_seed_keys" : "从种子/密钥还原", + "restore_description_from_seed_keys" : "从保存到安全地方的种子/钥匙取回钱包", + "restore_next" : "下一个", + "restore_title_from_backup" : "从备份文件还原", + "restore_description_from_backup" : "您可以从还原整个Cake Wallet应用\n您的备份文件", + "restore_seed_keys_restore" : "种子/密钥还原", + "restore_title_from_seed" : "从种子还原", + "restore_description_from_seed" : "从25个字中恢复您的钱包\n或13个字的组合码", + "restore_title_from_keys" : "从密钥还原", + "restore_description_from_keys" : "R从生成的电子钱包\n从您的私钥中保存的击键", + "restore_wallet_name" : "钱包名称", + "restore_address" : "地址", + "restore_view_key_private" : "查看金钥 (私人的)", + "restore_spend_key_private" : "支出金钥 (私人的)", + "restore_recover" : "恢复", + "restore_wallet_restore_description" : "钱包还原说明", + + + "seed_title" : "种子", + "seed_share" : "分享种子", + "copy" : "复制", + + + "seed_alert_first_text" : "下一页将显示\n你是种子.", + "seed_alert_second_text" : "请写下来以防万一\n万一您丢失或擦拭手机.", + "seed_alert_third_text" : "您也可以再次看到种子 \n在里面", + "seed_alert_settings" : "设定", + "seed_alert_menu" : " 菜单.", + "seed_alert_understand" : "我明白", + + + "send_title" : "发送门罗币", + "send_your_wallet" : "你的钱包", + "send_monero_address" : "门罗地址", + "send_payment_id" : "付款编号 (可选的)", + "all" : "所有", + "send_error_minimum_value" : "最小金额为0.01", + "send_error_currency" : "货币只能包含数字", + "send_estimated_fee" : "预估费用:", + "send_priority" : "目前,费用设置为 ${transactionPriority} 优先.\n交易优先级可以在设置中进行调整", + "send_creating_transaction" : "创建交易", + + + "settings_title" : "设定值", + "settings_nodes" : "节点数", + "settings_current_node" : "当前节点", + "settings_wallets" : "皮夹", + "settings_display_balance_as" : "将余额显示为", + "settings_currency" : "货币", + "settings_fee_priority" : "费用优先", + "settings_save_recipient_address" : "保存收件人地址", + "settings_personal" : "个人", + "settings_change_pin" : "更改密码", + "settings_change_language" : "改变语言", + "settings_allow_biometrical_authentication" : "允许生物特征认证", + "settings_dark_mode" : "暗模式", + "settings_transactions" : "交易次数", + "settings_trades" : "交易", + "settings_display_on_dashboard_list" : "显示在仪表板上", + "settings_all" : "所有", + "settings_only_trades" : "只交易", + "settings_only_transactions" : "仅交易", + "settings_none" : "没有", + "settings_support" : "支持", + "settings_terms_and_conditions" : "条款和条件", + "pin_is_incorrect" : "PIN码不正确", + + + "setup_pin" : "设定PIN码", + "enter_your_pin_again" : "再次输入您的PIN码", + "setup_successful" : "您的PIN码已成功设置!", + + + "wallet_keys" : "钱包钥匙", + "view_key_private" : "查看金钥 (私人的)", + "view_key_public" : "查看金钥 (public)", + "spend_key_private" : "支出金钥 (私人的)", + "spend_key_public" : "支出金钥 (public)", + "copied_key_to_clipboard" : "复制 ${key} 到剪贴板", + + + "new_subaddress_title" : "新子地址", + "new_subaddress_label_name" : "标签名称", + "new_subaddress_create" : "创建", + + + "subaddress_title" : "子地址清单", + + + "trade_details_title" : "交易明细", + "trade_details_id" : "ID", + "trade_details_state" : "条件", + "trade_details_fetching" : "正在取得", + "trade_details_provider" : "提供者", + "trade_details_created_at" : "创建于", + "trade_details_pair" : "对", + "trade_details_copied" : "${title} 复制到剪贴板", + + + "trade_history_title" : "交易历史", + + + "transaction_details_title" : "交易明细", + "transaction_details_transaction_id" : "交易编号", + "transaction_details_date" : "日期", + "transaction_details_height" : "高度", + "transaction_details_amount" : "量", + "transaction_details_copied" : "${title} 复制到剪贴板", + "transaction_details_recipient_address" : "收件人地址", + + + "wallet_list_title" : "Monero 钱包", + "wallet_list_create_new_wallet" : "创建新钱包", + "wallet_list_restore_wallet" : "恢复钱包", + "wallet_list_load_wallet" : "装入钱包", + "wallet_list_loading_wallet" : "载入中 ${wallet_name} 钱包", + "wallet_list_failed_to_load" : "加载失败 ${wallet_name} 钱包. ${error}", + "wallet_list_removing_wallet" : "拆下 ${wallet_name} 钱包", + "wallet_list_failed_to_remove" : "删除失败 ${wallet_name} 钱包. ${error}", + + + "widgets_address" : "地址", + "widgets_restore_from_blockheight" : "从块高还原", + "widgets_restore_from_date" : "从日期还原", + "widgets_or" : "要么", + "widgets_seed" : "种子", + + + "router_no_route" : "未定义路线 ${name}", + + + "error_text_account_name" : "帐户名称只能包含字母数字\n且必须介于1到15个字符之间", + "error_text_contact_name" : "联系人姓名不能包含`,' \" 符号\n并且必须介于1到32个字符之间", + "error_text_address" : "钱包地址必须与类型对应\n加密货币", + "error_text_node_address" : "请输入一个iPv4地址", + "error_text_node_port" : "节点端口只能包含0到65535之间的数字", + "error_text_payment_id" : "付款ID只能包含16到64个字符(十六进制)", + "error_text_xmr" : "XMR值不能超过可用余额.\n小数位数必须小于或等于12", + "error_text_fiat" : "金额不能超过可用余额.\n小数位数必须小于或等于2", + "error_text_subaddress_name" : "子地址名称不能包含`,' \" 符号\n并且必须在1到20个字符之间", + "error_text_amount" : "金额只能包含数字", + "error_text_wallet_name" : "钱包名称只能包含字母,数字\n且必须介于1到15个字符之间", + "error_text_keys" : "钱包密钥只能包含16个字符的十六进制字符", + "error_text_crypto_currency" : "小数位数\n必须小于或等于12", + + + "auth_store_ban_timeout" : "禁止超时", + "auth_store_banned_for" : "禁止 ", + "auth_store_banned_minutes" : " 分钟", + "auth_store_incorrect_password" : "PIN码错误", + "wallet_store_monero_wallet" : "Monero 钱包", + "wallet_restoration_store_incorrect_seed_length" : "种子长度错误", + + + "full_balance" : "全部余额", + "available_balance" : "可用余额", + "hidden_balance" : "隐藏余额", + + + "sync_status_syncronizing" : "同步化", + "sync_status_syncronized" : "已同步", + "sync_status_not_connected" : "未连接", + "sync_status_starting_sync" : "开始同步", + "sync_status_failed_connect" : "无法连接到节点", + "sync_status_connecting" : "连接中", + "sync_status_connected" : "连接的", + + + "transaction_priority_slow" : "慢", + "transaction_priority_regular" : "定期", + "transaction_priority_medium" : "介质", + "transaction_priority_fast" : "快速", + "transaction_priority_fastest" : "最快的", + + + "trade_for_not_created" : "交易 ${title} 未创建.", + "trade_not_created" : "未建立交易.", + "trade_id_not_found" : "贸易方式 ${tradeId} 的 ${title} 未找到.", + "trade_not_found" : "找不到交易.", + + + "trade_state_pending" : "待定", + "trade_state_confirming" : "确认中", + "trade_state_trading" : "贸易", + "trade_state_traded" : "交易", + "trade_state_complete" : "完成", + "trade_state_to_be_created" : "待创建", + "trade_state_unpaid" : "未付", + "trade_state_underpaid" : "支付不足", + "trade_state_paid_unconfirmed" : "付费未确认", + "trade_state_paid" : "已付费", + "trade_state_btc_sent" : "已发送", + "trade_state_timeout" : "超时", + "trade_state_created" : "已建立", + "trade_state_finished" : "已完成", + + "change_language" : "改變語言", + "change_language_to" : "將語言更改為 ${language}?" +} \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 000000000..175df3eb4 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cake_wallet/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(CakeWalletApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/tool/.secrets-test.json b/tool/.secrets-test.json new file mode 100644 index 000000000..2f08c9848 --- /dev/null +++ b/tool/.secrets-test.json @@ -0,0 +1,7 @@ +{ + "salt": "", + "key": "", + "walletSalt": "", + "shortKey": "", + "change_now_api_key": "" +} \ No newline at end of file diff --git a/tool/secrets.dart b/tool/secrets.dart new file mode 100644 index 000000000..615e6123d --- /dev/null +++ b/tool/secrets.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; +import 'dart:io'; + +const secretsProdPath = 'tool/.secrets-prod.json'; +const secretsTestPath = 'tool/.secrets-test.json'; +const outputPath = 'lib/.secrets.g.dart'; + +Future main() async { + final inputPath = FileSystemEntity.typeSync(secretsProdPath) != + FileSystemEntityType.notFound + ? secretsProdPath + : secretsTestPath; + + final inoutContent = File(inputPath).readAsStringSync(); + final config = json.decode(inoutContent); + final output = + 'const salt = \'${config["salt"]}\';\nconst key = \'${config["key"]}\';\nconst walletSalt = \'${config["walletSalt"]}\';\nconst shortKey = \'${config["shortKey"]}\';\nconst change_now_api_key = \'${config["change_now_api_key"]}\';'; + + File(outputPath).writeAsString(output); +}