diff --git a/.github/workflows/gradle6.yml b/.github/workflows/gradle6.yml
index 6d56cb1b54..8162039edc 100644
--- a/.github/workflows/gradle6.yml
+++ b/.github/workflows/gradle6.yml
@@ -7,40 +7,51 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- java: ['8', '11']
+ os: [ubuntu-latest, macos-latest]
+ java: ['11']
distribution: ['temurin']
gradle: ['6.7.1']
fail-fast: false
name: JAVA ${{ matrix.java }} OS ${{ matrix.os }} Gradle ${{ matrix.gradle }}
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v3
- - run: sed -i -e "s|7.2.0|4.2.2|g" build.gradle
+ - name: Downgrade Android plugin from 7.4.1 to 4.2.0
+ run: sed -i -e "s|7.4.1|4.2.0|g" build.gradle
+
+ - name: Downgrade DistributionUrl from Gradle 7.5.1 to Gradle 6.7.1
+ run: sed -i -e "s|7.5.1|6.7.1|g" gradle/wrapper/gradle-wrapper.properties
- name: Set up JDK
- uses: actions/setup-java@v2
+ uses: actions/setup-java@v3
with:
distribution: ${{ matrix.distribution }}
java-version: ${{ matrix.java }}
- name: Setup Android SDK
- uses: android-actions/setup-android@v2
+ uses: android-actions/setup-android@v2.0.10
+
+ - name: Install build-tools 33.0.2, platformtools and platform android-33
+ run: sdkmanager --install "build-tools;33.0.2" "platform-tools" "platforms;android-33"
+
+ - name: Fix build tools 33.0.2 issue
+ run: |
+ ln -s ${ANDROID_HOME}/build-tools/33.0.2/d8 ${ANDROID_HOME}/build-tools/33.0.2/dx
+ ln -s ${ANDROID_HOME}/build-tools/33.0.2/lib/d8.jar ${ANDROID_HOME}/build-tools/33.0.2/lib/dx.jar
- name: Build with Gradle
run: |
./gradlew wrapper --gradle-version=${{ matrix.gradle }}
- ./gradlew wrapper --gradle-version=${{ matrix.gradle }}
- ./gradlew clean build
+ ./gradlew clean build --stacktrace
- name: Upload artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: groestlcoin-wallet-JAVA${{ matrix.java }}-${{ matrix.os }}-${{ matrix.gradle }}
path: |
wallet/build/outputs/apk/
- name: Download artifacts
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: groestlcoin-wallet-JAVA${{ matrix.java }}-${{ matrix.os }}-${{ matrix.gradle }}
diff --git a/.github/workflows/gradle7.yml b/.github/workflows/gradle7.yml
index 81f2e9df1f..e4c40c02fc 100644
--- a/.github/workflows/gradle7.yml
+++ b/.github/workflows/gradle7.yml
@@ -10,37 +10,37 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
java: ['11', '17']
distribution: ['temurin']
- gradle: ['7.4.2']
+ gradle: ['7.5.1']
fail-fast: false
name: JAVA ${{ matrix.java }} OS ${{ matrix.os }} Gradle ${{ matrix.gradle }}
steps:
- - uses: actions/checkout@v1
-
- - run: sed -i -e "s|7.2.0|7.2.1|g" build.gradle
+ - uses: actions/checkout@v3
- name: Set up JDK
- uses: actions/setup-java@v2
+ uses: actions/setup-java@v3
with:
distribution: ${{ matrix.distribution }}
java-version: ${{ matrix.java }}
- name: Setup Android SDK
- uses: android-actions/setup-android@v2
+ uses: android-actions/setup-android@v2.0.10
+
+ - name: Install build-tools 33.0.2, platformtools and platform android-33
+ run: sdkmanager --install "build-tools;33.0.2" "platform-tools" "platforms;android-33"
- name: Build with Gradle
run: |
./gradlew wrapper --gradle-version=${{ matrix.gradle }}
- ./gradlew wrapper --gradle-version=${{ matrix.gradle }}
- ./gradlew clean build
+ ./gradlew clean build --stacktrace
- name: Upload artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: groestlcoin-wallet-JAVA${{ matrix.java }}-${{ matrix.os }}-${{ matrix.gradle }}
path: |
wallet/build/outputs/apk/
- name: Download artifacts
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: groestlcoin-wallet-JAVA${{ matrix.java }}-${{ matrix.os }}-${{ matrix.gradle }}
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index aba8e3096f..6680364fc2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,24 +1,56 @@
-image: ubuntu:focal
-
variables:
- ANDROID_HOME: "$PWD/android-sdk"
- ANDROID_SDK_TOOLS: "7583922_latest"
- ANDROID_SDK_LICENSE_HASH: "24333f8a63b6825ea9c5514f83c2829b004d1fee"
+ ANDROID_HOME: $PWD/android-sdk
before_script:
- apt-get update
- - apt-get -y upgrade
- - apt-get -y install ${JDK_PACKAGE}
- - apt-get -y install wget gradle
- - wget --quiet --output-document=commandlinetools-linux.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}.zip
- - mkdir -p ${ANDROID_HOME}
- - unzip -d ${ANDROID_HOME} commandlinetools-linux.zip
- - mkdir -p ${ANDROID_HOME}/licenses
- - echo -e "\n${ANDROID_SDK_LICENSE_HASH}" >> ${ANDROID_HOME}/licenses/android-sdk-license
-build:
- parallel:
- matrix:
- - JDK_PACKAGE: [ openjdk-8-jdk, openjdk-11-jdk ]
+after_script:
+ - gradle --version
+
+bullseye-jdk11:
+ image: debian:bullseye-backports
+ script:
+ - apt-get -y install openjdk-11-jdk-headless
+ - apt-get -y install sdkmanager gradle
+ - yes | sdkmanager --licenses >/dev/null || true
+ - gradle build --stacktrace
+ artifacts:
+ name: groestlcoin-wallet-$CI_JOB_NAME-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
+ paths:
+ - wallet/build/outputs/apk/**/*.apk
+
+bookworm-jdk17:
+ image: debian:bookworm-slim
+ script:
+ - apt-get -y install openjdk-17-jdk-headless
+ - apt-get -y install sdkmanager gradle
+ - yes | sdkmanager --licenses >/dev/null || true
+ - gradle build --stacktrace
+ artifacts:
+ name: groestlcoin-wallet-$CI_JOB_NAME-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
+ paths:
+ - wallet/build/outputs/apk/**/*.apk
+
+jammy-jdk11:
+ image: ubuntu:jammy
+ script:
+ - apt-get -y install openjdk-11-jdk-headless
+ - apt-get -y install sdkmanager gradle
+ - yes | sdkmanager --licenses >/dev/null || true
+ - gradle build --stacktrace
+ artifacts:
+ name: groestlcoin-wallet-$CI_JOB_NAME-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
+ paths:
+ - wallet/build/outputs/apk/**/*.apk
+
+lunar-jdk17:
+ image: ubuntu:lunar
script:
+ - apt-get -y install openjdk-17-jdk-headless
+ - apt-get -y install sdkmanager gradle
+ - yes | sdkmanager --licenses >/dev/null || true
- gradle build --stacktrace
+ artifacts:
+ name: groestlcoin-wallet-$CI_JOB_NAME-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
+ paths:
+ - wallet/build/outputs/apk/**/*.apk
diff --git a/README.md b/README.md
index f30eb80699..5d53b96b9d 100644
--- a/README.md
+++ b/README.md
@@ -8,21 +8,15 @@ This project contains several sub-projects:
The Android app itself. This is probably what you're searching for.
* __market__:
App description and promo material for the Google Play app store.
- * __integration-android__:
- A tiny library for integrating digitial payments into your own Android app
- (e.g. donations, in-app purchases).
- * __sample-integration-android__:
- A minimal example app to demonstrate integration of digital payments into
- your Android app.
### PREREQUISITES FOR BUILDING
-You'll need git, a Java 8 or 11 SDK and Gradle 4.4 (or later) for this. We'll assume Ubuntu 20.04 LTS (Focal Fossa)
-for the package installs, which comes with OpenJDK 8, OpenJDK 11 and Gradle 4.4.1 out of the box.
+You'll need git, a Java 11 SDK and Gradle between 4.4 and 6.9.x for this. We'll assume Ubuntu 22.04 LTS (Jammy Jellyfish)
+for the package installs, which comes with OpenJDK 11 and Gradle 4.4.1 out of the box.
# first time only
- sudo apt install git gradle openjdk-8-jdk
+ sudo apt install git gradle openjdk-11-jdk
Create a directory for the Android SDK (e.g. `android-sdk`) and point the `ANDROID_HOME` variable to it.
diff --git a/build.gradle b/build.gradle
index 34eea62975..1762111468 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,9 +6,9 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.2.0'
+ classpath 'com.android.tools.build:gradle:7.4.1'
//noinspection GradleDependency
- classpath 'net.sf.proguard:proguard-gradle:6.0.3'
+ classpath 'net.sf.proguard:proguard-gradle:6.2.2'
classpath('fr.avianey.androidsvgdrawable:gradle-plugin:3.1.1') {
exclude group: 'xerces'
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 73467bf1f3..c6de48a30e 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
diff --git a/integration-android/AUTHORS b/integration-android/AUTHORS
deleted file mode 100644
index ee589b3a19..0000000000
--- a/integration-android/AUTHORS
+++ /dev/null
@@ -1 +0,0 @@
-Andreas Schildbach
diff --git a/integration-android/COPYING b/integration-android/COPYING
deleted file mode 100644
index f433b1a53f..0000000000
--- a/integration-android/COPYING
+++ /dev/null
@@ -1,177 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
diff --git a/integration-android/build.gradle b/integration-android/build.gradle
deleted file mode 100644
index fc53cfd91e..0000000000
--- a/integration-android/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-plugins {
- id 'java'
- id 'java-library'
-}
-
-dependencies {
- implementation('com.google.android:android:4.0.1.2') {
- transitive false
- }
-}
-
-sourceSets {
- main {
- java {
- srcDir 'src'
- }
- resources {
- srcDir 'src'
- }
- }
-}
-
-compileJava {
- sourceCompatibility '1.8'
- targetCompatibility '1.8'
-}
diff --git a/integration-android/src/de/schildbach/wallet/integration/android/BitcoinIntegration.java b/integration-android/src/de/schildbach/wallet/integration/android/BitcoinIntegration.java
deleted file mode 100644
index ab88c2a21c..0000000000
--- a/integration-android/src/de/schildbach/wallet/integration/android/BitcoinIntegration.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package de.schildbach.wallet.integration.android;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.widget.Toast;
-
-/**
- * @author Andreas Schildbach
- */
-public final class BitcoinIntegration {
- private static final String INTENT_EXTRA_PAYMENTREQUEST = "paymentrequest";
- private static final String INTENT_EXTRA_PAYMENT = "payment";
- private static final String INTENT_EXTRA_TRANSACTION_HASH = "transaction_hash";
-
- private static final String MIMETYPE_PAYMENTREQUEST = "application/groestlcoin-paymentrequest"; // BIP 71
-
- /**
- * Request any amount of Groestlcoins (probably a donation) from user, without feedback from the app.
- *
- * @param context
- * Android context
- * @param address
- * Groestlcoin address
- */
- public static void request(final Context context, final String address) {
- final Intent intent = makeBitcoinUriIntent(address, null);
-
- start(context, intent);
- }
-
- /**
- * Request specific amount of Groestlcoins from user, without feedback from the app.
- *
- * @param context
- * Android context
- * @param address
- * Groestlcoin address
- * @param amount
- * Groestlcoin amount in satoshis
- */
- public static void request(final Context context, final String address, final long amount) {
- final Intent intent = makeBitcoinUriIntent(address, amount);
-
- start(context, intent);
- }
-
- /**
- * Request payment from user, without feedback from the app.
- *
- * @param context
- * Android context
- * @param paymentRequest
- * BIP70 formatted payment request
- */
- public static void request(final Context context, final byte[] paymentRequest) {
- final Intent intent = makePaymentRequestIntent(paymentRequest);
-
- start(context, intent);
- }
-
- /**
- * Request any amount of Groestlcoins (probably a donation) from user, with feedback from the app. Result
- * intent can be received by overriding {@code Activity#onActivityResult(int, int, Intent)}. Result indicates
- * either {@link Activity#RESULT_OK} or {@link Activity#RESULT_CANCELED}. In the success case, use
- * {@link #transactionHashFromResult(Intent)} to read the transaction hash from the intent.
- *
- * Warning: A success indication is no guarantee! To be on the safe side, you must drive your own Groestlcoin
- * infrastructure and validate the transaction.
- *
- * @param activity
- * Calling Android activity
- * @param requestCode
- * Code identifying the call when {@code Activity#onActivityResult(int, int, Intent)} is called
- * back
- * @param address
- * Groestlcoin address
- */
- public static void requestForResult(final Activity activity, final int requestCode, final String address) {
- final Intent intent = makeBitcoinUriIntent(address, null);
-
- startForResult(activity, requestCode, intent);
- }
-
- /**
- * Request specific amount of Groestlcoins from user, with feedback from the app. Result intent can be
- * received by overriding {@code Activity#onActivityResult(int, int, Intent)}. Result indicates either
- * {@link Activity#RESULT_OK} or {@link Activity#RESULT_CANCELED}. In the success case, use
- * {@link #transactionHashFromResult(Intent)} to read the transaction hash from the intent.
- *
- * Warning: A success indication is no guarantee! To be on the safe side, you must drive your own Groestlcoin
- * infrastructure and validate the transaction.
- *
- * @param activity
- * Calling Android activity
- * @param requestCode
- * Code identifying the call when {@code Activity#onActivityResult(int, int, Intent)} is called
- * back
- * @param address
- * Groestlcoin address
- */
- public static void requestForResult(final Activity activity, final int requestCode, final String address,
- final long amount) {
- final Intent intent = makeBitcoinUriIntent(address, amount);
-
- startForResult(activity, requestCode, intent);
- }
-
- /**
- * Request payment from user, with feedback from the app. Result intent can be received by overriding
- * {@code Activity#onActivityResult(int, int, Intent)}. Result indicates either {@link Activity#RESULT_OK} or
- * {@link Activity#RESULT_CANCELED}. In the success case, use {@link #transactionHashFromResult(Intent)}
- * to read the transaction hash from the intent.
- *
- * Warning: A success indication is no guarantee! To be on the safe side, you must drive your own Groestlcoin
- * infrastructure and validate the transaction.
- *
- * @param activity
- * Calling Android activity
- * @param requestCode
- * Code identifying the call when {@code Activity#onActivityResult(int, int, Intent)} is called
- * back
- * @param paymentRequest
- * BIP70 formatted payment request
- */
- public static void requestForResult(final Activity activity, final int requestCode, final byte[] paymentRequest) {
- final Intent intent = makePaymentRequestIntent(paymentRequest);
-
- startForResult(activity, requestCode, intent);
- }
-
- /**
- * Get payment request from intent. Meant for usage by applications accepting payment requests.
- *
- * @param intent
- * intent
- * @return payment request or null
- */
- public static byte[] paymentRequestFromIntent(final Intent intent) {
-
- return intent.getByteArrayExtra(INTENT_EXTRA_PAYMENTREQUEST);
- }
-
- /**
- * Put BIP70 payment message into result intent. Meant for usage by Groestlcoin wallet applications.
- *
- * @param result
- * result intent
- * @param payment
- * payment message
- */
- public static void paymentToResult(final Intent result, final byte[] payment) {
- result.putExtra(INTENT_EXTRA_PAYMENT, payment);
- }
-
- /**
- * Get BIP70 payment message from result intent. Meant for usage by applications initiating a Groestlcoin
- * payment.
- *
- * You can use the transactions contained in the payment to validate the payment. For this, you need your
- * own Groestlcoin infrastructure though. There is no guarantee that the payment will ever confirm.
- *
- * @param result
- * result intent
- * @return payment message
- */
- public static byte[] paymentFromResult(final Intent result) {
-
- return result.getByteArrayExtra(INTENT_EXTRA_PAYMENT);
- }
-
- /**
- * Put transaction hash into result intent. Meant for usage by groestlcoin wallet applications.
- *
- * @param result
- * result intent
- * @param txHash
- * transaction hash
- */
- public static void transactionHashToResult(final Intent result, final String txHash) {
- result.putExtra(INTENT_EXTRA_TRANSACTION_HASH, txHash);
- }
-
- /**
- * Get transaction hash from result intent. Meant for usage by applications initiating a Groestlcoin payment.
- *
- * You can use this hash to request the transaction from the groestlcoin network, in order to validate. For
- * this, you need your own Groestlcoin infrastructure though. There is no guarantee that the transaction has
- * ever been broadcasted to the Groestlcoin network.
- *
- * @param result
- * result intent
- * @return transaction hash
- */
- public static String transactionHashFromResult(final Intent result) {
-
- return result.getStringExtra(INTENT_EXTRA_TRANSACTION_HASH);
- }
-
- private static final int SATOSHIS_PER_COIN = 100000000;
-
- private static Intent makeBitcoinUriIntent(final String address, final Long amount) {
- final StringBuilder uri = new StringBuilder("groestlcoin:");
- if (address != null)
- uri.append(address);
- if (amount != null)
- uri.append("?amount=")
- .append(String.format("%d.%08d", amount / SATOSHIS_PER_COIN, amount % SATOSHIS_PER_COIN));
-
- return new Intent(Intent.ACTION_VIEW, Uri.parse(uri.toString()));
- }
-
- private static Intent makePaymentRequestIntent(final byte[] paymentRequest) {
- final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setType(MIMETYPE_PAYMENTREQUEST);
- intent.putExtra(INTENT_EXTRA_PAYMENTREQUEST, paymentRequest);
-
- return intent;
- }
-
- private static void start(final Context context, final Intent intent) {
- final PackageManager pm = context.getPackageManager();
- if (pm.resolveActivity(intent, 0) != null)
- context.startActivity(intent);
- else
- redirectToDownload(context);
- }
-
- private static void startForResult(final Activity activity, final int requestCode, final Intent intent) {
- final PackageManager pm = activity.getPackageManager();
- if (pm.resolveActivity(intent, 0) != null)
- activity.startActivityForResult(intent, requestCode);
- else
- redirectToDownload(activity);
- }
-
- private static void redirectToDownload(final Context context) {
- Toast.makeText(context, "No Groestlcoin application found.\nPlease install Groestlcoin Wallet.", Toast.LENGTH_LONG)
- .show();
-
- final Intent marketIntent = new Intent(Intent.ACTION_VIEW,
- Uri.parse("market://details?id=hashengineering.groestlcoin.wallet"));
- final Intent binaryIntent = new Intent(Intent.ACTION_VIEW,
- Uri.parse("https://github.com/HashEngineering/groestlcoin-wallet/releases"));
-
- final PackageManager pm = context.getPackageManager();
- if (pm.resolveActivity(marketIntent, 0) != null)
- context.startActivity(marketIntent);
- else if (pm.resolveActivity(binaryIntent, 0) != null)
- context.startActivity(binaryIntent);
- // else out of luck
- }
-}
diff --git a/wallet/graphics-prod/mipmap/ic_app_color_48dp-mdpi.svg b/market/mainnet-app-icon-48dp.svg
similarity index 100%
rename from wallet/graphics-prod/mipmap/ic_app_color_48dp-mdpi.svg
rename to market/mainnet-app-icon-48dp.svg
diff --git a/market/market-description-ar.txt b/market/market-description-ar.txt
index 005576bec9..d622e95bf4 100644
--- a/market/market-description-ar.txt
+++ b/market/market-description-ar.txt
@@ -1,10 +1,10 @@
-إمتلك البت كوينز الخاصة بك, دائماً في جيبك! تدفع بسرعة بمجرد مسح رمز الـ QR. و كتاجر، يمكنك قبض مبالغ خدماتك حالاً وبكل ثقة. محفظة البتكوين هي أول تطبيق محمول لعملة البتكوين، و كذا الأكثر أماناً.
+إمتلك البت كوينز الخاصة بك, دائماً في جيبك! تدفع بسرعة بمجرد مسح رمز الـ QR. و كتاجر، يمكنك قبض مبالغ خدماتك حالاً وبكل ثقة. محفظة الغرسلكوين هي أول تطبيق محمول لعملة الغرسلكوين، و كذا الأكثر أماناً.
المميزات
• لا داعي للتسجيل أو استخدام تطبيقات شبكة إنترنت أو الخدمات السحابية! كون الاتصال في هذه المحفظة غير مركزي (peer-to-peer) ولا تعتمد على سيرفر خدمة.
-إظهار رصيدك من البتكوين على شكل GRS و µGRS و mGRS.
+إظهار رصيدك من الغرسلكوين على شكل GRS و µGRS و mGRS.
• تحويل الغرسلكوين من وإلى العملات المحلية.
ارسال واستقبال عملة Groestlcoin عبر NFC, QR codes او Groestlcoin روابط
• دفتر عناوين لحفظ عناوين الغرسلكوين المستخدمة بشكل دوري.
@@ -15,7 +15,7 @@
للمشاركة
-محفظة البتكوين تطبيق حر و مفتوح المصدر. تحت رخصة : GPLv3
+محفظة الغرسلكوين تطبيق حر و مفتوح المصدر. تحت رخصة : GPLv3
https://www.gnu.org/licenses/gpl-3.0.ar.html
الكود المصدري للبرنامج متوافر على GitHub:
diff --git a/market/market-description-cs.txt b/market/market-description-cs.txt
index f180225441..9681265f13 100644
--- a/market/market-description-cs.txt
+++ b/market/market-description-cs.txt
@@ -12,6 +12,8 @@
• Systémové notifikace při přijaté platbě
• Načítání papírových peněženek (např. ty co se používají u cold storage).
• Widget pro zobrazení Groestlcoin bilance.
+• Bezpečnost: Podporuje Taproot, Segwit a nový formát bech32m.
+• Soukromí: Podporuje Tor prostřednictvím samostatné aplikace Orbot.
PŘISPĚJTE
@@ -26,4 +28,4 @@ Veškeré překlady jsou spravovány přes Transifex:
https://www.transifex.com/bitcoin-wallet/bitcoin-wallet/
-Použití na vlastní riziko!
+Používejte jen na vlastní nebezpečí! Používejte pouze pro malé částky.
diff --git a/market/market-description-cy.txt b/market/market-description-cy.txt
index ae2ebedbf0..bcb256e1c4 100644
--- a/market/market-description-cy.txt
+++ b/market/market-description-cy.txt
@@ -17,10 +17,10 @@
CYFRANNU
-Mae Waled Groestlcoin yn feddalwedd cod agored ac am ddim. Trwydded: GPLv3
+Mae Groestlcoin Walllet yn feddalwedd am ddim ac yn god agored. Trwydded: GPLv3
https://www.gnu.org/licenses/gpl-3.0.en.html
-Mae ein cod gwreiddiol ar gael ar GitHub:
+Mae ein cod ar gael ar GitHub:
https://github.com/Groestlcoin/groestlcoin-wallet
Rheolir pob cyfieithiad trwy Transifex:
diff --git a/market/market-description-fa.txt b/market/market-description-fa.txt
index 1ef6502e3b..037f5250a7 100644
--- a/market/market-description-fa.txt
+++ b/market/market-description-fa.txt
@@ -1,13 +1,13 @@
-بیتکوینهای تان را همیشه با خود در کیف پولتان داشته باشید. به سرعت با اسکن کردن QR-code پرداخت کنید. به عنوان فروشنده، پرداختها را با امنیت و سرعت دریافت کنید. Groestlcoin Wallet اولین برنامه بیتکوین بر روی گوشیهای همراه بود و همچنین به عنوانه امنترین برنامه در نظر گرفته میشود.
+غرسلكوينهای تان را همیشه با خود در کیف پولتان داشته باشید. به سرعت با اسکن کردن QR-code پرداخت کنید. به عنوان فروشنده، پرداختها را با امنیت و سرعت دریافت کنید. Groestlcoin Wallet اولین برنامه غرسلكوين بر روی گوشیهای همراه بود و همچنین به عنوانه امنترین برنامه در نظر گرفته میشود.
خصوصیات
به هیچگونه ثبت نام و یا سرویس جانبی اینترنتی نیاز ندارید. این کیف پول غیر مترمرکز و نظیر به نظیر (p2p) بوده.
-نمایش میزان بیتکوین به صورت واحد بیتکوین, میلی بیتکوین و میکرو بیتکوین.
+نمایش میزان غرسلكوين به صورت واحد غرسلكوين, میلی غرسلكوين و میکرو غرسلكوين.
تبدیل ارزش به واحد پولی کشورهای متفاوت.
-فرستادن و دریافت بیتکوین به صورت NFC ، Qrcode و آدرس بیتکوین.
-بدون دسترسی به اینترنت همچنان میتوان بیتکوین را از طریق بلوتوث پرداخت کرد.
+فرستادن و دریافت غرسلكوين به صورت NFC ، Qrcode و آدرس غرسلكوين.
+بدون دسترسی به اینترنت همچنان میتوان غرسلكوين را از طریق بلوتوث پرداخت کرد.
وقتی پول دریافت میکنید برنامه شما را مطلع میکند.
استفاده از کیف پولهای کاغذی (همانند آنهایی که به عنوانه cold storage استفاده میشود).
ویجت برای نشان دادن موجودی شما.
diff --git a/market/market-description-he.txt b/market/market-description-he.txt
index 472c264bce..028d537a0b 100644
--- a/market/market-description-he.txt
+++ b/market/market-description-he.txt
@@ -1,17 +1,17 @@
-שא את הביטקוין שלך עליך ללא תלות בשרות חיצוני, העבר או קבל כסף בקלות על ידי ברקוד, הכנסת כתובת או תקשורת מגע (NFC). כסוחר, אתה תקבל תשלומים בצורה בטוחה ומיידית. ארנק הביטקוין הוא היישום הנייד הראשון לביטקוין, וכנראה גם המאובטח ביותר!
+שא את הגרוסטלקוין שלך עליך ללא תלות בשרות חיצוני, העבר או קבל כסף בקלות על ידי ברקוד, הכנסת כתובת או תקשורת מגע (NFC). כסוחר, אתה תקבל תשלומים בצורה בטוחה ומיידית. ארנק הגרוסטלקוין הוא היישום הנייד הראשון לגרוסטלקוין, וכנראה גם המאובטח ביותר!
תכונות:
• אין צורך בהרשמה, שירות אינטרנט או ענן. הארנק הוא מבוזר ומעמית-לעמית.
-• הצגת היתרה בביטקוין, מילי-ביט או מיוביט.
+• הצגת היתרה בגרוסטלקוין, מילי-ביט או מיוביט.
• המרה למטבעות מקומיים.
-• קבלה ושליחה של ביטקוינים דרך NFC, קוד QR או באמצעות כתובות ביטקוין.
+• קבלה ושליחה של ביטקוינים דרך NFC, קוד QR או באמצעות כתובות גרוסטלקוין.
• כשאתה לא מחובר לרשת, עדיין תוכל לשלם באמצעות Bluetooth.
• התראות מערכת על קבלת מטבעות.
• גריפת ארנקי נייר (כלומר, אלו המשמשים לצבירה קרה)
-• וידג'ט לבדיקת יתרת הביטקוין שלך
+• וידג'ט לבדיקת יתרת הגרוסטלקוין שלך
-אם אתה רוצה לתרום לפרויקט ארנק הביטקוין, הפרויקט ממוקם ב
+אם אתה רוצה לתרום לפרויקט ארנק הגרוסטלקוין, הפרויקט ממוקם ב
https://github.com/Groestlcoin/groestlcoin-wallet
רישיון : GPLv3
diff --git a/market/market-description-hu.txt b/market/market-description-hu.txt
index 286067db91..4668e35029 100644
--- a/market/market-description-hu.txt
+++ b/market/market-description-hu.txt
@@ -1,4 +1,4 @@
-Mindig legyenek nálad a Groestlcoin-jaid a zsebedben! Úgy tudsz fizetni, hogy gyorsan beolvasol egy QR kódot. Kereskedőként megkapod a fizetséget megbízhatóan és azonnal. A Groestlcoin Wallet az első mobil Groestlcoin alkalmazás és vitathatatlanul a legbiztonságosabb is!
+Legyenek a Groestlcoinjaid mindig kéznél, a zsebedben! Fizess egyszerűen QR-kód beolvasásával. Kereskedőként az utalásokat gyorsan és megbízhatóan fogadhatod. A Groestlcoin Wallet a referencia az "Egyszerűsített Fizetési Igazolás" megvalósítására, ahogyan az a Groestlcoin publikációban szerepel.
JELLEMZŐK
@@ -12,6 +12,8 @@
• Rendszerüzenet Groestlcoin érkezésekor.
• Papírtárcák besöprése (pl. amik hűtőháznak vannak használva).
• Widget a Groestlcoin egyenlegről.
+• Biztonság: Támogatott a Taproot, Segwit és az új bech32m címek.
+• Adatvédelem: Tor támogatás Orbot-on keresztül.
HOZZÁJÁRULÁS
@@ -26,4 +28,4 @@ Minden fordítást a Transifex kezelt:
https://www.transifex.com/bitcoin-wallet/bitcoin-wallet/
-Saját felelősségedre használd!
+Kizárólag saját felelősségre! Csak zsebpénznyi összegek használatára.
diff --git a/market/market-description-it.txt b/market/market-description-it.txt
index 76cca05b09..9dfbbfb447 100644
--- a/market/market-description-it.txt
+++ b/market/market-description-it.txt
@@ -1,4 +1,4 @@
-Porta i tuoi Groestlcoin sempre con te, in tasca! Paghi velocemente scansionando un QR-code. Come un commerciante, riceverai i pagamenti sicuri e istantanei. Groestlcoin Wallet è la prima Groestlcoin app mobile, e probabilmente anche la più sicura!
+Tieni i tuoi Groestlcoin sempre con te, in tasca! Paghi scansionando rapidamente un codice QR. Come commerciante, ricevi pagamenti in modo affidabile e istantaneo. Groestlcoin Wallet è un'implementazione di riferimento della "Verifica dei pagamenti semplificata" come descritto nel whitepaper Groestlcoin.
CARATTERISTICHE
@@ -12,6 +12,8 @@
• Sistema di notifica per le monete ricevute.
• Svuotamento di portafogli cartacei (es: quelli utilizzati come depositi offline)
• App Widget per il saldo Groestlcoin
+• Sicurezza: supporta Taproot, Segwit ed il nuovo formato bench32m.
+• Privacy: supporta Tor tramite l'app Orbot.
CONTRIBUISCI
@@ -26,4 +28,4 @@ Tutte le traduzioni sono gestite attraverso Transifex:
https://www.transifex.com/bitcoin-wallet/bitcoin-wallet/
-Usala a tuo rischio!
+Uso a proprio rischio! Utilizzare solo per piccoli importi.
diff --git a/market/market-description-iw.txt b/market/market-description-iw.txt
index 969d93846c..82a3ff4e5b 100644
--- a/market/market-description-iw.txt
+++ b/market/market-description-iw.txt
@@ -1,16 +1,16 @@
-שא את הביטקוין שלך עליך ללא תלות בשרות חיצוני, העבר או קבל כסף בקלות על ידי ברקוד, הכנסת כתובת או תקשורת מגע (NFC). כסוחר, אתה תקבל תשלומים בצורה בטוחה ומיידית. ארנק הביטקוין הוא היישום הנייד הראשון לביטקוין, וכנראה גם המאובטח ביותר!
+שא את הגרוסטלקוין שלך עליך ללא תלות בשרות חיצוני, העבר או קבל כסף בקלות על ידי ברקוד, הכנסת כתובת או תקשורת מגע (NFC). כסוחר, אתה תקבל תשלומים בצורה בטוחה ומיידית. ארנק הגרוסטלקוין הוא היישום הנייד הראשון לגרוסטלקוין, וכנראה גם המאובטח ביותר!
FEATURES:
• אין צורך בהרשמה, שירות אינטרנט או ענן. הארנק הוא מבוזר ומעמית-לעמית.
-• הצגת היתרה בביטקוין, מילי-ביט או מיוביט.
+• הצגת היתרה בגרוסטלקוין, מילי-ביט או מיוביט.
• המרה למטבעות מקומיים.
-• קבלה ושליחה של ביטקוינים דרך NFC, קוד QR או באמצעות כתובות ביטקוין.
+• קבלה ושליחה של ביטקוינים דרך NFC, קוד QR או באמצעות כתובות גרוסטלקוין.
• כשאתה לא מחובר לרשת, עדיין תוכל לשלם באמצעות Bluetooth.
• התראות מערכת על קבלת מטבעות.
-• וידג'ט לבדיקת יתרת הביטקוין שלך
+• וידג'ט לבדיקת יתרת הגרוסטלקוין שלך
-אם אתה רוצה לתרום לפרויקט ארנק הביטקוין, הפרויקט ממוקם ב
+אם אתה רוצה לתרום לפרויקט ארנק הגרוסטלקוין, הפרויקט ממוקם ב
https://github.com/Groestlcoin/groestlcoin-wallet
רישיון : GPLv3
diff --git a/market/market-description-ja.txt b/market/market-description-ja.txt
index f77eae0404..61fef80e91 100644
--- a/market/market-description-ja.txt
+++ b/market/market-description-ja.txt
@@ -11,7 +11,9 @@
• オフラインの時でもBluetoothで支払い可能。
• コイン受取の通知。
• ペーパーウォレットをスィープ(例. コールドストレージ用)
-• グルシュルコイン残高のためのウィジェット。
+• グロストルコイン残高のためのウィジェット。
+・安全性: Taproot、Segwit、および新しいbech32mフォーマットをサポート。
+• プライバシー: 別途インストールしたOrbotアプリを通じてTorをサポート。
開発に協力ください
diff --git a/market/market-description-ko.txt b/market/market-description-ko.txt
index 9c7332d4f2..198317c0a6 100644
--- a/market/market-description-ko.txt
+++ b/market/market-description-ko.txt
@@ -1,21 +1,21 @@
-주머니에 항상 넣어다닐 수 있는 비트코인 지갑! QR 코드를 통해 빠르게 비트코인 결제가 가능합니다. 판매자의 경우 안전하고 빠르게 대금을 받을 수 있습니다. 비트코인 지갑(Groestlcoin Wallet)은 최초의 모바일 비트코인 앱이며 최고의 보안을 자랑합니다!
+주머니에 항상 넣어다닐 수 있는 그로스톨코인 지갑! QR 코드를 통해 빠르게 그로스톨코인 결제가 가능합니다. 판매자의 경우 안전하고 빠르게 대금을 받을 수 있습니다. 그로스톨코인 지갑(Groestlcoin Wallet)은 최초의 모바일 그로스톨코인 앱이며 최고의 보안을 자랑합니다!
특징
• 회원 가입이나 별도의 인증 과정이 없는 P2P 기반의 지갑
-• 다양한 비트코인 단위 설정 - GRS, mGRS, µGRS
-• 모든 국가별 비트코인 평가 환율 제공
-• 비트코인 URL, QR 코드, NFC를 통해 비트코인 거래 가능
+• 다양한 그로스톨코인 단위 설정 - GRS, mGRS, µGRS
+• 모든 국가별 그로스톨코인 평가 환율 제공
+• 그로스톨코인 URL, QR 코드, NFC를 통해 그로스톨코인 거래 가능
• 오프라인일 때에도 블루투스를 통해 거래 가능
-• 비트코인 수신 시 시스템의 자동 알림 기능
+• 그로스톨코인 수신 시 시스템의 자동 알림 기능
• 종이 지갑(오프라인 형태로 사용되는 출력물)에서 가져오기
• 전 세계 모든 통화별 잔액 표시 기능
도움받은 곳
-비트코인 지갑은 오픈소스이며 무료 소프트웨어입니다. 라이센스 정책 : GPLv3
+그로스톨코인 지갑은 오픈소스이며 무료 소프트웨어입니다. 라이센스 정책 : GPLv3
https://www.gnu.org/licenses/gpl-3.0.en.html
소스 코드는 깃허브(GitHub)에서 볼 수 있습니다.
diff --git a/market/market-description-nl.txt b/market/market-description-nl.txt
index 7b3b87c544..5638979e66 100644
--- a/market/market-description-nl.txt
+++ b/market/market-description-nl.txt
@@ -1,4 +1,4 @@
-Draag je Groestlcoins altijd bij je, in je broekzak! Je kunt vlot betalen door een QR-code te scannen. Als winkelier ontvang je je betalingen betrouwbaar en onmiddelijk. Groestlcoin Wallet is de eerste mobiele Groestlcoin-app, en misschien wel de veiligste!
+Neem je Groestlcoins altijd mee in je broekzak! Je kunt snel betalen door een QR-code te scannen. Als winkelier ontvang je je betalingen betrouwbaar en direct. Groestlcoin Wallet is de eerste mobiele Groestlcoin-app en misschien wel de veiligste!
EIGENSCHAPPEN
@@ -26,4 +26,4 @@ Alle vertalingen worden geregeld via Transifex:
https://www.transifex.com/bitcoin-wallet/bitcoin-wallet/
-Gebruik op eigen risico!
+Het gebruik van de app is voor eigen risico! Niet bedoeld voor grote bedragen.
diff --git a/market/market-description-pl.txt b/market/market-description-pl.txt
index a3c009efb0..2258c3cff9 100644
--- a/market/market-description-pl.txt
+++ b/market/market-description-pl.txt
@@ -1,7 +1,7 @@
-Miej swoje Groestlcoiny zawsze przy sobie, w Twojej kieszeni! Możesz płacić przez szybkie zeskanowanie QR kodu. Jako sprzedawca otrzymujesz przelew szybko i bezpiecznie. Groestlcoin Wallet jest pierwszą mobilną Groestlcoin aplikacją oraz jest uważany za najbardziej bezpieczną.
+Miej swoje Groestlcoins zawsze przy sobie, w kieszeni! Płacisz poprzez szybkie zeskanowanie kodu QR. Jako sprzedawca, otrzymujesz płatności niezawodnie i natychmiast. Groestlcoin Wallet jest referencyjną implementacją "Uproszczonej Weryfikacji Płatności" opisanej w dokumencie Groestlcoin Whitepaper.
-NOWOŚCI
+FUNKCJE
• Bez rejestracji, przeglądarki i połączenia z chmurą. Ten portfel jest zdecentralizowany i peer-to-peer.
• Wyświetlaj ilość groestlcoinów w GRS, mGRS lub µGRS.
@@ -11,9 +11,12 @@
• Powiadomienia o otrzymaniu monet.
• Zamiatanie portfeli papierowych (np. Używanych do przechowywania w chłodni).
• Widget z saldem Groestlcoinów.
+• Bezpieczeństwo: Obsługuje Taproot, Segwit i nowy format bech32m.
+• Prywatność: Wspiera Tor przy użyciu oddzielnej aplikacji Orbot.
PRZYCZYNIĆ SIĘ
+
Groestlcoin Wallet to oprogramowanie typu open source i free software. Licencja: GPLv3
https://www.gnu.org/licenses/gpl-3.0.en.html
@@ -24,4 +27,4 @@ Wszystkie tłumaczenia są zarządzane przez Transifex:
https://www.transifex.com/bitcoin-wallet/bitcoin-wallet/
-Używasz na własne ryzyko!
+Używaj na własne ryzyko! Stosować tylko do kieszonkowych ilości.
diff --git a/market/market-description-zh.txt b/market/market-description-zh.txt
index be07f04f67..415285c34a 100644
--- a/market/market-description-zh.txt
+++ b/market/market-description-zh.txt
@@ -1,17 +1,17 @@
-将比特币随身携带,在您的口袋中! 扫描二维码快速支付,即时可靠的接收付款。比特币钱包 (Groestlcoin Wallet) 是首选的、最安全的比特币应用!
+将格羅斯币随身携带,在您的口袋中! 扫描二维码快速支付,即时可靠的接收付款。格羅斯币钱包 (Groestlcoin Wallet) 是首选的、最安全的格羅斯币应用!
功能
-• 不需要注册,不需要网络服务,也不需要云!比特币钱包是“去中心化”的,点对点的 (peer to peer)。
-• 以 GRS, mGRS 以及 µGRS 为单位显示比特币金额。
-• 从比特币转换为国家货币,或者从国家货币转换为比特币。
-• 通过 NFC,二维码、比特币链接发送或接收比特币。
-• 为经常使用的比特币地址创建地址簿。
+• 不需要注册,不需要网络服务,也不需要云!格羅斯币钱包是“去中心化”的,点对点的 (peer to peer)。
+• 以 GRS, mGRS 以及 µGRS 为单位显示格羅斯币金额。
+• 从格羅斯币转换为国家货币,或者从国家货币转换为格羅斯币。
+• 通过 NFC,二维码、格羅斯币链接发送或接收格羅斯币。
+• 为经常使用的格羅斯币地址创建地址簿。
• 断网情况下仍然可以通过蓝牙付款。
-• 收到比特币时显示系统通知。
-• 与纸钱包交换比特币(纸钱包经常用于冷存储)。
-• 应用程序小部件,用于显示比特币余额。
+• 收到格羅斯币时显示系统通知。
+• 与纸钱包交换格羅斯币(纸钱包经常用于冷存储)。
+• 应用程序小部件,用于显示格羅斯币余额。
贡献
diff --git a/market/market-promo-graphic.png b/market/market-promo-graphic.png
deleted file mode 100644
index 2c20003609..0000000000
Binary files a/market/market-promo-graphic.png and /dev/null differ
diff --git a/market/market-promo-text-ar.txt b/market/market-promo-text-ar.txt
index 8fccde8965..a30318e707 100644
--- a/market/market-promo-text-ar.txt
+++ b/market/market-promo-text-ar.txt
@@ -1 +1 @@
-البتكوين الخاصة بك، في جيبك !
+الغرسلكوين الخاصة بك، في جيبك !
diff --git a/market/market-promo-text-cy.txt b/market/market-promo-text-cy.txt
index 74b07e6ac1..fa94b8bf92 100644
--- a/market/market-promo-text-cy.txt
+++ b/market/market-promo-text-cy.txt
@@ -1 +1 @@
-Dy Fitcoin dy hun, yn dy boced dy hun!
\ No newline at end of file
+Dy Groestlcoin dy hun, yn dy boced dy hun!
diff --git a/market/market-promo-text-ko.txt b/market/market-promo-text-ko.txt
index 7ee220071f..f0d5ce0145 100644
--- a/market/market-promo-text-ko.txt
+++ b/market/market-promo-text-ko.txt
@@ -1 +1 @@
-내 주머니, 내 비트코인!
+내 주머니, 내 그로스톨코인!
diff --git a/market/market-promo-text-zh.txt b/market/market-promo-text-zh.txt
index d87f457796..70fc13b8c3 100644
--- a/market/market-promo-text-zh.txt
+++ b/market/market-promo-text-zh.txt
@@ -1 +1 @@
-将您自己的比特币装进您自己的口袋!
\ No newline at end of file
+将您自己的格羅斯币装进您自己的口袋!
diff --git a/wallet/graphics/mipmap/ic_app_color_48dp-mdpi.svg b/market/testnet-app-icon-48dp.svg
similarity index 100%
rename from wallet/graphics/mipmap/ic_app_color_48dp-mdpi.svg
rename to market/testnet-app-icon-48dp.svg
diff --git a/sample-integration-android/AUTHORS b/sample-integration-android/AUTHORS
deleted file mode 100644
index ee589b3a19..0000000000
--- a/sample-integration-android/AUTHORS
+++ /dev/null
@@ -1 +0,0 @@
-Andreas Schildbach
diff --git a/sample-integration-android/AndroidManifest.xml b/sample-integration-android/AndroidManifest.xml
deleted file mode 100644
index e437a82cbd..0000000000
--- a/sample-integration-android/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sample-integration-android/COPYING b/sample-integration-android/COPYING
deleted file mode 100644
index f433b1a53f..0000000000
--- a/sample-integration-android/COPYING
+++ /dev/null
@@ -1,177 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
diff --git a/sample-integration-android/build.gradle b/sample-integration-android/build.gradle
deleted file mode 100644
index 79f840414f..0000000000
--- a/sample-integration-android/build.gradle
+++ /dev/null
@@ -1,56 +0,0 @@
-plugins {
- id 'com.android.application'
-}
-
-repositories {
- mavenLocal()
-}
-configurations {
- all*.exclude group: 'com.google.android', module: 'android'
-}
-
-dependencies {
- implementation project(':integration-android')
- implementation 'com.github.groestlcoin:groestlcoinj-core:0.15.10'
-}
-
-android {
- compileSdkVersion 'android-19'
- buildToolsVersion '30.0.3'
-
- defaultConfig {
- dexOptions {
- preDexLibraries false
- }
- // Enabling multidex support.
- multiDexEnabled true
- }
-
- sourceSets {
- main {
- manifest.srcFile 'AndroidManifest.xml'
- java.srcDirs = ['src']
- res.srcDirs = ['res']
- assets.srcDirs = ['assets']
- }
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
- lintOptions {
- checkReleaseBuilds false
- abortOnError false
- }
-
- packagingOptions {
- exclude 'lib/x86_64/darwin/libscrypt.dylib'
- exclude 'org/bitcoinj/crypto/mnemonic/wordlist/english.txt'
- exclude 'org/bitcoinj/crypto/cacerts'
- exclude 'org.groestlcoin.production.checkpoints.txt'
- exclude 'org.groestlcoin.test.checkpoints.txt'
- exclude '**/*.java'
- }
-}
diff --git a/sample-integration-android/res/layout/sample_activity.xml b/sample-integration-android/res/layout/sample_activity.xml
deleted file mode 100644
index 726610d77c..0000000000
--- a/sample-integration-android/res/layout/sample_activity.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sample-integration-android/src/de/schildbach/wallet/integration/sample/SampleActivity.java b/sample-integration-android/src/de/schildbach/wallet/integration/sample/SampleActivity.java
deleted file mode 100644
index 9093969586..0000000000
--- a/sample-integration-android/src/de/schildbach/wallet/integration/sample/SampleActivity.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package de.schildbach.wallet.integration.sample;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.style.TypefaceSpan;
-import android.view.View;
-import android.widget.Button;
-import android.widget.RadioButton;
-import android.widget.TextView;
-import android.widget.Toast;
-import com.google.protobuf.ByteString;
-import de.schildbach.wallet.integration.android.BitcoinIntegration;
-import org.bitcoin.protocols.payments.Protos;
-import org.bitcoinj.core.AddressFormatException;
-import org.bitcoinj.core.LegacyAddress;
-import org.bitcoinj.core.NetworkParameters;
-import org.bitcoinj.script.ScriptBuilder;
-
-/**
- * @author Andreas Schildbach
- */
-public class SampleActivity extends Activity {
- private static final long AMOUNT = 500000;
- private static final String[] DONATION_ADDRESSES_MAINNET = { "FYhcJLhMVX7ZDFinPLiLKxFfbD54zJCy9e",
- "FpaqMAX4A1DJ8JAer8pR9dqGHw2LBG49y7" };
- private static final String[] DONATION_ADDRESSES_TESTNET = { "n4Rmozh9gYKxqMBYxkrWqjbqEtUvK4aw6h",
- "mfs7YsNpFifX1HkdDrizWcGqpxzxu33Taj" };
- private static final String MEMO = "Sample donation";
- private static final int REQUEST_CODE = 0;
-
- private Button donateButton, requestButton;
- private TextView donateMessage;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.sample_activity);
-
- donateButton = (Button) findViewById(R.id.sample_donate_button);
- donateButton.setOnClickListener(v -> handleDonate());
-
- requestButton = (Button) findViewById(R.id.sample_request_button);
- requestButton.setOnClickListener(v -> handleRequest());
-
- donateMessage = (TextView) findViewById(R.id.sample_donate_message);
- }
-
- private String[] donationAddresses() {
- final boolean isMainnet = ((RadioButton) findViewById(R.id.sample_network_mainnet)).isChecked();
-
- return isMainnet ? DONATION_ADDRESSES_MAINNET : DONATION_ADDRESSES_TESTNET;
- }
-
- private void handleDonate() {
- final String[] addresses = donationAddresses();
-
- BitcoinIntegration.requestForResult(SampleActivity.this, REQUEST_CODE, addresses[0]);
- }
-
- private void handleRequest() {
- try {
- final String[] addresses = donationAddresses();
- final NetworkParameters params = LegacyAddress.getParametersFromAddress(addresses[0]);
-
- final Protos.Output.Builder output1 = Protos.Output.newBuilder();
- output1.setAmount(AMOUNT);
- output1.setScript(ByteString
- .copyFrom(ScriptBuilder.createOutputScript(LegacyAddress.fromBase58(params, addresses[0])).getProgram()));
-
- final Protos.Output.Builder output2 = Protos.Output.newBuilder();
- output2.setAmount(AMOUNT);
- output2.setScript(ByteString
- .copyFrom(ScriptBuilder.createOutputScript(LegacyAddress.fromBase58(params, addresses[1])).getProgram()));
-
- final Protos.PaymentDetails.Builder paymentDetails = Protos.PaymentDetails.newBuilder();
- paymentDetails.setNetwork(params.getPaymentProtocolId());
- paymentDetails.addOutputs(output1);
- paymentDetails.addOutputs(output2);
- paymentDetails.setMemo(MEMO);
- paymentDetails.setTime(System.currentTimeMillis());
-
- final Protos.PaymentRequest.Builder paymentRequest = Protos.PaymentRequest.newBuilder();
- paymentRequest.setSerializedPaymentDetails(paymentDetails.build().toByteString());
-
- BitcoinIntegration.requestForResult(SampleActivity.this, REQUEST_CODE,
- paymentRequest.build().toByteArray());
- } catch (final AddressFormatException x) {
- throw new RuntimeException(x);
- }
- }
-
- @Override
- protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- if (requestCode == REQUEST_CODE) {
- if (resultCode == Activity.RESULT_OK) {
- final String txHash = BitcoinIntegration.transactionHashFromResult(data);
- if (txHash != null) {
- final SpannableStringBuilder messageBuilder = new SpannableStringBuilder("Transaction hash:\n");
- messageBuilder.append(txHash);
- messageBuilder.setSpan(new TypefaceSpan("monospace"), messageBuilder.length() - txHash.length(),
- messageBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- if (BitcoinIntegration.paymentFromResult(data) != null)
- messageBuilder.append("\n(also a BIP70 payment message was received)");
-
- donateMessage.setText(messageBuilder);
- donateMessage.setVisibility(View.VISIBLE);
- }
-
- Toast.makeText(this, "Thank you!", Toast.LENGTH_LONG).show();
- } else if (resultCode == Activity.RESULT_CANCELED) {
- Toast.makeText(this, "Cancelled.", Toast.LENGTH_LONG).show();
- } else {
- Toast.makeText(this, "Unknown result.", Toast.LENGTH_LONG).show();
- }
- }
- }
-}
diff --git a/settings.gradle b/settings.gradle
index 0fa587f9e9..e2707dbdf4 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,15 +2,14 @@ import org.gradle.util.GradleVersion
import org.gradle.api.GradleScriptException
// required Gradle version
-def minGradleVersion = GradleVersion.version("4.4")
+def minGradleVersion = GradleVersion.version("4.4") // including
+def maxGradleVersion = GradleVersion.version("7.6.3") // excluding
-if (GradleVersion.current() < minGradleVersion)
- throw new GradleScriptException("build requires Gradle ${minGradleVersion} or later", null)
+if (GradleVersion.current() < minGradleVersion || GradleVersion.current() >= maxGradleVersion)
+ throw new GradleScriptException("build requires Gradle between ${minGradleVersion.version} (including) and ${maxGradleVersion.version} (excluding)", null)
gradle.startParameter.excludedTaskNames << "lint"
gradle.startParameter.excludedTaskNames << "lintVitalDevRelease"
gradle.startParameter.excludedTaskNames << "lintVitalProdRelease"
include 'wallet'
-include 'integration-android'
-include 'sample-integration-android'
diff --git a/wallet/.gitignore b/wallet/.gitignore
index c5b1246a8a..97f53f52f0 100644
--- a/wallet/.gitignore
+++ b/wallet/.gitignore
@@ -1,5 +1,5 @@
-res/mipmap-*hdpi/
-res-prod/mipmap-*hdpi/
+res/mipmap-*hdpi/ic_app_color_fg_108dp.png
+res-prod/mipmap-*hdpi/ic_app_color_fg_108dp.png
testNet3/res/mipmap-*hdpi/
.externalNativeBuild
diff --git a/wallet/AndroidManifest.xml b/wallet/AndroidManifest.xml
index 2a0439bdbd..5171d8fe3f 100644
--- a/wallet/AndroidManifest.xml
+++ b/wallet/AndroidManifest.xml
@@ -4,23 +4,24 @@
xmlns:tools="http://schemas.android.com/tools"
package="de.schildbach.wallet"
android:installLocation="internalOnly"
- android:versionCode="90301"
- android:versionName="9.03">
-
-
+ android:versionCode="100301"
+ android:versionName="10.03">
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:windowSoftInputMode="adjustResize">
@@ -165,8 +182,8 @@
+ android:exported="true"
+ android:theme="@style/My.Theme.Dialog">
@@ -200,8 +217,8 @@
+ android:exported="false"
+ android:label="@string/appwidget_wallet_balance_title">
@@ -215,14 +232,16 @@
android:permission="android.permission.BIND_JOB_SERVICE" />
+ android:exported="false"
+ android:foregroundServiceType="dataSync" />
-
+
@@ -235,4 +254,8 @@
android:name="android.nfc.disable_beam_default"
android:value="true" />
+
+
+
+
diff --git a/wallet/CHANGES b/wallet/CHANGES
index 070309710c..05fa0f64ba 100644
--- a/wallet/CHANGES
+++ b/wallet/CHANGES
@@ -1,198 +1,63 @@
-v9.0-v9.03
+v10.03
+* Updated to Bitcoin Wallet 10.03
-* The app now requires Android 7.0 (Nougat) or higher.
-* Taproot - Phase I: Send to Bech32m addresses.
-* Satoshi (sat) denomination can be selected in the settings.
+v9.03
+* Updated to Bitcoin Wallet 9.03
-v8.19
-
-* Target Android 11.
-
-v8.17-v8.18
-
-* Use D8 for bytecode desugaring.
-* Add more block explorers.
-
-v8.15-v8.16
-
-* Setting for enabling of showing local amounts.
-* Update translations from Transifex.
-
-v8.10-v8.14
-
-* Based on bitcoinj 0.15.10.
-
-v8.08-v8.09
-
-* Update translations from Transifex.
-* Based on bitcoinj 0.15.9.
-
-v8.04-v8.07
-
-* Trusted peers with custom TCP ports are now supported. Use the IPv4:port or [IPv6]:port syntax.
-
-v8.0-v8.03
-
-* The app now requires Android 6.0 (Marshmallow) or higher.
-* On modern devices: Introduce a blockchain synchronization mode that prioritizes privacy vs data usage.
-
-v7.62-v7.69
-
-* Allow multiple trusted peers by separating them with a space character.
-
-v7.60-v7.61
-
-* Visual overhaul of the transactions list.
-
-v7.59
-
-* Visual overhaul of the exchange rates list.
-
-v7.58
-
-* Visual overhaul of the network monitor.
-
-v7.57
-
-* Visual overhaul of the address book.
-
-v7.56
-
-* Remove the ability to directly add an address to the address book from the clipboard due to safety concerns.
- The QR code scanner can still be used, and addresses you've already sent to can be added as well.
-
-v7.55
-
-* Minor bugfixes.
-
-v7.52-v7.54
-
-* Get rid of the last internal content provider; replace it with a repository and an SQLite database.
-
-v7.50-v7.51
-
-* Use CoinGecko.com for exchange rates, as BitcoinAverage.com is currently broken.
-* The color of the transaction confirmation indicator has been changed. It now starts red with one confirmation,
- slowly blending to green for 7 confirmations.
-* On Android 9 and older, switched the app-widget back to dark. On Android 10, use the dark/light system setting.
-
-v7.47-v7.49
-
-* Restructure the settings page.
-* Update translations from Transifex.
-
-v7.46
-
-* Improve offline payments via Bluetooth for Android 8.1 (Oreo) and up.
- Merchants should configure their own Bluetooth address in the settings.
-
-v7.44-v7.45
-
-* Fix a build reproducibility issue.
-* Update translations from Transifex.
-
-v7.43
-
-* Customize the connectivity notification in trusted peer mode.
-
-v7.42
-
-* Fix layout on 10in tablets.
-
-v7.41
-
-* Fix issue with not being able to send due to blockchain replay.
-* Based on bitcoinj 0.15.6.
-
-v7.38-v7.40
-
-* Improve design of app-widget.
-* Update translations from Transifex.
-
-v7.37
-
-* Directly link from the settings to the notification settings.
-
-v7.36
-
-* More improvements on blockchain sync.
-
-v7.35
-
-* Implement light mode for app-widget.
-
-v7.34
-
-* Directly link from the settings to the data usage settings.
-
-v7.33
-
-* Show progress in connectivity notification.
-* Update translations from Transifex.
-
-v7.32
-
-* Enabled Java 8 language features.
-
-v7.30-v7.31
-
-* Use storage access framework for restoring wallet backups.
-* More improvements on blockchain sync.
-
-v7.28-v7.29
-
-* Various improvements on blockchain sync.
-
-v7.27
-
-* Use Gradle build variants for testnet (dev) and mainnet (prod) flavors.
-* Notifications are now subtly colored.
-* On Android 10, when uninstalling the app users have the choice to keep their wallet in app-private storage.
-* Update translations from Transifex.
-
-v7.25-v7.26
-
-* Fix wallet not syncing in the background on modern devices.
-
-v7.24
-
-* Various optimizations.
-
-v7.23
-
-* Update Android Gradle plugin to 3.1.0.
- This means it doesn't build reproducably without external help like 'disorderfs'.
- See https://issuetracker.google.com/issues/110237303
-* Modernize the sample-integration-android subproject a bit.
-* Various small bugfixes.
v8.14
* Updated to Bitcoin Wallet 8.14
+v7.38.1
+* Fix send and receive buttons in some languages where the text is too long. The Scan button was pushed off the screen.
+
+v7.38
+* Updated to Bitcoin Wallet 7.38
+
v7.22
* Updated to Bitcoin Wallet 7.22
* Use blockchain sync notifications if more than 1 hour behind
-v7.11.1
-* Use Gradle 4.6
-
v7.11
* Updated to Bitcoin Wallet 7.11
+v5.24.2
+* Updated icons/graphics for mainnet and testnet, Can receive v2 transactions (if not RBF) without waiting for a block confirmation, Fixed widget preview to reflect GRS
+
+v5.24.1
+* Added TestNet version (-test), Other fixes, Updated checkpoints
+
+v5.24.0
+* Update to Bitcoin Wallet 5.24
+
v4.46.3
* Corrected xpub
+v4.46.2
+* Rebrand as Groestlcoin
+
v4.46
* Updated to Bitcoin Wallet 4.46
+v2.11.26
+* Updated DNS Seeds and Checkpoints
+
+v0.25
+* Updated Protocol and Checkpoints
+
+v0.24-beta
+* Fixes some hashing
+
v0.23-beta
* Updated marketing and help files for Groestlcoin
v0.22-beta
* fixed paper wallet sweep
-v0.21-beta
-* changed to beta
-* updated icons
+v0.20-beta
+* More fixes:Exchange Rates, Formatting of Currencies, Transaction Details
+
+v0.16-beta
+* More fixes, graphics update!
-v0.20-alpha
-* updated to Bitcoin Wallet 4.14
+v0.15-beta
+* First beta release
diff --git a/wallet/assets-prod/checkpoints.txt b/wallet/assets-prod/checkpoints.txt
index 5df223f0ca..e3767a9507 100644
--- a/wallet/assets-prod/checkpoints.txt
+++ b/wallet/assets-prod/checkpoints.txt
@@ -1,6 +1,6 @@
TXT CHECKPOINTS 1
0
-2033
+2320
AAAAAAAAqoIj6b/CAAAH4HAAAACW87QcuYURfQCQgTwwHbOf5wdJ+Vc2j2EJwgcEAAAAANRef6o2ZorPwkpfPKSpi36wuE2V4iGagpDdamjVLOXe8KsvU+KCCBz/t5wA
AAAAAAACD7j5t1i3AAAPwHAAAACtudBX30ka/+I4J/GMPWhfzoE+3TweZuvmKqgAAAAAAKUdu3a3R6vkmWNYiAHkp4QEccbdJuT4W6fXhNaVBHKKUoQxU5SeAxxAAkWM
AAAAAAAEc4b95N9oAAAXoHAAAADBzrDRV5tSKOaIZpZPV/AOH+CwPFzi8B9W/pUAAAAAAOd23giH10etEBN9h+JTeXwA55t/s4g5K2AEYF2DXPLYSl4zUynMAxz9KywC
@@ -2034,3 +2034,290 @@ AAABK04yvdx09/vyAD5yQAAAACAMalxiQruS6GFCgvZpUhQndcBKwIpRUBDhDQAAAAAAAAXHRzH/dcD7
AAABK4Zv6SXc31cZAD56IAAAACCeTedHgDtOA5K15LoptUMWnUOSdxdQBQh5LQAAAAAAAAPwwvZBCp2Y3q0NfaK67EByExBHW3WYjYPNvIR+O1drhmCGYrCYLxr6vxef
AAABK8AknMvsIzDoAD6CAAAAACAU4HG6F8nJ/6OvXXn4obwsPijmUIIZc0qtBQAAAAAAAC+oruZxv/tkU1HKssdPWnB9QGm3TCPCHgkd8El01CFFmlCIYsVbMBriICAq
AAABK/cSITOXaK+JAD6J4AAAACB5NJMXVgzelpX0BLvzIFocn39FOohBcK3sBQAAAAAAAEMCcXdufyIim3OKa1UULdv0uWiiZBZA+FVAvsY4xK0xjECKYjbkGxrIeEtx
+AAABLC8EF7ew/sUWAD6RwAAAACAi7W2nGd3NQKYO3bQ7zj3AhUeJkNttQs4DAAAAAAAAAKQ9d8cASn2Ek0IHg9ZZPICqoqqh2eGJ3HZbS5OAwSq8vDKMYrkFJxp1/f2a
+AAABLGZYIMwFfcX4AD6ZoAAAACBzazGY9Vrl7+X1hDw3uuNuSbtcvH9N0ucuJgAAAAAAAGPSDxeAdBDFz+tfJQS9bRCzORqpLG+O7/+/uttDTvnHhyKOYjJYKxpcHuT/
+AAABLJ3Apd2A4s7YAD6hgAAAACBtVm+3llxWZYe7ahKNFUHBNyix74LIsmj+CgAAAAAAAEUozMPdjfTrrwwozNNSbzYGgrAJi3kCnPLuv1FgjWe0KRKQYg0HIxrJK3P+
+AAABLM+Hcu5iPo3hAD6pYAAAACC3orBnGNLCFFtqLyMH7ReeHFAFPB2OeEsYLQAAAAAAAMipIsdl0kNh/hRomVAR4m1VFL2g7xlk3zbXNwhCc5vfswKSYkgyNBokWTxP
+AAABLP0G0H6GGO7HAD6xQAAAACCjkPRSOzfl/E+mNeAaJyg/r+wU2wZi7LK5CAAAAAAAAOksQN6Pjb+EZIw3odppI+p8r8eruGaQZZFkY9aOekC0MvOTYj72Mxo35F0i
+AAABLSw85VbYx/23AD65IAAAACDEKLxqxx6E7KdFmH9Pmsvsd1WeZeLjnZ22IQAAAAAAAI3b7/KYUUPBnbZaLrlZUmgg4HedJrONBWCCGIapoeFisOOVYu93Ixp4T4kw
+AAABLVxkKzbiLd5BAD7BAAAAACCqJsywJTIbWUrSfKb6DmyPBZLTQSIG6e4oBgAAAAAAAGR+NuzRMH4ptuVqYD0GIVOqEa1SEAQW+4DPvOj3t2HAmtWXYmguLhr/cXUH
+AAABLYuwO5urLHDSAD7I4AAAACBTavjXU+uN/YVz7lIDSSLrF9GR3nAE9KNrDgAAAAAAAB3q2gezleBwHp5JSF8gBz9Go2o7kaqNEhl2pJdm+ZxPZ8aZYoa4JhogHu5G
+AAABLb2ww7QjN9eBAD7QwAAAACAcoqBuuO/QwRjcx4NKt1bTXFv0bSvVQqxeCwAAAAAAAJMJyrVIzSf3GiXgsCZw5IqWy2nCTnJRaYFc7O+Cqwjc/bebYtgqMBrnz44B
+AAABLfZMnqdUq7IwAD7YoAAAACD2pKeEdpiKlaOvexbtsaqcKbfTkcGcyqXQIQAAAAAAAC5YM1/lo3r5igkgjZJN5pf6aqJKq+EFr+0ihuwr/lEOQKidYl8LLRqybEBm
+AAABLizGG1R83ei3AD7ggAAAACBf9p5lBGbRZ17irg8zbuUnwoTjJy0MeG9CFAAAAAAAACD3iuJ4fYtGqD1hxkkBjW/5A1z6I/uME1bvCYFs5yHdQ5efYpiyIhq577Ro
+AAABLmIfoFHSbUpGAD7oYAAAACCfhbLNpebbe5S1uHdbwP7HdjS245+qnd6SDgAAAAAAAHjkK7rkc8LwXJvJQcJmn37H11fxi3QzzksuUt52EYQmV4ihYjNtKhrFbgSd
+AAABLpg8g1Vwr65VAD7wQAAAACC9b1Wc0T4dE1vksKcNvWIxNaQNYOlqxVUCFAAAAAAAAK2sdBmicXjkDBkKDYwpy7milgGpOSCkr30nBxa3Fphv6XejYrfOKRoY+KWB
+AAABLs2VP8HelXihAD74IAAAACC3m2ah8Dktm6DbnffG4h1D1QAVp9opLo/lIwAAAAAAAGUs2DFeKYc4Fp9rVON3wTDqscwU/gQFxVDAJsQSXbvRKGmlYoKzJRrPbMiu
+AAABLwKmUBoES8ciAD8AAAAAACCu+xG2pPcIL6Obfxdw9ZgcLe30dDqxFxTsHwAAAAAAAOqUx1+ACmjJHNzE6duLwAZzEvYvmY7WuThQsGAsF19MelmnYgrOJBp2oMit
+AAABLzEUoCLNubEyAD8H4AAAACAnPeTok2O8j/rDcCwb0PJjEJkSTFnhaCvjJQAAAAAAAHEwZBBtiUScJR/gRi3B7qr5YPHZuwHZzNv5MtM6Mk+J8UmpYrdPKxpOzLJo
+AAABL18V3hWfmyMeAD8PwAAAACA6F4q+VDHNOjvmOJTVJ9faG0dEnbmzXWNwBgAAAAAAALU8sffuL0GmCytok72cB+A4QOcPiSQyFTw8klBhJ4zD8zmrYt47LBpED1Ob
+AAABL4vxadEUfbhwAD8XoAAAACCKe2pcwB18A2abr7CZ3z/hb3uBcsdopc7/DgAAAAAAALqGZHGz7PZxjtF3GgXD5YoWybOHqFzn/sR6E2D1/dn7fSutYslFLRqJ7U6R
+AAABL7mxfHzUFjl0AD8fgAAAACAyLPCUfqdodFLQ6LFV/wsc6lmn7X+T8O55HAAAAAAAALrTH8v8RuIsIJJ9HjFoaUQgMC1QXHEkIICiyuLKPCROURyvYlz+KhpcD+8D
+AAABL+ll+aYo9eYVAD8nYAAAACAuElTUUk4sC6/PC+m6uoWdoFN+fy+XaiZhGwAAAAAAAFDF4u9Iy7BcgvGj95vz+j4luOtnIbjIqhc7tQzSQ19ITQyxYtZLKBql4wJw
+AAABMBOk37uF5u2BAD8vQAAAACAw1RqUFrR8CoKxDxC7ZfoTgf6bP58rJKk3AgAAAAAAAL00XKY0Ht0gsXbRA3FHmqWvgx4+665jxMzSUpWQVnNkmvqyYg+jHhpwUaXk
+AAABMDveKLtJ9SHIAD83IAAAACBEojRfbWOMog0xbWbG7YG3sPCsXpQZg8sDEAAAAAAAAGvbuI4j4JXGMERqj01sLQQ3XZlYDrwbBEH+9hlL+EeENuy0YvEqHRpif2OA
+AAABMGYKVA4coizAAD8/AAAAACByoQ9I8vxUrQuIFHiYlifsPbdMBI30AKVpJgAAAAAAAGDR6tjpvUC88YNSQappZfo99m2Ck9b0R4VfkyVliC8Jc9+2Yh8DPBooRa+j
+AAABMIvtf2Mpfo7CAD9G4AAAACDl8pJC4lEWlsYPpgE8zKT4yTGXKwHWba94GAAAAAAAAHXBYV136qhVmEneIDBZw+q1FNccBX4R4V4HSSPwpiSqIs+4Yj1TJxpU1ZBk
+AAABMLnHiZSITkrKAD9OwAAAACC6JHDivOO7sTsrfwS180KvmNesUejRlm7BJwAAAAAAAJ5jzqPtYDMkFeQgzAxx7uxInd+L5RZCHKWLHp7Jg30Fqr+6YhEtLho8/Uid
+AAABMOcpMsRalzgWAD9WoAAAACChw2puBwR7npzdBerZWYNVeOsIWZ1lkQjsKwAAAAAAAMJ/A3lCC4t66SiLjjK/zzxmomaIpie1Bqj5cjvsAaBYT7G8Yr1jNRpm+fAB
+AAABMRYnW3YzizT0AD9egAAAACCroms5JaKAlPAbuFAESHWCYgbDZheAbkGsCwAAAAAAAIaMetpfisG5C2GX7V0ZF2VrI1eg+JlUlm6FX2NJVG8+bp++YipKIBpzWAlO
+AAABMUfi5mCAHJcWAD9mYAAAACD1414iXnpCCRp4CDlgd257NP/qrBoojDo4HAAAAAAAAGCS/byisNeheEGHevcP0xVOKhDHWs7ArjmcfjdNXSFEbY/AYm3eIBrwZi95
+AAABMXk+WrdqfQmTAD9uQAAAACAmiD+SAxI/xK6aa338s8l1S4EuTNcETa3wBQAAAAAAAKl0FbFEqlU0aUydxI4n231G99oc0rEI/MtUUfbmSTYrzYDCYsEcGBpfNBCQ
+AAABMaghVw+cFjQPAD92IAAAACB2r1pKufNWDKxTfx2rOjuLI6n8yizcgBaiCAAAAAAAADDAdun17jqiIMNFQMqSjuTqPlT+dIqo0fuC22Uinj/eiHHEYswpIRqm67hs
+AAABMdVBN0KgpF6DAD9+AAAAACD5eLt3wNRoSRspc5UJPwD5jVRvh3bMmr1hHQAAAAAAAEtQRHYOE3Kvm6qHdMpcb5not+A2mqyzqfWEVREXEgiWOGLGYl9qLBp1Lk1L
+AAABMgDEKkxYP6CPAD+F4AAAACDVDYgc+fh634ZoTO4aPB5e7/mqQzCt2umwKAAAAAAAAPrm+znEE/rmmkeghdMc7+Qkd6j8mM+utHV97azJ+0LO5lTIYvzGNxrbHXqU
+AAABMi7fb9nrY/dPAD+NwAAAACDKu7anb9APKn/uz4lVDecKTQkM3ZarQWEeEwAAAAAAAITfFPlF7Tbfy5JdOZINTus2bgiDNgkiGiu04DniBhj7YUXKYjueMhrofvXB
+AAABMlnQHCo6fjiiAD+VoAAAACDBrvxEUfxu7cwdypI2Jz4LVOvlukV0RmsCJwAAAAAAAJDhOedMNksLeFJwKbBBtUX01FMb04mR+NhtIRz5pJrgizbMYtndTRq7/K5C
+AAABMoMFzwNx1vLJAD+dgAAAACBV/20p9jVJre8Lcsk4IPVmTw2Gou+reZiyDQAAAAAAABT6JJjlA3KHDfEjLKEgWRKiL0SJue9V+DiXdghz7LT/9CXOYr01Nxo2du+L
+AAABMq43qn1lW2NmAD+lYAAAACB7JhxA6FD9wAuv6nn+313A3XIWaVZJmsewHAAAAAAAALiETXAiwVE7vyvEY7fyzpAKu9dMKlT7nPGXaqDgm1FO6hbQYr02MRrjDOIG
+AAABMtgOx0399uh+AD+tQAAAACCblNvpeq2Yv3fmF0Ba4jpUfdpBM67NyU6YDQAAAAAAAPfnXpqLOw920D46SHhRPfSpl0OboLYeATyTqMGmDO65VAfSYjH8LRos6Q+P
+AAABMwHyBK3/vd/4AD+1IAAAACDV0VABujBA+AH6FfOfs92lksoREOe5OaGPGgAAAAAAAB2DW4CVt5C3Frnt+Uf69RSp4L/HY8WGx9YR71ok3jZElvfTYk/XJRogMTwl
+AAABMyyNIZG7VviXAD+9AAAAACBbsxrWCaVNy9M5d6tDo8u0xroxtAChzg+zDwAAAAAAAMJj3Cvjd0F4KkCipriSd456HVZgceyk6bX5v9AM8xSDOOnVYrOaNRoywjnN
+AAABM1oZ5FuxRJWQAD/E4AAAACA0avlULCmzuwOyvmev7oEZ28cW7Kqntex1HgAAAAAAAEgR31XJGU182NpsCBka/mhLHn0Zb1+RcsInG4JJSd8VUdrXYpuyMhrRkcvl
+AAABM4sXAXXI15hoAD/MwAAAACCH5tsX5F5h9qpRBUPHFeZq6yX1dYdHhZ3pBwAAAAAAAB8MVMcKGV/FsGRefo3VXr2/L53JZbGOIC6RSP33IeT+2srZYhN9MxowMqnK
+AAABM7kVZUZZrqbuAD/UoAAAACAiJ3imZn9Wmh/y+94yLHow2tUbpHEFdMl0FgAAAAAAAPApMMV86vvz9iByJ4d9HMp/eBHF5dGiD2NDgNSzZCaCybnbYseeGRrTVcXj
+AAABM+isr6kbTNJqAD/cgAAAACDDVYG1W5IP6eTGoYPAZqkRbgrGtDFbiQADGAAAAAAAAIZJqJnn9UDrCoEbs8zicGK8IeWRWAn5rMoY7de6vJxe9KzdYt7aJho6M4d4
+AAABNBk012q1E1CFAD/kYAAAACCC71A2tGNEhREEWAVnWFy5NmRKCpmTWoFnBwAAAAAAAEl+Zuxq2R9g2niWRGOPW+0gnCpLijcOvKIUZ8LYxqjXe57fYiBeLBpjGMZ2
+AAABNEfhnGLj7KGNAD/sQAAAACBlCMDW6pKHVY5eYdjbTGy7q41NtsZjMDIwEQAAAAAAAEYMoVaFEDr9y3e+F/xI4ch+BfcK95eW4DbFTqVCQflz047hYgiGMRrB+Jvu
+AAABNHUIsSiJMdcDAD/0IAAAACAA1dC0P1VJLq2KNxDdw7YxBLgBVcbcxMfPKAAAAAAAAIczhmrZzo9uWXKPsU2SRWRIgWYPza77h/ulw7RPra6nzX/jYulnLxpUWwEA
+AAABNKRfLmdiXZQpAD/8AAAAACBUP9p4izP0psJUilf/wjQgD8q+gAB2FcShDAAAAAAAAG9RstAPaUzD2SYaTPeNPI9Ba7nUVX/wPOIKZs+Jodq+9W/lYsTRKxo5qebg
+AAABNNA87dfbYBTvAEAD4AAAACAz6v5kNfyVI6TVm2oUIxcSYYkObvlUEtNHJgAAAAAAAFLIu0rr9wv3KsnXSAkUGgohzzXo/0/eGQGKeFnyHuVEl2LnYlQ8OxpssTy0
+AAABNP6QiaBR0ComAEALwAAAACCyb5PiWgGSPNjyojTp5lXmcIQAkyiKx4IyHQAAAAAAAP83z0Xgxn/HExH5+oMMIaF4rOkDDUIuBVv4OkMvhj34cFLpYk42JxrouooV
+AAABNSvlcFoGFDuxAEAToAAAACClfFOWpv3VRvgDlBtCR2vRyMq+VPBRGN3DJwAAAAAAADr8e5CRAnZLhPkSxffzEZYz1PXQtSlinvnepLI3JyxUC0TrYs/ROxpWjW5k
+AAABNVmqZMbkMeexAEAbgAAAACCHbXLNEN1SSJPcKWGsVqdztfx+ArUHlpVVBQAAAAAAAD+lMnulWyxy3NuE6YNkREPqiZ37NTb87wO25ED3W3aXpjPtYrkCKxq2CP+r
+AAABNYSEl+c7Bo1HAEAjYAAAACD7WzHELMOll3zZMY9U4XlBYlCJa7FlhrUlHwAAAAAAAHyC77qa/kPQ5o4N8gDrGtPE5ldhWkO/r/27psl2sZ7CRyPvYmqsJRolKqEb
+AAABNbEPcIv1JJZVAEArQAAAACCxSoWZVllmo3qNVsClSX+qa3heiPKUnnMxFAAAAAAAAPRyJWLpooF8Ibg/HhLtC8L64UDcU2AS8xyGwNq3XoB5nBXxYiHVORq2U2T3
+AAABNd4dDIM+tubJAEAzIAAAACA6KhbUy63YVrml0gNV1pCQzYDYZCg29bxCCwAAAAAAAG95rmEP377xNaRvwPX3Mhq0ctQAhOLcDaTs5LI4cL70oQTzYmjaLhq20UYl
+AAABNgmP7j4ehEpIAEA7AAAAACC6VBT2FJS+UKjrkrdusREmfew7K+rmsCIaRgAAAAAAAPwjJUK3vkhng6tTpSQgMxVBGlEC7xVsAmPYTR8y8Xh5F/b0YvE6RBoNibNr
+AAABNjMANjUI0YPhAEBC4AAAACCesyLiL9jwekbFzCxhut9FXw/X9v/jVPInGgAAAAAAAFrmqOqx9shwnFp3oJA16Ao1UbvoM/I+0dIQ511GBTaEteX2YpEnJxrACikd
+AAABNl8JJreT73o5AEBKwAAAACC8E/sZNj/DXUKKJqUvfePZrvF2ucFJSjSvBQAAAAAAAFEOaiHcQ3BPuXnULARqTbuvuhAhXtkWMrBH7G0cuocHyNb4YhihLxprN//G
+AAABNosqG6SAIkJLAEBSoAAAACBHXcjy+RhhBNsedICWYgepy3u1Rs07MWreGwAAAAAAAMPH+76QNpYykloMLXe7w9aJTQfmgWaXvjtdM6qJqRlvJMf6YtXxLBrDPy+B
+AAABNrdngO9WBOD5AEBagAAAACDkecWO0eDaBwEY3EE2ZLLVhimGTfGA4rvfKgAAAAAAAEW5CpD5/Zp/uBbJm6MBCgkDMCj54IF1+ppCnqpDro+L0rj8Yr7lNRqilnIH
+AAABNuLa7DLyUkQAAEBiYAAAACAlrW6lAsfEaNAAeFTz4P+56qtSaEas7XCpFgAAAAAAAOlxcp+DmGaxMaQLqz/xwkPOaDsVvISOWuB3/G+OeX2MKaj+YhPRIRqD0IS6
+AAABNw4EdIflevjqAEBqQAAAACBcVorkW5qZ93r6w3T70FvR8U2ADuf7o+NACQAAAAAAAFRaA/rFBpwjdwb7nOoOW3FDfAK9lRVmK9LWGM3Jc+zO0ZgAY7QyLBoYwVor
+AAABNzktbwu2XwAVAEByIAAAACB70UZ84uSFxKGoLJGDLjx4USSpGkQ6cBkIJgAAAAAAAPJXkNTCU8cPn3XnZSWIMC8YnRoyWqY+gAzGmzqPHutv7ogCY2h5KRq29PII
+AAABN2QssJCMNZ+tAEB6AAAAACA0CzkCyZZ5c0TqahAq7Kb4j9ZqBKYla/rsBwAAAAAAAPEKId/9H7+qfQDcfP3fegyp4WZ4Hy+teKjkhsuodx8weHoEY4zZLhrJ7IVM
+AAABN47PBw5l11haAECB4AAAACB4FHWlp3PCNs+zdksxlFADI/7g9E0l7Kl/JgAAAAAAAB3P4d4WKGshc1Tn0A+r0zlGAOSrzVMziy7ywjvgLrkFC2oGY6k3NRoZSbBq
+AAABN7sZzntEuUOFAECJwAAAACCarcTJ9yArHuFUTjbcp+Q7+MWYnGPbRQ5uKQAAAAAAAObUtYNs21Z7NbB0hCxqvX7nMYnrlVm8JOy70OXlzduhG1oIY7O6Mho4F857
+AAABN+kZQrhj5lVCAECRoAAAACDxmgCpmD4MxQ2+WI1pPr+DZTRu1ixTwMlCDwAAAAAAAJ8WVoCRqWMtRp2WGt1Avd/5grCtEaDwN1/ZgryErnjzX0kKY6weIRpjhtky
+AAABOBRJkHA8VVn2AECZgAAAACCf5qDVNi9LilvVEMv52Y31va9v/Q5j3+iNAAAAAAAAAJLcEg6JLWKYJNo/bdzOrdXwqckZl4uNZY53jgJY6Jnt3jsMYxcIPRq9rZFc
+AAABOEFMd03VyxwYAEChYAAAACA8xTTAj/3gdye/tzLmlwwNG5IGARJl6IUjAwAAAAAAAK8fhaJ2JWwADEf/xv3zZ67EV5YjU5359Icetd+qX2G/wiwOY5iSPhp3TI9Z
+AAABOG0c2zZvHLDEAECpQAAAACDzb6xD5VIz6TyilJtLCSJ6FKc/RYnv8ZgEFAAAAAAAAETowlh4wTgFr9O6FK6/arJWJPmvS42DPTiEaMwEpT8BoBsQY5SIHhrYVZAy
+AAABOJnOS9dugIeEAECxIAAAACCu8zISN5+W/LRkSkrUwTjnJcHFvIVVNLIhEAAAAAAAAH6XC37xH0I4jsoT2TYb6FcGkI6qTSVoKoH23eGATpDk9wwSY607Mxr1ZpgS
+AAABOMdIiNJburIGAEC5AAAAACBkjwE3JeFQZ//e6a6cuJ+bLbQaAZNwpCH4GwAAAAAAAMzPHPIEsW4z8uvMtbl4UY1usVDDsepemLHrvnu+dgal0v4TY0sYNxpGJ4ZD
+AAABOPMqzp1//zNcAEDA4AAAACBoyjgcW52PzfaPdC4Dlof1+v4Kqfmf+w1wJQAAAAAAACm/Iy36H8UX/aS9QsMX6egsn9pd6tc5khJcxbbFBE7lwu4VY4XeJxpGgM8W
+AAABOSAcNqTBnTgSAEDIwAAAACDwaw1Cu/ZE7Y1O/g8JiEISn7fUYGAhEqbxAwAAAAAAAHJFWKTtVcRF+Gtgb49gnfALth0C7gESQuAN6qgLOXbnh94XY2IfIRoi+849
+AAABOU7slmagMpdNAEDQoAAAACACE+0cAXQlXWbU/LgO7IjL08iw9P1SRIw8BgAAAAAAAOyTCLBgl9cm/Wv2KoBtm3nLs614rnnIc1Y0Vvr5cYphu88ZY9NPJxou2vz8
+AAABOXwkecd6Cs7cAEDYgAAAACB9RJpIhegIvughDpbIZQQinRjI+1FRNj7fEwAAAAAAAIk+ecATEpZNfNKzf57Z1nYa/GrHfWmOfpucJ+FdftaQzb8bYzkqLxptxp8d
+AAABOafohSnZOdCuAEDgYAAAACC30q/2sGD/i5u8eghdgVySYyPNde4mma6RJwAAAAAAAOXpFpCD8sJoKcvHkyKw4Ph4smWhnHOy5MhxorfqP6hNnrAdY5pmNRq2HPYx
+AAABOdZVQ63t/+YAAEDoQAAAACBAijpx15Bv9Lr6OaoAFzlEQ6HtJX5S26FdEAAAAAAAAEWgzMPqKy1VuWHzSvPhM8GqgDH3SvL68+M+S7QPdO3hNZ8fY1TkJBpASo3o
+AAABOgRt/DdTxTuEAEDwIAAAACAxvalJFUO/nbBbVCKUHuZZbSBfgUcX62MWFQAAAAAAAMuRByQezqItwumNFEUkDlGoW/llMKDcdpFTywvqOuLM15EhY+yONBqzn1XN
+AAABOjLG1dD8ZGnRAED4AAAAACBT5VJBz+rIqCVUNxSfrn7W7MS9IL4XdW1AAQAAAAAAAP+a5QzQvGyyHoqmGK3EX+xKhAEWjsAPVOLHnYApYa/WN4MjY9vmORquROUA
+AAABOmFB2UxF5HIZAED/4AAAACDVaRT0RaEtCSEclkJr/HTgTGGsD+2+HMnRGwAAAAAAALvS1LeMOO4BxG1V4b+ItaF+m9VxM9BIsoOmdpLJW84/LHMlY/kDPBozL0TM
+AAABOo/BBkeauPTNAEEHwAAAACDu9NqThZLFi6D2+PlWOEQ+6Td8S3Uh7kbQEgAAAAAAAPFi3rRCpYIq7bF5xZlROcczccUbUiA6ajNc0jwgBiW9mmInY4okMhrDvOwV
+AAABOr3q6FP2xncAAEEPoAAAACBHIvS7GN66gn49QEOQUwoBuTLkCmvbCmniEQAAAAAAAPa1fE8c6pmo3YAzm6o6l0OUldo79JoSQixmaPDFt6q+GVIpY881Kxpt09sl
+AAABOvIL7bbrTmXiAEEXgAAAACBv7cEqfenOL2gOg5Z5aAMvfJKoHPxsSjczDwAAAAAAACvbXA9PnpoTiKQkN/wxOJ5pamEnklU5tDFxVN7lwiZmnUErYzRZJRoiISRv
+AAABOyXXj9Uk8ImxAEEfYAAAACD64jxsl15S5Mbs3Oz/mzExF+1AXK0FgdGZDAAAAAAAACjjUzI/faeMEASKVGw659hoo1wzhOziMfFTr+S4cMnUgjMtYwkRKhqW7H7J
+AAABO1r483Yo83SoAEEnQAAAACDLdCTFBdIwBkvMmIOZL3Jcxea41RREbPB1FAAAAAAAAHHa3J2Y2js4v6iyvPT+rhgMM6Oqxcc4g/Tw2R3KfBII8iIvY8+8FhppHvmv
+AAABO5CqTagWEXQFAEEvIAAAACCNjqYwCMT6g+RBRh/L9UI7InqKcxI453pCHQAAAAAAAE1hALedxgS0+OVE/8wJphPpdR0Mn+zgsJM+0GT2nhOpHxYxYwSBMRpIhkCp
+AAABO8PAbz3Hv5Z5AEE3AAAAACDRtnrPTHMVgoFiUnaw+HGwvG8+Fp3mYmRRBAAAAAAAAEEobRLxedvjDSGtcVuxbMdb5OD0Z2EvFY6Fy6rgnhgU5wUzY6joJBqS56Uo
+AAABO/UsUQknLVrPAEE+4AAAACC0kaWxQ0C7JKpc55MP3BbddXeJeFplhgIRDAAAAAAAANcGG67RQKj4UM/w5d0Vw8+NwabgIe9OqURlHhl8kXLaLPU0Y5bfJhraehFl
+AAABPCd761kXzEfFAEFGwAAAACD7CpHoVbjvRKGlMoSWwjvcWmABb+WAQ0lmDQAAAAAAADddrCboCoiiYXNBkk9ltOiatDT3hsOzo8rqEs6VW9wQReY2Y+gINBoUpgwc
+AAABPFrJrqC+RDm4AEFOoAAAACASUI0GRhvr9sq+vzHvuuB/pXRipjo08QSaBgAAAAAAAF62DajU2gq2zxuCuB03DctjFYCcypLm0yn1b6LHx+KpI9U4YxgRIxqVMEi1
+AAABPIyYdWyFjburAEFWgAAAACB1sLEdwFoPDLLf1TH1EDiPZkVzvIa5ZF5CBgAAAAAAAOul0+94CxfayxHPzSnL1ZITAozPRhLM24Su0GBGv8jhPMc6Y6zkLRps/KxH
+AAABPLzdFFXzlRfcAEFeYAAAACCw5LANxM8PM2j7aKT0Kf/y0KsssS2/5Mk1CAAAAAAAANXoDZ5I1aHhbHks13NCh1aJsLL9r16RzEt+FzYs36wmUbc8Y2a4KhqXj7Fw
+AAABPOxV9IqJQFKEAEFmQAAAACDOgCm3kzrmiSlIX5UxmtmMtu6N0aRBB29ZDAAAAAAAAPDl2YPku/8mvDd7dFLcLiKmwZv9bsqWuZmIB9ZEsMxq16c+YxWyJhqrbtpz
+AAABPSA4SK4y+aKuAEFuIAAAACCPp47MghKrpX8Tm9JXRocQX03OcUjDJhH6FwAAAAAAAEK54x7LcjxOp4DDN6OQYiqsP0aWMgqMyXsZk8V7gecZN5dAYyMCJBpF4dxR
+AAABPVUqAHQYClKiAEF2AAAAACCbQMPhw/xuzWXLaDGBkm1fA2Jo55ITYkHHEAAAAAAAAAg4dgJ1+HK9g1WMTCNRy38RCX7TgI4iSXlHhzlbWtTCQolCY7m3Mxq1rhK4
+AAABPYiH3ogBotYfAEF94AAAACBwAqX6AzfrMxgNM22RaKjwwIqIC34OwKHlEAAAAAAAAMYRvL0ENKUslVpHaY9vuBoR3xBeMVN6fTgN9MBYLStw7XhEY175NhoSYiAa
+AAABPbrPZei5+SnLAEGFwAAAACDuSyeok4TuEy01coKipZbCNPb8jC5quZG7BQAAAAAAANwIPbX4fmE+izcMmPcGlicFS7t6wh4FddUCcu3Ma1cNsWhGY1v2HxqrHq1r
+AAABPfAKKUYVojC6AEGNoAAAACCpDiStLH96Nt1Nyuewy8cAB0dlC7E4y3VtCwAAAAAAADpDuqQ2uLPSvtqAYRZxCi+hHIiUJUiB9BcnEQdU/czbiVpIYxUHKhr+nNJt
+AAABPiW2xPWL7QRVAEGVgAAAACCe7DCcbIjws6uKNS0HNnklBMHCUT+M8xuRAAAAAAAAAJjsDHhNQiSObN1GRnZxYibzPB4xqXjPeBmk9uECBRDj5khKY/nYGBr5XwsB
+AAABPlwEuFvdS/rPAEGdYAAAACClgQCfXrprtQ5Bm5bveJE0L1yI9e5WX0r2AwAAAAAAAInh9NhU2Il7+ZrxAFcdiEMBNr6SRrlijz8EcJbrxMydYTpMYxEMJxp8mhtN
+AAABPpG+x4t8VxCGAEGlQAAAACDsLGQlF4xaixh6F+67lIUnuKD6z63EojogGgAAAAAAAF25Ndc2xNyPkVtg+s0/9pW16/JbrHR7MqUaFsi5IqSSqStOY/bqIRpFfVtc
+AAABPskMTvXU5s1BAEGtIAAAACBwHx+d8TV4RnnDvQ5EPJB+qlNzInLRtNM8BwAAAAAAAGIEIUglKT9ZU0znvDEytyiVWXyinyo55CrY3bPbIikKqBtQY9oIKBpbGGBQ
+AAABPv181pX1VOZ6AEG1AAAAACCzqvhdqlc3deyIRQH0vv5n3WN3S6oeJxM5MAAAAAAAAECcX3vfcd+fAOdSIvAQQv0M7KCywindg4rLRlYKd286/AxSY0gBPRrG4eEU
+AAABPzRoLYKkAxkVAEG84AAAACDvp4btC4o7vRAghpzdQCfXuE/wIm7zgFDVAQAAAAAAABdZbEhgB8pTFBS07mc3cgbltTfJT3UiJDxVTa/T9iHGkP1TY+kYKhpt0wVl
+AAABP20EeygJAIVqAEHEwAAAACCvnSS3ZU5cf62qrPyztqZS+JkfHUKdRiqIDwAAAAAAAP89ldZhCmJfIFsLkuFVkh56rO+kdauCAcaa9Mk6Zyt7vexVY1h4LRpQpTYV
+AAABP6GRK7/UsDGBAEHMoAAAACBlgcuhhlVS/xhz5vl0LSAA23epsyDo5Ir3DwAAAAAAAHLzHwVRQsTbMbIuci4ww6+rf0qE5sAs+30HevmZ0yWAeN1XY5d0FBrC5S5f
+AAABP9HbfBN1MezqAEHUgAAAACAcJjeFe9h72STPeAC9eeWqJ60BqzRDyzu6HQAAAAAAAMWsvewSk89jQsqp4dxGvnqzDgyaj+E6npcmUJlpUOTGUc5ZY+0jORqa3RaO
+AAABQAO1956L/+a2AEHcYAAAACD/ni8ahm9tq5LVCSsqZqY/8R9sjhYV8XqBBQAAAAAAAKWF0Ulp9iMZMGC1zN8wj4YSkh2nhZ4Dakw9kEq1LXtpZr5bY76QMRpa343C
+AAABQDPdSyelpARqAEHkQAAAACDG4+bEzKjjH1EMsr9Q9qq9QTaAJ8mYnD1bFwAAAAAAAG85XNonUzeoZrSZKS7pEbpMXcwDpaeEpZPgjsa7PJ2fZa9dY2GLKRoz+kau
+AAABQGX7xRxkOxPcAEHsIAAAACA8alZq6Omq5BixPhRWY5XfgPUQ7XG1mBecNQAAAAAAACToLsQuG+Y1+Vv7sWTzU0DEPNODYmbX4rgsxQyYuTop5Z9fYyRHQxpmEhAj
+AAABQJYvTPjinNH4AEH0AAAAACBsNHRN8KqIi1qI8J3pTqUsEGyWwjZ5VBiIHAAAAAAAAEjzDy0WCI5TBuFffRbqV55714jxHx80YnOc+Yd8NLm4bJBhY8RHNRrRqfOR
+AAABQMcCX1Czuuk0AEH74AAAACCPAYU8zMfSympd93/MPONEiyO42H4r+FMlHwAAAAAAAPsBH3cLC45ax8F6EDJNPljjKwLTQgaGmGGhPxR+Fk07nH9jY6QeIxqyaLw1
+AAABQPdZODDw5fHMAEIDwAAAACB9o66S0mcf0ZizHzJOvOBCnI+hsHSvuEirGAAAAAAAANq8NKkM8pyOYUj21rTmhPD0sgzzAcjfbiXgZ0YHusGviXBlY2jEIxoqeQSc
+AAABQSoElSUh9yoSAEILoAAAACCGVjypGB7yH4MdjbzwP3Vf7JeTFYWbWpYDDwAAAAAAAHOuM5eDLJe1iSV44NubR6QC6HOetBHl9iu1blpYcOb1c2FnY/gGGxqKaqbj
+AAABQVpFb5oWsE/4AEITgAAAACB9ocMEtNkVoTuhYI9NZRCSvnuA9stNdpiSEQAAAAAAACaYwX9n4tBKKxWE56Q5x0NGbRjjYpOHTaqfrOUgFNxAQ1JpY3FCIBqzr1Eh
+AAABQY4iVLAOtl/+AEIbYAAAACCwuArzu7nBgzVdb5vnNa+v4OqjVwajrtDyLwAAAAAAAK0soIjhvM928X+hCiVFIoawVYWRst4lA7jq1GH2gLpTeERrY0crNxpJ7WZy
+AAABQcM8vXukh65eAEIjQAAAACDLL8HGtJgZ95daCLQLWSqBR6tpcIsVmGLUEwAAAAAAAGweTr+0YrEqcKluHnflhOCqYZkKm5Ipybc4Q8iMzr9L6DNtY97tHRpCQB9x
+AAABQfWd6ZFfGoJHAEIrIAAAACDVBQ5sR/qF+M9DdVm9G6EspGxuIMPEWzsGFgAAAAAAAPNjf6QRm6mZjpzjutYtJEhn3ZGFkyYmtOJTkqziNxYS/iVvY8KiNxoAu3rz
+AAABQiZT4m/if6OwAEIzAAAAACCu6K3QsC1ZPIECjVbAwElQD1wMcwLDlz8xJAAAAAAAAFmslhBgqhMz8sV8fSv3PSQMYYD9X8r+fNeSUW1Ex6MqtRVxYy2KKBouayYE
+AAABQlc6WO3n47/qAEI64AAAACBEeZJP2hDidbkgY1EYlXfgtrP13MEOPW11KAAAAAAAACx++uzEznTMKkO9Zp3jQTPIKP+UrnWTMZzkPWfX/oVCUghzY0BKMRo4TMQ2
+AAABQoUygnMynKrXAEJCwAAAACB9eGILuJ42fmaU+dzHp/kxInH2F40CQK2dDwAAAAAAAHx0GCcesp0AyHNemitsuyiTe3Som5k8oMvqMf/DTnKSfPh0Y3jCJBpcUF+q
+AAABQrRkJSRsYKtrAEJKoAAAACDMuC5/iOz3atEQjmUDvBsei9zNEKOwEBopLQAAAAAAAFJaCQ91TfpYNqik7dV0cVT63zOMx/zD1erxT1D4Yucy9uh2YzRwLxreQqYh
+AAABQuZW2MDil9RrAEJSgAAAACA0dQzrgODP4jCEZXfndFu9/n33tTvRv03WEAAAAAAAAO8mk5pJQYzWEHw0vwKVvjWDj5vyz/I6nw/d+bFMgV0S8td4Y+z2HxrHdBt3
+AAABQxUlr/erC4LcAEJaYAAAACBAl9lNCxFbDRyv0i3eITjGXQTRpB6MQqVLGgAAAAAAAM+Z5lmMHshUK6atrNOhC6Y/dzTSwJVGeUcsNSxAfS2Ev8l6Y0C1Lhpz/xAl
+AAABQ0URqSGI6ZSzAEJiQAAAACBJjHN/7V6YAbPm5utzM3e8DzE/kLfxFqMuBQAAAAAAAA8MRqmhJkBhJWAKS1LGAGy59pOK8OTmpYTGrBVZ5BXKerh8Y6U1GhqtU7hy
+AAABQ3Xw0oiWIo+fAEJqIAAAACDnYPPef2ChlQ54hMlHp/M92sCPeeRSa2xKEAAAAAAAAKLGTzr6VvCDHHk944rta9z8EGyyW/bmwxBHfSaHXCMFOax+Y9/ZPxqcaoI2
+AAABQ6fb04TUQR2zAEJyAAAAACATFs8M6m3m+1L/yJwqn8GW7GcvyPA2bY48MAAAAAAAAFmo0D0Egc8FwzPUl9z5bz5frRzDoQCLzw6Ng78B7ZT9MZ2AY4OoNxqmzR+d
+AAABQ9oxbJz3xVNwAEJ54AAAACB9elJoo9Bob7JX7xIsHTs1ZVIQd7J6ZRwlDwAAAAAAAF4xgcdee003pDX60yFKizLa4swDAnZYcOscnDt+OSBD0I2CYzhvQRr4LJtm
+AAABRAyfaZSv/1F3AEKBwAAAACDzA5Ry3I0MdRvbpIgZP9hdStHsn05OhpnJJwAAAAAAAA3PhifoP/oZ1s70qbAR/4eY2IiLkiIgTrxhoSdCZs0KS36EY7bEKBrj10G2
+AAABRDzBPN79zRzHAEKJoAAAACBT2bZ0ZShVCAPWFFXSvam5cj2fgHYyOCqZGwAAAAAAAFiwrRFAMqnpf8LWbPQXqJhBoBKQ7drfLXLURQZPdjXK1W6GY3u0MBpTYeKZ
+AAABRGy2OvYffVvvAEKRgAAAACBbZM1RRqZ5mqXYAX/2MXsb25YpqtL/sQE+KwAAAAAAADCKkfcOrbLaH3Qes4ghBz7CbWQNX3byoPheJEXWthi04l+IY8DJMBpd5J0O
+AAABRJ5XrQ950otXAEKZYAAAACCURnH/+6XB+RJy7ZU3z0ZPgYDPMLBz90M2GAAAAAAAADZyJcj9SwA6oa3vcNFYLaP7XOZfpkXrmaPTifOy84o8qk6KY2STIxop/5rV
+AAABRNEI7WGob3uLAEKhQAAAACD0JYsJHmAK+XLM1wf3IVthFlXNzdL0thr/BAAAAAAAANBWgc3cO3wdL/ccwF7xLB/HslnNTnQ3WDhlG+rQqZTg5z2MY9MtIhpmETvJ
+AAABRQNe1dEsgx3TAEKpIAAAACCffxgbsPvDYeyFcQs3gAbG71LV7RkZ+ijCIgAAAAAAAFoAX8HGbyKyJLY30rDlPGRfze+dMb2UqYzGQvuYa0RimDCOY4bRPBpsuE26
+AAABRTVdfY2Ynwp3AEKxAAAAACAfLmXtPqFAQTBgR2MFVN1U2qmjmFfEcCsjBQAAAAAAAIGLz8eEo4xqgPjE51XbqA/X7AdHwOa6hx9u+JYckODBQiCQY1zvJBojlEPM
+AAABRWjaGgybU1DQAEK44AAAACDWcmadLNm7pJHQ6wc7hPfVGitknfatIJT2JwAAAAAAAOrh8Z9W225Tp1KhU7sfEK4518g2yH59TPkmmBQKE4BhXxCSY2P2KRpUfoVj
+AAABRZ7DKYDi63FeAELAwAAAACA03WoHc9ZlmpGHDjamujzMOWGDpt3almIxDQAAAAAAAG1QwfErpU3OJs1BSuqHSx6Q5bpvJAgnsR8tyUawCKF2NAGUY2b0IxqlS4Lz
+AAABRddR+NSH19IBAELIoAAAACCUGo2KBan7KpwEbTTtA5e43WpVylEL/pEyBgAAAAAAAIYEubW5slHKIRvY/yHScMjTSyomS4wpF9NuZPAYBBMYC/KVY32NGhpNLGje
+AAABRg+tSlG+oJr9AELQgAAAACAQcu3nPm2Fws7kcnOAhFdo9f2mMk0kD4XYAwAAAAAAADGzQBqBDx3iKYPksiVgL1khZEfv31zC8G1vhU/DqK1ctOKXYzgkIxpu2xeN
+AAABRkuQakTYgygiAELYYAAAACAExd/ycC0YX56I9ciWk470BSfqflqBBNojCAAAAAAAAK4UV3FsEj7vSkNlzQgXuFV2CePG/MZ+PiQB1TbD9MpGyNKZY3tBHxpbs9P6
+AAABRoc4Snq+fEZzAELgQAAAACDEQ43VVSE4/Dc+AYO6YjzKKq2TGlRsrlLaKAAAAAAAANR2rIsGz95va1q2XZR3f4nojbl0VbkyOxcOJH6RcE9G78ObYzNeKxoQqpUt
+AAABRsHUuj+GhUeXAELoIAAAACD4TN3mVUCGR8ZgCHArSCRhliHgGAB4jmpfDgAAAAAAAFT6LbbHJAHPHuQxoSuIgbQRI1gG0UiEUUyqYdzBELf2EbSdYwYPGhq4ISbg
+AAABRvzcylmnUT8iAELwAAAAACBPnOYLHCrQpbbGxCoCOmwjNgEI3yaqfK1nFAAAAAAAAM49vpWijG9EixeE7p9W50QroIG5uMeI8uU9EtqGocy5s6OfYyTRGRrydoGI
+AAABRzZqqJivhGFqAEL34AAAACCrBwNGQURdwcnmEh+NiXxdJocXg0C+EjbKEgAAAAAAAPCpp7drmfaDGCdbp0vvYzj2diZYSYWvM78OB8U+m8JTDJShY0qJHhqXG3Oj
+AAABR3FI7k8lx3ezAEL/wAAAACDJ+0nhS91r27KsgeSqSnIhKw/0dR50kTWZIgAAAAAAAJElxKRcanstkUDhhU/lw1HSMRryB3yyR1/jeBCTvv5TqoWjY53PKRqyH50H
+AAABR6y0Q+PXz3ZcAEMHoAAAACAjuQxw6hXTMt7JcxI6CouhV4p50HR3DKw2FQAAAAAAAM+PCaUkPGQLiLlLt2UryTsvcAPo4PfqnIO5NKGlGCvYq3WlY482IhpkBoqH
+AAABR+jkQXP3VRJgAEMPgAAAACBUQh/GVQMwhVyp2BtRYUTa1vqDwfgMEIHSAQAAAAAAAHbCK+8vnglbdTIxQwUH6smyxZZbltzYVNK9lX/neocqBWanY8aZIRrcObWz
+AAABSCIR7ZJ8Z7zcAEMXYAAAACB0fdTohogAXAH6ucg1oqitz8h9aRJ64agQFwAAAAAAAFGODd+fxrhc8utjklT6LoLTri3RuDCOp7fKBkdChf1EM1apY1+SJxqe6gpT
+AAABSF3W/ujCxn8bAEMfQAAAACAiZR9wSpECxStJfxD62+581x9sgPoA/G6bGQAAAAAAACLFWVoDyL2o20WyjJkXvSS2IM690LiSMqho58TgUtA5/UarY2ioHRpvz4XR
+AAABSJo5Xsn/thrQAEMnIAAAACDKj0KGYxUIK1Mbrrzl8ZvwpE9k637/MmsWFwAAAAAAAFgrBuu/qlxBiPhEs8BzA8ZP38zRb7MBb/wKb+57XwC//DetYxpoIhrt4JAG
+AAABSNRVAzsODJ1YAEMvAAAAACBvdBdkLAgI/WMIJ58Kei70d9StV913tHnrBQAAAAAAAC30XNcnEkhH8XKXKw7A3xCcx3VdbEFqWzM/MexbecM+8ievY21pGRrTJLGN
+AAABSQxjkrXZ3a6IAEM24AAAACCGFuLirnL4qMYPlpUCvriHfv27aCnPeDdsFAAAAAAAALNReZdeYSLoRBeDEVBB+0DKtTjv+bVu2QJQkojY9nL6bRixYwkFHRr5+nSW
+AAABSUURskZauK+lAEM+wAAAACD3XRoLLptqd3WzD+TR+YDLv4yUncF9pm/NEgAAAAAAAGoQb/NSaUbzFJySRmQEe6vWLVQTjlQCbgdtk4bgjgg59gmzY5+LJBpC7Xvw
+AAABSX1+uJ7mDkXxAENGoAAAACCqCZJpoHB0eQ6kbKw0fGYeHy2ukHotiy1ZGgAAAAAAABOe5mmGDSAzb4Tm5r+BZcXZ9DC9M8qF7ggU944pYzekI/u0Y973JxoOMNx0
+AAABSbUvZkl1FtTYAENOgAAAACD8F9Bx3iSIO+ngyqbjgzlj4AlqMB9kpaCKEAAAAAAAALoFCYaZSLwGgACAT3CD+NRu6gtkW8v4gpVx+kuvctjLAOy2Y5S5KRp35ydL
+AAABSe07ZneXjTz3AENWYAAAACCm3f3twLekwVonVMBtovg80pcitz+t5WrrCAAAAAAAADLGvqFdMnqpGF1hajSWr+zHi1GtjVzw+BPMWKMtuaAE39y4Y2CBJBqs8jm0
+AAABSiUs/v4IeTQ5AENeQAAAACAHdfPkJtUOqZQn3X7L8mZRaIzHOeVmCAawGwAAAAAAAJA/qi9SP3lMRF1MOTyKz2xysGpINdC5KQTWctm3AGsqsc26Y6F9Jhq2UxgH
+AAABSl9NfXExQaJ4AENmIAAAACBiIhanN4OOImRYKNzpA768J/UTNp2R2WD8KwAAAAAAAPvm8xW4tNemRqBPrJRdU01DP7+OgIik6rXDip8BfOwftr28Y4T1KRpFNSFL
+AAABSpXjs3B6XtG6AENuAAAAACC9DTpCuVFLTf4puSp5tff4iKpI2J7S2uJrGwAAAAAAAEAh52eOMpTsaTaD84Ip+8XVbdmSyxUPtwDPLj79BirW/q2+Y+W+Khqmp6/E
+AAABSszEbNsAUdlsAEN14AAAACBhh5VTkrTfm0vWMnBnKqTY/CGSwpackt8yHQAAAAAAAFdNxk3BSnItbDvz6TlBEvP/sSxZMYj/sXVwM51FbRBonZ/AYxtYLho+2q7t
+AAABSwhVwl8lSCyrAEN9wAAAACDpRJnIEZZnxuih41WOLVuT+dEM6GvAvZFcEwAAAAAAAC0BlOLGp8Q63lXRMRDDkAPN4NPO6n8i2KRmm9FBzM6n6Y3CYzihHhojJ9H5
+AAABS0O8ibhd00mXAEOFoAAAACC9Pulq2ty7mFOhLBo7nYyUsWP7n6uy4IUiEwAAAAAAAJPQ/yTrcsDW7nubx4V49tIWXeqhlBMugcGqYodTugc7wIDEY9zdJxriZzRk
+AAABS36svj/REOMHAEONgAAAACAEXZD93WyOZWtNgV2mfhoH57O/W5sdfsXuHAAAAAAAACkpa91z0ykC70Iys5lTQg2e/FQkgCu/47hvGT5zUQohv3DGYzqxKhok9GdO
+AAABS7nVnGv5rqRPAEOVYAAAACAW6vV74rkI5Gu0fgnvBKG1Kyqu8VBprxAbEgAAAAAAAHoCYJA/S17rtlzPxKIMwtNs564I4PjwcNYWOnFQ5f9FPWDIY/PwHRozC/GM
+AAABS/X3t5ZmgxNyAEOdQAAAACABJqjvGoh4bWXNIMnaDjJXSEExmJVgNgM3HAAAAAAAAG6IJyZuBFSY0/5oJ/QzHAXQKpbuVENxz56YhhYDythYclDKYwBRIBqfpsGM
+AAABTDEOeY09w5wSAEOlIAAAACDA6Pz4PKihSomCed3FbIbQaVaDhvr1l2qSEgAAAAAAAPcqa9lYAzxswqIBwIMuW87Zwh/MCJAvv8P50htrnesk7EDMY1aDHho7mDrN
+AAABTGoXmpXuQ35dAEOtAAAAACDShTRilltsGnNb/jOBR7cx5AcsZvGiDI8lCwAAAAAAAGutPFLmPkOdUaxrf7gZBQv0DT3rE9kKeB3lmgkmTvSx1zHOY0DFHRpABpvo
+AAABTKh7YOn/7eT3AEO04AAAACAT/dMDyrFINbdDYdFO4Zxrlbbp7yuW9iIJBwAAAAAAAK0hUKZj+AigrL9Hu5V3GRZQ+hzTFDWI1aERlblBPqjtPSLQYzgMHxrWq3Os
+AAABTOUtiDkVzZMcAEO8wAAAACApUAmhb/t99YgDh3d6hueJvmaHGObj1GVGJAAAAAAAADTAUPoluXliQsTLwnDO5zsuzMhPiryNOXkzsaQ6UhM6UxTSY96nMhpRSPVu
+AAABTSGyXNczu/HCAEPEoAAAACDmBDjUvc+ic7/oO/eGHjg2L3N1jSZPo7geHQAAAAAAAG2RAnuyCV2ze9ihWnJAbT/91apdCK3heSvJ8NFZLU+hFQXUYzU8IBpZg6K0
+AAABTWEUrdVUuRc+AEPMgAAAACCBrdX8K/qLCV8IK8q6zbhPRMSsiJ+kv0GWCAAAAAAAAJHhdk8tROY4Vsg9AsRMVVzPU79vWwoXf0wEd81LaGkM7/bVY88YHBptUFx/
+AAABTaHSr3EXgAjZAEPUYAAAACBesIIwA1sH2NSS/BBybDA+jxW57Z47u3exHAAAAAAAAH62qGOe0UPKccTyyiq6u8ka97epxflK0rDiR/WSrh3gWOfXYzkoHxqjT+By
+AAABTd+zAhooMo2fAEPcQAAAACCPZn+rbC/Dy7bSEIE50YOq+rKMOV0vQmKZEgAAAAAAAMruZaBXA2d1csr5eNBuRUxKBArO+Big+aubuBXw6Rmf0dfZY7FyIRpJf1uN
+AAABTh7abA2pDxgSAEPkIAAAACA07vtDW/PHkuCdbhSAWBQ2oK3nGa4TRaoYDgAAAAAAAOJwBFupNMb1tcpFWrwU3QArU5WM9eWBvGfIZG2QQi/PYcrbYwLBLBpmlyFJ
+AAABTlwlUu0M5/ZIAEPsAAAAACDePb7uy6W7+K3Mgtxl5JA5FvVWBsERZSYBLwAAAAAAAN4jAZ47d2FVnSvzmeQS/h3cnKU9LtB5ARA0dlcKP7vxELrdY4xPOhp2iZLk
+AAABTp3Ocnj9M/qGAEPz4AAAACD5qLugRRD8kDfjQpY6xSXxBH5OY6gtXa+sGAAAAAAAAKP3tgGv0ZPc1wRL5dQfx1hsn2XblIKg0FqUeAOYjvO8uqjfY4UoFxqqDBw8
+AAABTtxjZkmLTkGtAEP7wAAAACAEftGDcrUEP3R1Xa9SuxKH7ZHAkK6WgYtvBAAAAAAAAD0TwdHq+79JfbpbJxKenUHYfaf56kL3H/o+P0TUYmhHfpnhY6qbHhqrmG+7
+AAABTxxhuT3q+IjjAEQDoAAAACBQBb7cIMw6oLkMcb+xdkNGNPAnhz09fR67BAAAAAAAAIB6pLvnqAzYEWbB9P5tW8vKqWlLj1s2E8kfjVxP/c8+xIrjYwtrGRoD6bG6
+AAABT1qwy2Y6w3UBAEQLgAAAACD2BD3o0KWTXI+ylrb1i23v9BT7WX3/F8AqAQAAAAAAABrdBvO/nbms5Ae1+LdusUqpOoUgBAQB4yye1LS8ShbLsHvlY61sHhreCg7Z
+AAABT5fq1RAlpEPVAEQTYAAAACAPbptpcImR+EDTDucp5lo995ztSpfJc7usAQAAAAAAAOFAs4uohLU3tJAQ1s5t+5vUYmgXBZmgUWAY7/ZISAn1sGznY4xZJRp7sn8D
+AAABT9b7xG39CEjYAEQbQAAAACBv1YOlyujk4scoFNSvxtXa6R+qscRBLLRpCwAAAAAAAIbw96/bYIDHJKiS/BmAYk+imBB+XmyJwtPYZ48z1+huRlzpYxWnGBoZptq/
+AAABUBalsJOZgNQBAEQjIAAAACCRGMVeBWopX028jf3iAF9MfrU/s9s9O3fPAgAAAAAAAMVxBJzvjZuXjFiErTat3klyl5iZU5QtEOT2gxNt4+tfP0zrYw3bFRorDvWn
+AAABUFauzfvEw6AbAEQrAAAAACCbqWKTSxlqhPfxgH24wH9fR/vt5w7/0g+VHwAAAAAAADOkNl095xCoILylW/kOVpaTZFqmvFjlTjX3VpKI9x4dST7tY1n9JRoKRt/p
+AAABUJUNBPll/R8TAEQy4AAAACD7wFPJ2wtLTn8SLQK00J0LmxQHRvjtoCe2GwAAAAAAAJJrMEEeyb31hrt1VN+WHIPNttNcDX9J54MYO4oSyaI8PS/vY5JOJRqHNF+N
+AAABUNN6XEZG66XyAEQ6wAAAACAFwwSeUt2r26CrGmejy72ZTyAGhKFZGnhOEQAAAAAAAJrjkMkuMQKQROKtX7VDqS8BwA9IQI4RhnH+uTpysKJeVSDxY3GhLBrWmDOF
+AAABURGz1Jgo60CLAERCoAAAACDVpRExorhTmPHsZnpSc/S6W59+j9d7z902EgAAAAAAAADjqT3J432zxsf1duM51vL8Phgcjlrvtec6BiGP6E7q/g/zY+rdFhoOBbTz
+AAABUVJ6q5cTXxSAAERKgAAAACCAeW9st6dh4FZkvWoPgEq2s5pE0or0JctKEgAAAAAAAEEGyLgltc1Z59e5Niv5wFip+DvuGHubVyEH+6C6tvRXqwD1Y4SLHhq2+1DN
+AAABUZRkv0BoZiReAERSYAAAACAnmHFbBS7w1mvN9aUp0G4OaxxakLAy9ccDFwAAAAAAAHyLF+LtAsSLjb4/3o8Xfwj/iZ4tU4L+00I86690l9bRGfH2Y6mTJRqZ3y7G
+AAABUdBrZFylkfaLAERaQAAAACD/Kw219LqH+2BlWQjy9gauXSNOt2XO3+v4EAAAAAAAABOsig07y8Fvuim3UACnxAbxgwH6Wo0PijhByVi/FuOKu+H4Ywk3JhrFveta
+AAABUg+b+msaU7zSAERiIAAAACDlJSBvLqzRbWss9FOuey36PK1B0NDAe0hWBAAAAAAAANBJSGmc5klgUnYueZQaAqZLhE+PdYbIiPXW9Lp4wxTB9dD6Y0E+HxrG4UpE
+AAABUk8bCoVAJfY5AERqAAAAACCOpGRkTGcNvs3HHKZI1Aj/Gg4BZOSwxQOsCQAAAAAAAGaLrYrAFiNGztOxbK2un0THSaD2LrV90Z8sVriqlFbRcML8Y5nxKRqkMzEP
+AAABUowVY6q9z8hBAERx4AAAACACCwFABCRCpuqFNA6Eboy2NOVO+S6htGMmCQAAAAAAAHj3RymA3j8okJS0Hs+vwubcVuxufNZ3ofxxOiYQhWOZKrL+Y+ywGBqAs7Pk
+AAABUstAPRH5N6MNAER5wAAAACDINrEVdmMKSk8dUeGoyAkV7lhieXk1YvEZCQAAAAAAABgyPfyS5RnpnjxbQR2/c9xks9UXNSMxLhUxgi4ic3ey0KQAZK9mIBo0ppYE
+AAABUwzzyOewJ4maAESBoAAAACBlyfyBXc6IYrCbNEOQ+gSMMwS8XoHxVIoLCQAAAAAAAM3rCLZQ6OUVGvdd074T5+6mDFTKM6teDcjQdx7H6s3905YCZITEIxpbODuc
+AAABU02MVyIQKyCKAESJgAAAACBh13tE0RRaLYWK1y9qq0h/j/G9gGOYAH94EQAAAAAAABhwEOFVkodu68GiW01BZhkZ2fxukTjvCQqQZNDC3m8fm4YEZF1pJhq9OJcp
+AAABU4rH/dppvPg8AESRYAAAACBsqeoUQfd067gSjE/5u0Z9I6zljD1gJ2O0BgAAAAAAAOY03SoCj+mGOhDull6MCup7XcMxM8vpnkqfPhMtzFZ/EHcGZOG8IxolL4Iz
+AAABU8qx2O/j7cMOAESZQAAAACBp+Zp5w48NKwY8WAuNNTwAWTIV9F3sndpUDgAAAAAAAMXkXdUGc6m85FJYmDdy43hRTyBCwfaIUt2qL3DGFeuPxWcIZL5EJhqAuNn7
+AAABVAetl2QLv1S/AEShIAAAACC+jxQ88rcstOSHodUnpsLvGJaXaunJ104VEgAAAAAAAL2u2CwtM8zjoNZomlKoaDCJPh+TcY1C3hvTAKGIYLkkVVkKZPR3KhqjpUWn
+AAABVEJhNHtUOWCgAESpAAAAACAJP8gzHFrUnJ9z2BHrafcaPf2qX6IDj6ltCAAAAAAAAJ2Eo3Bp3vXB69gq0oJd9Po8378yasCZ54J04qveyGDVukkMZPIGIhrScfkl
+AAABVIESEjESJ7cHAESw4AAAACAFRCEkRnuHPeloRCOAvg01b5vdZ8nOb28HDQAAAAAAAJJPiPA7PR6LQERfI+zzJ8Rn2Mw0PFOKdczX9/yOkFCLmTkOZEtdJBqFxSaK
+AAABVL6NVyNHjqkAAES4wAAAACAgX6KHTIgcGzzDkHgX2rzV3K3gctW/74o8EAAAAAAAABqRAER0juGPCO9ygdDJomGlH77zkkpEd9ZcweudMsvLNykQZPgsHho+GZiQ
+AAABVPnxDeuttp+5AETAoAAAACAsyBx55tYaDF/hrH2wQedM/QJnFn8glw95KgAAAAAAAGapJIyTNzWRBCRf9xmR81yMH5b8vrHFDpPPmp1yK6p1yhoSZB8ILhqNv240
+AAABVThNewuaEL10AETIgAAAACCTHe2/Fesx+1Ue/PEryHZTRO/K2AGElmBmDwAAAAAAAHDMVDHZBwP8WUfQAH7q9wyrBUncms1+VElbRHVgNRrw+AkUZNVMFxrhVU8V
+AAABVXdCemWZHF1PAETQYAAAACDpX0ULJBfiHcUzgMD5+45bR37OHfXeI9iOAgAAAAAAAJ9auaweYzAaTLerO2z0HA+dtXUgPwQYolU/DZb6dvMFb/oVZH6SGBpf9dh0
+AAABVbko0vRNsUgMAETYQAAAACDntcj57MGd1SYCvnjVC9ZKVHCYi+42DgTuHwAAAAAAABfJDdoseKI3i5SKvEcgXcnPuQjFsFvrRQ8ybfev4yiycusXZNTFHRokpe4D
+AAABVfcnWsjXqBP1AETgIAAAACDJybztJirkrkB0bayStR5Xys4mDRt1KjB2DQAAAAAAAG7yRq3zIWMDu4RsqB5OCfSJjpSP/F1J77iV3Pzuu/UDOdsZZPtMGRp/RAv/
+AAABVjZpMvQR0LxoAEToAAAAACC6TrXs9/xMQIRU52Fp/z+Ulq1LlFYAJrY8IwAAAAAAAHBxjM6JxBSaBsFA1OtSKWuDfP80dT0PXe1lhtHey3sG8swbZIYdIxrgV1Qy
+AAABVnQJ3HI1E22TAETv4AAAACAN0lQtlLjIBEgIa5Wf/ide91i8za8OviQnGwAAAAAAAHWsLgOX+wO/VDMXpswlG+leNWcFIJ6akmOAU87vya2kMr4dZFv2Jxp4Rnk0
+AAABVrLvWFAr4ztmAET3wAAAACCdu8hWndjEUxrFXlau90u/wAzOEd4vDiWPDwAAAAAAALVw/LxzgY0TN1yRbz2vjjK0LdG+rP54BoC6Ff5z0hbgma4fZCoSJho0NE2R
+AAABVu0vTM4rAq55AET/oAAAACDz2wFOHxc7BsuStyewHsMp+Ujm7E45HtUNCwAAAAAAAK2tWvAU/O1mxkDtxoJtNMw43RyL24S00c4d4Fu9agaWb50hZNUzFRqZ9++A
+AAABVy2ejEbNzKI9AEUHgAAAACASDnwpY2OYTMi3EaAgdmi27mBbT6SzuEElFgAAAAAAABKTIWSf21eTniXi19MeR0cZxn6A8vmuaSmqb2Q/B1q+EJIjZGofNBoLBbxC
+AAABV23O52IGF906AEUPYAAAACBrNeex3av5445/URjaA+iGcucFR/mI7IewGAAAAAAAAAKIQPIunKdRTnTotAp16LbJMEIl8TML8XwmURCa2CyFEYElZCWDHRqNALuh
+AAABV6tNdpVXLOfSAEUXQAAAACCw6/uYRDHzYVaM5eLIWhcQYBpe66CJF/6BHQAAAAAAAOeFmZKW8J9o0yVcS65V/Dl2iEHlRmdklxILMi14vymXX3EnZPa5GxrHySEo
+AAABV+kUYbvwzDn2AEUfIAAAACCRgDzzrZCefckPF/gkM1HZLgsLVi90NAVrFwAAAAAAAPH5EX8kz3cnhTgzFBWXNes/g8TUxIrLx6PATltIM/HH5mEpZIEJGhq6vze/
+AAABWCg3ixQYAUaqAEUnAAAAACA8Dkh208A+GEjjOzwajdXyX9w2QCqy+3gSBQAAAAAAAL5xGTq7rSGySLhH+fYZG/qzPkraNHOhE8X3wNQqybbdE1QrZGCvJBrBT9no
+AAABWGTubSnv+aH8AEUu4AAAACBK/bqPOTPLjRQhfSWtJHQKKRuoi8MkJaRLHwAAAAAAALUonhKDJvjz05il9TKPLm12arIA2cUlSDLRQTXFw/xglEItZKnWJhpdTUAy
+AAABWKMp2EfYdcsjAEU2wAAAACBsGvoz7iUSnF2uhhROEmMJ2dv0jtqXapGFFQAAAAAAAIPfPq2495XOCeUp28pkTGwamIaNWWm+02iYV83zF8NPoTMvZMrQJxp62dKG
+AAABWN/+GOgh+XltAEU+oAAAACAsXAXX6OYX2qAaGNpsbW96e80Z1qIT6Lh+JQAAAAAAAAbnvxGndDAJ9yRGEpqbQkTWt7m+swIJEW2p/aBTzd00KiMxZDU6KhqKf8Ys
+AAABWR4YjRfuIpr0AEVGgAAAACAFPPpg4qehEcuqrVwvFMu5zQLdd00TlXj3CwAAAAAAAEkx9hQlq474ZE4i17qqWLgI6ZJd3cJ72YUi+Yokt3B1qBMzZEc/LxqYts2B
+AAABWVz6uqznZKWWAEVOYAAAACAOUr9WpPZgYeC2dn06SQ7Mkc1gvtJWc+C2BwAAAAAAAMiepbFE3oZc8g9ZDyeoMspPDy7cqnCQGUAeJIVkjYaYRwM1ZAkqHxr8zMtc
+AAABWZswfzm+t7mcAEVWQAAAACBXHApxVxhkiHWeRHhjTibQGzdjdYHfvJGbGgAAAAAAAB6MPov7ns9wjWEIniMVGtsQgeJV5LY/lutZyYa1PhMiy/I2ZO7RGhqmnpIb
+AAABWdh0cH95qe0+AEVeIAAAACBRGxFsY/mMlb/mouzByjGUm+SfJLENuizVDQAAAAAAALFJteO0mUZb9oEKYlAlb9Z6M3biorJ0gjRQt3DHxuDPFeQ4ZE90HBriFBh5
+AAABWhbKIPQ8xOLVAEVmAAAAACC28yT2nqut098ePfmtutW3by6MVXj+3ViuBAAAAAAAAKdzPlmzMYmcndEVpLhexEaAgXOBtG2mXVYyeals6474XdQ6ZIDOGBqRKsVG
+AAABWlUrXiq1gWWrAEVt4AAAACDHrPz/rixppN2hEZRCyQ2UCTuoIoH9taH1BQAAAAAAAPbkzIvUc685d90yHQwPr3Avoa5C/PSiVQOJZDjtmXvM9MU8ZOfeHxrJRGRr
+AAABWpOuf03l6aUUAEV1wAAAACCB0FihyIJakLGjd3MCezIKaa1x8YgbMocpEgAAAAAAAD1kZ8QcVgox2BgT2mIFKZpWPSwQjCbs7AM3uHnSuGvLqbY+ZLHfHhqDLVdk
+AAABWtJqGOlnYt7GAEV9oAAAACAjhQ8KhjeRSxBIPST7hPzqYvcwBhnPazEyFAAAAAAAANkiARiUSblKr75k39Z77eabwXRNUMPt9BOmqukvXrNNYqZAZFZcJBpTZE77
+AAABWxCGfqLSB5JUAEWFgAAAACA/qPmVMhTIshJq1I8se9ocjUDifKrOm9nkHwAAAAAAAFHCt5QXcBs5IP2bjULYvAffDKPVOqsNqyXAz2azkM4erJdCZK2vIxpjdCgx
+AAABW0y5vad+AFFCAEWNYAAAACC64LhlrZGco8POBZ9z4NoFfnqtH6YmsZNHAAAAAAAAACO87i3vR2fk8CMvBko+yWSuL4yWDE9F+isRbKsnwroQdYdEZMV1IxrDmyq/
+AAABW4nKpk0kAr//AEWVQAAAACBibmAF1WmVgLdPOpXp2OdRvWqPPJLUFHTPAQAAAAAAAHUHWff81gBymSjA+6psSWbQbY3y65WKGbCsedPnkfC3NHZGZKJHFRovw8nJ
+AAABW8V7mLlQUSQmAEWdIAAAACDR9ZebC6+vxv48XS4QXd4czXkCWnKfqv89GAAAAAAAAKkZPALckCXSrJ+brEXTPuwbmzNzTtlcy/IHWe7Z1RxkpGhIZAfSIxqd6Yrj
+AAABXAZtzbeUw1W4AEWlAAAAACCKFLb3c1AMw3cOeVBF31Hc3vTC0ll7ZWKmIgAAAAAAAM2zC7ebCFol+mt406MqBzZjSQ8Z3gI4t2iuGy8/dx5fmFlKZDCOJBraOjCL
+AAABXEVEdxrMIdJaAEWs4AAAACAQCSO2S9p0pc0wW9UnESSuKg/yJTkBo4PsAgAAAAAAAJ/btKWUEhwjyHejWlesPXADX4mzN19ASxfsXxrRUxslaEpMZNJQJhqZgt2C
+AAABXITkYE7p/V1AAEW0wAAAACA9eOz3qVLuv67ugHhVbyMWixbeVZCv2o+nIAAAAAAAABOkJeTI8KVeh4D0x5QppehGZuR/tLfNkAT/JhEKG3hB+jtOZFENKRqusDr1
+AAABXMVCYbM52ZDOAEW8oAAAACAymLGuQyjgfjcBtMbUpolkGmyj8i4Bm53jEwAAAAAAANHV5wX0wRtoBp0GM8Dg5/ZOQBHIj9J/tw/MVxMjuhXSsStQZPkgHxruOvXz
+AAABXQQcxWW5ig5eAEXEgAAAACCSf08itWvl8Fu3joh0pkmNGzmRp2/4fMplIAAAAAAAAM1rX1ao4k8YpXZOpBQWu9X2SA1SsLl13ly3Rxad8MvNZx1SZF61IRo4AFKG
+AAABXUM/iU+RRle4AEXMYAAAACC7UeUpJjJ3paMaMDbR1xaHfziKbd4CcgCsGgAAAAAAAAZ2np46ChCtX8orwP+QDANCfSj6h0vL7Q+NtJR25m/pqwxUZOC1HRrObvmT
+AAABXYJ4Ns5xf1LXAEXUQAAAACDDgaC1shwAJgxf/0Rh7ewsFPlBDFKuh0lbCAAAAAAAANH+NOoqsHBq1EbBUmyv+ldUv8+hDQ7SjawuqEfYO9uIAP5VZD+7JhrrooM9
+AAABXcDw2VAtKb36AEXcIAAAACBx1ICY/NpB3aKdS2M8EYQ7zMuFFiNVjSaABgAAAAAAAC0e9f9viIiJUJzyA9FkwWOeqFSRfjdT4nyKxDYnZUUK+O1XZOl0JRrSIbyP
+AAABXf4igEwiNGJAAEXkAAAAACC8LD32UITGljbo1KryMhsQV3ANEBQfrnmfIQAAAAAAAFIR8GCzYL4ksgrg8+ogbZeo3WH+G10+q84voX1hiAFZmt9ZZFLaKxqxqjrZ
+AAABXjrTAyRxyjkeAEXr4AAAACC6vGifbEjToJnf5J70HmlicjB/F03Ri5fTGQAAAAAAAAw3FBZ/lQq/FevH44GcFdO7prrjxtbB8sqPeg/sjrOVjM5bZF0/HxrWgwt5
+AAABXncwkSoIgDeMAEXzwAAAACD/foQ0XP5nUAd9opTP6vAmgYo7Y7T8SiqxFQAAAAAAADvpXwIVMdtODnO7sGjoatczrR2A72aKi44p0q5xR7FY77tdZHefFxoiwKbW
+AAABXrMIj6iszdhuAEX7oAAAACCLIMaVr1EQ02VFOMz7ofl2nbVOC2NQs0hZDgAAAAAAAOvt7xc5QYJxXYMNjU+nCXe5xW6gjC/dujNbp8wkqxDIcq1fZNCHHxr+qEMn
+AAABXu5fjeR/PfIUAEYDgAAAACCPOY5xCDo0NjsQAnEKUnP4m1t1iOplx4YwAgAAAAAAAH/DBBZs5pnUX5TVBZkP6MrOXANjrW6yTcAENfZF7SSvSp1hZDoAGxpBMSAK
+AAABXykCNdv+4SzjAEYLYAAAACDZo2dzBcBZ+gDvMOWEObx6breylysBtwn1AgAAAAAAAE/E1/tPBuWFtwaUwhRu89A9Cnm0jMDpH1QekaDCV5yoDpBjZJBKLRrobaO+
+AAABX2QqqpPjpJ3sAEYTQAAAACDupEKshgAsMdX/LSeHVULQxJg3Uf8muGhnEQAAAAAAALa+dThftdyfOfSNVEPrUKNav+xSZcUSffJYUpLFm/ymNIBlZD9eKhorNKfy
+AAABX541gkd7TN9EAEYbIAAAACDDIHtLWcNhRh0K3vVHU1qhLNPOah3wn5EYAQAAAAAAALwiIH5qGTSi6ExrU1OxXcrMJ1vUH9SKzk1FlVB/LcM5kG9nZEj4JBoHFnqU
+AAABX9h64i3FarbxAEYjAAAAACCEmQj7WCr22wQy6AhStJJ3wZ/NQFxHKN1XFgAAAAAAANlebUB/Je1HVj4Ff7KQDwCP3jHUBJvZfgx80C5eriWEG2BpZMfsHRq5R3ho
+AAABYBIQkMie3ibsAEYq4AAAACCRYGFOnHj6e5SqQ3HQVJLMcVd/pUHuxn8aEgAAAAAAACW0eOUngjuMN5rJSY0oyXaylSMUFFLFlvaokP6sXkDVXFFrZNAHJxrRHP5P
+AAABYE6Jea+AUz/vAEYywAAAACBCOJCulPFAPeSOui+vIO4rK1ukHqjs68IWIAAAAAAAAO3WlSIeNdSk8vD38fcBAXem1NXiFP37T6HsJqx3ztcRGkJtZM3LLRrR1o4n
+AAABYIpIG/7jLei7AEY6oAAAACCc7Fn2Dq5AE/UIGTmZmEEqmvaRQzgtQUyoGgAAAAAAAIXs2gqPyLgizPPVWeMOSyrlHEj8Omdehs9eD1RmkdmGNTFvZNm4IBrpwrW3
+AAABYMU92z81g48dAEZCgAAAACAbXGkyA3NobLvaiBc+7156rEKquHKpYXpEHAAAAAAAALIRv33lTSVnt+MC6ifSiUO9nB6c28u0mYVxBhW1AyIvGCNxZHJsJRqw8K8s
+AAABYQBcowI+w37jAEZKYAAAACCso0PNoAAF/dpqndBcAsKZ4OwJWYMXxD8lAwAAAAAAAHlGBGFCJdEb6WrA0lF5tHdscpsZQ37GTdHAjnMqfYYw0hFzZBUYHRrgPbSf
+AAABYTse+umA+iijAEZSQAAAACA43O0Pno2Gzc9QLf//ozk1K3kb+cR+Z8G/EwAAAAAAAARWnJ07i5jyOKC3k8qcz6ejTmx1SSAVSVhUHE6uZc02OQN1ZDOyIBrRfUY3
+AAABYXVLvHFmrAupAEZaIAAAACDAHQiwvA1szRZb924FLdamUNmbSWEzEVA0DQAAAAAAAAFd6CGdOxtSGBFE7+HdYumosL0Kuj72bbNZIyhP//7KqvN2ZGd+GhoblI44
+AAABYbCBIJgdEZ2mAEZiAAAAACCB/gJRGXSCoip+FX+dCHG+zfuWSgHxf7EBFwAAAAAAAPPc1g1ZlsjBO+1eT2gqYpZhV2MlbWo/YXyz/1df55jXH+R4ZCpYIBoxHRE3
+AAABYefRWxCHjpl4AEZp4AAAACC/NJt5s0R4v0D5PxdIgpFablrLZvsWfx+pHwAAAAAAAERojK6Qj3rzNH9ggtg+CxuD4+GkpLwRxUGcZzXtUTxsotR6ZBrvJBprfxaE
+AAABYiEqWxB1r6cNAEZxwAAAACBnBssldNnnL++DBX7wPztJa/SMOB2taAd+DgAAAAAAANy5m1GTW+cUXJDdOvIxLlwnqFe3FuLKQ+prfWOabNDh2MR8ZCzzJhr8mvMd
+AAABYlpey025baRHAEZ5oAAAACA9TZqNsG+cmm0iu/s22FeMRxw2agjFORHMHQAAAAAAALuuvR1Gv69we6MCZRGExRGLF5OvkqQtdVnj1lMYaW9CKLR+ZMNKHBoEn2dc
+AAABYpBjzdYBtiJRAEaBgAAAACDfao+A9tIYVFQ4QSOJ0VOYvMza8vMJBd4FFwAAAAAAAAUihg+hP3noKyCRqZoApiDwWXy9Yrz+wqcuuNsyLpFbGqSAZO8RHxrWsq5b
+AAABYscCN0KYXYKGAEaJYAAAACDNKzTOENPPR43EYghmwKmw1bAfn2NgziXqDgAAAAAAAJODG9wapFpXsM1Oy6GxuwX9adB4RrLyhlpufpWWh7dcyZWCZDxDORqzfYFz
+AAABYwDNPfO9Z7RiAEaRQAAAACD0Zi1Qz9AEuSr0qIT8MG3utYpeLpk4sOllEgAAAAAAAO9eUOYUFIdAWCdyF5rsbPdlJIC4dDaZmM4ROWszF7BHMISEZPtJHRrvKjW/
+AAABYzta6Dj7NIS0AEaZIAAAACBlLCsO5QSU1qYFPYY4E5iELsfdlTLQs1TSHAAAAAAAAEHHxKqTSOJlDMjW2PdMtMCpzvltibzVNwv4RLqq93rMGnWGZEI8LRqnuPfj
+AAABY3V5G0KYNuDPAEahAAAAACDkLNdIFscTaMwuJ96g1QsF43TB3ILm93j5EAAAAAAAAGHCvjR4KdahZo+ouQU2BJPV0BwgTgcTdWLzrgepogHHsmaIZBNPJxpsw5Mq
+AAABY7LZxdcSNdWpAEao4AAAACDdZpLFsoZfIwkYRC1d4SE61A8OK/KyWTv9JQAAAAAAAKJqane1Q0OImn6VQNpjAyIjAgoRWYVrIvNdKFmxkvoDu1aKZKVJJxqK//Dj
+AAABY+tjjd9n33xFAEawwAAAACBH11YpHw7twnV6wy0BnqflOO9YYglh+/vIFQAAAAAAAExukdriwDb9dcaOmn7NNsePlYVt9gl0IsEUMg1gJXy8u0eMZI2dIhrYFTAn
+AAABZB3TDG/mQVd4AEa4oAAAACDDTVzfMTHtag5ot5nhr7m3DtidVN6wcUL1FwAAAAAAAFRjT0jTWHaicMbrOG70THKcXoHuyOLl1brsjO01K3KLvTiOZG4dJBqMYdX4
+AAABZFdWTW8qCEehAEbAgAAAACDefodDeyxsuqz9KNgwJO+WUGqWjcLO38muGQAAAAAAADiL9ZKqtW7PJPZ8drFD6au9eKjOA/igcQBHU4vQX5rmmSmQZJLLKRoioxZI
+AAABZJGHMupnNhOUAEbIYAAAACBnX3Tr5Xdk7V/ECwNddKmgwhRuTC9D0kjIGwAAAAAAAM6g1wJKnmINsIeoRgu3Wdg2RvMAxiQulX/qZlP6E5PiThqSZCmoIBoK5bbf
+AAABZMywhb74dBW2AEbQQAAAACCFiB+7W1YV3ZX8xljjtwksIFPN0YzYci3TGQAAAAAAAIJC4S78t73Mxp42tMarkk+vaQFZSPP6dDO4OGojRPA9QQuUZGC+KRoBPUw0
+AAABZQchqyJQZC8vAEbYIAAAACA1Jza7MbdtNoIo65cBMqFtuSqBTF1Es2nCCgAAAAAAADqzyBAUajfmOgOtRfUM/EoDzCtXcbtq4ZCrP1SA3Lr9//mVZCp3HBpAHbfp
+AAABZUGnuGYgqu0IAEbgAAAAACCaKSxtTnztxQjrmr1YzyXhk3LTcroHfwKJHgAAAAAAAEnvoTYFs72JdAFJW9RnG7JNlHl4Bi4ABqgXMZjAqi9sUOyXZDrHLRrjKbX/
+AAABZXvIlnhAlgbXAEbn4AAAACBpHaNyEknlCuVBSOVBm00gtPwn18BnKpkPEwAAAAAAAAn7ZfrQnpORY0xR74ljvxEAcmHE+3dXCihNo0goe57B3tyZZG21IRr3cE6Z
+AAABZbZikJ0NGnc3AEbvwAAAACBKhgS0S+PcInFlJq+xwKqIwyHkY054eQvVEQAAAAAAAJlQmR9sUtD5PWw4wQDM/EFQAgOlsyfJTAftKM3oWf9p/sybZMCTHxoznbFW
+AAABZe8ySO0UyBLiAEb3oAAAACAk2vTqqZNpjZiaUHz9iJaisNq0m1kVIDnRFAAAAAAAANLFrR8F2cRTnMD3mv8OFHSLd2Llc4bwLW7tbc1Ed81IuL2dZI74HRrFIObL
+AAABZitJzzkgZzlCAEb/gAAAACDyIoDreSp0OCiMbwaawvBs+tsIbQ8X59ycFwAAAAAAAB0SNr+KP0aBKEWBty7S3KvJPYXZGztGc+BLKl2H2BIREa6fZAwSGRoneySP
+AAABZmc9l8+N2NVXAEcHYAAAACDTDtsPi3raaFTaeebuNjDQabQ0QHAnbmhCHgAAAAAAAEi1uznQlYBo1liYKO4nbfaoMDVSlL66H7XwKA3BsjVQlp+hZDMfIhrJQ1g2
+AAABZqII734NSKiwAEcPQAAAACBCOFvOS+l7OIkofbezpYcn3IyTT9hVD8klFgAAAAAAAO2BcEcYV6FeBkwgyliU7SVoXMT+jdyIrVcQymePggWxgpCjZCjlLBoS5cde
+AAABZtuA7zmFwd0IAEcXIAAAACBVlsqExVQ9FUovBcuMyWEYj6NwF0rlcahjEgAAAAAAAJvsClFXqOnyH8MtuNGNyjf9U5Gs3wqCd4bx/DCH/PPCoIClZAKFIho+cfmZ
+AAABZxYykYv3bZlyAEcfAAAAACAkJ+15UPRciO92Q7yh12N7h101/jptzJdKCQAAAAAAAIRSi3pnBsoRPkAoppjcjjjfCpjxMEUUxjfzHXUCFwRuoXKnZH33KxoIdb58
+AAABZ09AL5pZ14RPAEcm4AAAACAWKYI1t0vkx4LXSBs2wOoF749Lz5h5qLgtGgAAAAAAAO+04ULUKyN5j0SVFYZJk0wUbLwqoPlnzXOVHJqseHOHGmKpZH58IBoSpLQa
+AAABZ4nJhzvpYiDvAEcuwAAAACBdWSXPUmh3alRgINblRY81HHj+i6C8tIadJgAAAAAAADe9I9LGDoLOpn16R5YiNl4wQTT/F/AM311CnaC4MwfWmVKrZDhHKBorJJ/W
+AAABZ8HY2IXkUyW2AEc2oAAAACCcNjkT5yuPrl9vCc9QeqMKLKwj2/VeTDAlJAAAAAAAAD1OWJ/w95cMZJQcKlnA7P6wZ9XDJ12I5yK5xbxmAW2XckKtZJvGLhqEoGqq
+AAABZ/uxEdkeqZwKAEc+gAAAACDtkvI9b/FeU/0KXu7kNI87inGQS7Bxhuz7FwAAAAAAAP40+VxHTywe+bVAkFMrHAUUF8L8VkscBUqs6mqQIDoyuzSvZIIKJBrSyRFp
+AAABaDTooYozJezAAEdGYAAAACDWC/CUeXAyWrHy99tRAZqw2qQ6ABWLHy/gEwAAAAAAAKgwf1Ofym4bLlPbq/yZquqkzMn8pKBCeqVNMMcZBOWe5yKxZLijExr9Yq8w
+AAABaG/jfM1e8QGEAEdOQAAAACCWyCS3T5eexzjo6qtM20Wsfy5V4PgvLtiJHQAAAAAAAFV/pWFS4Luf8D+W88Suo+S/dREQPPb/GaNScC2ihGD2UhWzZHcwIxqKZ1cS
+AAABaKiy2Ey5s+WcAEdWIAAAACAtX83hzqtbBYxYE3+2vxRUB+JGeCX//MjqCgAAAAAAALeGuI0DfZOdHD8fppH9QKvgbicuK90Iw3ekAOXiY81G8wS1ZCRQKxo+N92e
+AAABaNyQWRPX0yoQAEdeAAAAACACtFGcHj9tGqR4dqRPa0oMb1AB2W2UL8KcAwAAAAAAAKGn7fpR0g0Zv4huHnwYuzFXG37NAoxIjAnD8IibxotoOfS2ZGIxGxpD+joH
diff --git a/wallet/assets/checkpoints.txt b/wallet/assets/checkpoints.txt
index d6994fa140..ef355a1635 100644
--- a/wallet/assets/checkpoints.txt
+++ b/wallet/assets/checkpoints.txt
@@ -1,6 +1,6 @@
TXT CHECKPOINTS 1
0
-1348
+1636
AAAAAAAAAAjLUjixAAAH4CMAACBkxXat0ABSalga0LV9hrH8LJ5ZPjBZ9POhKN3f9QAAAGSlj6VF6+3Et/y3c6jH5ydO2bUSmHv7GUo2mk3nfXwMDzSOWP//AB7SMUQA
AAAAAAAAABD+pHlkAAAPwCMAACBSzOr777B6wMQUL3mZRu2kB89cjAZ0u4WDMSD8PQAAAOjaN9YJl3SFx5HcsSYiMhFD6SD0dVh65ApdyO0WuFaeXDKQWCnjAB5sinoB
AAAAAAAAABlmUiPIAAAXoAAAACCEh/dNLkQGWRM/VxnAVEFW1TSeAjJq53vOqJaWfgAAAI34WCV2wAb6j2/w9nM3qFMvPkBU7woj+6BLJ82dR7xrnjOSWP//AB7mHR8A
@@ -1349,3 +1349,291 @@ AAAAAAAAP7pG12RnAClf4AAAACBFqM+ndniV/B24F0T6dC3tDoh3Nwiemtvw3Y9mPQAAAPb3BEm+h+w5
AAAAAAAAP8ZxHNohAClnwAAAACCa1GTFuxcankwkFGU7nqy8dqrdbgfGTW5BuhCLBwAAAB/dLHOPBWZehHZNXc96iSL5qSizh98Puzu4Id/NMvB1tam6Yhjzfh0AqgNK
AAAAAAAAP9Iu+JGBAClvoAAAACBnZfat32HgN08f11qydRjaS8vJCQcjxlugLx7LGAAAAPylnvM+XzFpQVD/QdibdCRhUDfDitbOKwO/F4LRxqH5uW28YpOjAB7s7esA
AAAAAAAAP92pbTjUACl3gAAAACBkxOmOIeQC0hEyRunRUl5vf7JIHx4zF+hXe50dVgAAALEgFjQ1Q7bX/bvOPJWmPE/ddtghE+h1Kz9H45Arr1eqeDC+Ylm+AB4AspXv
+AAAAAAAAP+hXEvZbACl/YAAAACAQdm9853oqus+NLn3BT+LSaEG5JQjBGfdUhk2CrAAAAJMwaj7Np8nA13JtNQr9X0pwb5xR68Z1m+nmbrGeN8pDZfu/YlvSAB5LRewA
+AAAAAAAAP/NzXD8PACmHQAAAACBDN/c7OcRS3X3kOLC1jAUiy1ZGgpM4xpZUKnB9IgAAABygjXDuMxIlfqOVpZfFbANfb2LDHlvuj/c/x6kUzARknsDBYjabAB4Acqk4
+AAAAAAAAP/7UeFwBACmPIAAAACBV7+ec6a/o6yIhaTn8rF7rOW82WChHGRpGw2pPUwAAAKk6luOymArpNsMNL0wls1g8WUO/aB2urYgB4ehlvQHr6YbDYv//AB4AGmyz
+AAAAAAAAQApeGDKzACmXAAAAACCU3EcGSpQbg5S8TeUzgISB3VC/8lwSINq5pYIyhgAAAEv1N5Gddwu9PlC+bZMLcWsDwjKcVyfc+V9eTnAF/OFzNknFYjyKAB4AGKvI
+AAAAAAAAQBX8L3K3ACme4AAAACB9qQpfGoIcvcE8qhVOClUgdlHBV6D7OUn0HvDL9wAAAAoTLEVGSEu6xUa+7SG2one/88867Uy/2ecBA8Q37WtVlAvHYmfHAB6q3ocA
+AAAAAAAAQCG8UVMFACmmwAAAACBo9OAu3B2jqJqwMSY1+Y616NZllXQ2Ac8d3j94XQAAAEGUKjAg3oc/oSbPlI6UbAvGbFPSo6J6+fv7jF/vq5VyQ8rIYriFAB4AhwF6
+AAAAAAAAQC0biJW3ACmuoAAAACBUOdp1VEGEtIrLUUqFcVKaZJlA18qZTJtkzxvVdwAAAB9+tqeCsEB+VIDLEJdfW8eljTADCy5rfMfH1Osj2ntuNo/KYmLEAB4Ah4f0
+AAAAAAAAQDh3Z88PACm2gAAAACB1L7lBt0qLFvQoqE6bhDpnoaYCl4khDwEyVcqRSAAAALUuvWwpOQdcTK2gWp8bwKppx3Mf47wO66vwDBRyM3iEzVLMYlu1AB4AWiwS
+AAAAAAAAQEO/X0JyACm+YAAAACDDvXKsqcTNRKjZzZAKK1piHNw+eytmn1JKShonXgAAAOD4LBPjtWqs3FR7DOsztT3bFwZAfG0CyxaGTH1iQPqp4BfOYhChbR0AUJmT
+AAAAAAAAQE+nibFuACnGQAAAACDnaRbhCPzkZNyUq5DeUDo6AWPoHU003G9la2MXeAAAAES01ZhETcPP+0yxhHX5KU4KvdI/Fr20KlRSyQwVDms2JNfPYv//AB4AFxKv
+AAAAAAAAQFsawu0eACnOIAAAACAVEZVxHZADVpMWlyExbuldBMebwhilD5VIc1d+cQAAAP202yQi/twESkDdlQdL5vL2J9YOdLcUmWet622ifAY78J3RYnWNAB4AAfHV
+AAAAAAAAQGbHkA6hACnWAAAAACBYrXZEStOfqmTf/EdeXOKN2wANYtqP0DOsL9eqTgAAAP7cz2/RPOHy5oNn59Xo1UbMXYZCg47zMoIIg/iOwb3Bx2DTYmmeAB4BShxx
+AAAAAAAAQHLJ0V/9ACnd4AAAACBABf/gMK5+rJCmbMd9137FQEQMQBDN6Ik0hVwaFAAAAMw5JrzL005ixGtLLCU1bXQ6QE+qIktA+8Dar8ety9YI1CDVYjyeAB69ul0A
+AAAAAAAAQH6G/7dtACnlwAAAACDcfFi3NhfV+cTn1zH3ZXpCYWZjPUWdZ0KLwmamMwAAAK44sqdgwgZCuXZu6mCIRYhjUkC5/lretqfLt2A3IxP8KuPWYnrVAB4ANiee
+AAAAAAAAQIpscM7/ACntoAAAACBS6tLetp7wuPA3U5Ka+M7mzAJ8x4ZM3BGvaPfddwAAAEAf+CDsI1L9EuahGb3W++QjQbo8Xu5D+elsUMMPtw6zO57YYv//AB4AFdYB
+AAAAAAAAQJZDCSQPACn1gAAAACCjCHGRSBXCuP9Rp9/ByWxJr1f/uBTNgKtzxFZ5qAAAAN+VvmdVgLXRvJE2FGjuwoMtP/OlMgHc7nDcEg/gyV7DClzaYijKAB4AEYsU
+AAAAAAAAQKJ9NHPzACn9YAAAACA4bQrSpTwP7ISC/sfcRiMviXj1gYoRkQYvtlijXwAAADVnAwLnmO6KAeYJ6oM8J4KbslY1nwfUoBN2/95HRA1d5xTcYv//AB4AIbsf
+AAAAAAAAQK5zfDlAACoFQAAAACCVrFG40RiZRlvt8JNb4DiLKl1aBbgslnsjXd6DNwAAAFpNBNYSGUxjuKHUCGr6k7M/UwLAOk2Mea9qm8KipoBh29DdYpKRZB0APQVD
+AAAAAAAAQLqbwO69ACoNIAAAACC+KAHuTQDgJnzKPMZsisNS/7zt7HFzbnmNUd7dDQAAAO/mt4i4jG5VtAB60YFjwIVfFVvm/mKLcR+gD3RAh8Xr9YzfYhqeAB4AX8MB
+AAAAAAAAQMbHLgBpACoVAAAAACDNll935fB86LGjv+MCA8oS0sBeO3RY9dwvAVZhIgAAAHbGGWm/EX4Wn0gfLD3KVE0vs4s3C35J7MUyd91CzHbmJ0jhYv//AB4Adfw1
+AAAAAAAAQNLkzg/iACoc4AAAACD2Kg6ygbYfha7Ra15KqTviVVQDFAMU8QCDUveyVQAAACGb9YQqiMqvN1e4sGcnqee5zq5qssvIC1FbDMQmQdSKkQHjYuHEAB4AUpac
+AAAAAAAAQN7ruK8RACokwAAAACA8M6PHBX6nYGIm56FcXmoHLHkqg2y5TZbugi1wRwAAAGyprl5YzXn2ERrJUhWnVZbAlwy67shR+Z9ft4OCouTEQL3kYp2yAB4Ak05j
+AAAAAAAAQOsVmsB1ACosoAAAACDAv4ggVmc+tG7oqEviuFkzKu+k+Hx4T+Nrz8xzWAAAAFnO+uZLdTk9tLszHbgIZxpYv1uGfnR/B+VJk9qC/sHWrHXmYoRndR0BCo2t
+AAAAAAAAQPcNPSztACo0gAAAACBO4P68yppxK6hnIhTJE6V59Xt9wR6SmFPAYqatIAAAAOlw5s5YJbgwADORPPtTYMOqlasPZPJXcFyflulOv/QswTHoYmCcAB4wjIsA
+AAAAAAAAQQN8/du3ACo8YAAAACBCsql980aZkugysksu389zq0mILODW7rsJnbJkWwAAAEhBrke+13WbbqNQFPlSdBa32k1JyZ9R5kwyD64m6fsYuuzpYsqZAB7NbI0A
+AAAAAAAAQQ/dxxZ2ACpEQAAAACCEq81Tkjb08wjphAh20yJDRSLPq1hr9Ab9iWw0FwAAAAXrQBObBnaRCw3xas6Pz/a75G2bmtY6aHusvaQJ54eC5qbrYknQAB4AR+pE
+AAAAAAAAQRvhsaSMACpMIAAAACCJSweyuu1ydzl8alNfUjhL405xvubwAEmKphwpNQAAANgY1VsKLnRqKC04XAAF3Prl76Cq27jRVG99/BdsxwTEo2PtYm21AB4AhPcP
+AAAAAAAAQSfrR3kSACpUAAAAACC8HNTP2q1taoERVEb9+2ot4+ywf6sE5jL/D2UEVQAAAI9BG1kijPzbJSTfjCAqFhHbrOszlv8ybBejmgRqCtLOXR7vYrYodh0AFdM+
+AAAAAAAAQTRtYlY9ACpb4AAAACCr4pyjtYsnPIvypu3OyOisUNz84GWgzl0c+yBncwAAAJumoIQe4AUY3NX1+VUIT7cBR4Omks8K19H5Ywd6DnW2+dnwYnaXAB4ArG3R
+AAAAAAAAQUC8S2mDACpjwAAAACAN0U1wF5mERzevFDxzMW4HRkQHRMmsBkK2yaLaFgAAAL0BUz7yS6H22RZsALMMrY6drwk0L7GaD26ZePaXVelNsZXyYrLVAB4AEa0E
+AAAAAAAAQU1au440ACproAAAACCtQmL/LaxpTfGfugsVw8bNAqZr24Ql+i7a0pQPUwAAAMSivOBxmjY2kuEWaF4cGTSCNIadyGINDiT6Riy3ZXkGikz0YvOeAB4ASp2J
+AAAAAAAAQVlI5RYZACpzgAAAACD+70R6DIfYf67bniB++zyBvSNAt+zBMzuFnvneSgAAAEs7aXGKPvGf4qIQ+T8sxVJvFlXlkKakPbWxfoNQ7YD2aQn2YuKrAB4Abpup
+AAAAAAAAQWWBuG/0ACp7YAAAACC/nXIeESiFbauwfEPnM+8rppoXoyYe9McLZvNHeAAAAHDZpKAhUdMN5IUSaFnnVvs0+vSGGvOrZIGIxDtjCv1mA8H3YlO3AB4AmLEv
+AAAAAAAAQXIhQLunACqDQAAAACDBAL6nIeqkcrEanhzRDIpAt96uKL6Ck1hsMX/FDgAAAEYK97sdjCAfFZzMxQSLeopkSM1XseTgloaWS7y+1QAlY3X5YmqAAB4ApEoq
+AAAAAAAAQX5xV/jdACqLIAAAACAkLB6TnGRoUmHq+lpRSW2NrxjPSTb1tQIcQ6lPAgAAADb7BYyZZhRRooMXm5QVts1TimSdsWyRM7rzNG+ikSQA0C37Yk6+AB4AJh6O
+AAAAAAAAQYpy/qhJACqTAAAAACAK0L7zgL9wbj88TxSncPkEy1hkLwOOUkgebCYjgAAAAFIsOY7nD1ZoIZs6oSHVWFFp+yn4rQuJIWk0ce+9LF5Oy+b8YhakAB4Anexy
+AAAAAAAAQZbm/nfDACqa4AAAACDk98E5tLrOkAh1kBqbuWfl0keZFErMRCoHUk18AQAAAA+wazBksHueOVr4OhizMq49CMuDpZz9Qpfh5co+DL+ng5/+YvydAB5PSCoA
+AAAAAAAAQaMnsdIvACqiwAAAACCbOy94eRJhQUateXx/BAsX0WcN6jIwo2aHa1r0hAAAAJg45yRxstGst2lTFcbEDL1YWPGtUtab4PFcPdwJA3E+K1kAYxiVAB4ALNa+
+AAAAAAAAQa+r9bU2ACqqoAAAACDRIgaq2RNFuRKaAXwWBMuYIGL8PgZvjm8jk/Jp0gAAACniUij0rsgytrd1GeBr10RrqigQHvxF7zMgxUzO9nWI5BECY9HrAB4AdE9Y
+AAAAAAAAQbv0uhBZACqygAAAACDZn7qdREY5h+mAW+QCdUxqrYaQh4CstY6wfx6vJQAAACBW3JQa+qjeHPjO78zN4/YwSe7cC4/fLxEjgz4OGarqQM8DY9OfAB7I+5IA
+AAAAAAAAQcgJ+T0GACq6YAAAACC5aGRKEI2AqG5Iu7fPLNShRxTNUZKojuJwVvzONAAAAC6Ntt8wwXd5sd0aqeFcTQePoDM98S8CIUtrXkn9ZnQSFI0FYwuRAB4Atn0K
+AAAAAAAAQdQHiJLWACrCQAAAACAHTHyU9zA1/0UCoDPTaXXIX/ZscF20EXG3sz5gsQAAAH4A0lRFIqQiW4tFWKL0pP8qT1VhlqiVAH0sjWUfs4iMs0oHY///AB4AMczh
+AAAAAAAAQeAW7dkhACrKIAAAACCsRhny9goUFB4NgLi4TsEB6+fA0JBFoEKzl6wRYAAAABADGMmEZA88t/IueIx/JJ16o/HdT5SpUpEFIiNNYMHSsgYJY7+LAB4AKGR7
+AAAAAAAAQewTC//4ACrSAAAAACDBzjJGgZbZHdkyaV3UbVub91wnyJN30jicYr4kNgAAAGfrIxUn4Fa786q29IyMCrCbw1PQhStFD0izpOb91o9jVcQKYwHCAB4VJF4A
+AAAAAAAAQfg5jZKIACrZ4AAAACBabGB4NonRWeC2T3856NArdYYXeD5G3cKm/2npEwAAAPAxjqH3beMuFHYXFJDov3/UdeWYD+Y8T+4/yii6ZAUNy4AMYySbAB4AcRy2
+AAAAAAAAQgRl64+wACrhwAAAACBsBpcWpRObdiES48unW6GX0tV9szjkgnkGqecnaAAAALw6trlRY8a8cFAK5+Tr+zszvhwRoGBPLDRH3bVfDeyliD0OY+7zAB4AJ3f2
+AAAAAAAAQhB6cgkbACrpoAAAACCNXZwujX7Zt88SwIuoUouaWkVMsh2Eh1b9X1dCWgAAAKVYcn4mOgONYzquGCxg/UcVH0TLlvleqeTq9HEZbnvVBfcPY5+pdB0AcxPN
+AAAAAAAAQhyfXLs+ACrxgAAAACBIG3b2tAt1YoGOvIPcWVWemhTNuFtW1U8Xc7HGbwAAAIU+LbFLVW8XfbcEZbmwPCtlzPujc+p17einy9LnshO/Y7MRY8GBAB4AI5BF
+AAAAAAAAQih/AJfYACr5YAAAACBd4KSCqSI3Mn3kHzpa+AngPOvcQSsK1Sn9a06bDAAAAC21d19r+OhEAWt6l96khd79EUwy+/oLfgGzBKqywKKvwG8TY6+6AB4Abvdn
+AAAAAAAAQjTMGFYEACsBQAAAACC8A8WJ0pp6qS382dhybG/gTqaxrNL0r3zO7jsGLgAAABHzpkVikahzbRPreKwHXPMf2ls2LWabHFLKcks9L6ydnigVY+egAB4AdThL
+AAAAAAAAQkDThOlDACsJIAAAACCKW7XAo8m2c4CdCIoLWXS1tkZeMd0g66B7S8a6NgAAAGhNTscM0z54nJwN/Dy7OYCwWapEBMFtfoJWixPqBTXKROUWYwmXAB4AJ75s
+AAAAAAAAQk2KLr+fACsRAAAAACCehT1jqFO2OSimCyvIsGDgswB8JBdLn35gJs1OZQAAAJKWS/rYylXPs81UQD+q8uV2to0aCVPiZ3+4/uUWaTYoLpwYY8rTAB4AT2V/
+AAAAAAAAQln3DMwIACsY4AAAACCTEl4oAfZbUqhMqgJbRTmOM+qvwPiZnU12Gk4/sQAAAAMGQ6kyv+o8CRhxwNurBtw35YmioVqXSVEj5hv0v9G9A1QaY///AB4AXpVD
+AAAAAAAAQmXrodZHACsgwAAAACBA2uLSlXurR4Sa+inPTfsOlUMnDpy1pVavEdhBoQAAAJfoL6PvT1Pd4OODqkSv7NW2OIyf+JNYPTfHBRLb0bKhJQ8cY9u2AB4AFTdE
+AAAAAAAAQnIcu2ePACsooAAAACCdXsSaGAJjLSkYpTwjFjs+1/GZyMR5TqFVheGbHgAAADHeNTvm0E3bkLUpakBU6tUiQX0my8De5/ztPiKD7YYUL8sdYzyTAB4AAxhp
+AAAAAAAAQn5iWinVACswgAAAACB+Y2fPJxwTa/xyhe1+SnsI7rtRNzYsjEFwNwGtGQAAAFOR51Kd25RKFvHsfUJrTgrbxuQSADaTeIPgPgT2P7fvpYUfY3iNAB4ABn2y
+AAAAAAAAQoqamjHcACs4YAAAACBAT9R2t4VpMUAbA/mMhrgaFR54w+ENRs784h6ROgAAABXtBslsHiiHZSrGG6hD8QotVCT+9UDeN7exJsR6ohXXDT8hY5SlAB4ALHEZ
+AAAAAAAAQpZwPZdlACtAQAAAACDWea4EpHr42GqJ/iQy8L9YeVBMvS/6rBY2+iVRSAAAAKBQbUeroS5+VnwVZVsoexe2YZXsEl9aEBR4pPRjHraR3AAjYyuWAB4ApYJN
+AAAAAAAAQqLVT1IfACtIIAAAACBIshvgkMFeM6frCyQzttAHeAZreyvQh+Qan0OwhAAAAHsR0sRVFzZ22jMVqilIuQLhASNmAju91AOLA9hAmU+G7r0kY7KkAB4AKFPa
+AAAAAAAAQq72VEzAACtQAAAAACDbrgJSgA00reZWKpf9tFQ7S/lbZbpXhe1kIe/tUgAAALSqd8AX1ETCBn8M0nJJ/iYmqN92TImDfVjQx86zBastKn4mY2CLAB4ALwRf
+AAAAAAAAQrtMh5QpACtX4AAAACBcun+DNEigog5AVi9c3PrR0nhUQEXgL0ibNSQDAQAAAAhcACeGk+/6Ozw+o13R+/QuZWDi2vY7Y8kIm/haJ0V6GDgoY///AB4A/21j
+AAAAAAAAQsePea4FACtfwAAAACC4wNY1D0fdA0MmRCBUBLfphvowdPU+HVi1AOh8KgAAAGLVYWvI8q1We8oId+VcGqeARY2nQSYtrp6jbgzY47yfhvApY/WSAB4AYFwZ
+AAAAAAAAQtOX4+wRACtnoAAAACBc+1VKfHMO9uR0bjHclUVOmJBtN8bhTchzgmERUQAAAL9Smp7lH1VlOkdctJ5cG7wBCpDKBvbnAWiNmSePonde1q4rY6qwAB4AF0Qc
+AAAAAAAAQt/aWv91ACtvgAAAACAqZ5Mjv7mcmxtrTXF5vSCe9+t+rQFEVp5IbbPSNwAAAA28J7BbyUHIymXO6q7HrHeQ5zDff3CxoWjf/R0TRp0z9WgtY+G2AB4At3n8
+AAAAAAAAQuvQVsX8ACt3YAAAACDeaydo0zB41Y3lCG2utWUK4BC94UhrXylH5kvrPQAAADlh8K+8WPvMwi3Wdi2wxNdTNqe3pxbQlGOKdddueDs1kCUvYwqeAB4AEtpn
+AAAAAAAAQvg2nkpkACt/QAAAACD9CfQOVbEXbOBMCE3UitrRmlQtfgNu6Cv1MvmWlwAAAJNkWoqG5bkIfgbwNghTAudveY6GQvbnLo5Dvrfn74ARS+AwY///AB54cDcA
+AAAAAAAAQwRz4AvZACuHIAAAACCYYR/39h/MBZUBid9gL+sCKpY6h5LjLEsueg2GlwAAACYcgOYfnddotqETy0CRsWtjvHaCWEGD2+pvelO9BlWHiJwyYzqzAB4ApQ0q
+AAAAAAAAQxCz4Y21ACuPAAAAACB5+YtJxQrORS4z7wDtonSFTzDV1yGSeUbInkPeXQAAAA8kZu3rcU12jWglmggUvphAIX9uTDiLWjwkwoIuNErWx1Y0Y/K9bx0AksSr
+AAAAAAAAQxzwFSR7ACuW4AAAACAVqk6h49lYYpf2FUsDrFp/hiLBLaZHI62ctwO4JAAAAMgLi7lGjZW+eDBaGX/5eVzHY8RtEqyupNYyJyrrxwB9RBE2Y12+AB4ASdJm
+AAAAAAAAQyhxV2DTACuewAAAACCz7aYd8tvO7z6urDwEQU3E3YrxYtIFX1QNmj+GrwAAAP8Mg0hDBhtcKARJC3qHz+RtSI87TGaiiduVSNsu+84x1t43Y///AB4jWgwA
+AAAAAAAAQzEYJsSnACumoAAAACDX1+CEovzwLfKpRK6ZBmDa3XBnCC8onk8iO42fAgAAABC4GI1SbNEd33kaeaR9RHcJQLccHNkJYOdzrjv886ZYq9c5Y///AB5F8WsD
+AAAAAAAAQzoRJZ9aACuugAAAACDPx0BYyYkupQ1MEIDpghzvGyj/O5T/B4K+RY1BdAAAABp9aKr4+ejzAl9BzhheiWjHiFYVXZRPJG8OLW6oO62hncg7Y///AB7UR3MB
+AAAAAAAAQ0X/g+xUACu2YAAAACB+qmqEoY0Gd0qvNLYsqEeNGGQmtY0CYYkoaLo6LgAAAGINKU/70XbUe9S88he6yZ/qhddpZQCCP1viKLwqyzcQ9oQ9YxiEAB4AKx8U
+AAAAAAAAQ1IIQ4nzACu+QAAAACDWNbcKsLvn7Mn6G3p00i6aekl/0zXLdpTRCWfKRQAAAGfAvNslaZDX4vth495Obt5tVxqIh7Uu/39efKA/6zVCSkI/YwezAB5StZcE
+AAAAAAAAQ15NYk3YACvGIAAAACDZ7P3KSOkuUyu8EAQEsYhXWm22kK1d5smG0fx9AwAAACoMfSsPHc6vF6AYFAuu7sv3/7A1dkfRMHsD8nyXTdnqzPlAYx6heB0AZeeS
+AAAAAAAAQ2pTw/dbACvOAAAAACDq3vvULPxPjbuzqzYgVm9TdnNO2yUCu5NbLEd7FQAAAFE0yuGU0PLIR3Go3lsl5w8NKO1sM/ci09ROwhLahnt19bRCYyqYAB4Aj8Wc
+AAAAAAAAQ3bv35WeACvV4AAAACCHssmg596XZZPg6/FGzVc4ivu77zI2LbGtgWtiXQAAACqHrLMssFF5vY3Ef8tclFdxFncgQ40oeWx9kKYYEmIOG2lEY1BMax0AmTzp
+AAAAAAAAQ4MflcfDACvdwAAAACD/VBGoA40reDAYPSEiTtroA3Y7u3PSqeJOymIHkwAAAEMUgVQNoAnll+WR4TN+3M3toSq9xyFIVaIxeaF7qXJUJCVGY3mzAB4AGPnB
+AAAAAAAAQ49w6IHwACvloAAAACAdH8S2v/NEQ+S+FX8E+5iuDarXSbCLh7vz/75TKwAAAK4kSYom394rhAGZyPRCcjdJk6tRzF8EfZ1LrR6D1vn+ld9HY///AB4AAJfH
+AAAAAAAAQ5tJSugIACvtgAAAACBLFHXgUFU8ApmwmeC0WNyLeAwoiSoOLVo5V4EwSgAAAHfy1ovcTcwoqUuHi9V/JQxAuXn1zHteyZWMYH5evPF5rppJY+W1dx0AYAgS
+AAAAAAAAQ6eMBik2ACv1YAAAACBhHTM3LNqgyn80yG9Q65UjxQ7rxTDx3CfmBxNyhQAAAH7nlU2OMnDNI4TvhdNBFvL3FBet8s0FXaaZBVkmP4DxiFRLYxqWAB6g3QwA
+AAAAAAAAQ7PHEjqvACv9QAAAACBKLOKOl+vfbNWxxGHybaNa0SsMiw6jmM77i4JZxQAAAJp7v4K+2qDlo41w9BwzPLa9GKTz/SQ7mw/DHw0sj+6wNxFNYzm6AB4AF9Db
+AAAAAAAAQ7/yReKMACwFIAAAACAphJ9mUX+UGQXdUVJbuqEwPcL9Zxiwa0syKHMnTgAAAHUBOu8sWTK7qcZn7m9+EHBvN6lFm/om9qM4JMV2iJ1nFsxOY15Kah0od70B
+AAAAAAAAQ8xS48mlACwNAAAAACCZURWAXqUMbqoD5VtgZ8OdngrhrT/Puj6rLn1CbwAAAHN++QVsnuFjh7cnYmKeEu0lkAl9SPpJUAxtrl4JtXEJVoNQY///AB4ARqSh
+AAAAAAAAQ9h1L8PUACwU4AAAACA6rjodnEHNyTVBewVC5IyZ/VOReTXI7+yiXbz1YQAAAB27Q2tmagDpxeIsRGSdfLw9FVrg/FnLZfGLrg58urHUh0BSY/2iAB4AlHfj
+AAAAAAAAQ+Sj29ELACwcwAAAACAK0+tKltrxF/9c8ZJx6M2qE4xAGbVSvcolVyfMiwAAACpfLX48Ob25fnNGOKYYcUcTN/I0ZnE0XbJESI7iMSeYcftTY7uFAB5J9y8A
+AAAAAAAAQ/DI8+ZAACwkoAAAACCzHZNHEugLIJwO78F9DzBW2tB2EFPco88cAIMRNAAAACDkF17JKgpMHAa7mNl5yKZ8thVwWP2J8SS6R6eiZbIjmrVVY16CAB6N78AC
+AAAAAAAAQ/0ZSNdlACwsgAAAACDXWqqCqAfkyhr2roDPKwuJ3BsmDzSNJWDVWSxJGgAAAIeIiTzGFl/si+4XAlXDJ/174+GZymaIpCRLvpnH3qsqYm9XY2OmAB4ADNbA
+AAAAAAAARAl0HO1lACw0YAAAACCyRi9IX+JAdEIHFGdhi4Ysnm7Rvv9GZ9q5t1ECwAAAAFYDuDeXX7GhJgokd5v3VyDbXnFuyuFV1mmfMpqXaZjhbShZY8loUR0Acsnc
+AAAAAAAARBWlftevACw8QAAAACBJxQAOwwVE31U469Q0f20ZYTmoryZaH1IQyiWGewAAALRE8rwX93goPKTnisi0s//r0qyM2EWQSQqV0GHbDP4xp+RaYy2MAB4uI3UA
+AAAAAAAARCGTvzXTACxEIAAAACB5JooXqblmwVAI8aBAFPSO5w7f5OGdhtVFh0W+4QAAAHX8Q3gtQexsoKYu16NomyXNy3uhujk5tuFpYdGL2EmfO6JcY///AB4AjzxG
+AAAAAAAARC2n1IkRACxMAAAAACC82TAn6BT3BtKNGatCVeYUs9/DN+N8QwjYlDUpsgAAAOgcLEmJP5TGrPen9jwg4W/CtAytZOq1bXl5mRPHRr9azlteY1yoAB4BLAyC
+AAAAAAAARDmE5tO5ACxT4AAAACAMql+bXAu1NhyLN2LTj+65ChaWjhbTWgVToJOlCAAAAD4g30te6UYB3fU6cwi09aC0cZJHCcrWSnTV921y/Qg1nR5gYwASex0AiPTU
+AAAAAAAAREXXKFQwACxbwAAAACDLiUyEI1r7bu1rYyftz2xSaDCWV/gZ+3qYQ3YwVwAAAOSszx2kdartTUaJATz1q72ZKMVard4mIer9QdLsaWcRH9hhYx5qfx0AJgC0
+AAAAAAAARFJBjNsUACxjoAAAACD+2UQGMb4ftP2cL8VrExA+WlgyQ9BosbKNXR8JJwAAAIHQW7rO+ORKtKumQU/wGA0QoaIjiq2goMzCT7SIMAIvcI5jYxqVAB4AekPF
+AAAAAAAARF4QQRKPACxrgAAAACD5Rb77Wazjg4XWmEe6auO1H0Rvw7zk/O1i+tMbNgAAAGaPK9lmJsG/yt57Wkscdf36wMZdCQDCgtC6liGexRkEMExlYwKvAB4AnQ0Q
+AAAAAAAARGpdGDK5ACxzYAAAACAxqbe7Mo1i4PoJ7v96ON8haV7o5Kutwe2Je0WBIwAAAK8wZnZVIGmssXsN2ehHNZI7j1FUtE/BAehrEMac5AyBQwVnY76VAB4AT3I/
+AAAAAAAARHY08UXHACx7QAAAACCLRtcwTUUByFg0ChW18pzdcdDQ8KTIEeNEfbDZOgAAAGbV9eDYr0DRjXn6iNeg3e4Z2ZfFPcYuwDe5K+OpCW3Yh79oY2yhAB4AhhU8
+AAAAAAAARIJSZbjzACyDIAAAACDA6SwGuzYwRaqhJI7jg4zVdjlLjo2DAcNAcA0GkwAAANSJulP17R+tpQZ/oTjA9ok8tcaVMz435o30M5Ey7SECkHpqY///AB4ACu/+
+AAAAAAAARI5IzZN+ACyLAAAAACDwlGDZECVL0t8uyZEQxV2SHUjeVwFoxbHvXfWBDgAAAHpyFoWmYHF0qdlTys8Q+tybwhAm4apXmUczxNBHX91ahjdsY4aIAB4AXkXz
+AAAAAAAARJpGDGxWACyS4AAAACCBEvpU26K448ZJgnYxOheSvyTDnPdbdDv0aAgeSAAAAOg87PndykO+ryPTN9VZWuWQvuhfin6+gtGnJmV/DXpIKvZtY9rFAB4AECDR
+AAAAAAAARKZZge8oACyawAAAACCRwJqVKBcylX/R6WwuVkZXdrR5vet7jYPq5M7XZgAAAAP8k1XPRnA17pT8oSLloO/DKdIf4wUF/uSrExe3bkLlSLFvY///AB4BI7Gc
+AAAAAAAARLKPUEsaACyioAAAACBpvbo8Z3b9+zFC5dkw/uKG2oLPpIZLBrt89U+sCgAAAAiWuJfQA92SSwj1HrxwJ3xG/9VssahVgPAfg4lFbND7eGlxY02KAB4AJP0v
+AAAAAAAARL5Cm1bQACyqgAAAACCHCAmzs23k8339zll20qLevDsBVnyRo38qgSTCcwAAAKaU8Cg6vd5E3+SpaYKZ8ggQTG17ZFcXkfwl7+SRoRtXNyhzY16rAB4AT5hr
+AAAAAAAARMps1EhHACyyYAAAACCUDjIgP16N0+hXfw2AZCUYQAvxEYsYf+X2Jsae3wAAAF/WLBSQPmJponjbPB74BT+FB8VM9pzcS9W5rRww7Ndpp+N0Yx3VAB4AmKDr
+AAAAAAAARNaUbPPXACy6QAAAACDSv0lQz0TyUyF/VQ9ozNGNr8hWJHnWD+QGiYMUSgAAABWG+Da2ZN3TE3YnXw1jvWmoCkg4ngmBFyLJOnQlMFmhWJ92Y4OCAB4BDvyP
+AAAAAAAAROLm2J/bACzCIAAAACBPKW+6MIzmtjMonsGWc2KiVyMX9qsqJCdvoG+mWAAAACQBStJU4iYzuUOe8Cjjkq73mOG3DHo9Vt99IJj9nIOsHVp4YyyAAB4AHCXb
+AAAAAAAARO7RHfe2ACzKAAAAACBD0eBAj0p31m6wFTQT7gdxQVKI1QqSfXH27iBVWAAAAPGjAnWPAkdOefB+JxTydZRNI7TGRQBwtFphBqFxThQFBxl6Y4DpAB4ABX/C
+AAAAAAAARPrrWR+sACzR4AAAACDqQx4pn5FgeoVMSUOpz6x917GeuWKOBfh8BzKAMQAAAIhkvOqTQYsnwOqM6E0M0D7ZWuYro3gEnhTLO8k/wNHo59l7Y/C3AB5jIEkA
+AAAAAAAARQbA3paSACzZwAAAACD3G0xlQ8aBRxezUm+Ox0ImZPtryeSWVfb9Tn2COQAAAOUwD/bXNmtyYOmZ1xcVfYQuH2Hd9sFEwQzL9Dryo/y5xpd9Y+KsAB4AmNZn
+AAAAAAAARRKefVT4ACzhoAAAACCmxLiP12WnSDzg/3CIRIUa+/6bfCuH9PTBW8k8BAAAAOFBiFV4Y9xMKcurjL5n3q9BcBceWDyuuLjhWFlnCsb7OFp/YzGxAB4AMJke
+AAAAAAAARR6nM4P0ACzpgAAAACDuYQC1PDYzhTGuMwrjuI/OFQZU+DLJo2X9OVj1MAAAAJlihXK3LHebyGsVUHp9dtRTfuNBKVyBHAhlzce9GIVaEBeBY8mpAB4AEcc+
+AAAAAAAARSs1l28RACzxYAAAACAeREzw4MsIhNCFEUa7EPAOfa7CKN3gRybTO1gbDAAAAHC4eDMT445IiWYRrYOe0Dqknu7eMAPqxy9EHhwvydeNsc6CY22OAB4ABQTS
+AAAAAAAARTcDoXYvACz5QAAAACBG4BM4aLyTUM7PCmtBaMNpQnKROcfUyQEEMJZtkAAAAPKPEv4ae0NS6V2eYNXhzJAokqh9mANrUDEYuhcMw2ElQIqEY7uwAB4AD8v6
+AAAAAAAARUNmGfq9AC0BIAAAACCRRjfG2cZpwMepKTJhD3kxqpH0NiExyFST9Xm1AQAAAMnWiDh+cqs6oCNrcT4TyoMXygTHbR0yZPIcDyrE9KgmIEOGY///AB6UJ0MC
+AAAAAAAARU/ojGx4AC0JAAAAACC7+Ox1m/f9+hBFXTarvAcrgpmv+lsAhLYfZ9tKrAAAAAKX+8KC6OuSFwsDPuDxisalSlPqwrYGHJ8ZC9MHMtWxIfuHY8DNAB7a5FMA
+AAAAAAAARVx95EK+AC0Q4AAAACB0h0X237wySys3IWHgUNseoQJ/K/UeOepfo49KZwAAAI+M8+MQJZDEmI7wgXqHFzA1FLRAXv33HXbNjvtu9238ebSJYyOlAB7+HQMA
+AAAAAAAARWhvil9bAC0YwAAAACCloBJrq6Ho3TWprKAMArLbDRJ1zeOrWfxjfhAVEwAAAOQcWkF9DRBiQJlQvBj1gosbJwJh/MC8/Y30aTb6M2mJxnCLY/eoAB4AH+mb
+AAAAAAAARXSNWQXRAC0goAAAACCyvhiO30G+ouogFm1fw1hB6BIz6uZFsKT1xn1kfAAAAFkhVJPm6FgaESNb6+VCciVg01rnWPDMp5swVPXAZ6ORrSuNY6SZAB4ACByH
+AAAAAAAARYEu8AO/AC0ogAAAACD9u+Ti0OGYs5CMS4dkOIkkyEqoUAa2X2jnzuF1JgAAAGMl9+gs+/DOvcIjo/Cn9zZTaCAaaFrNkADoa3GM9FZhMOKOYw97fB0AKxV3
+AAAAAAAARY0Q5QIsAC0wYAAAACBHEl0Hr+YdC6W3pvbqKogXx47GZzZq5WN895riTwAAAJENlBo/mC1zuviUJRDfts+waNqy8qYv1PoT4l/gty0mFJ+QY///AB4ALuLr
+AAAAAAAARZe9NqHNAC04QAAAACApdW4K4TIAdUAcAdJ9dXV3KJpc8DgLArMde91quQAAAHdeIFWrXj2I1VHt9tIQqd8GEwPbsdqiSVi+xIUw4956JXWSY2a4AB4AG8Tu
+AAAAAAAARaOfDzxIAC1AIAAAACCIEs8Y2ZGfZllk5myhZtrIgHkdlVUqndpYLpL4HAAAAKlPe1NskJ9W+3PAJCKk5cHk8VM4ljOH91dfiwX3yF+S1DCUY3FIXh0AQPe7
+AAAAAAAARa+AYMvwAC1IAAAAACCMaxFrarBB1syaGr10BV3s4ohDK0ppz2vRSY+lhgAAAAJqjkXtVWXR8A0OqkppMZ14ubtNJeO+wboAsleW8lE/ZO+VYzCSAB4APkLj
+AAAAAAAARbt7/r0aAC1P4AAAACACa2+3EUC+K8IPogmEwhEFSAjk+sZbaQ/5giM2dwAAALbpg1PNicia6lroR1+sGwtm83RHZ4y4R3V6SIdjorrWkK2XY7aWAB4AcYqa
+AAAAAAAARceGn6uwAC1XwAAAACB+jxQBH+kXhNazMLfciPQxwsApHrSAoxRbMNnOagAAAPFTumiNJzhXPTnkVPanYZzTrkd9644lXWqaIfxLnZNDXmyZYwJrdR1I/B8A
+AAAAAAAARdODAE/mAC1foAAAACCswf9Wlc8w87oajhvFI094YK+aUcKXrJ9crCIfKgAAAKdVYvH04Dp1dwKZG7tGG66ZfHtbvIjw9MThhFtpaqdiHSqbY1KnAB4ADvl0
+AAAAAAAARd+LKamdAC1ngAAAACBUdd3Uv/xSwFEmziRBSdouErMg8u2hVDGt135ndwAAAHNH+e4GGEWP+hC+v0jsUrPTDoDQHTmEzwTcGXCQ0IoM2uScY3GXAB4AegJh
+AAAAAAAARevPCAoCAC1vYAAAACD8k2MPX0gAiFbVw44R43A+ztbVewM0mr6PpjR2GwAAAFjUkAosH1NQCQFneQA4Vz3T0O+hHm7x5ryGWYsr1GJgmKGeY///AB5uVSoA
+AAAAAAAARfgTrNIJAC13QAAAACB8r4dFFxfB5GfOM7PNi9Afjg8xEG6OlkygssTmHAAAAP8dqe/by60KcdzSbdCnW9AkAWYdL6OUU+SveNqoTkUpMlqgY4u7AB4Am5bZ
+AAAAAAAARgQUdDzqAC1/IAAAACBHJrqShCYSgOu24UZ6/7PTwxnXc9gZD5p+xFRWHQAAAJqc1DvD53b3Um1X5MLBv9qlrn88ThBjpiYf6iBssjsTWROiY9abAB4AitpP
+AAAAAAAARhAOdOzPAC2HAAAAACC76LqWtsxhNxvwVhOWF8zJkN6K7Q4KXx1Y2FrzEwAAAGHhevQUi0TDrkgms4UW1uBVfNMeFtinrwIIPY036tBrjsyjYzCOAB6X724C
+AAAAAAAARhxX7w/RAC2O4AAAACAN18nTHWeXZNg9vaaqpUkG0H7DM149gJmpcvYoIQAAAPEA5pdXaNhzfosJ9dH6OYU0vYyIYtGGzMlHxhTroEADfYalY1qBAB4ACJD2
+AAAAAAAARihTJgm7AC2WwAAAACAK1vqWre7aymqo3J81hjGqpFIetlnMRtbQQQBwQQAAAK3yUfNUz+S/i6bhoUxjE2LS0ti+Ittzwurq0YCs74mHpEOnY///AB4AVCAm
+AAAAAAAARjQUtFZwAC2eoAAAACDCyTYdvl1xb/Dhg3E2eF78Uy9WVjPJWCCS+0evjwAAADFJ2OFAlODvufoAsfWFFKbNfDWf4CFMR6sG/r69cFAM6AKpYyfEAB4ASlfG
+AAAAAAAARkAD7dx2AC2mgAAAACBUgthdbUWpUARLyn0RZKTFqhGok63C6/LmwdgGgwAAAB1ycxDYgkA5HqqxR01A9LJDB34VQOFEGqbQ6dPjADqe372qY0u3AB4ADOIZ
+AAAAAAAARkwglOuQAC2uYAAAACAgALmIpTrfrAQwRNxtoCNwTXFoIQZc8ao3XO5sYQAAADKqqzkrTnGvnpQpLNtL38bIAhA+Fz8MMx5TFi+kvg4RM3msY0iTAB4AM0+l
+AAAAAAAARlgoWNaxAC22QAAAACChdYK1/yKOhwH1G1izOgxwXKod29XpgUaqLTsveAAAANvcyFl8wFebWexPT5h2iz6JBO/mCXlsY68wsEGFpLddkzWuY3qVAB4AXt7L
+AAAAAAAARmQpWuWWAC2+IAAAACAEALARXj5rnMseNz9yEfxfQ7Z//kOcYtnIDDnvlwAAAGGZRXkmtC+cnB2mVCE9CAvnOgV2I2fMMKOmRVOAR8AV1vCvY///AB4ADVfB
+AAAAAAAARm/H0FZWAC3GAAAAACBw0U6vMzLEP4qea5bTc4whE81w10akMZ19zUEdngAAABKPRbUwOzBv3nQgL5jpjiLm5tt+0ZGW1fLt5MJ8z+piiq6xY82aAB4MCYsA
+AAAAAAAARnv8QFotAC3N4AAAACBamJmobSBMBYL8l+2ph+N0viGYIzGB+lRuFn4ITgAAAKZ8tFIq5a6J92MOf/UI6M/48PXaTtx34f1AhiIBh/2RDmizY2ODAB4Ab59H
+AAAAAAAARof+42+VAC3VwAAAACDZvvIUtoFeXsSJlYDpaJXEmagYrZLwZvKA3J7/mgAAAELFFj4pdnfGRw58NUVc+FILZhKRGPmwGgnHoIkOMsbtEiS1Y7yYAB6FmjUA
+AAAAAAAARpRNC4myAC3doAAAACAaOKoBFlUC5bzLWamMALILkNdBVZmHeeQ4FzpLZwAAACPUXUMKKvqc4tx7j6T6sUdnF0Sa01MbcfIv8N54CZpdKdy2Y///AB4AG79o
+AAAAAAAARp/+xDG2AC3lgAAAACAsEkmiFUrRe0jq0GzxLzuQ17MizbASj0jkUbZcCwAAANH+lI1wWFCMZYMPEE2CTHSiEE5h/veShn5KRemGqPadb5q4YwVYeB1gbYsA
+AAAAAAAARqwere9uAC3tYAAAACDhWzH8XztlUvXc7SKPtPvxv4Duv8/yQVJljjhHmQAAAFypnDOM0f6+LystfKVCdSnbQKnBhfWBY2VU2IP31aicIFS6YxTHAB4AHsqZ
+AAAAAAAARrgBFy5eAC31QAAAACCUxKPkuWH/s0uQFOe4qfo+WXpbtxZpTJfi2OFUcQAAALENnSLBjJpspk21Lr7dApsjSd3b+uhnJxpiqds4E6eGohK8Y///AB4AEJsO
+AAAAAAAARsSTPUt5AC39IAAAACDxtCFcyeGltrOoFgkyzmuSJd+VNde1Ra1aDWlmnQAAAFQ8+I5mZ5DpL9bgD0ZNvG7y2jZo5fmxd4dRE3zVcZskq8i9Y1uoAB4ACgSD
+AAAAAAAARtCMWZ10AC4FAAAAACBZk+RXUKyChYBkYkyVMo4a8OU8sT7eR0Eirr41oAAAAIVC64XKLygoBLYuGp30gGOv8n3EcGR5XswIsmvic4FdbYW/Y+GxAB4AjXum
+AAAAAAAARtwjZIPgAC4M4AAAACC6w6pdy7C2dBIbzwdrWcxrKhTiZznQoZSfE+CXYQAAAEouelSnwq589mXwPCGEXfVP+sR/kPBT+12k3hWeKGg5WULBY+2FAB4Ahbh6
+AAAAAAAARueU7/diAC4UwAAAACALxc+S50zm3ErrNpQ947Qh2JLwnoSt1ztnNLv5TwAAAIqvv40JRLz8REkVXuTCm5DkhT0lF11PtIKJMKebfojo2wXDY765AB4AStHZ
+AAAAAAAARvNtIbVOAC4coAAAACAz3pwJDwsIJ+SPFE4GtYM1o5S3bVb+GY9kkgwoUAAAAKoJzJfSZdq1YgfiOpwlXGPQp3OKd1GsBWWb7XslIEZWA8TEYySUAB4AIHD5
+AAAAAAAARv8QOJ0kAC4kgAAAACA6VQUIEpu9llkZijJudLbY8RQJXRPuzI+IA5YvbQAAAKHmLdIZGW+BC+8kN/wR3ZbUm2hYQPgBN/rJx6e2HZip3YHGY4GEAB4AKfQY
+AAAAAAAARwr7DS6hAC4sYAAAACCzg91A+x53PMzOVTb63BHVQkJ1w5UOh6vL2ok9hQAAACQAYuBITyrfG0qhBfdxQv5ik0+3UGbzBu/6L2ypDjLqmj/IY6/TAB6MoFcA
+AAAAAAAARxaB3+9UAC40QAAAACAVKrfJlD00Lm78t0agzCQbH8H+AZS8zPZ+5a5dGgAAAN9KBkYCLr/E1/UkDUibweJHydHsRq/icr39NNlfDre9/AHKY///AB4BF18j
+AAAAAAAARyJFOdjTAC48IAAAACBHbc3wC6rm+c/dBKXZ0um7ImLzgVKZNgxUquhugAAAAD1qIv3H8/Mk6ICJXA1iUn7pAPbqzXeVYqJI0LzpuySszsHLY0WiAB4An6h8
+AAAAAAAARy5GrmDZAC5EAAAAACBX4nEf5tDuxFerdAijFHtpnxbZXd0Y1qahH26vJwAAALtOsUG2X839xnrQTK3khuSXcOh6ar7I5AMO9a/nBTGHb3zNY8e+AB4AE/Fi
+AAAAAAAARzo++Z7BAC5L4AAAACCsXGQLdIiTEInkQGzTJqCtLE9Y3S647qU5gAmQAwAAAIdm70351JHEZaS/U002E/W9RE+RiOQQEhYIp43NJat06DfPYwCFAB4AJv77
+AAAAAAAAR0Y7gQsEAC5TwAAAACCdSJMRN380ZuvNJ5qMzpsoP7FEeL9VqSoh8PosYwAAAE012evyIAQGzzOrfhPoZaepkuEaYqL/YCeGW9uwL5KZ5vTQY6TjAB4JZB4A
+AAAAAAAAR1HaMyLtAC5boAAAACC7RDrAh3D4pApgOIfuasQar9cjm+t1db9KLYhzewAAAAHpVjPGfDX59bJVSDxVKog3JrjVZK4DEi61EoZe5yPCqbHSY8KHAB4ASdcd
+AAAAAAAAR13pkPlnAC5jgAAAACCenO3FLc8Y1v2+891k33sMdjtUO2UGLJ6qEwbCJgAAANbhveRHz52aMp4jT6rwr7pQ2f+U6QZFWRv9VQbMFKPxJm/UY///AB4AQ5tJ
+AAAAAAAAR2oKa55fAC5rYAAAACCewk1j02ynmj+FuM3MMM5Nj8+yc2Q1rhkJj3TaJAAAAHQeoYhw52GI4aqk+cpa6lUFLsnObtMGyUKfm7HGDBWWfynWY8GgAB5p33sB
+AAAAAAAAR3X9FQKuAC5zQAAAACCF7hDuZ8mfWIcU7OBNpzNNEuXrOWJUmKYvQoriKwAAAP2DRwcoFPrzMSISpaWQIIg7/I9DeiztngG0KEknAibZU+TXY6SwAB4AKt8w
+AAAAAAAAR4FIBPwLAC57IAAAACANAeVhjUF+DR6sVPvlZLVHUkU96D91K1bPXrJqNQAAANPZ5yBVr98Go2AfZy13DyMIubRytQtb6vlg+Uyjc3WLYKbZY///AB4AKluY
+AAAAAAAAR40o/tixAC6DAAAAACCWgEj83APvuCxdTXzQP5MbzzgRNf2VlBNMClURvAAAAH5KxGdq4773CJXR4eU6wXqgvFJSIQEqXWQhwhID00h55mHbY2DbAB4ABAQD
+AAAAAAAAR5jyoC8OAC6K4AAAACDtKcYH0YOygmrokiYcByMnPj4rjXplnMVBbsQNGwAAADL81jGPl4Q1NkOaixrZlWKSfgq4rykciERG7xpGd15BWR7dY5CdAB4AJ/qv
+AAAAAAAAR6UGASd0AC6SwAAAACCcckTXfiaGwRqHLHq/NQ7jHl26405rqE4d7YTbHwAAACHaDZzZ8Hb+q7vBGvN0h95wku0Wqm3V9IA3teU5NP3pKtveYy7Xch0ADYDz
+AAAAAAAAR7D632kwAC6aoAAAACB0Qt/a3VBBbRt2i9VJS4caAz2fvAs+rd8v9ij0BwAAAKcSTshPL1H7QPI8xwF+D2b+olUScm237nHlmQlE9OS5IZrgY0DIAB4ASUwo
+AAAAAAAAR7y4rpoVAC6igAAAACAuU/SAzQqyUjHfk2dyzBbLt2Is1ULWFMpKdpqaGwAAAFMtLafuolYoZdpzASvzee6i6TzVyCt/lGUuG8nPH1SDrFriY/KaAB4SIgcA
+AAAAAAAAR8iXSA7FAC6qYAAAACDWFxOoPTaQ7kIR4dUn3nFc6nCPEn9ZLPpunYrqZAAAAJkVH7JO3r+7oAl/vCSATE4z5IqpB3859nxrv4WtXkGgDRbkY/KtAB4ATNu8
+AAAAAAAAR9QSBY9AAC6yQAAAACA/3DaiPiS4tRocg8ZukaHVYXUXT+jOUVcivCj/SwAAAP64jXenD/d7f58Y7fZlEXHVf0w4ai5iopdj4p42vHjXCNjlY4G9AB4AYkEn
+AAAAAAAAR9+xO4u2AC66IAAAACAi/W7/uZ4xZ16r8BZM5lHP+npHrAP+ceNV/iYozwAAAHx+p8kxdWTdLUshyApIBTTRYicwmXwzn284KBJMx/TKWpXnY///AB6ejlcA
+AAAAAAAAR+uYQN84AC7CAAAAACB2o3aqvQzvxyXE4GUuhRjR3ACoxntKTLjIatcLXwAAALPoRNwHXRurkp7RlcvdG1H/kdtZ6rcRx1H9PngIuV7by1PpY40nXh0ADqKu
+AAAAAAAAR/cjiE+5AC7J4AAAACAwsLtCyZb+Kg9vVHGLZPgf/kvKxn38tlvTdKuyoAAAAM+sKTQQB6fp7laR+DgrNcOnSk6qg15/E5eBcJ3X7kOZ4RHrYwqfAB6XfSQA
+AAAAAAAASAK2WH4gAC7RwAAAACDkdLrvcOjNjOcyTSOUdu0TXjDVPmqO4btuxdoOcAAAAGVuw131EkU1a/36JckzBqIuACLHS6nPyTpYbB/6rfS86tHsY8DeAB4Akq6y
+AAAAAAAASA6KRGpyAC7ZoAAAACBIi/VvMqoO+prVGOgDq77eCPgbXzgVXzjScKyivAAAAMN80eHReFgBBPTmBNVQRUJWKzZBKO+CnnHmDt+32dsws43uY4GkAB4Agkax
+AAAAAAAASBqZQzwXAC7hgAAAACBtBUtDW9oEjEbdRxRmg5cdxXX4DbQbeeANtAugkAAAAPMJ1XZiu/k8TLImDQR9VBrfM0Z1VWwwmBut3ZqRUu2n0EnwY46dAB4BDw/B
+AAAAAAAASCZnEWv3AC7pYAAAACAK9EkIFbq5QIfuJdqDA3KHZXed3VkVaEYmskAJmgAAAHhHKcgTariSkSRwhIndIxHPQwumiC7umSxfaQsq7QS03gXyYy7OAB7kDwIA
+AAAAAAAASDIm3llEAC7xQAAAACBTrc5pMIxGwgyWCdkDT3sR1lW34VECmD/eqjr/RAAAAMgVdMPig+ELcJMFRmXybzvBqvhoQp6+MH57pLLG0EJ8zsHzY59peR0AM+De
+AAAAAAAASD2xUjg1AC75IAAAACCEjJ+2T0nUnU9gefHcLqKD/WoWPA/U85MgvO6fvgAAAI01gtTyImRYH6OSwP4M7flLnJEcBFEnR2bmHCa9niZLTn/1Y8qtAB4AdqkC
+AAAAAAAASEk41P5EAC8BAAAAACDO2sd+AVFpfgByGA5qzi7KuHX2q6Li2zep2qTLQwAAAJcLutWvKgiPJRZf+bYklWfpwZ/po8LocB0jNSSsXpl8wz73Y1OtAB4APhxs
+AAAAAAAASFUDHxN/AC8I4AAAACAYgo2KclVuHtXeV9czK5WxHmX+NLbKDDPbK2MGCgAAAG/YN03Qww+h/fMseF1WemDnrIgoeGMmdK1q3MpM//csgvv4Y0+MAB4ASVrP
+AAAAAAAASGB/YnURAC8QwAAAACAZx/KXEPXjH6+Tm0LJfi1FLJkSYTCUrQiZB01bwQAAAL7FuFwZdg/byU/zaaEXp5y1SgzF4UENeI13E255qP0lKLv6Y4PNAB4AAVo6
+AAAAAAAASGvhM5KuAC8YoAAAACBDbiC72dacbWzkvMnbwdVLup/cYYnbPK2DIxh7jgAAABF8Gv9Ed6IR/9gCWytcTMzH+aPWDmL1+pZyWX6thanqSn78YyDhAB4AVRHm
+AAAAAAAASHeAG6yjAC8ggAAAACDdU/q92e/Z4s0WILFcLx2UhhcGQErSX0Q5P/VRbgAAAFfaw7TtmpuNOEeRgffaU/84xrn8LySpvoG2o8xy989T8Tr+YzTHAB4ATW4D
+AAAAAAAASIMk5EgoAC8oYAAAACAa4AlaBTThcHuotIOQ6+cX25cbXKTb59frLQfvlgAAAMlinMnDJudwrsj8JsZbavFWCveIRO+7mcD9RRukYH6zS/v/Y7eiAB4AQ1Na
+AAAAAAAASI6nXAofAC8wQAAAACBslpzo31SNmF4bn9/PhVl23aHtjfsrC3r0W7Y5JgAAAOtMlaBkzyNfcRUiwkIh07QWWIFyz9GC722XJyzchN+EALoBZEK4AB4AH9sl
+AAAAAAAASJp/WBFgAC84IAAAACBUZYGiFKw563ykz/71sL+x0RFlmgRr6iLZKuIRGwAAAOJBBDOKeVtuPB3JrDIpCTfAgsQKKrt76zMbrCmItzImgHcDZM6WAB4AVgUv
+AAAAAAAASKZevK4sAC9AAAAAACAasQcaspqKmPWy0hoJ8KJnuLSUrJiN2Epno5NCDwAAACrqtidslI79ZTGAPk20A/B/7RPKwkUr1VzloM/nifX13zQFZKKDAB4AmjnN
+AAAAAAAASLHqJs73AC9H4AAAACAhaqN7yVB6tWFr6i81yicA2qYOVnLzbcYqHuTrDwAAANR0ayf9/5nyqPBdcOV9WlvbM7Ins2kJBaZCIuvBE1mNfPEGZIboah0AOUw5
+AAAAAAAASL1bdlUOAC9PwAAAACAFYaenYXP8L6I86je9j7CpipmDaN8OTezzOTh3nAAAABXrZy0dd3ROCSlV2GzZ+9LNH707pvcv5+bOOGug2I0Cj7MIZGOzAB4Ai8aZ
+AAAAAAAASMkIc5HzAC9XoAAAACBRk4BRwJDFnmugTgy5eg6LTP1Gq+sL6ogKtnFTgwAAAHLVpGPjVaZB91ocQvz2JNGL0SkInf0Ri6vCjlKMaH1HPXEKZAbCAB4AAHsd
+AAAAAAAASNR862oCAC9fgAAAACDIEldzYsVF1UWZdnO3202hlL5ThaUfGeGOojV5SQAAAE62xUha0a2dHIrRDiON2oFlQ1dgfikvQ6kVSsx+sRkwqi8MZI52fx0AosiX
+AAAAAAAASOAjbjVbAC9nYAAAACBijJTqk+5mylAQPcNeWnFEnP8Y27T4N9brWmnnVwAAADYdBsLZ/wWlhDBd1ZlqCKsJhgK8JfCWCv6TU+FZQiXg6u0NZFe6AB4ANzqy
+AAAAAAAASOvqPSiAAC9vQAAAACDFPxuLZvj4T4DeRTLf94LpHMFOgdcj8P+k9Zp1KQAAAM8Juopm9PCDDZrDZcCJtF2YmCdY1iUUOrAyq1xgPK7U0acPZOSmAB7OjLoA
+AAAAAAAASPdn8v4VAC93IAAAACBhtEmSsygD+o0jm6gjPYbgnOJnib6pa3Wf7+RHVAAAAKulry75LbiN4mVE4rjl2nXZ0CqEcDF6Ka+mK21sdiFVaWgRZJioAB5eRaUA
+AAAAAAAASQNSfo/JAC9/AAAAACBYgEf6uES92j+bGt6GXYmt1tbEcvNjdnOcNjR3IAAAALSoXBwM3tppoANiKK34H0szdf7hiAx2PKbGWnRhrtVuaSMTZKayAB4V8EUA
+AAAAAAAASQ77PFW2AC+G4AAAACAgfTT3DpaxG3qkg5w1O7s7JFO5/eMJ/kOVLy+bgQAAADlcprpwe7E12jabNBNWFGGmOmcOosNYXHbw5pxdRy7treEUZP//AB5QqxkA
+AAAAAAAASRrXpvppAC+OwAAAACB1l0xrZn3ZcBMUTZ+ddgYA46wIiLyrMoxY9PB6jQAAAJENSHh+kI0N0yPzWVRwySw3XTsBt3wyHqHM9wKSnQhqGZ0WZB2VAB4AryZ3
+AAAAAAAASSaKbypaAC+WoAAAACB8F0NzCzAJgHo1OFRXgOHoEAjBWxktDvsnKjk1qAAAAKPWq28mrih53cGeG80pRXRWSVW+hPPgG+aV3hAcJJhh/VwYZEyxAB4AHRZq
+AAAAAAAASTI3QwzBAC+egAAAACBVKv7WUQNvbTPI/LZrgeOfBlDYjBRmW8GE7Ar74wAAAC6420+sDyqctS1DL3TzAfljEEq0lJQBddtTDgnlN4uW3BkaZHiaAB4AEg8q
+AAAAAAAAST3+NFghAC+mYAAAACCc/5Zxq9QTV/+Se9v0tjzGx1LdDtV1cdeiVOyv8wAAAKsn4gSRB/O/tL60NGX4mMRRGKd91C8l9zxIPBw6zJD1GdgbZNn7AB4AGLYr
+AAAAAAAASUhYpQG7AC+uQAAAACAt8nl6DmOJvK67KMchdbAqYC8ei0VMhxvyPSfEZAAAAOzjVZJEfRi5Bfm3+6CKAh6SOhjIaM0KdDuvUJhPhBAuEKEdZMWNAB4AHYV8
+AAAAAAAASVLL0TC7AC+2IAAAACDTYI0wHtlryyHFDKR6YOQEhCuQmU9COoEAlhxmhwAAAOQhune5Wd8pDHuf28Ff+RCDn4/TqYfSvgv9Uc299BSLZm8fZKHmAB4ACRVR
+AAAAAAAASV25yYqkAC++AAAAACClea24b/S+H1zQVFfESM+hnPmTl4H9kLJp55l6HQAAALzB0wqgKEY5vXTXS7u4lXmj2Vk2u2i4gJFMTXzwzW7XUDMhZA3XAB4A+GlU
+AAAAAAAASWgPXaMjAC/F4AAAACAyIy36f1NrWqfNZy6fsCJyKGBTe4SQZv5bccvIhQAAAGpftHW26eEo6rQpT3EETzPKa7h3JfDXYjRm0K1nUhNsRfwiZKWaAB4AY9pi
+AAAAAAAASXKX257rAC/NwAAAACCY1KTCAitd1uggtHvgoU053FzzBmefyyetQVokBQAAAPVfsiePo9bv70/rBGmDu5P35bKFqDAsWtCXH9713zzndcQkZDeeAB4Abytx
+AAAAAAAASXzYp4NbAC/VoAAAACA+WT8lUuNLBWpJlXEgue4fcHvKAzd9fq0ZmT94DAAAACnyB0TVYqBPn1XIia6QPBQOD0oRN9JNVGJpBrrxfCTCrpwmZB/OAB4AufqV
+AAAAAAAASYdZREuLAC/dgAAAACCCq9mELQC2kVTZr/NXUh18K3yMT47KRNCD742ubQAAALx9Nd56ujv4KAdV0wT+sWJLHKCuCUJ/9b2e2leFg6Db5WQoZEWJAB4AD8K8
+AAAAAAAASZG1/0+cAC/lYAAAACCSFNpcCuqryxiMxoySKxXFHwLs+RCrHibhQhXQSAAAANQpEvXa2k5DmMbyH7JXt5CyEQdoR+sW2leZu3Tmo1wnrS0qZM7FAB4A8bMF
+AAAAAAAASZw7Z3fuAC/tQAAAACCbVf0UZY5RI7uwvYEH6gDu/uo782MmGSlknm5SAgAAAOgmnDY8ugdXixROw9I/30aDEctsWrsTmncgyCdZr/7CGvcrZP//AB4AJIXn
+AAAAAAAASabjI5HcAC/1IAAAACAXRe4eWoEBSfPdJUJasCOTlZj/4rF8t3X9eCSWeQAAANRA3oPJ2LFPimDGAodw0QAjw+2qlqcUjlqMR50oGbv1Jr4tZEO+AB4AbbEY
+AAAAAAAASbHBQRN4AC/9AAAAACAFJgz651NOE6gpFppxoKOgf+leO2xP67iXlATcawAAAOUdc1HFYwr9KF3tz3M2OGeFSAdrkVQ2sU6W01+jnp3U/4MvZK6WAB4AC/5X
+AAAAAAAASbwOubefADAE4AAAACAvX2syMHdfVWqsSf+LkY5gwECs9+LluKQQhkBLUgAAAKb33hbXs9lijZraHXmTsyC5qb4ikr8wYpWQFiYx6rufT1AxZDS1AB4ADdq7
+AAAAAAAAScZcc4opADAMwAAAACDYevsTL5ndvYKEwHXjaPAaHaa05s8J9Nqp5kRkigAAAGjBvxKYdeU2sYZrkNwc8YM6/NEed56Ry/IbKwYCAUgcHhszZK3pAB4AIJWb
+AAAAAAAASdCoPnawADAUoAAAACDlZdFwMyjMgucTSwt1Ert0gIydTot6i9pKe4cYKwAAACwIkDaQPyIAd/AqE6Q3/rDc6HYLZZwysIfx42hzxWDMd+Y0ZCsdeh0AHCHo
+AAAAAAAASdqjR4JKADAcgAAAACBii7pDxpAvsxunw/XGe+ohTZZAbnnLrGsKUDRSXwAAAGpUpH8a8UoZyMvKEyTzdUdaIRQVgbl122B3n1un66KBu702ZKHHAB4AOp8R
+AAAAAAAASeTLAT+2ADAkYAAAACBz9S4TW09YzKDrtm7zmwlDwvzrrgy9zgwXDLCseQAAAJfdzIKzdCDRYQvq4pJGVSmjrkbgeHfy5+P+tJ5YaGQwJ5I4ZM6hAB4A61xq
+AAAAAAAASe6qOfhlADAsQAAAACAKcn/Gk4f3Hw6NxJHQQq12AsPKC9pCY73u6fdHlgAAAEXJBxVQdFX9MuUWns/k/M4lnlJSqTvhkoFPWUh66uDMymc6ZAGzAB4AMM16
+AAAAAAAASfiCMlDHADA0IAAAACANOHKJ4bP//47md6zEEkplHGppAl+DPPSx5d1LkQAAALrll0f3AF6hdiF3y6DKiekkkVlHyxxUF2QgINj5/TsTO0A8ZC72AB4AQgl3
+AAAAAAAASgKmJsN7ADA8AAAAACDdfeUtZzSM2k8dU8m2zKCdxcWNF0GbzoUl6FR3VgAAAPSFlaSi9D2T5VrDCT731kQ9PrnzcZahi1HInjeNBruJJhQ+ZMfHAB4AhnBy
+AAAAAAAASgzUUuk9ADBD4AAAACD8xKNVM34DW7wgbk1hcIxtavye8UhJQEaj+LARxQAAADB/BfvJ0yJtbS+HuktVMfYbf9VhU/9ETjY8zcAMj5V4zeY/ZP//AB4AemVw
+AAAAAAAAShaSlhTtADBLwAAAACBed0anlKOc3PLMDp4ZYGM3OVIAmYGwkw/b2lNicQAAAPnLZc7dIdHDIBgTc6cMs5NkAJq05RCcKsh8S6lX3iIgudNBZP//AB4AVQ8D
+AAAAAAAASiC92hopADBToAAAACBHUd5/JLyyXWRzTq86GALEwfKP4zmp9p25SUQWIQAAAEtRStKmV8BoE0Zd3YQEkZWmD/ZCI/ebDdNbVBFa7O6IAaNDZCrPAB4ABpz+
+AAAAAAAASir+XBTbADBbgAAAACBqqr1fuqjRhF/058zWJNRzVtwJv0tHMW1JYIJpZQAAALJKZ1pROwik16KcR8PTzbWPWWj6Yo4Oxv3FLvabEOm+EW5FZLq8AB4AR+D5
+AAAAAAAASjV+6booADBjYAAAACAfszIQ7FIwXxEOnXgZZqpJNheX2foKrKe5elL/xQAAADq5Ow/BaGyeWfric0aUe08QhNlZFOsU60A2kqelhwc2ITdHZGHmAB4A6SJ2
+AAAAAAAASkBLLJLzADBrQAAAACD16vYUxL1lYGwUr8i+gsJYBpDytGRdOUqffzaDaAAAAB698IJvJugnUBPscFXVS+RYwz1kpnn43eOhd8lsRFeR+/5IZP//AB4AIY1y
+AAAAAAAASkrwuPq3ADBzIAAAACBMOa+2Foj/c06d5tp6Tx0c2TFX50K7tGF8yVC0WQAAACFNgly/NYHkqhr2u4e7gCoxP3ISYsWADYCMvIGQSHcRtcRKZA2zAB4AXqJQ
+AAAAAAAASlWhygigADB7AAAAACBe7f/n5mUsuIvuACfDmoItx1p3Bq/ICEH8HJlVewAAAA2eJnJtin21cjNTAKAj4rYVerV2NC5xUq1EvaDksh0cpYxMZKrLAB4AR2J5
+AAAAAAAASmAfgNk+ADCC4AAAACAz1+F+yWfU9j5lsCjwLj4fhEYCD9Oaqz9+3qXnCAAAAD7CQckNwy7j8s+PnO3A010Qci992ErXgzDGdpSgqcxKWFROZP//AB4BGf+H
+AAAAAAAASmpVqByvADCKwAAAACC0JptGQtIat/cIg7+8N1OMkPcTgZrIuc5AwGJPAwAAAHLhs+Zkp+L3bb6ug24UuBZx5uG5IQiJ/ffSV65YrZMs7iNQZBCSAB4AALTy
+AAAAAAAASnR1bSLDADCSoAAAACCTtUmByF1Rg/h0W2psFG4jyXWXmp93y6yxuq6zNAAAACf9i2VYjjwM/i4VfyxBBQKR9UEewaIPoC5jG6k1cx8uRflRZBKCAB4BCUiF
+AAAAAAAASn4t10ZfADCagAAAACCsIDWLl4N+iHuikEvZUdy3ZsMRYcXfrDJIBnpL9AAAAHUqWzeT6aSKqZFE5Vdo1FbMSoop5Q7shuf5mGF8PAlUntpTZP//AB4AAlzf
+AAAAAAAASograUeTADCiYAAAACCivCHrfG/8vMbsxhup9IZqLP2b+k1O48CeHmN/agAAANU3s8DB+nakcJluJrUdKy/CktffWzNi3ch9EVukGMS2FK9VZCqqAB4Adoyc
+AAAAAAAASpE1UUsnADCqQAAAACDsx0Df0gpcMFfRLk6RJfUDiJZoKVYnPFwRkYuGuQAAADZ+PRUv2ZWrD+EpQdtgo3qEkWinrW4YPkr96xMFTn9/7p5XZM67AB4AH07p
+AAAAAAAASps5VJhoADCyIAAAACAgk+VQGwQYo8lhdQVSLdf8EvhDafsejZKcE1VHhQAAAGl04CiSoYRkRxoCSvQ5fg9BjAjPf9QeZFJomT0kH0+5znNZZNu9AB4AZEPj
+AAAAAAAASqVAdODMADC6AAAAACAY5vFKq8qHI/aNaUZLmE81+WP5k9O4tUzE9BGFSgAAADVUJo4ABd1GiyjyNKThEB6syqKBE2GvmxbshCMLT5ka00JbZMKuAB4AN5vH
+AAAAAAAASq+sm680ADDB4AAAACAiNeoH/lYouKrGD3y1A3HEnQ4X02vPRjEWIC/ARAAAAG4g0i4FORW08JpZymP1Pnvzefu7q6toVelXI8/HLQw7QRhdZP//AB4AA77V
+AAAAAAAASrmbPCk1ADDJwAAAACBottc+6kfdX5/0c7UrEvtoo0TClc9bUvFj8pR4fwAAAI2tJZhqhVdYgGg/aIYtikV/HlY8rA9Ywy7ecTTbn7ajH+1eZP//AB4AXvma
+AAAAAAAASsPboAKgADDRoAAAACBQxZbZ1kwBZBaisTwHUyXl2VpASe1kWYRTeCqXLAAAAJKbDiTfBtDSm96m9a4CV/MxhRnIeySY+dpIaRMEj+HOzLlgZBDcAB4AZibr
+AAAAAAAASs3eadRSADDZgAAAACBq9KzDVJ6AzENlGPmXvpou8uqUj3heYcmKSW6igAAAAMk8jUiQaUggoRdYxt0WzM3wTEA+uMncP1vwCPU4jtETNI9iZOK6AB4ASZgj
+AAAAAAAASteUmfacADDhYAAAACDxOmXz/gzEFnevs7HMqKMoN0FyWQjdusraLI9V6gAAADqhsecJ4//kmJSxUW3QKBZ+OcajueoawmrsWtTG3Ktx6WtkZP//AB4AX7Ws
+AAAAAAAASuFML0NbADDpQAAAACCMZtJ/Lmc4/VVWbb/cWBmUKKdYLS15vOOz+7IHtwAAAGfWV2OxL6QAHcCOoBWozFc2Qu1X6u+MyBMPha+QVyzjZUlmZJi1AB4Apjiz
+AAAAAAAASuuWyuSzADDxIAAAACBwXSZ10anTneSAZflX+czo1tSnwFy3tMqgtdx/jQAAABgq2ohUzyIil0dsIHALPWKdMUjiu7kMHP5yhV0X8I139hxoZOO5AB4AApag
+AAAAAAAASvXS0GxMADD5AAAAACBFZ7KJBi2U55Ul8/cYp3bpYJnyMDh3xuin648iRwAAAGGiJf668UCEoOq8yX3aIAgpZo2uzMYHxrkOrybkimnD4OtpZPDSAB4ACAWQ
+AAAAAAAASv/X4VkcADEA4AAAACBX4XfrzmdAh8TnqNnYiKD21aM8HnIzRfJC6YRH0AAAANiM4GQzTEpTMVuzDPhHcnC4WHLXFPUHg7OvuLqUs7f1gb5rZP//AB4AXxLI
+AAAAAAAASwoCDDVoADEIwAAAACDw2NJ1KazupDA+TweGo3ZThCRTJfCvD8+L0YkRnwAAAE2iyrJLvcsN3ThAKSfmSaRbZo+KH084PzhUMCIP3SNlk4xtZDacAB4AVO+H
+AAAAAAAASxOFrXPsADEQoAAAACDY/PPqbOjEa7Qi8+TPOyfaNtdHT7Gl31rjVlvorwAAAB+vbxMqrO0vUvltm1TTyLJtHVnL/i1hdvJ4HjQUImY6625vZP//AB4AdgQj
+AAAAAAAASx0QlUXcADEYgAAAACAmbkAhcaIBSfn30Gr+XqtBe24+unb1kpzzQIKopAAAAMsFoCy2Ie557jexTbUsN+V83umnuhaYWvZXtLr0PxQ1fEhxZP//AB4Aj0Ps
+AAAAAAAASybLSYilADEgYAAAACDxvQoDdTAPDBVCdrHcYrJHlaPlFEBHP4xMW1MfOgAAANk+jzcX/I6kvVa2ma7UWSc+Sut1RV3DBZghmkV6HZbEvSBzZNKsAB4ACLJ9
+AAAAAAAASzBQ+WJ7ADEoQAAAACAWKD+TsUc+dgAFsl2ifryOUu7mYPp/Vocgjco5mwAAABIUh1UVrn8Bjf1/2Hzpy3RzP+bcxpaMQYt6BIkynykm4wJ1ZDWuAB4AABWH
+AAAAAAAASzmGdDA1ADEwIAAAACD9XqhJ205h7n/u6dhFM3+1mtPB+3Fa9OIk90tbugAAAORQ089UUjlWlwRfg/X5NdjBPu9UEDcRn84UzxBWTcGwu+92ZDCrAB4Ah3eB
+AAAAAAAAS0NGHrLUADE4AAAAACDwOp00IgruqLpUHtWDVLku3z9mbllFv+r+BsSzeQAAAP5oXc+t+76ex+pWRqBVz3hkT0MTIRj8AtCJ4yd46dbIJct4ZKKzAB4AHj0C
+AAAAAAAAS0zsXBl9ADE/4AAAACC/mYr8olcxWvatWodrKT8DbyPSvqHAI5fbLgbNGgAAAGXBAtcQPChXY4XOTIh4imsHAqFdBErFHh6wQuOyZ84jTa56ZP//AB4AZjIQ
+AAAAAAAAS1Zs5EgQADFHwAAAACAHOwBXV+aKKcywoqSHB78zxgdyvdidvea+oFiilgAAAErX+MwETqcUhI8fVREhQ4YOQ4gKSFoKHTsmxlpe4NCu+o18ZKHJAB4ABvPI
+AAAAAAAAS1/3AtdjADFPoAAAACBzxmhMrTc49HHD6EBV8maBlDLlNNUtfgvLFO8CfwAAAL8FG5D7Men42ZCwwdZnWijIXS8iDMR94Dcw5er19eVcW3F+ZP//AB4AF2D+
+AAAAAAAAS2m2VJnOADFXgAAAACBth1Vo1nkFI4MnmQXvpixads6X3q3dlA6vzUjc6AAAAFteIPCCfHsbHFwkmkckufGYuKpbiVNGcUCMOYJgvm/QUU6AZCfuAB4AZCHM
+AAAAAAAAS3PBqQh0ADFfYAAAACCNd2TkhR5kmWDRIt3NE5kjWyunfIXZEYNbnWVzawAAAIxPZgZCBGWqMYrSqUCuOZIWs7avf2AFwx+TgJtYcXx1PyWCZP//AB4An7jK
+AAAAAAAAS344Rwl5ADFnQAAAACBbl2qIHp7HNXm6QdzLlTPH/y2b6w8PK4CGkMHLNgAAACYV4J3fO42dbeaOZVmxsiTqlQb3unv6ax6zkKGgpn2yA/CDZDmUAB4ATBbM
+AAAAAAAAS4gp4iw5ADFvIAAAACCpZ2Ia8rQmapD8pEUxf1uIiPj/xWq3GhFIr3B2qgAAAN0P2bKdf9Lq86QrG1LJphA6EFzVd8AgSoWHmaNTfZm7jMSFZP//AB4AcFWR
+AAAAAAAAS5G64ASQADF3AAAAACBzsXGn8YuJX16piuYm/j1J4szeHTlmxTgiQq+KmQAAABoSXSRtObNngK8JS8iIGJvs4xsi2v/bQYQ6WA3JVRtCuq2HZP//AB4ANMVF
+AAAAAAAAS5smKwKsADF+4AAAACBvH/Ag/JjVUM4qWHuX8hhEIoievdbAwGROpKkZLAAAAEEu8oj/D9TQQEuLaTe9NlLQ28g0BQOExA2JZdJq6XdyjJGJZCLtAB4ANggA
+AAAAAAAAS6QvjVKdADGGwAAAACDBZA6NF06Tr4o1ibBIGxTexJIy/YVyfdSld0uChgAAABzAZl53guNp90CWJxjnaTyrpHL50T284tUkOgNKlQem0IGLZP//AB4AGMdJ
+AAAAAAAAS60+s8ygADGOoAAAACD6Ccx7ESq8KwNul6xbJkZn5ogJDq4wHApt4LTLlgAAAFSWx7numD9zL7y0uxnLG92ZjMXgjNACZPyhFdNT7nN2gnKNZCbfAB4AXUuO
+AAAAAAAAS7Z2q6CeADGWgAAAACB1/Uy7FRp+bKI6kwIQo3+xI65TkyfNyRe5NsShfQAAAOUPMVD4sxhSCRLAVQNmTXz2sQCAogSfxyaiSjgxE73SS1mPZK2aAB4AgcI/
+AAAAAAAAS8A8gOQUADGeYAAAACDvf4VfUc9PwEuUuJC2snke1PuvqHHQuNwz0Z2l8wAAAHExTNDh5rTELJvXr8l8ICRNQf/qhBMZsCn39SC1BYo46DqRZErcAB4ANsHG
+AAAAAAAAS8ouhSlZADGmQAAAACDD7mpT5/+rAiFPmoKF2vhA/ySDZdffa7zoT7jVCgAAANdY8j7953v4xLhooWRb3XXa1ju8Vt0byWO5Io9ficcLUBCTZEypAB4AJqU5
+AAAAAAAAS9R2ltmyADGuIAAAACAqaoVDmQi7xBQ+vNzapwRhgu/wnGpLTeeVT1MtCgAAAFGqZ19EDfpo2LFB3WLy7INoV6ltuP+UdqZl4q4j0Zy2huKUZP//AB4ALxK4
+AAAAAAAAS96AMoIoADG2AAAAACCATWoi+kCm9vVzeMpSAfdeFBrUZB9M54aEix3RlAAAALkKh8Ku4ZEkkADq4n4zmHal7tEY5dCNsPxO45qvjeU697CWZDGeAB4AIKCF
+AAAAAAAAS+knAfuxADG94AAAACA/7t1TG8tfrmaAPVZopSN+Ao1NAU20gGNaDGh2QwAAAO+wf1h9oO4yHWvDz4xYsQ222R6LWVAuimy+0WdqXCGuO3iYZDaVAB4ASCgA
+AAAAAAAAS/N9hp+XADHFwAAAACDxva9gJj7xrAUdww67/Uk0R5MJlTxxyvzBv9kg0AAAADZ8eoHuE1P6K3gAuvGCZDYAna/96T2qZ/UO8VoFAF59qkiaZP//AB4AdpOo
+AAAAAAAAS/25+/7ZADHNoAAAACD23IJpYXInGJPQ2btvi40hXQPsDj8SMikDQ94tcgAAAAuRZ7HYxqd6LX+UqA448HYIX1QoZFemacG/sVxAe8BeKBacZEyeAB4AI3kH
+AAAAAAAATAicwBMXADHVgAAAACBKUAn7DV0UGSrEwGLJY/p6/nESWEsgOGCEAs5iewAAAJTelIyfmdhCX4tVjVTO4yZmHJ8La5/EY6/+dGd9CSZ02eCdZIbIAB4AbaEO
+AAAAAAAATBLxxFL+ADHdYAAAACDJ8ruAXwSksHAHal0+Y22jVdEmLnc9muMdrQ0e3gAAAOy2JfhuCSnP9T5WKeDa7Ua11BMt5swXPb9AzhbhFBf0oqmfZP//AB4AWvvr
+AAAAAAAATByeLCiRADHlQAAAACDIB2waavkowbVBMfZR6/00RcpbUe2cgqw/MFJwbQAAAAXqMaz3nYhOXuSOlnePD6OWcveRw3WkwIuc0AEQj9sBUIWhZP//AB4AOAm4
+AAAAAAAATCYxX3ByADHtIAAAACBVDhmpEipzTjYSKzwnj5SPTPqHtyWAOI5LfceKvwAAAGq2bw73EjD0zYMyNPe3QAc3C4dP5imFYLNr0ghxQJTRc2ejZJfPAB4ASH/l
+AAAAAAAATC/OdZXKADH1AAAAACBIE68Auz7hRISJvHwj8ItODHMTSezQhBZpc/G/QwAAANsxVGi7NEImw+uS3E/GH8nl6WO804BDrQOdZpG1H/OwyUWlZH+1AB4APtuy
+AAAAAAAATDlS4z+JADH84AAAACCH9FvJi4MFn9yLehdGFsj8UsujVF/HL5MZ7aRS3wAAALUp8fjN/Y5VVKEIvPtxGkwWQraDo1h+9zwA5h27vhHdPyanZP//AB4AS2aC
+AAAAAAAATELsfc8hADIEwAAAACBKCaFkwPmhwLqATP2F3p1suASIpwx2Kc5mPM+NmQAAAEDhjSd5SExygS3HFO6PeBGMeN2fQ8rEPMWj7bFQ62POiwOpZP//AB4AJraM
+AAAAAAAATEwpVVPqADIMoAAAACCNpa8qwMarfERrIRjlfDhI2v1w6f6wKjSsVB+2wQAAAIgmf8bNnD0jOFfe/XF2au6QqHxxTDkDVJlfJ16I6ufrre6qZP//AB4AP1Cc
+AAAAAAAATFWeeF1yADIUgAAAACAYSel0izfeYxSz0+s4NXE+PP0qRRjHKAWIIUvmrwAAAKIQwe06ks269YcHR69/v9OIevHoPmXta62fNXR2zAtFT9KsZNbhAB4AHgjs
+AAAAAAAATF9WYdudADIcYAAAACAYmKnfwWXe9MdvxafqJOwIN8tCK+ZsbZWV1OWz1QAAAJ8/vacyyFzDTUIoK7kJTADnqfhcH0vAR/6OlaOBmsYM/aquZGPmAB4AV+PN
+AAAAAAAATGlDj5AHADIkQAAAACDHaweRdllUvWPNuIiFfSjDsixC7ILzVgrTIwt4WgAAAL1mkYxf2lg/VYhBFyo+FRkMZCe7s91rtL0iN9mXrzmPZIqwZE2gAB4AXQbK
+AAAAAAAATHLjj5BRADIsIAAAACDOo2v5Apn6MHKPDprqA4tJnVyyVQDRn7Spa7VleQAAANPOE3zC8wtN3YjExvepodP2fLusrSeQrXTn1Q2DC3DP9GSyZNmrAB4ARVFB
+AAAAAAAATHzPqyzlADI0AAAAACANFuyMjWmCiT76Pv0oXkmDyEBuc2mVvfBo9zUQVAAAAGDf83z+l6ebrHtoKbFCsD1Ndyyg0DqVFfeLINbtDQJiGj60ZByPAB4AB5xy
+AAAAAAAATIZMpXKtADI74AAAACDA5+rDk03wYO4cd0AtOpb7LwTuQtpv10RvnI/gqwAAAESnHiBMBgV8gTLn8aMP0jedKaG1yClm6AAzn6xDHypCOBq2ZGO7AB4AjY+m
+AAAAAAAATI/sTtrAADJDwAAAACDXXTXp9yAmlLnvE8Dt6ujRdA1Gzc/eiOa0zkJ/KwAAANBMzjoYkiF92jyw2CPP3E6y6g+H/+z7WFrgO/SbhFclP/23ZHm5AB4ACtLo
+AAAAAAAATJmSsxzcADJLoAAAACD7PBSrwHYR78ZpGiC5BKNwInHCzSN7rFJuSGDdcgAAAN7fVpW2KJ/xsUsdCIzy+au7AR3q/hirCm0BKR6aty9nHta5ZIKMAB4ARznu
+AAAAAAAATKN15GefADJTgAAAACAd/E0aJxHp2bA6opeYX2rM2lE2wqAGbXmxgBLYBAAAAPiAOJkW4vu0HdEaBmnHrevoY3tI175PQ0MUbtQcqidNV6u7ZP//AB4AdSkO
diff --git a/wallet/assets/electrum-servers.txt b/wallet/assets/electrum-servers.txt
index 75434a3796..176cb7664d 100644
--- a/wallet/assets/electrum-servers.txt
+++ b/wallet/assets/electrum-servers.txt
@@ -1,2 +1,2 @@
-tls:electrum-test1.groestlcoin.org::5298df7122bf5776878243cd4f2b8a5872201ace1c6932443d6eb24fd5c58ba1
-tls:electrum-test2.groestlcoin.org::9efebdfe13ecc79535a5aae0b1d012b815d56e30778b3d42236f0aadaa191b69
\ No newline at end of file
+tls:electrum-test1.groestlcoin.org::5d4def5b9784dd1b2adaefe5666e335c6c5a4a7180c6ca79fbccdac8bee739e5
+tls:electrum-test2.groestlcoin.org::e4399b184a1500f2a5b81d30ed7546256771a981829929b057470d357716ff7e
\ No newline at end of file
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 92335a3eef..8fa2ae72d2 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -7,33 +7,35 @@ repositories {
mavenLocal()
}
configurations {
- all*.exclude group: 'com.google.android', module: 'android'
all*.exclude group: 'androidx.viewpager', module: 'viewpager'
+ all*.exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
+ all*.exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
}
dependencies {
- implementation project(':integration-android')
- implementation 'androidx.annotation:annotation:1.3.0'
- implementation 'androidx.core:core:1.6.0'
- implementation 'androidx.fragment:fragment:1.3.6'
- implementation 'androidx.recyclerview:recyclerview:1.2.1'
+ implementation 'androidx.annotation:annotation:1.5.0'
+ implementation 'androidx.core:core:1.9.0'
+ implementation 'androidx.activity:activity:1.7.0'
+ implementation 'androidx.fragment:fragment:1.5.6'
+ implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.cardview:cardview:1.0.0'
- implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
+ implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
- implementation 'androidx.lifecycle:lifecycle-service:2.4.0-alpha03'
- implementation 'androidx.sqlite:sqlite:2.2.0-alpha02'
- implementation "androidx.room:room-runtime:2.4.0-alpha04"
- annotationProcessor "androidx.room:room-compiler:2.4.0-alpha04"
- implementation 'com.github.groestlcoin:groestlcoinj-core:0.16.1'
- implementation 'com.google.guava:guava:31.0.1-android'
- implementation 'com.google.zxing:core:3.4.1'
+ implementation 'androidx.lifecycle:lifecycle-livedata:2.6.1'
+ implementation 'androidx.lifecycle:lifecycle-service:2.6.1'
+ implementation 'androidx.sqlite:sqlite:2.3.1'
+ implementation "androidx.room:room-runtime:2.5.1"
+ annotationProcessor "androidx.room:room-compiler:2.5.1"
+ implementation 'com.github.groestlcoin:groestlcoinj-core:0.16.2'
+ implementation 'com.google.guava:guava:31.1-android'
+ implementation 'com.google.zxing:core:3.5.1'
//noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
//noinspection GradleDependency
implementation 'com.squareup.okhttp3:logging-interceptor:3.14.9'
implementation 'com.squareup.moshi:moshi:1.11.0'
- implementation 'org.slf4j:slf4j-api:1.7.32'
- implementation 'com.github.tony19:logback-android:2.0.0'
+ implementation 'org.slf4j:slf4j-api:2.0.7'
+ implementation 'com.github.tony19:logback-android:3.0.0'
testImplementation 'junit:junit:4.13.2'
}
@@ -42,8 +44,8 @@ ext {
}
android {
- compileSdkVersion 'android-30'
- buildToolsVersion '30.0.3'
+ compileSdkVersion 'android-33'
+ buildToolsVersion '33.0.2'
defaultConfig {
applicationId 'hashengineering.groestlcoin'
@@ -54,8 +56,8 @@ android {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
- minSdkVersion 24
- targetSdkVersion 30
+ minSdkVersion 26
+ targetSdkVersion 33
}
externalNativeBuild {
@@ -115,17 +117,19 @@ android {
}
packagingOptions {
-/// doNotStrip '**/*.so' // comment needed for gradle 5 CI
exclude 'META-INF/*.version'
+ exclude 'META-INF/*.kotlin_module'
exclude 'META-INF/proguard/**'
exclude 'META-INF/services/**'
+ exclude 'META-INF/**/coroutines.pro'
+ exclude 'DebugProbesKt.bin'
exclude '**/*.proto'
+ exclude 'kotlin/**'
exclude 'org/bouncycastle/x509/*.properties'
exclude 'okhttp3/internal/publicsuffix/*'
exclude 'org/bitcoinj/crypto/mnemonic/wordlist/english.txt'
exclude 'org/bitcoinj/crypto/cacerts'
- exclude 'org.groestlcoin.production.checkpoints.txt'
- exclude 'org.groestlcoin.test.checkpoints.txt'
+ exclude 'org.groestlcoin.*.checkpoints.txt'
}
}
@@ -147,5 +151,4 @@ task svgToPngMipmapProd(type: fr.avianey.androidsvgdrawable.gradle.SvgDrawableTa
clean {
file('libs').deleteDir()
-
}
diff --git a/wallet/proguard.cfg b/wallet/proguard.cfg
index b498166ea5..47a0b23e77 100644
--- a/wallet/proguard.cfg
+++ b/wallet/proguard.cfg
@@ -40,6 +40,7 @@
public static ;
}
+-dontwarn module-info
-dontwarn java.lang.invoke.**
# android
@@ -51,9 +52,14 @@
}
-dontwarn androidx.viewpager.widget.PagerAdapter
-dontwarn androidx.core.**
+-dontwarn androidx.activity.**
+-dontwarn kotlinx.coroutines.**
+-dontwarn module-info
-dontnote androidx.core.**
-dontnote androidx.fragment.app.FragmentTransition
-dontnote androidx.versionedparcelable.VersionedParcel
+-dontnote kotlin.**
+-dontnote kotlinx.**
# bitcoinj
-keep,includedescriptorclasses class org.bitcoinj.wallet.Protos$** { *; }
@@ -109,5 +115,6 @@
-dontnote com.squareup.moshi.**
# logback-android
+-keep class org.slf4j.impl.LoggerServiceProvider
-dontwarn javax.mail.**
-dontnote ch.qos.logback.core.android.AndroidContextUtil
diff --git a/wallet/res/drawable-hdpi/app_icon.png b/wallet/res/drawable-hdpi/app_icon.png
deleted file mode 100644
index d5aed1671c..0000000000
Binary files a/wallet/res/drawable-hdpi/app_icon.png and /dev/null differ
diff --git a/wallet/res/drawable-xhdpi/app_icon.png b/wallet/res/drawable-xhdpi/app_icon.png
deleted file mode 100644
index b68a3fd067..0000000000
Binary files a/wallet/res/drawable-xhdpi/app_icon.png and /dev/null differ
diff --git a/wallet/res/drawable-xxhdpi/app_icon.png b/wallet/res/drawable-xxhdpi/app_icon.png
deleted file mode 100644
index f1c46c9ddf..0000000000
Binary files a/wallet/res/drawable-xxhdpi/app_icon.png and /dev/null differ
diff --git a/wallet/res/drawable/app_icon.xml b/wallet/res/drawable/app_icon.xml
new file mode 100644
index 0000000000..7d97b4d76b
--- /dev/null
+++ b/wallet/res/drawable/app_icon.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wallet/res/drawable/background_splash.xml b/wallet/res/drawable/background_splash.xml
index 3034ebacad..068892d440 100644
--- a/wallet/res/drawable/background_splash.xml
+++ b/wallet/res/drawable/background_splash.xml
@@ -3,29 +3,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
-
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/wallet/res/layout/wallet_balance_widget_content.xml b/wallet/res/layout/wallet_balance_widget_content.xml
index 7d99120749..ff4acd3fc2 100644
--- a/wallet/res/layout/wallet_balance_widget_content.xml
+++ b/wallet/res/layout/wallet_balance_widget_content.xml
@@ -8,11 +8,10 @@
رصيد
السعر من %sالبحث في اسعار الصرف
- لم يتم استلام مبالغ بيتكوين إلى الآن
- لم يتم ارسال مبالغ بيتكوين إلى الآن
+ لم يتم استلام مبالغ غرسلكوين إلى الآن
+ لم يتم ارسال مبالغ غرسلكوين إلى الآنتودّ معرفة طريقة الحصول على الغرسلكوين ؟ تاجر بالعملة العادية أو إعرض سلعا و خدمات أو تحصّل عليها بفضل العمل.تهانينا، لقد تلقيت أول دفعة خاصة بك! قل قمت بحفظ احتياطي لمحفظتك لحمايتها من الضياع؟نصيحة : يمكنك تعمية جهازك لتعزيز أمان محفظتك. ذلك يؤدي أيضا إلى تأمين بيانات التطبيقات الأخرى المُنصّبة.
@@ -40,12 +40,12 @@
ملاحظات تقنيةأترك بقشيش/ تبرعتبرع للبرنامج
- إنذار أمني
+ إنذار أمنيمساحة الذاكرة الداخلية مُتقلّصَة جدا !تستخدم محفظة الغرسلكوين وحدة التخزين الداخلية لتذكر المعاملات والكتل. إذا نفدت مساحة التخزين تلك، فإنها سوف تتوقف عن العمل، وسوف تكن الغرسلكوين الخاصة بك في خطر!\n\nهل تريد فتح برنامج مدير التطبيقات لحذف التطبيقات غير الضرورية؟إدارة التطبيقاتتحقق من صحة تاريخ اليوم و إعدادت الوقت
- إن إعدادت الوقت لجهازك منحرفة ب%d دقائق، لا يمكنك ارسال ولا استلام مبالغ بيتكوين بسبب هذه المشكلة.\nيجب أن تتأكد وتصحح إعدادات الوقت توقيت timezone.
+ إن إعدادت الوقت لجهازك منحرفة ب%d دقائق، لا يمكنك ارسال ولا استلام مبالغ غرسلكوين بسبب هذه المشكلة.\nيجب أن تتأكد وتصحح إعدادات الوقت توقيت timezone.يوجد إصدار جديد الآن!يقوم هذ الإصدار بإصلاح أعطال مهمة. للمزيد من التفاصيل، يرجى مراجعة سجل التغييرات في متجر جوجل بلاي %s.إن لم تتلقى أي إشعار بالتحديث فمِن المحتمل أنّ نسخة نظام الأندرويد الخاصة بك لم تعد مدعومة قط.
@@ -53,7 +53,7 @@
إصدار نظام الأندرويد الخاص بك قديم أو غير محدثهناك مشكلة عند فتح الإعداداتهذه كمية مال عالية قليلة لحفظها في جيبك. نرجوا نقلها لمكان اكثر اماناً.
- أرسل بيتكوين
+ أرسل غرسلكوينجلب التوقيع من %s…فشل جلب التوقيعالتوقيع غير صحيح!
@@ -61,7 +61,7 @@
مجهولإدفع إلىأدخِل عنوانا أو إسما
- عنوان بيتكوين غير صالح !
+ عنوان غرسلكوين غير صالح !أنت بصدد إرسال مبلغ إلى عنوانك الخاص !عنوان\nمعقّد(%s في انتظار التأكيد)
@@ -106,21 +106,21 @@
فك التعمية ...تم.نقل
- طلب مبلغ بيتكوين
+ طلب مبلغ غرسلكوينالمبلغ المطلوب (اختياري)قبول الدفع بواسطة البلوتوث.اطلب من المرسل بمسح لهذا الرمز بصريا Scan.أو أنقر جهازا مزودا بتقنية اتصال المدى القريب NFC.تم نسخ طلب المبلغ إلى الحافظةنشر طلب مبلغ الغرسلكوين…
- لم يتم العثور على أي تطبيق بيتكوين آخر
+ لم يتم العثور على أي تطبيق غرسلكوين آخرطلب من البرنامج المحليدفتر العناوينعناوينكالعناوين القديمةإرسال العناوينلا توجد مدخلات في دفتر العناوين
- أرسل بيتكوين إلى عنوان
+ أرسل غرسلكوين إلى عنوانتعديل العنوانحذف العنواننسخ إلى الحافظة
@@ -174,8 +174,8 @@
هناك مشكلة في الكاميرا. ربما تحتاج إلى إعادة تشغيل الجهاز.تعذر قراءة البيانات:\n%sتعذر تصنيف المدخل:\n%s
- عنوان بيتكوين غير صالح:\n%s
- لقد حصلت على عنوان بيتكوين غير صالح! \n(هل يحتمل وجود خلط بين Mainnet / Testnet؟)
+ عنوان غرسلكوين غير صالح:\n%s
+ لقد حصلت على عنوان غرسلكوين غير صالح! \n(هل يحتمل وجود خلط بين Mainnet / Testnet؟)تعذر التحقق من طلب الدفع:\n%sطلب دفع غير صالح:\n%sمعاملة غير صالحة:\n%s
@@ -194,7 +194,6 @@
مكتشف الكتل الخارجي الذي سيتم إستخدامه لعرض التعاملات و العناوين و الكتل.استخدام البياناتإظهار الخيارات لتقييد استخدام البيانات على شبكات الانترنت المحمول اللاسلكية
- تذكير بالميزانيةعرض التنبيههل قمت بقراءة ملاحظات السلامة ؟ و هل عمدت إلى حفظ نسخة إحتياطية لمحفظتك في مكان آمن ؟الإبلاغ عن مشكلة
@@ -222,7 +221,7 @@
رخصةالشيفرة المصدريةهذا التطبيق يَستخدم ...
- Groestlcoinj %s، هو إدخال و دعم لبروتوكول بتكوين
+ Groestlcoinj %s، هو إدخال و دعم لبروتوكول غرسلكوينZXingإعداد تقرير أو تقييم للتطبيقالأسئلة الشائعة
@@ -230,13 +229,13 @@
تم إستلام %sالأقران المتصلة peers %d لا يزال عندك بعضا من الغرسلكوين على هذا الجهاز !
- إن كنت لا تكترث بنقودك فيمكنك التبرع بها إلى مشروع محفظة البتكوين.
+ إن كنت لا تكترث بنقودك فيمكنك التبرع بها إلى مشروع محفظة الغرسلكوين.ذكِّرني لاحقالا تُذكِّرنيالدفعات المُسْتَقْبَلَةالنشاط في الخلفيةالتنبيهات الهامة
- ميزانية البتكوين
+ ميزانية الغرسلكوينحسناإقالة
@@ -252,8 +251,8 @@
لصقنشرتعيين كافتراضي
- طلب بيتكوين
- إرسال بيتكوين
+ طلب غرسلكوين
+ إرسال غرسلكوينمسح بصريالإعداداتعرض رمز الاستجابة السريعة QR-code
diff --git a/wallet/res/values-ca/strings.xml b/wallet/res/values-ca/strings.xml
index 71dc45d74a..bebc3a4e44 100644
--- a/wallet/res/values-ca/strings.xml
+++ b/wallet/res/values-ca/strings.xml
@@ -41,8 +41,7 @@
Notes tècniquesPropina / donarDonar a Groestlcoin Wallet
- Alerta de seguretat
- El component Bluetooth del teu dispositiu és vulnerable. Els teus Groestlcoins en aquest dispositiu estàn en risc, independentment de l\'aplicació que utilitzeu per emmagatzamar-los!\n\nEt suggerim que apaguis el Bluetooth immediatament i comprobis si el fabricant del teu dispositiu ha llançat una actualització que implementi un parche de seguretat nivell \"%s\" o superior.
+ Alerta de seguretatEspai d\'emmagatzematge intern baix!La Cartera de Groestlcoin utilitza emmagatzematge intern per a recordar els blocs i les transaccions. Si es queda sense espai, deixara de funcionar i els teus Groestlcoins estaran en risc!\n\nVols obrir el Gestor d\'Aplicacions per a desinstal·lar aplicacions innecessàries?Gestiona les aplicacions
@@ -246,8 +245,6 @@
Explorador de blocs extern a utilitzar per cercar transaccions, adreces i blocs.Ús de dadesMostra opcions per a restringir l\'ús de dades en xarxes mòbils.
- Recordatori de balanç
- Després de no ser utilitzat durant un parell de setmanes, l\'aplicació us avisarà si encara hi ha monedes a la cartera.Ensenyar renúnciesHas llegit les notes de seguretat? Ja has fet una còpia de seguretat i l\'has guardat en un lloc segur?Avisa d\'un problema
diff --git a/wallet/res/values-cs/strings.xml b/wallet/res/values-cs/strings.xml
index 187e963572..1546e3e1ab 100644
--- a/wallet/res/values-cs/strings.xml
+++ b/wallet/res/values-cs/strings.xml
@@ -45,6 +45,7 @@
Technické informacePřispějDar pro Groestlcoin Wallet
+ Bezpečnostní ohroženíNa vnitřním úložišti není dostatek místa!Groestlcoin Wallet používá vnitřní úložiště pro ukládání transakcí a bloků. Pokud dojde místo v ůložišti, přestane fungovat a vaše Groestlcoiny budou vystaveny riziku ztráty!\n\nChcete otevřít Správu Aplikací a odinstalovat nepotřebné aplikace?Spravovat aplikace
@@ -57,7 +58,8 @@
Verze systému Android je příliš staráJe pravděpodobné, že jedna z následujících verzí Groestlcoin Wallet nebude podporovat vaše zařízení. V některých případech může být v takové situaci komplikované utratit Groestlcoiny na tomto zařízení.\n\nPokud si nejste jistí co dělat, tak doporučujeme tyto Groestlcoiny přemístit brzy do jiného zařízení.Problém s otevřením nastavení.
- Tato částka je poměrně vysoká na nosení v kapse. Převeďte ji prosím na bezpečnější místo.
+ Udržujte nízké riziko
+ Částka v peněžence je pro běžné nošení v kapse poměrně vysoká. Přesuňte prosím část na bezpečnější místo, například do hardwarové peněženky.Zaslat GroestlcoinyStahuji podpis z %s…Načítání žádosti o platbu se nezdařilo
@@ -244,6 +246,7 @@
GRS, 4 desetinná místamGRS, 2 desetinná místaµGRS, bez desetinných míst
+ gro, bez desetinných místVlastní jménoVaše jméno nebo název vaší společnosti, který bude přidán k žádostem o platbu, které vystavíte svým zákazníkům.Snažte se to zkrátit.
@@ -263,12 +266,12 @@
Připojovat pouze k peerům označeným za důvěryhodné.Prohlížeč blokůExterní prohlížeč bloků použitý pro procházení transakcí, adres a bloků.
+ Zobrazit lokální částky
+ Získat směnné kurzy ze zdroje pro výpočet lokálních částek.Využití datOtevřít nastavení pro omezení datového toku na mobilní síti.OznámeníZobrazit možnosti pro zakázání nebo povolení konkrétních oznámení.
- Připomínka bilance
- Upozornění po několika týdnech nepoužití aplikace, pokud jsou v peněžence stále peníze.Zobrazit odmítnutí odpovědnostiOpravdu jste si přečetli bezpečnostní pokyny? Už jste zálohovali peněženku na bezpečné místo?Bluetooth adresa
diff --git a/wallet/res/values-cy/strings.xml b/wallet/res/values-cy/strings.xml
index 0b9394fffe..76e11ab7c8 100644
--- a/wallet/res/values-cy/strings.xml
+++ b/wallet/res/values-cy/strings.xml
@@ -45,10 +45,9 @@
Nodiadau technegolCildwrn / rhoddiRodd-daliad i Groestlcoin Wallet
- Rhybudd diogelwch
- Mae cydran Bluetooth dy ddyfais yn agored i niwed. Mae risg i Fitcoin ar y ddyfais hon, waeth pa bynnag ap wyt ti\'n ei ddefnyddio i\'w cadw.\n\nArgymhellwn troi Bluetooth i ffwrdd ar unwaith a gwirio gyda gwneuthurwr dy ddyfais am ddiweddariad i Android sy\'n gosod cywiriad diogelwch lefel \'%s\' neu ddiweddarach.
+ Rhybudd diogelwchDim llawer o le ar storfa fewnol y ddyfais!
- Mae Waled Groestlcoin yn defnyddio storfa fewnol i gofio trafodion a blociau. Os yw\'n rhedeg allan o le, mi fydd yn stopio gweithio a bydd risg i dy Fitcoin.\n\nWyt ti am agor y rheolwr apiau i ddadosod apiau nad wyt eu hangen?
+ Mae Waled Groestlcoin yn defnyddio storfa fewnol i gofio trafodion a blociau. Os yw\'n rhedeg allan o le, mi fydd yn stopio gweithio a bydd risg i dy Groestlcoin.\n\nWyt ti am agor y rheolwr apiau i ddadosod apiau nad wyt eu hangen?Rheoli apiauGwirio gosodiadau dyddiad ac amserMae amser dy ddyfais i ffwrdd o %d munud. Mae\'n debyg na ellir anfon na derbyn Groestlcoin oherwydd hyn.\n\nDylet wirio ac, os angen, cywiro dy osodiadau dyddiad, amser a chylchfa amser.
@@ -78,7 +77,7 @@
Telir ffi rhwydwaith o %s.Telir ffi blaenoriaeth o %s. Os wyt ti eisiau cadw ffioedd yn isel, defnyddia \'blaenoriaeth\' dim ond pan wyt ti angen cadarnhad cyn gynted â phosib.Mae\'r swm yn rhy fach i\'w anfon.
- Dim digon o Fitcoin ar gel. Rwyt %s yn fyr. Gall hyn fod oherwydd y ffi rhwydwaith.
+ Dim digon o Groestlcoin ar gel. Rwyt %s yn fyr. Gall hyn fod oherwydd y ffi rhwydwaith.Dyw cyfanswm y mân daliadau yn dy waled ddim o werth gellir ei anfon.Dyw taliadau ddim yn bosib ar hyn o bryd am fod ailadrodd ar y gweill.Anfon taliad yn uniongyrchol i\'r talai.
@@ -90,7 +89,7 @@
NôlFfi rhwydwaith sylweddolMae\'r ffi rhwydwaith ar gyfer y taliad hwn (%1$s) yn uchel o\'i gymharu â\'r swm (%2$s). Wyt ti am ei anfon beth bynnag?
- Dim digon o Fitcoin ar gael
+ Dim digon o Groestlcoin ar gaelRwyt %s yn fyr.Wyt ti am dalu gyda phopeth sydd gennyt?Methwyd gwagio\'r waled
@@ -113,7 +112,7 @@
Cwblhawyd.CodiYsgubo waled papur
- Rwyt ar fin ysgubo i waled neu gwpon papur. Bydd hyn yn tynnu\'r holl Fitcoin o\'r papur i dy waled ar y ddyfais hon. Pan fydd y trafodyn wedi\'i gadarnhau, bydd y papur gwerth dim byd ac am resymau diogelwch ni ddylid ei ddefnyddio eto.
+ Rwyt ar fin ysgubo i waled neu gwpon papur. Bydd hyn yn tynnu\'r holl Groestlcoin o\'r papur i dy waled ar y ddyfais hon. Pan fydd y trafodyn wedi\'i gadarnhau, bydd y papur gwerth dim byd ac am resymau diogelwch ni ddylid ei ddefnyddio eto.Defnyddir waledau papur yn bennaf ar gyfer storio oer. Mae rhai peiriannau arian yn y wal yn eu hargraffu ar slip o bapur yn hytrach nag anfon Groestlcoin yn uniongyrchol at dy ddyfais symudol. Mae rhai pobol weithiau yn defnyddio waledi papur i basio gwertho o gwmpas (ni argymhellir hyn).Dechreua gan sganio allwedd breifat waled papur. Defnyddia\'r botwm gweithred camera.Mae\'r allwedd breifat hon wedi\'i diogelu gan gyfrinair.
@@ -127,7 +126,7 @@
Methwyd â llwytho\'r balansMae gennyt gysylltiad gwael â\'r rhwydwaith Electrum-GRS.Mae\'r waled papur yn wag.
- Dim digon o Fitcoin
+ Dim digon o GroestlcoinMae swm y Groestlcoin yn y waled yn rhy fach i\'w ysgubo.Ail-lwytho\'r balansArgymhellir gwaith cynnal a chadw
@@ -135,13 +134,13 @@
Yn dadamgrymptio…Cwblhawyd.Symud
- Gwneud cais am Fitcoin
+ Gwneud cais am GroestlcoinSwm i geisio amdano (opsiynol)Derbyn taliad trwy Bluetooth am brosesu mwy dibynadwyCaiff yr anfonwr sganio\'r cod hwn.Neu dapio ar ddyfais gydag NFC wedi\'i alluogi.Copïwyd y cais Groestlcoin i\'r clipfwrdd
- Rhannu cais am Fitcoin…
+ Rhannu cais am Groestlcoin…Ni chanfuwyd ap Groestlcoin arallCais oddi wrth ap lleolLlyfr cyfeiriadau
@@ -191,7 +190,7 @@
Dim cyfoedion wedi cysylltu⇆ %d meAdfer waled
- Rwyt ar fin newid dy waled gyfredol. Caiff unrhyw Fitcoin yn dy waled eu colli oni bai bod gennyt gopi wrth gefn gwahanol o\'r waled.
+ Rwyt ar fin newid dy waled gyfredol. Caiff unrhyw Groestlcoin yn dy waled eu colli oni bai bod gennyt gopi wrth gefn gwahanol o\'r waled.Pwysig: Paid â llwytho allweddau preifat o ffynhonellau amheus! Os wyt ti, gall eraill gael rheolaeth dros dy gyllid.AdferAdferwyd y waled.
@@ -269,8 +268,6 @@
Dangos opsiynau i gyfyngu defnydd data ar rwydweithiau symudol.HysbysiadauDangos dewisiadau i alluogi neu analluogi hysbysiadau penodol.
- Atogffa o\'r balans
- Ar ôl cwpl o wythnosau heb ei ddefnyddio, bydd yr ap yn rhoi gwybod os oes Groestlcoin dal yn y waled.Dangos ymwadiadWyt ti wir wedi darllen y nodiadau diogelwch? Wyt ti eisoes wedi cadw copi wrth gefn o dy waled i le diogel?Cyfeiriad Bluethooth
@@ -304,7 +301,7 @@
TrwyddedCod wreiddiolMae\'r ap hwn yn defnyddio…
- Groestlcoinj %s, gweithrediad protocol Groestlcoin
+ groestlcoinj %s, gweithrediad protocol GroestlcoinZXing, llyfrgell prosesu codau QRBouncy Castle, llyfrgell cryptograffegOkHttp, llyfrgell cleient HTTP
@@ -346,7 +343,7 @@
GludoRhannuGosod fel rhagosodedig
- Gwneud cais am Fitcoin
+ Gwneud cais am GroestlcoinAnfon GroestlcoinSganioGosodiadau
diff --git a/wallet/res/values-cy/strings_help.xml b/wallet/res/values-cy/strings_help.xml
index 1a6f8deebd..bb1ce00368 100644
--- a/wallet/res/values-cy/strings_help.xml
+++ b/wallet/res/values-cy/strings_help.xml
@@ -16,7 +16,7 @@
Caiff taliadau a dderbyniwyd eu nodi mewn gwyrdd a thaliadau a anfonwyd mewn coch.
- Gellir anfon neu wneud cais am Fitcoin trwy tapio\'r botymau yn y bar gweithredoedd.
+ Gellir anfon neu wneud cais am Groestlcoin trwy tapio\'r botymau yn y bar gweithredoedd.
Mae rhagor o opsiynau ar gael yn y dewislen opsiynau.
]]>
@@ -24,11 +24,11 @@
- Gan ddefnyddio\'r deialog hwn, gellir wneud cais am Fitcoin gan dalwr.
+ Gan ddefnyddio\'r deialog hwn, gellir wneud cais am Groestlcoin gan dalwr.
Rhaid bod y talwr eisoes hefyd wedi gosod waled Groestlcoin.
- Yn gyntaf, gellir rhoi swm o Fitcoin i wneud cais amdano.
+ Yn gyntaf, gellir rhoi swm o Groestlcoin i wneud cais amdano.
Yna, rwyt naill ai\'n cael y talwr i sganio\'r cod QR a gynhyrchwyd.
@@ -76,7 +76,7 @@
Mae Groestlcoin yn cael eu cadw ar y ddyfais.
- Os wyt ti\'n ei golli, mi fyddi\'n colli dy Fitcoin.
+ Os wyt ti\'n ei golli, mi fyddi\'n colli dy Groestlcoin.
Golygir hyn bod angen cadw copi wrth gefn o dy waled!
@@ -84,12 +84,12 @@
Cadwa dy gopi wrth gefn yn ddiogel a chofia dy gyfrinair.
- Cyn dadosod (neu glirio data apiau/ailosod dy ddyfais), trosglwydda dy Fitcoin i waled arall.
+ Cyn dadosod (neu glirio data apiau/ailosod dy ddyfais), trosglwydda dy Groestlcoin i waled arall.
Caiff Groestlcoin sy\'n weddill eu colli.
Ni ellir dad-wneud taliadau.
- Os wyt yn anfon dy Fitcoin i\'r gwagle, does bron dim modd o\'u cael nhw nôl.
+ Os wyt yn anfon dy Groestlcoin i\'r gwagle, does bron dim modd o\'u cael nhw nôl.
Cadwa dy ddyfais yn ddiogel!
diff --git a/wallet/res/values-da/strings.xml b/wallet/res/values-da/strings.xml
index f16ef6be56..871356ca5d 100644
--- a/wallet/res/values-da/strings.xml
+++ b/wallet/res/values-da/strings.xml
@@ -44,13 +44,12 @@
Tekniske noterGiv drikkepenge / donérDonér til Groestlcoin Wallet
- Sikkerhedsadvarsel
- Din enheds Bluetooth-komponent er sårbar. Dine Groestlcoin på denne enhed er i fare, uanset hvilken app du bruger til at opbevare dem!\n\nVi foreslår, at du slukker for Bluetooth med det samme og tjekker hos din producenten af din enhed, om der findes en opdatering til Android-systemet, som indeholder sikkerhedslukning niveau \"%s\" eller senere.
+ SikkerhedsadvarselEnheds interne lagerplads er lav!Groestlcoin Wallet bruger intern lager til at huske overførsler og blokke. Hvis den løber tør for plads, vil den stoppe med at virke og dine Groestlcoins vil være i fare!\n\nVil du åbne app-håndteringen for at afinstallere ubrugte apps?Håndtér appsTjek dato- og tidsindstillinger
- Din enheds tid går %d minutter skævt. Du kan sandsynligvis ikke sende eller modtage Groestlcoin på grund af dette.\n\nDu bør tjekke, og om nødvendigt rette, dine dato-, tids- og tidszoneindstillinger.
+ Din enheds tid går %d minutter skævt. Du kan sandsynligvis ikke sende eller modtage groestlcoin på grund af dette.\n\nDu bør tjekke, og om nødvendigt rette, dine dato-, tids- og tidszoneindstillinger.En ny version er tilgængelig!Denne version retter vigtige fejl. Se ændringsloggen på %s for flere detaljer.Hvis du ikke ser en opdatering, betyder dette formodentlig, at din version af Android ikke understøttes længere.
@@ -77,7 +76,7 @@
Et netværksgebyr på %s vil blive betalt.Et prioritetsgebyr på %s vil blive betalt. Hvis du vil betale så lave gebyrer som muligt, brug da kun \"prioriteret\" hvis du har brug for bekræftelse så hurtigt som muligt.Beløbet er for lille til at blive sendt.
- Ikke nok tilgængelige Groestlcoin. Du mangler %s.
+ Ikke nok tilgængelige groestlcoin. Du mangler %s.Det samlede beløb af bittesmå betalinger i din tegnebog er ikke nok til at kunne sendes.Betalinger er i øjeblikket ikke mulige, da et gennemløb er under udførsel.Send betaling direkte til modtageren.
@@ -89,7 +88,7 @@
TilbageBetydeligt netværksgebyrNetværksgebyret for denne betaling (%1$s) er højt i forhold til beløbet (%2$s). Vil du sende alligevel?
- Ikke nok tilgængelig Groestlcoin
+ Ikke nok tilgængelig groestlcoinDu mangler %s.Vil du betale alt hvad du har?Tømning af tegnebog mislykkedes
@@ -98,7 +97,7 @@
Sender…Sendt!Mislykkedes!
- Problem under afsendelse af Groestlcoin!
+ Problem under afsendelse af groestlcoin!GebyrØkonomiskNormal
@@ -112,8 +111,8 @@
Færdig.ForhøjRyd papirtegnebog
- Du er ved at rydde en papirtegnebog eller kupon. Dette vil flytte alle Groestlcoin fra dette stykke papir til din tegnebog på denne enhed. Når overførslen er bekræftet, vil papiret være værdiløst og bør ikke genanvendes af sikkerhedsgrunde.
- Papirtegnebøger bruges oftest som kold opbevaring. Nogle hæveautomater udskriver dem på deres papirbon\'er frem for at sende Groestlcoins direkte til din mobile enhed. Folk bruger nogle gange papirtegnebøger til at sende værdier omkring (anbefales ikke).
+ Du er ved at rydde en papirtegnebog eller kupon. Dette vil flytte alle groestlcoin fra dette stykke papir til din tegnebog på denne enhed. Når overførslen er bekræftet, vil papiret være værdiløst og bør ikke genanvendes af sikkerhedsgrunde.
+ Papirtegnebøger bruges oftest som kold opbevaring. Nogle hæveautomater udskriver dem på deres papirbon\'er frem for at sende groestlcoins direkte til din mobile enhed. Folk bruger nogle gange papirtegnebøger til at sende værdier omkring (anbefales ikke).Start ved at scanne papirtegnebogens private nøgle. Brug kamera-knappen.Denne private nøgle beskyttes med en adgangskode.kodeord
@@ -126,8 +125,8 @@
Indlæsning af tegnebogens saldo mislykkedesDin forbindelse til Electrum-GRS-netværket er dårlig.Papirtegnebogen er tom.
- Ikke nok Groestlcoin
- Mængden af Groestlcoin i tegnebogen er for lille til rydning.
+ Ikke nok groestlcoin
+ Mængden af groestlcoin i tegnebogen er for lille til rydning.Genindlæs saldoVedligeholdelse anbefalesDu har modtaget %1$s til usikre adresser. Vil du flytte disse til sikre adresser? Et lille netværksgebyr på %2$s vil opkræves.
@@ -156,7 +155,7 @@
Scannet data kan ikke genkendesDen scannede adresse er din egen.Dette er din aktuelle modtagelsesadresse.
- Denne adresse kan være blevet misbrugt. Du bør ikke bruge den til at modtage Groestlcoins længere.
+ Denne adresse kan være blevet misbrugt. Du bør ikke bruge den til at modtage groestlcoin længere.Tilføj afsenderadresseRedigér mærke for afsenderadresseFøj mærke til din adresse
@@ -190,7 +189,7 @@
Ingen peers forbundet⇆ %d msGenopret tegnebog
- Du skal til at erstatte din nuværende tegnebog. Hvis den indeholder Groestlcoin, vil disse mistes, med mindre du har en separat sikkerhedskopi af tegnebogen.
+ Du skal til at erstatte din nuværende tegnebog. Hvis den indeholder groestlcoin, vil disse mistes, med mindre du har en separat sikkerhedskopi af tegnebogen.Vigtigt: Indlæs ikke private nøgler fra tvivlsomme kilder! Andre kan opnå kontrol over dine midler, hvis du gør dette.GenopretTegnebog blev genoprettet.
@@ -228,7 +227,7 @@
Kan ikke læse data:\n%sKan ikke genkende input:\n%sUgyldig Groestlcoin-URI:\n%s
- Modtog ugyldig Groestlcoin-adresse!\n(Forveksler du mainnet/testnet?)
+ Modtog ugyldig groestlcoin-adresse!\n(Forveksler du mainnet/testnet?)Kan ikke verificere betalingsforespørgsel:\n%sUgyldig betalingsforespørgsel:\n%sUgyldig overførsel:\n%s
@@ -263,8 +262,6 @@
Vis indstillinger for restriktion på dataforbrug på mobile netværk.NotifikationerVis muligheder for at aktivere eller deaktivere specifikke notifikationer.
- Saldopåmindelse
- Efter et par uger uden brug vil app\'en give besked, hvis der stadig er penge i tegnebogen.Vis ansvarsfraskrivelseHar du virkelig læst sikkerhedsnoterne? Har du allerede sikkerhedskopieret din tegnebog til et sikkert sted?Bluetooth-adresse
@@ -298,7 +295,7 @@
LicensKildekodeDenne app bruger…
- Groestlcoinj %s, en implementation af Groestlcoin-protokollen
+ groestlcoinj %s, en implementation af Groestlcoin-protokollenZXing, et bibliotek til processering af QR-koderBouncy Castle, et kryptografi-bibliotekOkHttp, et HTTP-klientbibliotek
@@ -340,8 +337,8 @@
IndsætDelSæt som standard
- Modtag
- Send
+ Modtag groestlcoin
+ Send groestlcoinScanOpsætningVis QR-kode
diff --git a/wallet/res/values-de/strings.xml b/wallet/res/values-de/strings.xml
index 6ef55fd72c..00d8dc3e0a 100644
--- a/wallet/res/values-de/strings.xml
+++ b/wallet/res/values-de/strings.xml
@@ -3,6 +3,7 @@
Deine Wallet wurde zurückgesetzt!\nDie Wiederherstellung wird einige Zeit benötigen.Es ist kein Web-Browser installiert um das externe Dokument zu öffnen.
+ Fehler des Web-Browsers: %sEs ist kein passender Netzspeicher-Dienst installiert.\nDu brauchst so etwas wie \"Nextcloud\" oder \"Google Drive\".Nutzung auf eigene Gefahr. Lies die <u>Sicherheitshinweise</u>.Bitte <u>sichere deine Wallet</u>!
@@ -46,8 +47,7 @@
Technische HinweiseSpendenSpende für Groestlcoin Wallet
- Sicherheitswarnung
- Die Bluetooth-Komponente deines Geräts ist verwundbar. Deine Groestlcoins auf diesem Gerät sind in Gefahr, unabhängig davon in welcher App du sie aufbewahrst!\n\nWir empfehlen dir, Bluetooth sofort auszuschalten und bei deinem Gerätehersteller zu prüfen, ob es ein Android-Update gibt, das Sicherheitspatch-Ebene \'%s\' oder später implementiert.
+ SicherheitswarnungInterner Speicher wird knapp!Groestlcoin Wallet nutzt internen Speicher um sich Transaktionen und Blöcke zu merken. Wenn der
Speicher vollläuft, wird es nicht mehr funktionieren und deine Groestlcoins sind in Gefahr!\n\nMöchtest Du die
@@ -64,6 +64,9 @@
Problem beim Öffnen der EinstellungenMinimiere das RisikoDer Betrag in deiner Wallet ist ziemlich hoch, um in der Tasche getragen zu werden. Bitte verschiebe etwas an einen sichereren Ort, etwa in ein Cold Wallet.
+ Empfange Zahlungen im Hintergrund
+ Im Moment ist dein Gerät so eingestellt, dass über eingehende Zahlungen nur informiert wird, wenn Groestlcoin Wallet sichtbar ist – aus Gründen des Akkuverbrauchs.\n\nWir empfehlen, der App zu erlauben, auch im Hintergrund die Blockchain up-to-date zu halten.\n\nKeine Angst, wir achten trotzdem auf deinen Akku.
+ ErlaubenGroestlcoins sendenRufe Signatur von %s ab…Abrufen der Zahlungsanforderung fehlgeschlagen
@@ -133,6 +136,7 @@
Die Paper-Wallet ist leer.Nicht genug GroestlcoinsDas zu leerende Guthaben ist zu gering zum Entleeren.
+ Kann privaten Schlüssel nicht dekodierenGuthaben aktualisierenWartung empfohlenDu hast %1$s auf unsicheren Adressen erhalten. Möchtest du diese Groestlcoins auf sichere Adressen verschieben? Eine geringe Netzwerk-Gebühr von %2$s wird gezahlt.
@@ -179,7 +183,7 @@
Diese Transaktion erhöht die Netzwerk-Gebühr für eine vorherige Zahlung.Diese Zahlung wurde direkt empfangen. Es besteht das Risiko, daß sie niemals verfügbar wird.Die Bestätigung dieser Zahlung ist verspätet, wahrscheinlich aufgrund einer Überlastung des Groestlcoin-Netzes.
- Diese Zahlung sollte in ein paar Minuten verfügbar werden.
+ Dieser Zahlung sollte nicht vertraut werden bis sie bestätigt ist. Bestätigungen benötigen ein paar Minuten.Wegen Wartungsarbeiten am Groestlcoin-Netzwerk sollte dieser Zahlung nicht vertraut werden bis sie voll bestätigt ist.Diese Zahlung hat ein erhöhtes Risiko daß sie vom Sender rückgängig gemacht wird! Warte wenn möglich auf Bestätigung.Diese Zahlung wurde vom Sender rückgängig gemacht.
@@ -274,10 +278,10 @@
Ruft die Wechselkurse von einem Feed ab, um lokale Beträge auszurechnen.DatenverbrauchZeigt Optionen um die mobile Datennutzung einzuschränken.
+ Akkuoptimierung abschalten
+ Um die Blockchain so aktuell wie möglich zu halten, schalte bitte die Akkuoptimierung für diese App ab.BenachrichtigungenZeigt Optionen um bestimmte Benachrichtigungen aus- oder einzuschalten.
- Erinnerung an Guthaben
- Nach einigen Wochen Nicht-Nutzung wird die App daran erinnern wenn sich noch Groestlcoins in der Wallet befinden.Disclaimer anzeigenHast du wirklich die Sicherheitshinweise gelesen? Hast du deine Wallet bereits an einen sicheren Ort gesichert?Bluetooth-Adresse
@@ -311,7 +315,7 @@
LizenzQuellcodeDiese App nutzt…
- Groestlcoinj %s, eine Implementierung des Groestlcoin-Protokolls
+ groestlcoinj %s, eine Implementierung des Groestlcoin-ProtokollsZXing, eine QR-Code-BibliothekBouncy Castle, eine Kryptographie-BibliothekOkHttp, eine HTTP-Client-Bibliothek
diff --git a/wallet/res/values-es/strings.xml b/wallet/res/values-es/strings.xml
index 3a2a094cf2..49381dfb16 100644
--- a/wallet/res/values-es/strings.xml
+++ b/wallet/res/values-es/strings.xml
@@ -22,8 +22,8 @@
saldoPrecio extraído de %sBuscar tasa de cambio
- Aún no se han recibido Groestlcoins.
- Aún no se han enviado Groestlcoins.
+ Aún no se han recibido groestlcoins.
+ Aún no se han enviado groestlcoins.¿Cómo obtener Groestlcoins?\nCámbialos por dinero tradicional,\nvende bienes o servicios o\ngánalos trabajandoPor favor haga una copia de seguridad de su billetera\nantes de recibir Groestlcoins!Enhorabuena. ¡Has recibido tu primer pago! ¿Has hecho ya una <u>copia de seguridad de tu cartera</u> como protección frente a pérdidas?
@@ -43,8 +43,7 @@
Notas técnicasSugerencias / donarDonación para Groestlcoin Wallet
- Alerta de seguridad
- El Bluetooth de tu dispositivo es vulnerable. Tus Groestlcoins en este dispositivo están en riesgo, independientemente de la aplicación que estas utilizando para almacenarlos. Recomendamos apagar tu Bluetooth inmediatamente y revisar por una actualización del sistema operativo Android que implemente parches de seguridad nivel \'%s\' o posteriores.
+ Alerta de seguridadEspacio de almacenamiento interno de dispositivo bajo!Groestlcoin Wallet usa la memoria interna para recordar transacciones y bloques. Si te quedas sin espacio, dejará de funcionar y tus Groestlcoins estarán en riesgo.\n\n¿Quieres abrir el administrador de aplicaciones para desinstalar apps innecesarias?Gestionar aplicaciones
@@ -88,7 +87,7 @@
AtrásComisión de red significativaLa comisión de la red para este pago (%1$s) es elevado en relación al monto (%2$s). Quiere enviarlo de todos modos?
- No hay suficientes Groestlcoins disponibles
+ No hay suficientes groestlcoins disponiblesTe faltan %s.¿Deseas pagar con todo lo que tienes?El vaciado de la cartera falló.
@@ -97,7 +96,7 @@
Enviando…¡Enviado!¡Error!
- ¡Problema enviando Groestlcoins!
+ ¡Problema enviando groestlcoins!ComisiónEconómicoNormal
@@ -111,8 +110,8 @@
Hecho.IncrementarConvertir cartera de papel
- Vas a convertir una cartera de papel. Esto transferirá todos los Groestlcoins del papel a tu cartera en este dispositivo. Cuando la transacción esté confirmada, el papel ya no tendrá valor y, por razones de seguridad, no debería ser reutilizado.
- Las carteras de papel normalmente se utilizan para la el depósito offline. Algunos cajeros ATM los imprimen en el recibo de papel en lugar de enviar los Groestlcoins directamente al dispositivo móvil. A veces se usan carteras de papel prepago para enviar valores a otras personas (no recomendado).
+ Vas a convertir una cartera de papel. Esto transferirá todos los groestlcoins del papel a tu cartera en este dispositivo. Cuando la transacción esté confirmada, el papel ya no tendrá valor y, por razones de seguridad, no debería ser reutilizado.
+ Las carteras de papel normalmente se utilizan para la el depósito offline. Algunos cajeros ATM los imprimen en el recibo de papel en lugar de enviar los groestlcoins directamente al dispositivo móvil. A veces se usan carteras de papel prepago para enviar valores a otras personas (no recomendado).Empieza escaneando la clave de una cartera de papel. Usa el botón de la cámara.Esta clave está protegida con una contraseña.Contraseña
@@ -125,11 +124,11 @@
Error al cargar el saldo de la carteraTienes una mala conexión a la red de Electrum-GRS.La billetera de papel está vacía.
- No hay suficientes Groestlcoins
- La cantidad de Groestlcoins en tu cartera es demasiado pequeña para ser convertidos.
+ No hay suficientes groestlcoins
+ La cantidad de groestlcoins en tu cartera es demasiado pequeña para ser convertidos.recargar el saldoSe recomienda mantenimiento
- Recibíste %1$s a en direcciones no seguras ¿Te gustaría mover estas Groestlcoins a direcciones seguras? Se pagará una pequeña cuota de red de %2$s.
+ Recibíste %1$s a en direcciones no seguras ¿Te gustaría mover estas groestlcoins a direcciones seguras? Se pagará una pequeña cuota de red de %2$s.Desencriptando…Hecho.Mover
@@ -147,7 +146,7 @@
Direcciones antiguasDirecciones para envíosNo hay entradas en la libreta de direcciones
- Enviar Groestlcoins a la dirección
+ Enviar groestlcoins a la direcciónEditar direcciónEliminar direcciónCopiar al portapapeles
@@ -155,7 +154,7 @@
No se reconocen los datos escaneadosLa dirección escaneada es la tuyaEsta es tu dirección actual para recibir.
- Esta dirección podría ya no ser segura. No debería seguir utilizándola para recibir Groestlcoins.
+ Esta dirección podría ya no ser segura. No debería seguir utilizándola para recibir groestlcoins.Añadir dirección de envíoEditar etiqueta de dirección de envíoAñadir nombre a tu dirección
@@ -189,7 +188,7 @@
No hay pares conectados⇆ %d msRestaurar cartera
- Está a punto de reemplazar su cartera actual. Todas las Groestlcoins en la cartera actual se perderán a menos que tenga una copia de seguridad.
+ Está a punto de reemplazar su cartera actual. Todas las groestlcoins en la cartera actual se perderán a menos que tenga una copia de seguridad.Importante: ¡No cargue claves de origen dudoso! Podría perder el control sobre sus fondos.RestaurarLa cartera ha sido restaurado.
@@ -262,8 +261,6 @@
Mostrar opciones para limitar el uso de datos en redes móviles.NotificacionesMostrar opciones para deshabilitar o habilitar notificaciones específicas.
- Aviso de balance
- Después de unas semanas sin uso, la aplicación te notificará que aún tienes Groestlcoins en la billetera.Mostrar disclaimer¿De verdad leíste las notas de seguridad? ¿Ya aseguraste tu monedero en un lugar seguro?Dirección bluethooth
@@ -296,7 +293,7 @@
LicenciaCódigo fuenteEsta aplicación utiliza…
- Groestlcoinj %s
+ groestlcoinj %sZXingBouncy Castle, una biblioteca de criptografíaOkHttp, una biblioteca de cliente HTTP
@@ -338,7 +335,7 @@
PegarCompartirEstablecer como predeterminado
- Pedir
+ Pedir groestlcoinsEnviarExplorarAjustes
diff --git a/wallet/res/values-fi/strings.xml b/wallet/res/values-fi/strings.xml
index 931031de8d..8eb1e5ccbe 100644
--- a/wallet/res/values-fi/strings.xml
+++ b/wallet/res/values-fi/strings.xml
@@ -1,6 +1,8 @@
Lompakkosi nollattiin!\nPalautuminen kestää hetken.
+ Ulkoisen asiakirjan avaamiseen ei ole asennettu verkkoselainta.
+ Mitään sopivaa pilvitallennustarjoajaa ei ole asennettu. \nYou tarvitsee esimerkiksi vastaavan kuin \"Nextcloud\" tai \"Google Drive\".Käytät ohjelmaa omalla vastuulla. Lue <u>turvallisuusohjeet</u>.Sinun on <u>varmuuskopioitava lompakkosi</u>!%1$s, %2$d tuntia takana
@@ -9,8 +11,8 @@
%1$s, %2$d kuukautta takanaSynkronoidaan verkon kanssaSynkronointi kaatunut
- Synkronointi: Tilaongelma
- Synkronointi: Verkko-ongelma
+ Ongelma synkronoinnissa: Vähäinen tallennustila
+ Ongelma synkronoinnissa: Ei verkkoyhteyttäGroestlcoin-osoite on kopioitu leikepöydälleSuojellaksesi yksityisyyttäsi, osoitteesi vaihtuu aina kerran kun se vastaanottaa maksun.Laitteesi on vanha ja turvaton. Käytäthän vain pienten määrien kanssa.
@@ -43,8 +45,7 @@
Tekniset tiedotAnna tippiä tai lahjoitaLahjoitus Groestlcoin Walletille
- Turvallisuusvaroitus
- Laitteesi Bluetooth-komponentti on haavoittuvainen. Tällä laitteella olevat Groestlcoinit ovat uhan alla riippumatta ohjelmasta jossa säilytät niitä. Suosittelemme, että sammutat Bluetoothin välittömästi ja tarkista laitevalmistajaltasi kanssa että Android OS:n päivitys on tasolla \'%s\' tai myöhempi.
+ TurvallisuusvaroitusSisäinen tallennustila on vähissä!Groestlcoin Wallet käyttää sisäistä tallennustilaa muistaakseen varainsiirrot ja lohkot. Jos se ei saa tarpeeksi tallennustilaa, se lakkaa toimimasta ja Groestlcoinisi ovat silloin vaarassa!\n\nHaluatko avata ohjelmien hallinan poistaaksesi tarpeettomia ohjelmia?Hallitse sovelluksia
@@ -154,6 +155,7 @@
Skannaa osoiteSkannattua tietoa ei tunnistetaSkannattu osoite on oma osoitteesi.
+ Tämä on nykyinen osoitteesi vastaanottamista varten.Tämä osoite on saatettu murtaa. Sinun ei tulisi enää käyttää sitä varojen vastaanottoon.Lisää lähetysosoiteMuokkaa lähetysosoitteen nimikettä
@@ -231,6 +233,9 @@
Kelvoton maksupyyntö:\n%sKelvoton rahansiirto:\n%sAsetukset
+ Yleinen
+ Yksityisyyden hallinta
+ KauppiaatDiagnostiikkaLaboratoriotNimellisarvo ja tarkkuus
@@ -241,9 +246,12 @@
mGRS, 2 desimaaliaµGRS, ei desimaaliaOma nimi
- Nimeä itsesi lisätäksesi nimesi maksupyyntöihin. Yritä pitää se lyhyenä.
+ Nimesi tai yrityksesi nimi lisätään asiakkaille lähettämiisi maksupyyntöihin.
+ Pidä se lyhyenä.Sulje kolikoiden lähetysikkuna automaattisestiKun maksu on suoritettu, lähetysikkuna sulkeutuu automaattisesti.
+ Tietojen käyttö vs. Yksityisyys
+ Valitse vähäinen datankäyttö ja parempi yksityisyys synkronoidessasi verkon kanssa.Luotettu vertainenYhdistettävän vertaisen IP-osoite tai isäntänimi.Selvitetään…
@@ -256,8 +264,6 @@
Näytä asetukset rajoittaaksesi datan käyttöä mobiiliverkoissa.IlmoituksetNäytä valinnat ottaaksesi käyttöön tiettyjä ilmoituksia.
- Saldomuistutin
- Kun käyttöä ei ole muutamaan viikkoon, sovellus ilmoittaa mikäli lompakossa on vielä kolikoita jäljellä.Näytä vastuuvapauslausekeOletko varmasti lukenut turvallisuusohjeet? Varmuuskopioitko jo lompakkosi turvalliseen paikkaan?Ilmoita ongelmasta
diff --git a/wallet/res/values-fr/strings.xml b/wallet/res/values-fr/strings.xml
index b31e5edb1a..2b692481dd 100644
--- a/wallet/res/values-fr/strings.xml
+++ b/wallet/res/values-fr/strings.xml
@@ -24,10 +24,10 @@
soldePrix de %sChercher le taux de change
- Aucun Groestlcoin reçu jusqu’à présent.
- Aucun Groestlcoin envoyé jusqu’à présent.
- Comment obtenir des Groestlcoins ?\nÉchangez-en contre de l’argent traditionnel,\nvendez des biens ou des services ou\ngagnez-en en travaillant.
- Veuillez sauvegarder votre porte-monnaie\navant de recevoir des Groestlcoins !
+ Aucun groestlcoin reçu jusqu’à présent.
+ Aucun groestlcoin envoyé jusqu’à présent.
+ Comment obtenir des groestlcoins ?\nÉchangez-en contre de l’argent traditionnel,\nvendez des biens ou des services ou\ngagnez-en en travaillant.
+ Veuillez sauvegarder votre porte-monnaie\navant de recevoir des groestlcoins !Félicitations, vous avez reçu votre premier paiement ! Avez-vous déjà <u>sauvegardé votre porte-monnaie</u> afin de vous protéger contre une perte ?Astuce : Pour augmenter la sécurité de votre porte-monnaie, vous pouvez <u>chiffrer votre appareil</u>. Cela protège aussi les données d’autres applis.Le réseau Groestlcoin fait l’objet de maintenance. Il vous est conseillé de ne pas envoyer ni de recevoir de pièces tant que la maintenance ne sera pas terminée. <u>Plus de renseignements.</u>
@@ -45,23 +45,22 @@
Notes techniquesFaire un donDon pour Groestlcoin Wallet
- Alerte de sécurité
- Le composant Bluetooth de votre appareil est vulnérable. Vos Groestlcoins sont exposés à des risques sur cet appareil, quelle que soit l’appli que vous utilisez pour les stocker !\n\nNous vous suggérons de désactiver Bluetooth immédiatement et de vérifier auprès du fabricant de votre appareil la disponibilité d’une mise à jour pour le SE Android qui met en œuvre le correctif de sécurité niveau \'%s\' ou ultérieur.
+ Alerte de sécuritéL’appareil manque d’espace de stockage interne !
- Groestlcoin Wallet utilise la mémoire interne pour se rappeler des transactions et des blocs. S’il manque d’espace, il cessera de fonctionner et vos Groestlcoins seront à risque !\n\nVoulez-vous ouvrir le gestionnaire d’applications pour désinstaller des applis inutiles ?
+ Groestlcoin Wallet utilise la mémoire interne pour se rappeler des transactions et des blocs. S’il manque d’espace, il cessera de fonctionner et vos groestlcoins seront à risque !\n\nVoulez-vous ouvrir le gestionnaire d’applications pour désinstaller des applis inutiles ?Gérer les applisVérifier les paramètres de date et heure
- L’heure de votre appareil est décalée de %d minutes. Vous ne pouvez probablement pas envoyer ou recevoir de Groestlcoins à cause de ce problème.\n\nVous devriez vérifier vos paramètres de date, heure et de fuseau horaire et si nécessaire les corriger.
+ L’heure de votre appareil est décalée de %d minutes. Vous ne pouvez probablement pas envoyer ou recevoir de groestlcoins à cause de ce problème.\n\nVous devriez vérifier vos paramètres de date, heure et de fuseau horaire et si nécessaire les corriger.Une nouvelle version est proposée !Cette version corrige des bogues importants. Pour plus de détails, veuillez consulter la liste des modifications sur %s.Si vous ne voyez pas de mise à jour, cela signifie probablement que votre version d’Android n’est plus prise en charge.TéléchargerLa version d’Android est obsolète
- Il est probable qu’une prochaine version de Groestlcoin Wallet ne prendra plus en charge votre appareil. Dans certains cas, il peut être difficile de dépenser des Groestlcoins avec cet appareil.\n\nÀ moins de savoir ce que vous faites, il est recommandé de bientôt déplacer vos Groestlcoins.
+ Il est probable qu’une prochaine version de Groestlcoin Wallet ne prendra plus en charge votre appareil. Dans certains cas, il peut être difficile de dépenser des groestlcoins avec cet appareil.\n\nÀ moins de savoir ce que vous faites, il est recommandé de bientôt déplacer vos groestlcoins.Problème d’ouverture des paramètresRéduisez le risqueLe montant dans votre porte-monnaie est plutôt élevé pour être gardé dans votre poche. Veuillez en stocker une partie à froid, en lieu sûr.
- Envoyer des Groestlcoins
+ Envoyer des groestlcoinsRécupération de la signature de %s…Échec de récupération de la demande de paiementLe bénéficiaire (%1$s) utilise un protocole de paiement incompatible (raison : %2$s).
@@ -136,13 +135,13 @@
Déchiffrement…Terminé.Déplacer
- Demander des Groestlcoins
+ Demander des groestlcoinsMontant demandé (facultatif)Accepter les paiements par Bluetooth pour un traitement plus fiable.Faites balayer ce code par l’expéditeur.Ou touchez un appareil ayant la NFC.
- La demande de Groestlcoins a été copiée dans le presse-papiers
- Partager la demande de Groestlcoins…
+ La demande de groestlcoins a été copiée dans le presse-papiers
+ Partager la demande de groestlcoins…Aucune autre appli Groestlcoin n’a été trouvéeDemander d’une appli localeCarnet d’adresses
@@ -150,7 +149,7 @@
Anciennes adressesAdresses d’envoiLe carnet d’adresses est vide
- Envoyer des Groestlcoins à une adresse
+ Envoyer des groestlcoins à une adresseModifier l’adresseSupprimer l’adresseCopier dans le presse-papiers
@@ -158,7 +157,7 @@
Les données balayées ne peuvent pas être reconnuesL’adresse balayée est la vôtre.C’est votre adresse actuelle de réception.
- Cette adresse pourrait être compromise. Vous ne devriez plus l’utiliser pour recevoir des Groestlcoins.
+ Cette adresse pourrait être compromise. Vous ne devriez plus l’utiliser pour recevoir des groestlcoins.Ajouter une adresse d’envoiModifier l’étiquette de l’adresse d’envoiAjouter une étiquette à l’adresse
@@ -266,12 +265,12 @@
Empêche la connexion à des pairs autres que le pair de confiance.Explorateur de blocsL’explorateur de blocs externe à utiliser pour consulter les transactions, les adresses et les blocs.
+ Afficher les montants locaux
+ Récupérer les taux de change d’un fil afin de calculer les montants locaux.Utilisation des donnéesMontrer les options de restriction de l’utilisation des données sur les réseaux mobiles.NotificationsAfficher des options pour désactiver ou activer des notifications particulières.
- Rappel de solde
- Après quelques semaines sans utilisation, l’appli vous signalera s’il y a encore des pièces dans le porte-monnaie.Afficher l’avis de non-responsabilitéAvez-vous vraiment lu les consignes de sécurité ? Avez-vous déjà sauvegardé votre porte-monnaie en lieu sûr ?Adresse Bluetooth
@@ -322,7 +321,7 @@
%d pairs connectésconnecténon connecté
- Vous avez encore des Groestlcoins sur cet appareil !
+ Vous avez encore des groestlcoins sur cet appareil !Souvenez-vous que votre solde de %s sera perdu si vous désinstallez l’appli Groestlcoin Wallet sans d’abord envoyer ce solde.Si ces quelques pièces vous importent peu, vous pourriez aussi en faire le don au projet Groestlcoin Wallet.Rappeler + tard
@@ -331,7 +330,7 @@
Paiements reçusActivité d’arrière-planAlertes importantes
- Solde en Groestlcoins
+ Solde en groestlcoinsValiderFermer
@@ -347,8 +346,8 @@
CollerPartagerDéfinir par défaut
- Demande
- Envoi
+ Demande de GRS
+ Envoi de GRSBalayerParamètresMontrer le code QR
diff --git a/wallet/res/values-hu/strings.xml b/wallet/res/values-hu/strings.xml
index 43ccaaed86..886f3f6d81 100644
--- a/wallet/res/values-hu/strings.xml
+++ b/wallet/res/values-hu/strings.xml
@@ -1,6 +1,9 @@
A tárcád alapállapotba került!\nA visszaállítás némi időt vesz igénybe.
+ Nincs böngésző telepítve amivel a külső dokumentum megnyitható.
+ Webböngésző hiba: %s
+ Nincs alkalmas felhőszolgáltatás telepítve.\nAmi hasznos lehet az a \"Nextcloud\" vagy \"Google Drive\".Csak saját felelősségre használd. Olvasd el a <u>biztonsági megjegyzéseket</u>.Szükséges <u>biztonsági mentést készítened a tárcádról</u>!%1$s, %2$d óra van még hátra
@@ -9,10 +12,10 @@
%1$s, %2$d hónap van még hátraSzinkronizálás a hálózattalA szinkronizálás szünetel
- Szinkronizálás: tárolóhely probléma
- Szinkronizálás: Hálózati probléma
+ Probléma a szinkronizáláskor: Kevés a szabad hely
+ Probléma a szinkronizáláskor: Nincs hálózati kapcsolatGroestlcoin cím a vágólapra másolva
- A magánéleted megvédéséhez a címed megváltozik, amint beérkezik egy fizetés.
+ A magánéleted védelmében a címed megváltozik, amint beérkezik egy fizetés.A készüléked régi és nem biztonságos. Kérlek csak kis összegekhez használd.A frissítés során az egyenleg nem elérhető.Árfolyamok
@@ -25,9 +28,10 @@
Eddig nem érkezett Groestlcoin.Eddig nem lett küldve Groestlcoin.Groestlcoint szerezhetsz\nhagyományos pénz eladásával,\náruért/szolgáltatásért cserébe,\nvagy munkával.
- Készíts biztonsági mentést a tárcádról,\nmielőtt Groestlcoint fogadsz!
+ Készíts biztonsági mentést a tárcádról,\nmielőtt groestlcoint fogadsz!Gratulálunk, megérkezett az első kifizetésed! Már készítettél <u>biztonsági mentést a tárcádról</u>, hogy megóvd a veszteségtől?Tipp: a tárcád biztonságának növeléséhez <u>titkosíthatod a készüléked</u>. Ez más alkalmazások adatait is védi.
+ A Groestlcoin hálózat jelenleg karbantartás alatt áll. Nem javasolt érméket küldeni vagy fogadni a karbantartás ideje alatt. <u>További információ.</u>bányászottbelsőSzűrő
@@ -42,13 +46,12 @@
Technikai megjegyzésekBorravaló / támogatásTámogatás a Groestlcoin Wallet-nek
- Biztonsági figyelmeztetés
- Eszközöd Bluetooth egysége sérülékenységet tartalmaz. Az ezen az eszközön tárolt groestlcoinjaid veszélyben vannak, függetlenül a tárolásukra használt apptól!\n\nAzt javasoljuk, azonnal kapcsold ki a Bluetooth-t, és szerezz be az eszközöd gyártójától egy olyan Android rendszerfrissítést, amelynek a biztonsági javítás szintje legalább \'%s\'.
+ Biztonsági figyelmeztetésKevés a hely a készülék belső tárolóján!A Groestlcoin Wallet belső tárhelyet használ a tranzakciók és blokkok megjegyzéséhez. Ha kifogy a helyből, leáll a működése és a Groestlcoin-jaid veszélyben lesznek!\n\nMeg akarod nyitni az Alkalmazáskezelőt, hogy eltávolítsd a szükségtelen alkalmazásokat?Alkalmazások kezeléseDátum & időbeállítások ellenőrzése
- A készülék időd késik %d percet. Valószínűleg nem tudsz küldeni és fogadni Groestlcoin-okat ezen probléma miatt.\n\nLe kellene ellenőrizned, és ha szükséges, akkor javítanod a dátum, idő és időzóna beállításaidat.
+ A készülék órája késik %d percet. Valószínűleg nem tudsz küldeni és fogadni Groestlcoin-okat ezen probléma miatt.\n\nLe kellene ellenőrizned, és ha szükséges, akkor javítanod a dátum, idő és időzóna beállításaidat.Egy új verzió elérhető!Ez a verzió fontos javításokat tartalmaz. Részletekért lásd a változásnaplót itt: %sHa nem látsz egy frissítést, az valószínűleg azt jelenti, hogy az Androidod verziója többé nem támogatott.
@@ -56,10 +59,15 @@
Ez az Android verzió elavultVan esély arra, hogy a Groestlcoin Wallet következő kiadásaiból egy már nem fogja többé támogatni a készüléked. Egyes esetekben bonyolult lehet érméket küldeni ezen a készüléken.\n\nHacsak nem vagy tisztában vele, hogy mit csinálsz, akkor minél előbb ajánlott az érméid átmozgatása.Probléma a beállítások megnyitásával
- Ez az összeg igen magas ahhoz, hogy a zsebedben hordd. Kérlek helyezd át egy részét biztonságosabb helyre.
+ Csökkentsd a kockázatot
+ A tárcádban tárolt összeg meglehetősen magas ahhoz képest, hogy mindig a zsebedben hordd. Kérjük küldd el egy részét egy biztonságosabb helyre, például egy hidegtárcába.
+ Kifizetések fogadása a háttérben
+ Jelenleg a készüléked beállításai miatt a beérkező kifizetések értesítései csak aktív Groestlcoin Wallet mellett jelennek meg – akkumulátorhasználat csökkentése miatt.\n\nAzt javasoljuk engedélyezd az alkalmazásnak a blokklánc folyamatos figyelését a háttérben is.\n\nNe aggódj, figyelünk az akkumulátorra is.
+ EngedélyezGroestlcoin küldéseAláírás begyűjtése tőle: %s…Fizetési kérelem begyűjtése sikertelen
+ A kedvezményezett (%1$s) nem támogatott fizetési protokollt használ (indok: %2$s).Helytelen aláírás!ellenőrizve általa: %sismeretlen
@@ -70,7 +78,9 @@
egész\ncím(%s megerősítésre vár)Fizetendő összeg
+ Gazdaságos díjként %s lesz fizetve. Fontos: csak akkor használd a \'gazdaságos\' díjszámítást, ha nem számít a megerősítéshez szükséges idő.Hálózati díjként %s lesz fizetve.
+ Elsőbbségi díjként %s lesz fizetve. Fontos: csak akkor használd az \'elsőbbségi\' díjszámítást, ha tényleg szükséges a mielőbbi megerősítés.Az összeg túl kicsi a küldéshez.Nincs elég elérhető érme, hiányzik még %s. Ez lehet a hálózati díj miatt is.A tárcádban lévő apró fizetségek összege nem tesz ki egy küldhető értéket.
@@ -108,7 +118,7 @@
EmelésPapírtárca söpréseArra készülsz, hogy söpörj egy papírtárcát vagy kupont. Ez áthelyezi az összes érmét arról a papírról az ezen a készüléken lévő tárcádba. Amennyiben a tranzakció megerősített, a papír értéktelen lesz és biztonsági okokból nem kéne újra használni.
- A papírtárcák leggyakrabban hűtőháznak vannak használva. Néhány ATM a saját papírfecnijére nyomtatja inkább, minthogy elküldje az érméket közvetlenül a mobilkészülékedre. Az emberek néha előre feltöltött papírtárcát használnak, hogy értéket mozgósítsanak (nem ajánlott).
+ A papírtárcák leggyakrabban hideg tárolónak vannak használva. Néhány ATM a saját papírfecnijére nyomtatja inkább, minthogy elküldje az érméket közvetlenül a mobilkészülékedre. Az emberek néha előre feltöltött papírtárcát használnak, hogy értéket mozgósítsanak (nem ajánlott).Kezdd egy papírtárca privát kulcsának beolvasásával. Használd a kamera akciógombot.Ez a privát kulcs jelszóval van védve.jelszó
@@ -123,6 +133,7 @@
A papírtárca üres.Nincs elég érmeA tárcában lévő összeg túl kevés a söpréshez.
+ Privát kulcs visszafejtése sikertelenEgyenleg újratöltéseKarbantartás ajánlottKaptál %1$s-t nem biztonságos címekre. Szeretnéd áthelyezni ezeket az érméket biztonságos címekre? Egy kis hálózati díj, %2$s kerül kifizetésre.
@@ -132,7 +143,7 @@
Groestlcoin kéréseIgényelt összeg (opcionális)Fizetség elfogadás Bluetooth-on keresztül a megbízhatóbb feldolgozásért
- A kód a küldő által beolvasva.
+ Olvastasd be ezt a kódot a küldővel.Vagy válassz ki egy NFC-képes készüléket.Groestlcoin igény a vágólapra másolvaGroestlcoin kérelem megosztása…
@@ -149,7 +160,8 @@
MásolásCím beolvasásaA beolvasott adat felismerhetetlen
- A beolvasott cím a tiéd.
+ A beolvasott cím a sajátod.
+ Ez a jelenlegi fogadó címed.Ez a cím vélhetőleg hamisítás áldozata lett. Ne használd a későbbiekben érmék fogadására.Küldési cím hozzáadása a címjegyzékhezKüldési cím címkéjének szerkesztése
@@ -160,14 +172,16 @@
MentésHálózati díjEz a kifizetés még nem került elküldésre.
- Ez az összeg jóvá lett írva a tárcádra.
+ Ez az összeg jóváírásra került a tárcádban.Kifizetésed sikeres volt, megérkezett a címzetthez.Ez a kifizetés sikertelen volt.Fizetésed sikertelen volt.Ez a művelet megerősíti a tárcád a lopások ellen. <u>Bővebb információ</u>Ez a tranzakció felemeli a hálózati díjat egy korábbi fizetésért.Ez a fizetés közvetlenül érkezett. Fennáll a veszélye, hogy talán soha nem lesz elkölthető.
- Ez a kifizetés perceken belül hozzáférhetővé válik.
+ Ennek a kifizetésnek a megerősítése késik, valószínűleg a Groestlcoin hálózat leterheltsége miatt.
+ Ez a kifizetés nem tényszerű amíg nincs megerősítve. A megerősítés pár percet vesz igénybe.
+ A Groestlcoin hálózat karbantartása miatt ez a tranzakció nem megbízható amíg teljesen megerősítésre nem kerül.Ennek a kifizetésnek megnövelt a kockázata a küldő általi visszafordításra. Ha tudsz, várj a megerősítésre.A kifizetés visszafordítva a küldő által.Ez a kis összeg valószínűleg soha nem lesz gazdaságosan elköltve.
@@ -178,7 +192,7 @@
BlokkokBányász nehézségi szint változásBányász jutalom feleződés
- csak most
+ épp mostNincs kapcsolat⇆ %d msTárca visszaállítása
@@ -216,14 +230,18 @@
SajnáljukA kamera nem válaszol. Próbáld meg újraindítani a készüléket.Kamera engedély
+ QR kódok beolvasásához engedélyezned kell az alkalmazásnak a kamera használatát.Nem lehet olvasni az adatot:\n%sNem lehet felismerni a bemenetet:\n%sÉrvénytelen Groestlcoin URI:\n%s
- A kapott Groestlcoin cím érvénytelen!\n(Össze van keverve a mainnet/testnet?)
+ A kapott groestlcoin cím érvénytelen!\n(Össze van keverve a mainnet/testnet?)Nem lehet megerősíteni a fizetési kérelmet:\n%sÉrvénytelen fizetési kérelem:\n%sÉrvénytelen tranzakció:\n%sBeállítások
+ Általános
+ Adatvédelem
+ KereskedőkDiagnosztikaLaborPénzegység és tizedesjegyek beállítása
@@ -233,39 +251,54 @@
GRS, 4 tizedeshelymGRS, 2 tizedeshelyµGRS, nincs tizedeshely
+ gro, nincs tizedeshelySaját név
- Saját elnevezésed, ami a fizetési kérelemhez adódik. Próbáld röviden.
+ Saját vagy a céged neve ami a vásárlóid számára kiállított fizetési kérelmekhez lesz csatolva.
+ Fogd rövidre.Érmék küldése párbeszédpanel automatikus bezárásaAmikor a fizetés megtörténik, a küldés párbeszédpanel automatikusan bezáródik.
+ Adathasználat és Magánélet
+ Válassz az alacsony adathasználat vagy a magánéleted megóvása között a szinkronizálás alatt.
+ Alacsony adatforgalom: Hálózati szűrőt alkalmaz, így csak a tárcádhoz kötődő utalásokat olvassa. Ez nagyjából havi 500 MB-ot használ, de közzéteszi a szűrőt a hálózaton.]]>
+ Fokozott magánélet: Semmilyen információt nem közöl a tárcádról a hálózaton. Ne feledd, emiatt minden tranzakciót beolvas, ami havi 10 GB-ot jelent. Használat előtt kérjük ellenőrizd a felhasználható adatforgalmad keretét.]]>Megbízható peerIP cím, vagy állomásnév a közvetlen peer kapcsolódáshoz.
+ Kérjük adj meg egy érvényes IP címet vagy kiszolgáló nevet. Ha Tor-t használsz az onion címek is megfelelnek.
+ Megadhadsz egyszerre több megbízható peer-t is egymástól szóközökkel elválasztva.Megoldás…
- Ismeretlen állomásnév!
+ Ismeretlen kiszolgáló!Szokásos peer felderítés kihagyásaMegakadályozza, hogy a megbízható kapcsolatokon kívül más peer-ekhez kapcsolódjon.Blokk felfedezőKülső blokk felfedező tranzakciók, címek és blokkok böngészésére használva.
+ Helyi pénznem mutatása
+ Árfolyamok lekérdezése adatfolyamból a helyi pénznembe történő átszámoláshoz.AdathasználatOpciók megjelenítése a mobilhálózatokon történő adathasználat korlátozásához.
- Egyenleg emlékeztető
- Néhány hét nem használat után az alkalmazás értesít, ha még vannak érméid a tárcában.
+ Akkumulátor optimalizálás letiltása
+ Ahhoz, hogy mindig naprakész maradjon a blokklánc kérjük tiltsd le az akkumulátor optimalizálását ehhez az alkalmazáshoz.
+ Értesítések
+ Beállítások mutatása az értesítések engedélyezéséhez vagy tiltásához.Figyelmeztetések mutatásaTényleg elolvastad a biztonsági megjegyzéseket? Elmentetted már a tárcádat biztonságos helyre?
+ Bluetooth cím
+ Bluetooth cím kézi beállítása így fogadhatsz másoktól kifizetéseket külön csatlakoztatás nélkül is.
+ Sajnos az Android megakadályozza az eszköz Bluetooth címének automatikus felismerését. Neked kell kikeresni az Android Bluetooth beállításainál majd ide beírni.\n\nKérjük ellenőrizd, hogy minden karakter egyezik. A kettőspontok elhagyhatók, kis- és nagybetűk nem számítanak.Hiba jelentéseBlokklánc visszaállításaAlaphelyzetbe állítja a blokkláncot, a tranzakciókat és a tárcaegyenleget. A frissítés sokáig tart, szóval csak akkor válaszd ezt, ha tudod, miért teszed.Szeretnéd visszaállítani és visszajátszani a blokkláncot?\n\nEz átmenetileg elrejti a tárca egyenleged és eltávolítja a tranzakciókat. Mindkettő visszaáll a blokklánc szinkronizálás előrehaladtával.Visszaállítás
- extended public key megjelenítése
+ Kiterjesztett nyilvános kulcs megjelenítéseNézd meg a tárcád kiterjesztett nyilvános kulcsát, így tudod importálni másik alkalmazásokba és szolgáltatásokba. Legyél óvatos: ezt téve felfeded a pénzügyi magánéletedet annak az alkalmazásnak.Megosztás…Kiterjesztett nyilvános kulcs
- extended public key megosztása…
+ Kiterjesztett nyilvános kulcs megosztása…Hiba jelentéseAz előző alkalommal a program összeomlott.Hiba jelentése a kiválasztott kifizetésrőlKérjük, fejtsd ki a problémádat elég részletesen ahhoz, hogy reprodukálni lehessen. Ha lehetséges, írj angol nyelven.
- Segítenél nekünk azzal, hogy elküldöd számunkra a hiba részleteit, hogy kijavíthassuk ezt a problémát a jövőben?
+ Elküldöd számunkra a hiba részleteit ezzel segítve, hogy kijavíthassuk ezt a problémát a jövőben?hibaleírásJelentésKészülékinformáció csatolása
@@ -274,7 +307,7 @@
Hiba küldése ezzel…RólunkVerzió
- Az APK SHA256 hash-e
+ Az APK SHA256 ellenőrzőösszegeSzerzői jogLicenszForráskód
@@ -291,13 +324,19 @@
GYIKGyakran ismételt kérdések az alkalmazásrólFogadott mennyiség: %s
+ Szinkronizálás a hálózattal…
+ Szinkronizálás a megbízható peer-el…%d peer kapcsolódva
+ kapcsolódva
+ szétkapcsolvaMég vannak Groestlcoin-jaid ezen a készüléken!Emlékezz, hogy a(z) %s egyenleged el fog veszni, ha eltávolítod a Groestlcoin Wallet alkalmazást anélkül, hogy először elküldenéd.Ha nem érdekelnek az érméid, el is adományozhatod őket a Groestlcoin Wallet projektnek.Emlékeztess későbbNe emlékeztess
+ Bluetooth-on keresztül fizetések fogadására készKapott fizetség
+ HáttérfolyamatokFontos riasztásokGroestlcoin egyenleg
diff --git a/wallet/res/values-hu/strings_help.xml b/wallet/res/values-hu/strings_help.xml
index d3104a2682..60939dee66 100644
--- a/wallet/res/values-hu/strings_help.xml
+++ b/wallet/res/values-hu/strings_help.xml
@@ -104,4 +104,21 @@
]]>
+
+
+ Technikai megjegyzések:
+
+
+ A Groestlcoin Wallet egyszerűsített fizetési igazolást használ és a legtöbb elvégzett munkát tartalmazó láncot követi (néha \"leghosszabb láncnak\", \"nyertes láncnak\" vagy \"Groestlcoin blokkláncnak\" is nevezik).
+ Csak ezen a láncon könyvelt pénzt tudsz elkölteni illetve fogadni.
+ Semmilyen garancia nem vonatkozik a mellékláncokra, azaz ami kevesebb elvégzett munkát tartalmaz.
+
+
+ Mindmáig a mellékláncok kivétel nélkül elég hamar jelentéktelenné váltak.
+ Ha mégis ilyen láncot szeretnél követni, akkor javasolt a \"megbízható peer\" funkció használata, hogy kizárólag ahhoz a groestlcoind verzióhoz csatlakozz, ami csakis a kiválasztott lánc használatát követeli.
+ Ha a tárcád és a megbízható peer közötti kapcsolat nem biztonságos, akkor használj VPN-t a kapcsolat hitelesítéséhez.
+
+ ]]>
+
diff --git a/wallet/res/values-it/strings.xml b/wallet/res/values-it/strings.xml
index ca5bb67623..644372f2fe 100644
--- a/wallet/res/values-it/strings.xml
+++ b/wallet/res/values-it/strings.xml
@@ -1,6 +1,8 @@
Il portafoglio è stato reinizializzato!\nOccorrerà del tempo per ripristinarlo.
+ Non è installato alcun browser Web con cui aprire il documento esterno.
+ Non è installato alcun provider di archiviazione onine adatto.\nÈ necessario qualcosa come \"Nextcloud\" o \"Google Drive\".Usalo a tuo rischio. Leggi le <u>avvertenze per la sicurezza</u>.Hai bisogno di <u>eseguire un backup del tuo portafoglio</u>!%1$s, %2$d ore indietro
@@ -9,8 +11,8 @@
%1$s, %2$d mesi indietroSincronizzazione con la reteSincronizzazione bloccata
- Sincronizzazione: problema di Memorizzazione
- Sincronizzazione: problema di Rete
+ Problema con la sincronizzazione: Poco spazio di archiviazione
+ Problema con la sincronizzazione: nessuna connettività di reteIndirizzo Groestlcoin copiato negli appuntiPer proteggere la tua privacy, il tuo indirizzo cambierà alla ricezione di un pagamento.Il tuo dispositivo è vecchio e insicuro. Si prega di usarlo solo per piccole quantità.
@@ -24,7 +26,8 @@
Ricerca tasso di cambioNessun Groestlcoin ancora ricevutoNessun Groestlcoin ancora inviato
- Come ottenere Groestlcoins?\nBarattali per soldi reali,\nvendi beni o servizi oppure\nricavali lavorando.
+ Come ottenere i Groestlcoin?\nScambiali con la moneta tradizionale,\nvendi prodotti o servizzi o\nguadagnali lavorando.
+ Effettua il backup del tuo portafoglio\nprima di ricevere Groestlcoin!Congratulazioni, hai ricevuto i tuoi primi pagamenti! Hai già <u>eseguito un backup del tuo portafoglio </u>, per proteggerlo dai danni?Suggerimento: per incrementare la sicurezza del tuo portafoglio, puoi <u>criptare il tuo dispositivo</u>. Questo protegge i dati anche delle altre applicazioni.Il network di Groestlcoin è attualmente in manutenzione. Sei pregato di non inviare e non ricevere coins fino alla fine della manutenzione.
@@ -34,6 +37,7 @@
Tutti i pagamentiPagamenti ricevutiPagamenti inviati
+ Richiesta all\'indirizzo legacySalvataggioImposta il PIN per spendere.Cambia il PIN di spesa.
@@ -41,24 +45,24 @@
Note tecnicheMancia / donazioneDonazione per Groestlcoin Wallet
- Avviso di sicurezza
- Il componente tuo dispositivo Bluetooth è vulnerabile. I tuoi Groestlcoins su questo dispositivo sono a rischio, indipendentemente dall\'app che stai usando per salvarli! Suggeriamo di spegnere immediatamente il Bluetooth e richiedere al produttore del vostro dispositivo un aggiornamento del SO Android che implementi una patch di sicurezza di livello \'%s\' o superiore.
+ Avviso di sicurezzaSpazio di memorizzazione interno quasi esaurito!Groestlcoin Wallet usa lo spazio interno per ricordarsi le transizioni e i blocchi. Se non hai più spazio, l\'applicazione smetterà di funzionare e i tuoi Groestlcoin saranno a rischio!\n\nVuoi aprire il gestore delle applicazioni per disinstallare le applicazioni inutili/non necessarie?Gestisci applicazioniControlla data e ora!Il tuo dispositivo è spento da %d minuti. Probabilmente non puoi inviare ne ricevere Groestlcoins per via di questo problema.\n\nDevi controllare e correggere l\'ora e il fuso orario del tuo dispositivo.Una nuova versione è disponibile!
- Questa versione ha corretto bug importanti. Per dettagli, guarda il change log su %s.
+ Questa versione corregge bug importanti. Per i dettagli, consultare il registro delle modifiche in %s.Se non si vede un aggiornamento, questo probabilmente significa che la vostra versione di Android non è più supportata.ScaricaLa versione Android non è aggiornataPotrebbe essere che le prossime versioni di Groestlcoin Wallet non supporterà più il tuo dispositivo. In questi casi, potrebbe essere difficile spendere i Groestlcoin su questo dispositivo.\n\nA meno che tu non sappia quello che stai facendo, ti raccomandiamo di spostare il tuo denaro al più presto.Problema nell\'aprire le impostazioni
- Questo importo è alto per essere portato con se. Si prega di spostarne alcuni in un luogo più sicuro.
+ L\'importo nel tuo wallet è piuttosto elevato da portare in tasca. Per favore spostare una parte in un posto più sicuro, come un cold wallet.Invia GroestlcoinsAcquisizione firma da %s…
- Acquisizione firma fallita
+ Recupero della richiesta di pagamento fallita
+ Il destinatario (%1$s) sta usando un\' incompatibile protocollo di pagamento (Errorre: %2$s).Firma errata!confermato da: %ssconosciuto
@@ -75,7 +79,7 @@
L\'importo è troppo piccolo per l\'invio.Non ci sono abbastanza monete disponibili. Ne mancano %s.La quantità di piccoli pagamenti nel vostro portamonete non raggiunge valori inviabili.
- I pagamenti correnti non sono possibili perché è in corso un replay.
+ I pagamenti correnti non sono possibili perchè è in corso un replay.Invia il pagamento al beneficiarioIl tuo pagamento è stato inviato con successo.Il tuo pagamento via connessione diretta è stato rifiutato.
@@ -83,6 +87,8 @@
Il tuo pagamento sarà trasmesso tramite la rete P2P.InviaIndietro
+ Significanti tasse della rete
+ La tassa della rete per questo pagamento (%1$s) è maggiore rispetto alla quantità (%2$s). Desideri inviare lo stesso?Non abbastanza monete disponibiliManca %s.Vuoi pagare con tutto quello che hai?
@@ -118,6 +124,8 @@
Decrittazione in corso…\n impiega fino a un massimo di 2 minuti.Caricamento saldo…Caricamento saldo del portafoglio fallito
+ Hai una connessione carente con la rete Electrum-GRS.
+ Il portafoglio cartaceo è vuoto.Non hai abbastanza soldi disponibiliLa quantità di coins che hai nel portafoglio è troppo piccoloRicarica saldo
@@ -147,6 +155,7 @@
Scansione indirizzoDati scannerizzati irriconoscibliL\'indirizzo scansionato è tuo.
+ Questo è il tuo indirizzo attuale per la ricezione.Questo indirizzo potrebbe essere compromesso. Non dovresti usarlo più per ricevere denaro.Aggiungi indirizzo invianteModifica etichetta dell\'indirizzo inviante
@@ -157,6 +166,10 @@
SalvaCommissione di reteQuesto pagamento non è ancora stato trasmesso.
+ Questo pagamento è stato accreditato sul tuo portafoglio.
+ Il tuo pagamento è andato a buon fine. Ha raggiunto il beneficiario.
+ Questo pagamento non è andato a buon fine.
+ Il tuo pagamento non è andato a buon fine.Questa transazione rinforza il tuo portafoglio contro i furti. <u>Maggiori dettagli.</u>Questa transazione aumenta l\'imposta della rete per un precedente pagamento.Questo pagamento è stato ricevuto direttamente. C\'è il rischio che non diventi spendibile.
@@ -188,7 +201,7 @@
Il vostro backup sarà cifrato con la password scelta e scritto su una memoria esterna.Il tuo portafoglio è protetto da un PIN di sicurezza. Assicurati di ricordare il PIN assieme alla password di backup!Backup
- Le tue chiavi private sono state salvate con un backup a\n\n%s\n\nMantienile sicure, e mantieni sicura anche la tua password!
+ Il tuo portafoglio è stato salvato fino a %s
L\'unico posto del backup esiste nel tuo dispositivo, stai correndo il rischio di perdere entrambi nello stesso momeno!
In questo caso, assicurati di ricordarti la tua password del backup.
]]>Non è stato possibile effettuare il backup del tuo portafoglio:\n%spasswordancora
@@ -215,11 +228,14 @@
Impossibile leggere i dati:\n%sImpossibile classificare l\'input:\n%sURI Groestlcoin errato:\n%s
- Ricevuto indirizzo Groestlcoin non valido!\n(Hai mescolato la mainnet con la testnet?)
+ Ricevuto indirizzo groestlcoin non valido!\n(Hai mescolato la mainnet con la testnet?)Impossibile verificare la richiesta di pagamento:\n%sRichiesta di pagamento invalida:\n%sTransazione non valida:\n%sImpostazioni
+ Generale
+ Controllo della privacy
+ CommercianteDiagnosticaLaboratoriDenominazione e precisione
@@ -230,11 +246,18 @@
µGRS, 2 cifre decimaliµGRS, nessuna cifra decimaleNome
- Il tuo nome da aggiungere alle richieste di pagamento. Prova a mantenerlo corto.
+ Il tuo nome o il nome della tua azienda, da aggiungere alle richieste di pagamento che invii ai tuoi clienti.
+ Tienilo breve.Auto-chiusura messaggio di invio moneteQuando viene fatto un pagamento, il messaggio di invio si chiuderà automaticamente.
+ Utilizzo dati vs. Privacy
+ Scegli tra basso utilizzo dei dati e privacy migliorata durante la sincronizzazione con la rete.
+ Basso utilizzo dei dati: utilizza il filtro di rete per leggere solo le transazioni rilevanti per il tuo portafoglio. Utilizzerà circa 500 MB al mese, ma rivelerà il filtro alla rete.]]>
+ Privacy migliorata: Non divulgherà alcuna informazione sul tuo portafoglio alla rete. Tieni presente che, poiché tutte le transazioni vengono lette, verranno utilizzati 10 GB al mese. Prima di utilizzare questa opzione, si prega di controllare il proprio piano dati.]]>Peer attendibiliIndirizzo IP o hostname di un singolo peer a cui connettersi.
+ Inserisci un indirizzo IP o un nome host valido. Se hai Tor impostato, puoi usare anche qui un indirizzo onion.
+ È possibile specificare più peer attendibili separandoli con un carattere spazio.Risoluzione…Hostname sconosciuto!Salta il regolare rilevamento dei peer
@@ -243,20 +266,23 @@
Analizzatore di blocchi esterno da utilizzare per esaminare transazioni, indirizzi e blocchi.Utilizzo dei datiMostra le opzioni per restringere l\'utilizzo dei dati su reti mobili.
- Promemoria saldo.
- Dopo un paio di settimane in disuso, l\'app notificherà se ci sono ancora monete nel portafoglio.
+ Notifiche
+ Mostra le opzioni per disabilitare o abilitare notifiche specifiche.Controlla l\'esenzione delle responsabilitàHai veramente letto le note di sicurezza? Hai già fatto il backup del tuo portafogli in un posto sicuro?
+ Indirizzo Bluetooth
+ Configura manualmente il tuo indirizzo Bluetooth in modo da poter ricevere pagamenti da pagatori senza connettività.
+ Purtroppo Android impedisce il rilevamento automatico dell\'indirizzo Bluetooth del dispositivo. Devi cercarlo nelle impostazioni Bluetooth di Android e inserirlo qui.\n\nAssicurati che ogni carattere sia corretto. I due punti possono essere omessi e puoi non tenere conto di differenziare tra caratteri maiuscoli e minuscoli.Segnala un problema
- Reinizializza il block chain
- Reinizializza il block chain, transazioni e conto del portafoglio. Il processo impiegherà un po\'.
- Vorresti riavviare e ripetere il block chain?\n\nQuesto nasconderà temporaneamente il tuo bilancio dal portafoglio, e rimuoverà le transazioni. Entrambi saranno recuperati con la sincronizzazione del block chain.
+ Reinizializza il blockchain
+ Reinizializza il blockchain, transazioni e conto del portafoglio. Il processo impiegherà un po\'.
+ Vorresti riavviare e ripetere il blockchain?\n\nQuesto nasconderà temporaneamente il tuo bilancio dal portafoglio, e rimuoverà le transazioni. Entrambi saranno recuperati con la sincronizzazione del blockchain.Reset
- Mostra extended public key
+ Mostra la chiave pubblica estesaMostra la chiave pubblica estesa del tuo portafoglio, in modo tale che possa essere importata su altre applicazioni e servizi. Fai attenzione: facendo ciò ti priverai della tua privacy monetaria nei confronti dell\'applicazione.Condividi…Chiave pubblica estesa
- Condividi extended public key…
+ Condividi la chiave pubblica estesa…Segnala un problemaCrash precedente rilevatoSegnala il problema sul pagamento selezionato
@@ -270,23 +296,34 @@
Invio della segnalazione…InformazioniVersione
+ SHA256 hash di APKCopyrightLicenzaCodice SorgenteQuesta applicazione fa uso di…
- Groestlcoinj %s, un\'implementazione del protocollo Groestlcoin
+ groestlcoinj %s, un\'implementazione del protocollo GroestlcoinZXing, una libreria di elaborazione di QR-code
- Pagina su %s
+ Bouncy Castle, una libreria di crittografia
+ OkHttp, una libreria HTTP client
+ Moshi, una libreria JSON
+ Guava, una raccolta di validi aiutanti
+ SLF4J, un framework di registrazione
+ %s paginaRecensire o votare l\'appFAQDomande richieste frequentemente riguardo all\'appRicevuti %s
+ Sincronizzazione con la rete…
+ Sincronizzazione con il tuo peer di fiducia…Connesso a %d peers
+ Connesso
+ non connessoHai ancora dei Groestlcoin su questo dispositivo!Ricorda che il tuo saldo di %s andrà perso se disinstalli l\'app Groestlcoin Wallet senza prima averli inviati via.Se non sei interessato ai tuoi soldi, puoi anche donarli al progetto Groestlcoin Wallet.RicordameloNon ricordarmelo
+ Pronto a ricevere pagamenti via BluetoothPagamenti ricevutiAttività in backgroundAvvisi importanti
@@ -306,8 +343,8 @@
IncollaCondividiImposta come predefinito
- Richiedi
- Invia
+ Richiedi Groestlcoins
+ Invia GroestlcoinsAnalizzaImpostazioniMostra codice QR
@@ -317,6 +354,7 @@
(non etichettato)PINPIN errato!
+ Codice QRI/O errore: %sErrore di analisi: %s
diff --git a/wallet/res/values-it/strings_help.xml b/wallet/res/values-it/strings_help.xml
index 4ed899183b..eebc8bf3f7 100644
--- a/wallet/res/values-it/strings_help.xml
+++ b/wallet/res/values-it/strings_help.xml
@@ -3,12 +3,12 @@
- La parte in alto a sinistra dello schermo mostra il tuo saldo in Groestlcoins e nella valuta nazionale selezionata.
- Toccare per visualizzare la lista delle valute nazionali, e toccare una valuta per sceglierla come predefinita.
+ La parte in alto a sinistra dello schermo mostra il tuo saldo in Groestlcoin e nella valuta nazionale selezionata.
+ Clicca per visualizzare la lista delle valute nazionali, e scegli una valuta per selezionarla come predefinita.
Il codice QR sulla destra contiene il vostro indirizzo Groestlcoin e può essere utilizzato per la scansione da parte di un altro soggetto.
- Toccare il codice QR per ingrandirlo.
+ Tocca il codice QR per ingrandirlo.
La parte inferiore dello schermo è occupata dalla lista delle transazioni, inizialmente è vuota.
@@ -16,7 +16,7 @@
Toccare un elemento della lista per aggiungere l\'indirizzo del mittente o del destinatario nella rubrica degli indirizzi.
- È possibile inviare o ricevere Groestlcoins toccando i pulsanti nella barra delle azioni.
+ È possibile inviare o ricevere Groestlcoin cliccando i pulsanti nella barra delle azioni.
Ulteriori opzioni sono disponibili nel menu delle opzioni.
+ Come un portafoglio SPV/light, Groestlcoin Wallet segue la catena della prova di lavoro a volte chiamata \"catena più lunga\", \"catena vincente\" o \"catena di blocchi Groestlcoin\".
+ Riceverai pagamenti in entrata e sarai in grado di pagare solo su quella catena.
+ Non è possibile fornire garanzie sui Groestlcoin sulle catene più corte, ovvero sulle catene con meno prova di lavoro.
+
+
+ In passato, le catene più sono sempre diventate irrilevanti molto rapidamente.
+ Se desideri seguire comunque una catena minore, ti consigliamo di utilizzare la funzione \"peer attendibile\" per connetterti esclusivamente ad un\'implementazione groestlcoind che impone la catena desiderata.
+ Se la rete tra il tuo portafoglio e il peer attendibile non è affidabile, usa una VPN per autenticare quella connessione.
+
+ ]]>
+
diff --git a/wallet/res/values-iw/strings.xml b/wallet/res/values-iw/strings.xml
index 47143071b4..c278d18e26 100644
--- a/wallet/res/values-iw/strings.xml
+++ b/wallet/res/values-iw/strings.xml
@@ -12,7 +12,7 @@
סינכרון עוקבסינכרון: בעיית אחסוןסינכרון:בעיית רשת
- כתובת הביטקוין הועתקה ללוח
+ כתובת הגרוסטלקוין הועתקה ללוחהיתרה אינה זמינה במהלך שחזורשערי המרה(ברירת מחדל)
@@ -24,15 +24,15 @@
נשלחלא נתקבלו ביטקוינים עד כה.לא נשלחו ביטקוינים עד כה.
- איך להשיג ביטקוין ?\nהמר תמורת כסף פיאט,\nמכור סחורות או שירותים או\nהשג על ידי עבודה.
+ איך להשיג גרוסטלקוין ?\nהמר תמורת כסף פיאט,\nמכור סחורות או שירותים או\nהשג על ידי עבודה.ברכותי, קיבלת את התשלום הראשון! האם כבר <u>גיבית את הארנק</u> כדי להגן מפני אבדן?נכרופנימיהוראות בטיחותתרומה
- תרום לארנק ביטקוין
+ תרום לארנק גרוסטלקויןאין מקום פנוי על המכשיר
- ארנק הביטקוין משתמש באחסון הפנימי לצורך שמירת עסקאות ובלוקים. אם נגמר המקום, הוא יפסיק לעבוד והביטקוינים שלך יהיו בסיכון!\n\nהאם אתה רוצה לפתוח את מנהל היישומים ולהסיר יישומים שאינם נחוצים?
+ ארנק הגרוסטלקוין משתמש באחסון הפנימי לצורך שמירת עסקאות ובלוקים. אם נגמר המקום, הוא יפסיק לעבוד והביטקוינים שלך יהיו בסיכון!\n\nהאם אתה רוצה לפתוח את מנהל היישומים ולהסיר יישומים שאינם נחוצים?נהל יישומיםבדוק את הגדרות הזמנים שלךזמן המכשיר מאחר ב %d דקות. אתה כנראה לא יכול לקבל או לשלוח ביטקוינים בגלל בעיה זהו.\n\nכדאי שתבדוק את הגדרות הזמן ואיזור הזמן שלך.
@@ -41,8 +41,8 @@
אם אתה לא רואה עדכון, זה כנראה אומר שגרסאת האנדרואיד שלך אינה נתמכת יותר.הורדהגירסת אנדרויד לא מעודכנת
- יש סיכוי כי מהגרסאות הקרובות של ארנק ביטקוין לא נתמוך במכשירך יותר. במקרים מסוימים יהיה קשה להשתמש במטבעות על המכשיר.\n\nאלא אם אתה יודע מה אתה עושה, מומלץ שתעביר את המטבעות בקרוב.
- שלח ביטקוין
+ יש סיכוי כי מהגרסאות הקרובות של ארנק גרוסטלקוין לא נתמוך במכשירך יותר. במקרים מסוימים יהיה קשה להשתמש במטבעות על המכשיר.\n\nאלא אם אתה יודע מה אתה עושה, מומלץ שתעביר את המטבעות בקרוב.
+ שלח גרוסטלקויןאוסף חתימה מ%s…איסוף חתימה נכשלחתימה שגויה!
@@ -50,9 +50,9 @@
לא ידועשלם להקש כתובת או שם
- כתובת ביטקוין לא חוקית!
+ כתובת גרוסטלקוין לא חוקית!כתובת\nמסובכת
- (%s ביטקוין מחכים לאישור)
+ (%s גרוסטלקוין מחכים לאישור)סכום לתשלוםשלח תשלום ישירות לנמען.התשלום שלך נשלח בהצלחה
@@ -70,13 +70,13 @@
נכשל!בעיה בשליחת מטבעותארנק ריק
- בקש ביטקוין
+ בקש גרוסטלקויןכמות לבקשהקבל תשלום באמצעות Bluetooth לעיבוד מאובטח יותרתן לשולח לסרוק ברקוד זהאו גע עם מכשיר NFCבקשה הועתקה ללוח
- שתף בקשה לביטקוין
+ שתף בקשה לגרוסטלקויןבקשה מיישום מקומיספר כתובותבכתובת שלך
@@ -117,13 +117,13 @@
סיסמאהצג סיסמאשגיאה
- שחזור מפתח ביטקוין
+ שחזור מפתח גרוסטלקויןסליחהבעיה במצלמה, אנא אחתל מחדש את המכשיר שלך.לא יכול לקרוא מידע:\n%sלא יכול לסווג את הקלט:\n%s
- כתובת ביטקוין לא חוקית:\n%s
- כתובת ביטקוין לא חוקית
+ כתובת גרוסטלקוין לא חוקית:\n%s
+ כתובת גרוסטלקוין לא חוקיתלא יכול לאמת בקשת תשלום:\n%sבקשת תשלום לא חוקית:\n%sעסקה לא חוקית:\n%s
diff --git a/wallet/res/values-iw/strings_help.xml b/wallet/res/values-iw/strings_help.xml
index 642049c4e5..97dfa7d591 100644
--- a/wallet/res/values-iw/strings_help.xml
+++ b/wallet/res/values-iw/strings_help.xml
@@ -4,11 +4,11 @@
- הצד העליון של המסך מציג את כתובת הביטקוין הנבחרת שלך, שאנשים אחרים צריכים לדעת כדי לשלוח לך ביטקוינים.
+ הצד העליון של המסך מציג את כתובת הגרוסטלקוין הנבחרת שלך, שאנשים אחרים צריכים לדעת כדי לשלוח לך ביטקוינים.
לחץ כאן כדי לשנות את הכתובת.
- קוד הQR מימין מכיל את כתובת הביטקוין שלך ויכול להסרק על ידי אחרים.
+ קוד הQR מימין מכיל את כתובת הגרוסטלקוין שלך ויכול להסרק על ידי אחרים.
לחץ על קוד הQR כדי לקבל גרסא גדולה יותר שלו.
@@ -30,7 +30,7 @@
- באמצעות התיבה כאן תוכל לבקש מטבעות ממשתמש אחר (שצריך להתקין גם ארנק ביטקוין).
+ באמצעות התיבה כאן תוכל לבקש מטבעות ממשתמש אחר (שצריך להתקין גם ארנק גרוסטלקוין).
ראשית, עלייך להכניס את מספר הביטקוינים שתבקש.
@@ -38,7 +38,7 @@
לאחר מכן, עלייך להציג את קוד הQR שנוצר על ידי המשתמש השני.
או שתוכל לשלוח לו בקשה לשתף איתך את הקוד באמצעות פס הפעילות.
- הוא יצטרך להקליק על כתובת הביטקוין שלה.
+ הוא יצטרך להקליק על כתובת הגרוסטלקוין שלה.
בכל מקרה, למשתמש השני תוצג תיבת דו-שיח לשליחת המטבעות כאשר כל השדות יהיו מלאים מראש.
@@ -50,12 +50,12 @@
התחלת בתהליך שליחת ביטקוינים.
- הגעת לכאן בין אם באמצעות המסך הראשי או על ידי לחיצה על קישור ביטקוין בדפדפן הנייד שלך.
+ הגעת לכאן בין אם באמצעות המסך הראשי או על ידי לחיצה על קישור גרוסטלקוין בדפדפן הנייד שלך.
- קודם כל, הכנס את כתובת הביטקוין שברצונך לשלם לה.
+ קודם כל, הכנס את כתובת הגרוסטלקוין שברצונך לשלם לה.
הכתובת תושלם בצורה אוטומטית מרשימת הכתובות המוכרות בספר הכתובות שלך.
- אתה גם יכול לסרוק קוד QR של כתובות ביטקוין או בקשות לביטקוין על ידי לחיצה על כפתור הQR או בר הפעילות.
+ אתה גם יכול לסרוק קוד QR של כתובות גרוסטלקוין או בקשות לגרוסטלקוין על ידי לחיצה על כפתור הQR או בר הפעילות.
לאחר מכן, תוכל לראות כמה ביטקוינים תוכל להוציא באותו הרגע.
@@ -66,7 +66,7 @@
אתה יכול להכניס תשלום גם כשאינך מחובר לרשת.
- הוא ישלח באתחול הבא של ארנק ביטקוין.
+ הוא ישלח באתחול הבא של ארנק גרוסטלקוין.
אם אתה רוצה שהתשלום יועבר במהרה ודא שאתה מחובר (לדוגמא לחיבור רשת מהיר).
@@ -92,7 +92,7 @@
לפני הסרה (או מחיקה של היישום או המכשיר), העבר את היתרה לארנק אחר.
- ביטקוין משוחרר לא יוחזר.
+ גרוסטלקוין משוחרר לא יוחזר.
תשלומים אינם ניתנים להחזרה.
diff --git a/wallet/res/values-ja/strings.xml b/wallet/res/values-ja/strings.xml
index bb7a78a982..356827731a 100644
--- a/wallet/res/values-ja/strings.xml
+++ b/wallet/res/values-ja/strings.xml
@@ -2,6 +2,7 @@
ウォレットはリセットされました!\n復元には多少時間がかかります。外部文書を開くためのウェブブラウザがインストールされていません。
+ ウェブブラウザのエラー: %s利用できるクラウドストレージプロバイダがインストールされていません。\n\"Nextcloud\"や\"Google Drive\"などが必要です。自己の責任においてご使用下さい。<u>安全に関する注意事項</u>をお読み下さい。<u>ウォレットをバックアップ</u>する必要があります!
@@ -11,8 +12,9 @@
%1$s, %2$dか月遅れネットワークと同期中同期が停滞しています
+ 同期中に問題が発生しました: ストレージの空き容量が足りません。同期中に問題が発生しました: ネットワークに繋がっていません。
- グロストルコインコインアドレスをクリップボードにコピーしました
+ グロストルコインアドレスをクリップボードにコピーしましたプライバシー保護のため、アドレスは支払いを受け取るごとに変化します。このデバイスは古く、安全ではありません。少額のみ扱うようにしてください。再生中は残高情報は利用できません。
@@ -23,13 +25,13 @@
残高%s による価格為替レートの検索
- まだグロストルコインコインを受け取っていません。
- まだグロストルコインコインを送っていません。
- グロストルコインコインを受け取る方法ですか?\n従来の通貨と交換するか、物やサービスを売るか、\n仕事をして得ましょう。
- グロストルコインコインを受け取り始める前に\nウォレットをバックアップしてください!
+ まだグロストルコインを受け取っていません。
+ まだグロストルコインを送っていません。
+ グロストルコインを受け取る方法ですか?\n従来の通貨と交換するか、物やサービスを売るか、\n仕事をして得ましょう。
+ グロストルコインを受け取り始める前に\nウォレットをバックアップしてください!おめでとうございます、初めて支払いを受け取りました。遺失を防ぐために<u>ウォレットをバックアップ</u>していますか?ヒント: ウォレットの安全性を向上させる方法として、<u>デバイスの暗号化</u>もあります。その場合、他のアプリのデータも保護されます。
- グロストルコインコインネットワークはメンテナンス中です。メンテナンスが終わるまではコインの送受信を一切しないようお勧めします。<u>詳細情報</u>。
+ グロストルコインネットワークはメンテナンス中です。メンテナンスが終わるまではコインの送受信を一切しないようお勧めします。<u>詳細情報</u>。採掘内部絞り込み
@@ -44,13 +46,12 @@
技術的補足チップ/寄付Groestlcoin Walletへ寄付する
- セキュリティ警告
- あなたのデバイスのBluetooth機能に脆弱性があります。このデバイスのグロストルコインコインは、コインを保存しているアプリに関わらず危険にさらされています!\n\nBluetoothを直ちにオフにして、セキュリティパッチレベル\'%s\'以降を実装したAndroid OSのアップデートがないかデバイスの製造者に確認してください。
+ セキュリティ警告内部デバイスのストレージ容量が残りわずかです!
- Groestlcoin Walletはトランザクションやブロックを記憶するのに内部ストレージを使用します。もし容量が不足した場合、動作しなくなりあなたのグロストルコインコインが危機にさらされるでしょう!\n\n不要なアプリを削除するためにアプリケーションマネージャを開きますか?
+ Groestlcoin Walletはトランザクションやブロックを記憶するのに内部ストレージを使用します。もし容量が不足した場合、動作しなくなりあなたのグロストルコインが危機にさらされるでしょう!\n\n不要なアプリを削除するためにアプリケーションマネージャを開きますか?アプリの管理日付と時刻の設定を確認してください
- デバイスの時刻が%d分ずれています。この問題のためグロストルコインコインのやり取りができなくなる可能性があります。\n\n日付と時刻とタイムゾーンの設定を確認し、必要であれば修正してください。
+ デバイスの時刻が%d分ずれています。この問題のためグロストルコインのやり取りができなくなる可能性があります。\n\n日付と時刻とタイムゾーンの設定を確認し、必要であれば修正してください。新バージョンが利用可能です!このバージョンでは重大なバグを修正しました。詳細については%sをご覧ください。アップデートが表示されない場合、それはおそらくあなたのバージョンのAndroidはもうサポートされないということです。
@@ -60,7 +61,10 @@
設定を開けませんでしたリスクを軽減しましょうウォレットの残高がポケットに入れて持ち歩くには多すぎます。一部をコールドウォレットなどの安全な場所に移動させてください。
- ビットコインの送金
+ バックグラウンドでの支払いの受け取り
+ 現在この端末はGroestlcoin Walletが表示されているときのみ支払いの受け取りを通知するように設定されています。バッテリー使用量のためです。\n\nこのアプリがバックグラウンドの状態でも常に最新のブロックチェーンに追随するのを許可するよう提案します。\n\n心配しないでください、バッテリーには配慮します。
+ 許可
+ グロストルコインの送金%s から署名の取得中……支払いリクエストの取得に失敗しました受取人 (%1$s) は互換性がない支払いプロトコルを使用しています (原因: %2$s)。
@@ -69,7 +73,7 @@
不明支払先アドレスもしくは名前を入力
- 無効なグロストルコインコインアドレスです!
+ 無効なグロストルコインアドレスです!自分自身に送金しようとしています!複雑な\nアドレス(%sが確認待ちです)
@@ -129,27 +133,28 @@
ペーパーウォレットは空です。充分なコインがありませんウォレット内のコインがスイープするには不十分です。
+ 秘密鍵をデコードできませんでした残高のリロード補修をお勧めします安全でないアドレス宛てに %1$s を受け取りました。このコインを安全なアドレスに移動しますか? 少額ですがネットワーク手数料として %2$s が支払われます。復号中……完了移動
- グロストルコインコインのリクエスト
+ グロストルコインのリクエストリクエストする額(省略可能)より信頼できる手順としてBluetooth経由での支払いを許可このコードを送金者にスキャンさせてください。または、NFCが使用可能なデバイスアをタップしてください。
- グロストルコインコインのリクエストがクリップボードにコピーされました
- グロストルコインコインのリクエストを共有……
- 他のグロストルコインコインアプリが見付かりませんでした
+ グロストルコインのリクエストがクリップボードにコピーされました
+ グロストルコインのリクエストを共有……
+ 他のグロストルコインアプリが見付かりませんでしたローカルアプリからのリクエストアドレスブックあなたのアドレス古いアドレス送金先アドレスアドレスブックにエントリーがありません
- アドレスへグロストルコインコインを送金
+ アドレスへグロストルコインを送金アドレスを編集アドレスを削除クリップボードへコピー
@@ -174,9 +179,9 @@
このトランザクションはウォレットの窃盗に対する強度を上げます。<u>より詳細な情報。</u>このトランザクションは前の支払いのネットワーク手数料を増額します。この支払は直接届きました。利用可能とならないリスクがあります。
- この支払いの確認が遅れています。おそらくグロストルコインコインネットワークの過負荷によるものです。
- この支払は数分程度で利用可能となります。
- グロストルコインコインネットワークがメンテナンス中のため、このトランザクションは完全に確認されるまで信用するべきではありません。
+ この支払いの確認が遅れています。おそらくグロストルコインネットワークの過負荷によるものです。
+ この支払いは確認がなされるまで信頼すべきではありません。確認には数分かかります。
+ グロストルコインネットワークがメンテナンス中のため、このトランザクションは完全に確認されるまで信用するべきではありません。この支払いは、送金者によって引き戻されるリスクが高くなっています! 可能であれば確認されるのを待ってください。この支払は送金者により、引き戻されました。少額すぎて、通常の支払等には使用できないでしょう。
@@ -228,8 +233,8 @@
QRコードをスキャンするためには、カメラの利用許可を与える必要があります。データを読込めませんでした:\n%s入力を認識できません:\n%s
- 無効なグロストルコインコインURI:\n%s
- 無効なグロストルコインコインアドレスを入手しました!\n(メインネット/テストネットの混同?)
+ 無効なグロストルコインURI:\n%s
+ 無効なグロストルコインアドレスを入手しました!\n(メインネット/テストネットの混同?)支払いリクエストの検証が出来ません:\n%s無効な支払のリクエスト:\n%s無効なトランザクション:\n%s
@@ -246,6 +251,7 @@
GRS、小数点以下4桁mGRS、小数点以下2桁µGRS、小数点無し
+ gro、小数点無し自身の名前あなたが発行する支払いリクエストに含めるあなたの名前、またはあなたの会社の名前。短くしてください。
@@ -265,12 +271,14 @@
信頼されたピア以外に接続しない。ブロックエクスプローラトランザクション・アドレス・ブロックを閲覧するための外部のブロックエクスプローラ。
+ 地域の通貨での額を表示
+ 地域の通貨での額を計算するためにフィードから為替レートを取得する。データの使用方法モバイルネットワークにおけるデータ使用制限のオプションを表示する。
+ バッテリー使用量の最適化を無効にする
+ ブロックチェーンを可能な限り最新に保ちたい場合、このアプリに対するバッテリー使用量の最適化を無効にしてください。通知特定の通知を有効/無効にするオプションを表示する。
- 残金通知
- 数週間使われていない場合、まだウォレット内にコインが残っていればアプリが通知を出します。免責条項を表示する本当に安全に関する注意事項を読みましたか? ウォレットを安全な場所にバックアップしましたか?Bluetoothアドレス
@@ -304,7 +312,7 @@
ライセンスソースコードこのアプリは以下のソフトウェアを利用しています。
- Groestlcoinj %s、Groestlcoinプロトコルの実装
+ groestlcoinj %s、Groestlcoinプロトコルの実装ZXing、QRコード処理ライブラリBouncy Castle、暗号ライブラリOkHttp, HTTPクライアントライブラリ
@@ -321,7 +329,7 @@
%dピアが接続済接続されました接続されていません
- まだこの端末にグロストルコインコインが残っています!
+ まだこの端末にグロストルコインが残っています!残金を他の場所に移さずにGroestlcoin Walletアプリをアンインストールした場合、%sの残金は失なわれてしまうことに注意してください。もしコインが必要ないのであれば、Groestlcoin Walletプロジェクトに寄付するという選択肢もあります。後で再び通知する
@@ -330,7 +338,7 @@
支払いの受け取りバックグラウンドでの動作重要なお知らせ
- グロストルコインコイン残高
+ グロストルコイン残高OK閉じる
diff --git a/wallet/res/values-ko/strings.xml b/wallet/res/values-ko/strings.xml
index f232f06016..d8c750a5f0 100644
--- a/wallet/res/values-ko/strings.xml
+++ b/wallet/res/values-ko/strings.xml
@@ -41,7 +41,7 @@
기술 참조 노트팁 / 기부Groestlcoin Wallet 앱의 기부 주소
- 보안 경고
+ 보안 경고저장장치 용량 부족그로스톨코인 지갑은 거래 기록과 블록을 기억하기 위해 저장 장치의 공간을 사용합니다. 저장 공간이 부족할 경우 앱 작동이 중단될 수 있으며, 그로스톨코인 보관과 거래에 문제가 있을 수 있습니다.\n\n불필요한 앱 정리를 위해 어플리케이션 매니저를 여시겠습니까?앱 정리하기
@@ -242,8 +242,6 @@
트랜잭션, 주소, 블럭을 검색하기 위한 외부의 블럭 탐색기 사용데이터 사용량모바일 네트워크에서 사용할 데이터 사용량 제한 설정을 보여줍니다.
- 잔액 알림
- 앱에 잔액이 남아 있는 상태에서 몇 주간 사용을 하지 않으면 앱이 알람을 보냅니다.면책 조항 표시오류 보고블럭체인 재설정
@@ -272,7 +270,7 @@
라이센스소스 코드이 앱이 사용 중인 프로토콜
- Groestlcoinj %s 버전
+ groestlcoinj %s 버전ZXing의 QR코드 처리 라이브러리앱 리뷰 또는 등급 점수 매기러 가기FAQ
diff --git a/wallet/res/values-nb/strings.xml b/wallet/res/values-nb/strings.xml
index a0f4d214ba..cd42f658e3 100644
--- a/wallet/res/values-nb/strings.xml
+++ b/wallet/res/values-nb/strings.xml
@@ -22,9 +22,9 @@
saldoPris fra %sSøk i valutakurser
- Ingen Groestlcoins mottatt ennå.
- Ingen Groestlcoins sendt ennå.
- Hvordan får man Groestlcoins?\nKjøp for tradisjonelle penger,\nselg varer eller tjenester eller\ntjen dem ved å arbeide.
+ Ingen groestlcoins mottatt ennå.
+ Ingen groestlcoins sendt ennå.
+ Hvordan får man groestlcoins?\nKjøp for tradisjonelle penger,\nselg varer eller tjenester eller\ntjen dem ved å arbeide.Gratulerer, du har mottatt din første betaling! Har du <u>tatt en sikkerhetskopi av din lommebok</u>, for å beskytte deg mot tap?Tips: For å øke sikkerheten for lommeboka, kan du <u>kryptere enheten</u>. Dette beskytter også andre apper sine data.Groestlcoin-nettverket er under vedlikehold. Du rådes til å ikke sende eller motta mynter inntil vedlikeholdet er ferdig. <u>Mer informasjon.</u>
@@ -41,13 +41,12 @@
Tekniske notaterTips / DonerDoner til Groestlcoin Wallet
- Sikkerhetsvarsel
- Din enhets blåtann-komponent er sårbar. Dine Groestlcoin på denne enheten er i fare, uavhengig av appen du bruker til å lagre dem!\n\nVi foreslår at du slår av blåtann umiddelbart og sjekker med produsenten av enheten for en oppdatering av Android OS som implementerer sikkerhetsoppdateringsnivå \'%s\' eller nyere.
+ SikkerhetsvarselEnhetens interne lagringsplass er lav!
- Groestlcoin Wallet bruker lagring internt for å huske transaksjoner og blokker. Hvis den løper tom for plass, vil den stoppe å virke og dine Groestlcoins vil være under risiko!\n\nVil du åpne app-håndteringen for å avinstallere ubrukte apps?
+ Groestlcoin Wallet bruker lagring internt for å huske transaksjoner og blokker. Hvis den løper tom for plass, vil den stoppe å virke og dine groestlcoins vil være under risiko!\n\nVil du åpne app-håndteringen for å avinstallere ubrukte apps?Håndter appsSjekk dato- og tidsinnstillinger
- Din enhets tid fraviker med %d minutter. Du kan sannsynligvis ikke sende eller motta Groestlcoin på grunn av dette problemet.\n\nDu bør sjekke og eventuelt korrigere innstillingene for dato, klokkeslett og tidssone.
+ Din enhets tid fraviker med %d minutter. Du kan sannsynligvis ikke sende eller motta groestlcoin på grunn av dette problemet.\n\nDu bør sjekke og eventuelt korrigere innstillingene for dato, klokkeslett og tidssone.En ny versjon er tilgjengelig!Denne versjonen retter viktige feil. For detaljer, se endringsloggen på %s.Hvis du ikke ser en oppdatering, betyr dette sannsynligvis at din versjon av Android ikke støttes lenger.
@@ -56,7 +55,7 @@
Der er risiko for at en av de neste utgivelsene av Groestlcoin Wallet ikke vil støtte din enhet lenger. I noen tilfeller kan det være vanskelig å bruke mynter på denne enheten.\n\nMed mindre du vet hva du gjør, anbefales det å straks flytte dine mynter vekk.Problem med å åpne innstillingerDette beløpet er ganske høyt for å bære rundt i lommen din. Vennligst flytt noe til et tryggere sted.
- Send Groestlcoins
+ Send groestlcoinsHenter signatur fra %s…Henting av signatur mislyktesFeil signatur!
@@ -126,13 +125,13 @@
Dekrypterer…Ferdig.Flytt
- Forespør Groestlcoins
+ Forespør groestlcoinsForespurt beløp (valgfri)Motta betaling via blåtann for mer pålitelig prosesseringFå avsenderen til å skanne denne koden.Eller berør en NFC-aktivert enhet.Groestlcoin-forespørsel kopieret til utklippstavlen
- Del forespørsel etter Groestlcoins…
+ Del forespørsel etter groestlcoins…Ingen andre Groestlcoinapper funnetForespørsel fra lokal appAdressebok
@@ -140,7 +139,7 @@
Gamle adresserSender adresserIngen elementer i adressebok
- Send Groestlcoins til adresse
+ Send groestlcoins til adresseRediger adresseFjern adresseKopier til utklippstavlen
@@ -243,8 +242,6 @@
Ekstern blokkleser som kan brukes til å se gjennom transaksjoner, adresser og blokker.DataforbrukVis innstillinger for restriksjon for dataforbruk på mobile nettverk.
- Saldopåminnelse
- Etter et par uker uten bruk vil appen varsle om det fortsatt er penger i lommeboken.Vis ansvarsfraskrivelseHar du virkelig lest sikkerhetsnotatene? Har du allerede sikkerhetskopiert lommeboken til et trygt sted?Rapporter problem
@@ -282,7 +279,7 @@
Ofte stilte spørsmål om appenMottok %s%d peers tilkoblet
- Du har fortsatt Groestlcoins på denne enheten!
+ Du har fortsatt groestlcoins på denne enheten!Husk at din saldo på %s vil gå tapt hvis du avinstallerer appen Groestlcoin Wallet uten å først sende pengene videre.Hvis du ikke bryr deg om myntene dine, kan du også donere dem til Groestlcoin Wallet.Påminn senere
@@ -306,8 +303,8 @@
Lim innDelSett som standard
- Spør etter
- Send
+ Spør etter mynter
+ Send mynterSkannInnstillingerVis QR-kode
diff --git a/wallet/res/values-nl/strings.xml b/wallet/res/values-nl/strings.xml
index f3a584d4ce..ae72c439af 100644
--- a/wallet/res/values-nl/strings.xml
+++ b/wallet/res/values-nl/strings.xml
@@ -11,8 +11,8 @@
%1$s, %2$d maanden achterSynchroniseren met netwerkSynchronisatie gestremd
- Synchronisatie: Opslagprobleem
- Synchroniseren: Netwerkprobleem
+ Fout tijdens het synchroniseren: Te weinig opslagcapaciteit
+ Fout tijdens het synchroniseren: Geen netwerkverbindingGroestlcoinadres gekopieërd naar klembordOm je privacy te beschermen zal het adres wijzigen zodra een betaling ontvangen is.Je apparaat is oud en onveilig. Gebruik aub alleen voor kleine hoeveelheden.
@@ -26,7 +26,7 @@
Zoek wisselkoersNog geen Groestlcoins ontvangenNog geen Groestlcoins verzonden
- Hoe kom ik aan Groestlcoins?\nKoop voor regulier geld,\nverkoop goederen of diensten of\nverdien door te werken.
+ Hoe verkrijg ik Groestlcoins?\nKoop voor regulier geld,\nverkoop goederen of diensten, of\nverdien door te werken.Maak a.u.b. een backup van je portemonnee\nvoordat er Groestlcoins worden ontvangen!Gefeliciteerd, u heeft uw eerste betaling ontvangen! Heeft u al een <u>back-up gemaakt van uw portemonnee</u>, om verlies tegen te gaan?Tip: om de veiligheid van je portemonnee te vergroten, kun je <u>je apparaat versleutelen</u>. Dit beschermt ook de gegevens van andere apps.
@@ -45,24 +45,23 @@
Technische aantekeningenGeef fooi / DoneerDonatie aan Groestlcoin Wallet
- Beveiligings alarm
- Bluetooth op je apparaat is kwetsbaar. Je Groestlcoins op je apparaat lopen hierdoor risico ongeacht de app die je gebruikt om ze te bewaren!\n\nWe raden aan om Bluetooth direct uit te schakelen en contact op te nemen met de fabrikant van je apparaat voor een Android OS update dat beveiligingsniveau patch level \'%s\' of hoger heeft.
+ Beveiligings alarmInterne opslag bijna vol!
- Groestlcoin Wallet gebruikt interne opslag voor transacties en blokken. Wanneer er te weinig opslag beschikbaar is zal het stoppen met werken en lopen uw Groestlcoins gevaar!\n\nWilt u de Applicatie beheer instellingen openen om ongebruikte apps te verwijderen?
+ Groestlcoin Wallet gebruikt interne opslag voor transacties en blokken. Wanneer er te weinig opslag beschikbaar is zal het stoppen met werken en lopen uw groestlcoins gevaar!\n\nWilt u de Applicatie beheer instellingen openen om ongebruikte apps te verwijderen?Beheer appsCheck je datum- en tijdsinstellingenDe tijd van je apparaat wijkt af met %d minuten. Hierdoor kun je waarschijnlijk geen Groestlcoins verzenden of ontvangen.\n\nCheck je instellingen voor datum, tijd en tijdzone en pas die indien nodig aan.
- Een nieuwe versie is beschikbaar!
+ Er is een nieuwe versie beschikbaar!Deze versie lost belangrijke fouten op. Voor details, zie het wijzigingslogboek op %s.Wanneer je geen update ziet wordt jouw Android-versie waarschijnlijk niet meer ondersteund.DownloadAndroid-versie is verouderd
- De kans bestaat dat één van de volgende versies van Groestlcoin Wallet uw apparaat niet langer ondersteunen zal. In sommige gevallen kan het lastig worden nog met Groestlcoins te kunnen betalen vanaf dit apparaat.\n\nTenzij u weet wat u doet wordt u aanbevolen om coins elders onder te brengen.
+ De kans bestaat dat één van de volgende versies van Groestlcoin Wallet uw apparaat niet langer ondersteunen zal. In sommige gevallen kan het lastig worden nog met groestlcoins te kunnen betalen vanaf dit apparaat.\n\nTenzij u weet wat u doet wordt u aanbevolen om coins elders onder te brengen.Probleem met het openen van de instellingenDit is relatief veel om op zak te hebben. Verplaats iets naar een veiligere plek.Verzend GRSHandtekening van %s ophalen…
- Ophalen betalingsverzoek is gefaald
+ Ophalen betalingsverzoek is misluktDe betaler (%1$s) gebruikt een incompatibel betalingsprotocol (reden: %2$s).Verkeerde handtekening!geverifieerd door: %s
@@ -114,7 +113,7 @@
VerhoogImporteer papieren walletU staat op het punt een papieren wallet of coupon te scannen. Dit verplaatst alle coins van dat papier naar de portemonnee op dit apparaat. Zodra deze transactie is bevestigd zal het papier zijn waarde hebben verloren en om veiligheidsredenen niet meer opnieuw gebruikt moeten worden.
- Papieren wallets worden meestal gebruikt voor offline opslag. Sommige geldautomaten printen ze uit op een bon in plaats van de coins direct naar uw mobiele apparaat te versturen. Van te voren met Groestlcoins opgewaardeerde papieren wallets worden door sommige mensen gebruikt om waarde aan elkaar door te geven (niet aanbevolen).
+ Papieren wallets worden meestal gebruikt voor offline opslag. Sommige geldautomaten printen ze uit op een bon in plaats van de coins direct naar uw mobiele apparaat te versturen. Van te voren met groestlcoins opgewaardeerde papieren wallets worden door sommige mensen gebruikt om waarde aan elkaar door te geven (niet aanbevolen).Begin met het scannen van de geheime sleutel van de papieren portemonnee. Gebruik de camera actieknop.Deze privé-sleutel is met een wachtwoord beveiligd.wachtwoord
@@ -128,7 +127,7 @@
Je hebt een slechte verbinding met het Electrum-GRS-netwerk.De papieren portemonnee is leegNiet genoeg saldo
- De hoeveelheid Groestlcoins in de wallet is te laag om te kunnen overdragen.
+ De hoeveelheid groestlcoins in de wallet is te laag om te kunnen overdragen.Saldo verversenOnderhoud aanbevolenU ontving %1$s op onveilige adressen. Wilt U deze Groestlcoins overbrengen naar veilige adressen? Kleine netwerkkosten ter grootte van %2$s zal worden afgedragen.
@@ -191,7 +190,7 @@
Geen connecties⇆ %d msHerstel portemonnee
- U staat op het punt uw huidige wallet te vervangen. Tenzij u een losse back-up van uw Wallet heeft gemaakt zullen alle Groestlcoins die nog in uw huidige wallet aanwezig zijn verloren gaan!
+ U staat op het punt uw huidige wallet te vervangen. Tenzij u een losse back-up van uw Wallet heeft gemaakt zullen alle groestlcoins die nog in uw huidige wallet aanwezig zijn verloren gaan!Belangrijk: Laad geen prive-sleutels van twijfelachtige bronnen! Anderen kunnen anders wellicht over je saldo beschikken.HerstellenPortemonnee is hersteld.
@@ -247,7 +246,7 @@
mGRS, 2 decimale getallenµGRS, geen decimalenJe naam
- Je naam, voor om toe te voegen aan betalingsverzoeken. Probeer het kort te houden.
+ Je naam om toe te voegen aan betalingsverzoeken. Probeer het kort te houden.Hou het kort.Dialoog voor verzonden coins automatisch sluiten.Als de betaling is voltooid zal de verzend dialoog automatisch sluiten.
@@ -269,8 +268,6 @@
Toon opties om bandbreedte op mobiele netwerken te beperken.NotificatiesToon opties om specifieke notificaties aan en uit te zetten.
- Saldo herinnering
- Als de app een paar weken niet gebruikt is zal de wallet laten merken dat er nog steeds coins aanwezig.Toon disclaimerHeb je echt de veiligheidsaanwijzingen gelezen? Heb je al op een veilige plek een backup gemaakt van je portemonnee?Bluetoothadres
@@ -281,7 +278,7 @@
Reset blokketen, transacties en wallet-inhoud. Herstel kan enige tijd duren dus doe dit alleen als je weet waarom.Wil je de blokketen resetten en opnieuw laden?\n\nDit zal tijdelijk de wallet-inhoud en transacties laten verbergen. Beiden zullen terugkomen tijdens het synchronisatieproces. Zorg dat je een betrouwbare verbinding hebt met het internet.Reset
- Toon extended public key
+ Toon uitgebreide public keyToon de uitgebreide public key van je portemonnee zodat het geïmporteerd kan worden in andere apps en services. Wees voorzichtig: hiermee geef je je monetaire privacy prijs aan die app.Deel…Extended Public Key
@@ -304,7 +301,7 @@
LicentieBroncodeDeze app gebruikt…
- Groestlcoinj %s, een Groestlcoin protocol implementatie
+ groestlcoinj %s, een Groestlcoin protocol implementatieZXing, een QR-code processing bibliotheekBouncy Castle, een cryptografische bibliotheekOkHttp, een HTTP-client-bibliotheek
@@ -322,7 +319,7 @@
verbondenniet verbondenJe hebt nog steeds Groestlcoins op dit apparaat!
- Onthou dat je saldo van %s verloren zal zijn als je de app verwijdert zonder de Groestlcoins eerst te verzenden.
+ Onthou dat je saldo van %s verloren zal zijn als je de app verwijdert zonder de groestlcoins eerst te verzenden.Als je geen waarde hecht aan je coins zou je deze ook kunnen doneren aan het Groestlcoin Wallet projectOpnieuw vragenNiet herinneren
@@ -346,8 +343,8 @@
PlakkenDelenKies als standaard
- Verzoek
- Verzend
+ Verzoek Groestlcoins
+ Verzend GroestlcoinsScan QRInstellingenToon QR-code
diff --git a/wallet/res/values-nl/strings_help.xml b/wallet/res/values-nl/strings_help.xml
index 499c25cd78..432303e91f 100644
--- a/wallet/res/values-nl/strings_help.xml
+++ b/wallet/res/values-nl/strings_help.xml
@@ -86,7 +86,7 @@
Vergelijkbaar met het verlies van uw portemonnee.
- Voor het verwijderen van Groestlcoin Wallet (of verschonen van appdata of wipe van het apparaat), dient u de Groestlcoins over te zetten naar een andere wallet..
+ Voor het verwijderen van Groestlcoin Wallet (of verschonen van appdata of wipe van het apparaat), dient u de Groestlcoins over te zetten naar een andere portemonnee..
Overblijvende Groestlcoins zullen verloren gaan.
@@ -96,7 +96,7 @@
Houd uw Android apparaat veilig!
Installeer alleen apps die u volledig vertrouwt.
- Kwaadaardige apps zouden kunnen proberen om uw wallet te stelen.
+ Kwaadaardige apps zouden kunnen proberen om uw portemonnee te stelen.
Houd het risico laag!
diff --git a/wallet/res/values-pl/strings.xml b/wallet/res/values-pl/strings.xml
index 8740674ef7..925b0cb77a 100644
--- a/wallet/res/values-pl/strings.xml
+++ b/wallet/res/values-pl/strings.xml
@@ -1,6 +1,8 @@
Twój portfel został zresetowany!\nTo zajmie trochę czasu, aby odzyskać.
+ Nie ma zainstalowanej przeglądarki internetowej do otwierania zewnętrznego dokumentu.
+ Nie zainstalowano odpowiedniego dostawcy pamięci w chmurze.\nPotrzebujesz czegoś takiego jak „Nextcloud” lub „Dysk Google”.Używaj na własne ryzyko. Przeczytaj <u>instrukcje bezpieczeństwa</u>.Musisz zrobić <u>kopię zapasową</u> swojego portfela!%1$s, przestarzały o %2$d godzin
@@ -9,10 +11,11 @@
%1$s, przestarzały o %2$d miesięcySynchronizowanie z sieciąSynchronizacja wstrzymana
- Synchronizowanie: Problem z pamięcią
- Synchronizowanie: Problem z siecią
+ Problem z synchronizacją: mało miejsca na pliki
+ Problem z synchronizacją: brak połączenia z sieciąAdres Groestlcoin skopiowano do schowkaAby chronić twoją prywatność, twój adres będzie zmieniany za każdym razem jak otrzymasz płatność.
+ Twoje urządzenie jest stare i nie do końca bezpieczne. Używaj tylko dla małych kwot.Saldo nie jest dostępne w trakcie odtwarzania.Kursy wymianyNie odnaleziono kursu wymiany.
@@ -24,13 +27,17 @@
Nie otrzymano jeszcze żadnych Groestlcoinów.Nie wysłano jeszcze żadnych Groestlcoinów.Jak zdobyć Groestlcoiny?\nWymień za tradycyjną walutę,\nsprzedawaj dobra bądź usługi lub\nzarabiaj pracując.
+ Utwórz kopię zapasową swojego portfela\nprzed otrzymaniem groestlcoinów!Gratulacje, otrzymałeś swoją pierwszą płatność! Czy już <u>wykonałeś kopię zapasową swojego portfela</u>, by ochronić się przed jej utratą?
+ Wskazówka: aby zwiększyć bezpieczeństwo swojego portfela, możesz <u>zaszyfrować swoje urządzenie</u>. Chroni to również dane innych aplikacji.
+ Sieć Groestlcoin jest w trakcie konserwacji. Radzimy, aby nie wysyłać ani nie odbierać żadnych monet do czasu zakończenia konserwacji. <u>Więcej informacji.</u>wykopanywewnętrznyFiltrWszystkie płatnościOtrzymane płatnościWysłane płatności
+ Prośba na stary adresBezpieczeństwoUstaw PINZmień PIN
@@ -38,7 +45,7 @@
Uwagi techniczneNapiwek / dotacjaDotacje dla Groestlcoin Wallet
- Alert bezpieczeństwa
+ Alert bezpieczeństwaMało przestrzeni w pamięci wewnętrznej!Groestlcoin Wallet używa pamięć wewnętrzną na zapamiętywanie transakcji i bloków. Jak nie będzie miejsca w pamięci, portfel przestanie pracować i Twoje Groestlcoiny będą narażone na ryzyko!\n\nChcesz otworzyć administratora aplikacji i odinstalować niepotrzebne aplikacje?Zarządzaj aplikacjami
@@ -51,9 +58,12 @@
Wersja Androida jest przestarzałaIstnieje szansa że wraz z kolejnymi wydaniami Groestlcoin Wallet zakończy wsparcie dla twojego urządzenia. W niektórych przypadkach wydawanie monet z tego urządzenia może ulec utrudnieniu.\n\nZalecane jest przeniesienie swoich monet w najbliższym czasie, chyba że wiesz co robisz.Problem z otwieraniem ustawień
- Masz dość pokaźną sumę w portfelu. Proszę przenieś część w bezpieczniejsze miejsce.
+ Utrzymuj niskie ryzyko
+ Kwota w portfelu jest dość wysoka, aby nosić ją w kieszeni. Proszę przenieść ją w bezpieczne miejsce, na przykład, do zimnego portfela.Wyślij GroestlcoinyPobieranie sygnatury z %s…
+ Nie udało się pobrać prośby o płatność
+ Odbiorca płatności (%1$s) używa niezgodnego protokołu płatności (przyczyna: %2$s).Nieprawidłowy podpis!zweryfikowane przez: %snieznany
@@ -64,8 +74,13 @@
złożone\nadresy(%s oczekuje na potwierdzenie)Kwota do zapłacenia
+ Opłata ekonomiczna w wysokości %s zostanie opłacona. Ważne: Użyj wariant „ekonomiczny” tylko wtedy, gdy nie zależy Ci na czasie potwierdzenia.
+ Opłata sieci zostanie poniesiona na poziomie %s.
+ Opłacona zostanie opłata priorytetowa w wysokości %s. Jeśli zależy Ci na niskich opłatach, używaj \'priorytetu\' tylko wtedy, gdy potrzebujesz potwierdzenia jak najszybciej.Kwota jest za mała aby ją wysłać.
+ Za mało dostępnych monet. Brakuje ci %s. Może to również wynikać z opłaty sieciowej.Ilość małych płatności w Twoim portfelu łącznie nie wygląda na wartość nadającą się do wysłania.
+ Obecnie płatności nie są możliwe, ponieważ trwa powtórka.Wyślij płatność bezpośrednio do odbiorcyTwoja płatność została pomyślnie wysłana bezpośrednio.Twoja płatność została odrzucona przez bezpośrednie połączenie.
@@ -73,6 +88,8 @@
Twoja płatność zostanie rozesłana poprzez sieć P2P.WyślijCofnij
+ Znacząca opłata sieciowa
+ Opłata sieciowa za tę płatność (%1$s) jest wysoka w stosunku do kwoty (%2$s). Czy mimo to chcesz wysłać?Brak wystarczającej ilości monetBrakuje Ci %s.Czy chcesz zapłacić wszystkimi, które posiadasz?
@@ -88,10 +105,16 @@
NormalnyPiorytetowyOpróżnij portfel
+ Podnieś opłatę sieciową
+ Czy chcesz podnieść opłatę sieciową tej płatności o %s? Sprawi to, że płatność zostanie potwierdzona szybciej.
+ Określanie opłaty sieciowej…
+ Opłata sieciowa tej płatności nie może zostać podniesiona.Odszyfrowanie…Gotowe.ZwiększImportuj papierowy portfel
+ Masz zamiar objąć papierowy portfel lub kupon. Spowoduje to przeniesienie wszystkich monet z tego papieru do Twojego portfela na tym urządzeniu. Po potwierdzeniu transakcji papier będzie bezwartościowy i ze względów bezpieczeństwa nie należy go ponownie wykorzystywać.
+ Portfele papierowe są najczęściej używane do przechowywania w chłodnych pomieszczeniach. Niektóre bankomaty drukują je na kartce papieru, zamiast wysyłać monety bezpośrednio na urządzenia mobilne. Ludzie czasami używają wstępnie naładowanych papierowych portfeli do przekazywania wartości (co nie jest zalecane).Rozpocznij skanowanie klucza prywatnego z papierowego portfela (zimny portfel). Użyj przycisku kamery.Ten klucz prywatny jest chroniony hasłem.hasło
@@ -100,11 +123,15 @@
DeszyfrujZaimportujDeszyfracja…\nMoże zająć do 2 minut.
- Ładowanie salda…
- Błąd ładowania salda
+ Wczytywanie salda…
+ Błąd wczytywania salda
+ Masz złe połączenie z siecią Electrum-GRS.
+ Papierowy portfel jest pusty.Brak wystarczającej ilości monetIlość monet w tym portfelu jest zbyt mała aby zaimportować.Odśwież saldo
+ Zalecana konserwacja
+ Otrzymałeś %1$s na niezabezpieczone adresy. Czy chcesz przenieść te monety pod bezpieczne adresy? Zostanie zapłacona niewielka opłata sieciowa w wysokości %2$s.Deszyfracja…Zakończono.Przenieś
@@ -129,6 +156,7 @@
Zeskanuj adresZeskanowane dane są nierozpoznawalneZeskanowany adres jest Twoim adresem.
+ To jest Twój aktualny adres do odbioru.Ten adres mógł zostać podrobiony. Nie powinieneś go więcej używać do odbierania transferów.Dodaj adres odbiorcyEdytuj etykietę dla adresu wysyłającego
@@ -139,15 +167,26 @@
ZapiszOpłata sieciTa płatność nie została jeszcze przesłana.
+ Ta płatność została zaksięgowana w Twoim portfelu.
+ Twoja płatność się powiodła. Dotarła do odbiorcy.
+ Ta płatność nie powiodła się.
+ Twoja płatność nie powiodła się.Ta transakcja wzmocni Twój portfel przed kradzieżą. <u>Więcej informacji</u>
+ Ta transakcja podnosi opłatę sieciową za poprzednią płatność.
+ Ta płatność została otrzymana bezpośrednio. Istnieje ryzyko, że nigdy nie będzie można jej wydać.Potwierdzenie tej płatności jest opóźnione, prawdopodobnie z powodu przeciążenia sieci Groestlcoin.Ta płatność powinna stać się dostępna za kilka minut.Ze względu na niedostępność sieci Groestlcoin, transakcja ta nie powinna być zaufana, dopóki nie zostanie w pełni potwierdzona.
+ Ta płatność wiąże się ze zwiększonym ryzykiem cofnięcia przez nadawcę! Jeśli możesz, poczekaj na potwierdzenie.Ta płatność została cofnięta przez nadawcę.Tak mała kwota prawdopodobnie nigdy nie zostanie wydana ekonomicznie.
+ Ta płatność jest opłacana do wielu portfeli poza Twoim, co powoduje, że aplikacja z czasem zwalnia. Jeśli możesz, spróbuj otrzymywać płatności, które płacą tylko Tobie.
+ Ta płatność została opóźniona, ponieważ nadawca użył niezabezpieczonego typu transakcji.Monitor sieciPołączeniaBloki
+ Regulacja trudności wydobycia
+ Zmniejszenie o połowę nagrody za wydobyciewłasnie terazBrak połączonych partnerów⇆ %d ms
@@ -155,10 +194,10 @@
Zamierzasz zastąpić aktualny portfel. Wszystkie monety z tego portfela zostaną utracone jeśli nie zrobiono jego kopii bezpieczeństwa.Ważne: Nie importuj kluczy z niepewnych źródeł! W przeciwnym razie inni mogą przejąć kontrolę nad twoimi środkami.Odzyskaj
- Nie udało się zaimportować kluczy prywatnych:\n\n%s\n\nBłędne hasło?Portfel został odzyskany.Twój portfel został pomyślnie przywrócony. Stan konta zostanie ustalony w następnej kolejności. Zajmie to trochę czasu.Ostrzeżenie: Twój portfel jest chroniony kodem PIN. Upewnij się, że pamiętasz kod przed otrzymaniem środków na ten portfel.
+ Nie udało się zaimportować kluczy prywatnych:\n\n%s\n\nBłędne hasło?Kopia zapasowa portfelaKopia zapasowa zostanie zaszyfrowana przy użyciu wybranego hasła i zapisana w pamięci zewnętrznej.Twój portfel jest chroniony kodem PIN. Upewnij się, że pamiętasz kod PIN wraz z hasłem do odzyskiwania kopii zapasowej portfela!
@@ -185,41 +224,69 @@
Zakończono.PrzepraszamNastąpił problem z aparatem fotograficznym. Prawdopodobnie musisz zrestartować urządzenie.
- Otrzymano niepoprawny adres Groestlcoin!\n(Pomylono Testnet z Prodnet?)Uprawnienia do aparatuAby zeskanować QR kod musisz przyznać aplikacji uprawienie do aparatu.Nie można odczytać danych:\n%sNie rozpoznano wejścia:\n%sNieprawidłowe Groestlcoin URI:\n%s
+ Otrzymano niepoprawny adres Groestlcoin!\n(Pomylono Testnet z Prodnet?)Nie można zweryfikować żądania płatności:\n%s Nieprawidłowe żądanie płatności:\n%sNieprawidłowa transakcja:\n%sUstawienia
+ Ogólne
+ Kontrola prywatności
+ KupcyDiagnostykaEksperymentalneWaluta i precyzjaJednostka w której ma być podawana ilość. Nie wpływa na obliczenia.
+ GRS, 8 miejsc po przecinku
+ GRS, 6 miejsc po przecinku
+ GRS, 4 miejsca po przecinku
+ mGRS, 2 miejsca po przecinku
+ µGRS, bez miejsc po przecinku
+ gro, bez miejsc po przecinkuWłasna nazwa
+ Twoje imię i nazwisko lub nazwa firmy, które mają być dodawane do wezwań do zapłaty wysyłanych do klientów.
+ Niech to będzie krótkie.Automatycznie zamknij okno wysyłania monetPo dokonaniu płatności okno wysyłania zostanie zamknięte automatycznie.
+ Korzystanie z danych i prywatność
+ Wybieraj między niskim zużyciem danych i większą prywatnością podczas synchronizacji z siecią.
+ Niskie zużycie danych: Użyj filtrowania sieci, aby odczytać tylko transakcje istotne dla Twojego portfela. Zużyje około 500 MB miesięcznie, ale ujawni filtr sieci.]]>
+ Poprawiona prywatność: nie ujawni w sieci żadnych informacji o Twoim portfelu. Należy pamiętać, że ponieważ wszystkie transakcje są odczytywane, będzie to zużywać ogromne 10 GB miesięcznie. Przed użyciem sprawdź limit danych.]]>Zaufany partnerIP lub nazwa hosta pojedynczego partnera z którym możesz się połączyć.
+ Proszę wpisać prawidłowy adres IP lub nazwę hosta. Jeśli masz skonfigurowany Tor, można również wykorzystać tutaj adres onion.
+ Można wskazać kilka zaufanych węzłów równorzędnych, oddzielając je spacją.Rozwiązywanie…Nieznana nazwa hosta!Pomiń zwykłe odkrywanie węzłówZapobiega łączeniu się ze wszystkimi partnerami poza tym zaufanym.Eksplorator bloków
+ Zewnętrzny eksplorator bloków do przeglądania transakcji, adresów i bloków.
+ Wyświetl lokalne kwoty
+ Pobierz kursy z kanału, aby obliczyć lokalne kwoty.Zużycie danychPokaż opcje ograniczenia transferu danych przez sieci mobilne
+ Powiadomienia
+ Pokaż opcje wyłączenia lub włączenia pewnych powiadomień.Pokaż wyłączenie odpowiedzialnościCzy naprawdę przeczytałeś wskazówki bezpieczeństwa? Czy wykonałeś kopię portfela w bezpiecznym miejscu?
+ Adres Bluetooth
+ Ręcznie skonfiguruj swój własny adres Bluetooth, aby można było otrzymywać płatności od płatników bez połączenia z internetem.
+ Niestety Android uniemożliwia automatyczne wykrywanie adresu Bluetooth Twojego urządzenia. Musisz go wyszukać w ustawieniach Bluetooth Androida i wprowadzić tutaj.\n\nUpewnij się, że każdy znak jest poprawny. Dwukropki można pominąć, a wielkość liter nie ma znaczenia.Zgłoś problemZresetuj łańcuch blokówResetuj łańcuch bloków, transakcje i saldo portfela. Odtworzenie go zajmie trochę czasu.Czy chcesz zresetować i re-synchronizować łańcuch bloków?\n\nTa czynność tymczasowo wyzeruje saldo portfela i usunie transakcje. Zostaną one przywrócone wraz z postępem synchronizacji łańcucha bloków.Resetuj
+ Wyświetl rozszerzony klucz publiczny
+ Wyświetl rozszerzony klucz publiczny swojego portfela, aby można go było importować do innych aplikacji i usług. Bądź ostrożny: spowoduje to ujawnienie Twojej prywatności finansowej w tej aplikacji.Udostępnij…Rozszerzony Klucz Prywatny
+ Udostępnij rozszerzony klucz publiczny…Zgłoś problemWykryto poprzednią awarięZgłoś problem z wybraną płatnością
@@ -233,20 +300,38 @@
Wyślij zgłoszenie używając…OWersja
+ SHA256 hash dla APKPrawa autorskieLicencjaKod źródłowyTa aplikacja używa…
+ groestlcoinj %s, implementacja protokołu Groestlcoin
+ ZXing, biblioteka do przetwarzania kodów QR
+ Bouncy Castle, biblioteka kryptograficzna
+ OkHttp, biblioteka klienta HTTP
+ Moshi, biblioteka JSON
+ Guava, zbiór przydatnych pomocników
+ SLF4J, platforma rejestrowania
+ strona %sWyraź swoją opinię o Aplikacji lub ją oceńFAQCzęsto zadawane pytania o aplikacjiOtrzymano %s
+ Synchronizacja z siecią…
+ Synchronizacja z zaufanym partnerem…%d połączonych partnerów
+ połączono
+ nie połączonoNadal posiadasz GRS na tym urządzeniu!
+ Pamiętaj, że Twoje saldo w wysokości %s zostanie utracone, jeśli odinstalujesz aplikację Groestlcoin Wallet bez uprzedniego jej odesłania.
+ Jeśli nie dbasz o swoje monety, możesz również przekazać je na projekt Groestlcoin Wallet.
+ Przypomnij mi późniejNie przypominaj
+ Gotowość do przyjmowania płatności za pośrednictwem BluetoothOtrzymane płatnościAktywność aplikacji w tle
- Ilość GRS
+ Ważne ostrzeżenia
+ Saldo GRSOKOdrzuć
@@ -262,8 +347,8 @@
WklejUdostępnijUstaw jako domyślne
- Poproś
- Wyślij
+ Poproś o monety
+ Wyślij monetySkanujUstawieniaPokaż kod QR
@@ -273,6 +358,7 @@
(nieopisane)PINBłędny PIN!
+ Kod QRBłąd I/O: %sBłąd parsowania: %s
diff --git a/wallet/res/values-pl/strings_help.xml b/wallet/res/values-pl/strings_help.xml
index f7abfb2af5..22ec8cbe1b 100644
--- a/wallet/res/values-pl/strings_help.xml
+++ b/wallet/res/values-pl/strings_help.xml
@@ -105,5 +105,21 @@
]]>
-
+
+
+ Uwagi techniczne:
+
+
+ Jako portfel SPV/lekki, Groestlcoin Wallet podąża za łańcuchem najmocniejszego dowodu pracy (czasami nazywanym również „najdłuższym łańcuchem”, „łańcuchem wygranych” lub „łańcuchem bloków Groestlcoin”).
+ Będziesz otrzymywać tylko płatności przychodzące i będziesz mógł płacić w tym łańcuchu.
+ Nie możemy udzielać żadnych gwarancji dotyczących Groestlcoinów w sieciach mniejszościowych, czyli łańcuchach z mniejszą liczbą dowodów pracy.
+
+
+ W przeszłości łańcuchy mniejszości zawsze bardzo szybko traciły na znaczeniu.
+ Jeśli mimo wszystko chcesz podążać za łańcuchem mniejszości, zaleca się użycie funkcji \"zaufanego partnera\", aby połączyć się wyłącznie z implementacją groestlcoind, która wymusza pożądany łańcuch.
+ Jeśli sieć między Twoim portfelem a zaufanym partnerem nie jest zaufana, użyj VPN do uwierzytelnienia tego połączenia.
+
+ ]]>
+
diff --git a/wallet/res/values-pt-rBR/strings.xml b/wallet/res/values-pt-rBR/strings.xml
index 1b2bf4ecb3..07935a2888 100644
--- a/wallet/res/values-pt-rBR/strings.xml
+++ b/wallet/res/values-pt-rBR/strings.xml
@@ -27,7 +27,7 @@
Como conseguir Groestlcoins?\nTroque por dinheiro tradicional,\nvenda bens ou serviços ou\nganhe trabalhando.Parabéns, você recebeu o seu primeiro pagamento! Deseja efetuar o <u>backup de sua carteira</u>, para proteger seus Groestlcoins?Dica: para aumentar a segurança de sua carteira, você pode criptografar o seu dispositivo. Isso também protege os dados de outros aplicativos.
- A rede Groestlcoin está sob manutenção. Recomenda-se que você não envie ou receba nenhum Groestlcoin até que a manutenção termine. <u>Mais informações.</u>
+ A rede Groestlcoin está sob manutenção. Recomenda-se que você não envie ou receba nenhum groestlcoin até que a manutenção termine. <u>Mais informações.</u>minadosinternoFiltro
@@ -69,7 +69,7 @@
Uma taxa econômica de %s será paga. Importante: Use o \'econômico\' somente se você não precisa que a transação seja confirmada em breve.Uma taxa de rede de %s será paga.Uma taxa de prioridade de %s será paga. Utilize a \'prioridade\' somente quando você precisar que a confirmação ocorra o mais breve possível.
- A quantidade de Groestlcoins na carteira é muito pequena para enviar.
+ A quantidade de groestlcoins na carteira é muito pequena para enviar.Saldo insuficiente. Faltam %s.A quantidade de pequenos pagamentos em sua carteira não somam um valor para envio.Atualmente os pagamentos não são possíveis porque um replay está em andamento.
@@ -103,8 +103,8 @@
Feito.AumentarVarrer carteira de papel
- Você está prestes a varrer uma carteira de papel ou cupom. Essa função irá mover para o seu dipositivo todos os Groestlcoins contidos na carteira de papel. Quando a transação for confirmada, o endereço Groestlcoin da carteira de papel ficará com saldo zero e você deverá inutilizar a sua carteira de papel por questões de segurança. Nunca reutilize uma carteira de papel antiga para receber novas transações.
- As carteiras de papel podem ser facilmente criadas por qualquer usuários e mais comumente são usadas como uma maneira segura de armazenamento offline (frio) de Groestlcoins. Alguns caixas eletrônicos imprimem carteiras de papel ao invés de enviar diretamente os Groestlcoins para o dispositivo móvel. Algumas pessoas usam carteiras de papel com créditos pré-carregados para passar um valor (método inseguro, não recomendado).
+ Você está prestes a varrer uma carteira de papel ou cupom. Essa função irá mover para o seu dipositivo todos os groestlcoins contidos na carteira de papel. Quando a transação for confirmada, o endereço groestlcoin da carteira de papel ficará com saldo zero e você deverá inutilizar a sua carteira de papel por questões de segurança. Nunca reutilize uma carteira de papel antiga para receber novas transações.
+ As carteiras de papel podem ser facilmente criadas por qualquer usuários e mais comumente são usadas como uma maneira segura de armazenamento offline (frio) de Groestlcoins. Alguns caixas eletrônicos imprimem carteiras de papel ao invés de enviar diretamente os groestlcoins para o dispositivo móvel. Algumas pessoas usam carteiras de papel com créditos pré-carregados para passar um valor (método inseguro, não recomendado).Comece varrendo a chave privada de uma carteira de papel. Use o botão de ação da câmera.Essa chave privada está protegida com uma senha.senha
@@ -119,7 +119,7 @@
A quantidade de Groestlcoins na carteira é muita pequena para ser varrida.Recarregar saldoManutenção recomendada
- Você recebeu %1$s em um endereço não seguro, Gostaria de mover esses Groestlcoins para um endereço seguro? Uma pequena taxa de rede de %2$s será cobrada.
+ Você recebeu %1$s em um endereço não seguro, Gostaria de mover esses groestlcoins para um endereço seguro? Uma pequena taxa de rede de %2$s será cobrada.Descriptografando…Concluído.Mover
@@ -144,7 +144,7 @@
Escanear endereçoOs dados digitalizados estão irreconhecíveisO endereço escaneado é o seu próprio.
- Este endereço pode estar comprometido. Você não deve mais usá-lo para receber Groestlcoins.
+ Este endereço pode estar comprometido. Você não deve mais usá-lo para receber groestlcoins.Adicionar endereços de envioEditar etiqueta para o endereço de envioAdicionar etiqueta para o seu endereço
@@ -174,12 +174,12 @@
Sem pontos conectados⇆ %d msRestaurar carteira
- Você está prestes a substituir sua carteira atual. Quaisquer Groestlcoins contidos na carteira atual serão perdidos, a menos que você tenha feito um backup.
+ Você está prestes a substituir sua carteira atual. Quaisquer groestlcoins contidos na carteira atual serão perdidos, a menos que você tenha feito um backup.Importante: Não carregue chaves privadas de fontes duvidosas! Outros podem ganhar controle sobre o seu saldo antes de você.RestaurarA carteira foi restaurada.Sua carteira foi restaurada com sucesso. O saldo será determinado em seguida. Isso poderá levar algum tempo.
- Aviso: A sua carteira está protegida por um PIN que será exigido quando você for gastar os Groestlcoins contidos nela. Certifique-se de que você ainda se lembra dele antes de você receber Groestlcoins nessa carteira.
+ Aviso: A sua carteira está protegida por um PIN que será exigido quando você for gastar os groestlcoins contidos nela. Certifique-se de que você ainda se lembra dele antes de você receber groestlcoins nessa carteira.A carteira não pôde ser restaurada:\n\n%s\n\nSenha ruim?Fazer backup da carteiraSeu backup será criptografado com a senha escolhida e será salvo no armazenamento externo.
@@ -240,8 +240,6 @@
Explorador de blocos externo para visualizar as transações, endereços e blocos.Uso de dadosMostrar opções para restringir o uso de dados em redes móveis.
- Aviso de saldo
- Após ficar alguns semanas sem ser utilizado, o app irá notificar se ainda existem Groestlcoins na carteira.Mostrar aviso legalVocê realmente leu as notas de segurança? Já fez backup de sua carteira para um lugar seguro?Informar problema
@@ -271,7 +269,7 @@
LicençaCódigo fonteEste app está usando…
- Groestlcoinj %s, um protocolo de implementação Groestlcoin
+ groestlcoinj %s, um protocolo de implementação Groestlcoin ZXing, uma biblioteca de processamento para QR-codePágina no %sComente ou classifique o aplicativo
@@ -281,7 +279,7 @@
%d pontos conectadosVocê ainda tem Groestlcoins nesse dispositivo!Lembre-se que o seu saldo de %s será perdido se você desinstalar o app Groestlcoin Wallet sem antes enviá-los para outra carteira.
- Se você não quiser mais seus Groestlcoins, você pode doá-los para o projeto Groestlcoin Wallet.
+ Se você não quiser mais seus groestlcoins, você pode doá-los para o projeto Groestlcoin Wallet.Lembrar depoisNão me lembrarPagamentos recebidos
@@ -303,8 +301,8 @@
ColarCompartilharDefinir como padrão
- Solicitar
- Enviar
+ Solicitar Groestlcoins
+ Enviar GroestlcoinsEscanearConfiguraçõesMostrar Código QR
diff --git a/wallet/res/values-pt-rPT/strings.xml b/wallet/res/values-pt-rPT/strings.xml
index 6005c4e63e..04cc85c860 100644
--- a/wallet/res/values-pt-rPT/strings.xml
+++ b/wallet/res/values-pt-rPT/strings.xml
@@ -43,8 +43,7 @@
Notas técnicasGratificação / donativoDoar para o projeto Groestlcoin Wallet
- Alerta de segurança
- O componente de Bluetooth do seu dispositivo é vulnerável. Os seus Groestlcoins neste dispositivo estão em risco, independentemente da aplicação que utilize para os armazenar!\n\nRecomendamos desligar o Bluetooth imediatamente e verificar se existe uma atualização do Android que implemente o nível de correção de segurança \'%s\' ou posterior.
+ Alerta de segurançaPouco espaço no armazenamento do dispositivo interno!Groestlcoin Wallet utiliza o armazenamento interno para lembrar as transações e os blocos. Se este ficar sem espaço, este deixará de funcionar e as suas Groestlcoins irão estar em risco!\n\nQuer abrir o Gestor de Aplicações para desinstalar as aplicações desnecessárias?Gerir aplicações
@@ -76,7 +75,7 @@
Será paga uma taxa de rede de %s.Será paga uma taxa na prioridade %s. Utilize a \'prioridade\' apenas quando precisar que a confirmação ocorra o mais cedo possível.O valor é demasiado pequeno para enviar.
- Sem Groestlcoins suficientes. Faltam-lhe %s. Isto pode dever-se também a comissão da rede.
+ Sem groestlcoins suficientes. Faltam-lhe %s. Isto pode dever-se também a comissão da rede.A quantidade de pequenos pagamentos na sua carteira não somam um valor suficiente para envio.Neste momento os pagamentos não são possíveis porque está a decorrer uma repetição.Enviar o pagamento diretamente para o beneficiário
@@ -111,8 +110,8 @@
Concluído.AumentarConverter carteira de papel
- Vai converter uma carteira de papel ou cupão. Isto irá mover todos os Groestlcoins nessa carteira de papel para a sua carteira neste dispositivo. Quando a transação for confirmada, a carteira de papel não terá valor e não deve ser usada novamente por questões de segurança.
- As carteiras de papel são usadas normalmente para armazenamento seguro não ligado à Internet (cold storage). Alguns terminais bancários imprimem-nas em papel em vez de enviar as Groestlcoins diretamente para o seu dispositivo. As pessoas por vezes usam cartiras de papel pré-carregadas para transacionarem o valor para outras pessoas (não recomendado).
+ Vai converter uma carteira de papel ou cupão. Isto irá mover todos os groestlcoins nessa carteira de papel para a sua carteira neste dispositivo. Quando a transação for confirmada, a carteira de papel não terá valor e não deve ser usada novamente por questões de segurança.
+ As carteiras de papel são usadas normalmente para armazenamento seguro não ligado à Internet (cold storage). Alguns terminais bancários imprimem-nas em papel em vez de enviar as groestlcoins diretamente para o seu dispositivo. As pessoas por vezes usam cartiras de papel pré-carregadas para transacionarem o valor para outras pessoas (não recomendado).Comece por digitalizar a chave privada de uma carteira de papel. Use o botão da câmara.Esta chave privada está protegida com uma palavra-passe.palavra-passe
@@ -129,7 +128,7 @@
A quantidade de moedas na carteira é muito pequena para limpar.Recarregar saldoRecomendada a manutenção
- Recebeu %1$s num endereço não seguro, Gostaria de mover esses Groestlcoins para um endereço seguro? Será cobrada uma pequena taxa de rede de %2$s.
+ Recebeu %1$s num endereço não seguro, Gostaria de mover esses groestlcoins para um endereço seguro? Será cobrada uma pequena taxa de rede de %2$s.A desencriptar…Concluído.Mover
@@ -188,12 +187,12 @@
Sem pares ligados⇆ %d msRestaurar carteira
- Está prestes a substituir a sua carteira atual. Quaisquer Groestlcoins contidos na carteira atual serão perdidos, a não ser que tenha feito uma cópia de segurança.
+ Está prestes a substituir a sua carteira atual. Quaisquer groestlcoins contidos na carteira atual serão perdidos, a não ser que tenha feito uma cópia de segurança.Importante: não carregue chaves privadas de fontes duvidosas! Se o fizer, outras pessoas podem controlar o seu saldo.RestaurarA carteira foi restaurada.A sua carteira foi restaurada com sucesso. O saldo será determinado em seguida. Isso poderá levar algum tempo.
- Aviso: A sua carteira está protegida por um PIN que será pedido quando for gastar os Groestlcoins contidos nela. Certifique-se que ainda se lembra dele antes de receber Groestlcoins nesta carteira.
+ Aviso: A sua carteira está protegida por um PIN que será pedido quando for gastar os groestlcoins contidos nela. Certifique-se que ainda se lembra dele antes de receber groestlcoins nesta carteira.Não foi possível restaurar a sua carteira:\n\n%s\n\nPalavra-passe errada?Copiar carteiraA sua cópia de segurança será encriptada com a palavra-passe escolhida e será guardada no armazenamento externo.
@@ -226,7 +225,7 @@
Não é possível ler os dados:\n%sNão é possível reconhecer a entrada:\n%sURl de Groestlcoin inválidoI:\n%s
- Foi obtido um endereço Groestlcoin inválido !\n(Misturando mainnet/testnet?)
+ Foi obtido um endereço groestlcoin inválido !\n(Misturando mainnet/testnet?)Não é possível verificar o pedido de pagamento:\n%sPedido de pagamento inválido:\n%sTransação inválida:\n%s
@@ -256,8 +255,6 @@
Mostrar as opções para limitar a utilização de dados nas redes móveis.NotificaçõesMostrar opções para desativar ou ativar notificações específicas.
- Lembrete de saldo
- Após ficar alguns semanas sem ser utilizado, a aplicação irá notificar se ainda existem Groestlcoins na carteira.Mostrar limitação de responsabilidadeLeu realmente as notas de segurança? Já fez a cópia de segurança da sua carteira para um lugar seguro?Comunicar problema
@@ -288,7 +285,7 @@
LicençaCódigo fonteEsta aplicação está a usar…
- Groestlcoinj %s, uma implementação do protocol de Groestlcoin
+ groestlcoinj %s, uma implementação do protocol de GroestlcoinZXing, uma biblioteca de processamento de código QRBouncy Castle, uma biblioteca de criptografiaOkHttp, um cliente de biblioteca HTTP
@@ -304,7 +301,7 @@
%d pares ligadosAinda tem Groestlcoins neste dispositivo!Lembre-se que o seu saldo de %s será perdido se desinstalar o programa Groestlcoin Wallet sem enviar primeiro o saldo para outra carteira.
- Se não quiser saber mais dos seus Groestlcoins, pode fazer um donativo para o projeto Groestlcoin Wallet.
+ Se não quiser saber mais dos seus groestlcoins, pode fazer um donativo para o projeto Groestlcoin Wallet.Lembrar + tardeNão lembrar-mePagamentos recebidos
diff --git a/wallet/res/values-ru/strings.xml b/wallet/res/values-ru/strings.xml
index ca592f52e2..a51ec64ae0 100644
--- a/wallet/res/values-ru/strings.xml
+++ b/wallet/res/values-ru/strings.xml
@@ -1,6 +1,7 @@
Ваш кошелёк был сброшен!\nВосстановление займёт некоторое время.
+ У вас не установлен Web-Бразуер для открытия внешнего документа.Используйте на свой страх и риск. Прочитайте <u>заметки по безопасности</u>.Вам необходимо <u>сделать резервную копию вашего кошелька</u>!%1$s, %2$d часов отставания
@@ -8,11 +9,11 @@
%1$s, %2$d недель отставания%1$s, %2$d месяцев отставанияСинхронизация с сетью
- Синхронизация зависла
- Синхронизация: проблема с хранилищем
- Синхронизация: проблема с сетью
+ Синхронизация приостановлена.
+ Проблема с синхронизацией: Недостаточно свободного места.
+ Проблема с синхронизацией: Нет интернет- соединения.Адрес скопирован в буфер обмена
- Для защиты вашей конфиденциальности ваш адрес будет меняться-> как только он получает деньги.
+ Для защиты вашей конфиденциальности ваш адрес будет меняться , как только он получает деньги.Ваше устройство устарело и недостаточно защищено. Используйте только для небольших сумм денег.Во время воспроизведения баланс недоступен.Обменный курс
@@ -28,7 +29,7 @@
Пожалуйста сделайте резервную копию кошелька\nперед получением любых платежей!Поздравляем, вы получили первый платёж! Вы уже <u>сделали резервную копию кошелька</u>, чтобы обезопаситься от потерь?Подсказка: для улучшения безопасности вашего кошелька вы можете <u>зашифровать ваше устройство</u>. Это также защитит данные других приложений.
- Сеть Биткойн в данный момент на техобслуживании. Советуем не переводить и не получать биткойны до его окончания. <u> Подробнее.</u>
+ Сеть Грoстлкоин в данный момент на техобслуживании. Советуем не переводить и не получать Грoстлкоины до его окончания. <u> Подробнее.</u>добытовнутреннийФильтр
@@ -43,8 +44,7 @@
Технические деталиПожертвоватьПожертвование для Groestlcoin Wallet
- Предупреждение о безопасности
- Bluetooth модуль вашего устройства подвержен уязвимости. Ваши биткойны на этом устройстве находятся в опасности, вне зависимости от того, какое приложение используемого для их хранения!\n\nМы предлагаем вам немедленно выключить Bluetooth и проверить, есть ли у разработчика вашего устройства обновление Android, реализующее исправление безопасности \'%s\' или более позднее.
+ Предупреждение о безопасностиМало места во внутреннем хранилище!Groestlcoin Wallet использует внутреннее хранилище для транзакций и блогов. Если оно переполнится, кошелек перестанет работать и вы рискуете потерять монеты!\n\nХотите открыть Application Manager и деинсталлировать ненужные приложения?Управлять приложениями
@@ -173,7 +173,7 @@
Этот платеж был проведен напрямую. Существует риск того, что вы никогда не сможете потратить полученные средства.Подтверждение этого платежа задерживается, возможно, из-за перегрузки сети Groestlcoin.Этот платёж должен стать доступным через несколько минут.
- Из-за техобслуживания сети Биткойн данная транзакция не будет произведена пока полностью не подтверждена.
+ Из-за техобслуживания сети Грoстлкоин данная транзакция не будет произведена пока полностью не подтверждена.Этот платёж с высокой вероятностью может быть отменён отправителем! Если можете, подождите подтверждения.Этот платёж был отменён отправителем.Столь малая сумма, скорее всего, никогда не будет потрачена экономически.
@@ -255,8 +255,6 @@
Использование данныхПоказать опции ограничения использования данных в мобильных сетях.Уведомления
- Напоминание баланса
- Если приложение не использовалось несколько недель, вам придёт напоминание, если в бумажнике есть монеты.Показать отказ от ответственностиВы действительно прочитали заметки о безопасности? Вы уже храните резервную копию кошелька в безопасном месте?Сообщить о проблеме
diff --git a/wallet/res/values-sl/strings.xml b/wallet/res/values-sl/strings.xml
index cd5b68a838..a3866b7f36 100644
--- a/wallet/res/values-sl/strings.xml
+++ b/wallet/res/values-sl/strings.xml
@@ -25,7 +25,7 @@
Poišči menjalni tečajPrejeli niste še nobenih novcev.Poslali niste še nobenih novcev.
- Kako do Groestlcoinov?\nZamenjajte običajen denar zanje,\nprodajte blago ali storitve,\nali pa jih zaslužite z delom.
+ Kako do groestlcoinov?\nZamenjajte običajen denar zanje,\nprodajte blago ali storitve,\nali pa jih zaslužite z delom.Prosimo, naredite varnostno kopijo denarnice, preen prejmete groestlcoine!Čestitke, prejeli ste prvo plačilo! Ste že napravili <u>rezervno kopijo ključev</u> za zaščito pred izgubo novcev?Naimg: za povečano varnost denarnice lahko <u>svojo napravo šifrirate</u>. To zaščiti tudi podatke drugih aplikacij.
@@ -44,13 +44,12 @@
Tehnične opombeDonacijeDonacija za Groestlcoin Wallet
- Varnostno opozorilo
- Bluetooth-komponenta na vaši napravi je varnostno ranljiva. Groestlcoini na tej napravi niso varni, ne glede na to, v kateri aplikaciji jih hranite!\n\nSvetujemo vam, da takoj izklopite Bluetooth in preverite, ali proizvajalec vaše naprave nudi posodobitev sistema Android, ki vsebuje varnostni popravek stopnje \'%s\' ali novejše.
+ Varnostno opozoriloNa notranjem pomnilniškem mediju zmanjkuje prostora!Groestlcoin Wallet uporablja notranji pomnilnik, da si zapomni nakazila in bloke. Če tu zmanjka prostora, bo denarnica prenehala delovati in vaši novci bodo v nevarnosti!\n\nŽelite odpreti Application Manager, da boste lahko odstranili odvečne aplikacije?Upravljanje z aplikacijamiPreverite nastavitve časa!
- Čas na vaši napravi prehiteva ali zaostaja za %d minut. Zaradi tega verjetno ne morete prejemati ali pošiljati Groestlcoinov.\n\nProsimo, preverite in popravite svoje časovne nastavitve.
+ Čas na vaši napravi prehiteva ali zaostaja za %d minut. Zaradi tega verjetno ne morete prejemati ali pošiljati groestlcoinov.\n\nProsimo, preverite in popravite svoje časovne nastavitve.Na voljo je nova verzija!V tej verziji so popravljeni pomembni hrošči. Za podrobnosti glejte zgodovino sprememb na %s.Če ne vidite nove verzije, vaša verzija Androida najbrž ni več podprta.
@@ -227,8 +226,8 @@
Da boste lahko slikali QR-kode, morate dovoliti uporabo fotoaparata.Branje podatkov spodletelo:\n%sNeznana vrsta vhoda:\n%s
- Neveljaven Groestlcoin URI:\n%s
- Neveljaven Groestlcoin naslov!\n(Zamenjava testnega in pravega omrežja?)
+ Neveljaven groestlcoin URI:\n%s
+ Neveljaven groestlcoin naslov!\n(Zamenjava testnega in pravega omrežja?)Ne morem preveriti zahtevka za plačilo:\n%sNeveljavna prošnja za plačilo:\n%sNeveljavno nakazilo:\n%s
@@ -264,8 +263,6 @@
Prikaži možnosti za omejitev porabe podatkov prek mobilnih omrežij.ObvestilaPrikaži nastavitve prikazovanja obvestil
- Opomnik stanja
- Po nekaj tednih neuporabe vas bo aplikacija obvestila, če še ne bo prazna.Prikaži zavrnitev odgovornostiSte res prebali varnostna navodila? Ste naredili varnostno kopijo in jo shranili na varno mesto?Naslov Bluetooth
@@ -341,8 +338,8 @@
PrilepiDeliNastavi kot privzeto
- Prejmi
- Pošlji
+ Prejmi novce
+ Pošlji novcePrečitajNastavitvePrikazi QR-kodo
diff --git a/wallet/res/values-sv/strings.xml b/wallet/res/values-sv/strings.xml
index 6fa8217952..3d3b9de7d6 100644
--- a/wallet/res/values-sv/strings.xml
+++ b/wallet/res/values-sv/strings.xml
@@ -22,9 +22,9 @@
saldoPris från %sSök växlingskurs
- Inga Groestlcoin har mottagits hittills.
- Inga Groestlcoin har skickats hittills.
- Hur får man tag i Groestlcoin?\nKöp för svenska kronor,\nsälj varor eller tjänster eller\ntjäna genom att arbeta.
+ Inga groestlcoin har mottagits hittills.
+ Inga groestlcoin har skickats hittills.
+ Hur får man tag i groestlcoin?\nKöp för svenska kronor,\nsälj varor eller tjänster eller\ntjäna genom att arbeta.Vänligen säkerhetskopiera din plånbok\ninnan du tar emot några Groestlcoin!Grattis, du fick din första betalning! Har du redan <u>säkerhetskopierat din plånbok</u>, för att skydda mot förlust?Tips: för att öka säkerheten på din plånbok kan du <u>kryptera din enhet</u>. Det skyddar också dina andra appar.
@@ -41,9 +41,9 @@
Tekniska anteckningarGe dricks/doneraDonera till Groestlcoin Wallet
- Säkerhetsvarning
+ SäkerhetsvarningLagringsutrymme snart slut
- Groestlcoin Wallet använder intern lagring för att komma ihåg transaktioner och block. Om lagringsutrymmet tar slut kommer den att sluta fungera och dina Groestlcoin hamnar i riskzonen!\n\nVill du öppna Application Manager för att avinstallera onödiga appar?
+ Groestlcoin Wallet använder intern lagring för att komma ihåg transaktioner och block. Om lagringsutrymmet tar slut kommer den att sluta fungera och dina groestlcoin hamnar i riskzonen!\n\nVill du öppna Application Manager för att avinstallera onödiga appar?Hantera apparKontrollera inställningar för datum & tidEnhetens tid är fel med %d minuter. Du kan förmodligen inte skicka eller ta emot Groestlcoin på grund av detta.\n\nDu bör kontrollera och korrigera tiden och tidszonen.
@@ -52,10 +52,10 @@
Om du inte ser en uppdatering så beror det förmodligen på att din version av Android inte längre stöds.Ladda nerAndroid-versionen är inaktuell
- Chansen är stor att någon av de kommande releaserna av Groestlcoin Wallet inte kommer att fungera på din enhet längre. I vissa fall kan det bli svårt att spendera dina tillgångar med den här enheten.\n\nSåvida du inte vet vad du gör, rekommenderas att du flyttar dina Groestlcoin snart.
+ Chansen är stor att någon av de kommande releaserna av Groestlcoin Wallet inte kommer att fungera på din enhet längre. I vissa fall kan det bli svårt att spendera dina tillgångar med den här enheten.\n\nSåvida du inte vet vad du gör, rekommenderas att du flyttar dina groestlcoin snart.Problem att öppna inställningarDet här är ett stort belopp att bära omkring på i fickan. Flytta gärna över en del till en säkrare plats.
- Skicka Groestlcoin
+ Skicka groestlcoinHämtar signatur från %sHämtning av signatur misslyckadesFelaktig signatur!
@@ -63,7 +63,7 @@
okändBetala tillskriv adress eller namn
- Ogiltig Groestlcoinadress!
+ Ogiltig groestlcoinadress!Du skickar till dig själv!Komplex\nadress(%s väntar på bekräftelse)
@@ -91,7 +91,7 @@
Skickar…Skickat!Misslyckades!
- Problem att skicka Groestlcoin!
+ Problem att skicka groestlcoin!AvgiftEkonomiNormal
@@ -106,7 +106,7 @@
HöjLäs av pappersplånbokDu är på väg att läsa av en pappersplånbok eller kupong. Detta kommer att flytta alla tillgångar från papperet till din plånbok på den här enheten. När transaktionen är bekräftad, kommer papperet att vara värdelöst och bör inte återanvändas av säkerhetsskäl.
- En pappersplånbok används oftast för säker långtidsförvaring. Vissa Groestlcoinbankomater skriver ut en sådan på ett papperskvitto istället för att skicka medel till din mobila enhet direkt. Vissa använder ibland en förladdad pappersplånbok för att byta och skicka medel med varandra (rekommenderas inte).
+ En pappersplånbok används oftast för säker långtidsförvaring. Vissa groestlcoinbankomater skriver ut en sådan på ett papperskvitto istället för att skicka medel till din mobila enhet direkt. Vissa använder ibland en förladdad pappersplånbok för att byta och skicka medel med varandra (rekommenderas inte).Börja genom att läsa av pappersplånbokens privata nyckel med din mobilkamera.Denna privata nyckel är skyddad med ett lösenord.lösenord
@@ -125,28 +125,28 @@
Dekrypterar…Klar.Flytta
- Ta emot Groestlcoin
+ Ta emot groestlcoinBegärt belopp (valfritt)Acceptera betalning via Bluetooth för en mer pålitligt processSe till att den här koden skannas av avsändaren.Eller nudda en NFC-aktiverad enhet.
- Förfrågan om Groestlcoin kopierades
- Dela en förfrågan om Groestlcoin…
- Ingen annan Groestlcoin-app hittades
+ Förfrågan om groestlcoin kopierades
+ Dela en förfrågan om groestlcoin…
+ Ingen annan groestlcoin-app hittadesBegäran från lokal appAdressbokDina adresserGammla adresserMottagaradresserDin adressbok är tom
- Skicka Groestlcoin till adressen
+ Skicka groestlcoin till adressenRedigera adressTa bort adressenKopiera till urklippScanna adressAvläst data är oigenkännligDen skannade adressen är din egen.
- Denna adress kanske har komprometterats. Du bör inte använda den för att ta emot Groestlcoin igen.
+ Denna adress kanske har komprometterats. Du bör inte använda den för att ta emot groestlcoin igen.Skapa adressetikettRedigera adressetikettSkapa din adressetikett
@@ -213,7 +213,7 @@
Kan inte läsa av data:\n%sKan inte känna igen inmatning:\n%sOgiltig Groestlcoin URI:\n%s
- Fick en ogiltig Groestlcoinadress!\n(Blandar du ihop Mainnet/Testnet?)
+ Fick en ogiltig groestlcoinadress!\n(Blandar du ihop Mainnet/Testnet?)Kan inte verifiera betalningsförfrågant:\n%sOgiltig betalningsförffrågan:\n%sOgiltig transaktion:\n%s
@@ -229,7 +229,7 @@
µGRS, inga decimalerDitt namnSkriv ditt namn för att användas vid betalningsmottagande. Gärna kort.
- Stäng \"skicka Groestlcoin\"-dialogen automatiskt.
+ Stäng \"skicka groestlcoin\"-dialogen automatiskt.Stänger dialogrutan när en betalning har slutförts.Säkra noderIP eller värdnamn hos enskild peer att ansluta till.
@@ -241,8 +241,6 @@
Extern block-utforskare för att leta efter transaktioner, adresser och block.DataanvändningVisa alternativ för att begränsa dataanvändning över mobila nätverk.
- Påminnelse om summa
- Efter ett par veckors inaktivitet kommer appen ge en påminnelse om det fortfarande finns ett belopp i plånboken.Visa ansvarsfriskrivningLäste du verkligen säkerhetsinformationen? Har du redan säkerhetskopierat din plånbok till en säker plats?Rapportera problem
@@ -280,9 +278,9 @@
Vanligt ställda frågor om appenMottagit %sAnsluten till %d noder
- Du har fortfarande Groestlcoin på den här enheten!
+ Du har fortfarande groestlcoin på den här enheten!Kom ihåg att din plånbokssumma på %s kommer att försvinna om du avinstallerar Groestlcoin-appen utan att skicka bort den först.
- Om du inte bryr dig om dina Groestlcoin kan du också donera dem till Groestlcoin Wallet-projektet.
+ Om du inte bryr dig om dina groestlcoin kan du också donera dem till Groestlcoin Wallet-projektet.Påminn senarePåminn mig inteMottagna betalningar
@@ -304,8 +302,8 @@
Klistra inDelaAnge som standard
- Mottag
- Skicka
+ Mottag groestlcoin
+ Skicka groestlcoinSkannaInställningarVisa QR-kod
diff --git a/wallet/res/values-tr/strings.xml b/wallet/res/values-tr/strings.xml
index 7d73337753..04093173c4 100644
--- a/wallet/res/values-tr/strings.xml
+++ b/wallet/res/values-tr/strings.xml
@@ -30,7 +30,7 @@
Groestlcoin almadan önce\nlütfen cüzdanınızı yedekleyin!Tebrikler, ilk ödemenizi aldınız! Kaybetme riskine karşı korunmak için <u>cüzdanınızı yedeklediniz mi</u>?İpucu: cüzdanınızın güvenliğini arttırmak için <u>cihazınızı şifreleyebilirsiniz</u>. Bu aynı zamanda diğer uygulamaları da koruyacaktır.
- Groestlcoin ağı bakımdadır. Bakım bitene kadar Groestlcoin gönderip almanızı tavsiye etmiyoruz. <u>Daha fazla bilgi.</u>
+ Groestlcoin ağı bakımdadır. Bakım bitene kadar groestlcoin gönderip almanızı tavsiye etmiyoruz. <u>Daha fazla bilgi.</u>madendendahiliFiltre
@@ -45,8 +45,7 @@
Teknik notlarBağışGroestlcoin Wallet için Bağış
- Güvenlik uyarısı
- Cihazınızın Bluetooth bileşeni savunmasızdır. Bu cihazdaki Groestlcoins\'leriniz, onları saklamak için kullandığınız uygulamadan bağımsız olarak tehlikededir!\n\nBluetooth\'u derhal kapatın ve güvenlik düzeltme eki seviyesi \'%s\' veya üstünü destekleyen bir Android veya OS güncellemesi için cihazınızın üreticisine danışın.
+ Güvenlik uyarısıDahili depolama alanı düşük!Groestlcoin cüzdanı muameleleri ve blokları hatırlamak için dahili depolama alanını kullanır. Bu alan yetersiz kalırsa cüzdan çalışmayı durduracak ve Groestlcoinleriniz risk altında kalacaktır!\n\nKullanılmayan uygulamaları kaldırmak için Uygulama Yöneticisini başlatmak ister misiniz?Uygulamaları yönet
@@ -57,7 +56,7 @@
Eğer bir güncelleme görmüyorsanız, bu muhtemelen sizin Android sürümünüzün artık desteklenmediği anlamına gelir.İndirAndroid sürümü eski
- Gelecek Groestlcoin Wallet sürümünün cihazınızı desteklememesi mümkündür. Bazı durumlarda bu cihazla Groestlcoin harcamak zor olabilir.\n\nGroestlcoinlerinizi güvenli bir yere aktarmanız tavsiye edilir.
+ Gelecek Groestlcoin Wallet sürümünün cihazınızı desteklememesi mümkündür. Bazı durumlarda bu cihazla groestlcoin harcamak zor olabilir.\n\nGroestlcoinlerinizi güvenli bir yere aktarmanız tavsiye edilir.Ayarları açarken bir sorun oluştuRiski düşük tutunCüzdanınızdaki miktar, cebinizde tutmak için oldukça fazla. Lütfen bir miktarını soğuk cüzdan gibi daha güvenli bir alana taşıyın.
@@ -79,7 +78,7 @@
%s değerinde ağ ücreti ödenecektir.%s değerinde öncelik ücreti ödenecektir. Eğer düşük bir ücret sizin için önemliyse, \'öncelik\' seçeneğini sadece teyidi mümkün olduğu kadar çabuk almak için kullanın.Bu meblağ göndermek için çok düşüktür.
- Yeterli miktarda Groestlcoin yok. Unutmayın %s. Bu aynı zamanda ağ ücretinden de olabilir.
+ Yeterli miktarda groestlcoin yok. Unutmayın %s. Bu aynı zamanda ağ ücretinden de olabilir.Cüzdanınızdaki düşük ödemelerin miktarı gönderilebilir değere erişmiyor.Şu anda ödeme yapmak mümkün değildir çünkü bir tekrar oynatma sürmektedir.Ödemeyi doğrudan alacaklıya gönder.
@@ -115,7 +114,7 @@
ArttırKağıt cüzdanı taraBir kupon ya da kağıt cüzdan taramak üzeresiniz. Bunu yapmanız bu kağıttaki tüm groestlcoinleri cihazınızdaki cüzdana taşıyacaktır. İşlem onaylandığında kağıt değersiz olacak ve güvenlik nedeniyle tekrar kullanılmayacaktır.
- Kağıt cüzdanlar en sık soğuk depolama için kullanılır. Bazı bankamatikler doğrudan mobil cihazınıza Groestlcoin yollamaktan ziyade bunu kağıt çıktısına yazarlar. Bazı kişiler de bazen ön yüklü kağıt cüzdanları para değeri aktarmak için kullanırlar (tavsiye edilmez).
+ Kağıt cüzdanlar en sık soğuk depolama için kullanılır. Bazı bankamatikler doğrudan mobil cihazınıza groestlcoin yollamaktan ziyade bunu kağıt çıktısına yazarlar. Bazı kişiler de bazen ön yüklü kağıt cüzdanları para değeri aktarmak için kullanırlar (tavsiye edilmez).Kağıt cüzdanın özel anahtarını tarayarak başlayın. Kameranın etkinleştirme düğmesini kullanın.Özel anahtar bir parola ile korunmuştur.parola
@@ -132,7 +131,7 @@
Cüzdandaki Groestlcoin miktarı işlenmek için çok düşük.Bakiyeyi tekrar yükleBakım tavsiye edilir
- Güvensiz adreslere %1$s aldınız. Bu Groestlcoinleri güvenli adreslere taşımak ister misiniz? %2$s tutarında düşük bir şebeke ücreti ödenecektir.
+ Güvensiz adreslere %1$s aldınız. Bu groestlcoinleri güvenli adreslere taşımak ister misiniz? %2$s tutarında düşük bir şebeke ücreti ödenecektir.Şifre çözülüyor…Tamamlandı.Taşı
@@ -230,7 +229,7 @@
Veri okunamadı:\n%sGirdi tanınamadı:\n%sGeçersiz Groestlcoin URI\'si:\n%s
- Geçersiz Groestlcoin adresi alındı!\n(esas ağ ile deneme ağı karışmış olmasın?)
+ Geçersiz groestlcoin adresi alındı!\n(esas ağ ile deneme ağı karışmış olmasın?)Ödeme talebi denetlenemedi:\n%sGeçersiz ödeme talebi:\n%sGeçersiz işlem:\n%s
@@ -247,6 +246,7 @@
GRS, 4 ondalıkmGRS, 2 ondalıkµGRS, ondalıksız
+ gro, ondalıksızKendi adınızAdınız veya şirketinizin adı, müşterilerinize gönderdiğiniz ödeme taleplerine eklenecektir.Kısa tut.
@@ -266,12 +266,14 @@
Güvenilen eş dışındaki eşlere bağlantıları engeller.Blok kaşifiİşlemlere, adreslere ve bloklara göz atmak için kullanılacak harici blok gezgini.
+ Yerel tutarları göster
+ Yerel tutarları hesaplamak için bir özet akışından döviz kurlarını alın.Veri kullanımıMobil ağlarda veri kullanımını kısıtlama seçeneklerini gösterin.
+ Pil optimizasyonunu devre dışı bırak
+ Blok zinciriyle olabildiğince güncel kalmak için uygulama için pil optimizasyonunu devre dışı bırakın.BildirimlerBelirli bildirimleri devre dışı bırakmak veya etkinleştirmek için seçenekleri gösterin.
- Bakiye hatırlacısı
- İki hafta kullanılmaz ise, uygulama cüzdanda hâlâ Groestlcoin bulunduğunu bildirecektir.Sorumluluk reddini gösterGüvenlik notlarını gerçekten okudunuz mu? Cüzdanınızı zaten güvenli bir yere yedeklediniz mi?Bluetooth adresi
@@ -304,7 +306,7 @@
LisansKaynak koduBu uygulama şunları kullanmaktadır…
- Groestlcoinj %s, Groestlcoin protokolü uygulaması
+ groestlcoinj %s, Groestlcoin protokolü uygulamasıZXing, QR kodlarını işleme kütüphanesiBouncy Castle, bir kriptografi kütüphanesiOkHttp, bir HTTP istemci kitaplığı
@@ -346,8 +348,8 @@
YapıştırPaylaşVarsayılan olarak ayarla
- Talep
- Gönder
+ Groestlcoin talep et
+ Groestlcoin gönderTaraAyarlarQR kodu göster
diff --git a/wallet/res/values-v26/drawables.xml b/wallet/res/values-v26/drawables.xml
deleted file mode 100644
index c1827258fe..0000000000
--- a/wallet/res/values-v26/drawables.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- @mipmap/ic_app_color_108dp
-
diff --git a/wallet/res/values-zh-rTW/strings.xml b/wallet/res/values-zh-rTW/strings.xml
index 64af468290..1b2b9de465 100644
--- a/wallet/res/values-zh-rTW/strings.xml
+++ b/wallet/res/values-zh-rTW/strings.xml
@@ -240,8 +240,6 @@
用來瀏覽交易資料、位址、以及區塊的外部區塊探索網站資料使用量顯示限制行動網路資料使用量的選項
- 餘額提醒
- 這個應用程式在兩個禮拜沒被使用後,會提醒你錢包裡面是否還有錢。顯示免責提示像是:確定看過安全提示了嗎?已經把錢包備份到安全的地方了嗎?問題回報
diff --git a/wallet/res/values-zh/strings.xml b/wallet/res/values-zh/strings.xml
index e793520aef..f584d8a943 100644
--- a/wallet/res/values-zh/strings.xml
+++ b/wallet/res/values-zh/strings.xml
@@ -45,8 +45,7 @@
技术手册技巧 / 捐助向格羅斯币钱包 (Groestlcoin Wallet) 捐款
- 安全警告
- 您的设备的蓝牙模块是不可靠的。无论您使用何种应用来保存格羅斯币,它们的安全都会受到威胁!\n\n我们建议您立即关闭蓝牙开关,并且检查Android的系统更新(安全补丁等级“%s”或者更高)。
+ 安全警告内部设备存储空间不足!格羅斯币钱包使用内部存储设备来记录交易和区块。如果存储空间不足,它将会停止工作,并且您的格羅斯币将存在风险!\n您是否想要打开应用程序管理器,以卸载掉一些不需要的应用程序?管理应用程序
@@ -269,8 +268,6 @@
显示限制移动网络数据流量消耗的选项。通知显示选项,可禁用或启用各种通知。
- 余额提示
- 如果几个星期后都没有被使用,并且钱包里有余额,应用程序将发出通知。显示声明您真的已经读过安全须知了吗?您已经把钱包备份到安全的位置了吗?蓝牙地址
@@ -305,7 +302,7 @@
许可协议源代码此应用正在使用...
- Groestlcoinj %s, 一个格羅斯币协议库
+ groestlcoinj %s, 一个格羅斯币协议库ZXing,一个QR码处理库Bouncy Castle,一个密码学函数库OkHttp,一个HTTP客户端函数库
diff --git a/wallet/res/values/drawables.xml b/wallet/res/values/drawables.xml
index 6766546edb..c1827258fe 100644
--- a/wallet/res/values/drawables.xml
+++ b/wallet/res/values/drawables.xml
@@ -1,4 +1,4 @@
- @mipmap/ic_app_color_48dp
+ @mipmap/ic_app_color_108dp
diff --git a/wallet/res/values/strings.xml b/wallet/res/values/strings.xml
index 903675f24f..867d678178 100644
--- a/wallet/res/values/strings.xml
+++ b/wallet/res/values/strings.xml
@@ -2,6 +2,7 @@
Your wallet was reset!\nIt will take some time to recover.There is no web browser installed to open the external document.
+ Web browser error: %sNo suitable cloud storage provider is installed.\nYou need something like \"Nextcloud\" or \"Google Drive\".Use at your own risk. Read the <u>safety notes</u>.You need to <u>back up your wallet</u>!
@@ -45,8 +46,7 @@
Technical notesTip / donateDonation for Groestlcoin Wallet
- Security alert
- Your device\'s Bluetooth component is vulnerable. Your Groestlcoins on this device are at risk, regardless of the app you\'re using to store them!\n\nWe suggest to switch off Bluetooth immediately and check with your device manufacturer for an Android OS update that implements security patch level \'%s\' or later.
+ Security alertInternal device storage space low!Groestlcoin Wallet uses internal storage for remembering transactions and blocks. If it runs out of space, it will stop working and your Groestlcoins will be at risk!\n\nDo you want to open the Application Manager to uninstall unneeded apps?Manage apps
@@ -61,7 +61,10 @@
Problem with opening of settingsKeep the risk lowThe amount in your wallet is quite high for carrying in your pocket. Please move some to a safer place, like a cold wallet.
- Send Groestlcoin
+ Receive payments in background
+ At the moment your device is configured to only notify incoming payments while Groestlcoin Wallet is visible – due to battery usage.\n\nWe suggest allowing the app to always stay up-to-date with the blockchain, even in the background.\n\nDon\'t worry, we\'ll still mind your battery.
+ Allow
+ Send GroestlcoinsFetching signature from %s…Fetching payment request failedThe payee (%1$s) is using an incompatible payment protocol (reason: %2$s).
@@ -130,6 +133,7 @@
The paper wallet is empty.Not enough coinsThe amount of coins in the wallet is too small for sweeping.
+ Cannot decode private keyReload balanceMaintenance recommendedYou received %1$s to unsecure addresses. Would you like to move these coins to secure addresses? A small network fee of %2$s will be paid.
@@ -176,7 +180,7 @@
This transaction raises the network fee for a previous payment.This payment has been received directly. There is a risk it might never become spendable.The confirmation of this payment is delayed, likely due to an overload of the Groestlcoin network.
- This payment should become spendable in a few minutes.
+ This payment should not be trusted until it is confirmed. Confirmations take a few minutes.Due to maintenance on the Groestlcoin network, this transaction should not be trusted until it is fully confirmed.This payment has an increased risk of being reversed by the sender! If you can, wait for confirmation.This payment has been reversed by the sender.
@@ -271,10 +275,10 @@
Fetch exchange rates from a feed in order to calculate local amounts.Data usageShow options to restrict data usage on mobile networks.
+ Disable battery optimization
+ To stay as up-to-date with the blockchain as possible please disable battery optimization for this app.NotificationsShow options to disable or enable specific notifications.
- Balance reminder
- After a couple of weeks of not being used, the app will notify if there are still coins in the wallet.Show disclaimerHave you really read the safety notes? Did you already back up your wallet to a safe place?Bluetooth address
diff --git a/wallet/res/xml/locale_config.xml b/wallet/res/xml/locale_config.xml
new file mode 100644
index 0000000000..e46d806538
--- /dev/null
+++ b/wallet/res/xml/locale_config.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wallet/res/xml/preference_about.xml b/wallet/res/xml/preference_about.xml
index a8eda7a15a..feab91e3b8 100644
--- a/wallet/res/xml/preference_about.xml
+++ b/wallet/res/xml/preference_about.xml
@@ -6,7 +6,7 @@
android:key="about_version"
android:title="@string/about_version_title" />
-
-
+
+
+
+
trustedPeers = getTrustedPeers();
+ return trustedPeers != null && !trustedPeers.isEmpty() && getTrustedPeersOnly();
}
public Uri getBlockExplorer() {
@@ -182,12 +185,44 @@ public boolean isEnableExchangeRates() {
return Constants.ENABLE_EXCHANGE_RATES && prefs.getBoolean(PREFS_KEY_ENABLE_EXCHANGE_RATES, true);
}
- public boolean remindBalance() {
- return prefs.getBoolean(PREFS_KEY_REMIND_BALANCE, true);
+ private long getBatteryOptimizationDialogTime() {
+ return prefs.getLong(PREFS_KEY_BATTERY_OPTIMIZATION_DIALOG_TIME, 0);
}
- public void setRemindBalance(final boolean remindBalance) {
- prefs.edit().putBoolean(PREFS_KEY_REMIND_BALANCE, remindBalance).apply();
+ public boolean isTimeForBatteryOptimizationDialog() {
+ final long now = System.currentTimeMillis();
+ return now >= getBatteryOptimizationDialogTime();
+ }
+
+ private void setBatteryOptimizationDialogTime(final long batteryOptimizationDialogTime) {
+ prefs.edit().putLong(PREFS_KEY_BATTERY_OPTIMIZATION_DIALOG_TIME, batteryOptimizationDialogTime).apply();
+ }
+
+ public void setBatteryOptimizationDialogTimeIn(final long durationMs) {
+ final long now = System.currentTimeMillis();
+ setBatteryOptimizationDialogTime(now + durationMs);
+ }
+
+ public void removeBatteryOptimizationDialogTime() {
+ prefs.edit().remove(PREFS_KEY_BATTERY_OPTIMIZATION_DIALOG_TIME).apply();
+ }
+
+ private long getRemindBalanceTime() {
+ return prefs.getLong(PREFS_KEY_REMIND_BALANCE_TIME, 0);
+ }
+
+ private void setRemindBalanceTime(final long remindBalanceTime) {
+ prefs.edit().putLong(PREFS_KEY_REMIND_BALANCE_TIME, remindBalanceTime).apply();
+ }
+
+ public boolean isTimeToRemindBalance() {
+ final long now = System.currentTimeMillis();
+ return now >= getRemindBalanceTime();
+ }
+
+ public void setRemindBalanceTimeIn(final long durationMs) {
+ final long now = System.currentTimeMillis();
+ setRemindBalanceTime(now + durationMs);
}
public boolean remindBackup() {
@@ -281,8 +316,8 @@ public long getLastUsedAgo() {
public void touchLastUsed() {
final long prefsLastUsed = prefs.getLong(PREFS_KEY_LAST_USED, 0);
final long now = System.currentTimeMillis();
- prefs.edit().putLong(PREFS_KEY_LAST_USED, now).apply();
-
+ prefs.edit().putLong(PREFS_KEY_LAST_USED, now).putLong(PREFS_KEY_REMIND_BALANCE_TIME,
+ now + Constants.LAST_USAGE_THRESHOLD_INACTIVE_MS).apply();
log.info("just being used - last used {} minutes ago", (now - prefsLastUsed) / DateUtils.MINUTE_IN_MILLIS);
}
diff --git a/wallet/src/de/schildbach/wallet/Constants.java b/wallet/src/de/schildbach/wallet/Constants.java
index 2fcc163154..8696be1be2 100644
--- a/wallet/src/de/schildbach/wallet/Constants.java
+++ b/wallet/src/de/schildbach/wallet/Constants.java
@@ -36,6 +36,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.time.Duration;
import java.util.concurrent.TimeUnit;
/**
@@ -47,17 +48,17 @@ public final class Constants {
public static final NetworkParameters NETWORK_PARAMETERS =
!BuildConfig.FLAVOR.equals("prod") ? TestNet3Params.get() : MainNetParams.get();
- /** Bitcoinj global context. */
+ /** Groestlcoinj global context. */
public static final Context CONTEXT = new Context(NETWORK_PARAMETERS);
/**
- * The type of Bitcoin addresses used for the initial wallet: {@link Script.ScriptType#P2PKH} for classic
+ * The type of Groestlcoin addresses used for the initial wallet: {@link Script.ScriptType#P2PKH} for classic
* Base58, {@link Script.ScriptType#P2WPKH} for segwit Bech32.
*/
public static final Script.ScriptType DEFAULT_OUTPUT_SCRIPT_TYPE = Script.ScriptType.P2WPKH;
/**
- * The type of Bitcoin addresses to upgrade the current wallet to: {@link Script.ScriptType#P2PKH} for classic
+ * The type of Groestlcoin addresses to upgrade the current wallet to: {@link Script.ScriptType#P2PKH} for classic
* Base58, {@link Script.ScriptType#P2WPKH} for segwit Bech32.
*/
public static final Script.ScriptType UPGRADE_OUTPUT_SCRIPT_TYPE = Script.ScriptType.P2WPKH;
@@ -110,10 +111,10 @@ public final static class Files {
}
/** URL to fetch version alerts from. */
- public static final HttpUrl VERSION_URL = HttpUrl.parse("https://wallet.schildbach.de/version"
+ public static final HttpUrl VERSION_URL = HttpUrl.parse("https://groestlcoin.org/groestlcoin-wallet-version"
+ (NETWORK_PARAMETERS.getId().equals(NetworkParameters.ID_MAINNET) ? "" : "-test"));
/** URL to fetch dynamic fees from. */
- public static final HttpUrl DYNAMIC_FEES_URL = HttpUrl.parse("https://wallet.schildbach.de/fees");
+ public static final HttpUrl DYNAMIC_FEES_URL = HttpUrl.parse("https://groestlcoin.org/fees");
/** MIME type used for transmitting single transactions. */
public static final String MIMETYPE_TRANSACTION = "application/x-grstx";
@@ -170,6 +171,17 @@ public final static class Files {
public static final long LAST_USAGE_THRESHOLD_RECENTLY_MS = DateUtils.WEEK_IN_MILLIS;
public static final long LAST_USAGE_THRESHOLD_INACTIVE_MS = 4 * DateUtils.WEEK_IN_MILLIS;
+ public static final Duration SERVICE_STOP_DELAY_AFTER_START =
+ NETWORK_PARAMETERS.getId().equals(NetworkParameters.ID_MAINNET) ?
+ Duration.ofMinutes(1) :
+ Duration.ofMinutes(2);
+ public static final Duration SERVICE_STOP_DELAY_AFTER_TRANSACTION =
+ Duration.ofMinutes(5);
+ public static final Duration SERVICE_STOP_DELAY_AFTER_EVENT =
+ NETWORK_PARAMETERS.getId().equals(NetworkParameters.ID_MAINNET) ?
+ Duration.ofSeconds(30) :
+ Duration.ofMinutes(2);
+
public static final long DELAYED_TRANSACTION_THRESHOLD_MS = 2 * DateUtils.HOUR_IN_MILLIS;
public static final long AUTOCLOSE_DELAY_MS = 1000;
@@ -178,9 +190,11 @@ public final static class Files {
public static final Coin TOO_MUCH_BALANCE_THRESHOLD = Coin.COIN.multiply(1000);
/** A balance above this amount will cause the donate option to be shown */
public static final Coin SOME_BALANCE_THRESHOLD = Coin.COIN;
+ /** Values less than this are considered not spendable in an economic way */
+ public static final Coin MIN_NONDUST = Coin.valueOf(546); // satoshis
- public static final int SDK_DEPRECATED_BELOW = Build.VERSION_CODES.N;
- public static final String SECURITY_PATCH_INSECURE_BELOW = "2020-10-01";
+ public static final int SDK_DEPRECATED_BELOW = Build.VERSION_CODES.O;
+ public static final String SECURITY_PATCH_INSECURE_BELOW = "2021-07-01";
public static final int NOTIFICATION_ID_CONNECTIVITY = 1;
public static final int NOTIFICATION_ID_COINS_RECEIVED = 2;
diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java
index 87d7e85761..5a956d9873 100644
--- a/wallet/src/de/schildbach/wallet/WalletApplication.java
+++ b/wallet/src/de/schildbach/wallet/WalletApplication.java
@@ -22,7 +22,7 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
+import android.bluetooth.BluetoothManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.media.AudioAttributes;
@@ -57,8 +57,8 @@
import de.schildbach.wallet.util.Toast;
import de.schildbach.wallet.util.WalletUtils;
import org.bitcoinj.core.VersionMessage;
-import org.bitcoinj.crypto.LinuxSecureRandom;
import org.bitcoinj.crypto.MnemonicCode;
+import org.bitcoinj.utils.ContextPropagatingThreadFactory;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.UnreadableWalletException;
import org.bitcoinj.wallet.Wallet;
@@ -95,14 +95,15 @@ public class WalletApplication extends Application {
private static final Logger log = LoggerFactory.getLogger(WalletApplication.class);
+ public WalletApplication() {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll().permitDiskReads().permitDiskWrites().penaltyLog().build());
+ }
+
@Override
public void onCreate() {
- new LinuxSecureRandom(); // init proper random number generator
-
Logging.init(getFilesDir());
- initStrictMode();
-
Threading.throwOnLockCycles();
org.bitcoinj.core.Context.enableStrictMode();
org.bitcoinj.core.Context.propagate(Constants.CONTEXT);
@@ -117,17 +118,18 @@ public void onCreate() {
final PackageInfo packageInfo = packageInfo();
Threading.uncaughtExceptionHandler = (thread, throwable) -> {
- log.info("bitcoinj uncaught exception", throwable);
+ log.info("groestlcoinj uncaught exception", throwable);
CrashReporter.saveBackgroundTrace(throwable, packageInfo);
};
- activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ activityManager = getSystemService(ActivityManager.class);
walletFile = getFileStreamPath(Constants.Files.WALLET_FILENAME_PROTOBUF);
final Configuration config = getConfiguration();
config.updateLastVersionCode(packageInfo.versionCode);
- final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ final BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
+ final BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter != null)
config.updateLastBluetoothAddress(Bluetooth.getAddress(bluetoothAdapter));
@@ -169,7 +171,7 @@ public Wallet getWallet() {
}
}
- private final Executor getWalletExecutor = Executors.newSingleThreadExecutor();
+ private final Executor getWalletExecutor = Executors.newSingleThreadExecutor(new ContextPropagatingThreadFactory("get wallet"));
private final Object getWalletLock = new Object();
@AnyThread
@@ -177,7 +179,6 @@ public void getWalletAsync(final OnWalletLoadedListener listener) {
getWalletExecutor.execute(new Runnable() {
@Override
public void run() {
- org.bitcoinj.core.Context.propagate(Constants.CONTEXT);
synchronized (getWalletLock) {
initMnemonicCode();
if (walletFiles == null)
@@ -299,34 +300,27 @@ private void cleanupFiles() {
}
}
- public static void initStrictMode() {
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().permitDiskReads()
- .permitDiskWrites().penaltyLog().build());
- }
-
private void initNotificationManager() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- final Stopwatch watch = Stopwatch.createStarted();
- final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-
- final NotificationChannel received = new NotificationChannel(Constants.NOTIFICATION_CHANNEL_ID_RECEIVED,
- getString(R.string.notification_channel_received_name), NotificationManager.IMPORTANCE_DEFAULT);
- received.setSound(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.coins_received),
- new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT).build());
- nm.createNotificationChannel(received);
-
- final NotificationChannel ongoing = new NotificationChannel(Constants.NOTIFICATION_CHANNEL_ID_ONGOING,
- getString(R.string.notification_channel_ongoing_name), NotificationManager.IMPORTANCE_LOW);
- nm.createNotificationChannel(ongoing);
-
- final NotificationChannel important = new NotificationChannel(Constants.NOTIFICATION_CHANNEL_ID_IMPORTANT,
- getString(R.string.notification_channel_important_name), NotificationManager.IMPORTANCE_HIGH);
- nm.createNotificationChannel(important);
-
- log.info("created notification channels, took {}", watch);
- }
+ final Stopwatch watch = Stopwatch.createStarted();
+ final NotificationManager nm = getSystemService(NotificationManager.class);
+
+ final NotificationChannel received = new NotificationChannel(Constants.NOTIFICATION_CHANNEL_ID_RECEIVED,
+ getString(R.string.notification_channel_received_name), NotificationManager.IMPORTANCE_DEFAULT);
+ received.setSound(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.coins_received),
+ new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT).build());
+ nm.createNotificationChannel(received);
+
+ final NotificationChannel ongoing = new NotificationChannel(Constants.NOTIFICATION_CHANNEL_ID_ONGOING,
+ getString(R.string.notification_channel_ongoing_name), NotificationManager.IMPORTANCE_LOW);
+ nm.createNotificationChannel(ongoing);
+
+ final NotificationChannel important = new NotificationChannel(Constants.NOTIFICATION_CHANNEL_ID_IMPORTANT,
+ getString(R.string.notification_channel_important_name), NotificationManager.IMPORTANCE_HIGH);
+ nm.createNotificationChannel(important);
+
+ log.info("created notification channels, took {}", watch);
}
private PackageInfo packageInfo;
diff --git a/wallet/src/de/schildbach/wallet/WalletBalanceWidgetProvider.java b/wallet/src/de/schildbach/wallet/WalletBalanceWidgetProvider.java
index 2bc63b30a0..7fa87a0ec5 100644
--- a/wallet/src/de/schildbach/wallet/WalletBalanceWidgetProvider.java
+++ b/wallet/src/de/schildbach/wallet/WalletBalanceWidgetProvider.java
@@ -23,7 +23,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Spannable;
import android.text.Spanned;
@@ -42,6 +41,7 @@
import de.schildbach.wallet.util.MonetarySpannable;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
+import org.bitcoinj.utils.ContextPropagatingThreadFactory;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.Fiat;
import org.bitcoinj.utils.MonetaryFormat;
@@ -50,11 +50,15 @@
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
/**
* @author Andreas Schildbach
*/
public class WalletBalanceWidgetProvider extends AppWidgetProvider {
+ private final Executor executor = Executors.newSingleThreadExecutor(new ContextPropagatingThreadFactory("appwidget"));
+
private static final StrikethroughSpan STRIKE_THRU_SPAN = new StrikethroughSpan();
private static final Logger log = LoggerFactory.getLogger(WalletBalanceWidgetProvider.class);
@@ -62,7 +66,7 @@ public class WalletBalanceWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) {
final PendingResult result = goAsync();
- AsyncTask.execute(() -> {
+ executor.execute(() -> {
final WalletApplication application = (WalletApplication) context.getApplicationContext();
final Coin balance = application.getWallet().getBalance(BalanceType.ESTIMATED);
final Configuration config = application.getConfiguration();
@@ -82,7 +86,7 @@ public void onAppWidgetOptionsChanged(final Context context, final AppWidgetMana
log.info("app widget {} options changed: minWidth={}", appWidgetId,
newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
final PendingResult result = goAsync();
- AsyncTask.execute(() -> {
+ executor.execute(() -> {
final WalletApplication application = (WalletApplication) context.getApplicationContext();
final Coin balance = application.getWallet().getBalance(BalanceType.ESTIMATED);
final Configuration config = application.getConfiguration();
@@ -165,14 +169,14 @@ else if (MonetaryFormat.CODE_UBTC.equals(currencyCode))
views.setViewVisibility(R.id.widget_button_send_qr, minWidth > 200 ? View.VISIBLE : View.GONE);
}
- views.setOnClickPendingIntent(R.id.widget_button_balance,
- PendingIntent.getActivity(context, 0, new Intent(context, WalletActivity.class), 0));
- views.setOnClickPendingIntent(R.id.widget_button_request,
- PendingIntent.getActivity(context, 0, new Intent(context, RequestCoinsActivity.class), 0));
- views.setOnClickPendingIntent(R.id.widget_button_send,
- PendingIntent.getActivity(context, 0, new Intent(context, SendCoinsActivity.class), 0));
- views.setOnClickPendingIntent(R.id.widget_button_send_qr,
- PendingIntent.getActivity(context, 0, new Intent(context, SendCoinsQrActivity.class), 0));
+ views.setOnClickPendingIntent(R.id.widget_button_balance, PendingIntent.getActivity(context, 0,
+ new Intent(context, WalletActivity.class), PendingIntent.FLAG_IMMUTABLE));
+ views.setOnClickPendingIntent(R.id.widget_button_request, PendingIntent.getActivity(context, 0,
+ new Intent(context, RequestCoinsActivity.class), PendingIntent.FLAG_IMMUTABLE));
+ views.setOnClickPendingIntent(R.id.widget_button_send, PendingIntent.getActivity(context, 0,
+ new Intent(context, SendCoinsActivity.class), PendingIntent.FLAG_IMMUTABLE));
+ views.setOnClickPendingIntent(R.id.widget_button_send_qr, PendingIntent.getActivity(context, 0,
+ new Intent(context, SendCoinsQrActivity.class), PendingIntent.FLAG_IMMUTABLE));
appWidgetManager.updateAppWidget(appWidgetId, views);
}
diff --git a/wallet/src/de/schildbach/wallet/data/PaymentIntent.java b/wallet/src/de/schildbach/wallet/data/PaymentIntent.java
index 9a7097ac67..6485c69375 100644
--- a/wallet/src/de/schildbach/wallet/data/PaymentIntent.java
+++ b/wallet/src/de/schildbach/wallet/data/PaymentIntent.java
@@ -42,7 +42,8 @@
import java.util.Arrays;
-import static androidx.core.util.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Andreas Schildbach
@@ -56,9 +57,9 @@ public final static class Output implements Parcelable {
public final Coin amount;
public final Script script;
- public Output(final Coin amount, final Script script) {
+ public Output(@Nullable final Coin amount, final Script script) {
this.amount = amount;
- this.script = script;
+ this.script = checkNotNull(script);
}
public static Output valueOf(final PaymentProtocol.Output output)
@@ -82,7 +83,7 @@ public String toString() {
builder.append(getClass().getSimpleName());
builder.append('[');
- builder.append(hasAmount() ? amount.toPlainString() : "null");
+ builder.append(amount != null ? amount.toPlainString() : "null");
builder.append(',');
final Address toAddress = WalletUtils.getToAddress(script);
if (ScriptPattern.isP2PK(script))
@@ -105,8 +106,12 @@ public int describeContents() {
@Override
public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeSerializable(amount);
-
+ if (amount != null) {
+ dest.writeByte((byte) 1);
+ dest.writeLong(amount.getValue());
+ } else {
+ dest.writeByte((byte) 0);
+ }
final byte[] program = script.getProgram();
dest.writeInt(program.length);
dest.writeByteArray(program);
@@ -125,8 +130,10 @@ public Output[] newArray(final int size) {
};
private Output(final Parcel in) {
- amount = (Coin) in.readSerializable();
-
+ if (in.readByte() != 0)
+ amount = Coin.valueOf(in.readLong());
+ else
+ amount = null;
final int programLength = in.readInt();
final byte[] program = new byte[programLength];
in.readByteArray(program);
diff --git a/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothService.java b/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothService.java
index 8f3ebf5fd7..9452537496 100644
--- a/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothService.java
+++ b/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothService.java
@@ -18,6 +18,7 @@
package de.schildbach.wallet.offline;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -27,6 +28,7 @@
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.text.format.DateUtils;
+import androidx.annotation.WorkerThread;
import androidx.core.app.NotificationCompat;
import androidx.lifecycle.LifecycleService;
import de.schildbach.wallet.Constants;
@@ -44,7 +46,7 @@
import java.io.IOException;
-import static androidx.core.util.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Andreas Schildbach
@@ -87,8 +89,9 @@ public void onCreate() {
super.onCreate();
this.application = (WalletApplication) getApplication();
- final BluetoothAdapter bluetoothAdapter = checkNotNull(BluetoothAdapter.getDefaultAdapter());
- final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ final BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
+ final BluetoothAdapter bluetoothAdapter = checkNotNull(bluetoothManager.getAdapter());
+ final PowerManager pm = getSystemService(PowerManager.class);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
wakeLock.acquire();
@@ -132,6 +135,7 @@ public boolean handleTx(final Transaction tx) {
});
}
+ @WorkerThread
private boolean handleTx(final Transaction tx) {
log.info("tx {} arrived via blueooth", tx.getTxId());
@@ -139,8 +143,8 @@ private boolean handleTx(final Transaction tx) {
try {
if (wallet.isTransactionRelevant(tx)) {
wallet.receivePending(tx, null);
- new BlockchainServiceLiveData(this).observe(this,
- blockchainService -> blockchainService.broadcastTransaction(tx));
+ handler.post(() -> new BlockchainServiceLiveData(this).observe(this,
+ blockchainService -> blockchainService.broadcastTransaction(tx)));
} else {
log.info("tx {} irrelevant", tx.getTxId());
}
diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainService.java b/wallet/src/de/schildbach/wallet/service/BlockchainService.java
index 34ca47e0b3..3f512d3d61 100644
--- a/wallet/src/de/schildbach/wallet/service/BlockchainService.java
+++ b/wallet/src/de/schildbach/wallet/service/BlockchainService.java
@@ -17,6 +17,7 @@
package de.schildbach.wallet.service;
+import android.app.ForegroundServiceStartNotAllowedException;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -29,6 +30,7 @@
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -75,7 +77,6 @@
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionBroadcast;
import org.bitcoinj.core.TransactionConfidence.ConfidenceType;
-import org.bitcoinj.core.Utils;
import org.bitcoinj.core.VersionMessage;
import org.bitcoinj.core.listeners.AbstractPeerDataEventListener;
import org.bitcoinj.core.listeners.PeerConnectedEventListener;
@@ -96,6 +97,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
@@ -106,7 +110,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import static androidx.core.util.Preconditions.checkState;
+import static com.google.common.base.Preconditions.checkState;
/**
* @author Andreas Schildbach
@@ -156,6 +160,19 @@ public class BlockchainService extends LifecycleService {
private static final Logger log = LoggerFactory.getLogger(BlockchainService.class);
public static void start(final Context context, final boolean cancelCoinsReceived) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ try {
+ attemptStart(context, cancelCoinsReceived);
+ } catch (final ForegroundServiceStartNotAllowedException x) {
+ log.info("failed to start in foreground", x);
+ }
+ } else {
+ attemptStart(context, cancelCoinsReceived);
+ }
+ }
+
+ private static void attemptStart(final Context context, final boolean cancelCoinsReceived) {
+ log.info("attempting to start {} in foreground", BlockchainService.class.getName());
if (cancelCoinsReceived)
ContextCompat.startForegroundService(context,
new Intent(BlockchainService.ACTION_CANCEL_COINS_RECEIVED, null, context, BlockchainService.class));
@@ -239,8 +256,8 @@ private void notifyCoinsReceived(@Nullable final Address address, final Coin amo
}
summaryNotification.setContentText(text);
}
- summaryNotification
- .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, WalletActivity.class), 0));
+ summaryNotification.setContentIntent(PendingIntent.getActivity(this, 0,
+ new Intent(this, WalletActivity.class), PendingIntent.FLAG_IMMUTABLE));
nm.notify(Constants.NOTIFICATION_ID_COINS_RECEIVED, summaryNotification.build());
// child notification
@@ -262,8 +279,8 @@ private void notifyCoinsReceived(@Nullable final Address address, final Coin amo
else
childNotification.setContentText(addressStr);
}
- childNotification
- .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, WalletActivity.class), 0));
+ childNotification.setContentIntent(PendingIntent.getActivity(this, 0,
+ new Intent(this, WalletActivity.class), PendingIntent.FLAG_IMMUTABLE));
childNotification.setSound(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.coins_received));
nm.notify(transactionHash.toString(), Constants.NOTIFICATION_ID_COINS_RECEIVED, childNotification.build());
}
@@ -278,7 +295,7 @@ public void stop() {
@Override
public void onPeerConnected(final Peer peer, final int peerCount) {
- postDelayedStopSelf(DateUtils.MINUTE_IN_MILLIS / 2);
+ postDelayedStopSelf(Constants.SERVICE_STOP_DELAY_AFTER_EVENT);
changed(peerCount);
}
@@ -307,7 +324,7 @@ private class BlockchainDownloadListener extends AbstractPeerDataEventListener i
@Override
public void onChainDownloadStarted(final Peer peer, final int blocksToDownload) {
- postDelayedStopSelf(DateUtils.MINUTE_IN_MILLIS / 2);
+ postDelayedStopSelf(Constants.SERVICE_STOP_DELAY_AFTER_EVENT);
this.blocksToDownload.set(blocksToDownload);
if (blocksToDownload >= CONNECTIVITY_NOTIFICATION_PROGRESS_MIN_BLOCKS) {
config.maybeIncrementBestChainHeightEver(blockChain.getChainHead().getHeight() + blocksToDownload);
@@ -332,7 +349,7 @@ public void onBlocksDownloaded(final Peer peer, final Block block, final Filtere
public void run() {
lastMessageTime.set(System.currentTimeMillis());
- postDelayedStopSelf(DateUtils.MINUTE_IN_MILLIS / 2);
+ postDelayedStopSelf(Constants.SERVICE_STOP_DELAY_AFTER_EVENT);
final int blocksToDownload = this.blocksToDownload.get();
final int blocksLeft = this.blocksLeft.get();
if (blocksToDownload >= CONNECTIVITY_NOTIFICATION_PROGRESS_MIN_BLOCKS)
@@ -350,7 +367,7 @@ private static class ImpedimentsLiveData extends LiveData> {
public ImpedimentsLiveData(final WalletApplication application) {
this.application = application;
- this.connectivityManager = (ConnectivityManager) application.getSystemService(Context.CONNECTIVITY_SERVICE);
+ this.connectivityManager = application.getSystemService(ConnectivityManager.class);
setValue(impediments);
}
@@ -432,9 +449,9 @@ private void handleIntent(final Intent intent) {
log.info("stop is deferred because service still bound");
};
- private void postDelayedStopSelf(final long ms) {
+ private void postDelayedStopSelf(final Duration delay) {
delayHandler.removeCallbacks(delayedStopSelfRunnable);
- delayHandler.postDelayed(delayedStopSelfRunnable, ms);
+ delayHandler.postDelayed(delayedStopSelfRunnable, delay.toMillis());
}
private final BroadcastReceiver deviceIdleModeReceiver = new BroadcastReceiver() {
@@ -476,8 +493,8 @@ public void onCreate() {
application = (WalletApplication) getApplication();
config = application.getConfiguration();
- pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ pm = getSystemService(PowerManager.class);
+ nm = getSystemService(NotificationManager.class);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
log.info("acquiring {}", wakeLock);
@@ -488,7 +505,7 @@ public void onCreate() {
R.string.notification_connectivity_syncing_trusted_peer :
R.string.notification_connectivity_syncing_message));
connectivityNotification.setContentIntent(PendingIntent.getActivity(BlockchainService.this, 0,
- new Intent(BlockchainService.this, WalletActivity.class), 0));
+ new Intent(BlockchainService.this, WalletActivity.class), PendingIntent.FLAG_IMMUTABLE));
connectivityNotification.setWhen(System.currentTimeMillis());
connectivityNotification.setOngoing(true);
connectivityNotification.setPriority(NotificationCompat.PRIORITY_LOW);
@@ -544,7 +561,7 @@ public void onChanged(final Wallet wallet) {
if (!blockChainFileExists && earliestKeyCreationTimeSecs > 0) {
try {
log.info("loading checkpoints for birthdate {} from '{}'",
- Utils.dateTimeFormat(earliestKeyCreationTimeSecs * 1000),
+ DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochSecond(earliestKeyCreationTimeSecs)),
Constants.Files.CHECKPOINTS_ASSET);
final Stopwatch watch = Stopwatch.createStarted();
final InputStream checkpointsInputStream = getAssets()
@@ -580,7 +597,7 @@ private void observeLiveDatasThatAreDependentOnWalletAndBlockchain() {
final NewTransactionLiveData newTransaction = new NewTransactionLiveData(wallet.getValue());
newTransaction.observe(this, tx -> {
final Wallet wallet = BlockchainService.this.wallet.getValue();
- postDelayedStopSelf(5 * DateUtils.MINUTE_IN_MILLIS);
+ postDelayedStopSelf(Constants.SERVICE_STOP_DELAY_AFTER_TRANSACTION);
final Coin amount = tx.getValue(wallet);
if (amount.isPositive()) {
final Address address = WalletUtils.getWalletAddressOfReceived(tx, wallet);
@@ -669,7 +686,7 @@ protected void onUnknownHost(final HostAndPort hostAndPort) {
peerGroup.startAsync();
peerGroup.startBlockChainDownload(blockchainDownloadListener);
- postDelayedStopSelf(DateUtils.MINUTE_IN_MILLIS / 2);
+ postDelayedStopSelf(Constants.SERVICE_STOP_DELAY_AFTER_START);
}
private void shutdown() {
@@ -688,7 +705,7 @@ private void shutdown() {
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
super.onStartCommand(intent, flags, startId);
- postDelayedStopSelf(DateUtils.MINUTE_IN_MILLIS);
+ postDelayedStopSelf(Constants.SERVICE_STOP_DELAY_AFTER_START);
if (intent != null) {
final String action = intent.getAction();
diff --git a/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java b/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java
index de1decf72e..7b1ed222e8 100644
--- a/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java
+++ b/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java
@@ -22,38 +22,44 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.os.AsyncTask;
+import android.text.format.DateUtils;
import androidx.annotation.WorkerThread;
import androidx.core.app.NotificationCompat;
import de.schildbach.wallet.Configuration;
import de.schildbach.wallet.Constants;
import de.schildbach.wallet.R;
import de.schildbach.wallet.WalletApplication;
+import de.schildbach.wallet.data.PaymentIntent;
import de.schildbach.wallet.ui.WalletActivity;
import de.schildbach.wallet.ui.send.FeeCategory;
import de.schildbach.wallet.ui.send.SendCoinsActivity;
import org.bitcoinj.core.Coin;
+import org.bitcoinj.utils.ContextPropagatingThreadFactory;
import org.bitcoinj.utils.MonetaryFormat;
import org.bitcoinj.wallet.Wallet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
/**
* @author Andreas Schildbach
*/
public class BootstrapReceiver extends BroadcastReceiver {
+ private final Executor executor = Executors.newSingleThreadExecutor(new ContextPropagatingThreadFactory("bootstrap"));
+
private static final Logger log = LoggerFactory.getLogger(BootstrapReceiver.class);
private static final String ACTION_DISMISS = BootstrapReceiver.class.getPackage().getName() + ".dismiss";
private static final String ACTION_DISMISS_FOREVER = BootstrapReceiver.class.getPackage().getName() +
".dismiss_forever";
- private static final String ACTION_DONATE = BootstrapReceiver.class.getPackage().getName() + ".donate";
@Override
public void onReceive(final Context context, final Intent intent) {
log.info("got broadcast: " + intent);
final PendingResult result = goAsync();
- AsyncTask.execute(() -> {
+ executor.execute(() -> {
org.bitcoinj.core.Context.propagate(Constants.CONTEXT);
onAsyncReceive(context, intent);
result.finish();
@@ -79,11 +85,9 @@ private void onAsyncReceive(final Context context, final Intent intent) {
// if the app hasn't been used for a while and contains coins, maybe show reminder
maybeShowInactivityNotification(application);
} else if (ACTION_DISMISS.equals(action)) {
- dismissNotification(context);
+ dismissNotification(context, application.getConfiguration());
} else if (ACTION_DISMISS_FOREVER.equals(action)) {
dismissNotificationForever(context, application.getConfiguration());
- } else if (ACTION_DONATE.equals(action)) {
- donate(context, application.getWallet());
} else {
throw new IllegalArgumentException(action);
}
@@ -108,7 +112,7 @@ private void maybeUpgradeWallet(final Wallet wallet) {
@WorkerThread
private void maybeShowInactivityNotification(final WalletApplication application) {
final Configuration config = application.getConfiguration();
- if (!config.remindBalance() || !config.hasBeenUsed() || config.getLastUsedAgo() <= Constants.LAST_USAGE_THRESHOLD_INACTIVE_MS)
+ if (!config.isTimeToRemindBalance())
return;
final Wallet wallet = application.getWallet();
@@ -136,8 +140,8 @@ private void maybeShowInactivityNotification(final WalletApplication application
notification.setSmallIcon(R.drawable.stat_notify_received_24dp);
notification.setContentTitle(title);
notification.setContentText(text);
- notification.setContentIntent(PendingIntent.getActivity(application, 0, new Intent(application, WalletActivity.class),
- 0));
+ notification.setContentIntent(PendingIntent.getActivity(application, 0,
+ new Intent(application, WalletActivity.class), PendingIntent.FLAG_IMMUTABLE));
notification.setAutoCancel(true);
if (!canDonate) {
@@ -145,49 +149,45 @@ private void maybeShowInactivityNotification(final WalletApplication application
dismissIntent.setAction(ACTION_DISMISS);
notification.addAction(new NotificationCompat.Action.Builder(0,
application.getString(R.string.notification_inactivity_action_dismiss),
- PendingIntent.getBroadcast(application, 0, dismissIntent, 0)).build());
+ PendingIntent.getBroadcast(application, 0, dismissIntent, PendingIntent.FLAG_IMMUTABLE)).build());
}
final Intent dismissForeverIntent = new Intent(application, BootstrapReceiver.class);
dismissForeverIntent.setAction(ACTION_DISMISS_FOREVER);
notification.addAction(new NotificationCompat.Action.Builder(0,
application.getString(R.string.notification_inactivity_action_dismiss_forever),
- PendingIntent.getBroadcast(application, 0, dismissForeverIntent, 0)).build());
+ PendingIntent.getBroadcast(application, 0, dismissForeverIntent, PendingIntent.FLAG_IMMUTABLE)).build());
if (canDonate) {
- final Intent donateIntent = new Intent(application, BootstrapReceiver.class);
- donateIntent.setAction(ACTION_DONATE);
- notification.addAction(new NotificationCompat.Action.Builder(0,
- application.getString(R.string.wallet_options_donate), PendingIntent.getBroadcast(application, 0,
- donateIntent, 0)).build());
+ final PaymentIntent paymentIntent = PaymentIntent.from(Constants.DONATION_ADDRESS,
+ application.getString(R.string.wallet_donate_address_label),
+ Constants.NETWORK_PARAMETERS.getMaxMoney());
+ final Intent donateIntent = SendCoinsActivity.startIntent(application, paymentIntent,
+ FeeCategory.ECONOMIC, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(application, 0, donateIntent,
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+ final NotificationCompat.Action action = new NotificationCompat.Action.Builder(0,
+ application.getString(R.string.wallet_options_donate), pendingIntent).build();
+ notification.addAction(action);
}
- final NotificationManager nm = (NotificationManager) application.getSystemService(Context.NOTIFICATION_SERVICE);
+ final NotificationManager nm = application.getSystemService(NotificationManager.class);
nm.notify(Constants.NOTIFICATION_ID_INACTIVITY, notification.build());
}
@WorkerThread
- private void dismissNotification(final Context context) {
+ private void dismissNotification(final Context context, final Configuration config) {
log.info("dismissing inactivity notification");
- final NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ config.setRemindBalanceTimeIn(DateUtils.DAY_IN_MILLIS);
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
nm.cancel(Constants.NOTIFICATION_ID_INACTIVITY);
}
@WorkerThread
private void dismissNotificationForever(final Context context, final Configuration config) {
log.info("dismissing inactivity notification forever");
- config.setRemindBalance(false);
- final NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- nm.cancel(Constants.NOTIFICATION_ID_INACTIVITY);
- }
-
- @WorkerThread
- private void donate(final Context context, final Wallet wallet) {
- final Coin balance = wallet.getBalance(Wallet.BalanceType.AVAILABLE_SPENDABLE);
- SendCoinsActivity.startDonate(context, balance, FeeCategory.ECONOMIC,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- final NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ config.setRemindBalanceTimeIn(DateUtils.WEEK_IN_MILLIS * 52);
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
nm.cancel(Constants.NOTIFICATION_ID_INACTIVITY);
- context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
}
diff --git a/wallet/src/de/schildbach/wallet/service/StartBlockchainService.java b/wallet/src/de/schildbach/wallet/service/StartBlockchainService.java
index c2fe1994f9..499ed34683 100644
--- a/wallet/src/de/schildbach/wallet/service/StartBlockchainService.java
+++ b/wallet/src/de/schildbach/wallet/service/StartBlockchainService.java
@@ -22,10 +22,8 @@
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.Build;
import android.os.PowerManager;
import android.text.format.DateUtils;
import de.schildbach.wallet.Configuration;
@@ -61,24 +59,22 @@ else if (lastUsedAgo < Constants.LAST_USAGE_THRESHOLD_RECENTLY_MS)
lastUsedAgo / DateUtils.MINUTE_IN_MILLIS, expectLargeData ? " and expecting large data" : "",
interval / DateUtils.MINUTE_IN_MILLIS);
- final JobScheduler jobScheduler = (JobScheduler) application.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ final JobScheduler jobScheduler = application.getSystemService(JobScheduler.class);
final JobInfo.Builder jobInfo = new JobInfo.Builder(0, new ComponentName(application,
StartBlockchainService.class));
jobInfo.setMinimumLatency(interval);
jobInfo.setOverrideDeadline(DateUtils.WEEK_IN_MILLIS);
jobInfo.setRequiredNetworkType(expectLargeData ? JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY);
jobInfo.setRequiresDeviceIdle(true);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- jobInfo.setRequiresBatteryNotLow(true);
- jobInfo.setRequiresStorageNotLow(true);
- }
+ jobInfo.setRequiresBatteryNotLow(true);
+ jobInfo.setRequiresStorageNotLow(true);
jobScheduler.schedule(jobInfo.build());
}
@Override
public void onCreate() {
super.onCreate();
- pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ pm = getSystemService(PowerManager.class);
}
@Override
diff --git a/wallet/src/de/schildbach/wallet/ui/AbstractWalletActivity.java b/wallet/src/de/schildbach/wallet/ui/AbstractWalletActivity.java
index 0eb320e5e4..24771b6329 100644
--- a/wallet/src/de/schildbach/wallet/ui/AbstractWalletActivity.java
+++ b/wallet/src/de/schildbach/wallet/ui/AbstractWalletActivity.java
@@ -75,8 +75,11 @@ public void startExternalDocument(final Uri url) {
try {
startActivity(new Intent(Intent.ACTION_VIEW, url));
} catch (final ActivityNotFoundException x) {
- log.info("Cannot view {}", url);
+ log.info("Cannot view " + url, x);
new Toast(this).longToast(R.string.toast_start_external_document_failed);
+ } catch (final Exception x) {
+ log.info("Cannot view " + url, x);
+ new Toast(this).longToast(getString(R.string.toast_start_external_document_error, x.getClass().getName()));
}
}
}
diff --git a/wallet/src/de/schildbach/wallet/ui/AddressBookActivity.java b/wallet/src/de/schildbach/wallet/ui/AddressBookActivity.java
index 21d2068134..68b5e69aa0 100644
--- a/wallet/src/de/schildbach/wallet/ui/AddressBookActivity.java
+++ b/wallet/src/de/schildbach/wallet/ui/AddressBookActivity.java
@@ -17,14 +17,16 @@
package de.schildbach.wallet.ui;
-import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
+import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
@@ -52,13 +54,42 @@ public static void start(final Context context) {
private AbstractWalletActivityViewModel walletActivityViewModel;
private AddressBookViewModel viewModel;
- private static final int REQUEST_CODE_SCAN = 0;
-
public static final int POSITION_WALLET_ADDRESSES = 0;
public static final int POSITION_SENDING_ADDRESSES = 1;
private static final int[] TAB_LABELS = { R.string.address_book_list_receiving_title,
R.string.address_book_list_sending_title };
+ private final ActivityResultLauncher scanLauncher =
+ registerForActivityResult(new ScanActivity.Scan(), input -> {
+ if (input == null) return;
+ new InputParser.StringInputParser(input) {
+ @Override
+ protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
+ if (paymentIntent.hasAddress()) {
+ final Wallet wallet = walletActivityViewModel.wallet.getValue();
+ final Address address = paymentIntent.getAddress();
+ if (!wallet.isAddressMine(address)) {
+ viewModel.showEditAddressBookEntryDialog.setValue(new Event<>(address));
+ } else {
+ viewModel.showScanOwnAddressDialog.setValue(Event.simple());
+ }
+ } else {
+ viewModel.showScanInvalidDialog.setValue(Event.simple());
+ }
+ }
+
+ @Override
+ protected void handleDirectTransaction(final Transaction transaction) throws VerificationException {
+ cannotClassify(input);
+ }
+
+ @Override
+ protected void error(final int messageResId, final Object... messageArgs) {
+ viewModel.showScanInvalidDialog.setValue(Event.simple());
+ }
+ }.parse();
+ });
+
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -125,63 +156,30 @@ protected void onEvent(final Void v) {
pager.setOffscreenPageLimit(1);
pager.setAdapter(new AddressBookActivity.PagerAdapter());
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
- if (requestCode == REQUEST_CODE_SCAN) {
- if (resultCode == Activity.RESULT_OK) {
- final String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
- new InputParser.StringInputParser(input) {
- @Override
- protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
- if (paymentIntent.hasAddress()) {
- final Wallet wallet = walletActivityViewModel.wallet.getValue();
- final Address address = paymentIntent.getAddress();
- if (!wallet.isAddressMine(address)) {
- viewModel.showEditAddressBookEntryDialog.setValue(new Event<>(address));
- } else {
- viewModel.showScanOwnAddressDialog.setValue(Event.simple());
- }
- } else {
- viewModel.showScanInvalidDialog.setValue(Event.simple());
- }
- }
-
- @Override
- protected void handleDirectTransaction(final Transaction transaction) throws VerificationException {
- cannotClassify(input);
- }
-
- @Override
- protected void error(final int messageResId, final Object... messageArgs) {
- viewModel.showScanInvalidDialog.setValue(Event.simple());
- }
- }.parse();
+ addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.address_book_activity_options, menu);
}
- } else {
- super.onActivityResult(requestCode, resultCode, intent);
- }
- }
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- getMenuInflater().inflate(R.menu.address_book_activity_options, menu);
- final PackageManager pm = getPackageManager();
- menu.findItem(R.id.sending_addresses_options_scan).setVisible(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
- || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT));
- return super.onCreateOptionsMenu(menu);
- }
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ final PackageManager pm = getPackageManager();
+ menu.findItem(R.id.sending_addresses_options_scan).setVisible(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
+ || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT));
+ }
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- int itemId = item.getItemId();
- if (itemId == R.id.sending_addresses_options_scan) {
- ScanActivity.startForResult(this, REQUEST_CODE_SCAN);
- return true;
- }
- return super.onOptionsItemSelected(item);
+ @Override
+ public boolean onMenuItemSelected(final MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == R.id.sending_addresses_options_scan) {
+ scanLauncher.launch(null);
+ return true;
+ }
+ return false;
+ }
+ });
}
private class PagerAdapter extends FragmentStateAdapter {
diff --git a/wallet/src/de/schildbach/wallet/ui/AlertDialogsFragment.java b/wallet/src/de/schildbach/wallet/ui/AlertDialogsFragment.java
index b642792971..f73f283116 100644
--- a/wallet/src/de/schildbach/wallet/ui/AlertDialogsFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/AlertDialogsFragment.java
@@ -17,52 +17,27 @@
package de.schildbach.wallet.ui;
+import android.Manifest;
import android.app.Dialog;
-import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
-import android.text.format.DateUtils;
-import androidx.annotation.Nullable;
+import android.provider.Settings;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
-import com.google.common.base.Splitter;
-import com.google.common.primitives.Ints;
+import de.schildbach.wallet.Configuration;
import de.schildbach.wallet.Constants;
import de.schildbach.wallet.R;
-import de.schildbach.wallet.WalletApplication;
-import de.schildbach.wallet.util.CrashReporter;
import de.schildbach.wallet.util.Installer;
-import okhttp3.Call;
-import okhttp3.ConnectionSpec;
-import okhttp3.Headers;
-import okhttp3.HttpUrl;
-import okhttp3.OkHttpClient.Builder;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.bitcoinj.core.Coin;
-import org.bitcoinj.params.MainNetParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.BufferedReader;
-import java.net.SocketException;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Locale;
-import java.util.Map;
-
/**
* @author Andreas Schildbach
*/
@@ -78,21 +53,24 @@ public static void add(final FragmentManager fm) {
}
private AbstractWalletActivity activity;
- private WalletApplication application;
private PackageManager packageManager;
- private @Nullable Installer installer;
+ private Configuration config;
private AlertDialogsViewModel viewModel;
private static final Logger log = LoggerFactory.getLogger(AlertDialogsFragment.class);
+ private final ActivityResultLauncher requestPermissionLauncher =
+ registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> {
+ // do nothing
+ });
+
@Override
public void onAttach(final Context context) {
super.onAttach(context);
this.activity = (AbstractWalletActivity) context;
- this.application = activity.getWalletApplication();
this.packageManager = activity.getPackageManager();
- this.installer = Installer.from(application);
+ this.config = activity.getWalletApplication().getConfiguration();
}
@Override
@@ -107,11 +85,11 @@ protected void onEvent(final Long diffMinutes) {
createTimeskewAlertDialog(diffMinutes).show();
}
});
- viewModel.showVersionAlertDialog.observe(this, new Event.Observer() {
+ viewModel.showVersionAlertDialog.observe(this, new Event.Observer() {
@Override
- protected void onEvent(final Void v) {
+ protected void onEvent(final Installer market) {
log.info("showing version alert dialog");
- createVersionAlertDialog().show();
+ createVersionAlertDialog(market).show();
}
});
viewModel.showInsecureDeviceAlertDialog.observe(this, new Event.Observer() {
@@ -121,13 +99,6 @@ protected void onEvent(final String minSecurityPatchLevel) {
createInsecureDeviceAlertDialog(minSecurityPatchLevel).show();
}
});
- viewModel.showInsecureBluetoothAlertDialog.observe(this, new Event.Observer() {
- @Override
- protected void onEvent(final String minSecurityPatchLevel) {
- log.info("showing insecure bluetooth alert dialog");
- createInsecureBluetoothAlertDialog(minSecurityPatchLevel).show();
- }
- });
viewModel.showLowStorageAlertDialog.observe(this, new Event.Observer() {
@Override
protected void onEvent(final Void v) {
@@ -149,156 +120,22 @@ protected void onEvent(final Void v) {
createTooMuchBalanceAlertDialog().show();
}
});
-
- if (savedInstanceState == null)
- process();
- }
-
- private void process() {
- final PackageInfo packageInfo = application.packageInfo();
- final HttpUrl.Builder url = Constants.VERSION_URL.newBuilder();
- url.addEncodedQueryParameter("package", packageInfo.packageName);
- final String installerPackageName = Installer.installerPackageName(application);
- if (installerPackageName != null)
- url.addEncodedQueryParameter("installer", installerPackageName);
- url.addQueryParameter("sdk", Integer.toString(Build.VERSION.SDK_INT));
- url.addQueryParameter("current", Integer.toString(packageInfo.versionCode));
- final HttpUrl versionUrl = url.build();
-
- AsyncTask.execute(() -> {
- try {
- log.debug("querying \"{}\"...", versionUrl);
- final Request.Builder request = new Request.Builder();
- request.url(versionUrl);
- final Headers.Builder headers = new Headers.Builder();
- headers.add("Accept-Charset", "utf-8");
- final String userAgent = application.httpUserAgent();
- if (userAgent != null)
- headers.add("User-Agent", userAgent);
- request.headers(headers.build());
-
- final Builder httpClientBuilder = Constants.HTTP_CLIENT.newBuilder();
- httpClientBuilder.connectionSpecs(Collections.singletonList(ConnectionSpec.RESTRICTED_TLS));
- final Call call = httpClientBuilder.build().newCall(request.build());
-
- final Response response = call.execute();
- if (response.isSuccessful()) {
- // Maybe show timeskew alert.
- final Date serverDate = response.headers().getDate("Date");
- if (serverDate != null) {
- final long diffMinutes = Math.abs(
- (System.currentTimeMillis() - serverDate.getTime()) / DateUtils.MINUTE_IN_MILLIS);
- if (diffMinutes >= 60) {
- log.info("according to \"" + versionUrl + "\", system clock is off by " + diffMinutes
- + " minutes");
- viewModel.showTimeskewAlertDialog.postValue(new Event<>(diffMinutes));
- return;
- }
- }
-
- // Read properties from server.
- final Map properties = new HashMap<>();
- try (final BufferedReader reader = new BufferedReader(response.body().charStream())) {
- while (true) {
- final String line = reader.readLine();
- if (line == null)
- break;
- if (line.charAt(0) == '#')
- continue;
-
- final Splitter splitter = Splitter.on('=').trimResults();
- final Iterator split = splitter.split(line).iterator();
- if (!split.hasNext())
- continue;
- final String key = split.next();
- if (!split.hasNext()) {
- properties.put(null, key);
- continue;
- }
- final String value = split.next();
- if (!split.hasNext()) {
- properties.put(key.toLowerCase(Locale.US), value);
- continue;
- }
- log.info("Ignoring line: {}", line);
- }
- }
-
- // Maybe show version alert.
- String versionKey = null;
- String version = null;
- if (installer != null) {
- versionKey = "version." + installer.name().toLowerCase(Locale.US);
- version = properties.get(versionKey);
- }
- if (version == null) {
- versionKey = "version";
- version = properties.get(versionKey);
- }
- if (version != null) {
- log.info("according to \"{}\", strongly recommended minimum app {} is \"{}\"", versionUrl,
- versionKey, version);
- final Integer recommendedVersionCode = Ints.tryParse(version);
- if (recommendedVersionCode != null) {
- if (recommendedVersionCode > application.packageInfo().versionCode) {
- viewModel.showVersionAlertDialog.postValue(Event.simple());
- return;
- }
- }
- }
-
- // Maybe show insecure device alert.
- if (Build.VERSION.SECURITY_PATCH.compareToIgnoreCase(Constants.SECURITY_PATCH_INSECURE_BELOW) < 0) {
- viewModel.showInsecureDeviceAlertDialog.postValue(new Event<>(Constants.SECURITY_PATCH_INSECURE_BELOW));
- return;
- }
-
- // Maybe show insecure bluetooth alert.
- final String minSecurityPatchLevel = properties.get("min.security_patch.bluetooth");
- if (minSecurityPatchLevel != null) {
- log.info("according to \"{}\", minimum security patch level for bluetooth is {}",
- versionUrl, minSecurityPatchLevel);
- if (Build.VERSION.SECURITY_PATCH.compareTo(minSecurityPatchLevel) < 0) {
- final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- if (bluetoothAdapter != null && BluetoothAdapter.getDefaultAdapter().isEnabled()) {
- viewModel.showInsecureBluetoothAlertDialog
- .postValue(new Event<>(minSecurityPatchLevel));
- return;
- }
- }
- }
-
- // Maybe show low storage alert.
- final Intent stickyIntent = activity.registerReceiver(null,
- new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
- if (stickyIntent != null) {
- viewModel.showLowStorageAlertDialog.postValue(Event.simple());
- return;
- }
-
- // Maybe show too much balance alert.
- if (Constants.NETWORK_PARAMETERS.getId().equals(MainNetParams.ID_MAINNET)) {
- final Coin balance = application.getWallet().getBalance();
- if (balance.isGreaterThan(Constants.TOO_MUCH_BALANCE_THRESHOLD)) {
- viewModel.showTooMuchBalanceAlertDialog.postValue(Event.simple());
- return;
- }
- }
-
- log.info("all good, no alert dialog shown");
- }
- } catch (final Exception x) {
- if (x instanceof UnknownHostException || x instanceof SocketException
- || x instanceof SocketTimeoutException) {
- // swallow
- log.debug("problem reading", x);
- } else {
- CrashReporter.saveBackgroundTrace(new RuntimeException(versionUrl.toString(), x),
- application.packageInfo());
- log.warn("problem parsing", x);
- }
+ viewModel.showBatteryOptimizationDialog.observe(this, new Event.Observer() {
+ @Override
+ protected void onEvent(final Void v) {
+ log.info("showing battery optimization dialog");
+ createBatteryOptimizationDialog().show();
}
});
+ viewModel.startBatteryOptimizationActivity.observe(this, v ->
+ startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
+ Uri.parse("package:" + activity.getPackageName())))
+ );
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ viewModel.requestNotificationPermissionDialog.observe(this, v ->
+ requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
+ );
+ }
}
private Dialog createTimeskewAlertDialog(final long diffMinutes) {
@@ -319,20 +156,19 @@ private Dialog createTimeskewAlertDialog(final long diffMinutes) {
return dialog.create();
}
- private Dialog createVersionAlertDialog() {
- final Installer installer = this.installer != null ? this.installer : Installer.F_DROID;
+ private Dialog createVersionAlertDialog(final Installer market) {
final Intent marketIntent = new Intent(Intent.ACTION_VIEW,
- Uri.parse(installer.appStorePageFor(application).toString()));
+ Uri.parse(market.appStorePageFor(activity).toString()));
final Intent binaryIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.BINARY_URL));
final StringBuilder message = new StringBuilder(
- getString(R.string.wallet_version_dialog_msg, installer.displayName));
+ getString(R.string.wallet_version_dialog_msg, market.displayName));
if (Build.VERSION.SDK_INT < Constants.SDK_DEPRECATED_BELOW)
message.append("\n\n").append(getString(R.string.wallet_version_dialog_msg_deprecated));
final DialogBuilder dialog = DialogBuilder.warn(activity, R.string.wallet_version_dialog_title, message);
if (packageManager.resolveActivity(marketIntent, 0) != null) {
- dialog.setPositiveButton(installer.displayName, (d, id) -> {
+ dialog.setPositiveButton(market.displayName, (d, id) -> {
startActivity(marketIntent);
activity.finish();
});
@@ -352,31 +188,12 @@ private Dialog createVersionAlertDialog() {
private Dialog createInsecureDeviceAlertDialog(final String minSecurityPatch) {
final DialogBuilder dialog = DialogBuilder.warn(activity,
- R.string.alert_dialogs_fragment_insecure_bluetooth_title,
+ R.string.alert_dialogs_fragment_insecure_device_title,
R.string.wallet_balance_fragment_insecure_device);
dialog.setNegativeButton(R.string.button_dismiss, null);
return dialog.create();
}
- private Dialog createInsecureBluetoothAlertDialog(final String minSecurityPatch) {
- final Intent settingsIntent = new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
- final DialogBuilder dialog = DialogBuilder.warn(activity,
- R.string.alert_dialogs_fragment_insecure_bluetooth_title,
- R.string.alert_dialogs_fragment_insecure_bluetooth_message, minSecurityPatch);
- if (packageManager.resolveActivity(settingsIntent, 0) != null) {
- dialog.setPositiveButton(R.string.button_settings, (d, id) -> {
- try {
- startActivity(settingsIntent);
- activity.finish();
- } catch (final Exception x) {
- viewModel.showSettingsFailedDialog.setValue(new Event<>(x.getMessage()));
- }
- });
- }
- dialog.setNegativeButton(R.string.button_dismiss, null);
- return dialog.create();
- }
-
private Dialog createLowStorageAlertDialog() {
final Intent settingsIntent = new Intent(android.provider.Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
final DialogBuilder dialog = DialogBuilder.warn(activity, R.string.wallet_low_storage_dialog_title,
@@ -410,4 +227,15 @@ private Dialog createTooMuchBalanceAlertDialog() {
dialog.singleDismissButton(null);
return dialog.create();
}
+
+ private Dialog createBatteryOptimizationDialog() {
+ final DialogBuilder dialog = DialogBuilder.dialog(activity,
+ R.string.alert_dialogs_fragment_battery_optimization_dialog_title,
+ R.string.alert_dialogs_fragment_battery_optimization_dialog_message);
+ dialog.setPositiveButton(R.string.alert_dialogs_fragment_battery_optimization_dialog_button_allow,
+ (d, which) -> viewModel.handleBatteryOptimizationDialogPositiveButton());
+ dialog.setNegativeButton(R.string.button_dismiss,
+ (d, which) -> viewModel.handleBatteryOptimizationDialogNegativeButton());
+ return dialog.create();
+ }
}
diff --git a/wallet/src/de/schildbach/wallet/ui/AlertDialogsViewModel.java b/wallet/src/de/schildbach/wallet/ui/AlertDialogsViewModel.java
index ed9bdff6fc..ef640eb831 100644
--- a/wallet/src/de/schildbach/wallet/ui/AlertDialogsViewModel.java
+++ b/wallet/src/de/schildbach/wallet/ui/AlertDialogsViewModel.java
@@ -17,18 +17,251 @@
package de.schildbach.wallet.ui;
+import android.Manifest;
+import android.app.Application;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.PowerManager;
+import android.text.format.DateUtils;
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.ViewModel;
+import com.google.common.base.Splitter;
+import com.google.common.primitives.Ints;
+import de.schildbach.wallet.Configuration;
+import de.schildbach.wallet.Constants;
+import de.schildbach.wallet.WalletApplication;
+import de.schildbach.wallet.util.CrashReporter;
+import de.schildbach.wallet.util.Installer;
+import okhttp3.Call;
+import okhttp3.ConnectionSpec;
+import okhttp3.Headers;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.bitcoinj.core.Coin;
+import org.bitcoinj.params.MainNetParams;
+import org.bitcoinj.utils.ContextPropagatingThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/**
* @author Andreas Schildbach
*/
-public class AlertDialogsViewModel extends ViewModel {
+public class AlertDialogsViewModel extends AndroidViewModel {
+ private final WalletApplication application;
+ private final Configuration config;
+ private final PowerManager powerManager;
+ public final @Nullable Installer installer;
public final MutableLiveData> showTimeskewAlertDialog = new MutableLiveData<>();
- public final MutableLiveData> showVersionAlertDialog = new MutableLiveData<>();
+ public final MutableLiveData> showVersionAlertDialog = new MutableLiveData<>();
public final MutableLiveData> showInsecureDeviceAlertDialog = new MutableLiveData<>();
- public final MutableLiveData> showInsecureBluetoothAlertDialog = new MutableLiveData<>();
public final MutableLiveData> showLowStorageAlertDialog = new MutableLiveData<>();
public final MutableLiveData> showSettingsFailedDialog = new MutableLiveData<>();
public final MutableLiveData> showTooMuchBalanceAlertDialog = new MutableLiveData<>();
+ public final MutableLiveData> showBatteryOptimizationDialog = new MutableLiveData<>();
+ public final MutableLiveData> startBatteryOptimizationActivity = new MutableLiveData<>();
+ public final MutableLiveData> requestNotificationPermissionDialog = new MutableLiveData<>();
+
+ private final ExecutorService executor = Executors.newSingleThreadExecutor(
+ new ContextPropagatingThreadFactory("query-versions"));
+
+ private static final Logger log = LoggerFactory.getLogger(AlertDialogsViewModel.class);
+
+ public AlertDialogsViewModel(final Application application) {
+ super(application);
+ this.application = (WalletApplication) application;
+ this.config = this.application.getConfiguration();
+ this.powerManager = application.getSystemService(PowerManager.class);
+ this.installer = Installer.from(application);
+
+ process();
+ }
+
+ @MainThread
+ private void process() {
+ final PackageInfo packageInfo = application.packageInfo();
+ final HttpUrl.Builder url = Constants.VERSION_URL.newBuilder();
+ url.addEncodedQueryParameter("package", packageInfo.packageName);
+ final String installerPackageName = Installer.installerPackageName(application);
+ if (installerPackageName != null)
+ url.addEncodedQueryParameter("installer", installerPackageName);
+ url.addQueryParameter("sdk", Integer.toString(Build.VERSION.SDK_INT));
+ url.addQueryParameter("current", Integer.toString(packageInfo.versionCode));
+
+ executor.execute(() -> processAsync(url.build()));
+ }
+
+ @WorkerThread
+ private void processAsync(final HttpUrl versionUrl) {
+ try {
+ log.debug("querying \"{}\"...", versionUrl);
+ final Request.Builder request = new Request.Builder();
+ request.url(versionUrl);
+ final Headers.Builder headers = new Headers.Builder();
+ headers.add("Accept-Charset", "utf-8");
+ final String userAgent = application.httpUserAgent();
+ if (userAgent != null)
+ headers.add("User-Agent", userAgent);
+ request.headers(headers.build());
+
+ final OkHttpClient.Builder httpClientBuilder = Constants.HTTP_CLIENT.newBuilder();
+ httpClientBuilder.connectionSpecs(Collections.singletonList(ConnectionSpec.RESTRICTED_TLS));
+ final Call call = httpClientBuilder.build().newCall(request.build());
+
+ final Response response = call.execute();
+ if (response.isSuccessful()) {
+ // Maybe show timeskew alert.
+ final Date serverDate = response.headers().getDate("Date");
+ if (serverDate != null) {
+ final long diffMinutes = Math.abs(
+ (System.currentTimeMillis() - serverDate.getTime()) / DateUtils.MINUTE_IN_MILLIS);
+ if (diffMinutes >= 60) {
+ log.info("according to \"" + versionUrl + "\", system clock is off by " + diffMinutes
+ + " minutes");
+ showTimeskewAlertDialog.postValue(new Event<>(diffMinutes));
+ return;
+ }
+ }
+
+ // Read properties from server.
+ final Map properties = new HashMap<>();
+ try (final BufferedReader reader = new BufferedReader(response.body().charStream())) {
+ while (true) {
+ final String line = reader.readLine();
+ if (line == null)
+ break;
+ if (line.charAt(0) == '#')
+ continue;
+
+ final Splitter splitter = Splitter.on('=').trimResults();
+ final Iterator split = splitter.split(line).iterator();
+ if (!split.hasNext())
+ continue;
+ final String key = split.next();
+ if (!split.hasNext()) {
+ properties.put(null, key);
+ continue;
+ }
+ final String value = split.next();
+ if (!split.hasNext()) {
+ properties.put(key.toLowerCase(Locale.US), value);
+ continue;
+ }
+ log.info("Ignoring line: {}", line);
+ }
+ }
+
+ // Maybe show version alert.
+ String recommendedVersionKey = "version";
+ Integer recommendedVersion = properties.containsKey(recommendedVersionKey) ?
+ Ints.tryParse(properties.get(recommendedVersionKey)) : null;
+ Installer recommendedMarket = Installer.F_DROID;
+ if (installer != null) {
+ final String versionKey = "version." + installer.name().toLowerCase(Locale.US);
+ final Integer version = properties.containsKey(versionKey) ?
+ Ints.tryParse(properties.get(versionKey)) : null;
+ if (recommendedVersion == null || (version != null && version > recommendedVersion)) {
+ recommendedVersionKey = versionKey;
+ recommendedVersion = version;
+ recommendedMarket = installer;
+ }
+ }
+ if (recommendedVersion != null) {
+ log.info("according to \"{}\" strongly recommended minimum app {} is \"{}\", recommended " +
+ "market is {}", versionUrl, recommendedVersionKey, recommendedVersion, recommendedMarket);
+ if (recommendedVersion > application.packageInfo().versionCode) {
+ showVersionAlertDialog.postValue(new Event<>(recommendedMarket));
+ return;
+ }
+ }
+
+ // Maybe show insecure device alert.
+ if (Build.VERSION.SECURITY_PATCH.compareToIgnoreCase(Constants.SECURITY_PATCH_INSECURE_BELOW) < 0) {
+ showInsecureDeviceAlertDialog.postValue(new Event<>(Constants.SECURITY_PATCH_INSECURE_BELOW));
+ return;
+ }
+
+ // Maybe show low storage alert.
+ final Intent stickyIntent = application.registerReceiver(null,
+ new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
+ if (stickyIntent != null) {
+ showLowStorageAlertDialog.postValue(Event.simple());
+ return;
+ }
+
+ // Maybe show too much balance alert.
+ if (Constants.NETWORK_PARAMETERS.getId().equals(MainNetParams.ID_MAINNET)) {
+ final Coin balance = application.getWallet().getBalance();
+ if (balance.isGreaterThan(Constants.TOO_MUCH_BALANCE_THRESHOLD)) {
+ showTooMuchBalanceAlertDialog.postValue(Event.simple());
+ return;
+ }
+ }
+
+ final boolean walletIsEmpty = application.getWallet().getTransactions(true).isEmpty();
+
+ // Maybe show battery optimization dialog.
+ if (config.isTimeForBatteryOptimizationDialog() &&
+ ContextCompat.checkSelfPermission(application,
+ Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_GRANTED &&
+ !powerManager.isIgnoringBatteryOptimizations(application.getPackageName()) &&
+ !walletIsEmpty) {
+ showBatteryOptimizationDialog.postValue(Event.simple());
+ return;
+ }
+
+ // Maybe request notification permission.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
+ ContextCompat.checkSelfPermission(application,
+ Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED &&
+ !walletIsEmpty) {
+ requestNotificationPermissionDialog.postValue(Event.simple());
+ return;
+ }
+
+ log.info("all good, no alert dialog shown");
+ }
+ } catch (final Exception x) {
+ if (x instanceof UnknownHostException || x instanceof SocketException || x instanceof SocketTimeoutException) {
+ // swallow
+ log.debug("problem reading", x);
+ } else {
+ CrashReporter.saveBackgroundTrace(new RuntimeException(versionUrl.toString(), x),
+ application.packageInfo());
+ log.warn("problem parsing", x);
+ }
+ }
+ }
+
+ @MainThread
+ public void handleBatteryOptimizationDialogPositiveButton() {
+ startBatteryOptimizationActivity.setValue(Event.simple());
+ config.removeBatteryOptimizationDialogTime();
+ }
+
+ @MainThread
+ public void handleBatteryOptimizationDialogNegativeButton() {
+ config.setBatteryOptimizationDialogTimeIn(DateUtils.WEEK_IN_MILLIS * 12);
+ }
}
diff --git a/wallet/src/de/schildbach/wallet/ui/CurrencyAmountView.java b/wallet/src/de/schildbach/wallet/ui/CurrencyAmountView.java
index 7f248a2653..039fe0a29d 100644
--- a/wallet/src/de/schildbach/wallet/ui/CurrencyAmountView.java
+++ b/wallet/src/de/schildbach/wallet/ui/CurrencyAmountView.java
@@ -40,8 +40,11 @@
import de.schildbach.wallet.util.MonetarySpannable;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Monetary;
+import org.bitcoinj.utils.Fiat;
import org.bitcoinj.utils.MonetaryFormat;
+import java.util.Objects;
+
/**
* @author Andreas Schildbach
*/
@@ -202,17 +205,25 @@ public Monetary getAmount() {
return inputFormat.parseFiat(localCurrencyCode, amountStr);
}
- public void setAmount(@Nullable final Monetary amount, final boolean fireListener) {
- if (!fireListener)
+ public void setAmount(@Nullable final Monetary amount) {
+ final Monetary oldAmount = getAmount();
+ final Spannable oldSpannable = oldAmount != null ? new MonetarySpannable(inputFormat, amountSigned, oldAmount) : null;
+ final Spannable newSpannable = amount != null ? new MonetarySpannable(inputFormat, amountSigned, amount) : null;
+ final String oldStr = oldSpannable != null ? oldSpannable.toString() : null;
+ final String newStr = newSpannable != null ? newSpannable.toString() : null;
+ if (!Objects.equals(newStr, oldStr)) {
+ // never fire listener if set from externally
textViewListener.setFire(false);
-
- if (amount != null)
- textView.setText(new MonetarySpannable(inputFormat, amountSigned, amount));
- else
- textView.setText(null);
-
- if (!fireListener)
+ final int selectionStart = textView.getSelectionStart();
+ final int selectionEnd = textView.getSelectionEnd();
+ textView.setText(newSpannable);
+ if (textView instanceof EditText && newSpannable != null && selectionStart != -1 && selectionEnd != -1) {
+ // preserve cursor position
+ final int limit = newSpannable.length();
+ ((EditText) textView).setSelection(Math.min(selectionStart, limit), Math.min(selectionEnd, limit));
+ }
textViewListener.setFire(true);
+ }
}
@Override
@@ -243,7 +254,6 @@ public TextView getTextView() {
public void setNextFocusId(final int nextFocusId) {
textView.setNextFocusDownId(nextFocusId);
- textView.setNextFocusForwardId(nextFocusId);
}
private boolean isValidAmount(final boolean zeroIsValid) {
@@ -272,7 +282,8 @@ private boolean isValidAmount(final boolean zeroIsValid) {
private final OnClickListener deleteClickListener = new OnClickListener() {
@Override
public void onClick(final View v) {
- setAmount(null, true);
+ // intentionally fire listener
+ textView.setText(null);
textView.requestFocus();
}
};
@@ -309,7 +320,13 @@ protected Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable("super_state", super.onSaveInstanceState());
state.putParcelable("child_textview", textView.onSaveInstanceState());
- state.putSerializable("amount", getAmount());
+ final Monetary amount = getAmount();
+ if (amount instanceof Coin) {
+ state.putLong("coin_amount", amount.getValue());
+ } else if (amount instanceof Fiat) {
+ state.putLong("fiat_amount", amount.getValue());
+ state.putString("fiat_currency", ((Fiat) amount).getCurrencyCode());
+ }
return state;
}
@@ -319,7 +336,12 @@ protected void onRestoreInstanceState(final Parcelable state) {
final Bundle bundle = (Bundle) state;
super.onRestoreInstanceState(bundle.getParcelable("super_state"));
textView.onRestoreInstanceState(bundle.getParcelable("child_textview"));
- setAmount((Monetary) bundle.getSerializable("amount"), false);
+ if (bundle.containsKey("coin_amount"))
+ setAmount(Coin.valueOf(bundle.getLong("coin_amount")));
+ else if (bundle.containsKey("fiat_amount"))
+ setAmount(Fiat.valueOf(bundle.getString("fiat_currency"), bundle.getLong("fiat_amount")));
+ else
+ setAmount(null);
} else {
super.onRestoreInstanceState(state);
}
@@ -364,7 +386,7 @@ public void onFocusChange(final View v, final boolean hasFocus) {
if (!hasFocus) {
final Monetary amount = getAmount();
if (amount != null)
- setAmount(amount, false);
+ setAmount(amount);
}
if (listener != null && fire)
diff --git a/wallet/src/de/schildbach/wallet/ui/CurrencyCalculatorLink.java b/wallet/src/de/schildbach/wallet/ui/CurrencyCalculatorLink.java
index 6634e862b9..564e473b71 100644
--- a/wallet/src/de/schildbach/wallet/ui/CurrencyCalculatorLink.java
+++ b/wallet/src/de/schildbach/wallet/ui/CurrencyCalculatorLink.java
@@ -25,6 +25,8 @@
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.Fiat;
+import java.util.Objects;
+
/**
* @author Andreas Schildbach
*/
@@ -90,15 +92,17 @@ public void setListener(@Nullable final Listener listener) {
}
public void setEnabled(final boolean enabled) {
- this.enabled = enabled;
-
- update();
+ if (enabled != this.enabled) {
+ this.enabled = enabled;
+ update();
+ }
}
public void setExchangeRate(final ExchangeRate exchangeRate) {
- this.exchangeRate = exchangeRate;
-
- update();
+ if (!Objects.equals(exchangeRate, this.exchangeRate)) {
+ this.exchangeRate = exchangeRate;
+ update();
+ }
}
public ExchangeRate getExchangeRate() {
@@ -107,23 +111,12 @@ public ExchangeRate getExchangeRate() {
@Nullable
public Coin getAmount() {
- if (exchangeDirection) {
+ if (exchangeDirection)
return (Coin) btcAmountView.getAmount();
- } else if (exchangeRate != null) {
- final Fiat localAmount = (Fiat) localAmountView.getAmount();
- if (localAmount == null)
- return null;
- try {
- final Coin btcAmount = exchangeRate.fiatToCoin(localAmount);
- if (((Coin) btcAmount).isGreaterThan(Constants.NETWORK_PARAMETERS.getMaxMoney()))
- throw new ArithmeticException();
- return btcAmount;
- } catch (ArithmeticException x) {
- return null;
- }
- } else {
+ else if (exchangeRate != null)
+ return fiatToCoin((Fiat) localAmountView.getAmount());
+ else
return null;
- }
}
public boolean hasAmount() {
@@ -141,27 +134,15 @@ private void update() {
final Coin btcAmount = (Coin) btcAmountView.getAmount();
if (btcAmount != null) {
btcAmountView.setHint(null);
- localAmountView.setAmount(null, false);
- try {
- final Fiat localAmount = exchangeRate.coinToFiat(btcAmount);
- localAmountView.setHint(localAmount);
- } catch (final ArithmeticException x) {
- localAmountView.setHint(null);
- }
+ localAmountView.setAmount(null);
+ localAmountView.setHint(coinToFiat(btcAmount));
}
} else {
final Fiat localAmount = (Fiat) localAmountView.getAmount();
if (localAmount != null) {
localAmountView.setHint(null);
- btcAmountView.setAmount(null, false);
- try {
- final Coin btcAmount = exchangeRate.fiatToCoin(localAmount);
- if (((Coin) btcAmount).isGreaterThan(Constants.NETWORK_PARAMETERS.getMaxMoney()))
- throw new ArithmeticException();
- btcAmountView.setHint(btcAmount);
- } catch (final ArithmeticException x) {
- btcAmountView.setHint(null);
- }
+ btcAmountView.setAmount(null);
+ btcAmountView.setHint(fiatToCoin(localAmount));
}
}
} else {
@@ -172,9 +153,10 @@ private void update() {
}
public void setExchangeDirection(final boolean exchangeDirection) {
- this.exchangeDirection = exchangeDirection;
-
- update();
+ if (exchangeDirection != this.exchangeDirection) {
+ this.exchangeDirection = exchangeDirection;
+ update();
+ }
}
public boolean getExchangeDirection() {
@@ -196,7 +178,15 @@ public void setBtcAmount(final Coin amount) {
final Listener listener = this.listener;
this.listener = null;
- btcAmountView.setAmount(amount, true);
+ if (exchangeDirection) {
+ btcAmountView.setAmount(amount);
+ if (exchangeRate != null)
+ localAmountView.setHint(coinToFiat(amount));
+ } else {
+ btcAmountView.setHint(amount);
+ if (exchangeRate != null)
+ localAmountView.setAmount(coinToFiat(amount));
+ }
this.listener = listener;
}
@@ -205,4 +195,27 @@ public void setNextFocusId(final int nextFocusId) {
btcAmountView.setNextFocusId(nextFocusId);
localAmountView.setNextFocusId(nextFocusId);
}
+
+ private Fiat coinToFiat(final Coin coinAmount) {
+ if (coinAmount == null)
+ return null;
+ try {
+ return exchangeRate.coinToFiat(coinAmount);
+ } catch (final ArithmeticException x) {
+ return null;
+ }
+ }
+
+ private Coin fiatToCoin(final Fiat fiatAmount) {
+ if (fiatAmount == null)
+ return null;
+ try {
+ final Coin coin = exchangeRate.fiatToCoin(fiatAmount);
+ if (coin.isGreaterThan(Constants.NETWORK_PARAMETERS.getMaxMoney()))
+ throw new ArithmeticException();
+ return coin;
+ } catch (final ArithmeticException x) {
+ return null;
+ }
+ }
}
diff --git a/wallet/src/de/schildbach/wallet/ui/Event.java b/wallet/src/de/schildbach/wallet/ui/Event.java
index fd78390182..b4e56d1e13 100644
--- a/wallet/src/de/schildbach/wallet/ui/Event.java
+++ b/wallet/src/de/schildbach/wallet/ui/Event.java
@@ -17,7 +17,7 @@
package de.schildbach.wallet.ui;
-import static androidx.core.util.Preconditions.checkState;
+import static com.google.common.base.Preconditions.checkState;
/**
* @author Andreas Schildbach
diff --git a/wallet/src/de/schildbach/wallet/ui/ExchangeRatesFragment.java b/wallet/src/de/schildbach/wallet/ui/ExchangeRatesFragment.java
index 016168998d..135b59562e 100644
--- a/wallet/src/de/schildbach/wallet/ui/ExchangeRatesFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/ExchangeRatesFragment.java
@@ -32,6 +32,7 @@
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.ViewAnimator;
+import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -70,7 +71,6 @@ public void onAttach(final Context context) {
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
viewModel = new ViewModelProvider(this).get(ExchangeRatesViewModel.class);
if (config.isEnableExchangeRates()) {
@@ -110,6 +110,48 @@ public void onCreate(final Bundle savedInstanceState) {
config.registerOnSharedPreferenceChangeListener(this);
viewModel.setInitialExchangeRate(config.getExchangeCurrencyCode());
+
+ activity.addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.exchange_rates_fragment_options, menu);
+ }
+
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ final MenuItem searchMenuItem = menu.findItem(R.id.exchange_rates_options_search);
+ if (config.isEnableExchangeRates()) {
+ final SearchView searchView = (SearchView) searchMenuItem.getActionView();
+ searchView.setOnQueryTextListener(new OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextChange(final String newText) {
+ viewModel.setConstraint(Strings.emptyToNull(newText.trim()));
+ maybeSubmitList();
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(final String query) {
+ searchView.clearFocus();
+ return true;
+ }
+ });
+
+ // Workaround for not being able to style the SearchView
+ final int id = getResources().getIdentifier("android:id/search_src_text", null, null);
+ final EditText searchInput = searchView.findViewById(id);
+ searchInput.setTextColor(activity.getColor(R.color.fg_on_dark_bg_network_significant));
+ searchInput.setHintTextColor(activity.getColor(R.color.fg_on_dark_bg_network_insignificant));
+ } else {
+ searchMenuItem.setVisible(false);
+ }
+ }
+
+ @Override
+ public boolean onMenuItemSelected(final MenuItem menuItem) {
+ return false;
+ }
+ });
}
@Override
@@ -157,41 +199,6 @@ public boolean onClickBlockContextMenuItem(final MenuItem item, final String exc
}
}
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.exchange_rates_fragment_options, menu);
-
- final MenuItem searchMenuItem = menu.findItem(R.id.exchange_rates_options_search);
- if (config.isEnableExchangeRates()) {
- final SearchView searchView = (SearchView) searchMenuItem.getActionView();
- searchView.setOnQueryTextListener(new OnQueryTextListener() {
- @Override
- public boolean onQueryTextChange(final String newText) {
- viewModel.setConstraint(Strings.emptyToNull(newText.trim()));
- maybeSubmitList();
- return true;
- }
-
- @Override
- public boolean onQueryTextSubmit(final String query) {
- searchView.clearFocus();
- return true;
- }
- });
-
- // Workaround for not being able to style the SearchView
- final int id = searchView.getContext().getResources().getIdentifier("android:id/search_src_text", null,
- null);
- final EditText searchInput = searchView.findViewById(id);
- searchInput.setTextColor(activity.getColor(R.color.fg_on_dark_bg_network_significant));
- searchInput.setHintTextColor(activity.getColor(R.color.fg_on_dark_bg_network_insignificant));
- } else {
- searchMenuItem.setVisible(false);
- }
-
- super.onCreateOptionsMenu(menu, inflater);
- }
-
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
if (Configuration.PREFS_KEY_EXCHANGE_CURRENCY.equals(key))
diff --git a/wallet/src/de/schildbach/wallet/ui/ReportIssueDialogFragment.java b/wallet/src/de/schildbach/wallet/ui/ReportIssueDialogFragment.java
index 9251dc7d19..7cce3983d5 100644
--- a/wallet/src/de/schildbach/wallet/ui/ReportIssueDialogFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/ReportIssueDialogFragment.java
@@ -21,7 +21,7 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
-import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
@@ -29,6 +29,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.PowerManager;
import android.widget.Button;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
@@ -45,7 +46,6 @@
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
-import org.bitcoinj.core.Utils;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.wallet.Wallet;
import org.slf4j.Logger;
@@ -53,10 +53,10 @@
import java.io.File;
import java.io.IOException;
-import java.util.Calendar;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Formatter;
-import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -89,6 +89,7 @@ public static void show(final FragmentManager fm, final int titleResId, final in
private AbstractWalletActivity activity;
private WalletApplication application;
+ private PowerManager powerManager;
private Button positiveButton;
@@ -101,6 +102,7 @@ public void onAttach(final Context context) {
super.onAttach(context);
this.activity = (AbstractWalletActivity) context;
this.application = activity.getWalletApplication();
+ this.powerManager = activity.getSystemService(PowerManager.class);
}
@Override
@@ -213,7 +215,6 @@ private void appendApplicationInfo(final Appendable report, final WalletApplicat
throws IOException {
final PackageInfo pi = application.packageInfo();
final Configuration config = application.getConfiguration();
- final Calendar calendar = new GregorianCalendar(UTC);
report.append("Version: ").append(pi.versionName).append(" (").append(String.valueOf(pi.versionCode)).append(
")\n");
@@ -227,35 +228,26 @@ private void appendApplicationInfo(final Appendable report, final WalletApplicat
report.append("Installer: ").append(installer.displayName).append(" (").append(installerPackageName).append(")\n");
else
report.append("Installer: unknown\n");
+ final boolean isIgnoringBatteryOptimization =
+ powerManager.isIgnoringBatteryOptimizations(application.getPackageName());
+ report.append("Battery optimization: ").append(isIgnoringBatteryOptimization ? "no" : "yes").append("\n");
report.append("Timezone: ").append(TimeZone.getDefault().getID()).append("\n");
- calendar.setTimeInMillis(System.currentTimeMillis());
- report.append("Current time: ").append(String.format(Locale.US, "%tF %tT %tZ", calendar, calendar, calendar)).append("\n");
- calendar.setTimeInMillis(WalletApplication.TIME_CREATE_APPLICATION);
- report.append("Time of app launch: ").append(String.format(Locale.US, "%tF %tT %tZ", calendar, calendar,
- calendar)).append("\n");
- calendar.setTimeInMillis(pi.firstInstallTime);
- report.append("Time of first app install: ").append(String.format(Locale.US, "%tF %tT %tZ", calendar,
- calendar, calendar)).append("\n");
- calendar.setTimeInMillis(pi.lastUpdateTime);
- report.append("Time of last app update: ").append(String.format(Locale.US, "%tF %tT %tZ", calendar, calendar,
- calendar)).append("\n");
+ report.append("Current time: ").append(DateTimeFormatter.ISO_INSTANT.format(Instant.now())).append("\n");
+ report.append("Time of app launch: ").append(DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(WalletApplication.TIME_CREATE_APPLICATION))).append("\n");
+ report.append("Time of first app install: ").append(DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(pi.firstInstallTime))).append("\n");
+ report.append("Time of last app update: ").append(DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(pi.lastUpdateTime))).append("\n");
final long lastBackupTime = config.getLastBackupTime();
- calendar.setTimeInMillis(lastBackupTime);
- report.append("Time of last backup: ").append(lastBackupTime > 0 ? String.format(Locale.US, "%tF %tT %tZ",
- calendar, calendar, calendar) : "none").append("\n");
+ report.append("Time of last backup: ").append(lastBackupTime > 0 ?
+ DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(lastBackupTime)) : "none").append("\n");
final long lastRestoreTime = config.getLastRestoreTime();
- calendar.setTimeInMillis(lastRestoreTime);
- report.append("Time of last restore: ").append(lastRestoreTime > 0 ? String.format(Locale.US, "%tF %tT %tZ",
- calendar, calendar, calendar) : "none").append("\n");
+ report.append("Time of last restore: ").append(lastRestoreTime > 0 ?
+ DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(lastRestoreTime)) : "none").append("\n");
final long lastEncryptKeysTime = config.getLastEncryptKeysTime();
- calendar.setTimeInMillis(lastEncryptKeysTime);
- report.append("Time of last encrypt keys: ").append(lastEncryptKeysTime > 0 ? String.format(Locale.US, "%tF " +
- "%tT %tZ", calendar, calendar, calendar) :
- "none").append("\n");
+ report.append("Time of last encrypt keys: ").append(lastEncryptKeysTime > 0 ?
+ DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(lastEncryptKeysTime)) : "none").append("\n");
final long lastBlockchainResetTime = config.getLastBlockchainResetTime();
- calendar.setTimeInMillis(lastBlockchainResetTime);
- report.append("Time of last blockchain reset: ").append(lastBlockchainResetTime > 0
- ? String.format(Locale.US, "%tF %tT %tZ", calendar, calendar, calendar) : "none").append("\n");
+ report.append("Time of last blockchain reset: ").append(lastBlockchainResetTime > 0 ?
+ DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(lastBlockchainResetTime)) : "none").append("\n");
report.append("Network: ").append(Constants.NETWORK_PARAMETERS.getId()).append("\n");
report.append("Sync mode: ").append(config.getSyncMode().name()).append("\n");
final Wallet wallet = walletActivityViewModel.wallet.getValue();
@@ -281,7 +273,7 @@ private void appendApplicationInfo(final Appendable report, final WalletApplicat
final int lastBlockSeenHeight = wallet.getLastBlockSeenHeight();
final Date lastBlockSeenTime = wallet.getLastBlockSeenTime();
report.append("Last block seen: ").append(String.valueOf(lastBlockSeenHeight)).append(" (")
- .append(lastBlockSeenTime == null ? "time unknown" : Utils.dateTimeFormat(lastBlockSeenTime))
+ .append(lastBlockSeenTime == null ? "time unknown" : DateTimeFormatter.ISO_INSTANT.format(lastBlockSeenTime.toInstant()))
.append(")\n");
report.append("Best chain height ever: ").append(Integer.toString(config.getBestChainHeightEver()))
.append("\n");
@@ -302,11 +294,10 @@ private static void appendDir(final Appendable report, final File file, final in
for (int i = 0; i < indent; i++)
report.append(" - ");
+ final String lastModified = DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(file.lastModified()));
final Formatter formatter = new Formatter(report);
- final Calendar calendar = new GregorianCalendar(UTC);
- calendar.setTimeInMillis(file.lastModified());
- formatter.format(Locale.US, "%tF %tT %8d kB %s\n",
- calendar, calendar, file.length() / 1024, file.getName());
+ formatter.format(Locale.US, "%s %8d kB %s\n",
+ lastModified, file.length() / 1024, file.getName());
formatter.close();
final File[] files = file.listFiles();
@@ -318,9 +309,8 @@ private static void appendDir(final Appendable report, final File file, final in
private static void appendDeviceInfo(final Appendable report, final Context context) throws IOException {
final Resources res = context.getResources();
final android.content.res.Configuration config = res.getConfiguration();
- final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- final DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context
- .getSystemService(Context.DEVICE_POLICY_SERVICE);
+ final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+ final DevicePolicyManager devicePolicyManager = context.getSystemService(DevicePolicyManager.class);
report.append("Manufacturer: ").append(Build.MANUFACTURER).append("\n");
report.append("Device Model: ").append(Build.MODEL).append("\n");
@@ -343,14 +333,15 @@ private static void appendDeviceInfo(final Appendable report, final Context cont
.append(String.valueOf(activityManager.getLargeMemoryClass()))
.append(activityManager.isLowRamDevice() ? " (low RAM device)" : "").append("\n");
report.append("Storage Encryption Status: ").append(String.valueOf(devicePolicyManager.getStorageEncryptionStatus())).append("\n");
- report.append("Bluetooth MAC: ").append(bluetoothMac()).append("\n");
+ report.append("Bluetooth MAC: ").append(bluetoothMac(context)).append("\n");
report.append("Runtime: ").append(System.getProperty("java.vm.name")).append(" ")
.append(System.getProperty("java.vm.version")).append("\n");
}
- private static String bluetoothMac() {
+ private static String bluetoothMac(final Context context) {
try {
- return Bluetooth.getAddress(BluetoothAdapter.getDefaultAdapter());
+ final BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+ return Bluetooth.getAddress(bluetoothManager.getAdapter());
} catch (final Exception x) {
return x.getMessage();
}
diff --git a/wallet/src/de/schildbach/wallet/ui/RequestCoinsActivity.java b/wallet/src/de/schildbach/wallet/ui/RequestCoinsActivity.java
index 94b7591150..0c5a472926 100644
--- a/wallet/src/de/schildbach/wallet/ui/RequestCoinsActivity.java
+++ b/wallet/src/de/schildbach/wallet/ui/RequestCoinsActivity.java
@@ -21,8 +21,10 @@
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.Nullable;
+import androidx.core.view.MenuProvider;
import androidx.lifecycle.ViewModelProvider;
import de.schildbach.wallet.R;
import org.bitcoinj.script.Script;
@@ -63,26 +65,30 @@ protected void onEvent(final Integer messageResId) {
HelpDialogFragment.page(getSupportFragmentManager(), messageResId);
}
});
- }
- @Override
- public void onAttachedToWindow() {
- setShowWhenLocked(true);
- }
+ addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.request_coins_activity_options, menu);
+ }
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- getMenuInflater().inflate(R.menu.request_coins_activity_options, menu);
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ }
- return super.onCreateOptionsMenu(menu);
+ @Override
+ public boolean onMenuItemSelected(final MenuItem item) {
+ if (item.getItemId() == R.id.request_coins_options_help) {
+ viewModel.showHelpDialog.setValue(new Event<>(R.string.help_request_coins));
+ return true;
+ }
+ return false;
+ }
+ });
}
@Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- if (item.getItemId() == R.id.request_coins_options_help) {
- viewModel.showHelpDialog.setValue(new Event<>(R.string.help_request_coins));
- return true;
- }
- return super.onOptionsItemSelected(item);
+ public void onAttachedToWindow() {
+ setShowWhenLocked(true);
}
}
diff --git a/wallet/src/de/schildbach/wallet/ui/RequestCoinsFragment.java b/wallet/src/de/schildbach/wallet/ui/RequestCoinsFragment.java
index 0e82718a0a..a8623300b9 100644
--- a/wallet/src/de/schildbach/wallet/ui/RequestCoinsFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/RequestCoinsFragment.java
@@ -17,8 +17,9 @@
package de.schildbach.wallet.ui;
-import android.app.Activity;
+import android.Manifest;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
@@ -33,6 +34,7 @@
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
+import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
@@ -44,10 +46,13 @@
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.core.app.ShareCompat;
import androidx.core.content.ContextCompat;
+import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
@@ -86,21 +91,35 @@ public final class RequestCoinsFragment extends Fragment {
private TextView initiateRequestView;
private CurrencyCalculatorLink amountCalculatorLink;
- private static final int REQUEST_CODE_ENABLE_BLUETOOTH = 0;
private static final String KEY_RECEIVE_ADDRESS = "receive_address";
private RequestCoinsViewModel viewModel;
private static final Logger log = LoggerFactory.getLogger(RequestCoinsFragment.class);
+ private final ActivityResultLauncher requestPermissionLauncher =
+ registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> {
+ if (granted)
+ maybeStartBluetoothListening();
+ else
+ acceptBluetoothPaymentView.setChecked(false);
+ });
+ private final ActivityResultLauncher requestEnableBluetoothLauncher =
+ registerForActivityResult(new RequestEnableBluetooth(), enabled -> {
+ boolean started = false;
+ if (enabled && bluetoothAdapter != null)
+ started = maybeStartBluetoothListening();
+ acceptBluetoothPaymentView.setChecked(started);
+ });
+
@Override
public void onAttach(final Context context) {
super.onAttach(context);
this.activity = (AbstractWalletActivity) context;
this.application = activity.getWalletApplication();
this.config = application.getConfiguration();
- this.clipboardManager = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
- this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ this.clipboardManager = activity.getSystemService(ClipboardManager.class);
+ this.bluetoothAdapter = activity.getSystemService(BluetoothManager.class).getAdapter();
this.nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
}
@@ -109,8 +128,6 @@ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.fragmentManager = getChildFragmentManager();
- setHasOptionsMenu(true);
-
viewModel = new ViewModelProvider(this).get(RequestCoinsViewModel.class);
final Intent intent = activity.getIntent();
if (intent.hasExtra(RequestCoinsActivity.INTENT_EXTRA_OUTPUT_SCRIPT_TYPE))
@@ -146,6 +163,37 @@ protected void onEvent(final Bitmap bitmap) {
}
});
+ activity.addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.request_coins_fragment_options, menu);
+ }
+
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ final boolean hasBitcoinUri = viewModel.bitcoinUri.getValue() != null;
+ menu.findItem(R.id.request_coins_options_copy).setEnabled(hasBitcoinUri);
+ menu.findItem(R.id.request_coins_options_share).setEnabled(hasBitcoinUri);
+ menu.findItem(R.id.request_coins_options_local_app).setEnabled(hasBitcoinUri);
+ }
+
+ @Override
+ public boolean onMenuItemSelected(final MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == R.id.request_coins_options_copy) {
+ handleCopy();
+ return true;
+ } else if (itemId == R.id.request_coins_options_share) {
+ handleShare();
+ return true;
+ } else if (itemId == R.id.request_coins_options_local_app) {
+ handleLocalApp();
+ return true;
+ }
+ return false;
+ }
+ });
+
if (savedInstanceState != null) {
restoreInstanceState(savedInstanceState);
}
@@ -181,19 +229,12 @@ public View onCreateView(final LayoutInflater inflater, final ViewGroup containe
bluetoothAdapter != null &&
(Bluetooth.getAddress(bluetoothAdapter) != null || config.getLastBluetoothAddress() != null || config.getBluetoothAddress() != null) ?
View.VISIBLE : View.GONE);
- acceptBluetoothPaymentView.setChecked(bluetoothAdapter != null && bluetoothAdapter.isEnabled());
+ acceptBluetoothPaymentView.setChecked(bluetoothAdapter != null && bluetoothAdapter.isEnabled() && checkBluetoothConnectPermission());
acceptBluetoothPaymentView.setOnCheckedChangeListener((buttonView, isChecked) -> {
- if (bluetoothAdapter != null && isChecked) {
- if (bluetoothAdapter.isEnabled()) {
- maybeStartBluetoothListening();
- } else {
- // ask for permission to enable bluetooth
- startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE),
- REQUEST_CODE_ENABLE_BLUETOOTH);
- }
- } else {
+ if (bluetoothAdapter != null && isChecked)
+ maybeStartBluetoothListening();
+ else
stopBluetoothListening();
- }
});
initiateRequestView = view.findViewById(R.id.request_coins_fragment_initiate_request);
@@ -228,7 +269,7 @@ public void focusChanged(final boolean hasFocus) {
});
final BluetoothAdapter bluetoothAdapter = this.bluetoothAdapter;
- if (bluetoothAdapter != null && bluetoothAdapter.isEnabled() && acceptBluetoothPaymentView.isChecked())
+ if (bluetoothAdapter != null && bluetoothAdapter.isEnabled() && checkBluetoothConnectPermission() && acceptBluetoothPaymentView.isChecked())
maybeStartBluetoothListening();
}
@@ -264,30 +305,35 @@ private void restoreInstanceState(final Bundle savedInstanceState) {
savedInstanceState.getString(KEY_RECEIVE_ADDRESS)));
}
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- if (requestCode == REQUEST_CODE_ENABLE_BLUETOOTH) {
- boolean started = false;
- if (resultCode == Activity.RESULT_OK && bluetoothAdapter != null)
- started = maybeStartBluetoothListening();
- acceptBluetoothPaymentView.setChecked(started);
- }
+ private boolean checkBluetoothConnectPermission() {
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ContextCompat.checkSelfPermission(activity,
+ Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
}
private boolean maybeStartBluetoothListening() {
- String bluetoothAddress = Bluetooth.getAddress(bluetoothAdapter);
- if (bluetoothAddress == null)
- bluetoothAddress = config.getLastBluetoothAddress();
- if (bluetoothAddress == null)
- bluetoothAddress = config.getBluetoothAddress();
- if (bluetoothAddress != null && acceptBluetoothPaymentView.isChecked()) {
- viewModel.bluetoothServiceIntent = new Intent(activity, AcceptBluetoothService.class);
- ContextCompat.startForegroundService(activity, viewModel.bluetoothServiceIntent);
- viewModel.bluetoothMac.setValue(Bluetooth.compressMac(bluetoothAddress));
- return true;
- } else {
- return false;
+ if (!checkBluetoothConnectPermission()) {
+ log.info("missing {}, requesting", Manifest.permission.BLUETOOTH_CONNECT);
+ requestPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT);
+ } else if (!bluetoothAdapter.isEnabled()) {
+ log.info("bluetooth disabled, requesting to enable");
+ requestEnableBluetoothLauncher.launch(null);
+ } else if (acceptBluetoothPaymentView.isChecked()) {
+ String bluetoothAddress = Bluetooth.getAddress(bluetoothAdapter);
+ if (bluetoothAddress == null)
+ bluetoothAddress = config.getLastBluetoothAddress();
+ if (bluetoothAddress == null)
+ bluetoothAddress = config.getBluetoothAddress();
+ if (bluetoothAddress != null) {
+ log.info("starting bluetooth service");
+ viewModel.bluetoothServiceIntent = new Intent(activity, AcceptBluetoothService.class);
+ ContextCompat.startForegroundService(activity, viewModel.bluetoothServiceIntent);
+ viewModel.bluetoothMac.setValue(Bluetooth.compressMac(bluetoothAddress));
+ return true;
+ } else {
+ log.info("no bluetooth mac, not starting service");
+ }
}
+ return false;
}
private void stopBluetoothListening() {
@@ -298,40 +344,9 @@ private void stopBluetoothListening() {
viewModel.bluetoothMac.setValue(null);
}
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.request_coins_fragment_options, menu);
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- @Override
- public void onPrepareOptionsMenu(final Menu menu) {
- final boolean hasBitcoinUri = viewModel.bitcoinUri.getValue() != null;
- menu.findItem(R.id.request_coins_options_copy).setEnabled(hasBitcoinUri);
- menu.findItem(R.id.request_coins_options_share).setEnabled(hasBitcoinUri);
- menu.findItem(R.id.request_coins_options_local_app).setEnabled(hasBitcoinUri);
- super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- int itemId = item.getItemId();
- if (itemId == R.id.request_coins_options_copy) {
- handleCopy();
- return true;
- } else if (itemId == R.id.request_coins_options_share) {
- handleShare();
- return true;
- } else if (itemId == R.id.request_coins_options_local_app) {
- handleLocalApp();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
private void handleCopy() {
final Uri request = viewModel.bitcoinUri.getValue();
- clipboardManager.setPrimaryClip(ClipData.newRawUri("Bitcoin payment request", request));
+ clipboardManager.setPrimaryClip(ClipData.newRawUri("Groestlcoin payment request", request));
log.info("payment request copied to clipboard: {}", request);
new Toast(activity).toast(R.string.request_coins_clipboard_msg);
}
diff --git a/wallet/src/de/schildbach/wallet/ui/RequestEnableBluetooth.java b/wallet/src/de/schildbach/wallet/ui/RequestEnableBluetooth.java
new file mode 100644
index 0000000000..1fafffb2dd
--- /dev/null
+++ b/wallet/src/de/schildbach/wallet/ui/RequestEnableBluetooth.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright the original author or authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package de.schildbach.wallet.ui;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import androidx.activity.result.contract.ActivityResultContract;
+
+public class RequestEnableBluetooth extends ActivityResultContract {
+ @Override
+ public Intent createIntent(final Context context, Void unused) {
+ return new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ }
+
+ @Override
+ public Boolean parseResult(final int resultCode, final Intent intent) {
+ return resultCode == Activity.RESULT_OK;
+ }
+}
diff --git a/wallet/src/de/schildbach/wallet/ui/SendCoinsQrActivity.java b/wallet/src/de/schildbach/wallet/ui/SendCoinsQrActivity.java
index 9d6ba43d37..368f0b9f2f 100644
--- a/wallet/src/de/schildbach/wallet/ui/SendCoinsQrActivity.java
+++ b/wallet/src/de/schildbach/wallet/ui/SendCoinsQrActivity.java
@@ -17,10 +17,9 @@
package de.schildbach.wallet.ui;
-import android.app.Activity;
import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
import android.os.Bundle;
+import androidx.activity.result.ActivityResultLauncher;
import androidx.lifecycle.ViewModelProvider;
import de.schildbach.wallet.Constants;
import de.schildbach.wallet.data.PaymentIntent;
@@ -38,58 +37,55 @@
public final class SendCoinsQrActivity extends AbstractWalletActivity {
private AbstractWalletActivityViewModel walletActivityViewModel;
- private static final int REQUEST_CODE_SCAN = 0;
+ private final ActivityResultLauncher scanLauncher =
+ registerForActivityResult(new ScanActivity.Scan(), input -> {
+ if (input != null) {
+ new StringInputParser(input) {
+ @Override
+ protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
+ SendCoinsActivity.start(SendCoinsQrActivity.this, paymentIntent);
+ SendCoinsQrActivity.this.finish();
+ }
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- walletActivityViewModel = new ViewModelProvider(this).get(AbstractWalletActivityViewModel.class);
-
- if (savedInstanceState == null)
- ScanActivity.startForResult(this, REQUEST_CODE_SCAN);
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
- if (requestCode == REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) {
- final String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
+ @Override
+ protected void handlePrivateKey(final PrefixedChecksummedBytes key) {
+ if (Constants.ENABLE_SWEEP_WALLET) {
+ SweepWalletActivity.start(SendCoinsQrActivity.this, key);
+ SendCoinsQrActivity.this.finish();
+ } else {
+ super.handlePrivateKey(key);
+ }
+ }
- new StringInputParser(input) {
- @Override
- protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
- SendCoinsActivity.start(SendCoinsQrActivity.this, paymentIntent);
+ @Override
+ protected void handleDirectTransaction(final Transaction transaction) throws VerificationException {
+ walletActivityViewModel.broadcastTransaction(transaction);
+ SendCoinsQrActivity.this.finish();
+ }
- SendCoinsQrActivity.this.finish();
- }
+ @Override
+ protected void error(final int messageResId, final Object... messageArgs) {
+ final DialogBuilder dialog = DialogBuilder.dialog(SendCoinsQrActivity.this, 0,
+ messageResId, messageArgs);
+ dialog.singleDismissButton(dismissListener);
+ dialog.show();
+ }
- @Override
- protected void handlePrivateKey(final PrefixedChecksummedBytes key) {
- if (Constants.ENABLE_SWEEP_WALLET) {
- SweepWalletActivity.start(SendCoinsQrActivity.this, key);
- SendCoinsQrActivity.this.finish();
- } else {
- super.handlePrivateKey(key);
- }
+ private final OnClickListener dismissListener =
+ (dialog, which) -> SendCoinsQrActivity.this.finish();
+ }.parse();
+ } else {
+ finish();
}
+ });
- @Override
- protected void handleDirectTransaction(final Transaction transaction) throws VerificationException {
- walletActivityViewModel.broadcastTransaction(transaction);
- SendCoinsQrActivity.this.finish();
- }
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
- @Override
- protected void error(final int messageResId, final Object... messageArgs) {
- final DialogBuilder dialog = DialogBuilder.dialog(SendCoinsQrActivity.this, 0, messageResId, messageArgs);
- dialog.singleDismissButton(dismissListener);
- dialog.show();
- }
+ walletActivityViewModel = new ViewModelProvider(this).get(AbstractWalletActivityViewModel.class);
- private final OnClickListener dismissListener = (dialog, which) -> SendCoinsQrActivity.this.finish();
- }.parse();
- } else {
- finish();
- }
+ if (savedInstanceState == null)
+ scanLauncher.launch(null);
}
}
diff --git a/wallet/src/de/schildbach/wallet/ui/SendingAddressesFragment.java b/wallet/src/de/schildbach/wallet/ui/SendingAddressesFragment.java
index b78d6ef0cc..ea10cd601d 100644
--- a/wallet/src/de/schildbach/wallet/ui/SendingAddressesFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/SendingAddressesFragment.java
@@ -73,7 +73,7 @@ public void onAttach(final Context context) {
super.onAttach(context);
this.activity = (AbstractWalletActivity) context;
this.addressBookDao = AddressBookDatabase.getDatabase(context).addressBookDao();
- this.clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+ this.clipboardManager = context.getSystemService(ClipboardManager.class);
}
@Override
diff --git a/wallet/src/de/schildbach/wallet/ui/TransactionsAdapter.java b/wallet/src/de/schildbach/wallet/ui/TransactionsAdapter.java
index bfa012837f..3ca144a78f 100644
--- a/wallet/src/de/schildbach/wallet/ui/TransactionsAdapter.java
+++ b/wallet/src/de/schildbach/wallet/ui/TransactionsAdapter.java
@@ -327,7 +327,7 @@ public TransactionItem(final Context context, final Transaction tx, final @Nulla
this.message = SpannedString
.valueOf(context.getString(R.string.transaction_row_message_received_direct));
this.messageColor = colorInsignificant;
- } else if (!sent && value.compareTo(Transaction.MIN_NONDUST_OUTPUT) < 0) {
+ } else if (!sent && value.compareTo(Constants.MIN_NONDUST) < 0) {
this.message = SpannedString
.valueOf(context.getString(R.string.transaction_row_message_received_dust));
this.messageColor = colorInsignificant;
diff --git a/wallet/src/de/schildbach/wallet/ui/WalletActionsFragment.java b/wallet/src/de/schildbach/wallet/ui/WalletActionsFragment.java
index d972959487..68d102a3a6 100644
--- a/wallet/src/de/schildbach/wallet/ui/WalletActionsFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/WalletActionsFragment.java
@@ -26,7 +26,6 @@
import android.widget.FrameLayout;
import androidx.fragment.app.Fragment;
import de.schildbach.wallet.R;
-import de.schildbach.wallet.util.CheatSheet;
/**
* @author Andreas Schildbach
@@ -53,7 +52,7 @@ public View onCreateView(final LayoutInflater inflater, final ViewGroup containe
final View sendQrButton = view.findViewById(R.id.wallet_actions_send_qr);
sendQrButton.setOnClickListener(v -> activity.handleScan(v));
- CheatSheet.setup(sendQrButton);
+ sendQrButton.setTooltipText(sendQrButton.getContentDescription());
return view;
}
diff --git a/wallet/src/de/schildbach/wallet/ui/WalletActivity.java b/wallet/src/de/schildbach/wallet/ui/WalletActivity.java
index 49dd5750c3..b5aafc30ef 100644
--- a/wallet/src/de/schildbach/wallet/ui/WalletActivity.java
+++ b/wallet/src/de/schildbach/wallet/ui/WalletActivity.java
@@ -23,7 +23,6 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
-import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -34,10 +33,14 @@
import android.os.Environment;
import android.os.Handler;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
+import androidx.activity.result.ActivityResultLauncher;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.core.app.ActivityOptionsCompat;
+import androidx.core.view.MenuProvider;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
@@ -82,7 +85,37 @@ public final class WalletActivity extends AbstractWalletActivity {
private AbstractWalletActivityViewModel walletActivityViewModel;
private WalletActivityViewModel viewModel;
- private static final int REQUEST_CODE_SCAN = 0;
+ private final ActivityResultLauncher scanLauncher =
+ registerForActivityResult(new ScanActivity.Scan(), input -> {
+ if (input == null) return;
+ new StringInputParser(input) {
+ @Override
+ protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
+ SendCoinsActivity.start(WalletActivity.this, paymentIntent);
+ }
+
+ @Override
+ protected void handlePrivateKey(final PrefixedChecksummedBytes key) {
+ if (Constants.ENABLE_SWEEP_WALLET)
+ SweepWalletActivity.start(WalletActivity.this, key);
+ else
+ super.handlePrivateKey(key);
+ }
+
+ @Override
+ protected void handleDirectTransaction(final Transaction tx) throws VerificationException {
+ walletActivityViewModel.broadcastTransaction(tx);
+ }
+
+ @Override
+ protected void error(final int messageResId, final Object... messageArgs) {
+ final DialogBuilder dialog = DialogBuilder.dialog(WalletActivity.this, R.string.button_scan,
+ messageResId, messageArgs);
+ dialog.singleDismissButton(null);
+ dialog.show();
+ }
+ }.parse();
+ });
@Override
protected void onCreate(final Bundle savedInstanceState) {
@@ -188,6 +221,93 @@ public void onAnimationEnd(final Animator animation) {
handleIntent(getIntent());
+ addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.wallet_options, menu);
+ }
+
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ final Resources res = getResources();
+ final boolean showExchangeRatesOption = config.isEnableExchangeRates()
+ && res.getBoolean(R.bool.show_exchange_rates_option);
+ menu.findItem(R.id.wallet_options_exchange_rates).setVisible(showExchangeRatesOption);
+ menu.findItem(R.id.wallet_options_sweep_wallet).setVisible(Constants.ENABLE_SWEEP_WALLET);
+ final String externalStorageState = Environment.getExternalStorageState();
+ final boolean enableRestoreWalletOption = Environment.MEDIA_MOUNTED.equals(externalStorageState)
+ || Environment.MEDIA_MOUNTED_READ_ONLY.equals(externalStorageState);
+ menu.findItem(R.id.wallet_options_restore_wallet).setEnabled(enableRestoreWalletOption);
+ final Boolean isEncrypted = viewModel.walletEncrypted.getValue();
+ if (isEncrypted != null) {
+ final MenuItem encryptKeysOption = menu.findItem(R.id.wallet_options_encrypt_keys);
+ encryptKeysOption.setTitle(isEncrypted ? R.string.wallet_options_encrypt_keys_change
+ : R.string.wallet_options_encrypt_keys_set);
+ encryptKeysOption.setVisible(true);
+ }
+ final Boolean isLegacyFallback = viewModel.walletLegacyFallback.getValue();
+ if (isLegacyFallback != null) {
+ final MenuItem requestLegacyOption = menu.findItem(R.id.wallet_options_request_legacy);
+ requestLegacyOption.setVisible(isLegacyFallback);
+ }
+ }
+
+ @Override
+ public boolean onMenuItemSelected(final MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == R.id.wallet_options_request) {
+ handleRequestCoins();
+ return true;
+ } else if (itemId == R.id.wallet_options_request_legacy) {
+ RequestCoinsActivity.start(WalletActivity.this, Script.ScriptType.P2PKH);
+ return true;
+ } else if (itemId == R.id.wallet_options_send) {
+ handleSendCoins();
+ return true;
+ } else if (itemId == R.id.wallet_options_scan) {
+ handleScan(null);
+ return true;
+ } else if (itemId == R.id.wallet_options_address_book) {
+ AddressBookActivity.start(WalletActivity.this);
+ return true;
+ } else if (itemId == R.id.wallet_options_exchange_rates) {
+ startActivity(new Intent(WalletActivity.this, ExchangeRatesActivity.class));
+ return true;
+ } else if (itemId == R.id.wallet_options_sweep_wallet) {
+ SweepWalletActivity.start(WalletActivity.this);
+ return true;
+ } else if (itemId == R.id.wallet_options_network_monitor) {
+ startActivity(new Intent(WalletActivity.this, NetworkMonitorActivity.class));
+ return true;
+ } else if (itemId == R.id.wallet_options_restore_wallet) {
+ viewModel.showRestoreWalletDialog.setValue(Event.simple());
+ return true;
+ } else if (itemId == R.id.wallet_options_backup_wallet) {
+ viewModel.showBackupWalletDialog.setValue(Event.simple());
+ return true;
+ } else if (itemId == R.id.wallet_options_encrypt_keys) {
+ viewModel.showEncryptKeysDialog.setValue(Event.simple());
+ return true;
+ } else if (itemId == R.id.wallet_options_preferences) {
+ startActivity(new Intent(WalletActivity.this, PreferenceActivity.class));
+ return true;
+ } else if (itemId == R.id.wallet_options_safety) {
+ viewModel.showHelpDialog.setValue(new Event<>(R.string.help_safety));
+ return true;
+ } else if (itemId == R.id.wallet_options_technical_notes) {
+ viewModel.showHelpDialog.setValue(new Event<>(R.string.help_technical_notes));
+ return true;
+ } else if (itemId == R.id.wallet_options_report_issue) {
+ viewModel.showReportIssueDialog.setValue(Event.simple());
+ return true;
+ } else if (itemId == R.id.wallet_options_help) {
+ viewModel.showHelpDialog.setValue(new Event<>(R.string.help_wallet));
+ return true;
+ }
+ return false;
+ }
+ });
+
final FragmentManager fragmentManager = getSupportFragmentManager();
MaybeMaintenanceFragment.add(fragmentManager);
AlertDialogsFragment.add(fragmentManager);
@@ -216,13 +336,10 @@ protected void onPause() {
private AnimatorSet buildEnterAnimation(final View contentView) {
final Drawable background = getWindow().getDecorView().getBackground();
final int duration = getResources().getInteger(android.R.integer.config_mediumAnimTime);
- final Animator splashBackgroundFadeOut = AnimatorInflater.loadAnimator(WalletActivity.this, R.animator.fade_out_drawable);
- final Animator splashForegroundFadeOut = AnimatorInflater.loadAnimator(WalletActivity.this, R.animator.fade_out_drawable);
- splashBackgroundFadeOut.setTarget(((LayerDrawable) background).getDrawable(1));
- splashForegroundFadeOut.setTarget(((LayerDrawable) background).getDrawable(2));
+ final Animator splashFadeOut = AnimatorInflater.loadAnimator(WalletActivity.this, R.animator.fade_out_drawable);
+ splashFadeOut.setTarget(((LayerDrawable) background).getDrawable(1));
final AnimatorSet fragmentEnterAnimation = new AnimatorSet();
- final AnimatorSet.Builder fragmentEnterAnimationBuilder =
- fragmentEnterAnimation.play(splashBackgroundFadeOut).with(splashForegroundFadeOut);
+ final AnimatorSet.Builder fragmentEnterAnimationBuilder = fragmentEnterAnimation.play(splashFadeOut);
final View slideInLeftView = contentView.findViewWithTag("slide_in_left");
if (slideInLeftView != null) {
@@ -333,138 +450,6 @@ protected void error(final int messageResId, final Object... messageArgs) {
}
}
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
- if (requestCode == REQUEST_CODE_SCAN) {
- if (resultCode == Activity.RESULT_OK) {
- final String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
-
- new StringInputParser(input) {
- @Override
- protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
- SendCoinsActivity.start(WalletActivity.this, paymentIntent);
- }
-
- @Override
- protected void handlePrivateKey(final PrefixedChecksummedBytes key) {
- if (Constants.ENABLE_SWEEP_WALLET)
- SweepWalletActivity.start(WalletActivity.this, key);
- else
- super.handlePrivateKey(key);
- }
-
- @Override
- protected void handleDirectTransaction(final Transaction tx) throws VerificationException {
- walletActivityViewModel.broadcastTransaction(tx);
- }
-
- @Override
- protected void error(final int messageResId, final Object... messageArgs) {
- final DialogBuilder dialog = DialogBuilder.dialog(WalletActivity.this, R.string.button_scan, messageResId, messageArgs);
- dialog.singleDismissButton(null);
- dialog.show();
- }
- }.parse();
- }
- } else {
- super.onActivityResult(requestCode, resultCode, intent);
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- super.onCreateOptionsMenu(menu);
-
- getMenuInflater().inflate(R.menu.wallet_options, menu);
-
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(final Menu menu) {
- super.onPrepareOptionsMenu(menu);
-
- final Resources res = getResources();
-
- final boolean showExchangeRatesOption = config.isEnableExchangeRates()
- && res.getBoolean(R.bool.show_exchange_rates_option);
- menu.findItem(R.id.wallet_options_exchange_rates).setVisible(showExchangeRatesOption);
- menu.findItem(R.id.wallet_options_sweep_wallet).setVisible(Constants.ENABLE_SWEEP_WALLET);
- final String externalStorageState = Environment.getExternalStorageState();
- final boolean enableRestoreWalletOption = Environment.MEDIA_MOUNTED.equals(externalStorageState)
- || Environment.MEDIA_MOUNTED_READ_ONLY.equals(externalStorageState);
- menu.findItem(R.id.wallet_options_restore_wallet).setEnabled(enableRestoreWalletOption);
- final Boolean isEncrypted = viewModel.walletEncrypted.getValue();
- if (isEncrypted != null) {
- final MenuItem encryptKeysOption = menu.findItem(R.id.wallet_options_encrypt_keys);
- encryptKeysOption.setTitle(isEncrypted ? R.string.wallet_options_encrypt_keys_change
- : R.string.wallet_options_encrypt_keys_set);
- encryptKeysOption.setVisible(true);
- }
- final Boolean isLegacyFallback = viewModel.walletLegacyFallback.getValue();
- if (isLegacyFallback != null) {
- final MenuItem requestLegacyOption = menu.findItem(R.id.wallet_options_request_legacy);
- requestLegacyOption.setVisible(isLegacyFallback);
- }
-
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- int itemId = item.getItemId();
- if (itemId == R.id.wallet_options_request) {
- handleRequestCoins();
- return true;
- } else if (itemId == R.id.wallet_options_request_legacy) {
- RequestCoinsActivity.start(this, Script.ScriptType.P2PKH);
- return true;
- } else if (itemId == R.id.wallet_options_send) {
- handleSendCoins();
- return true;
- } else if (itemId == R.id.wallet_options_scan) {
- handleScan(null);
- return true;
- } else if (itemId == R.id.wallet_options_address_book) {
- AddressBookActivity.start(this);
- return true;
- } else if (itemId == R.id.wallet_options_exchange_rates) {
- startActivity(new Intent(this, ExchangeRatesActivity.class));
- return true;
- } else if (itemId == R.id.wallet_options_sweep_wallet) {
- SweepWalletActivity.start(this);
- return true;
- } else if (itemId == R.id.wallet_options_network_monitor) {
- startActivity(new Intent(this, NetworkMonitorActivity.class));
- return true;
- } else if (itemId == R.id.wallet_options_restore_wallet) {
- viewModel.showRestoreWalletDialog.setValue(Event.simple());
- return true;
- } else if (itemId == R.id.wallet_options_backup_wallet) {
- viewModel.showBackupWalletDialog.setValue(Event.simple());
- return true;
- } else if (itemId == R.id.wallet_options_encrypt_keys) {
- viewModel.showEncryptKeysDialog.setValue(Event.simple());
- return true;
- } else if (itemId == R.id.wallet_options_preferences) {
- startActivity(new Intent(this, PreferenceActivity.class));
- return true;
- } else if (itemId == R.id.wallet_options_safety) {
- viewModel.showHelpDialog.setValue(new Event<>(R.string.help_safety));
- return true;
- } else if (itemId == R.id.wallet_options_technical_notes) {
- viewModel.showHelpDialog.setValue(new Event<>(R.string.help_technical_notes));
- return true;
- } else if (itemId == R.id.wallet_options_report_issue) {
- viewModel.showReportIssueDialog.setValue(Event.simple());
- return true;
- } else if (itemId == R.id.wallet_options_help) {
- viewModel.showHelpDialog.setValue(new Event<>(R.string.help_wallet));
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
public void handleRequestCoins() {
RequestCoinsActivity.start(this);
}
@@ -477,7 +462,13 @@ public void handleScan(final View clickView) {
// The animation must be ended because of several graphical glitching that happens when the
// Camera/SurfaceView is used while the animation is running.
enterAnimation.end();
- ScanActivity.startForResult(this, clickView, WalletActivity.REQUEST_CODE_SCAN);
+ if (clickView != null) {
+ final ActivityOptionsCompat options = ActivityOptionsCompat.makeClipRevealAnimation(clickView, 0, 0,
+ clickView.getWidth(), clickView.getHeight());
+ scanLauncher.launch(null, options);
+ } else {
+ scanLauncher.launch(null);
+ }
}
private static final class QuickReturnBehavior extends CoordinatorLayout.Behavior {
diff --git a/wallet/src/de/schildbach/wallet/ui/WalletAddressesFragment.java b/wallet/src/de/schildbach/wallet/ui/WalletAddressesFragment.java
index 98b5bf09d2..c066d17c2a 100644
--- a/wallet/src/de/schildbach/wallet/ui/WalletAddressesFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/WalletAddressesFragment.java
@@ -83,7 +83,7 @@ public void onAttach(final Context context) {
this.activity = (AbstractWalletActivity) context;
this.application = activity.getWalletApplication();
this.addressBookDao = AddressBookDatabase.getDatabase(context).addressBookDao();
- this.clipboardManager = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ this.clipboardManager = activity.getSystemService(ClipboardManager.class);
}
@Override
diff --git a/wallet/src/de/schildbach/wallet/ui/WalletBalanceFragment.java b/wallet/src/de/schildbach/wallet/ui/WalletBalanceFragment.java
index eeb34290ad..41afd5da9e 100644
--- a/wallet/src/de/schildbach/wallet/ui/WalletBalanceFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/WalletBalanceFragment.java
@@ -28,6 +28,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import de.schildbach.wallet.Configuration;
@@ -72,7 +73,6 @@ public void onAttach(final Context context) {
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
activityViewModel = new ViewModelProvider(activity).get(WalletActivityViewModel.class);
viewModel = new ViewModelProvider(this).get(WalletBalanceViewModel.class);
@@ -84,6 +84,30 @@ public void onCreate(final Bundle savedInstanceState) {
activityViewModel.balanceLoadingFinished();
});
viewModel.getExchangeRate().observe(this, exchangeRate -> updateView());
+
+ activity.addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.wallet_balance_fragment_options, menu);
+ }
+
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ final Coin balance = viewModel.getBalance().getValue();
+ final boolean hasSomeBalance = balance != null && !balance.isLessThan(Constants.SOME_BALANCE_THRESHOLD);
+ menu.findItem(R.id.wallet_balance_options_donate)
+ .setVisible(Constants.DONATION_ADDRESS != null && hasSomeBalance);
+ }
+
+ @Override
+ public boolean onMenuItemSelected(final MenuItem item) {
+ if (item.getItemId() == R.id.wallet_balance_options_donate) {
+ handleDonate();
+ return true;
+ }
+ return false;
+ }
+ });
}
@Override
@@ -116,30 +140,6 @@ public void onViewCreated(final View view, final Bundle savedInstanceState) {
viewProgress = view.findViewById(R.id.wallet_balance_progress);
}
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.wallet_balance_fragment_options, menu);
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- @Override
- public void onPrepareOptionsMenu(final Menu menu) {
- final Coin balance = viewModel.getBalance().getValue();
- final boolean hasSomeBalance = balance != null && !balance.isLessThan(Constants.SOME_BALANCE_THRESHOLD);
- menu.findItem(R.id.wallet_balance_options_donate)
- .setVisible(Constants.DONATION_ADDRESS != null && hasSomeBalance);
- super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- if (item.getItemId() == R.id.wallet_balance_options_donate) {
- handleDonate();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
private void handleDonate() {
SendCoinsActivity.startDonate(activity, null, FeeCategory.ECONOMIC, 0);
}
diff --git a/wallet/src/de/schildbach/wallet/ui/WalletTransactionsFragment.java b/wallet/src/de/schildbach/wallet/ui/WalletTransactionsFragment.java
index a1f8c554d7..742d104008 100644
--- a/wallet/src/de/schildbach/wallet/ui/WalletTransactionsFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/WalletTransactionsFragment.java
@@ -36,6 +36,7 @@
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.ViewAnimator;
+import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
@@ -90,7 +91,7 @@ public void onAttach(final Context context) {
this.application = activity.getWalletApplication();
this.config = application.getConfiguration();
this.addressBookDao = AddressBookDatabase.getDatabase(context).addressBookDao();
- this.devicePolicyManager = (DevicePolicyManager) application.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ this.devicePolicyManager = application.getSystemService(DevicePolicyManager.class);
}
@Override
@@ -98,8 +99,6 @@ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.fragmentManager = getChildFragmentManager();
- setHasOptionsMenu(true);
-
activityViewModel = new ViewModelProvider(activity).get(WalletActivityViewModel.class);
viewModel = new ViewModelProvider(this).get(WalletTransactionsViewModel.class);
@@ -162,6 +161,48 @@ protected void onEvent(final Sha256Hash transactionHash) {
});
adapter = new TransactionsAdapter(activity, this, this);
+
+ activity.addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.wallet_transactions_fragment_options, menu);
+ filterMenuItem = menu.findItem(R.id.wallet_transactions_options_filter);
+ }
+
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ final WalletTransactionsViewModel.Direction direction = viewModel.direction.getValue();
+ if (direction == null) {
+ menu.findItem(R.id.wallet_transactions_options_filter_all).setChecked(true);
+ filterMenuItem.setIcon(R.drawable.ic_filter_list_white_24dp);
+ } else if (direction == WalletTransactionsViewModel.Direction.RECEIVED) {
+ menu.findItem(R.id.wallet_transactions_options_filter_received).setChecked(true);
+ filterMenuItem.setIcon(R.drawable.transactions_list_filter_received);
+ } else if (direction == WalletTransactionsViewModel.Direction.SENT) {
+ menu.findItem(R.id.wallet_transactions_options_filter_sent).setChecked(true);
+ filterMenuItem.setIcon(R.drawable.transactions_list_filter_sent);
+ }
+ }
+
+ @Override
+ public boolean onMenuItemSelected(final MenuItem item) {
+ final int itemId = item.getItemId();
+ if (itemId == R.id.wallet_transactions_options_filter_all) {
+ viewModel.setDirection(null);
+ filterMenuItem.setIcon(R.drawable.ic_filter_list_white_24dp);
+ return true;
+ } else if (itemId == R.id.wallet_transactions_options_filter_received) {
+ viewModel.setDirection(WalletTransactionsViewModel.Direction.RECEIVED);
+ filterMenuItem.setIcon(R.drawable.transactions_list_filter_received);
+ return true;
+ } else if (itemId == R.id.wallet_transactions_options_filter_sent) {
+ viewModel.setDirection(WalletTransactionsViewModel.Direction.SENT);
+ filterMenuItem.setIcon(R.drawable.transactions_list_filter_sent);
+ return true;
+ }
+ return false;
+ }
+ });
}
@Override
@@ -203,50 +244,6 @@ public void onResume() {
viewModel.setWarning(warning());
}
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.wallet_transactions_fragment_options, menu);
- filterMenuItem = menu.findItem(R.id.wallet_transactions_options_filter);
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- @Override
- public void onPrepareOptionsMenu(final Menu menu) {
- final WalletTransactionsViewModel.Direction direction = viewModel.direction.getValue();
- if (direction == null) {
- menu.findItem(R.id.wallet_transactions_options_filter_all).setChecked(true);
- filterMenuItem.setIcon(R.drawable.ic_filter_list_white_24dp);
- } else if (direction == WalletTransactionsViewModel.Direction.RECEIVED) {
- menu.findItem(R.id.wallet_transactions_options_filter_received).setChecked(true);
- filterMenuItem.setIcon(R.drawable.transactions_list_filter_received);
- } else if (direction == WalletTransactionsViewModel.Direction.SENT) {
- menu.findItem(R.id.wallet_transactions_options_filter_sent).setChecked(true);
- filterMenuItem.setIcon(R.drawable.transactions_list_filter_sent);
- }
- super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- final int itemId = item.getItemId();
- final WalletTransactionsViewModel.Direction direction;
- if (itemId == R.id.wallet_transactions_options_filter_all) {
- direction = null;
- filterMenuItem.setIcon(R.drawable.ic_filter_list_white_24dp);
- } else if (itemId == R.id.wallet_transactions_options_filter_received) {
- direction = WalletTransactionsViewModel.Direction.RECEIVED;
- filterMenuItem.setIcon(R.drawable.transactions_list_filter_received);
- } else if (itemId == R.id.wallet_transactions_options_filter_sent) {
- direction = WalletTransactionsViewModel.Direction.SENT;
- filterMenuItem.setIcon(R.drawable.transactions_list_filter_sent);
- } else {
- return false;
- }
-
- viewModel.setDirection(direction);
- return true;
- }
-
@Override
public void onTransactionClick(final View view, final Sha256Hash transactionId) {
viewModel.selectedTransaction.setValue(transactionId);
diff --git a/wallet/src/de/schildbach/wallet/ui/backup/BackupWalletDialogFragment.java b/wallet/src/de/schildbach/wallet/ui/backup/BackupWalletDialogFragment.java
index 11c40582db..819d90c6cd 100644
--- a/wallet/src/de/schildbach/wallet/ui/backup/BackupWalletDialogFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/backup/BackupWalletDialogFragment.java
@@ -23,9 +23,7 @@
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.graphics.Typeface;
-import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.Html;
@@ -36,6 +34,8 @@
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Observer;
@@ -49,7 +49,6 @@
import de.schildbach.wallet.ui.DialogBuilder;
import de.schildbach.wallet.ui.ShowPasswordCheckListener;
import de.schildbach.wallet.util.Crypto;
-import de.schildbach.wallet.util.Iso8601Format;
import de.schildbach.wallet.util.Toast;
import de.schildbach.wallet.util.WalletUtils;
import org.bitcoinj.wallet.Protos;
@@ -66,12 +65,13 @@
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.TimeZone;
-import static androidx.core.util.Preconditions.checkNotNull;
-import static androidx.core.util.Preconditions.checkState;
+import static com.google.common.base.Preconditions.checkState;
/**
* @author Andreas Schildbach
@@ -97,10 +97,78 @@ public static void show(final FragmentManager fm) {
private AbstractWalletActivityViewModel walletActivityViewModel;
private BackupWalletViewModel viewModel;
- private static final int REQUEST_CODE_CREATE_DOCUMENT = 0;
-
private static final Logger log = LoggerFactory.getLogger(BackupWalletDialogFragment.class);
+ private final ActivityResultLauncher createDocumentLauncher =
+ registerForActivityResult(new ActivityResultContracts.CreateDocument(Constants.MIMETYPE_WALLET_BACKUP),
+ uri -> {
+ if (uri != null) {
+ walletActivityViewModel.wallet.observe(this, new Observer() {
+ @Override
+ public void onChanged(final Wallet wallet) {
+ walletActivityViewModel.wallet.removeObserver(this);
+
+ final String targetProvider = WalletUtils.uriToProvider(uri);
+ final String password = passwordView.getText().toString().trim();
+ checkState(!password.isEmpty());
+ wipePasswords();
+ dismiss();
+
+ byte[] plainBytes = null;
+ try (final Writer cipherOut = new OutputStreamWriter(
+ activity.getContentResolver().openOutputStream(uri),
+ StandardCharsets.UTF_8)) {
+ final Protos.Wallet walletProto =
+ new WalletProtobufSerializer().walletToProto(wallet);
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ walletProto.writeTo(baos);
+ baos.close();
+ plainBytes = baos.toByteArray();
+
+ final String cipherText = Crypto.encrypt(plainBytes, password.toCharArray());
+ cipherOut.write(cipherText);
+ cipherOut.flush();
+
+ log.info("backed up wallet to: '{}'{}, {} characters written", uri,
+ targetProvider != null ? " (" + targetProvider + ")" : "",
+ cipherText.length());
+ } catch (final IOException x) {
+ log.error("problem backing up wallet to " + uri, x);
+ ErrorDialogFragment.showDialog(getParentFragmentManager(), x.toString());
+ return;
+ }
+
+ try (final Reader cipherIn = new InputStreamReader(
+ activity.getContentResolver().openInputStream(uri),
+ StandardCharsets.UTF_8)) {
+ final StringBuilder cipherText = new StringBuilder();
+ CharStreams.copy(cipherIn, cipherText);
+ cipherIn.close();
+
+ final byte[] plainBytes2 = Crypto.decryptBytes(cipherText.toString(),
+ password.toCharArray());
+ if (!Arrays.equals(plainBytes, plainBytes2))
+ throw new IOException("verification failed");
+
+ log.info("verified successfully: '" + uri + "'");
+ application.getConfiguration().disarmBackupReminder();
+ SuccessDialogFragment.showDialog(getParentFragmentManager(),
+ targetProvider != null ? targetProvider : uri.toString());
+ } catch (final IOException x) {
+ log.error("problem verifying backup from " + uri, x);
+ ErrorDialogFragment.showDialog(getParentFragmentManager(), x.toString());
+ return;
+ }
+ }
+ });
+ } else {
+ log.info("cancelled backing up wallet");
+ passwordView.setEnabled(true);
+ passwordAgainView.setEnabled(true);
+ activity.finish();
+ }
+ });
+
private final TextWatcher textWatcher = new TextWatcher() {
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
@@ -249,95 +317,19 @@ private void backupWallet() {
passwordView.setEnabled(false);
passwordAgainView.setEnabled(false);
- final DateFormat dateFormat = new Iso8601Format("yyyy-MM-dd-HH-mm");
- dateFormat.setTimeZone(TimeZone.getDefault());
-
+ final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm");
final StringBuilder filename = new StringBuilder(Constants.Files.EXTERNAL_WALLET_BACKUP);
filename.append('-');
- filename.append(dateFormat.format(new Date()));
+ filename.append(dateFormat.format(Instant.now().atZone(TimeZone.getDefault().toZoneId())));
- final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType(Constants.MIMETYPE_WALLET_BACKUP);
- intent.putExtra(Intent.EXTRA_TITLE, filename.toString());
try {
- startActivityForResult(intent, REQUEST_CODE_CREATE_DOCUMENT);
+ createDocumentLauncher.launch(filename.toString());
} catch (final ActivityNotFoundException x) {
- log.warn("Cannot open document selector: {}", intent);
+ log.warn("Cannot open document selector: {}", filename);
new Toast(activity).longToast(R.string.toast_start_storage_provider_selector_failed);
}
}
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
- if (requestCode == REQUEST_CODE_CREATE_DOCUMENT) {
- if (resultCode == Activity.RESULT_OK) {
- walletActivityViewModel.wallet.observe(this, new Observer() {
- @Override
- public void onChanged(final Wallet wallet) {
- walletActivityViewModel.wallet.removeObserver(this);
-
- final Uri targetUri = checkNotNull(intent.getData());
- final String targetProvider = WalletUtils.uriToProvider(targetUri);
- final String password = passwordView.getText().toString().trim();
- checkState(!password.isEmpty());
- wipePasswords();
- dismiss();
-
- byte[] plainBytes = null;
- try (final Writer cipherOut = new OutputStreamWriter(
- activity.getContentResolver().openOutputStream(targetUri), StandardCharsets.UTF_8)) {
- final Protos.Wallet walletProto = new WalletProtobufSerializer().walletToProto(wallet);
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- walletProto.writeTo(baos);
- baos.close();
- plainBytes = baos.toByteArray();
-
- final String cipherText = Crypto.encrypt(plainBytes, password.toCharArray());
- cipherOut.write(cipherText);
- cipherOut.flush();
-
- log.info("backed up wallet to: '{}'{}, {} characters written", targetUri,
- targetProvider != null ? " (" + targetProvider + ")" : "", cipherText.length());
- } catch (final IOException x) {
- log.error("problem backing up wallet to " + targetUri, x);
- ErrorDialogFragment.showDialog(getParentFragmentManager(), x.toString());
- return;
- }
-
- try (final Reader cipherIn = new InputStreamReader(
- activity.getContentResolver().openInputStream(targetUri), StandardCharsets.UTF_8)) {
- final StringBuilder cipherText = new StringBuilder();
- CharStreams.copy(cipherIn, cipherText);
- cipherIn.close();
-
- final byte[] plainBytes2 = Crypto.decryptBytes(cipherText.toString(),
- password.toCharArray());
- if (!Arrays.equals(plainBytes, plainBytes2))
- throw new IOException("verification failed");
-
- log.info("verified successfully: '" + targetUri + "'");
- application.getConfiguration().disarmBackupReminder();
- SuccessDialogFragment.showDialog(getParentFragmentManager(),
- targetProvider != null ? targetProvider : targetUri.toString());
- } catch (final IOException x) {
- log.error("problem verifying backup from " + targetUri, x);
- ErrorDialogFragment.showDialog(getParentFragmentManager(), x.toString());
- return;
- }
- }
- });
- } else if (resultCode == Activity.RESULT_CANCELED) {
- log.info("cancelled backing up wallet");
- passwordView.setEnabled(true);
- passwordAgainView.setEnabled(true);
- activity.finish();
- }
- } else {
- super.onActivityResult(requestCode, resultCode, intent);
- }
- }
-
public static class SuccessDialogFragment extends DialogFragment {
private static final String FRAGMENT_TAG = SuccessDialogFragment.class.getName();
private static final String KEY_TARGET = "target";
diff --git a/wallet/src/de/schildbach/wallet/ui/backup/RestoreWalletDialogFragment.java b/wallet/src/de/schildbach/wallet/ui/backup/RestoreWalletDialogFragment.java
index e252610cd3..76cfbc8b99 100644
--- a/wallet/src/de/schildbach/wallet/ui/backup/RestoreWalletDialogFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/backup/RestoreWalletDialogFragment.java
@@ -23,7 +23,6 @@
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@@ -33,7 +32,8 @@
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
-import androidx.annotation.Nullable;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
@@ -69,7 +69,6 @@
public class RestoreWalletDialogFragment extends DialogFragment {
private static final String FRAGMENT_TAG = RestoreWalletDialogFragment.class.getName();
private static final String KEY_BACKUP_URI = "backup_uri";
- private static final int REQUEST_CODE_OPEN_DOCUMENT = 0;
private AbstractWalletActivity activity;
private WalletApplication application;
@@ -86,6 +85,17 @@ public class RestoreWalletDialogFragment extends DialogFragment {
private static final Logger log = LoggerFactory.getLogger(RestoreWalletDialogFragment.class);
+ private final ActivityResultLauncher openDocumentLauncher =
+ registerForActivityResult(new ActivityResultContracts.OpenDocument(), uri -> {
+ if (uri != null) {
+ viewModel.backupUri.setValue(uri);
+ } else {
+ log.info("cancelled restoring wallet");
+ dismiss();
+ maybeFinishActivity();
+ }
+ });
+
public static void showPick(final FragmentManager fm) {
final DialogFragment newFragment = new RestoreWalletDialogFragment();
newFragment.show(fm, FRAGMENT_TAG);
@@ -146,39 +156,15 @@ protected void onEvent(final String message) {
if (args != null) {
viewModel.backupUri.setValue((Uri) args.getParcelable(KEY_BACKUP_URI));
} else {
- final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
try {
- startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT);
+ openDocumentLauncher.launch(new String[] { "*/*" });
} catch (final ActivityNotFoundException x) {
- log.warn("Cannot open document selector: {}", intent);
+ log.warn("Cannot open document selector: {}", "*/*");
new Toast(activity).longToast(R.string.toast_start_storage_provider_selector_failed);
}
}
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
- if (requestCode == REQUEST_CODE_OPEN_DOCUMENT) {
- if (resultCode == Activity.RESULT_OK) {
- if (data != null) {
- viewModel.backupUri.setValue(data.getData());
- } else {
- log.info("didn't get uri");
- dismiss();
- maybeFinishActivity();
- }
- } else if (resultCode == Activity.RESULT_CANCELED) {
- log.info("cancelled restoring wallet");
- dismiss();
- maybeFinishActivity();
- }
- } else {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final View view = LayoutInflater.from(activity).inflate(R.layout.restore_wallet_dialog, null);
diff --git a/wallet/src/de/schildbach/wallet/ui/monitor/PeerListAdapter.java b/wallet/src/de/schildbach/wallet/ui/monitor/PeerListAdapter.java
index 503de19e56..b5724abd87 100644
--- a/wallet/src/de/schildbach/wallet/ui/monitor/PeerListAdapter.java
+++ b/wallet/src/de/schildbach/wallet/ui/monitor/PeerListAdapter.java
@@ -72,7 +72,7 @@ public static List buildListItems(final Context context, final List {
try {
- final InetAddress address = InetAddress.getByName(hostAndPort.getHost()); // blocks on network
+ final InetAddress address = checkNotNull(InetAddress.getByName(hostAndPort.getHost())); // blocks on network
final int port = hostAndPort.getPortOrDefault(Constants.NETWORK_PARAMETERS.getPort());
final InetSocketAddress socketAddress = new InetSocketAddress(address, port);
callbackHandler.post(() -> onSuccess(hostAndPort, socketAddress));
diff --git a/wallet/src/de/schildbach/wallet/ui/preference/SettingsFragment.java b/wallet/src/de/schildbach/wallet/ui/preference/SettingsFragment.java
index 6000e9cbf9..d667c8346b 100644
--- a/wallet/src/de/schildbach/wallet/ui/preference/SettingsFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/preference/SettingsFragment.java
@@ -17,8 +17,10 @@
package de.schildbach.wallet.ui.preference;
+import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
@@ -26,6 +28,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.PowerManager;
import android.os.Process;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
@@ -38,6 +41,7 @@
import android.text.InputFilter;
import android.text.Spanned;
import android.text.TextWatcher;
+import androidx.core.content.ContextCompat;
import com.google.common.net.HostAndPort;
import de.schildbach.wallet.Configuration;
import de.schildbach.wallet.Constants;
@@ -59,6 +63,8 @@ public final class SettingsFragment extends PreferenceFragment implements OnPref
private WalletApplication application;
private Configuration config;
private PackageManager pm;
+ private PowerManager powerManager;
+ private BluetoothManager bluetoothManager;
private final Handler handler = new Handler();
private HandlerThread backgroundThread;
@@ -80,6 +86,8 @@ public void onAttach(final Activity activity) {
this.application = (WalletApplication) activity.getApplication();
this.config = application.getConfiguration();
this.pm = activity.getPackageManager();
+ this.powerManager = activity.getSystemService(PowerManager.class);
+ this.bluetoothManager = activity.getSystemService(BluetoothManager.class);
}
@Override
@@ -120,10 +128,16 @@ public void onCreate(final Bundle savedInstanceState) {
if (dataUsagePreference.getIntent() == null || pm.resolveActivity(dataUsagePreference.getIntent(), 0) == null)
removeOrDisablePreference(dataUsagePreference);
+ final Preference batteryOptimizationPreference = findPreference(Configuration.PREFS_KEY_BATTERY_OPTIMIZATION);
+ if (ContextCompat.checkSelfPermission(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_GRANTED)
+ batteryOptimizationPreference.setIntent(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
+ Uri.parse("package:" + application.getPackageName())));
+ if (powerManager.isIgnoringBatteryOptimizations(application.getPackageName()) || pm.resolveActivity(batteryOptimizationPreference.getIntent(), 0) == null)
+ removeOrDisablePreference(batteryOptimizationPreference);
+
final Preference notificationsPreference = findPreference(Configuration.PREFS_KEY_NOTIFICATIONS);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
- notificationsPreference.setIntent(new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
- .putExtra(Settings.EXTRA_APP_PACKAGE, application.getPackageName()));
+ notificationsPreference.setIntent(new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+ .putExtra(Settings.EXTRA_APP_PACKAGE, application.getPackageName()));
if (notificationsPreference.getIntent() == null || pm.resolveActivity(notificationsPreference.getIntent(), 0) == null)
removeOrDisablePreference(notificationsPreference);
@@ -218,7 +232,7 @@ private void updateOwnName() {
}
private void updateBluetoothAddress() {
- final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ final BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter != null) {
String bluetoothAddress = Bluetooth.getAddress(bluetoothAdapter);
if (bluetoothAddress == null)
@@ -240,10 +254,7 @@ private void updateBluetoothAddress() {
}
private void removeOrDisablePreference(final Preference preference) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
- preference.getParent().removePreference(preference);
- else
- preference.setEnabled(false);
+ preference.getParent().removePreference(preference);
}
private static class RestrictToHex implements InputFilter {
diff --git a/wallet/src/de/schildbach/wallet/ui/scan/ScanActivity.java b/wallet/src/de/schildbach/wallet/ui/scan/ScanActivity.java
index fb4328749d..d1b65cb531 100644
--- a/wallet/src/de/schildbach/wallet/ui/scan/ScanActivity.java
+++ b/wallet/src/de/schildbach/wallet/ui/scan/ScanActivity.java
@@ -18,10 +18,7 @@
package de.schildbach.wallet.ui.scan;
import android.Manifest;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
-import android.app.ActivityOptions;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -31,7 +28,6 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
-import android.graphics.drawable.ColorDrawable;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.os.Bundle;
@@ -44,14 +40,12 @@
import android.view.TextureView;
import android.view.TextureView.SurfaceTextureListener;
import android.view.View;
-import android.view.ViewAnimationUtils;
import android.view.WindowManager;
-import android.view.animation.AccelerateInterpolator;
-import androidx.annotation.Nullable;
-import androidx.core.app.ActivityCompat;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContract;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import com.google.zxing.BinaryBitmap;
@@ -66,7 +60,6 @@
import de.schildbach.wallet.ui.AbstractWalletActivity;
import de.schildbach.wallet.ui.DialogBuilder;
import de.schildbach.wallet.ui.Event;
-import de.schildbach.wallet.util.OnFirstPreDraw;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -77,35 +70,22 @@
* @author Andreas Schildbach
*/
@SuppressWarnings("deprecation")
-public final class ScanActivity extends AbstractWalletActivity
- implements SurfaceTextureListener, ActivityCompat.OnRequestPermissionsResultCallback {
- private static final String INTENT_EXTRA_SCENE_TRANSITION_X = "scene_transition_x";
- private static final String INTENT_EXTRA_SCENE_TRANSITION_Y = "scene_transition_y";
- public static final String INTENT_EXTRA_RESULT = "result";
-
- public static void startForResult(final Activity activity, @Nullable final View clickView, final int requestCode) {
- if (clickView != null) {
- final int[] clickViewLocation = new int[2];
- clickView.getLocationOnScreen(clickViewLocation);
- final Intent intent = new Intent(activity, ScanActivity.class);
- intent.putExtra(ScanActivity.INTENT_EXTRA_SCENE_TRANSITION_X,
- (int) (clickViewLocation[0] + clickView.getWidth() / 2));
- intent.putExtra(ScanActivity.INTENT_EXTRA_SCENE_TRANSITION_Y,
- (int) (clickViewLocation[1] + clickView.getHeight() / 2));
- final ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(activity, clickView,
- "transition");
- activity.startActivityForResult(intent, requestCode, options.toBundle());
- } else {
- startForResult(activity, requestCode);
- }
- }
+public final class ScanActivity extends AbstractWalletActivity implements SurfaceTextureListener {
+ private static final String INTENT_EXTRA_RESULT = "result";
- public static void startForResult(final Activity activity, final int resultCode) {
- activity.startActivityForResult(new Intent(activity, ScanActivity.class), resultCode);
- }
+ public static class Scan extends ActivityResultContract {
+ @Override
+ public Intent createIntent(final Context context, Void unused) {
+ return new Intent(context, ScanActivity.class);
+ }
- public static void startForResult(final Fragment fragment, final Activity activity, final int resultCode) {
- fragment.startActivityForResult(new Intent(activity, ScanActivity.class), resultCode);
+ @Override
+ public String parseResult(final int resultCode, final Intent intent) {
+ if (resultCode == Activity.RESULT_OK)
+ return intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
+ else
+ return null;
+ }
}
private static final long VIBRATE_DURATION = 50L;
@@ -118,7 +98,6 @@ public static void startForResult(final Fragment fragment, final Activity activi
private TextureView previewView;
private volatile boolean surfaceCreated = false;
- private Animator sceneTransition = null;
private Vibrator vibrator;
private HandlerThread cameraThread;
@@ -128,10 +107,20 @@ public static void startForResult(final Fragment fragment, final Activity activi
private static final Logger log = LoggerFactory.getLogger(ScanActivity.class);
+ private final ActivityResultLauncher requestPermissionLauncher =
+ registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> {
+ if (granted) {
+ maybeOpenCamera();
+ } else {
+ log.info("missing {}, showing error", Manifest.permission.CAMERA);
+ viewModel.showPermissionWarnDialog.setValue(Event.simple());
+ }
+ });
+
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+ vibrator = getSystemService(Vibrator.class);
viewModel = new ViewModelProvider(this).get(ScanViewModel.class);
viewModel.showPermissionWarnDialog.observe(this, new Event.Observer() {
@@ -148,23 +137,6 @@ protected void onEvent(final Void v) {
getString(R.string.scan_camera_problem_dialog_message));
}
});
- viewModel.maybeStartSceneTransition.observe(this, new Event.Observer() {
- @Override
- protected void onEvent(final Void v) {
- if (sceneTransition != null) {
- contentView.setAlpha(1);
- sceneTransition.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- getWindow().setBackgroundDrawable(new ColorDrawable(
- getColor(android.R.color.black)));
- }
- });
- sceneTransition.start();
- sceneTransition = null;
- }
- }
- });
// Stick to the orientation the activity was started with. We cannot declare this in the
// AndroidManifest.xml, because it's not allowed in combination with the windowIsTranslucent=true
@@ -186,31 +158,7 @@ public void onAnimationEnd(Animator animation) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
log.info("missing {}, requesting", Manifest.permission.CAMERA);
- ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.CAMERA }, 0);
- }
-
- if (savedInstanceState == null) {
- final Intent intent = getIntent();
- final int x = intent.getIntExtra(INTENT_EXTRA_SCENE_TRANSITION_X, -1);
- final int y = intent.getIntExtra(INTENT_EXTRA_SCENE_TRANSITION_Y, -1);
- if (x != -1 || y != -1) {
- // Using alpha rather than visibility because 'invisible' will cause the surface view to never
- // start up, so the animation will never start.
- contentView.setAlpha(0);
- getWindow().setBackgroundDrawable(
- new ColorDrawable(getColor(android.R.color.transparent)));
- OnFirstPreDraw.listen(contentView, () -> {
- float finalRadius = (float) (Math.max(contentView.getWidth(), contentView.getHeight()));
- final int duration = getResources().getInteger(android.R.integer.config_mediumAnimTime);
- sceneTransition = ViewAnimationUtils.createCircularReveal(contentView, x, y, 0, finalRadius);
- sceneTransition.setDuration(duration);
- sceneTransition.setInterpolator(new AccelerateInterpolator());
- // TODO Here, the transition should start in a paused state, showing the first frame
- // of the animation. Sadly, RevealAnimator doesn't seem to support this, unlike
- // (subclasses of) ValueAnimator.
- return false;
- });
- }
+ requestPermissionLauncher.launch(Manifest.permission.CAMERA);
}
}
@@ -242,17 +190,6 @@ protected void onDestroy() {
super.onDestroy();
}
- @Override
- public void onRequestPermissionsResult(final int requestCode, final String[] permissions,
- final int[] grantResults) {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- maybeOpenCamera();
- } else {
- log.info("missing {}, showing error", Manifest.permission.CAMERA);
- viewModel.showPermissionWarnDialog.setValue(Event.simple());
- }
- }
-
private void maybeOpenCamera() {
if (surfaceCreated && ContextCompat.checkSelfPermission(this,
Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)
@@ -339,7 +276,6 @@ public void run() {
if (nonContinuousAutoFocus)
cameraHandler.post(new AutoFocusRunnable(camera));
- viewModel.maybeStartSceneTransition.postValue(Event.simple());
cameraHandler.post(fetchAndDecodeRunnable);
} catch (final Exception x) {
log.info("problem opening camera", x);
diff --git a/wallet/src/de/schildbach/wallet/ui/scan/ScanViewModel.java b/wallet/src/de/schildbach/wallet/ui/scan/ScanViewModel.java
index aa70c8d23b..51835b936a 100644
--- a/wallet/src/de/schildbach/wallet/ui/scan/ScanViewModel.java
+++ b/wallet/src/de/schildbach/wallet/ui/scan/ScanViewModel.java
@@ -27,5 +27,4 @@
public class ScanViewModel extends ViewModel {
public final MutableLiveData> showPermissionWarnDialog = new MutableLiveData<>();
public final MutableLiveData> showProblemWarnDialog = new MutableLiveData<>();
- public final MutableLiveData> maybeStartSceneTransition = new MutableLiveData<>();
}
diff --git a/wallet/src/de/schildbach/wallet/ui/send/DeriveKeyTask.java b/wallet/src/de/schildbach/wallet/ui/send/DeriveKeyTask.java
index 3cee5371a1..68212e32e8 100644
--- a/wallet/src/de/schildbach/wallet/ui/send/DeriveKeyTask.java
+++ b/wallet/src/de/schildbach/wallet/ui/send/DeriveKeyTask.java
@@ -27,8 +27,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static androidx.core.util.Preconditions.checkNotNull;
-import static androidx.core.util.Preconditions.checkState;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
/**
* @author Andreas Schildbach
diff --git a/wallet/src/de/schildbach/wallet/ui/send/RaiseFeeDialogFragment.java b/wallet/src/de/schildbach/wallet/ui/send/RaiseFeeDialogFragment.java
index f4bc4b2d0e..47a7b27d7d 100644
--- a/wallet/src/de/schildbach/wallet/ui/send/RaiseFeeDialogFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/send/RaiseFeeDialogFragment.java
@@ -56,7 +56,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static androidx.core.util.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Andreas Schildbach
diff --git a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.java b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.java
index e6451ca9c4..d1e4042d55 100644
--- a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.java
+++ b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsActivity.java
@@ -21,8 +21,10 @@
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.Nullable;
+import androidx.core.view.MenuProvider;
import androidx.lifecycle.ViewModelProvider;
import de.schildbach.wallet.Constants;
import de.schildbach.wallet.R;
@@ -46,13 +48,19 @@ public final class SendCoinsActivity extends AbstractWalletActivity {
public static void start(final Context context, final PaymentIntent paymentIntent,
final @Nullable FeeCategory feeCategory, final int intentFlags) {
+ final Intent intent = startIntent(context, paymentIntent, feeCategory, intentFlags);
+ context.startActivity(intent);
+ }
+
+ public static Intent startIntent(final Context context, final PaymentIntent paymentIntent,
+ final FeeCategory feeCategory, final int intentFlags) {
final Intent intent = new Intent(context, SendCoinsActivity.class);
intent.putExtra(INTENT_EXTRA_PAYMENT_INTENT, paymentIntent);
if (feeCategory != null)
intent.putExtra(INTENT_EXTRA_FEE_CATEGORY, feeCategory);
if (intentFlags != 0)
intent.setFlags(intentFlags);
- context.startActivity(intent);
+ return intent;
}
public static void start(final Context context, final PaymentIntent paymentIntent) {
@@ -81,22 +89,26 @@ protected void onEvent(final Integer messageResId) {
}
});
- BlockchainService.start(this, false);
- }
+ addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.send_coins_activity_options, menu);
+ }
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- getMenuInflater().inflate(R.menu.send_coins_activity_options, menu);
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ }
- return super.onCreateOptionsMenu(menu);
- }
+ @Override
+ public boolean onMenuItemSelected(final MenuItem item) {
+ if (item.getItemId() == R.id.send_coins_options_help) {
+ viewModel.showHelpDialog.setValue(new Event<>(R.string.help_send_coins));
+ return true;
+ }
+ return false;
+ }
+ });
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- if (item.getItemId() == R.id.send_coins_options_help) {
- viewModel.showHelpDialog.setValue(new Event<>(R.string.help_send_coins));
- return true;
- }
- return super.onOptionsItemSelected(item);
+ BlockchainService.start(this, false);
}
}
diff --git a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.java b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.java
index da441b5a96..5ce24fd87d 100644
--- a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.java
@@ -17,8 +17,10 @@
package de.schildbach.wallet.ui.send;
+import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -28,6 +30,7 @@
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -50,7 +53,11 @@
import android.widget.EditText;
import android.widget.Filter;
import android.widget.TextView;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
@@ -65,7 +72,6 @@
import de.schildbach.wallet.addressbook.AddressBookEntry;
import de.schildbach.wallet.data.PaymentIntent;
import de.schildbach.wallet.data.PaymentIntent.Standard;
-import de.schildbach.wallet.integration.android.BitcoinIntegration;
import de.schildbach.wallet.offline.DirectPaymentTask;
import de.schildbach.wallet.service.BlockchainState;
import de.schildbach.wallet.ui.AbstractWalletActivity;
@@ -78,6 +84,7 @@
import de.schildbach.wallet.ui.InputParser.StreamInputParser;
import de.schildbach.wallet.ui.InputParser.StringInputParser;
import de.schildbach.wallet.ui.ProgressDialogFragment;
+import de.schildbach.wallet.ui.RequestEnableBluetooth;
import de.schildbach.wallet.ui.TransactionsAdapter;
import de.schildbach.wallet.ui.scan.ScanActivity;
import de.schildbach.wallet.util.Bluetooth;
@@ -152,15 +159,53 @@ public final class SendCoinsFragment extends Fragment {
private Button viewGo;
private Button viewCancel;
- private static final int REQUEST_CODE_SCAN = 0;
- private static final int REQUEST_CODE_ENABLE_BLUETOOTH_FOR_PAYMENT_REQUEST = 1;
- private static final int REQUEST_CODE_ENABLE_BLUETOOTH_FOR_DIRECT_PAYMENT = 2;
-
private AbstractWalletActivityViewModel walletActivityViewModel;
private SendCoinsViewModel viewModel;
private static final Logger log = LoggerFactory.getLogger(SendCoinsFragment.class);
+ private final ActivityResultLauncher scanLauncher =
+ registerForActivityResult(new ScanActivity.Scan(), input -> {
+ if (input == null) return;
+ new StringInputParser(input) {
+ @Override
+ protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
+ setState(null);
+
+ updateStateFrom(paymentIntent);
+ }
+
+ @Override
+ protected void handleDirectTransaction(final Transaction transaction) throws VerificationException {
+ cannotClassify(input);
+ }
+
+ @Override
+ protected void error(final int messageResId, final Object... messageArgs) {
+ final DialogBuilder dialog = DialogBuilder.dialog(activity, R.string.button_scan, messageResId, messageArgs);
+ dialog.singleDismissButton(null);
+ dialog.show();
+ }
+ }.parse();
+ });
+ private final ActivityResultLauncher requestPermissionLauncher =
+ registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> {
+ if (granted)
+ maybeEnableBluetooth();
+ else
+ directPaymentEnableView.setChecked(false);
+ });
+ private final ActivityResultLauncher requestEnableBluetoothForPaymentRequestLauncher =
+ registerForActivityResult(new RequestEnableBluetooth(), enabled -> {
+ if (viewModel.paymentIntent.isBluetoothPaymentRequestUrl())
+ requestPaymentRequest();
+ });
+ private final ActivityResultLauncher requestEnableBluetoothForDirectPaymentLauncher =
+ registerForActivityResult(new RequestEnableBluetooth(), enabled -> {
+ if (viewModel.paymentIntent.isBluetoothPaymentUrl())
+ directPaymentEnableView.setChecked(enabled);
+ });
+
private final class ReceivingAddressListener
implements OnFocusChangeListener, TextWatcher, AdapterView.OnItemClickListener {
@Override
@@ -207,8 +252,7 @@ public void onItemClick(final AdapterView> parent, final View view, final int
private final CurrencyAmountView.Listener amountsListener = new CurrencyAmountView.Listener() {
@Override
public void changed() {
- updateView();
- handler.post(dryrunRunnable);
+ viewModel.amount.setValue(amountCalculatorLink.getAmount());
}
@Override
@@ -296,6 +340,7 @@ public void onAttach(final Context context) {
this.config = application.getConfiguration();
this.addressBookDao = AddressBookDatabase.getDatabase(context).addressBookDao();
this.contentResolver = application.getContentResolver();
+ this.bluetoothAdapter = application.getSystemService(BluetoothManager.class).getAdapter();
}
@Override
@@ -303,8 +348,6 @@ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.fragmentManager = getChildFragmentManager();
- setHasOptionsMenu(true);
-
walletActivityViewModel = new ViewModelProvider(activity).get(AbstractWalletActivityViewModel.class);
walletActivityViewModel.wallet.observe(this, wallet -> updateView());
viewModel = new ViewModelProvider(this).get(SendCoinsViewModel.class);
@@ -316,10 +359,8 @@ public void onCreate(final Bundle savedInstanceState) {
amountCalculatorLink.setExchangeRate(exchangeRate != null ? exchangeRate.exchangeRate() : null);
});
}
- viewModel.dynamicFees.observe(this, dynamicFees -> {
- updateView();
- handler.post(dryrunRunnable);
- });
+ viewModel.dynamicFees.observe(this, dynamicFees -> updateView());
+ viewModel.feeCategory.observe(this, feeCategory -> updateView());
application.blockchainState.observe(this, blockchainState -> updateView());
viewModel.balance.observe(this, coin -> activity.invalidateOptionsMenu());
viewModel.progress.observe(this, new ProgressDialogFragment.Observer(fragmentManager));
@@ -335,13 +376,70 @@ else if (numBroadcastPeers > 1 || confidenceType == ConfidenceType.BUILDING)
}
updateView();
});
-
- bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ viewModel.amount.observe(this, amount -> {
+ updateView();
+ viewModel.maybeDryrun();
+ });
+ viewModel.visibleAmount.observe(this, amount -> amountCalculatorLink.setBtcAmount(amount));
+ viewModel.dryrunTransaction.observe(this, transaction -> updateView());
+ viewModel.dryrunException.observe(this, e -> updateView());
backgroundThread = new HandlerThread("backgroundThread", Process.THREAD_PRIORITY_BACKGROUND);
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
+ activity.addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.send_coins_fragment_options, menu);
+ }
+
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ final MenuItem scanAction = menu.findItem(R.id.send_coins_options_scan);
+ final PackageManager pm = activity.getPackageManager();
+ scanAction.setVisible(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
+ || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT));
+ scanAction.setEnabled(viewModel.state == SendCoinsViewModel.State.INPUT);
+
+ final MenuItem emptyAction = menu.findItem(R.id.send_coins_options_empty);
+ emptyAction.setEnabled(viewModel.state == SendCoinsViewModel.State.INPUT
+ && viewModel.paymentIntent.mayEditAmount() && viewModel.balance.getValue() != null);
+
+ final MenuItem feeCategoryAction = menu.findItem(R.id.send_coins_options_fee_category);
+ final FeeCategory feeCategory = viewModel.feeCategory.getValue();
+ feeCategoryAction.setEnabled(viewModel.state == SendCoinsViewModel.State.INPUT);
+ if (feeCategory == FeeCategory.ECONOMIC)
+ menu.findItem(R.id.send_coins_options_fee_category_economic).setChecked(true);
+ else if (feeCategory == FeeCategory.NORMAL)
+ menu.findItem(R.id.send_coins_options_fee_category_normal).setChecked(true);
+ else if (feeCategory == FeeCategory.PRIORITY)
+ menu.findItem(R.id.send_coins_options_fee_category_priority).setChecked(true);
+ }
+
+ @Override
+ public boolean onMenuItemSelected(final MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == R.id.send_coins_options_scan) {
+ scanLauncher.launch(null);
+ return true;
+ } else if (itemId == R.id.send_coins_options_fee_category_economic) {
+ handleFeeCategory(FeeCategory.ECONOMIC);
+ return true;
+ } else if (itemId == R.id.send_coins_options_fee_category_normal) {
+ handleFeeCategory(FeeCategory.NORMAL);
+ return true;
+ } else if (itemId == R.id.send_coins_options_fee_category_priority) {
+ handleFeeCategory(FeeCategory.PRIORITY);
+ return true;
+ } else if (itemId == R.id.send_coins_options_empty) {
+ handleEmpty();
+ return true;
+ }
+ return false;
+ }
+ });
+
if (savedInstanceState == null) {
final Intent intent = activity.getIntent();
final String action = intent.getAction();
@@ -361,7 +459,7 @@ else if (numBroadcastPeers > 1 || confidenceType == ConfidenceType.BUILDING)
initStateFromPaymentRequest(mimeType, ndefMessagePayload);
} else if ((Intent.ACTION_VIEW.equals(action))
&& PaymentProtocol.MIMETYPE_PAYMENTREQUEST.equals(mimeType)) {
- final byte[] paymentRequest = BitcoinIntegration.paymentRequestFromIntent(intent);
+ final byte[] paymentRequest = paymentRequestFromIntent(intent);
if (intentUri != null)
initStateFromIntentUri(mimeType, intentUri);
@@ -414,11 +512,8 @@ public View onCreateView(final LayoutInflater inflater, final ViewGroup containe
directPaymentEnableView = view.findViewById(R.id.send_coins_direct_payment_enable);
directPaymentEnableView.setOnCheckedChangeListener((buttonView, isChecked) -> {
- if (viewModel.paymentIntent.isBluetoothPaymentUrl() && isChecked && !bluetoothAdapter.isEnabled()) {
- // ask for permission to enable bluetooth
- startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE),
- REQUEST_CODE_ENABLE_BLUETOOTH_FOR_DIRECT_PAYMENT);
- }
+ if (viewModel.paymentIntent.isBluetoothPaymentUrl() && isChecked)
+ maybeEnableBluetooth();
});
hintView = view.findViewById(R.id.send_coins_hint);
@@ -468,7 +563,6 @@ public void onResume() {
privateKeyPasswordView.addTextChangedListener(privateKeyPasswordListener);
updateView();
- handler.post(dryrunRunnable);
}
@Override
@@ -492,99 +586,6 @@ public void onDestroy() {
super.onDestroy();
}
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
- handler.post(() -> onActivityResultResumed(requestCode, resultCode, intent));
- }
-
- private void onActivityResultResumed(final int requestCode, final int resultCode, final Intent intent) {
- if (requestCode == REQUEST_CODE_SCAN) {
- if (resultCode == Activity.RESULT_OK) {
- final String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
-
- new StringInputParser(input) {
- @Override
- protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
- setState(null);
-
- updateStateFrom(paymentIntent);
- }
-
- @Override
- protected void handleDirectTransaction(final Transaction transaction) throws VerificationException {
- cannotClassify(input);
- }
-
- @Override
- protected void error(final int messageResId, final Object... messageArgs) {
- final DialogBuilder dialog = DialogBuilder.dialog(activity, R.string.button_scan, messageResId, messageArgs);
- dialog.singleDismissButton(null);
- dialog.show();
- }
- }.parse();
- }
- } else if (requestCode == REQUEST_CODE_ENABLE_BLUETOOTH_FOR_PAYMENT_REQUEST) {
- if (viewModel.paymentIntent.isBluetoothPaymentRequestUrl())
- requestPaymentRequest();
- } else if (requestCode == REQUEST_CODE_ENABLE_BLUETOOTH_FOR_DIRECT_PAYMENT) {
- if (viewModel.paymentIntent.isBluetoothPaymentUrl())
- directPaymentEnableView.setChecked(resultCode == Activity.RESULT_OK);
- }
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.send_coins_fragment_options, menu);
-
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- @Override
- public void onPrepareOptionsMenu(final Menu menu) {
- final MenuItem scanAction = menu.findItem(R.id.send_coins_options_scan);
- final PackageManager pm = activity.getPackageManager();
- scanAction.setVisible(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
- || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT));
- scanAction.setEnabled(viewModel.state == SendCoinsViewModel.State.INPUT);
-
- final MenuItem emptyAction = menu.findItem(R.id.send_coins_options_empty);
- emptyAction.setEnabled(viewModel.state == SendCoinsViewModel.State.INPUT
- && viewModel.paymentIntent.mayEditAmount() && viewModel.balance.getValue() != null);
-
- final MenuItem feeCategoryAction = menu.findItem(R.id.send_coins_options_fee_category);
- feeCategoryAction.setEnabled(viewModel.state == SendCoinsViewModel.State.INPUT);
- if (viewModel.feeCategory == FeeCategory.ECONOMIC)
- menu.findItem(R.id.send_coins_options_fee_category_economic).setChecked(true);
- else if (viewModel.feeCategory == FeeCategory.NORMAL)
- menu.findItem(R.id.send_coins_options_fee_category_normal).setChecked(true);
- else if (viewModel.feeCategory == FeeCategory.PRIORITY)
- menu.findItem(R.id.send_coins_options_fee_category_priority).setChecked(true);
-
- super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- int itemId = item.getItemId();
- if (itemId == R.id.send_coins_options_scan) {
- ScanActivity.startForResult(this, activity, REQUEST_CODE_SCAN);
- return true;
- } else if (itemId == R.id.send_coins_options_fee_category_economic) {
- handleFeeCategory(FeeCategory.ECONOMIC);
- return true;
- } else if (itemId == R.id.send_coins_options_fee_category_normal) {
- handleFeeCategory(FeeCategory.NORMAL);
- return true;
- } else if (itemId == R.id.send_coins_options_fee_category_priority) {
- handleFeeCategory(FeeCategory.PRIORITY);
- return true;
- } else if (itemId == R.id.send_coins_options_empty) {
- handleEmpty();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
private void validateReceivingAddress() {
try {
final String addressStr = receivingAddressView.getText().toString().trim();
@@ -619,10 +620,10 @@ private boolean isPayeePlausible() {
}
private boolean isAmountPlausible() {
- if (viewModel.dryrunTransaction != null)
- return viewModel.dryrunException == null;
+ if (viewModel.dryrunTransaction.getValue() != null)
+ return viewModel.dryrunException.getValue() == null;
else if (viewModel.paymentIntent.mayEditAmount())
- return amountCalculatorLink.hasAmount();
+ return viewModel.amount.getValue() != null;
else
return viewModel.paymentIntent.hasAmount();
}
@@ -678,23 +679,23 @@ private void signAndSendPayment(final KeyParameter encryptionKey) {
setState(SendCoinsViewModel.State.SIGNING);
// final payment intent
+ final Coin amount = viewModel.amount.getValue();
final PaymentIntent finalPaymentIntent = viewModel.paymentIntent.mergeWithEditedValues(
- amountCalculatorLink.getAmount(),
- viewModel.validatedAddress != null ? viewModel.validatedAddress.address : null);
+ amount, viewModel.validatedAddress != null ? viewModel.validatedAddress.address : null);
final Coin finalAmount = finalPaymentIntent.getAmount();
// prepare send request
final Map fees = viewModel.dynamicFees.getValue();
final Wallet wallet = walletActivityViewModel.wallet.getValue();
final SendRequest sendRequest = finalPaymentIntent.toSendRequest();
- sendRequest.emptyWallet = viewModel.paymentIntent.mayEditAmount()
- && finalAmount.equals(wallet.getBalance(BalanceType.AVAILABLE));
- sendRequest.feePerKb = fees.get(viewModel.feeCategory);
+ sendRequest.emptyWallet =
+ viewModel.paymentIntent.mayEditAmount() && amount.equals(Constants.NETWORK_PARAMETERS.getMaxMoney());
+ sendRequest.feePerKb = fees.get(viewModel.feeCategory.getValue());
sendRequest.memo = viewModel.paymentIntent.memo;
sendRequest.exchangeRate = amountCalculatorLink.getExchangeRate();
sendRequest.aesKey = encryptionKey;
- final Coin fee = viewModel.dryrunTransaction.getFee();
+ final Coin fee = viewModel.dryrunTransaction.getValue().getFee();
if (fee.isGreaterThan(finalAmount)) {
setState(SendCoinsViewModel.State.INPUT);
@@ -739,9 +740,9 @@ protected void onSuccess(final Transaction transaction) {
log.info("returning result to calling activity: {}", callingActivity.flattenToString());
final Intent result = new Intent();
- BitcoinIntegration.transactionHashToResult(result, transaction.getTxId().toString());
+ transactionHashToResult(result, transaction.getTxId().toString());
if (viewModel.paymentIntent.standard == Standard.BIP70)
- BitcoinIntegration.paymentToResult(result, payment.toByteArray());
+ paymentToResult(result, payment.toByteArray());
activity.setResult(Activity.RESULT_OK, result);
}
}
@@ -840,56 +841,14 @@ protected void onFailure(Exception exception) {
}
private void handleFeeCategory(final FeeCategory feeCategory) {
- viewModel.feeCategory = feeCategory;
log.info("switching to {} fee category", feeCategory);
-
- updateView();
- handler.post(dryrunRunnable);
+ viewModel.feeCategory.setValue(feeCategory);
}
private void handleEmpty() {
- final Coin available = viewModel.balance.getValue();
- amountCalculatorLink.setBtcAmount(available);
-
- updateView();
- handler.post(dryrunRunnable);
+ viewModel.amount.setValue(Constants.NETWORK_PARAMETERS.getMaxMoney());
}
- private Runnable dryrunRunnable = new Runnable() {
- @Override
- public void run() {
- if (viewModel.state == SendCoinsViewModel.State.INPUT)
- executeDryrun();
-
- updateView();
- }
-
- private void executeDryrun() {
- viewModel.dryrunTransaction = null;
- viewModel.dryrunException = null;
-
- final Wallet wallet = walletActivityViewModel.wallet.getValue();
- final Map fees = viewModel.dynamicFees.getValue();
- final Coin amount = amountCalculatorLink.getAmount();
- if (amount != null && fees != null) {
- try {
- final Address dummy = wallet.currentReceiveAddress(); // won't be used, tx is never
- // committed
- final SendRequest sendRequest = viewModel.paymentIntent.mergeWithEditedValues(amount, dummy)
- .toSendRequest();
- sendRequest.signInputs = false;
- sendRequest.emptyWallet = viewModel.paymentIntent.mayEditAmount()
- && amount.equals(wallet.getBalance(BalanceType.AVAILABLE));
- sendRequest.feePerKb = fees.get(viewModel.feeCategory);
- wallet.completeTx(sendRequest);
- viewModel.dryrunTransaction = sendRequest.tx;
- } catch (final Exception x) {
- viewModel.dryrunException = x;
- }
- }
- }
- };
-
private void setState(final SendCoinsViewModel.State state) {
viewModel.state = state;
@@ -902,6 +861,8 @@ private void updateView() {
final Map fees = viewModel.dynamicFees.getValue();
final BlockchainState blockchainState = application.blockchainState.getValue();
final Map addressBook = AddressBookEntry.asMap(viewModel.addressBook.getValue());
+ final Transaction dryrunTransaction = viewModel.dryrunTransaction.getValue();
+ final Exception dryrunException = viewModel.dryrunException.getValue();
if (viewModel.paymentIntent != null) {
final MonetaryFormat btcFormat = config.getFormat();
@@ -995,26 +956,27 @@ else if (viewModel.validatedAddress.label != null)
hintView.setTextColor(activity.getColor(R.color.fg_error));
hintView.setVisibility(View.VISIBLE);
hintView.setText(R.string.send_coins_fragment_receiving_address_error);
- } else if (viewModel.dryrunException != null) {
+ } else if (dryrunException != null) {
hintView.setTextColor(activity.getColor(R.color.fg_error));
hintView.setVisibility(View.VISIBLE);
- if (viewModel.dryrunException instanceof DustySendRequested)
+ if (dryrunException instanceof DustySendRequested)
hintView.setText(getString(R.string.send_coins_fragment_hint_dusty_send));
- else if (viewModel.dryrunException instanceof InsufficientMoneyException)
+ else if (dryrunException instanceof InsufficientMoneyException)
hintView.setText(getString(R.string.send_coins_fragment_hint_insufficient_money,
- btcFormat.format(((InsufficientMoneyException) viewModel.dryrunException).missing)));
- else if (viewModel.dryrunException instanceof CouldNotAdjustDownwards)
+ btcFormat.format(((InsufficientMoneyException) dryrunException).missing)));
+ else if (dryrunException instanceof CouldNotAdjustDownwards)
hintView.setText(getString(R.string.send_coins_fragment_hint_empty_wallet_failed));
else
- hintView.setText(viewModel.dryrunException.toString());
- } else if (viewModel.dryrunTransaction != null && viewModel.dryrunTransaction.getFee() != null) {
+ hintView.setText(dryrunException.toString());
+ } else if (dryrunTransaction != null && dryrunTransaction.getFee() != null) {
+ final FeeCategory feeCategory = viewModel.feeCategory.getValue();
hintView.setVisibility(View.VISIBLE);
final int hintResId;
final int colorResId;
- if (viewModel.feeCategory == FeeCategory.ECONOMIC) {
+ if (feeCategory == FeeCategory.ECONOMIC) {
hintResId = R.string.send_coins_fragment_hint_fee_economic;
colorResId = R.color.fg_less_significant;
- } else if (viewModel.feeCategory == FeeCategory.PRIORITY) {
+ } else if (feeCategory == FeeCategory.PRIORITY) {
hintResId = R.string.send_coins_fragment_hint_fee_priority;
colorResId = R.color.fg_less_significant;
} else {
@@ -1022,7 +984,7 @@ else if (viewModel.dryrunException instanceof CouldNotAdjustDownwards)
colorResId = R.color.fg_insignificant;
}
hintView.setTextColor(activity.getColor(colorResId));
- hintView.setText(getString(hintResId, btcFormat.format(viewModel.dryrunTransaction.getFee())));
+ hintView.setText(getString(hintResId, btcFormat.format(dryrunTransaction.getFee())));
} else if (viewModel.paymentIntent.mayEditAddress() && viewModel.validatedAddress != null
&& wallet != null && wallet.isAddressMine(viewModel.validatedAddress.address)) {
hintView.setTextColor(activity.getColor(R.color.fg_insignificant));
@@ -1050,11 +1012,13 @@ else if (viewModel.dryrunException instanceof CouldNotAdjustDownwards)
directPaymentMessageView.setVisibility(View.GONE);
}
- viewCancel.setEnabled(viewModel.state != SendCoinsViewModel.State.REQUEST_PAYMENT_REQUEST
+ final boolean viewCancelEnabled = viewModel.state != SendCoinsViewModel.State.REQUEST_PAYMENT_REQUEST
&& viewModel.state != SendCoinsViewModel.State.DECRYPTING
- && viewModel.state != SendCoinsViewModel.State.SIGNING);
- viewGo.setEnabled(everythingPlausible() && viewModel.dryrunTransaction != null && wallet != null
- && fees != null && (blockchainState == null || !blockchainState.replaying));
+ && viewModel.state != SendCoinsViewModel.State.SIGNING;
+ viewCancel.setEnabled(viewCancelEnabled);
+ final boolean viewGoEnabled = everythingPlausible() && dryrunTransaction != null && wallet != null
+ && fees != null && (blockchainState == null || !blockchainState.replaying);
+ viewGo.setEnabled(viewGoEnabled);
if (viewModel.state == null || viewModel.state == SendCoinsViewModel.State.REQUEST_PAYMENT_REQUEST) {
viewCancel.setText(R.string.button_cancel);
@@ -1088,12 +1052,18 @@ else if (viewModel.dryrunException instanceof CouldNotAdjustDownwards)
// focus linking
final int activeAmountViewId = amountCalculatorLink.activeTextView().getId();
receivingAddressView.setNextFocusDownId(activeAmountViewId);
- receivingAddressView.setNextFocusForwardId(activeAmountViewId);
- amountCalculatorLink.setNextFocusId(
- privateKeyPasswordViewVisible ? R.id.send_coins_private_key_password : R.id.send_coins_go);
+ if (privateKeyPasswordViewVisible)
+ amountCalculatorLink.setNextFocusId(R.id.send_coins_private_key_password);
+ else if (viewGoEnabled)
+ amountCalculatorLink.setNextFocusId(R.id.send_coins_go);
+ else if (viewCancelEnabled)
+ amountCalculatorLink.setNextFocusId(R.id.send_coins_cancel);
+ else
+ amountCalculatorLink.setNextFocusId(View.NO_ID);
privateKeyPasswordView.setNextFocusUpId(activeAmountViewId);
privateKeyPasswordView.setNextFocusDownId(R.id.send_coins_go);
- privateKeyPasswordView.setNextFocusForwardId(R.id.send_coins_go);
+ viewCancel.setNextFocusUpId(
+ privateKeyPasswordViewVisible ? R.id.send_coins_private_key_password : activeAmountViewId);
viewGo.setNextFocusUpId(
privateKeyPasswordViewVisible ? R.id.send_coins_private_key_password : activeAmountViewId);
} else {
@@ -1108,7 +1078,7 @@ private void initStateFromIntentExtras(final Bundle extras) {
if (feeCategory != null) {
log.info("got fee category {}", feeCategory);
- viewModel.feeCategory = feeCategory;
+ viewModel.feeCategory.setValue(feeCategory);
}
updateStateFrom(paymentIntent);
@@ -1195,8 +1165,7 @@ private void updateStateFrom(final PaymentIntent paymentIntent) {
requestPaymentRequest();
else
// ask for permission to enable bluetooth
- startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE),
- REQUEST_CODE_ENABLE_BLUETOOTH_FOR_PAYMENT_REQUEST);
+ requestEnableBluetoothForPaymentRequestLauncher.launch(null);
} else if (paymentIntent.hasPaymentRequestUrl() && paymentIntent.isHttpPaymentRequestUrl()) {
requestPaymentRequest();
} else {
@@ -1204,19 +1173,37 @@ private void updateStateFrom(final PaymentIntent paymentIntent) {
receivingAddressView.setText(null);
amountCalculatorLink.setBtcAmount(paymentIntent.getAmount());
+ viewModel.amount.setValue(paymentIntent.getAmount());
if (paymentIntent.isBluetoothPaymentUrl())
- directPaymentEnableView.setChecked(bluetoothAdapter != null && bluetoothAdapter.isEnabled());
+ directPaymentEnableView.setChecked(bluetoothAdapter != null && bluetoothAdapter.isEnabled() && checkBluetoothConnectPermission());
else if (paymentIntent.isHttpPaymentUrl())
directPaymentEnableView.setChecked(true);
requestFocusFirst();
updateView();
- handler.post(dryrunRunnable);
+ viewModel.maybeDryrun();
}
});
}
+ private boolean checkBluetoothConnectPermission() {
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ContextCompat.checkSelfPermission(activity,
+ Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void maybeEnableBluetooth() {
+ if (!checkBluetoothConnectPermission()) {
+ log.info("missing {}, requesting", Manifest.permission.BLUETOOTH_CONNECT);
+ requestPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT);
+ } else if (!bluetoothAdapter.isEnabled()) {
+ log.info("bluetooth disabled, requesting to enable");
+ requestEnableBluetoothForDirectPaymentLauncher.launch(null);
+ } else {
+ log.info("bluetooth enabled, ready to connect");
+ }
+ }
+
private void requestPaymentRequest() {
final String paymentRequestHost;
if (!Bluetooth.isBluetoothUrl(viewModel.paymentIntent.paymentRequestUrl))
@@ -1239,7 +1226,6 @@ public void onPaymentIntent(final PaymentIntent paymentIntent) {
setState(SendCoinsViewModel.State.INPUT);
updateStateFrom(paymentIntent);
updateView();
- handler.post(dryrunRunnable);
} else {
final List reasons = new LinkedList<>();
if (!viewModel.paymentIntent.equalsAddress(paymentIntent))
@@ -1284,4 +1270,40 @@ public void onFail(final int messageResId, final Object... messageArgs) {
new RequestPaymentRequestTask.BluetoothRequestTask(backgroundHandler, callback, bluetoothAdapter)
.requestPaymentRequest(viewModel.paymentIntent.paymentRequestUrl);
}
+
+ // from BitcoinIntegration.java:
+
+ private static final String INTENT_EXTRA_PAYMENTREQUEST = "paymentrequest";
+ private static final String INTENT_EXTRA_PAYMENT = "payment";
+ private static final String INTENT_EXTRA_TRANSACTION_HASH = "transaction_hash";
+
+ /**
+ * Get payment request from intent. Meant for usage by applications accepting payment requests.
+ *
+ * @param intent intent
+ * @return payment request or null
+ */
+ private static byte[] paymentRequestFromIntent(final Intent intent) {
+ return intent.getByteArrayExtra(INTENT_EXTRA_PAYMENTREQUEST);
+ }
+
+ /**
+ * Put BIP70 payment message into result intent. Meant for usage by Groestlcoin wallet applications.
+ *
+ * @param result result intent
+ * @param payment payment message
+ */
+ private static void paymentToResult(final Intent result, final byte[] payment) {
+ result.putExtra(INTENT_EXTRA_PAYMENT, payment);
+ }
+
+ /**
+ * Put transaction hash into result intent. Meant for usage by Groestlcoin wallet applications.
+ *
+ * @param result result intent
+ * @param txHash transaction hash
+ */
+ private static void transactionHashToResult(final Intent result, final String txHash) {
+ result.putExtra(INTENT_EXTRA_TRANSACTION_HASH, txHash);
+ }
}
diff --git a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsViewModel.java b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsViewModel.java
index 267d5f63c0..c694420f61 100644
--- a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsViewModel.java
+++ b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsViewModel.java
@@ -21,7 +21,9 @@
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
+import de.schildbach.wallet.Constants;
import de.schildbach.wallet.WalletApplication;
import de.schildbach.wallet.addressbook.AddressBookDatabase;
import de.schildbach.wallet.addressbook.AddressBookEntry;
@@ -31,10 +33,18 @@
import de.schildbach.wallet.data.TransactionLiveData;
import de.schildbach.wallet.data.WalletBalanceLiveData;
import de.schildbach.wallet.ui.AddressAndLabel;
+import org.bitcoinj.core.Address;
+import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
+import org.bitcoinj.utils.ContextPropagatingThreadFactory;
+import org.bitcoinj.wallet.SendRequest;
+import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.Wallet.BalanceType;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/**
* @author Andreas Schildbach
@@ -47,9 +57,11 @@ public enum State {
}
private final WalletApplication application;
+ private final Wallet wallet;
public final LiveData> addressBook;
public final SelectedExchangeRateLiveData exchangeRate;
public final DynamicFeeLiveData dynamicFees;
+ public final MutableLiveData feeCategory = new MutableLiveData<>(FeeCategory.NORMAL);
public final WalletBalanceLiveData balance;
public final MutableLiveData progress = new MutableLiveData<>();
public final TransactionLiveData sentTransaction;
@@ -58,23 +70,63 @@ public enum State {
public State state = null;
@Nullable
public PaymentIntent paymentIntent = null;
- public FeeCategory feeCategory = FeeCategory.NORMAL;
@Nullable
public AddressAndLabel validatedAddress = null;
@Nullable
- public Boolean directPaymentAck = null;
- @Nullable
- public Transaction dryrunTransaction = null;
+ public final MutableLiveData amount = new MutableLiveData<>(); // MAX_MONEY means available balance
+ public final MediatorLiveData visibleAmount = new MediatorLiveData<>();
@Nullable
- public Exception dryrunException = null;
+ public Boolean directPaymentAck = null;
+ public MutableLiveData dryrunTransaction = new MutableLiveData<>();
+ public MutableLiveData dryrunException = new MutableLiveData<>();
+
+ private final ExecutorService executor = Executors.newSingleThreadExecutor(
+ new ContextPropagatingThreadFactory("send"));
public SendCoinsViewModel(final Application application) {
super(application);
this.application = (WalletApplication) application;
+ this.wallet = this.application.getWallet();
this.addressBook = AddressBookDatabase.getDatabase(this.application).addressBookDao().getAll();
this.exchangeRate = new SelectedExchangeRateLiveData(this.application);
this.dynamicFees = new DynamicFeeLiveData(this.application);
this.balance = new WalletBalanceLiveData(this.application, BalanceType.AVAILABLE);
this.sentTransaction = new TransactionLiveData(this.application);
+ this.visibleAmount.addSource(amount, amount -> amountOrBalanceChanged());
+ this.visibleAmount.addSource(balance, balance -> amountOrBalanceChanged());
+
+ dynamicFees.observeForever(dynamicFees -> maybeDryrun());
+ feeCategory.observeForever(feeCategory -> maybeDryrun());
+ }
+
+ private void amountOrBalanceChanged() {
+ final Coin amount = this.amount.getValue();
+ if (amount != null && amount.equals(Constants.NETWORK_PARAMETERS.getMaxMoney()))
+ visibleAmount.setValue(this.balance.getValue());
+ else
+ visibleAmount.setValue(amount);
+ }
+
+ public void maybeDryrun() {
+ final Map fees = this.dynamicFees.getValue();
+ final Coin amount = this.amount.getValue();
+ dryrunTransaction.setValue(null);
+ dryrunException.setValue(null);
+ if (state == State.INPUT && amount != null && fees != null) {
+ final Address dummy = wallet.currentReceiveAddress(); // won't be used, tx is never committed
+ final SendRequest sendRequest = paymentIntent.mergeWithEditedValues(amount, dummy).toSendRequest();
+ sendRequest.signInputs = false;
+ sendRequest.emptyWallet =
+ paymentIntent.mayEditAmount() && amount.equals(Constants.NETWORK_PARAMETERS.getMaxMoney());
+ sendRequest.feePerKb = fees.get(feeCategory.getValue());
+ executor.execute(() -> {
+ try {
+ wallet.completeTx(sendRequest);
+ dryrunTransaction.postValue(sendRequest.tx);
+ } catch (final Exception x) {
+ dryrunException.postValue(x);
+ }
+ });
+ }
}
}
diff --git a/wallet/src/de/schildbach/wallet/ui/send/SweepWalletActivity.java b/wallet/src/de/schildbach/wallet/ui/send/SweepWalletActivity.java
index da2f59707e..0321c6112c 100644
--- a/wallet/src/de/schildbach/wallet/ui/send/SweepWalletActivity.java
+++ b/wallet/src/de/schildbach/wallet/ui/send/SweepWalletActivity.java
@@ -37,7 +37,7 @@ public static void start(final Context context) {
public static void start(final Context context, final PrefixedChecksummedBytes key) {
final Intent intent = new Intent(context, SweepWalletActivity.class);
- intent.putExtra(INTENT_EXTRA_KEY, key);
+ intent.putExtra(INTENT_EXTRA_KEY, key.toString());
context.startActivity(intent);
}
diff --git a/wallet/src/de/schildbach/wallet/ui/send/SweepWalletFragment.java b/wallet/src/de/schildbach/wallet/ui/send/SweepWalletFragment.java
index 89716aa77f..86b75ff54c 100644
--- a/wallet/src/de/schildbach/wallet/ui/send/SweepWalletFragment.java
+++ b/wallet/src/de/schildbach/wallet/ui/send/SweepWalletFragment.java
@@ -17,7 +17,6 @@
package de.schildbach.wallet.ui.send;
-import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -36,7 +35,9 @@
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
+import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
+import androidx.core.view.MenuProvider;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
@@ -56,6 +57,8 @@
import de.schildbach.wallet.ui.TransactionsAdapter;
import de.schildbach.wallet.ui.scan.ScanActivity;
import de.schildbach.wallet.util.MonetarySpannable;
+import de.schildbach.wallet.util.Toast;
+import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey;
@@ -86,7 +89,7 @@
import java.util.Set;
import java.util.TreeSet;
-import static androidx.core.util.Preconditions.checkState;
+import static com.google.common.base.Preconditions.checkState;
/**
* @author Andreas Schildbach
@@ -112,16 +115,40 @@ public class SweepWalletFragment extends Fragment {
private Button viewGo;
private Button viewCancel;
- private MenuItem reloadAction;
- private MenuItem scanAction;
-
private AbstractWalletActivityViewModel walletActivityViewModel;
private SweepWalletViewModel viewModel;
- private static final int REQUEST_CODE_SCAN = 0;
-
private static final Logger log = LoggerFactory.getLogger(SweepWalletFragment.class);
+ private final ActivityResultLauncher scanLauncher =
+ registerForActivityResult(new ScanActivity.Scan(), input -> {
+ if (input == null) return;
+ new StringInputParser(input) {
+ @Override
+ protected void handlePrivateKey(final PrefixedChecksummedBytes key) {
+ viewModel.privateKeyToSweep.setValue(key);
+ viewModel.state.setValue(SweepWalletViewModel.State.DECODE_KEY);
+ maybeDecodeKey();
+ }
+
+ @Override
+ protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
+ cannotClassify(input);
+ }
+
+ @Override
+ protected void handleDirectTransaction(final Transaction transaction) throws VerificationException {
+ cannotClassify(input);
+ }
+
+ @Override
+ protected void error(final int messageResId, final Object... messageArgs) {
+ viewModel.showDialog.setValue(DialogEvent.dialog(R.string.button_scan,
+ messageResId, messageArgs));
+ }
+ }.parse();
+ });
+
@Override
public void onAttach(final Context context) {
super.onAttach(context);
@@ -135,14 +162,13 @@ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.fragmentManager = getChildFragmentManager();
- setHasOptionsMenu(true);
-
if (!Constants.ENABLE_SWEEP_WALLET)
throw new IllegalStateException("ENABLE_SWEEP_WALLET is disabled");
walletActivityViewModel = new ViewModelProvider(activity).get(AbstractWalletActivityViewModel.class);
walletActivityViewModel.wallet.observe(this, wallet -> updateView());
viewModel = new ViewModelProvider(this).get(SweepWalletViewModel.class);
+ viewModel.state.observe(this, state -> updateView());
viewModel.getDynamicFees().observe(this, dynamicFees -> updateView());
viewModel.progress.observe(this, new ProgressDialogFragment.Observer(fragmentManager));
viewModel.privateKeyToSweep.observe(this, privateKeyToSweep -> updateView());
@@ -161,16 +187,17 @@ public void onCreate(final Bundle savedInstanceState) {
balanceView.setVisibility(View.GONE);
}
updateView();
+ activity.invalidateOptionsMenu();
});
viewModel.sentTransaction.observe(this, transaction -> {
- if (viewModel.state == SweepWalletViewModel.State.SENDING) {
+ if (viewModel.state.getValue() == SweepWalletViewModel.State.SENDING) {
final TransactionConfidence confidence = transaction.getConfidence();
final ConfidenceType confidenceType = confidence.getConfidenceType();
final int numBroadcastPeers = confidence.numBroadcastPeers();
if (confidenceType == ConfidenceType.DEAD)
- setState(SweepWalletViewModel.State.FAILED);
+ viewModel.state.setValue(SweepWalletViewModel.State.FAILED);
else if (numBroadcastPeers > 1 || confidenceType == ConfidenceType.BUILDING)
- setState(SweepWalletViewModel.State.SENT);
+ viewModel.state.setValue(SweepWalletViewModel.State.SENT);
}
updateView();
});
@@ -187,12 +214,48 @@ protected void onBuildButtons(final DialogBuilder dialog) {
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
+ activity.addMenuProvider(new MenuProvider() {
+ @Override
+ public void onCreateMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.sweep_wallet_fragment_options, menu);
+ }
+
+ @Override
+ public void onPrepareMenu(final Menu menu) {
+ final SweepWalletViewModel.State state = viewModel.state.getValue();
+ final PackageManager pm = activity.getPackageManager();
+ final MenuItem reloadAction = menu.findItem(R.id.sweep_wallet_options_reload);
+ reloadAction.setEnabled(state == SweepWalletViewModel.State.CONFIRM_SWEEP && viewModel.walletToSweep.getValue() != null);
+ final MenuItem scanAction = menu.findItem(R.id.sweep_wallet_options_scan);
+ scanAction.setVisible(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT));
+ scanAction.setEnabled(state == SweepWalletViewModel.State.DECODE_KEY || state == SweepWalletViewModel.State.CONFIRM_SWEEP);
+ }
+
+ @Override
+ public boolean onMenuItemSelected(final MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == R.id.sweep_wallet_options_reload) {
+ handleReload();
+ return true;
+ } else if (itemId == R.id.sweep_wallet_options_scan) {
+ scanLauncher.launch(null);
+ return true;
+ }
+ return false;
+ }
+ });
+
if (savedInstanceState == null) {
final Intent intent = activity.getIntent();
if (intent.hasExtra(SweepWalletActivity.INTENT_EXTRA_KEY)) {
- final PrefixedChecksummedBytes privateKeyToSweep = (PrefixedChecksummedBytes) intent
- .getSerializableExtra(SweepWalletActivity.INTENT_EXTRA_KEY);
+ final String encodedKey = intent.getStringExtra(SweepWalletActivity.INTENT_EXTRA_KEY);
+ PrefixedChecksummedBytes privateKeyToSweep;
+ try {
+ privateKeyToSweep = DumpedPrivateKey.fromBase58(Constants.NETWORK_PARAMETERS, encodedKey);
+ } catch (AddressFormatException x) {
+ privateKeyToSweep = BIP38PrivateKey.fromBase58(Constants.NETWORK_PARAMETERS, encodedKey);
+ }
viewModel.privateKeyToSweep.setValue(privateKeyToSweep);
// delay until fragment is resumed
@@ -224,9 +287,10 @@ public View onCreateView(final LayoutInflater inflater, final ViewGroup containe
viewGo = view.findViewById(R.id.send_coins_go);
viewGo.setOnClickListener(v -> {
- if (viewModel.state == SweepWalletViewModel.State.DECODE_KEY)
+ final SweepWalletViewModel.State state = viewModel.state.getValue();
+ if (state == SweepWalletViewModel.State.DECODE_KEY)
handleDecrypt();
- if (viewModel.state == SweepWalletViewModel.State.CONFIRM_SWEEP)
+ if (state == SweepWalletViewModel.State.CONFIRM_SWEEP)
handleSweep();
});
@@ -242,67 +306,6 @@ public void onDestroy() {
super.onDestroy();
}
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
- if (requestCode == REQUEST_CODE_SCAN) {
- if (resultCode == Activity.RESULT_OK) {
- final String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
-
- new StringInputParser(input) {
- @Override
- protected void handlePrivateKey(final PrefixedChecksummedBytes key) {
- viewModel.privateKeyToSweep.setValue(key);
- setState(SweepWalletViewModel.State.DECODE_KEY);
- maybeDecodeKey();
- }
-
- @Override
- protected void handlePaymentIntent(final PaymentIntent paymentIntent) {
- cannotClassify(input);
- }
-
- @Override
- protected void handleDirectTransaction(final Transaction transaction) throws VerificationException {
- cannotClassify(input);
- }
-
- @Override
- protected void error(final int messageResId, final Object... messageArgs) {
- viewModel.showDialog.setValue(DialogEvent.dialog(R.string.button_scan,
- messageResId, messageArgs));
- }
- }.parse();
- }
- }
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- inflater.inflate(R.menu.sweep_wallet_fragment_options, menu);
-
- reloadAction = menu.findItem(R.id.sweep_wallet_options_reload);
- scanAction = menu.findItem(R.id.sweep_wallet_options_scan);
-
- final PackageManager pm = activity.getPackageManager();
- scanAction.setVisible(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
- || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT));
-
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- int itemId = item.getItemId();
- if (itemId == R.id.sweep_wallet_options_reload) {
- handleReload();
- return true;
- } else if (itemId == R.id.sweep_wallet_options_scan) {
- ScanActivity.startForResult(this, activity, REQUEST_CODE_SCAN);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
private void handleReload() {
if (viewModel.walletToSweep.getValue() == null)
return;
@@ -312,13 +315,18 @@ private void handleReload() {
private final Runnable maybeDecodeKeyRunnable = () -> maybeDecodeKey();
private void maybeDecodeKey() {
- checkState(viewModel.state == SweepWalletViewModel.State.DECODE_KEY);
+ checkState(viewModel.state.getValue() == SweepWalletViewModel.State.DECODE_KEY);
final PrefixedChecksummedBytes privateKeyToSweep = viewModel.privateKeyToSweep.getValue();
checkState(privateKeyToSweep != null);
if (privateKeyToSweep instanceof DumpedPrivateKey) {
- final ECKey key = ((DumpedPrivateKey) privateKeyToSweep).getKey();
- askConfirmSweep(key);
+ try {
+ final ECKey key = ((DumpedPrivateKey) privateKeyToSweep).getKey();
+ askConfirmSweep(key);
+ } catch (final IllegalArgumentException x) {
+ log.info("failed decoding dumped private key", x);
+ new Toast(activity).longToast(R.string.sweep_wallet_fragment_decode_failed);
+ }
} else if (privateKeyToSweep instanceof BIP38PrivateKey) {
badPasswordView.setVisibility(View.INVISIBLE);
@@ -359,7 +367,7 @@ private void askConfirmSweep(final ECKey key) {
walletToSweep.importKey(key);
viewModel.walletToSweep.setValue(walletToSweep);
- setState(SweepWalletViewModel.State.CONFIRM_SWEEP);
+ viewModel.state.setValue(SweepWalletViewModel.State.CONFIRM_SWEEP);
// delay until fragment is resumed
handler.post(requestWalletBalanceRunnable);
@@ -439,22 +447,17 @@ public void onFail(final int messageResId, final Object... messageArgs) {
new RequestWalletBalanceTask(backgroundHandler, callback).requestWalletBalance(activity.getAssets(), key);
}
- private void setState(final SweepWalletViewModel.State state) {
- viewModel.state = state;
-
- updateView();
- }
-
private void updateView() {
+ final SweepWalletViewModel.State state = viewModel.state.getValue();
final PrefixedChecksummedBytes privateKeyToSweep = viewModel.privateKeyToSweep.getValue();
final Wallet wallet = walletActivityViewModel.wallet.getValue();
final Map fees = viewModel.getDynamicFees().getValue();
final MonetaryFormat btcFormat = config.getFormat();
- if (viewModel.state == SweepWalletViewModel.State.DECODE_KEY && privateKeyToSweep == null) {
+ if (state == SweepWalletViewModel.State.DECODE_KEY && privateKeyToSweep == null) {
messageView.setVisibility(View.VISIBLE);
messageView.setText(R.string.sweep_wallet_fragment_wallet_unknown);
- } else if (viewModel.state == SweepWalletViewModel.State.DECODE_KEY && privateKeyToSweep != null) {
+ } else if (state == SweepWalletViewModel.State.DECODE_KEY && privateKeyToSweep != null) {
messageView.setVisibility(View.VISIBLE);
messageView.setText(R.string.sweep_wallet_fragment_encrypted);
} else if (privateKeyToSweep != null) {
@@ -462,12 +465,10 @@ private void updateView() {
}
passwordViewGroup.setVisibility(
- viewModel.state == SweepWalletViewModel.State.DECODE_KEY && privateKeyToSweep != null
- ? View.VISIBLE : View.GONE);
+ state == SweepWalletViewModel.State.DECODE_KEY && privateKeyToSweep != null ? View.VISIBLE : View.GONE);
hintView.setVisibility(
- viewModel.state == SweepWalletViewModel.State.DECODE_KEY && privateKeyToSweep == null
- ? View.VISIBLE : View.GONE);
+ state == SweepWalletViewModel.State.DECODE_KEY && privateKeyToSweep == null ? View.VISIBLE : View.GONE);
final Transaction sentTransaction = viewModel.sentTransaction.getValue();
if (sentTransaction != null) {
@@ -480,42 +481,34 @@ private void updateView() {
}
final Wallet walletToSweep = viewModel.walletToSweep.getValue();
- if (viewModel.state == SweepWalletViewModel.State.DECODE_KEY) {
+ if (state == SweepWalletViewModel.State.DECODE_KEY) {
viewCancel.setText(R.string.button_cancel);
viewGo.setText(R.string.sweep_wallet_fragment_button_decrypt);
viewGo.setEnabled(privateKeyToSweep != null);
- } else if (viewModel.state == SweepWalletViewModel.State.CONFIRM_SWEEP) {
+ } else if (state == SweepWalletViewModel.State.CONFIRM_SWEEP) {
viewCancel.setText(R.string.button_cancel);
viewGo.setText(R.string.sweep_wallet_fragment_button_sweep);
viewGo.setEnabled(wallet != null && walletToSweep != null
&& walletToSweep.getBalance(BalanceType.ESTIMATED).signum() > 0 && fees != null);
- } else if (viewModel.state == SweepWalletViewModel.State.PREPARATION) {
+ } else if (state == SweepWalletViewModel.State.PREPARATION) {
viewCancel.setText(R.string.button_cancel);
viewGo.setText(R.string.send_coins_preparation_msg);
viewGo.setEnabled(false);
- } else if (viewModel.state == SweepWalletViewModel.State.SENDING) {
+ } else if (state == SweepWalletViewModel.State.SENDING) {
viewCancel.setText(R.string.send_coins_fragment_button_back);
viewGo.setText(R.string.send_coins_sending_msg);
viewGo.setEnabled(false);
- } else if (viewModel.state == SweepWalletViewModel.State.SENT) {
+ } else if (state == SweepWalletViewModel.State.SENT) {
viewCancel.setText(R.string.send_coins_fragment_button_back);
viewGo.setText(R.string.send_coins_sent_msg);
viewGo.setEnabled(false);
- } else if (viewModel.state == SweepWalletViewModel.State.FAILED) {
+ } else if (state == SweepWalletViewModel.State.FAILED) {
viewCancel.setText(R.string.send_coins_fragment_button_back);
viewGo.setText(R.string.send_coins_failed_msg);
viewGo.setEnabled(false);
}
- viewCancel.setEnabled(viewModel.state != SweepWalletViewModel.State.PREPARATION);
-
- // enable actions
- if (reloadAction != null)
- reloadAction.setEnabled(
- viewModel.state == SweepWalletViewModel.State.CONFIRM_SWEEP && walletToSweep != null);
- if (scanAction != null)
- scanAction.setEnabled(viewModel.state == SweepWalletViewModel.State.DECODE_KEY
- || viewModel.state == SweepWalletViewModel.State.CONFIRM_SWEEP);
+ viewCancel.setEnabled(state != SweepWalletViewModel.State.PREPARATION);
}
private void handleDecrypt() {
@@ -523,7 +516,7 @@ private void handleDecrypt() {
}
private void handleSweep() {
- setState(SweepWalletViewModel.State.PREPARATION);
+ viewModel.state.setValue(SweepWalletViewModel.State.PREPARATION);
final Wallet wallet = walletActivityViewModel.wallet.getValue();
final Wallet walletToSweep = viewModel.walletToSweep.getValue();
@@ -535,7 +528,7 @@ private void handleSweep() {
@Override
protected void onSuccess(final Transaction transaction) {
viewModel.sentTransaction.setValue(transaction);
- setState(SweepWalletViewModel.State.SENDING);
+ viewModel.state.setValue(SweepWalletViewModel.State.SENDING);
final ListenableFuture future = walletActivityViewModel.broadcastTransaction(transaction);
future.addListener(() -> {
@@ -547,7 +540,7 @@ protected void onSuccess(final Transaction transaction) {
@Override
protected void onInsufficientMoney(@Nullable final Coin missing) {
- setState(SweepWalletViewModel.State.FAILED);
+ viewModel.state.setValue(SweepWalletViewModel.State.FAILED);
viewModel.showDialog.setValue(DialogEvent.warn(
R.string.sweep_wallet_fragment_insufficient_money_title,
R.string.sweep_wallet_fragment_insufficient_money_msg)
@@ -556,7 +549,7 @@ protected void onInsufficientMoney(@Nullable final Coin missing) {
@Override
protected void onEmptyWalletFailed() {
- setState(SweepWalletViewModel.State.FAILED);
+ viewModel.state.setValue(SweepWalletViewModel.State.FAILED);
viewModel.showDialog.setValue(DialogEvent.warn(
R.string.sweep_wallet_fragment_insufficient_money_title,
R.string.sweep_wallet_fragment_insufficient_money_msg)
@@ -565,7 +558,7 @@ protected void onEmptyWalletFailed() {
@Override
protected void onFailure(final Exception exception) {
- setState(SweepWalletViewModel.State.FAILED);
+ viewModel.state.setValue(SweepWalletViewModel.State.FAILED);
viewModel.showDialog.setValue(DialogEvent.warn(0, R.string.send_coins_error_msg,
exception.toString())
);
diff --git a/wallet/src/de/schildbach/wallet/ui/send/SweepWalletViewModel.java b/wallet/src/de/schildbach/wallet/ui/send/SweepWalletViewModel.java
index cfec25f9ad..3ede9dd659 100644
--- a/wallet/src/de/schildbach/wallet/ui/send/SweepWalletViewModel.java
+++ b/wallet/src/de/schildbach/wallet/ui/send/SweepWalletViewModel.java
@@ -38,6 +38,7 @@ public enum State {
}
private final WalletApplication application;
+ public final MutableLiveData state = new MutableLiveData<>(State.DECODE_KEY);
private DynamicFeeLiveData dynamicFees;
public final MutableLiveData progress = new MutableLiveData<>();
public final MutableLiveData privateKeyToSweep = new MutableLiveData<>();
@@ -46,8 +47,6 @@ public enum State {
public final MutableLiveData showDialog = new MutableLiveData<>();
public final MutableLiveData showDialogWithRetryRequestBalance = new MutableLiveData<>();
- public State state = State.DECODE_KEY;
-
public SweepWalletViewModel(final Application application) {
super(application);
this.application = (WalletApplication) application;
diff --git a/wallet/src/de/schildbach/wallet/util/Base43.java b/wallet/src/de/schildbach/wallet/util/Base43.java
index 8320f31b14..b905a3b5f4 100644
--- a/wallet/src/de/schildbach/wallet/util/Base43.java
+++ b/wallet/src/de/schildbach/wallet/util/Base43.java
@@ -20,10 +20,10 @@
import java.util.Arrays;
/**
- * Base43, derived from bitcoinj Base58. It's meant to be used for efficiently stuffing binary data into QR
+ * Base43, derived from groestlcoinj Base58. It's meant to be used for efficiently stuffing binary data into QR
* codes. The alphabet is picked to match the 'Alphanumeric' input mode of QR codes as closely as possible,
* but at the same time be allowed in URIs.
- *
+ *
* @author Andreas Schildbach
*/
public class Base43 {
diff --git a/wallet/src/de/schildbach/wallet/util/Bluetooth.java b/wallet/src/de/schildbach/wallet/util/Bluetooth.java
index 9c40c3b7ba..df039d22a3 100644
--- a/wallet/src/de/schildbach/wallet/util/Bluetooth.java
+++ b/wallet/src/de/schildbach/wallet/util/Bluetooth.java
@@ -52,9 +52,13 @@ public class Bluetooth {
if (adapter == null)
return null;
- final String address = adapter.getAddress();
- if (!MARSHMELLOW_FAKE_MAC.equals(address))
- return address;
+ try {
+ final String address = adapter.getAddress();
+ if (!MARSHMELLOW_FAKE_MAC.equals(address))
+ return address;
+ } catch (final SecurityException x) {
+ log.info("Problem determining Bluetooth MAC via official API", x);
+ }
// Horrible reflection hack needed to get the Bluetooth MAC for Marshmellow and above.
try {
diff --git a/wallet/src/de/schildbach/wallet/util/CheatSheet.java b/wallet/src/de/schildbach/wallet/util/CheatSheet.java
deleted file mode 100644
index 7e52b3b492..0000000000
--- a/wallet/src/de/schildbach/wallet/util/CheatSheet.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package de.schildbach.wallet.util;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.text.TextUtils;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.Toast;
-
-/**
- * Helper class for showing cheat sheets (tooltips) for icon-only UI elements on long-press. This is already
- * default platform behavior for icon-only {@link android.app.ActionBar} items and tabs. This class provides
- * this behavior for any other such UI element.
- *
- *
- * Based on the original action bar implementation in
- * ActionMenuItemView.java.
- */
-public class CheatSheet {
- /**
- * The estimated height of a toast, in dips (density-independent pixels). This is used to determine
- * whether or not the toast should appear above or below the UI element.
- */
- private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48;
-
- /**
- * Sets up a cheat sheet (tooltip) for the given view by setting its
- * {@link android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with the
- * view's {@link android.view.View#getContentDescription() content description} will be shown either above
- * (default) or below the view (if there isn't room above it).
- *
- * @param view
- * The view to add a cheat sheet for.
- */
- public static void setup(View view) {
- view.setOnLongClickListener(v -> showCheatSheet(v, v.getContentDescription()));
- }
-
- /**
- * Sets up a cheat sheet (tooltip) for the given view by setting its
- * {@link android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with the
- * given text will be shown either above (default) or below the view (if there isn't room above it).
- *
- * @param view
- * The view to add a cheat sheet for.
- * @param textResId
- * The string resource containing the text to show on long-press.
- */
- public static void setup(View view, final int textResId) {
- view.setOnLongClickListener(v -> showCheatSheet(v, v.getContext().getString(textResId)));
- }
-
- /**
- * Sets up a cheat sheet (tooltip) for the given view by setting its
- * {@link android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with the
- * given text will be shown either above (default) or below the view (if there isn't room above it).
- *
- * @param view
- * The view to add a cheat sheet for.
- * @param text
- * The text to show on long-press.
- */
- public static void setup(View view, final CharSequence text) {
- view.setOnLongClickListener(v -> showCheatSheet(v, text));
- }
-
- /**
- * Removes the cheat sheet for the given view by removing the view's
- * {@link android.view.View.OnLongClickListener}.
- *
- * @param view
- * The view whose cheat sheet should be removed.
- */
- public static void remove(final View view) {
- view.setOnLongClickListener(null);
- }
-
- /**
- * Internal helper method to show the cheat sheet toast.
- */
- private static boolean showCheatSheet(View view, CharSequence text) {
- if (TextUtils.isEmpty(text)) {
- return false;
- }
-
- final int[] screenPos = new int[2]; // origin is device display
- final Rect displayFrame = new Rect(); // includes decorations (e.g. status bar)
- view.getLocationOnScreen(screenPos);
- view.getWindowVisibleDisplayFrame(displayFrame);
-
- final Context context = view.getContext();
- final int viewWidth = view.getWidth();
- final int viewHeight = view.getHeight();
- final int viewCenterX = screenPos[0] + viewWidth / 2;
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
- final int estimatedToastHeight = (int) (ESTIMATED_TOAST_HEIGHT_DIPS
- * context.getResources().getDisplayMetrics().density);
-
- Toast cheatSheet = Toast.makeText(context, text, Toast.LENGTH_SHORT);
- boolean showBelow = screenPos[1] < estimatedToastHeight;
- if (showBelow) {
- // Show below
- // Offsets are after decorations (e.g. status bar) are factored in
- cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, viewCenterX - screenWidth / 2,
- screenPos[1] - displayFrame.top + viewHeight);
- } else {
- // Show above
- // Offsets are after decorations (e.g. status bar) are factored in
- // NOTE: We can't use Gravity.BOTTOM because when the keyboard is up
- // its height isn't factored in.
- cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, viewCenterX - screenWidth / 2,
- screenPos[1] - displayFrame.top - estimatedToastHeight);
- }
-
- cheatSheet.show();
- return true;
- }
-}
diff --git a/wallet/src/de/schildbach/wallet/util/CrashReporter.java b/wallet/src/de/schildbach/wallet/util/CrashReporter.java
index c4ff7cd069..43f6199514 100644
--- a/wallet/src/de/schildbach/wallet/util/CrashReporter.java
+++ b/wallet/src/de/schildbach/wallet/util/CrashReporter.java
@@ -30,8 +30,8 @@
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.TimeZone;
@@ -93,9 +93,9 @@ public static void saveBackgroundTrace(final Throwable throwable, final PackageI
synchronized (backgroundTracesFile) {
try (final PrintWriter writer = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(backgroundTracesFile, true), StandardCharsets.UTF_8))) {
- final Calendar now = new GregorianCalendar(UTC);
- writer.println(String.format(Locale.US, "\n--- collected at %tF %tT %tZ on version %s (%d) ---\n", now,
- now, now, packageInfo.versionName, packageInfo.versionCode));
+ final String now = DateTimeFormatter.ISO_INSTANT.format(Instant.now());
+ writer.println(String.format(Locale.US, "\n--- collected at %s on version %s (%d) ---\n",
+ now, packageInfo.versionName, packageInfo.versionCode));
appendTrace(writer, throwable);
} catch (final IOException x) {
log.error("problem writing background trace", x);
diff --git a/wallet/src/de/schildbach/wallet/util/Crypto.java b/wallet/src/de/schildbach/wallet/util/Crypto.java
index 2fbff88559..8e2fece9e8 100644
--- a/wallet/src/de/schildbach/wallet/util/Crypto.java
+++ b/wallet/src/de/schildbach/wallet/util/Crypto.java
@@ -42,7 +42,7 @@
* This class encrypts and decrypts a string in a manner that is compatible with OpenSSL.
*
* If you encrypt a string with this class you can decrypt it with the OpenSSL command: openssl enc -d
- * -aes-256-cbc -a -in cipher.txt -out plain.txt -pass pass:aTestPassword
+ * -aes-256-cbc -md md5 -a -in cipher.txt -out plain.txt -pass pass:aTestPassword
*
* where: cipher.txt = file containing the cipher text plain.txt - where you want the plaintext to be saved
*
diff --git a/wallet/src/de/schildbach/wallet/util/Installer.java b/wallet/src/de/schildbach/wallet/util/Installer.java
index b6447a0de8..2a19766951 100644
--- a/wallet/src/de/schildbach/wallet/util/Installer.java
+++ b/wallet/src/de/schildbach/wallet/util/Installer.java
@@ -17,7 +17,6 @@
package de.schildbach.wallet.util;
-import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import okhttp3.HttpUrl;
@@ -56,17 +55,17 @@ public static Installer from(final Context context) {
return from(installerPackageName(context));
}
- public HttpUrl appStorePageFor(final Application application) {
+ public HttpUrl appStorePageFor(final Context context) {
final HttpUrl.Builder url;
if (this == F_DROID) {
url = HttpUrl.parse("https://f-droid.org/de/packages/").newBuilder();
- url.addPathSegment(application.getPackageName());
+ url.addPathSegment(context.getPackageName());
} else if (this == GOOGLE_PLAY || this == AURORA_STORE) {
url = HttpUrl.parse("https://play.google.com/store/apps/details").newBuilder();
- url.addQueryParameter("id", application.getPackageName());
+ url.addQueryParameter("id", context.getPackageName());
} else if (this == AMAZON_APPSTORE) {
url = HttpUrl.parse("https://www.amazon.com/gp/mas/dl/android").newBuilder();
- url.addQueryParameter("p", application.getPackageName());
+ url.addQueryParameter("p", context.getPackageName());
} else {
throw new IllegalStateException(this.toString());
}
diff --git a/wallet/src/de/schildbach/wallet/util/Iso8601Format.java b/wallet/src/de/schildbach/wallet/util/Iso8601Format.java
deleted file mode 100644
index 1a90562be5..0000000000
--- a/wallet/src/de/schildbach/wallet/util/Iso8601Format.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright the original author or authors.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package de.schildbach.wallet.util;
-
-import android.annotation.SuppressLint;
-
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-
-/**
- * @author Andreas Schildbach
- */
-@SuppressLint("SimpleDateFormat")
-public class Iso8601Format extends SimpleDateFormat {
- private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
-
- public Iso8601Format(final String formatString) {
- super(formatString, Locale.US);
- setTimeZone(UTC);
- }
-
- public static DateFormat newTimeFormat() {
- return new Iso8601Format("HH:mm:ss");
- }
-
- public static DateFormat newDateFormat() {
- return new Iso8601Format("yyyy-MM-dd");
- }
-
- public static DateFormat newDateTimeFormat() {
- return new Iso8601Format("yyyy-MM-dd HH:mm:ss");
- }
-
- public static String formatDateTime(final Date date) {
- return newDateTimeFormat().format(date);
- }
-
- public static Date parseDateTime(final String source) throws ParseException {
- return newDateTimeFormat().parse(source);
- }
-
- public static DateFormat newDateTimeFormatT() {
- return new Iso8601Format("yyyy-MM-dd'T'HH:mm:ss'Z'");
- }
-
- public static String formatDateTimeT(final Date date) {
- return newDateTimeFormatT().format(date);
- }
-
- public static Date parseDateTimeT(final String source) throws ParseException {
- return newDateTimeFormatT().parse(source);
- }
-}
diff --git a/wallet/src/de/schildbach/wallet/util/MonetarySpannable.java b/wallet/src/de/schildbach/wallet/util/MonetarySpannable.java
index 910e600235..06a3396cc4 100644
--- a/wallet/src/de/schildbach/wallet/util/MonetarySpannable.java
+++ b/wallet/src/de/schildbach/wallet/util/MonetarySpannable.java
@@ -29,7 +29,7 @@
import java.util.regex.Matcher;
-import static androidx.core.util.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkArgument;
/**
* @author Andreas Schildbach
diff --git a/wallet/src/de/schildbach/wallet/util/OnFirstPreDraw.java b/wallet/src/de/schildbach/wallet/util/OnFirstPreDraw.java
index a699467dd0..ccb17cf1ce 100644
--- a/wallet/src/de/schildbach/wallet/util/OnFirstPreDraw.java
+++ b/wallet/src/de/schildbach/wallet/util/OnFirstPreDraw.java
@@ -24,7 +24,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
-import static androidx.core.util.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Andreas Schildbach
diff --git a/wallet/translations.txt b/wallet/translations.txt
index 5c809c5b42..6ee41cfe23 100644
--- a/wallet/translations.txt
+++ b/wallet/translations.txt
@@ -22,4 +22,4 @@ mk
Биткоин -> Грoстлкоин
hebrew (he)
-גרוסטלקוין -> גרוסטלקוין // from right to left
+גרוסטלקוין -> ביטקוין // from right to left