From 4adbc901a003133bf77bc2a80331cddb4117dc66 Mon Sep 17 00:00:00 2001 From: fekt Date: Tue, 29 Nov 2022 20:49:44 -0500 Subject: [PATCH] Initial commit This initial commit includes HUSH specific changes starting at this commit: https://github.com/nullfekt/zcash-android-wallet/commit/d14637012c8ce8b8bc27bda979cc184c7e8f86c0 --- .run/_app_androidTest.run.xml | 53 + .run/app_androidTest.run.xml | 53 + .run/dependencyUpdates.run.xml | 23 + CHANGELOG.md | 208 ++ CONDUCT.md | 61 + CONTRIBUTING.md | 98 + LICENSE | 21 + README.md | 82 + app/.gitignore | 1 + app/build.gradle | 193 ++ app/proguard-rules.pro | 12 + .../java/cash/z/ecc/android/MemoTest.kt | 43 + .../android/integration/ConversionsTest.kt | 138 ++ .../android/integration/IntegrationTest.kt | 122 + .../z/ecc/android/integration/LockBoxTest.kt | 73 + .../android/preference/PreferenceKeysTest.kt | 41 + .../ecc/android/preference/PreferencesTest.kt | 46 + .../test/FragmentNavigationScenario.kt | 32 + .../z/ecc/android/test/UiTestPrerequisites.kt | 29 + .../AutoshieldingInformationFragmentTest.kt | 154 ++ app/src/main/AndroidManifest.xml | 43 + .../assets/saplingtree/mainnet/1225600.json | 7 + .../assets/saplingtree/mainnet/1250000.json | 7 + .../assets/saplingtree/mainnet/1290000.json | 7 + .../assets/saplingtree/mainnet/1300000.json | 7 + .../assets/saplingtree/mainnet/1335000.json | 7 + .../assets/saplingtree/testnet/1380300.json | 7 + .../assets/saplingtree/testnet/1450000.json | 7 + .../assets/saplingtree/testnet/1454000.json | 7 + app/src/main/assets/sound_receive_small.mp3 | Bin 0 -> 50225 bytes .../cash/z/ecc/android/StrictModeHelper.kt | 60 + .../java/cash/z/ecc/android/ZcashWalletApp.kt | 117 + .../z/ecc/android/di/DependenciesHolder.kt | 44 + .../z/ecc/android/di/InitializerComponent.kt | 14 + .../main/java/cash/z/ecc/android/ext/Const.kt | 53 + .../z/ecc/android/ext/CurrencyFormatter.kt | 76 + .../java/cash/z/ecc/android/ext/Dialogs.kt | 192 ++ .../java/cash/z/ecc/android/ext/EditText.kt | 83 + .../java/cash/z/ecc/android/ext/Extensions.kt | 69 + .../java/cash/z/ecc/android/ext/Fragment.kt | 9 + .../main/java/cash/z/ecc/android/ext/Int.kt | 46 + .../cash/z/ecc/android/ext/LifeCycleOwner.kt | 14 + .../java/cash/z/ecc/android/ext/Spannable.kt | 20 + .../main/java/cash/z/ecc/android/ext/View.kt | 81 + .../z/ecc/android/feedback/FeedbackConsole.kt | 22 + .../z/ecc/android/feedback/FeedbackFile.kt | 38 + .../cash/z/ecc/android/feedback/Report.kt | 255 ++ .../ecc/android/preference/PreferenceKeys.kt | 6 + .../z/ecc/android/preference/Preferences.kt | 12 + .../preference/SharedPreferenceFactory.kt | 9 + .../preference/model/BooleanDefaultValue.kt | 35 + .../android/preference/model/DefaultValue.kt | 23 + .../preference/model/LongDefaultValue.kt | 33 + .../cash/z/ecc/android/ui/MainActivity.kt | 669 ++++++ .../cash/z/ecc/android/ui/MainViewModel.kt | 36 + .../z/ecc/android/ui/base/BaseFragment.kt | 95 + .../ecc/android/ui/history/HistoryFragment.kt | 103 + .../android/ui/history/HistoryViewModel.kt | 179 ++ .../android/ui/history/TransactionAdapter.kt | 46 + .../android/ui/history/TransactionFragment.kt | 221 ++ .../ui/history/TransactionViewHolder.kt | 168 ++ .../ui/history/TransactionsDrawableFooter.kt | 52 + .../android/ui/history/TransactionsFooter.kt | 48 + .../home/AutoshieldingInformationFragment.kt | 69 + .../android/ui/home/BalanceDetailFragment.kt | 158 ++ .../android/ui/home/BalanceDetailViewModel.kt | 147 ++ .../z/ecc/android/ui/home/HomeFragment.kt | 596 +++++ .../z/ecc/android/ui/home/HomeViewModel.kt | 158 ++ .../z/ecc/android/ui/home/MagicSnakeLoader.kt | 155 ++ .../ecc/android/ui/profile/AwesomeFragment.kt | 288 +++ .../ecc/android/ui/profile/ProfileFragment.kt | 208 ++ .../android/ui/profile/ProfileViewModel.kt | 164 ++ .../android/ui/receive/ReceiveTabFragment.kt | 88 + .../android/ui/receive/ReceiveViewModel.kt | 18 + .../cash/z/ecc/android/ui/scan/QrAnalyzer.kt | 66 + .../z/ecc/android/ui/scan/ScanFragment.kt | 216 ++ .../z/ecc/android/ui/scan/ScanViewModel.kt | 29 + .../ecc/android/ui/send/AutoShieldFragment.kt | 236 ++ .../android/ui/send/AutoShieldViewModel.kt | 158 ++ .../android/ui/send/FundsAvailableFragment.kt | 37 + .../ecc/android/ui/send/SendFinalFragment.kt | 169 ++ .../z/ecc/android/ui/send/SendFragment.kt | 373 +++ .../z/ecc/android/ui/send/SendMemoFragment.kt | 121 + .../z/ecc/android/ui/send/SendViewModel.kt | 274 +++ .../android/ui/settings/SettingsFragment.kt | 151 ++ .../android/ui/settings/SettingsViewModel.kt | 88 + .../z/ecc/android/ui/setup/BackupFragment.kt | 158 ++ .../z/ecc/android/ui/setup/LandingFragment.kt | 216 ++ .../z/ecc/android/ui/setup/RestoreFragment.kt | 246 ++ .../z/ecc/android/ui/setup/SeedWordAdapter.kt | 103 + .../android/ui/setup/WalletSetupViewModel.kt | 233 ++ .../ui/tab_layout/TabLayoutFragment.kt | 144 ++ .../android/ui/tab_layout/ViewPagerAdapter.kt | 13 + .../android/ui/util/AddressPartNumberSpan.kt | 28 + .../z/ecc/android/ui/util/DebugFileTwig.kt | 22 + .../cash/z/ecc/android/ui/util/MemoUtil.kt | 54 + .../ecc/android/ui/util/PermissionFragment.kt | 47 + .../main/java/cash/z/ecc/android/util/Twig.kt | 200 ++ .../main/res/anim/anim_enter_from_bottom.xml | 9 + .../main/res/anim/anim_enter_from_left.xml | 9 + .../main/res/anim/anim_enter_from_right.xml | 9 + app/src/main/res/anim/anim_exit_to_left.xml | 10 + app/src/main/res/anim/anim_exit_to_right.xml | 10 + app/src/main/res/anim/anim_fade_in.xml | 6 + .../main/res/anim/anim_fade_in_scanner.xml | 6 + app/src/main/res/anim/anim_fade_out.xml | 6 + .../main/res/anim/anim_fade_out_address.xml | 6 + .../main/res/anim/anim_fade_out_medium.xml | 6 + .../res/color/selector_button_text_dark.xml | 5 + .../res/color/selector_button_text_light.xml | 5 + .../selector_button_text_light_dimmed.xml | 5 + .../selector_button_text_light_to_dimmed.xml | 5 + .../res/color/selector_feedback_button.xml | 6 + .../selector_primary_button_activatable.xml | 8 + .../selector_secondary_button_activatable.xml | 8 + .../main/res/drawable-anydpi/ic_settings.xml | 10 + .../drawable-v24/ic_launcher_background.xml | 23 + .../drawable-v24/ic_launcher_foreground.xml | 12 + ...round_balance_detail_amounts_container.xml | 6 + .../background_balance_details_total.xml | 5 + ...background_balance_details_transparent.xml | 5 + .../main/res/drawable/background_banner.xml | 7 + .../res/drawable/background_banner_large.xml | 7 + .../drawable/background_button_rounded.xml | 10 + .../main/res/drawable/background_circle.xml | 9 + .../res/drawable/background_circle_solid.xml | 9 + .../main/res/drawable/background_footer.xml | 29 + .../background_gradient_balance_details.xml | 8 + .../drawable/background_gradient_bottom.xml | 7 + .../main/res/drawable/background_header.xml | 27 + app/src/main/res/drawable/background_home.xml | 11 + .../drawable/background_indicator_failed.xml | 6 + .../drawable/background_indicator_inbound.xml | 10 + .../background_indicator_outbound.xml | 9 + .../drawable/background_indicator_unknown.xml | 9 + .../res/drawable/background_send_final.xml | 11 + .../res/drawable/background_title_primary.xml | 7 + app/src/main/res/drawable/bg_chip_view.xml | 7 + .../res/drawable/chip_details_background.xml | 7 + .../main/res/drawable/ic_account_circle.xml | 5 + app/src/main/res/drawable/ic_address_qr.xml | 138 ++ .../res/drawable/ic_arrow_back_black_24dp.xml | 9 + .../main/res/drawable/ic_background_qr.xml | 12 + .../main/res/drawable/ic_baseline_done_24.xml | 5 + .../ic_baseline_keyboard_arrow_down_24.xml | 5 + .../res/drawable/ic_baseline_launch_24.xml | 5 + app/src/main/res/drawable/ic_cancel.xml | 5 + app/src/main/res/drawable/ic_check_shield.xml | 14 + .../main/res/drawable/ic_check_shielded.xml | 12 + .../main/res/drawable/ic_close_black_24dp.xml | 9 + app/src/main/res/drawable/ic_content_copy.xml | 5 + app/src/main/res/drawable/ic_done_24dp.xml | 5 + .../res/drawable/ic_expand_memo_enabled.xml | 24 + app/src/main/res/drawable/ic_info_24dp.xml | 5 + app/src/main/res/drawable/ic_logo_landing.xml | 48 + app/src/main/res/drawable/ic_memo.xml | 12 + .../main/res/drawable/ic_profile_zebra_01.xml | 1252 ++++++++++ .../main/res/drawable/ic_profile_zebra_02.xml | 1252 ++++++++++ app/src/main/res/drawable/ic_qr_scan.xml | 163 ++ app/src/main/res/drawable/ic_qrcode_24dp.xml | 10 + app/src/main/res/drawable/ic_receipt_24dp.xml | 9 + app/src/main/res/drawable/ic_receive.xml | 5 + .../main/res/drawable/ic_receive_funds.xml | 12 + app/src/main/res/drawable/ic_sadzebra.xml | 496 ++++ app/src/main/res/drawable/ic_scan_corner.xml | 12 + app/src/main/res/drawable/ic_scan_frame.xml | 36 + app/src/main/res/drawable/ic_scan_overlay.xml | 48 + .../res/drawable/ic_scan_overlay_edited.xml | 44 + app/src/main/res/drawable/ic_shield.xml | 14 + .../main/res/drawable/ic_shield_address.xml | 166 ++ app/src/main/res/drawable/ic_shielded.xml | 12 + app/src/main/res/drawable/ic_warning_24dp.xml | 5 + .../main/res/drawable/ic_zcash_primary.xml | 1252 ++++++++++ app/src/main/res/drawable/ic_zcash_white.xml | 1252 ++++++++++ app/src/main/res/drawable/ic_zcashlogo.xml | 1252 ++++++++++ app/src/main/res/drawable/ic_zec_symbol.xml | 1252 ++++++++++ .../main/res/drawable/ic_zec_symbol_right.xml | 1252 ++++++++++ .../res/drawable/ripple_button_circle.xml | 8 + .../selector_pressed_ripple_circle.xml | 6 + app/src/main/res/font/inconsolata.ttf | Bin 0 -> 96964 bytes app/src/main/res/font/zboto.otf | Bin 0 -> 4988 bytes app/src/main/res/layout/chip_view.xml | 65 + .../main/res/layout/chip_view_filterable.xml | 50 + .../res/layout/dialog_first_use_message.xml | 28 + .../layout/dialog_solicit_feedback_rating.xml | 132 ++ .../main/res/layout/footer_transactions.xml | 6 + app/src/main/res/layout/fragment_address.xml | 19 + .../main/res/layout/fragment_auto_shield.xml | 168 ++ .../fragment_auto_shield_information.xml | 108 + app/src/main/res/layout/fragment_awesome.xml | 168 ++ app/src/main/res/layout/fragment_backup.xml | 390 ++++ .../res/layout/fragment_balance_detail.xml | 226 ++ app/src/main/res/layout/fragment_confirm.xml | 19 + .../res/layout/fragment_funds_available.xml | 100 + app/src/main/res/layout/fragment_history.xml | 224 ++ app/src/main/res/layout/fragment_home.xml | 470 ++++ app/src/main/res/layout/fragment_landing.xml | 84 + app/src/main/res/layout/fragment_memo.xml | 19 + app/src/main/res/layout/fragment_profile.xml | 252 ++ app/src/main/res/layout/fragment_restore.xml | 200 ++ app/src/main/res/layout/fragment_scan.xml | 224 ++ app/src/main/res/layout/fragment_send.xml | 530 +++++ .../main/res/layout/fragment_send_address.xml | 201 ++ .../main/res/layout/fragment_send_final.xml | 155 ++ .../main/res/layout/fragment_send_memo.xml | 235 ++ app/src/main/res/layout/fragment_settings.xml | 162 ++ .../main/res/layout/fragment_tab_layout.xml | 108 + .../layout/fragment_tab_receive_shielded.xml | 179 ++ .../main/res/layout/fragment_transaction.xml | 554 +++++ .../res/layout/fragment_wallet_import.xml | 19 + .../main/res/layout/fragment_wallet_new.xml | 19 + app/src/main/res/layout/item_transaction.xml | 141 ++ app/src/main/res/layout/main_activity.xml | 67 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3547 bytes .../mipmap-hdpi/ic_launcher_adaptive_back.png | Bin 0 -> 4137 bytes .../mipmap-hdpi/ic_launcher_adaptive_fore.png | Bin 0 -> 6102 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5510 bytes .../ic_launcher_round_adaptive_back.png | Bin 0 -> 4137 bytes .../ic_launcher_round_adaptive_fore.png | Bin 0 -> 6102 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2175 bytes .../mipmap-mdpi/ic_launcher_adaptive_back.png | Bin 0 -> 2199 bytes .../mipmap-mdpi/ic_launcher_adaptive_fore.png | Bin 0 -> 3246 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3423 bytes .../ic_launcher_round_adaptive_back.png | Bin 0 -> 2199 bytes .../ic_launcher_round_adaptive_fore.png | Bin 0 -> 3246 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5212 bytes .../ic_launcher_adaptive_back.png | Bin 0 -> 6513 bytes .../ic_launcher_adaptive_fore.png | Bin 0 -> 7693 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 8131 bytes .../ic_launcher_round_adaptive_back.png | Bin 0 -> 6513 bytes .../ic_launcher_round_adaptive_fore.png | Bin 0 -> 7693 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 8090 bytes .../ic_launcher_adaptive_back.png | Bin 0 -> 11194 bytes .../ic_launcher_adaptive_fore.png | Bin 0 -> 14732 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 12870 bytes .../ic_launcher_round_adaptive_back.png | Bin 0 -> 11194 bytes .../ic_launcher_round_adaptive_fore.png | Bin 0 -> 14732 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 12322 bytes .../ic_launcher_adaptive_back.png | Bin 0 -> 16737 bytes .../ic_launcher_adaptive_fore.png | Bin 0 -> 18635 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 18668 bytes .../ic_launcher_round_adaptive_back.png | Bin 0 -> 16737 bytes .../ic_launcher_round_adaptive_fore.png | Bin 0 -> 18635 bytes .../main/res/navigation/mobile_navigation.xml | 310 +++ .../main/res/raw/lottie_button_forever.json | 1 + .../main/res/raw/lottie_button_loading.json | 1 + .../res/raw/lottie_button_loading_new.json | 1 + app/src/main/res/raw/lottie_sending.json | 1 + app/src/main/res/raw/lottie_shielding.json | 1 + app/src/main/res/raw/lottie_success.json | 1 + .../res/values-es/custom_translations.xml | 16 + app/src/main/res/values-es/translated.xml | 74 + .../res/values-it/custom_translations.xml | 16 + app/src/main/res/values-it/translated.xml | 63 + .../res/values-ko/custom_translations.xml | 16 + app/src/main/res/values-ko/translated.xml | 74 + .../res/values-ru/custom_translations.xml | 16 + app/src/main/res/values-ru/translated.xml | 69 + .../res/values-zh/custom_translations.xml | 16 + app/src/main/res/values-zh/translated.xml | 69 + app/src/main/res/values/colors.xml | 91 + .../main/res/values/custom_translations.xml | 19 + app/src/main/res/values/dimens.xml | 10 + app/src/main/res/values/integer.xml | 6 + .../main/res/values/missing_translation.xml | 221 ++ .../res/values/missing_translation_urls.xml | 8 + app/src/main/res/values/strings-urls.xml | 4 + app/src/main/res/values/strings.xml | 293 +++ app/src/main/res/values/styles.xml | 102 + app/src/main/res/values/translated.xml | 75 + app/src/main/res/values/word_list_bip39.xml | 2053 +++++++++++++++++ app/src/main/res/xml/file_paths.xml | 5 + app/src/qa/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4779 bytes .../qa/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 6310 bytes app/src/qa/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3061 bytes .../qa/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3717 bytes app/src/qa/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7010 bytes .../qa/res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 9263 bytes app/src/qa/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11553 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 15478 bytes app/src/qa/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 16339 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 22654 bytes app/src/qa/res/values/colors.xml | 6 + app/src/qa/res/values/strings.xml | 3 + .../java/cash/z/ecc/android/ScratchPad.kt | 54 + .../cash/z/ecc/android/SendViewModelTest.kt | 112 + .../org.mockito.plugins.MockMaker | 1 + app/src/zcashmainnet/res/values/integers.xml | 4 + app/src/zcashmainnet/res/values/strings.xml | 5 + app/src/zcashtestnet/res/values/integers.xml | 4 + app/src/zcashtestnet/res/values/strings.xml | 6 + .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4779 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 6310 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3061 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3717 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7010 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 9263 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 11553 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 15478 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 16339 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 22654 bytes app/src/zcashtestnetQa/res/values/colors.xml | 6 + app/src/zcashtestnetQa/res/values/strings.xml | 3 + build-convention/build.gradle.kts | 23 + build-convention/buildscript-gradle.lockfile | 45 + build-convention/gradle.lockfile | 25 + build-convention/settings.gradle.kts | 31 + .../zcash.ktlint-conventions.gradle.kts | 44 + build.gradle.kts | 47 + buildSrc/build.gradle.kts | 15 + .../java/cash/z/ecc/android/Dependencies.kt | 117 + feedback/.gitignore | 1 + feedback/build.gradle | 43 + feedback/consumer-rules.pro | 0 feedback/proguard-rules.pro | 21 + feedback/src/main/AndroidManifest.xml | 2 + .../cash/z/ecc/android/feedback/Feedback.kt | 284 +++ .../android/feedback/FeedbackCoordinator.kt | 128 + .../ecc/android/feedback/util/CompositeJob.kt | 45 + .../ecc/android/feedback/CoroutineTestRule.kt | 31 + .../android/feedback/FeedbackObserverTest.kt | 55 + .../z/ecc/android/feedback/FeedbackTest.kt | 156 ++ gradle.properties | 19 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 240 ++ gradlew.bat | 91 + lockbox/.gitignore | 1 + lockbox/build.gradle | 49 + lockbox/consumer-rules.pro | 0 lockbox/proguard-rules.pro | 21 + .../cash/z/ecc/android/lockbox/LockBoxText.kt | 52 + lockbox/src/main/AndroidManifest.xml | 2 + .../cash/z/ecc/android/lockbox/LockBox.kt | 133 ++ mnemonic/.gitignore | 1 + mnemonic/build.gradle | 14 + .../cash/z/ecc/kotlin/mnemonic/MnemonicExt.kt | 18 + .../cash/z/ecc/kotlin/mnemonic/Mnemonics.kt | 25 + .../cash/z/ecc/android/util/MnemonicTest.kt | 90 + placeholder.keystore | Bin 0 -> 2473 bytes qrecycler/.gitignore | 1 + qrecycler/build.gradle | 39 + qrecycler/proguard-rules.pro | 21 + qrecycler/src/main/AndroidManifest.xml | 4 + .../cash/z/android/qrecycler/QRecycler.kt | 62 + .../java/cash/z/android/qrecycler/QScanner.kt | 8 + .../src/main/res/layout/texture_view.xml | 23 + qrecycler/src/main/res/values/attrs.xml | 58 + qrecycler/src/main/res/values/public.xml | 21 + qrecycler/src/main/res/values/strings.xml | 2 + qrecycler/src/main/res/values/styles.xml | 24 + responsible_disclosure.md | 1 + settings.gradle.kts | 53 + 355 files changed, 31799 insertions(+) create mode 100644 .run/_app_androidTest.run.xml create mode 100644 .run/app_androidTest.run.xml create mode 100644 .run/dependencyUpdates.run.xml create mode 100644 CHANGELOG.md create mode 100644 CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/cash/z/ecc/android/MemoTest.kt create mode 100644 app/src/androidTest/java/cash/z/ecc/android/integration/ConversionsTest.kt create mode 100644 app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt create mode 100644 app/src/androidTest/java/cash/z/ecc/android/integration/LockBoxTest.kt create mode 100644 app/src/androidTest/java/cash/z/ecc/android/preference/PreferenceKeysTest.kt create mode 100644 app/src/androidTest/java/cash/z/ecc/android/preference/PreferencesTest.kt create mode 100644 app/src/androidTest/java/cash/z/ecc/android/test/FragmentNavigationScenario.kt create mode 100644 app/src/androidTest/java/cash/z/ecc/android/test/UiTestPrerequisites.kt create mode 100644 app/src/androidTest/java/cash/z/ecc/android/ui/home/AutoshieldingInformationFragmentTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/assets/saplingtree/mainnet/1225600.json create mode 100644 app/src/main/assets/saplingtree/mainnet/1250000.json create mode 100644 app/src/main/assets/saplingtree/mainnet/1290000.json create mode 100644 app/src/main/assets/saplingtree/mainnet/1300000.json create mode 100644 app/src/main/assets/saplingtree/mainnet/1335000.json create mode 100644 app/src/main/assets/saplingtree/testnet/1380300.json create mode 100644 app/src/main/assets/saplingtree/testnet/1450000.json create mode 100644 app/src/main/assets/saplingtree/testnet/1454000.json create mode 100644 app/src/main/assets/sound_receive_small.mp3 create mode 100644 app/src/main/java/cash/z/ecc/android/StrictModeHelper.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt create mode 100644 app/src/main/java/cash/z/ecc/android/di/DependenciesHolder.kt create mode 100644 app/src/main/java/cash/z/ecc/android/di/InitializerComponent.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/Const.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/CurrencyFormatter.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/Dialogs.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/EditText.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/Extensions.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/Fragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/Int.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/LifeCycleOwner.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/Spannable.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ext/View.kt create mode 100644 app/src/main/java/cash/z/ecc/android/feedback/FeedbackConsole.kt create mode 100644 app/src/main/java/cash/z/ecc/android/feedback/FeedbackFile.kt create mode 100644 app/src/main/java/cash/z/ecc/android/feedback/Report.kt create mode 100644 app/src/main/java/cash/z/ecc/android/preference/PreferenceKeys.kt create mode 100644 app/src/main/java/cash/z/ecc/android/preference/Preferences.kt create mode 100644 app/src/main/java/cash/z/ecc/android/preference/SharedPreferenceFactory.kt create mode 100644 app/src/main/java/cash/z/ecc/android/preference/model/BooleanDefaultValue.kt create mode 100644 app/src/main/java/cash/z/ecc/android/preference/model/DefaultValue.kt create mode 100644 app/src/main/java/cash/z/ecc/android/preference/model/LongDefaultValue.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/MainViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/base/BaseFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/history/HistoryFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/history/HistoryViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/history/TransactionAdapter.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/history/TransactionFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/history/TransactionViewHolder.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/history/TransactionsDrawableFooter.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/history/TransactionsFooter.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/home/AutoshieldingInformationFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/home/HomeFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/home/HomeViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/home/MagicSnakeLoader.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/profile/AwesomeFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/profile/ProfileFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/profile/ProfileViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveTabFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/scan/ScanViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/send/FundsAvailableFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/send/SendFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/send/SendMemoFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/settings/SettingsFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/settings/SettingsViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/setup/RestoreFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/setup/SeedWordAdapter.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/tab_layout/TabLayoutFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/tab_layout/ViewPagerAdapter.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/util/AddressPartNumberSpan.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/util/DebugFileTwig.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/util/MemoUtil.kt create mode 100644 app/src/main/java/cash/z/ecc/android/ui/util/PermissionFragment.kt create mode 100644 app/src/main/java/cash/z/ecc/android/util/Twig.kt create mode 100644 app/src/main/res/anim/anim_enter_from_bottom.xml create mode 100644 app/src/main/res/anim/anim_enter_from_left.xml create mode 100644 app/src/main/res/anim/anim_enter_from_right.xml create mode 100644 app/src/main/res/anim/anim_exit_to_left.xml create mode 100644 app/src/main/res/anim/anim_exit_to_right.xml create mode 100644 app/src/main/res/anim/anim_fade_in.xml create mode 100644 app/src/main/res/anim/anim_fade_in_scanner.xml create mode 100644 app/src/main/res/anim/anim_fade_out.xml create mode 100644 app/src/main/res/anim/anim_fade_out_address.xml create mode 100644 app/src/main/res/anim/anim_fade_out_medium.xml create mode 100644 app/src/main/res/color/selector_button_text_dark.xml create mode 100644 app/src/main/res/color/selector_button_text_light.xml create mode 100644 app/src/main/res/color/selector_button_text_light_dimmed.xml create mode 100644 app/src/main/res/color/selector_button_text_light_to_dimmed.xml create mode 100644 app/src/main/res/color/selector_feedback_button.xml create mode 100644 app/src/main/res/color/selector_primary_button_activatable.xml create mode 100644 app/src/main/res/color/selector_secondary_button_activatable.xml create mode 100644 app/src/main/res/drawable-anydpi/ic_settings.xml create mode 100644 app/src/main/res/drawable-v24/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/background_balance_detail_amounts_container.xml create mode 100644 app/src/main/res/drawable/background_balance_details_total.xml create mode 100644 app/src/main/res/drawable/background_balance_details_transparent.xml create mode 100644 app/src/main/res/drawable/background_banner.xml create mode 100644 app/src/main/res/drawable/background_banner_large.xml create mode 100644 app/src/main/res/drawable/background_button_rounded.xml create mode 100644 app/src/main/res/drawable/background_circle.xml create mode 100644 app/src/main/res/drawable/background_circle_solid.xml create mode 100644 app/src/main/res/drawable/background_footer.xml create mode 100644 app/src/main/res/drawable/background_gradient_balance_details.xml create mode 100644 app/src/main/res/drawable/background_gradient_bottom.xml create mode 100644 app/src/main/res/drawable/background_header.xml create mode 100644 app/src/main/res/drawable/background_home.xml create mode 100644 app/src/main/res/drawable/background_indicator_failed.xml create mode 100644 app/src/main/res/drawable/background_indicator_inbound.xml create mode 100644 app/src/main/res/drawable/background_indicator_outbound.xml create mode 100644 app/src/main/res/drawable/background_indicator_unknown.xml create mode 100644 app/src/main/res/drawable/background_send_final.xml create mode 100644 app/src/main/res/drawable/background_title_primary.xml create mode 100644 app/src/main/res/drawable/bg_chip_view.xml create mode 100644 app/src/main/res/drawable/chip_details_background.xml create mode 100644 app/src/main/res/drawable/ic_account_circle.xml create mode 100644 app/src/main/res/drawable/ic_address_qr.xml create mode 100644 app/src/main/res/drawable/ic_arrow_back_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_background_qr.xml create mode 100644 app/src/main/res/drawable/ic_baseline_done_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_launch_24.xml create mode 100644 app/src/main/res/drawable/ic_cancel.xml create mode 100644 app/src/main/res/drawable/ic_check_shield.xml create mode 100644 app/src/main/res/drawable/ic_check_shielded.xml create mode 100644 app/src/main/res/drawable/ic_close_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_content_copy.xml create mode 100644 app/src/main/res/drawable/ic_done_24dp.xml create mode 100644 app/src/main/res/drawable/ic_expand_memo_enabled.xml create mode 100644 app/src/main/res/drawable/ic_info_24dp.xml create mode 100644 app/src/main/res/drawable/ic_logo_landing.xml create mode 100644 app/src/main/res/drawable/ic_memo.xml create mode 100644 app/src/main/res/drawable/ic_profile_zebra_01.xml create mode 100644 app/src/main/res/drawable/ic_profile_zebra_02.xml create mode 100644 app/src/main/res/drawable/ic_qr_scan.xml create mode 100644 app/src/main/res/drawable/ic_qrcode_24dp.xml create mode 100644 app/src/main/res/drawable/ic_receipt_24dp.xml create mode 100644 app/src/main/res/drawable/ic_receive.xml create mode 100644 app/src/main/res/drawable/ic_receive_funds.xml create mode 100644 app/src/main/res/drawable/ic_sadzebra.xml create mode 100644 app/src/main/res/drawable/ic_scan_corner.xml create mode 100644 app/src/main/res/drawable/ic_scan_frame.xml create mode 100644 app/src/main/res/drawable/ic_scan_overlay.xml create mode 100644 app/src/main/res/drawable/ic_scan_overlay_edited.xml create mode 100644 app/src/main/res/drawable/ic_shield.xml create mode 100644 app/src/main/res/drawable/ic_shield_address.xml create mode 100644 app/src/main/res/drawable/ic_shielded.xml create mode 100644 app/src/main/res/drawable/ic_warning_24dp.xml create mode 100644 app/src/main/res/drawable/ic_zcash_primary.xml create mode 100644 app/src/main/res/drawable/ic_zcash_white.xml create mode 100644 app/src/main/res/drawable/ic_zcashlogo.xml create mode 100644 app/src/main/res/drawable/ic_zec_symbol.xml create mode 100644 app/src/main/res/drawable/ic_zec_symbol_right.xml create mode 100644 app/src/main/res/drawable/ripple_button_circle.xml create mode 100644 app/src/main/res/drawable/selector_pressed_ripple_circle.xml create mode 100644 app/src/main/res/font/inconsolata.ttf create mode 100644 app/src/main/res/font/zboto.otf create mode 100644 app/src/main/res/layout/chip_view.xml create mode 100644 app/src/main/res/layout/chip_view_filterable.xml create mode 100644 app/src/main/res/layout/dialog_first_use_message.xml create mode 100644 app/src/main/res/layout/dialog_solicit_feedback_rating.xml create mode 100644 app/src/main/res/layout/footer_transactions.xml create mode 100644 app/src/main/res/layout/fragment_address.xml create mode 100644 app/src/main/res/layout/fragment_auto_shield.xml create mode 100644 app/src/main/res/layout/fragment_auto_shield_information.xml create mode 100644 app/src/main/res/layout/fragment_awesome.xml create mode 100644 app/src/main/res/layout/fragment_backup.xml create mode 100644 app/src/main/res/layout/fragment_balance_detail.xml create mode 100644 app/src/main/res/layout/fragment_confirm.xml create mode 100644 app/src/main/res/layout/fragment_funds_available.xml create mode 100644 app/src/main/res/layout/fragment_history.xml create mode 100644 app/src/main/res/layout/fragment_home.xml create mode 100644 app/src/main/res/layout/fragment_landing.xml create mode 100644 app/src/main/res/layout/fragment_memo.xml create mode 100644 app/src/main/res/layout/fragment_profile.xml create mode 100644 app/src/main/res/layout/fragment_restore.xml create mode 100644 app/src/main/res/layout/fragment_scan.xml create mode 100644 app/src/main/res/layout/fragment_send.xml create mode 100644 app/src/main/res/layout/fragment_send_address.xml create mode 100644 app/src/main/res/layout/fragment_send_final.xml create mode 100644 app/src/main/res/layout/fragment_send_memo.xml create mode 100644 app/src/main/res/layout/fragment_settings.xml create mode 100644 app/src/main/res/layout/fragment_tab_layout.xml create mode 100644 app/src/main/res/layout/fragment_tab_receive_shielded.xml create mode 100644 app/src/main/res/layout/fragment_transaction.xml create mode 100644 app/src/main/res/layout/fragment_wallet_import.xml create mode 100644 app/src/main/res/layout/fragment_wallet_new.xml create mode 100644 app/src/main/res/layout/item_transaction.xml create mode 100644 app/src/main/res/layout/main_activity.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_back.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_fore.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round_adaptive_back.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round_adaptive_fore.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_back.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_fore.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round_adaptive_back.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round_adaptive_fore.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_back.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round_adaptive_back.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round_adaptive_fore.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round_adaptive_back.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round_adaptive_fore.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_back.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_fore.png create mode 100644 app/src/main/res/navigation/mobile_navigation.xml create mode 100644 app/src/main/res/raw/lottie_button_forever.json create mode 100644 app/src/main/res/raw/lottie_button_loading.json create mode 100644 app/src/main/res/raw/lottie_button_loading_new.json create mode 100644 app/src/main/res/raw/lottie_sending.json create mode 100644 app/src/main/res/raw/lottie_shielding.json create mode 100644 app/src/main/res/raw/lottie_success.json create mode 100644 app/src/main/res/values-es/custom_translations.xml create mode 100644 app/src/main/res/values-es/translated.xml create mode 100644 app/src/main/res/values-it/custom_translations.xml create mode 100644 app/src/main/res/values-it/translated.xml create mode 100644 app/src/main/res/values-ko/custom_translations.xml create mode 100644 app/src/main/res/values-ko/translated.xml create mode 100644 app/src/main/res/values-ru/custom_translations.xml create mode 100644 app/src/main/res/values-ru/translated.xml create mode 100644 app/src/main/res/values-zh/custom_translations.xml create mode 100644 app/src/main/res/values-zh/translated.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/custom_translations.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/integer.xml create mode 100644 app/src/main/res/values/missing_translation.xml create mode 100644 app/src/main/res/values/missing_translation_urls.xml create mode 100644 app/src/main/res/values/strings-urls.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/values/translated.xml create mode 100644 app/src/main/res/values/word_list_bip39.xml create mode 100644 app/src/main/res/xml/file_paths.xml create mode 100644 app/src/qa/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/qa/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/qa/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/qa/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/qa/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/qa/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/qa/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/qa/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/qa/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/qa/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/qa/res/values/colors.xml create mode 100644 app/src/qa/res/values/strings.xml create mode 100644 app/src/test/java/cash/z/ecc/android/ScratchPad.kt create mode 100644 app/src/test/java/cash/z/ecc/android/SendViewModelTest.kt create mode 100644 app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 app/src/zcashmainnet/res/values/integers.xml create mode 100644 app/src/zcashmainnet/res/values/strings.xml create mode 100644 app/src/zcashtestnet/res/values/integers.xml create mode 100644 app/src/zcashtestnet/res/values/strings.xml create mode 100644 app/src/zcashtestnetQa/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/zcashtestnetQa/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/zcashtestnetQa/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/zcashtestnetQa/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/zcashtestnetQa/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/zcashtestnetQa/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/zcashtestnetQa/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/zcashtestnetQa/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/zcashtestnetQa/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/zcashtestnetQa/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/zcashtestnetQa/res/values/colors.xml create mode 100644 app/src/zcashtestnetQa/res/values/strings.xml create mode 100644 build-convention/build.gradle.kts create mode 100644 build-convention/buildscript-gradle.lockfile create mode 100644 build-convention/gradle.lockfile create mode 100644 build-convention/settings.gradle.kts create mode 100644 build-convention/src/main/kotlin/zcash.ktlint-conventions.gradle.kts create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt create mode 100644 feedback/.gitignore create mode 100644 feedback/build.gradle create mode 100644 feedback/consumer-rules.pro create mode 100644 feedback/proguard-rules.pro create mode 100644 feedback/src/main/AndroidManifest.xml create mode 100644 feedback/src/main/java/cash/z/ecc/android/feedback/Feedback.kt create mode 100644 feedback/src/main/java/cash/z/ecc/android/feedback/FeedbackCoordinator.kt create mode 100644 feedback/src/main/java/cash/z/ecc/android/feedback/util/CompositeJob.kt create mode 100644 feedback/src/test/java/cash/z/ecc/android/feedback/CoroutineTestRule.kt create mode 100644 feedback/src/test/java/cash/z/ecc/android/feedback/FeedbackObserverTest.kt create mode 100644 feedback/src/test/java/cash/z/ecc/android/feedback/FeedbackTest.kt create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 lockbox/.gitignore create mode 100644 lockbox/build.gradle create mode 100644 lockbox/consumer-rules.pro create mode 100644 lockbox/proguard-rules.pro create mode 100644 lockbox/src/androidTest/java/cash/z/ecc/android/lockbox/LockBoxText.kt create mode 100644 lockbox/src/main/AndroidManifest.xml create mode 100644 lockbox/src/main/java/cash/z/ecc/android/lockbox/LockBox.kt create mode 100644 mnemonic/.gitignore create mode 100644 mnemonic/build.gradle create mode 100644 mnemonic/src/main/java/cash/z/ecc/kotlin/mnemonic/MnemonicExt.kt create mode 100644 mnemonic/src/main/java/cash/z/ecc/kotlin/mnemonic/Mnemonics.kt create mode 100644 mnemonic/src/test/java/cash/z/ecc/android/util/MnemonicTest.kt create mode 100644 placeholder.keystore create mode 100644 qrecycler/.gitignore create mode 100644 qrecycler/build.gradle create mode 100644 qrecycler/proguard-rules.pro create mode 100644 qrecycler/src/main/AndroidManifest.xml create mode 100644 qrecycler/src/main/java/cash/z/android/qrecycler/QRecycler.kt create mode 100644 qrecycler/src/main/java/cash/z/android/qrecycler/QScanner.kt create mode 100644 qrecycler/src/main/res/layout/texture_view.xml create mode 100644 qrecycler/src/main/res/values/attrs.xml create mode 100644 qrecycler/src/main/res/values/public.xml create mode 100644 qrecycler/src/main/res/values/strings.xml create mode 100644 qrecycler/src/main/res/values/styles.xml create mode 100644 responsible_disclosure.md create mode 100644 settings.gradle.kts diff --git a/.run/_app_androidTest.run.xml b/.run/_app_androidTest.run.xml new file mode 100644 index 0000000..d001c28 --- /dev/null +++ b/.run/_app_androidTest.run.xml @@ -0,0 +1,53 @@ + + + + + \ No newline at end of file diff --git a/.run/app_androidTest.run.xml b/.run/app_androidTest.run.xml new file mode 100644 index 0000000..c781bd7 --- /dev/null +++ b/.run/app_androidTest.run.xml @@ -0,0 +1,53 @@ + + + + + \ No newline at end of file diff --git a/.run/dependencyUpdates.run.xml b/.run/dependencyUpdates.run.xml new file mode 100644 index 0000000..98b02f0 --- /dev/null +++ b/.run/dependencyUpdates.run.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e58fa4d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,208 @@ +Change Log +========== + +Version 1.0.0-alpha74 *(2021-06-21)* +------------------------------------ +- New: Added workflow for automatically shielding funds. +- New: Automatically recover from more network failure states. +- New: Link to play store from the build number. +- New: Hide available/total toggle when there are no pending funds. +- New: Easter Egg to access the testnet faucets. +- New: Updated checkpoints for mainnet and testnet. +- Fix: Expand tappable area for showing the balance details. +- Fix: Off by one error when calculating confirmations. +- Fix: Do not show time in transaction details for pending transactions. + +Version 1.0.0-alpha72 *(2021-06-07)* +------------------------------------ +- New: Address tabs with t-address support [Credit @herou]. +- New: Ktlint support [Credit @nighthawk24] +- New: Balance details screen [Credit @herou]. +- New: Better balance information around unmined transactions. +- New: Add toggle to show available v. total funds. +- New: Auto-shielding via balance details screen. +- New: 'Ask Later' on feedback popup. +- Fix: Repaired QR scanning on older devices (below API 24). +- Fix: Several of the most frequent crashes reported in bugsnag. +- Fix: Corrected over-sized icon in history. +- Fix: History no longer displays negative balance during initial sync. +- Fix: Errors that prevented sync from working in some situations. +- Fix: Improved support for smaller screens and older devices. + +Version 1.0.0-alpha67 *(2021-04-22)* +------------------------------------ +- Fix: Crash after entering 24th seed word on certain devices +- Fix: Correct errors on certain devices around biometrics. +- Fix: Display information when invalid addresses are scanned. +- Fix: Prevent memo for transparent transactions [Credit @mandeepbhalothia]. +- Fix: Security finding: remove aparent logging [Credit @mandeepbhalothia]. +- New: View more info for failed sends [Credit @herou]. +- New: Switch away from google libraries for QR code parsing [Credit @herou]. +- New: Ability to make testnet releases [Credit @herou]. +- New: Updated design for wallet history [Credit @mandeepbhalothia]. +- New: Added responsible disclosure document for vulnerabilities [Credit: @zebambam] +- New: Update to latest SDK changes, including using one library instead of two. +- New: Auto-shielding Easter Egg. +- New: Periodically solicit user feedback. +- New: Wallet restore improvements including 'clear' feature. +- New: Rescan/Wipe feature for fixing wallet errors +- New: Add ability to copy the contents of the memo. +- New: Add ability to paste and parse an entire seed phrase at once +- New: Basic support for QRs prefixed with zcash: +- New: Made it easier to add vibration and leveraged it whenever text is copied. +- New: Adds cleanup and removal of failed transactions. +- New: Improved logic for determining the wallet birthday. +- New: Capture performance metrics for scanning. +- New: Additional messaging during a scan for better responsiveness. +- New: Improved handling of critical errors. + +Version 1.0.0-alpha43 *(2020-12-20)* +------------------------------------ +- Fix: Repaired the upgrade flow, which could not reorg because of missing birthday height +- Fix: Repaired create wallet flow which was being covered by the loading screen + +Version 1.0.0-alpha42 *(2020-12-19)* +------------------------------------ +- Fix: Correct race condition when launching the app +- Fix: Display loading screen while waiting for app to initialize + +Version 1.0.0-alpha41 *(2020-12-19)* +------------------------------------ +- New: Upgrade to the latest SDK. +- New: Implements ZIP-313, reducing the default fee from 10,000 to 1,000 zats. +- New: Adds authentication prior to viewing backup seed words. +- New: Adds blockchair as the transaction explorer. +- Fix: Authentication bugs on older devices that were preventing sends and mishandling cancels. +- Fix: Users can now upgrade from seed-only prior versions without crashing or needing to restore. +- Fix: Improved internal metrics for troubleshooting issues. + +Version 1.0.0-alpha37 *(2020-10-07)* +------------------------------------ +- New: Localization in 5 languages Russian, Italian, Spanish, Chinese and Korean. +- New: Store and sync using just the ViewingKey. +- New: Added QA build flavor for better testing. +- New: Ability to change servers (thanks @Nighthawk!) +- Fix: Critical bug in 3rd-party secure storage library impacting large strings. +- Fix: Devices without PIN can use the wallet again. +- Fix: Developer logs now work on all devices. + +Version 1.0.0-alpha34 *(2020-08-28)* +------------------------------------ +- New: Implemented transaction detail view. +- New: Updated receive screen and scan screen. +- New: Added optional blockchain explorer with privacy warning. +- Fix: Update key dependencies for performance. +- Fix: Iterated on send flow with lots of improvements and fixes. +- Fix: Trim improperly parsed characters from memos. +- Fix: Keypad stops working when navigating back to home screen. +- Fix: Prevent black screen after failed initialization. + +Version 1.0.0-alpha33 *(2020-08-13)* +------------------------------------ +- New: Fully removed crashlytics, in favor of bugsnag. +- New: Change the default lightwalletd server. +- New: Switched to the latest SDK. + +Version 1.0.0-alpha32 *(2020-08-01)* +------------------------------------ +- New: entirely revamped send flow +- New: added biometric authentication support +- New: add robust support for tx cancellation +- New: support precise birthday heights for faster restore +- New: switched to Reply-To standard for memos +- New: improved feedback while scanning QRs +- New: more compatible with memo reply-to formats +- New: update to latest librustzcash crates +- New: checkpoints +- Fix: amount not clearing on return to home screen +- Fix: address cursor resetting while typing +- Fix: app crash when opening application logs +- Fix: limit decimal places to 8 places +- Fix: wallet history now scrolls to the top +- Fix: consistent currency formatting +- Fix: security finding around compromised file system + +Version 1.0.0-alpha31 *(2020-06-11)* +------------------------------------ +- Source code now available on github! +- New: Improved mnemonic phrase handling and correctness +- Fix: mitigated several security findings +- New: Integrated with latest SDK, now available on jcenter +- New: Improved error handling in several areas +- New: Built-in support for the Heartwood consensus branch +- Fix: Wallet details screen now refreshes data +- Fix: Seed phrase display error + +Version 1.0.0-alpha29 *(2020-06-10)* +------------------------------------ +- Fix: Removed 3rd party mnemonic library due to restrictive license +- New: Verify checksum for imported mnemonics and warn user +- Fix: Validate address to mitigate security finding +- New: Integrated with latest SDK, now available on jcenter +- New: Improved error handling in several areas +- New: Updated all dependencies +- New: Built-in support for the Heartwood consensus branch +- Fix: Wallet details screen was not refreshing values +- Fix: Polling interval vulnerability + +Version 1.0.0-alpha25 *(2020-03-27)* +------------------------------------ +- New: added full memo support +- New: added feedback screen and related logging +- New: added full wallet restore, including outbound txs, outbound recipients and inbound memos +- New: show sender address in details list, when we can parse it from the memo +- New: long press transaction details to copy related address +- New: clarified UI for pending transactions +- New: improved the handling of disconnected state +- New: improved the behavior when returning from the background +- New: changed doc format to html instead of markdown +- New: iterated on button styles, including intial send button animation +- Fix: last digit of amount no longer lingers when returning to home screen +- Fix: database migration issues in certain versions +- Fix: added more detailed logs around network failures for future troubleshooting +- Fix: avoid negative numbers in the UI +- Fix: occasional crashes while closing the camera +- New: Simplified some SDK APIs so they are easier to use +- New: added more checkpoints so new wallets initialize faster + +Version 1.0.0-alpha23 *(2020-02-21)* +------------------------------------ +- Fix: reorg improvements, squashing critical bugs that disabled wallets +- New: extend analytics to include taps, screen views, and send flow. +- New: add crash reporting via Crashlytics. +- New: expose user logs and developer logs as files. +- New: improve feature for creating checkpoints. +- New: added DB schemas to the repository for tracking. +- Fix: numerous bug fixes, test fixes and cleanup. +- New: improved error handling and user experience + +Version 1.0.0-alpha17 *(2020-02-07)* +------------------------------------ +- New: implemented wallet import +- New: display the memo when tapping outbound transactions +- Fix: removed the sad zebra and softened wording for sending z->t +- Fix: removed restriction on smallest sendable ZEC amount +- Fix: removed "fund now" +- New: turned on developer logging to help with troubleshooting +- New: improved wallet details ability to handle small amounts of ZEC +- New: added ability to clear the memo +- Fix: changed "SEND WITHOUT MEMO" to "OMIT MEMO" +- Fix: corrected wording when the address is included in the memo +- New: display the approximate wallet birthday with the backup words +- New: improved crash reporting +- Fix: fixed bug when returning from the background +- New: added logging for failed transactions +- New: added logic to verify setup and offer explanation when the wallet is corrupted +- New: refactored and improved wallet initialization +- New: added ability to contribute 'plugins' to the SDK +- New: added tons more checkpoints to reduce startup/import time +- New: exposed logic to derive addresses directly from seeds +- Fix: fixed several crashes + +Version 1.0.0-alpha11 *(2020-01-15)* +------------------------------------ +- Initial ECC release + +Version 1.0.0-alpha03 *(2019-12-18)* +------------------------------------ +- Initial internal wallet team release diff --git a/CONDUCT.md b/CONDUCT.md new file mode 100644 index 0000000..ceb5f50 --- /dev/null +++ b/CONDUCT.md @@ -0,0 +1,61 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of +fostering an open and welcoming community, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating +documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free +experience for everyone, regardless of level of experience, gender, gender +identity and expression, sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. Note that contributors may be volunteers +who do not represent Electric Coin Company. They are free to express their own +opinions so long as they adhere to these guidelines. + +By adopting this Code of Conduct, project maintainers commit themselves to +fairly and consistently applying these principles to every aspect of managing +this project. Project maintainers who do not follow or enforce the Code of +Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting a project maintainer (see below). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. Maintainers are +obligated to maintain confidentiality with regard to the reporter of an +incident. + +You may send reports to [our Conduct email](mailto:conduct@z.cash). + +If you wish to contact specific maintainers directly, the following have made +themselves available for conduct issues: + +- Daira Hopwood (daira at z.cash) +- Sean Bowe (sean at z.cash) + + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.3.0, available at +[https://www.contributor-covenant.org/version/1/3/0/][version] + +[homepage]: https://www.contributor-covenant.org +[version]: https://www.contributor-covenant.org/version/1/3/0/ + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6c0ab69 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,98 @@ +# Contributing Guidelines + +This document contains information and guidelines about contributing to this project. +Please read it before you start participating. + +**Topics** + +* [Asking Questions](#asking-questions) +* [Reporting Security Issues](#reporting-security-issues) +* [Reporting Non Security Issues](#reporting-other-issues) +* [Developers Certificate of Origin](#developers-certificate-of-origin) + +## Asking Questions + +Questions are welcome! We encourage you to ask questions through GitHub issues. +Before doing so, please check that the project issues database doesn't already +include an answer to your question. Then open a new Issue and use the "Question" +label. + +## Reporting Security Issues + +If you have discovered an issue with this code that could present a security hazard or wish to discuss a sensitive issue with our security team, please contact security@z.cash [security.asc](https://z.cash/gpg-pubkeys/security.asc). Key fingerprint = AF85 0445 546C 18B7 86F9 2C62 88FB 8B86 D8B5 A68C + +## Reporting Non Security Issues + +A great way to contribute to the project +is to send a detailed issue when you encounter a problem. +We always appreciate a well-written, thorough bug report. + +Check that the project issues database +doesn't already include that problem or suggestion before submitting an issue. +If you find a match, add a quick "+1" or "I have this problem too." +Doing this helps prioritize the most common problems and requests. + +When reporting issues, please include the following: + +* The Android API you're using +* The device you're targeting +* The full output of any stack trace or compiler error +* A code snippet that reproduces the described behavior, if applicable +* Any other details that would be useful in understanding the problem + +This information will help us review and fix your issue faster. + +## Pull Requests + +We **love** pull requests! + +All contributions _will_ be licensed under the MIT license. + +Code/comments should adhere to the following rules: + +* Every Pull request must have an Issue associated to it. PRs with not +associated with an Issue will be closed +* Code build and Code Lint must pass. +* Names should be descriptive and concise. +* Although they are not mandatory, PRs that include significant testing will be +prioritized. +* All enhancements and bug fixes need to be documented in the CHANGELOG. +* When writing comments, use properly constructed sentences, including + punctuation. +* When documenting APIs and/or source code, don't make assumptions or make + implications about race, gender, religion, political orientation or anything + else that isn't relevant to the project. +* Remember that source code usually gets written once and read often: ensure + the reader doesn't have to make guesses. Make sure that the purpose and inner + logic are either obvious to a reasonably skilled professional, or add a + comment that explains it. + +## Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +- (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +- (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +- (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +- (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + + + +This contribution guide is inspired on great projects like [AlamoFire](https://github.com/Alamofire/Foundation/blob/master/CONTRIBUTING.md) and [CocoaPods](https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..51ce0d3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017-2021 Electric Coin Company + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..15daaf7 --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# zcash-android-wallet +A sample Android wallet using the [Zcash Android SDK](https://github.com/zcash/zcash-android-wallet-sdk). + +### Motivation +[Dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) - _transitive verb_ - is the practice of an organization using its own product. This app was created to help us learn. + +Please take note: the wallet is not an official product by ECC, but rather a tool for learning about our libraries that it is built on. This means that we do not have robust infrasturcture or user support for this application. We open sourced it as a resource to make wallet development easier for the Zcash ecosystem. + +# Disclaimers +There are some known areas for improvement: + +- This app is mainly intended for learning and improving the related libraries that it uses. There may be bugs. +- Traffic analysis, like in other cryptocurrency wallets, can leak some privacy of the user. +- The wallet requires a trust in the server to display accurate transaction information. + +See the [Wallet App Threat Model](https://zcash.readthedocs.io/en/latest/rtd_pages/wallet_threat_model.html) +for more information about the security and privacy limitations of the wallet. + +If you'd like to sign up to help us test, reach out on discord and let us know! We're always happy to get feedback! + +# Description +This a sample wallet for the following set of features: +- z2z transactions w/ encrypted memos +- reply-to formatted memos +- z2t transactions +- transparent receive-only +- autoshielding on threshold from receive only t-address + +note: z means sapling shielded addresses. + +# Prerequisites +- [The code](https://github.com/zcash/zcash-android-wallet) +- [Android Studio](https://developer.android.com/studio/index.html) or [adb](https://www.xda-developers.com/what-is-adb/) +- An Android device or emulator + +# Building the App +To run, clone the repo, open it in Android Studio and press play. It should just work.™ + +## Install from Android Studio +1. [Install Android Studio](https://developer.android.com/studio/install) and setup an emulator + 1a. If using a device, be sure to [put it in developer mode](https://developer.android.com/studio/debug/dev-options) to enable side-loading apps +2. `Import` the zcash-android-wallet folder. + It will be recognized as an Android project. +3. Press play (once it is done opening and indexing) + +## OR Install from the command line +To build from the command line, [setup ADB](https://www.xda-developers.com/install-adb-windows-macos-linux/) and connect your device. Then simply run this and it will both build and install the app: +```bash +cd /path/to/zcash-android-wallet +./gradlew +``` +Note: The lack of an explicit Gradle task is not a typo. A default task is configured via [build.gradle.kts](build.gradle.kts). + +Tip: On macOS and Linux, Gradle is invoked with `./gradlew`. On Windows, Gradle is invoked with `gradlew`. + + +# Included builds +To simplify implementation of SDK features in conjunction with changes to the app, a Gradle [Included Build](https://docs.gradle.org/current/userguide/composite_builds.html) can be configured. + +1. Check out the SDK to a directory path of `../zcash-android-sdk` relative to the root of this app's repo. For example: + + parent/ + zcash-android-wallet/ + zcash-android-sdk/ + +1. Verify that the `zcash-android-sdk` builds correctly on its own +1. Build `zcash-android-wallet`, setting the Gradle property `IS_SDK_INCLUDED_BUILD=true` + +There are some limitations of included builds: +1. Properties from `zcash-android-wallet` will override those set in `zcash-android-sdk` with the same name +1. Modules in each project cannot share the same name. For this reason, build-conventions have different names in each repo (`zcash-android-sdk/build-conventions` vs `secant-android-wallet/build-convention`) +1. Kotlin and KSP versions will need to be coordinated between the two projects, because KSP is tightly coupled to the Kotlin version + +# Contributing + +Contributions are very much welcomed! Please read our [Contributing Guidelines](/CONTRIBUTING.md) and [Code of Conduct](/CONDUCT.md). Our backlog has many Issues tagged with the `good first issue` label. Please fork the repo and make a pull request for us to review. + +# Reporting an issue + +If you wish to report a security issue, please follow our [Responsible Disclosure guidelines](https://github.com/zcash/zcash-android-wallet-sdk/blob/master/responsible_disclosure.md). + +For other kind of inquiries, feel welcome to open an Issue if you encounter a bug or would like to request a feature. diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..6bb2507 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,193 @@ +import cash.z.ecc.android.Deps + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: "androidx.navigation.safeargs.kotlin" +apply plugin: 'com.github.ben-manes.versions' + +archivesBaseName = 'zcash-android-wallet' +group = 'cash.z.ecc.android' +version = Deps.versionName + +android { + ndkVersion "21.1.6352462" + compileSdkVersion Deps.compileSdkVersion + defaultConfig { + applicationId Deps.packageName + minSdkVersion Deps.minSdkVersion + targetSdkVersion Deps.targetSdkVersion + versionCode = Deps.versionCode + versionName = Deps.versionName + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + if (Boolean.parseBoolean(isUseTestOrchestrator)) { + testInstrumentationRunnerArguments clearPackageData: 'true' + } + multiDexEnabled true + resValue 'string', 'bugsnag_api_key', "${(project.findProperty('BUGSNAG_API_KEY') ?: System.getenv('BUGSNAG_API_KEY')) ?: ''}" + + // this setting allows using color resources in vector drawables, rather than hardcoded values (note: only works when minApi is 21) + // per https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.VectorDrawablesOptions.html: If set to an empty collection, all special handling of vector drawables will be disabled. + vectorDrawables.generatedDensities = [] + } + buildFeatures { + viewBinding true + } + flavorDimensions 'network' + productFlavors { + // would rather name them "testnet" and "mainnet" but product flavor names cannot start with the word "test" + zcashtestnet { + dimension 'network' + applicationId 'cash.z.ecc.android.testnet' + buildConfigField "String", "DEFAULT_SERVER_URL", '"lite2.hushpool.is"' + matchingFallbacks = ['zcashtestnet', 'debug'] + } + + zcashmainnet { + dimension 'network' + buildConfigField "String", "DEFAULT_SERVER_URL", '"lite2.hushpool.is"' + matchingFallbacks = ['zcashmainnet', 'release'] + } + } + signingConfigs { + placeholder { + storeFile file("${rootProject.projectDir}/placeholder.keystore") + keyAlias "androiddebugkey" + keyPassword "android" + storePassword "android" + } + } + buildTypes { + release { + minifyEnabled false + shrinkResources false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.placeholder + } + debug { + minifyEnabled false + shrinkResources false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + // builds for testing only in the wallet team, typically unfinished features + // this flavor can be installed alongside the others + qa { + initWith debug + debuggable true + applicationIdSuffix ".internal" + matchingFallbacks = ['debug'] + signingConfig signingConfigs.placeholder + } + } + compileOptions { + // enable support for new language APIs but also fix the issue with zxing on API < 24 + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi" + freeCompilerArgs += "-opt-in=kotlin.time.ExperimentalTime" +// freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ObsoleteCoroutinesApi" +// freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.FlowPreview" + } + testOptions { + if (Boolean.parseBoolean(isUseTestOrchestrator)) { + execution 'ANDROIDX_TEST_ORCHESTRATOR' + } + } + kapt { + arguments { + arg 'dagger.fastInit', 'enabled' + arg 'dagger.fullBindingGraphValidation', 'ERROR' + } + } + packagingOptions { + resources { + excludes += ['META-INF/AL2.0', 'META-INF/LGPL2.1'] + } + } + namespace 'cash.z.ecc.android' + applicationVariants.all { variant -> + variant.outputs.all { + if (variant.buildType.name == "qa") { + it.versionNameOverride = "${Deps.versionName}-QA" + } + outputFileName = "$archivesBaseName-v${Deps.versionName}-${variant.buildType.name}.apk" + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':qrecycler') + implementation project(':feedback') + implementation project(':mnemonic') + implementation project(':lockbox') + + // Zcash + implementation Deps.Zcash.ANDROID_WALLET_PLUGINS + implementation Deps.Zcash.SDK + + // Kotlin + implementation Deps.Kotlin.STDLIB + + // Android + implementation Deps.AndroidX.ANNOTATION + implementation Deps.AndroidX.APPCOMPAT + implementation Deps.AndroidX.BIOMETRICS + implementation Deps.AndroidX.CONSTRAINT_LAYOUT + implementation Deps.AndroidX.CORE_KTX + implementation Deps.AndroidX.FRAGMENT_KTX + implementation Deps.AndroidX.LEGACY + implementation Deps.AndroidX.PAGING + implementation Deps.AndroidX.RECYCLER + implementation Deps.AndroidX.CameraX.CAMERA2 + implementation Deps.AndroidX.CameraX.CORE + implementation Deps.AndroidX.CameraX.LIFECYCLE + implementation Deps.AndroidX.CameraX.View.EXT + implementation Deps.AndroidX.CameraX.View.VIEW + implementation Deps.AndroidX.Lifecycle.LIFECYCLE_RUNTIME_KTX + implementation Deps.AndroidX.Navigation.FRAGMENT_KTX + implementation Deps.AndroidX.Navigation.UI_KTX + implementation Deps.AndroidX.Room.ROOM_KTX + kapt Deps.AndroidX.Room.ROOM_COMPILER + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + + + // Google + implementation Deps.Google.GUAVA + implementation Deps.Google.MATERIAL + + // grpc-java + implementation Deps.Grpc.ANDROID + implementation Deps.Grpc.OKHTTP + implementation Deps.Grpc.PROTOBUG + implementation Deps.Grpc.STUB + implementation 'com.squareup.okio:okio:2.8.0' + implementation Deps.JavaX.JAVA_ANNOTATION + + // Misc. + implementation Deps.Misc.LOTTIE + implementation Deps.Misc.CHIPS + implementation Deps.Misc.Plugins.QR_SCANNER + + // Tests + testImplementation Deps.Test.JUNIT + testImplementation Deps.Test.MOKITO + testImplementation Deps.Test.MOKITO_KOTLIN + + androidTestImplementation Deps.Kotlin.REFLECT + androidTestImplementation(Deps.Kotlin.Coroutines.TEST) + androidTestImplementation Deps.Test.Android.JUNIT + androidTestImplementation Deps.Test.Android.CORE + androidTestImplementation Deps.Test.Android.FRAGMENT + androidTestImplementation Deps.Test.Android.ESPRESSO + androidTestImplementation Deps.Test.Android.ESPRESSO_INTENTS + androidTestImplementation Deps.Test.Android.NAVIGATION + // androidTestImplementation is preferred, but then the androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity isn't available + debugImplementation Deps.Test.Android.FRAGMENT +} + +defaultTasks 'clean', 'assembleZcashmainnetRelease' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..a90817f --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,12 @@ +-dontobfuscate +-keepattributes SourceFile,LineNumberTable + +# Reports +-printusage build/outputs/logs/R8-removed-code-report.txt +-printseeds build/outputs/logs/R8-entry-points-report.txt + +## Okio +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java +-dontwarn org.codehaus.mojo.animal_sniffer.* + +#-keep class cash.z.** { *; } \ No newline at end of file diff --git a/app/src/androidTest/java/cash/z/ecc/android/MemoTest.kt b/app/src/androidTest/java/cash/z/ecc/android/MemoTest.kt new file mode 100644 index 0000000..ae10a26 --- /dev/null +++ b/app/src/androidTest/java/cash/z/ecc/android/MemoTest.kt @@ -0,0 +1,43 @@ +package cash.z.ecc.android + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.delay +import org.junit.Ignore +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@Ignore("It'd need additional implementation changes to have this one working.") +@RunWith(AndroidJUnit4::class) +// @RunWith(Parameterized::class) +class MemoTest(val input: String, val output: String) { + +// @Test +// fun testExtractValidAddress() = runBlocking { +// val result = MemoUtil.findAddressInMemo(input, ::validateMemo) +// assertEquals(output, result) +// } + + suspend fun validateMemo(memo: String): Boolean { + delay(20) + return true + } + + companion object { + val validTaddr = "tmWGKMEpxSUf97H12MmGtgiER1drVbGjzWM" + val validZaddr = "ztestsapling1ukadr59p0hxcl2pq8mfagnfx3h74nsusdkm59gkys7hxze92whxj54mfdn3n37zusum7w4jlj35" + val invalidAddr = "ztestsaplinn9ukadr59p0hxcl2pq8mfagnfx3h74nsusdkm59gkys7hxze92whxj54mfdn3n37zusum7w4jlj35" + + @JvmStatic + @Parameterized.Parameters + fun data() = listOf( + arrayOf( + "thanks for the food reply-to: $validZaddr", + validZaddr + ), + arrayOf( + "thanks for the food reply-to: $validTaddr", + validTaddr + ) + ) + } +} diff --git a/app/src/androidTest/java/cash/z/ecc/android/integration/ConversionsTest.kt b/app/src/androidTest/java/cash/z/ecc/android/integration/ConversionsTest.kt new file mode 100644 index 0000000..b19a065 --- /dev/null +++ b/app/src/androidTest/java/cash/z/ecc/android/integration/ConversionsTest.kt @@ -0,0 +1,138 @@ +package cash.z.ecc.android.integration + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import cash.z.ecc.android.ext.WalletZecFormmatter +import cash.z.ecc.android.sdk.model.Zatoshi +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ConversionsTest { + + @Test + fun testToZatoshi() { + val input = "1" + val result = WalletZecFormmatter.toZatoshi(input) + Assert.assertEquals(100_000_000L, result) + } + + @Test + fun testToZecString_short() { + val input = Zatoshi(112_340_000L) + val result = WalletZecFormmatter.toZecStringShort(input) + Assert.assertEquals("1.1234", result) + } + + @Test + fun testToZecString_shortRoundUp() { + val input = Zatoshi(112_355_600L) + val result = WalletZecFormmatter.toZecStringShort(input) + Assert.assertEquals("1.1236", result) + } + + @Test + fun testToZecString_shortRoundDown() { + val input = Zatoshi(112_343_999L) + val result = WalletZecFormmatter.toZecStringShort(input) + Assert.assertEquals("1.1234", result) + } + + @Test + fun testToZecString_shortRoundHalfEven() { + val input = Zatoshi(112_345_000L) + val result = WalletZecFormmatter.toZecStringShort(input) + Assert.assertEquals("1.1234", result) + } + + @Test + fun testToZecString_shortRoundHalfOdd() { + val input = Zatoshi(112_355_000L) + val result = WalletZecFormmatter.toZecStringShort(input) + Assert.assertEquals("1.1236", result) + } + + @Test + fun testToBigDecimal_noCommas() { + val input = "1000" + val result = WalletZecFormmatter.toBigDecimal(input)!! + Assert.assertEquals(1000, result.longValueExact()) + } + + @Test + fun testToBigDecimal_thousandComma() { + val input = "1,000" + val result = WalletZecFormmatter.toBigDecimal(input)!! + Assert.assertEquals(1000, result.longValueExact()) + } + + @Test + fun testToBigDecimal_thousandCommaWithDecimal() { + val input = "1,000.00" + val result = WalletZecFormmatter.toBigDecimal(input)!! + Assert.assertEquals(1000, result.longValueExact()) + } + + @Test + fun testToBigDecimal_oneDecimal() { + val input = "1.000" + val result = WalletZecFormmatter.toBigDecimal(input)!! + Assert.assertEquals(1, result.longValueExact()) + } + + @Test + fun testToBigDecimal_thousandWithThinSpace() { + val input = "1 000" + val result = WalletZecFormmatter.toBigDecimal(input)!! + Assert.assertEquals(1000, result.longValueExact()) + } + + @Test + fun testToBigDecimal_oneWithThinSpace() { + val input = "1.000 000" + val result = WalletZecFormmatter.toBigDecimal(input)!! + Assert.assertEquals(1, result.longValueExact()) + } + + @Test + fun testToBigDecimal_oneDecimalWithComma() { + val input = "1.000,00" + val result = WalletZecFormmatter.toBigDecimal(input)!! + Assert.assertEquals(1, result.longValueExact()) + } + + @Test + fun testToZecString_full() { + val input = Zatoshi(112_341_123L) + val result = WalletZecFormmatter.toZecStringFull(input) + Assert.assertEquals("1.12341123", result) + } + + @Test + fun testToZecString_fullRoundUp() { + val input = Zatoshi(112_355_678L) + val result = WalletZecFormmatter.toZecStringFull(input) + Assert.assertEquals("1.12355678", result) + } + + @Test + fun testToZecString_fullRoundDown() { + val input = Zatoshi(112_349_999L) + val result = WalletZecFormmatter.toZecStringFull(input) + Assert.assertEquals("1.12349999", result) + } + + @Test + fun testToZecString_fullRoundHalfEven() { + val input = Zatoshi(112_250_009L) + val result = WalletZecFormmatter.toZecStringFull(input) + Assert.assertEquals("1.12250009", result) + } + + @Test + fun testToZecString_fullRoundHalfOdd() { + val input = Zatoshi(112_350_004L) + val result = WalletZecFormmatter.toZecStringFull(input) + Assert.assertEquals("1.12350004", result) + } +} diff --git a/app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt b/app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt new file mode 100644 index 0000000..d018b92 --- /dev/null +++ b/app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt @@ -0,0 +1,122 @@ +package cash.z.ecc.android.integration + +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.Initializer +import cash.z.ecc.android.sdk.model.ZcashNetwork +import cash.z.ecc.kotlin.mnemonic.Mnemonics +import kotlinx.coroutines.test.runTest +import okio.Buffer +import okio.GzipSink +import okio.buffer +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class IntegrationTest { + + private lateinit var appContext: Context + private val network = ZcashNetwork.Testnet + private val mnemonics = Mnemonics() + private val phrase = + "human pulse approve subway climb stairs mind gentle raccoon warfare fog roast sponsor" + + " under absorb spirit hurdle animal original honey owner upper empower describe" + + @Before + fun start() { + appContext = InstrumentationRegistry.getInstrumentation().targetContext + } + + @Test + fun testSeed_generation() { + val seed = mnemonics.toSeed(phrase.toCharArray()) + assertEquals( + "Generated incorrect BIP-39 seed!", + "f4e3d38d9c244da7d0407e19a93c80429614ee82dcf62c141235751c9f1228905d12a1f275f" + + "5c22f6fb7fcd9e0a97f1676e0eec53fdeeeafe8ce8aa39639b9fe", + seed.toHex() + ) + } + + @Test + fun testSeed_storage() { + val seed = mnemonics.toSeed(phrase.toCharArray()) + val lb = LockBox(appContext) + lb.setBytes("seed", seed) + assertTrue(seed.contentEquals(lb.getBytes("seed")!!)) + } + + @Test + fun testPhrase_storage() { + val lb = LockBox(appContext) + val phraseChars = phrase.toCharArray() + lb.setCharsUtf8("phrase", phraseChars) + assertTrue(phraseChars.contentEquals(lb.getCharsUtf8("phrase")!!)) + } + + @Test + fun testPhrase_maxLengthStorage() { + val lb = LockBox(appContext) + // find and expose the max length + var acceptedSize = 256 + while (acceptedSize > 0) { + try { + lb.setCharsUtf8("temp", nextString(acceptedSize).toCharArray()) + break + } catch (t: Throwable) { + } + acceptedSize-- + } + + val maxSeedPhraseLength = 8 * 24 + 23 // 215 (max length of each word is 8) + assertTrue( + "LockBox does not support the maximum length seed phrase." + + " Expected: $maxSeedPhraseLength but was: $acceptedSize", + acceptedSize > maxSeedPhraseLength + ) + } + + @Test + @Ignore("It'd need additional implementation changes to have this one working.") + fun testAddress() = runTest { + val seed = mnemonics.toSeed(phrase.toCharArray()) + val initializer = Initializer.new(appContext) { config -> + // config.newWallet(seed, network) + } + assertEquals( + "Generated incorrect z-address!", + "zs1gn2ah0zqhsxnrqwuvwmgxpl5h3ha033qexhsz8tems53fw877f4gug353eefd6z8z3n4zxty65c", + // initializer.rustBackend.getShieldedAddress() + ) + initializer.erase() + } + + private fun ByteArray.toHex(): String { + val sb = StringBuilder(size * 2) + for (b in this) + sb.append(String.format("%02x", b)) + return sb.toString() + } + + fun String.gzip(): ByteArray { + val result = Buffer() + val sink = GzipSink(result).buffer() + sink.use { + sink.write(toByteArray()) + } + return result.readByteArray() + } + + fun nextString(length: Int): String { + val allowedChars = "ACGT" + return (1..length) + .map { allowedChars.random() } + .joinToString("") + } +} diff --git a/app/src/androidTest/java/cash/z/ecc/android/integration/LockBoxTest.kt b/app/src/androidTest/java/cash/z/ecc/android/integration/LockBoxTest.kt new file mode 100644 index 0000000..67a5f6a --- /dev/null +++ b/app/src/androidTest/java/cash/z/ecc/android/integration/LockBoxTest.kt @@ -0,0 +1,73 @@ +package cash.z.ecc.android.integration + +import android.content.Context +import androidx.test.filters.LargeTest +import androidx.test.platform.app.InstrumentationRegistry +import cash.z.ecc.android.lockbox.LockBox +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Ignore +import org.junit.Test + +class LockBoxTest { + + lateinit var lockBox: LockBox + lateinit var appContext: Context + private val hex = ('a'..'f') + ('0'..'9') + private val iterations = 50 + + @Before + fun setUp() { + appContext = InstrumentationRegistry.getInstrumentation().targetContext + lockBox = LockBox(appContext) + lockBox.clear() + } + + @Test + @LargeTest + @Ignore("This test is extremely slow") + fun testLongString() { + var successCount = 0 + repeat(iterations) { + val sampleHex = List(500) { hex.random() }.joinToString("") + + lockBox["longStr"] = sampleHex + val actual: String = lockBox["longStr"]!! + if (sampleHex == actual) successCount++ + lockBox.clear() + } + assertEquals(iterations, successCount) + } + + @Test + @LargeTest + @Ignore("This test is extremely slow") + fun testShortString() { + var successCount = 0 + repeat(iterations) { + val sampleHex = List(50) { hex.random() }.joinToString("") + + lockBox["shortStr"] = sampleHex + val actual: String = lockBox["shortStr"]!! + if (sampleHex == actual) successCount++ + lockBox.clear() + } + assertEquals(iterations, successCount) + } + + @Test + @LargeTest + @Ignore("This test is extremely slow") + fun testGiantString() { + var successCount = 0 + repeat(iterations) { + val sampleHex = List(2500) { hex.random() }.joinToString("") + + lockBox["giantStr"] = sampleHex + val actual: String = lockBox["giantStr"]!! + if (sampleHex == actual) successCount++ + lockBox.clear() + } + assertEquals(iterations, successCount) + } +} diff --git a/app/src/androidTest/java/cash/z/ecc/android/preference/PreferenceKeysTest.kt b/app/src/androidTest/java/cash/z/ecc/android/preference/PreferenceKeysTest.kt new file mode 100644 index 0000000..90d167e --- /dev/null +++ b/app/src/androidTest/java/cash/z/ecc/android/preference/PreferenceKeysTest.kt @@ -0,0 +1,41 @@ +package cash.z.ecc.android.preference + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.reflect.Modifier +import kotlin.reflect.full.memberProperties + +@RunWith(AndroidJUnit4::class) +class PreferenceKeysTest { + @SmallTest + @Test + @Throws(IllegalAccessException::class) + fun fields_public_static_and_final() { + PreferenceKeys::class.java.fields.forEach { + val modifiers = it.modifiers + assertThat(Modifier.isFinal(modifiers), equalTo(true)) + assertThat(Modifier.isStatic(modifiers), equalTo(true)) + assertThat(Modifier.isPublic(modifiers), equalTo(true)) + } + } + + // This test is primary to prevent copy-paste errors in preference keys + @SmallTest + @Test + fun key_values_unique() { + val fieldValueSet = mutableSetOf() + + PreferenceKeys::class.memberProperties + .map { it.getter.call() } + .map { it as String } + .forEach { + assertThat("Duplicate key $it", fieldValueSet.contains(it), equalTo(false)) + + fieldValueSet.add(it) + } + } +} diff --git a/app/src/androidTest/java/cash/z/ecc/android/preference/PreferencesTest.kt b/app/src/androidTest/java/cash/z/ecc/android/preference/PreferencesTest.kt new file mode 100644 index 0000000..08e36bc --- /dev/null +++ b/app/src/androidTest/java/cash/z/ecc/android/preference/PreferencesTest.kt @@ -0,0 +1,46 @@ +package cash.z.ecc.android.preference + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import cash.z.ecc.android.preference.model.DefaultValue +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.reflect.Modifier +import kotlin.reflect.full.memberProperties + +@RunWith(AndroidJUnit4::class) +class PreferencesTest { + @SmallTest + @Test + @Throws(IllegalAccessException::class) + fun fields_public_static_and_final() { + Preferences::class.java.fields.forEach { + val modifiers = it.modifiers + assertThat(Modifier.isFinal(modifiers), equalTo(true)) + assertThat(Modifier.isStatic(modifiers), equalTo(true)) + assertThat(Modifier.isPublic(modifiers), equalTo(true)) + } + } + + // This test is primary to prevent copy-paste errors in preference keys + @SmallTest + @Test + fun key_values_unique() { + val fieldValueSet = mutableSetOf() + + Preferences::class.memberProperties + .map { it.getter.call(Preferences) } + .map { it as DefaultValue<*> } + .forEach { + assertThat( + "Duplicate key ${it.key}", + fieldValueSet.contains(it.key), + equalTo(false) + ) + + fieldValueSet.add(it.key) + } + } +} diff --git a/app/src/androidTest/java/cash/z/ecc/android/test/FragmentNavigationScenario.kt b/app/src/androidTest/java/cash/z/ecc/android/test/FragmentNavigationScenario.kt new file mode 100644 index 0000000..290d095 --- /dev/null +++ b/app/src/androidTest/java/cash/z/ecc/android/test/FragmentNavigationScenario.kt @@ -0,0 +1,32 @@ +package cash.z.ecc.android.test + +import androidx.annotation.IdRes +import androidx.fragment.app.Fragment +import androidx.fragment.app.testing.FragmentScenario +import androidx.navigation.Navigation +import androidx.navigation.testing.TestNavHostController +import androidx.test.core.app.ApplicationProvider + +data class FragmentNavigationScenario( + val fragmentScenario: FragmentScenario, + val navigationController: TestNavHostController +) { + + companion object { + fun new( + fragmentScenario: FragmentScenario, + @IdRes currentDestination: Int + ): FragmentNavigationScenario { + val navController = TestNavHostController(ApplicationProvider.getApplicationContext()) + + fragmentScenario.onFragment { + navController.setGraph(cash.z.ecc.android.R.navigation.mobile_navigation) + navController.setCurrentDestination(currentDestination) + + Navigation.setViewNavController(it.requireView(), navController) + } + + return FragmentNavigationScenario(fragmentScenario, navController) + } + } +} diff --git a/app/src/androidTest/java/cash/z/ecc/android/test/UiTestPrerequisites.kt b/app/src/androidTest/java/cash/z/ecc/android/test/UiTestPrerequisites.kt new file mode 100644 index 0000000..b1b8a73 --- /dev/null +++ b/app/src/androidTest/java/cash/z/ecc/android/test/UiTestPrerequisites.kt @@ -0,0 +1,29 @@ +package cash.z.ecc.android.test + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.os.PowerManager +import androidx.test.core.app.ApplicationProvider +import org.junit.Before +import java.lang.AssertionError + +/** + * Subclass this for UI tests to ensure they run correctly. This helps when developers run tests + * against a physical device that might have gone to sleep. + */ +open class UiTestPrerequisites { + @Before + fun verifyScreenOn() { + if (!isScreenOn()) { + throw AssertionError("Screen must be on for UI tests to run") // $NON-NLS + } + } + + @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) + private fun isScreenOn(): Boolean { + val powerService = ApplicationProvider.getApplicationContext() + .getSystemService(Context.POWER_SERVICE) as PowerManager + return powerService.isInteractive + } +} diff --git a/app/src/androidTest/java/cash/z/ecc/android/ui/home/AutoshieldingInformationFragmentTest.kt b/app/src/androidTest/java/cash/z/ecc/android/ui/home/AutoshieldingInformationFragmentTest.kt new file mode 100644 index 0000000..4922420 --- /dev/null +++ b/app/src/androidTest/java/cash/z/ecc/android/ui/home/AutoshieldingInformationFragmentTest.kt @@ -0,0 +1,154 @@ +package cash.z.ecc.android.ui.home + +import android.content.ComponentName +import android.content.Context +import androidx.fragment.app.testing.FragmentScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.Intents.intended +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import cash.z.ecc.android.preference.Preferences +import cash.z.ecc.android.preference.SharedPreferenceFactory +import cash.z.ecc.android.preference.model.get +import cash.z.ecc.android.test.FragmentNavigationScenario +import cash.z.ecc.android.test.UiTestPrerequisites +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AutoshieldingInformationFragmentTest : UiTestPrerequisites() { + @Test + @MediumTest + fun dismiss_returns_home_when_autoshield_not_available() { + val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = false) + + onView(withId(cash.z.ecc.android.R.id.button_autoshield_dismiss)).also { + it.perform(ViewActions.click()) + } + + assertThat( + fragmentNavigationScenario.navigationController.currentDestination?.id, + equalTo(cash.z.ecc.android.R.id.nav_home) + ) + } + + @Test + @MediumTest + fun dismiss_starts_autoshield_when_autoshield_available() { + val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = true) + + onView(withId(cash.z.ecc.android.R.id.button_autoshield_dismiss)).also { + it.perform(ViewActions.click()) + } + + assertThat( + fragmentNavigationScenario.navigationController.currentDestination?.id, + equalTo(cash.z.ecc.android.R.id.nav_shield_final) + ) + } + + @Test + @MediumTest + fun clicking_more_info_launches_browser() { + val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = false) + + onView(withId(cash.z.ecc.android.R.id.button_autoshield_more_info)).also { + it.perform(ViewActions.click()) + } + + assertThat( + fragmentNavigationScenario.navigationController.currentDestination?.id, + equalTo(cash.z.ecc.android.R.id.nav_autoshielding_info_details) + ) + + // Note: it is difficult to verify that the browser is launched, because of how the + // navigation component works. + } + + @Test + @MediumTest + fun starting_fragment_does_not_launch_activities() { + Intents.init() + try { + val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = false) + + // The test framework launches an Activity to host the Fragment under test + // Since the class name is not a public API, this could break in the future with newer + // versions of the AndroidX Test libraries. + intended( + hasComponent( + ComponentName( + ApplicationProvider.getApplicationContext(), + "androidx.test.core.app.InstrumentationActivityInvoker\$BootstrapActivity" + ) + ) + ) + + // Verifying that no other Activities (e.g. the link view) are launched without explicit + // user interaction + Intents.assertNoUnverifiedIntents() + + assertThat( + fragmentNavigationScenario.navigationController.currentDestination?.id, + equalTo(cash.z.ecc.android.R.id.nav_autoshielding_info) + ) + } finally { + Intents.release() + } + } + + @Test + @MediumTest + fun display_fragment_sets_preference() { + newScenario(isAutoshieldAvailable = false) + + assertThat( + Preferences.isAcknowledgedAutoshieldingInformationPrompt.get(ApplicationProvider.getApplicationContext()), + equalTo(true) + ) + } + + @Test + @MediumTest + fun back_navigates_home() { + val fragmentNavigationScenario = newScenario(isAutoshieldAvailable = false) + + fragmentNavigationScenario.fragmentScenario.onFragment { + // Probably closest we can come to simulating back with the navigation test framework + fragmentNavigationScenario.navigationController.navigateUp() + } + + assertThat( + fragmentNavigationScenario.navigationController.currentDestination?.id, + equalTo(cash.z.ecc.android.R.id.nav_home) + ) + } + + companion object { + private fun newScenario(isAutoshieldAvailable: Boolean): FragmentNavigationScenario { + // Clear preferences for each scenario, as this most closely reflects how this fragment + // is used in the app, as it is displayed usually on first launch + SharedPreferenceFactory.getSharedPreferences(ApplicationProvider.getApplicationContext()) + .edit().clear().apply() + + val scenario = FragmentScenario.launchInContainer( + AutoshieldingInformationFragment::class.java, + HomeFragmentDirections.actionNavHomeToAutoshieldingInfo(isAutoshieldAvailable).arguments, + cash.z.ecc.android.R.style.ZcashTheme, + null + ) + + return FragmentNavigationScenario.new( + scenario, + cash.z.ecc.android.R.id.nav_autoshielding_info + ) + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..baaecf1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/saplingtree/mainnet/1225600.json b/app/src/main/assets/saplingtree/mainnet/1225600.json new file mode 100644 index 0000000..b33872f --- /dev/null +++ b/app/src/main/assets/saplingtree/mainnet/1225600.json @@ -0,0 +1,7 @@ +{ + "network": "main", + "height": 1225600, + "hash": "0000000000196bafb2472eb7a3b1aa85bccc00904d5650a7952dd437859fc38c", + "time": 1619215931, + "tree": "0128411e8cb2f543c46ca943736c96ab4fa86cab1e3e2e394ed458d56b395bd5050120303bbaf4f19e37a06c1e9ea815567fc23990cc65494c2be29f8e6e4a9d9a6c130001010e9388fdf9bf49e3adf4adb57d83e0b5ba34f63a2681eceb54d3aaaf236b210001c0920d177f77815c4f643c2b331bd6b86d291d6bc2c1c20f6bc501f49adcdb3b000001b7958828206f53c25465943d4173af16de3cee94ae01b2e17a32c51c06fde3630001b31ed2e29d0d894604f0d7bf4735d4bcf25dc9f859c5e296a5689af7ca8c94720134ca9a7c4309349dfe003f3b4b95898b4303631e9be3a25b4e917a4f3472b52f00000121c25bceccda091622bfac1b7973ffaa638abe1f334b3b56f48dc93dc549c9070001ece344ca21dbd3b681f167163d4792165efe8239390afc13378e50d044fee65a01089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644" +} diff --git a/app/src/main/assets/saplingtree/mainnet/1250000.json b/app/src/main/assets/saplingtree/mainnet/1250000.json new file mode 100644 index 0000000..8850e40 --- /dev/null +++ b/app/src/main/assets/saplingtree/mainnet/1250000.json @@ -0,0 +1,7 @@ +{ + "network": "main", + "height": 1250000, + "hash": "0000000000f3d2c352c395d66866032bcb67094228dd4a27e561b1c399ea612e", + "time": 1621056898, + "tree": "01c9a0dd6f6dfaaafe6ae4b432c2d1c41d2a73e564c8cb6d2c5ab637c7001a2456001300000000017da32b486a8ea9f13afb93b99d2b1de69aa969e7c2fd7b9ee958bece70c08d6b000001b3a4486b176dfcedc0b3d9287c0333ff464ecbd02bac7c89bcda7932e6a0a36100010d451c18b56877b8a11cb401ab7024c82b9669ede862a53e461087f57220035001a1c5260bc4dfe010510b8135209c6f64229965f71717f1e693abdcf88a58f36700012f0bf70e372e536fc3b76ecd7e2b69eebf2fbcf71b828c64b0a8b99390fbf754018e7922ca798cd3e26d3369ca2425ec19baa7d79407a979ec1090ae48fdcd094a01ece344ca21dbd3b681f167163d4792165efe8239390afc13378e50d044fee65a01089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644" +} diff --git a/app/src/main/assets/saplingtree/mainnet/1290000.json b/app/src/main/assets/saplingtree/mainnet/1290000.json new file mode 100644 index 0000000..f6c8e7c --- /dev/null +++ b/app/src/main/assets/saplingtree/mainnet/1290000.json @@ -0,0 +1,7 @@ +{ + "network": "main", + "height": 1290000, + "hash": "00000000014836c3cbc011276cbd3702a76a1fea7eb2c0c2c257321220376450", + "time": 1624075741, + "tree": "01accf4fc3dc4233bbe757f94e0d4cd23b4aa2e6ac472601f4f53ca4dc86a8a05901fae977171a6103a0338990e073ffe50e29fc8bf0400dcd3378ebfe7a146ed1481300014f7b33dd5159ac66f2670b7db8925065e7154e0199ff7ee7559b276ba56ad1200173e9881f21357e54027a4275114f0f6ad4ca17143554182f63c77f3288a23a20011d65465ab942440e200d429ef892452b4b05c5b21e9a6e6d968a719c67b5e85b000000000000000150926c74975e2d8ff095defb75a4a6d9f17007e87a74230a65a3265d8f45032900012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644" +} diff --git a/app/src/main/assets/saplingtree/mainnet/1300000.json b/app/src/main/assets/saplingtree/mainnet/1300000.json new file mode 100644 index 0000000..923b3a8 --- /dev/null +++ b/app/src/main/assets/saplingtree/mainnet/1300000.json @@ -0,0 +1,7 @@ +{ + "network": "main", + "height": 1300000, + "hash": "00000000027222bdbcf9c5f807f851f97312ac6e0dbbc2b93f2be21a69c59d44", + "time": 1624830312, + "tree": "01f5a97e2679a2bb9103caf37b825f92fcd73fff836234844dfcf1815394522b2c01526587b9b9e8aeb0eb572d81fec1f5127b8278ba0f57e451bd6b796596940a2213000131c7ff90fafff6159b8fb6544a2bcbba6c102903158fce8f9a9d3c6654abb23300013555cb7f4f79badeaca9bf2dca5a8704f0929053d50e95c03002f9a4d5286c3a01ad3557e11c1607ec888dc84f5f8899c3c79fb1f50b613946452ec7dd5e53763c0001c4583f4482b949390dba355fc8fa63019c83acd644ddd633cb50211d236f870600000001088da0d78eefd0c222507927e403b972d0890d0c31e08b02268fbe39ac4a6e170001edf82d4e2b4893ea2028ca8c5149e50a4c358b856d73f2de2b9a22034fa78f22012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644" +} diff --git a/app/src/main/assets/saplingtree/mainnet/1335000.json b/app/src/main/assets/saplingtree/mainnet/1335000.json new file mode 100644 index 0000000..714a3ef --- /dev/null +++ b/app/src/main/assets/saplingtree/mainnet/1335000.json @@ -0,0 +1,7 @@ +{ + "network": "main", + "height": 1335000, + "hash": "00000000001d428474214f2844ac7adacab9c9b706f89ebb24e1e43189edff2d", + "time": 1627468889, + "tree": "01105d94f868041b1680f862dad6211ab815a30c79a63b839c2b2043ce6530834801e53ee3fef11ddfaef984c8653dffa0354929b79aad7321b00c10cb3b60c8b7111301f5693ff9b17a8fc0b032c192841d1fc08b7ec9fe4fcc2b628a550434af70886a01838a7001b5ed5dcdec7bce1ea4250bbeebe8c22aa27fd69e7baf343458e95c7101030f11dfda75a9e4a63bab19fe3bf92c545a3f58a57ca41ae7609290dad01436018923004af490f5718e834215ef61f2f60aee24685c1c2cffb3c686dff57ab82501eb86680f83fa0f9c47da3875645344a2734d56edcf1d99747ecbf25ea0e86e22000001cf6872911593b4f1af2fd03dce8a48d434af849ad1bc872442e7881bbc04e8610168fbde909e21c25e1a686fac9982ee11fb0d05da3568579bfba8b71f7632d62700012965494015cdab2ce010c1ae4ea88306c286128275de391dcf57d3fa85be7e1b01a090ee174239a34a5d684425d09006d238c6075a61c5842d0fc26043f09ccd7001a2b7ee187c7b8ce18ebda8600bed7695b12f7d35ac971ed6ee67184a7ceebd490001b35fe4a943a47404f68db220c77b0573e13c3378a65c6f2396f93be7609d8f2a000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644" +} diff --git a/app/src/main/assets/saplingtree/testnet/1380300.json b/app/src/main/assets/saplingtree/testnet/1380300.json new file mode 100644 index 0000000..183e47d --- /dev/null +++ b/app/src/main/assets/saplingtree/testnet/1380300.json @@ -0,0 +1,7 @@ +{ + "network": "test", + "height": 1380300, + "hash": "00342c648fb9c5d109df4dd5b7849a4357f27f1dfdb8d3a0071e8254072d1a4a", + "time": 1619216615, + "tree": "01f5b47ef533c9b6240826210d7e66691f36b21ac1ce1e4a231399ff4f8b1286600198dc26bbe8f037c5dbd8a43e94c482bb513898bd1ee1a734c07c57450b9ec01b1000000001b18e52aa826dcf85a08ae15d1bb4c8559166fcd5cffd74b597a8b50bf32d311100018dc0c02e20384fcdc238a6c01a0e4598da69f546646acc177fd91b86a0f8236200000001ba0d7aa9e68417291c63b835fa64114f5899208238de59ee360f594c8b6c1b72018469338dcbdf2f7e54bca5bc3e1c5fad4a656f206040436d3d0433a901218b5e016d559de7a1a382349cf97fe01a2fba41a49bb5e3b306d9ff8c2bcc301c731c00000001f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" +} diff --git a/app/src/main/assets/saplingtree/testnet/1450000.json b/app/src/main/assets/saplingtree/testnet/1450000.json new file mode 100644 index 0000000..9fbb97e --- /dev/null +++ b/app/src/main/assets/saplingtree/testnet/1450000.json @@ -0,0 +1,7 @@ +{ + "network": "test", + "height": 1450000, + "hash": "000008a97bc133de13ca304e0c6a2a1b3f2facdceac2cde5b4141179f2a743cc", + "time": 1623815069, + "tree": "0175626cf9d8448de98f68fcc585dd7a276c946c11bbc3b192ee08db99c542b86b01acf5a110dc7ab911b534984c46bf56592f0c4cc8cf70dbd6a9cc4a5b47d2c81c1001c91f518ccb74093a217a640c537b69b095de058e0430046c8783f231caa1fa4201f7c982ce76b2c9343fb771e077357322f9a7dabfd7ab93b7adee32806c930d6600000170910ab6355ec614412fae56dad5fdc1747ce1b306a4b8ae03b77513b612b00800000000000000013d2fd009bf8a22d68f720eac19c411c99014ed9c5f85d5942e15d1fc039e28680001f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" +} diff --git a/app/src/main/assets/saplingtree/testnet/1454000.json b/app/src/main/assets/saplingtree/testnet/1454000.json new file mode 100644 index 0000000..87dd546 --- /dev/null +++ b/app/src/main/assets/saplingtree/testnet/1454000.json @@ -0,0 +1,7 @@ +{ + "network": "test", + "height": 1454000, + "hash": "003254b452f221d36ba81d051a1a63edeb203de7ab457500d08b4110bcc86620", + "time": 1624073536, + "tree": "01007501338f9d31446b9c0228b87e81886555100fbb1b5bec7966617559d5400901d830393653a5379f1f071bffa191f9b56d0664859d9b19b9e9ae4e1c76f7d34f1001ca92ad0eeb818c3bb57ca30ed500dd58703fe14c4837f14ac8a1491622f0a8550001a1d6a89c888e46ce950d5af54739e9847fab81f383586ad5dc51dd00f65ed85d0160f01e9c484861b220f5a4650119f192217a89854ada30019fae9ab46ff4c4120001462c8d06a58ddec91ed309dcb041cdedcca73446889496332054d54e1561633b000000000000013d2fd009bf8a22d68f720eac19c411c99014ed9c5f85d5942e15d1fc039e28680001f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39" +} diff --git a/app/src/main/assets/sound_receive_small.mp3 b/app/src/main/assets/sound_receive_small.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d88fdbef0d3de1b0a728f5088775e7ddd4ab526f GIT binary patch literal 50225 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 literal 0 HcmV?d00001 diff --git a/app/src/main/java/cash/z/ecc/android/StrictModeHelper.kt b/app/src/main/java/cash/z/ecc/android/StrictModeHelper.kt new file mode 100644 index 0000000..090420c --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/StrictModeHelper.kt @@ -0,0 +1,60 @@ +package cash.z.ecc.android + +import android.annotation.SuppressLint +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.os.StrictMode + +object StrictModeHelper { + + fun enableStrictMode() { + configureStrictMode() + + // Workaround for Android bug + // https://issuetracker.google.com/issues/36951662 + // Not needed if target O_MR1 and running on O_MR1 + // Don't really need to check target, because of Google Play enforcement on targetSdkVersion for app updates + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) { + Handler(Looper.getMainLooper()).postAtFrontOfQueue { configureStrictMode() } + } + } + + @SuppressLint("NewApi") + private fun configureStrictMode() { + StrictMode.enableDefaults() + + StrictMode.setThreadPolicy( + StrictMode.ThreadPolicy.Builder().apply { + detectAll() + penaltyLog() + }.build() + ) + + // Don't enable missing network tags, because those are noisy. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + StrictMode.setVmPolicy( + StrictMode.VmPolicy.Builder().apply { + detectActivityLeaks() + detectCleartextNetwork() + detectContentUriWithoutPermission() + detectFileUriExposure() + detectLeakedClosableObjects() + detectLeakedRegistrationObjects() + detectLeakedSqlLiteObjects() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // Disable because this is mostly flagging Android X and Play Services + // builder.detectNonSdkApiUsage(); + } + }.build() + ) + } else { + StrictMode.setVmPolicy( + StrictMode.VmPolicy.Builder().apply { + detectAll() + penaltyLog() + }.build() + ) + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt new file mode 100644 index 0000000..a0a5fd1 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt @@ -0,0 +1,117 @@ +package cash.z.ecc.android + +import android.app.Application +import android.content.Context +import androidx.camera.camera2.Camera2Config +import androidx.camera.core.CameraXConfig +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.tryWithWarning +import cash.z.ecc.android.feedback.FeedbackCoordinator +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.sdk.model.ZcashNetwork +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.* + +class ZcashWalletApp : Application(), CameraXConfig.Provider { + + private val coordinator: FeedbackCoordinator + get() = DependenciesHolder.feedbackCoordinator + + lateinit var defaultNetwork: ZcashNetwork + + var creationTime: Long = 0 + private set + + var creationMeasured: Boolean = false + + /** The amount of transparent funds that need to accumulate before autoshielding is triggered */ + val autoshieldThreshold: Long = Zatoshi.ZATOSHI_PER_ZEC // 1 ZEC + + /** + * Intentionally private Scope for use with launching Feedback jobs. The feedback object has the + * longest scope in the app because it needs to be around early in order to measure launch times + * and stick around late in order to catch crashes. We intentionally don't expose this because + * application objects can have odd lifecycles, given that there is no clear onDestroy moment in + * many cases. + */ + private var feedbackScope: CoroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) + + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + + // Setting a global reference to the application object is icky; we should try to refactor + // this away if possible. Doing this in attachBaseContext instead of onCreate() + // to avoid any lifecycle issues, as certain components can run before Application.onCreate() + // (like ContentProvider initialization), but attachBaseContext will still run before that. + instance = this + } + + override fun onCreate() { + super.onCreate() + + // Register this before the uncaught exception handler, because we want to make sure the + // exception handler also doesn't do disk IO. Since StrictMode only applies for debug builds, + // we'll also see the crashes during development right away and won't miss them if they aren't + // reported by the crash reporting. + if (BuildConfig.DEBUG) { + StrictModeHelper.enableStrictMode() + cash.z.ecc.android.sdk.internal.Twig.enabled(true) + cash.z.ecc.android.util.Twig.enabled(true) + } + + // Setup handler for uncaught exceptions. + Thread.getDefaultUncaughtExceptionHandler()?.let { + Thread.setDefaultUncaughtExceptionHandler(ExceptionReporter(it)) + } + creationTime = System.currentTimeMillis() + + defaultNetwork = ZcashNetwork.from(resources.getInteger(R.integer.zcash_network_id)) + feedbackScope.launch { + coordinator.feedback.start() + } + } + + override fun getCameraXConfig(): CameraXConfig { + return Camera2Config.defaultConfig() + } + + companion object { + lateinit var instance: ZcashWalletApp + } + + /** + * @param feedbackCoordinator inject a provider so that if a crash happens before configuration + * is complete, we can lazily initialize all the feedback objects at this moment so that we + * don't have to add any time to startup. + */ + inner class ExceptionReporter(private val ogHandler: Thread.UncaughtExceptionHandler) : + Thread.UncaughtExceptionHandler { + override fun uncaughtException(t: Thread?, e: Throwable?) { + twig("Uncaught Exception: $e caused by: ${e?.cause}") + // Things can get pretty crazy during a fatal exception + // so be cautious here to avoid freezing the app + tryWithWarning("Unable to report fatal crash") { + // note: these are the only reported crashes that set isFatal=true + coordinator.feedback.report(e, true) + } + tryWithWarning("Unable to flush the feedback coordinator") { + coordinator.flush() + } + + try { + // can do this if necessary but first verify that we need it + runBlocking { + coordinator.await() + coordinator.feedback.stop() + } + } catch (t: Throwable) { + twig("WARNING: failed to wait for the feedback observers to complete.") + } finally { + // it's important that this always runs so we use the finally clause here + // rather than another tryWithWarning block + ogHandler.uncaughtException(t, e) + Thread.sleep(2000L) + } + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/di/DependenciesHolder.kt b/app/src/main/java/cash/z/ecc/android/di/DependenciesHolder.kt new file mode 100644 index 0000000..5dcfce4 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/di/DependenciesHolder.kt @@ -0,0 +1,44 @@ +package cash.z.ecc.android.di + +import android.content.ClipboardManager +import android.content.Context +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.ext.Const +import cash.z.ecc.android.feedback.* +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.ui.util.DebugFileTwig +import cash.z.ecc.android.util.SilentTwig +import cash.z.ecc.android.util.Twig +import cash.z.ecc.kotlin.mnemonic.Mnemonics + +object DependenciesHolder { + + fun provideAppContext(): Context = ZcashWalletApp.instance + + val initializerComponent by lazy { InitializerComponent() } + + val synchronizer by lazy { Synchronizer.newBlocking(initializerComponent.initializer) } + + val clipboardManager by lazy { provideAppContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager } + + val lockBox by lazy { LockBox(provideAppContext()) } + + val prefs by lazy { LockBox(provideAppContext()) } + + val feedback by lazy { Feedback() } + + val feedbackCoordinator by lazy { + lockBox.getBoolean(Const.Pref.FEEDBACK_ENABLED).let { isEnabled -> + // observe nothing unless feedback is enabled + Twig.plant(if (isEnabled) DebugFileTwig() else SilentTwig()) + FeedbackCoordinator(feedback) + } + } + + val feedbackFile by lazy { FeedbackFile() } + + val feedbackConsole by lazy { FeedbackConsole() } + + val mnemonics by lazy { Mnemonics() } +} diff --git a/app/src/main/java/cash/z/ecc/android/di/InitializerComponent.kt b/app/src/main/java/cash/z/ecc/android/di/InitializerComponent.kt new file mode 100644 index 0000000..65c5f31 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/di/InitializerComponent.kt @@ -0,0 +1,14 @@ +package cash.z.ecc.android.di + +import cash.z.ecc.android.sdk.Initializer + +class InitializerComponent { + + lateinit var initializer: Initializer + private set + + fun createInitializer(config: Initializer.Config) { + initializer = Initializer.newBlocking(DependenciesHolder.provideAppContext(), config) + } + +} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ext/Const.kt b/app/src/main/java/cash/z/ecc/android/ext/Const.kt new file mode 100644 index 0000000..4683904 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/Const.kt @@ -0,0 +1,53 @@ +package cash.z.ecc.android.ext + +import cash.z.ecc.android.BuildConfig + +object Const { + /** + * Named objects for Dependency Injection. + */ + object Name { + /** application data other than cryptographic keys */ + const val APP_PREFS = "const.name.app_prefs" + const val BEFORE_SYNCHRONIZER = "const.name.before_synchronizer" + const val SYNCHRONIZER = "const.name.synchronizer" + } + + /** + * App preference key names. + */ + object Pref { + const val FIRST_USE_VIEW_TX = "const.pref.first_use_view_tx" + const val EASTER_EGG_TRIGGERED_SHIELDING = "const.pref.easter_egg_shielding" + const val FEEDBACK_ENABLED = "const.pref.feedback_enabled" + const val SERVER_HOST = "const.pref.server_host" + const val SERVER_PORT = "const.pref.server_port" + } + + /** + * Constants used for wallet backup. + */ + object Backup { + const val SEED = "cash.z.ecc.android.SEED" + const val SEED_PHRASE = "cash.z.ecc.android.SEED_PHRASE" + const val HAS_SEED = "cash.z.ecc.android.HAS_SEED" + const val HAS_SEED_PHRASE = "cash.z.ecc.android.HAS_SEED_PHRASE" + const val HAS_BACKUP = "cash.z.ecc.android.HAS_BACKUP" + + // Config + const val VIEWING_KEY = "cash.z.ecc.android.VIEWING_KEY" + const val PUBLIC_KEY = "cash.z.ecc.android.PUBLIC_KEY" + const val BIRTHDAY_HEIGHT = "cash.z.ecc.android.BIRTHDAY_HEIGHT" + } + + /** + * Default values to use application-wide. Ideally, this set of values should remain very short. + */ + object Default { + object Server { + // If you've forked the ECC repo, change this to your hosted lightwalletd instance + const val HOST = BuildConfig.DEFAULT_SERVER_URL + const val PORT = 9067 + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ext/CurrencyFormatter.kt b/app/src/main/java/cash/z/ecc/android/ext/CurrencyFormatter.kt new file mode 100644 index 0000000..e1ee647 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/CurrencyFormatter.kt @@ -0,0 +1,76 @@ +package cash.z.ecc.android.ext + +import cash.z.ecc.android.ext.ConversionsUniform.FULL_FORMATTER +import cash.z.ecc.android.ext.ConversionsUniform.LONG_SCALE +import cash.z.ecc.android.ext.ConversionsUniform.SHORT_FORMATTER +import cash.z.ecc.android.sdk.ext.Conversions +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.model.Zatoshi +import java.math.BigDecimal +import java.math.MathContext +import java.math.RoundingMode +import java.text.DecimalFormat +import java.text.NumberFormat +import java.util.Locale + +/** + * Do the necessary conversions in one place + * + * "1.234" -> to zatoshi + * (zecStringToZatoshi) + * String.toZatoshi() + * + * 123123 -> to "1.2132" + * (zatoshiToZecString) + * Long.toZecString() + * + */ +object ConversionsUniform { + val ONE_ZEC_IN_ZATOSHI = BigDecimal(Zatoshi.ZATOSHI_PER_ZEC, MathContext.DECIMAL128) + val LONG_SCALE = 8 + val SHORT_SCALE = 4 + val SHORT_FORMATTER = from(SHORT_SCALE, SHORT_SCALE) + val FULL_FORMATTER = from(LONG_SCALE) + + val roundingMode = RoundingMode.HALF_EVEN + + private fun from(maxDecimals: Int = 8, minDecimals: Int = 0) = (NumberFormat.getNumberInstance(Locale("en", "USA")) as DecimalFormat).apply { +// applyPattern("###.##") + isParseBigDecimal = true + roundingMode = roundingMode + maximumFractionDigits = maxDecimals + minimumFractionDigits = minDecimals + minimumIntegerDigits = 1 + } +} + +object WalletZecFormmatter { + fun toZatoshi(zecString: String): Long? { + return toBigDecimal(zecString)?.multiply(Conversions.ONE_ZEC_IN_ZATOSHI, MathContext.DECIMAL128)?.toLong() + } + fun toZecStringShort(amount: Zatoshi?): String { + return SHORT_FORMATTER.format((amount ?: Zatoshi(0)).toZec()) + } + fun toZecStringFull(amount: Zatoshi?): String { + return formatFull((amount ?: Zatoshi(0)).toZec()) + } + fun formatFull(zec: BigDecimal): String { + return FULL_FORMATTER.format(zec) + } + fun toBigDecimal(zecString: String?): BigDecimal? { + if (zecString.isNullOrEmpty()) return BigDecimal.ZERO + return try { + // ignore commas and whitespace + var sanitizedInput = zecString.filter { it.isDigit() or (it == '.') } + BigDecimal.ZERO.max(FULL_FORMATTER.parse(sanitizedInput) as BigDecimal) + } catch (t: Throwable) { + return null + } + } + + // convert a zatoshi value to ZEC as a BigDecimal + private fun Zatoshi?.toZec(): BigDecimal = + BigDecimal(this?.value ?: 0L, MathContext.DECIMAL128) + .divide(ConversionsUniform.ONE_ZEC_IN_ZATOSHI) + .setScale(LONG_SCALE, ConversionsUniform.roundingMode) +} diff --git a/app/src/main/java/cash/z/ecc/android/ext/Dialogs.kt b/app/src/main/java/cash/z/ecc/android/ext/Dialogs.kt new file mode 100644 index 0000000..cb8e755 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/Dialogs.kt @@ -0,0 +1,192 @@ +package cash.z.ecc.android.ext + +import android.app.ActivityManager +import android.app.Dialog +import android.content.Context +import android.text.Html +import androidx.annotation.StringRes +import androidx.core.content.getSystemService +import cash.z.ecc.android.R +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +fun Context.showClearDataConfirmation(onDismiss: () -> Unit = {}, onCancel: () -> Unit = {}): Dialog { + return MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_nuke_wallet_title) + .setMessage(R.string.dialog_nuke_wallet_message) + .setCancelable(false) + .setPositiveButton(R.string.dialog_nuke_wallet_button_positive) { dialog, _ -> + dialog.dismiss() + onDismiss() + onCancel() + } + .setNegativeButton(R.string.dialog_nuke_wallet_button_negative) { dialog, _ -> + dialog.dismiss() + onDismiss() + getSystemService()?.clearApplicationUserData() + } + .show() +} + +fun Context.showUninitializedError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog { + return MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_error_uninitialized_title) + .setMessage(R.string.dialog_error_uninitialized_message) + .setCancelable(false) + .setPositiveButton(getString(R.string.dialog_error_uninitialized_button_positive)) { dialog, _ -> + dialog.dismiss() + onDismiss() + if (error != null) throw error + } + .setNegativeButton(getString(R.string.dialog_error_uninitialized_button_negative)) { dialog, _ -> + showClearDataConfirmation( + onDismiss, + onCancel = { + // do not let the user back into the app because we cannot recover from this case + showUninitializedError(error, onDismiss) + } + ) + } + .show() +} + +fun Context.showInvalidSeedPhraseError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog { + return MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_error_invalid_seed_phrase_title) + .setMessage(getString(R.string.dialog_error_invalid_seed_phrase_message, error?.message ?: "")) + .setCancelable(false) + .setPositiveButton(getString(R.string.dialog_error_invalid_seed_phrase_button_positive)) { dialog, _ -> + dialog.dismiss() + onDismiss() + } + .show() +} + +fun Context.showScanFailure(error: Throwable?, onCancel: () -> Unit = {}, onDismiss: () -> Unit = {}): Dialog { + val message = if (error == null) { + "Unknown error" + } else { + "${error.message}${if (error.cause != null) "\n\nCaused by: ${error.cause}" else ""}" + } + return MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_error_scan_failure_title) + .setMessage(message) + .setCancelable(true) + .setPositiveButton(R.string.dialog_error_scan_failure_button_positive) { d, _ -> + d.dismiss() + onDismiss() + } + .setNegativeButton(R.string.dialog_error_scan_failure_button_negative) { d, _ -> + d.dismiss() + onCancel() + onDismiss() + } + .show() +} + +fun Context.showCriticalMessage(@StringRes titleResId: Int, @StringRes messageResId: Int, onDismiss: () -> Unit = {}): Dialog { + return showCriticalMessage(titleResId.toAppString(), messageResId.toAppString(), onDismiss) +} + +fun Context.showCriticalMessage(title: String, message: String, onDismiss: () -> Unit = {}): Dialog { + return MaterialAlertDialogBuilder(this) + .setTitle(title) + .setMessage(message) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { d, _ -> + d.dismiss() + onDismiss() + } + .show() +} + +fun Context.showCriticalProcessorError(error: Throwable?, onRetry: () -> Unit = {}): Dialog { + return MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_error_processor_critical_title) + .setMessage(error?.message ?: getString(R.string.dialog_error_processor_critical_message)) + .setCancelable(false) + .setPositiveButton(R.string.dialog_error_processor_critical_button_positive) { d, _ -> + d.dismiss() + onRetry() + } + .setNegativeButton(R.string.dialog_error_processor_critical_button_negative) { dialog, _ -> + dialog.dismiss() + throw error ?: RuntimeException("Critical error while processing blocks and the user chose to exit.") + } + .show() +} + +fun Context.showUpdateServerCriticalError(userFacingMessage: String, onConfirm: () -> Unit = {}): Dialog { + return MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_error_change_server_title) + .setMessage(userFacingMessage) + .setCancelable(false) + .setPositiveButton(R.string.dialog_error_change_server_button_positive) { d, _ -> + d.dismiss() + onConfirm() + } + .show() +} + +fun Context.showUpdateServerDialog(positiveResId: Int = R.string.dialog_modify_server_button_positive, onCancel: () -> Unit = {}, onUpdate: () -> Unit = {}): Dialog { + return MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_modify_server_title) + .setMessage(R.string.dialog_modify_server_message) + .setCancelable(false) + .setPositiveButton(positiveResId) { dialog, _ -> + dialog.dismiss() + onUpdate() + } + .setNegativeButton(R.string.dialog_modify_server_button_negative) { dialog, _ -> + dialog.dismiss() + onCancel() + } + .show() +} + +fun Context.showRescanWalletDialog(quickDistance: String, quickEstimate: String, fullDistance: String, fullEstimate: String, onWipe: () -> Unit = {}, onFullRescan: () -> Unit = {}, onQuickRescan: () -> Unit = {}): Dialog { + return MaterialAlertDialogBuilder(this) + .setTitle(R.string.dialog_rescan_wallet_title) + .setMessage(Html.fromHtml(getString(R.string.dialog_rescan_wallet_message, quickDistance, quickEstimate, fullDistance, fullEstimate))) + .setCancelable(true) + .setPositiveButton(R.string.dialog_rescan_wallet_button_positive) { dialog, _ -> + dialog.dismiss() + onQuickRescan() + } + .setNeutralButton(R.string.dialog_rescan_wallet_button_neutral) { dialog, _ -> + dialog.dismiss() + onWipe() + } + .setNegativeButton(R.string.dialog_rescan_wallet_button_negative) { dialog, _ -> + dialog.dismiss() + onFullRescan() + } + .show() +} + +fun Context.showConfirmation(title: String, message: String, positiveButton: String, negativeButton: String = "Cancel", onPositive: () -> Unit = {}): Dialog { + return MaterialAlertDialogBuilder(this) + .setTitle(title) + .setMessage(message) + .setPositiveButton(positiveButton) { dialog, _ -> + dialog.dismiss() + onPositive() + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + .show() +} + +/** + * Error to show when the Rust libraries did not properly link. This problem can happen pretty often + * during development when a build of the SDK failed to compile and resulted in an AAR file with no + * shared libraries (*.so files) inside. In theory, this should never be seen by an end user but if + * it does occur it is better to show a clean message explaining the situation. Nothing can be done + * other than rebuilding the SDK or switching to a functional version. + * As a developer, this error probably means that you need to comment out mavenLocal() as a repo. + */ +fun Context.showSharedLibraryCriticalError(e: Throwable): Dialog = showCriticalMessage( + titleResId = R.string.dialog_error_critical_link_title, + messageResId = R.string.dialog_error_critical_link_message, + onDismiss = { throw e } +) diff --git a/app/src/main/java/cash/z/ecc/android/ext/EditText.kt b/app/src/main/java/cash/z/ecc/android/ext/EditText.kt new file mode 100644 index 0000000..6b2b1d1 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/EditText.kt @@ -0,0 +1,83 @@ +package cash.z.ecc.android.ext + +import android.text.Editable +import android.text.TextWatcher +import android.view.inputmethod.EditorInfo.IME_ACTION_DONE +import android.widget.EditText +import android.widget.TextView +import cash.z.ecc.android.sdk.ext.convertZecToZatoshi +import cash.z.ecc.android.sdk.ext.safelyConvertToBigDecimal +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.util.twig + +fun EditText.onEditorActionDone(block: (EditText) -> Unit) { + this.setOnEditorActionListener { _, actionId, _ -> + if (actionId == IME_ACTION_DONE) { + block(this) + true + } else { + false + } + } +} + +inline fun EditText.limitDecimalPlaces(max: Int) { + val editText = this + addTextChangedListener(object : TextWatcher { + var previousValue = "" + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + // Cache the previous value + previousValue = text.toString() + } + + override fun afterTextChanged(s: Editable?) { + var textStr = text.toString() + + if (textStr.isNotEmpty()) { + val oldText = text.toString() + val number = textStr.safelyConvertToBigDecimal() + + if (number != null && number.scale() > 8) { + // Prevent the user from adding a new decimal place somewhere in the middle if we're already at the limit + if (editText.selectionStart == editText.selectionEnd && editText.selectionStart != textStr.length) { + textStr = previousValue + } else { + textStr = WalletZecFormmatter.formatFull(number) + } + } + + // Trim leading zeroes + textStr = textStr.trimStart('0') + // Append a zero if this results in an empty string or if the first symbol is not a digit + if (textStr.isEmpty() || !textStr.first().isDigit()) { + textStr = "0$textStr" + } + + // Restore the cursor position + if (oldText != textStr) { + val cursorPosition = editText.selectionEnd + editText.setText(textStr) + editText.setSelection( + (cursorPosition - (oldText.length - textStr.length)).coerceIn( + 0, + editText.text.toString().length + ) + ) + } + } + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + }) +} + +fun TextView.convertZecToZatoshi(): Zatoshi? { + return try { + text.toString().safelyConvertToBigDecimal()?.convertZecToZatoshi() + } catch (t: Throwable) { + twig("Failed to convert text to Zatoshi: $text") + null + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ext/Extensions.kt b/app/src/main/java/cash/z/ecc/android/ext/Extensions.kt new file mode 100644 index 0000000..4e17819 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/Extensions.kt @@ -0,0 +1,69 @@ +package cash.z.ecc.android.ext + +import android.content.Context +import android.os.Build +import androidx.fragment.app.Fragment +import cash.z.ecc.android.sdk.model.WalletBalance +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.util.Bush +import cash.z.ecc.android.util.Twig +import cash.z.ecc.android.util.twig +import java.util.* +import kotlin.math.roundToInt + +/** + * Distribute a string into evenly-sized chunks and then execute a function with each chunk. + * + * @param chunks the number of chunks to create + * @param block a function to be applied to each zero-indexed chunk. + */ +fun String.distribute(chunks: Int, block: (Int, String) -> T) { + val charsPerChunk = length / chunks.toFloat() + val wholeCharsPerChunk = charsPerChunk.toInt() + val chunksWithExtra = ((charsPerChunk - wholeCharsPerChunk) * chunks).roundToInt() + repeat(chunks) { i -> + val part = if (i < chunksWithExtra) { + substring(i * (wholeCharsPerChunk + 1), (i + 1) * (wholeCharsPerChunk + 1)) + } else { + substring(i * wholeCharsPerChunk + chunksWithExtra, (i + 1) * wholeCharsPerChunk + chunksWithExtra) + } + block(i, part) + } +} + +inline val WalletBalance.pending: Zatoshi + get() = (this.total - this.available) + +inline fun tryWithWarning(message: String = "", block: () -> R): R? { + return try { + block() + } catch (error: Throwable) { + twig("WARNING: $message") + null + } +} + +inline fun failWith(specificErrorType: E, block: () -> R): R { + return try { + block() + } catch (error: Throwable) { + throw specificErrorType + } +} + +inline fun Fragment.locale(): Locale = context?.locale() ?: Locale.getDefault() + +inline fun Context.locale(): Locale { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + resources.configuration.locales.get(0) + } else { + //noinspection deprecation + resources.configuration.locale + } +} + +// TODO: add this to the SDK and if the trunk is a CompositeTwig, search through there before returning null +inline fun Twig.find(): T? { + return if (Bush.trunk::class.java.isAssignableFrom(T::class.java)) Bush.trunk as T + else null +} diff --git a/app/src/main/java/cash/z/ecc/android/ext/Fragment.kt b/app/src/main/java/cash/z/ecc/android/ext/Fragment.kt new file mode 100644 index 0000000..aefe34c --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/Fragment.kt @@ -0,0 +1,9 @@ +package cash.z.ecc.android.ext + +import androidx.fragment.app.Fragment + +/** + * A safer alternative to [Fragment.requireContext], as it avoids leaking Fragment or Activity context + * when Application context is often sufficient. + */ +fun Fragment.requireApplicationContext() = requireContext().applicationContext diff --git a/app/src/main/java/cash/z/ecc/android/ext/Int.kt b/app/src/main/java/cash/z/ecc/android/ext/Int.kt new file mode 100644 index 0000000..da8d5c6 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/Int.kt @@ -0,0 +1,46 @@ +package cash.z.ecc.android.ext + +import android.content.res.Resources +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.annotation.IntegerRes +import androidx.annotation.StringRes +import androidx.core.content.res.ResourcesCompat +import cash.z.ecc.android.ZcashWalletApp + +/** + * Grab a color out of the application resources, using the default theme + */ +@ColorInt +internal inline fun @receiver:ColorRes Int.toAppColor(): Int { + return ResourcesCompat.getColor(ZcashWalletApp.instance.resources, this, ZcashWalletApp.instance.theme) +} + +/** + * Grab a string from the application resources + */ +internal inline fun @receiver:StringRes Int.toAppString(lowercase: Boolean = false): String { + return ZcashWalletApp.instance.getString(this).let { + if (lowercase) it.toLowerCase() else it + } +} + +/** + * Grab a formatted string from the application resources + */ +internal inline fun @receiver:StringRes Int.toAppStringFormatted(vararg formatArgs: Any): String { + return ZcashWalletApp.instance.getString(this, *formatArgs) +} + +/** + * Grab an integer from the application resources + */ +internal inline fun @receiver:IntegerRes Int.toAppInt(): Int { + return ZcashWalletApp.instance.resources.getInteger(this) +} + +fun Float.toPx() = this * Resources.getSystem().displayMetrics.density + +fun Int.toPx() = (this * Resources.getSystem().displayMetrics.density + 0.5f).toInt() + +fun Int.toDp() = (this / Resources.getSystem().displayMetrics.density + 0.5f).toInt() diff --git a/app/src/main/java/cash/z/ecc/android/ext/LifeCycleOwner.kt b/app/src/main/java/cash/z/ecc/android/ext/LifeCycleOwner.kt new file mode 100644 index 0000000..5f4284c --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/LifeCycleOwner.kt @@ -0,0 +1,14 @@ +package cash.z.ecc.android.ext + +import android.view.View +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +fun LifecycleOwner.onClick(view: T, throttle: Long = 250L, block: (T) -> Unit) { + view.clicks().debounce(throttle).onEach { + block(view) + }.launchIn(this.lifecycleScope) +} diff --git a/app/src/main/java/cash/z/ecc/android/ext/Spannable.kt b/app/src/main/java/cash/z/ecc/android/ext/Spannable.kt new file mode 100644 index 0000000..62e8f56 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/Spannable.kt @@ -0,0 +1,20 @@ +package cash.z.ecc.android.ext + +import android.text.Spanned +import android.text.style.ForegroundColorSpan +import androidx.annotation.ColorRes +import androidx.core.text.toSpannable + +fun CharSequence.toColoredSpan(@ColorRes colorResId: Int, coloredPortion: String): CharSequence { + return toSpannable().apply { + val start = this@toColoredSpan.indexOf(coloredPortion) + setSpan(ForegroundColorSpan(colorResId.toAppColor()), start, start + coloredPortion.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } +} + +fun CharSequence.toSplitColorSpan(@ColorRes startColorResId: Int, @ColorRes endColorResId: Int, startColorLength: Int): CharSequence { + return toSpannable().apply { + setSpan(ForegroundColorSpan(startColorResId.toAppColor()), 0, startColorLength - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + setSpan(ForegroundColorSpan(endColorResId.toAppColor()), startColorLength, this@toSplitColorSpan.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ext/View.kt b/app/src/main/java/cash/z/ecc/android/ext/View.kt new file mode 100644 index 0000000..8dc4a2c --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ext/View.kt @@ -0,0 +1,81 @@ +package cash.z.ecc.android.ext + +import android.view.View +import android.view.View.GONE +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import cash.z.ecc.android.ui.MainActivity +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.channelFlow + +fun View.gone() { + visibility = GONE +} + +fun View.invisible() { + visibility = INVISIBLE +} + +fun View.visible() { + visibility = VISIBLE +} + +// NOTE: avoid `visibleIf` function because the false case is ambiguous: would it be gone or invisible? + +fun View.goneIf(isGone: Boolean) { + visibility = if (isGone) GONE else VISIBLE +} + +fun View.invisibleIf(isInvisible: Boolean) { + visibility = if (isInvisible) INVISIBLE else VISIBLE +} + +fun View.disabledIf(isDisabled: Boolean) { + isEnabled = !isDisabled +} + +fun View.transparentIf(isTransparent: Boolean) { + alpha = if (isTransparent) 0.0f else 1.0f +} + +fun View.onClickNavTo(navResId: Int, block: (() -> Any) = {}) { + setOnClickListener { + block() + (context as? MainActivity)?.safeNavigate(navResId) + ?: throw IllegalStateException( + "Cannot navigate from this activity. " + + "Expected MainActivity but found ${context.javaClass.simpleName}" + ) + } +} + +fun View.onClickNavUp(block: (() -> Any) = {}) { + setOnClickListener { + block() + (context as? MainActivity)?.navController?.navigateUp() + ?: throw IllegalStateException( + "Cannot navigate from this activity. " + + "Expected MainActivity but found ${context.javaClass.simpleName}" + ) + } +} + +fun View.onClickNavBack(block: (() -> Any) = {}) { + setOnClickListener { + block() + (context as? MainActivity)?.navController?.popBackStack() + ?: throw IllegalStateException( + "Cannot navigate from this activity. " + + "Expected MainActivity but found ${context.javaClass.simpleName}" + ) + } +} + +fun View.clicks() = channelFlow { + setOnClickListener { + trySend(this@clicks) + } + awaitClose { + setOnClickListener(null) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackConsole.kt b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackConsole.kt new file mode 100644 index 0000000..2acbdbd --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackConsole.kt @@ -0,0 +1,22 @@ +package cash.z.ecc.android.feedback + +import android.util.Log + +class FeedbackConsole : FeedbackCoordinator.FeedbackObserver { + + override fun onMetric(metric: Feedback.Metric) { + log(metric.toString()) + } + + override fun onAction(action: Feedback.Action) { + log(action.toString()) + } + + override fun flush() { + // TODO: flush logs (once we have the real logging in place) + } + + private fun log(message: String) { + Log.d("@TWIG", message) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackFile.kt b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackFile.kt new file mode 100644 index 0000000..19becda --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackFile.kt @@ -0,0 +1,38 @@ +package cash.z.ecc.android.feedback + +import cash.z.ecc.android.ZcashWalletApp +import okio.appendingSink +import okio.buffer +import java.io.File +import java.text.SimpleDateFormat + +class FeedbackFile(fileName: String = "user_log.txt") : + FeedbackCoordinator.FeedbackObserver { + + val file = File("${ZcashWalletApp.instance.filesDir}/logs", fileName) + private val format = SimpleDateFormat("MM-dd HH:mm:ss.SSS") + + override fun initialize(): FeedbackCoordinator.FeedbackObserver = apply { + file.parentFile?.apply { + if (!exists()) mkdirs() + } + } + + override fun onMetric(metric: Feedback.Metric) { + appendToFile(metric.toString()) + } + + override fun onAction(action: Feedback.Action) { + appendToFile(action.toString()) + } + + override fun flush() { + // TODO: be more sophisticated about how we open/close the file. And then flush it here. + } + + private fun appendToFile(message: String) { + file.appendingSink().buffer().use { + it.writeUtf8("${format.format(System.currentTimeMillis())}|\t$message\n") + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/feedback/Report.kt b/app/src/main/java/cash/z/ecc/android/feedback/Report.kt new file mode 100644 index 0000000..9677877 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/feedback/Report.kt @@ -0,0 +1,255 @@ +package cash.z.ecc.android.feedback + +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.sdk.model.BlockHeight + +object Report { + + object Funnel { + sealed class Send(stepName: String, step: Int, vararg properties: Pair) : Feedback.Funnel("send", stepName, step, *properties) { + object AddressPageComplete : Send("addresspagecomplete", 10) + object MemoPageComplete : Send("memopagecomplete", 20) + object ConfirmPageComplete : Send("confirmpagecomplete", 30) + + // Beginning of send + object SendSelected : Send("sendselected", 50) + object SpendingKeyFound : Send("keyfound", 60) + object Creating : Send("creating", 70) + object Cancelled : Send("cancelled", 72) + class Created(id: Long) : Send("created", 80, "id" to id) + object Submitted : Send("submitted", 90) + class Mined(minedHeight: Int) : Send("mined", 100, "minedHeight" to minedHeight) + + // Errors + abstract class Error(stepName: String, step: Int, val errorCode: Int?, val errorMessage: String?, vararg properties: Pair) : Send("error.$stepName", step, "isError" to true, *properties) + object ErrorNotFound : Error("notfound", 51, null, "Key not found") + class ErrorEncoding(errorCode: Int? = null, errorMessage: String? = null) : Error( + "encode", 71, errorCode, errorMessage, + "errorCode" to (errorCode ?: -1), + "errorMessage" to (errorMessage ?: "None") + ) + class ErrorSubmitting(errorCode: Int? = null, errorMessage: String? = null) : Error( + "submit", 81, errorCode, errorMessage, + "errorCode" to (errorCode ?: -1), + "errorMessage" to (errorMessage ?: "None") + ) + } + + sealed class Restore(stepName: String, step: Int, vararg properties: Pair) : Feedback.Funnel("restore", stepName, step, *properties) { + object Initiated : Restore("initiated", 0) + object SeedWordsStarted : Restore("wordsstarted", 10) + class SeedWordCount(wordCount: Int) : Restore("wordsmodified", 15, "seedWordCount" to wordCount) + object SeedWordsCompleted : Restore("wordscompleted", 20) + object Stay : Restore("stay", 21) + object Exit : Restore("stay", 22) + object Done : Restore("doneselected", 30) + object ImportStarted : Restore("importstarted", 40) + object ImportCompleted : Restore("importcompleted", 50) + object Success : Restore("success", 100) + } + + sealed class UserFeedback(stepName: String, step: Int, vararg properties: Pair) : Feedback.Funnel("feedback", stepName, step, *properties) { + object Started : UserFeedback("started", 0) + object Cancelled : UserFeedback("cancelled", 1) + class Submitted(rating: Int, question1: String, question2: String, question3: String, isSolicited: Boolean) : UserFeedback("submitted", 100, "rating" to rating, "question1" to question1, "question2" to question2, "question3" to question3, "isSolicited" to isSolicited) + } + } + + object Error { + object NonFatal { + class Reorg(errorBlockHeight: BlockHeight, rewindBlockHeight: BlockHeight) : Feedback.AppError( + "reorg", + "Chain error detected at height $errorBlockHeight, rewinding to $rewindBlockHeight", + false, + "errorHeight" to errorBlockHeight, + "rewindHeight" to rewindBlockHeight + ) { + val errorHeight: Int by propertyMap + val rewindHeight: Int by propertyMap + } + class TxUpdateFailed(t: Throwable) : Feedback.AppError("txupdate", t, false) + abstract class TxError(action: String, val errorCode: Int?, val errorMessage: String?) : Feedback.AppError( + "tx.$action", + "Failed to $action transaction due to $errorMessage", + false, + "errorCode" to (errorCode ?: 1) + ) + class TxEncodeError(errorCode: Int?, errorMessage: String?) : TxError("encode", errorCode, errorMessage) + class TxSubmitError(errorCode: Int?, errorMessage: String?) : TxError("submit", errorCode, errorMessage) + } + } + + sealed class Performance(name: String, vararg properties: Pair) : Feedback.MappedAction( + "metricName" to name, + "isPerformanceMetric" to true, + *properties + ) { + override val key = "performance.$name" + override fun toString() = "$key: ${toMap().let { if (it.size > 1) "${it.entries}" else "" }}" + + class ScanRate(network: String, cumulativeItems: Int, cumulativeTime: Long, cumulativeIps: Float) : Performance("scan.bps", "network" to network, "totalBlocks" to cumulativeItems, "totalTime" to cumulativeTime, "blocksPerSecond" to cumulativeIps) + } + + // placeholder for things that we want to monitor + sealed class Issue(name: String, vararg properties: Pair) : Feedback.MappedAction( + "issueName" to name, + "isIssue" to true, + *properties + ) { + override val key = "issue.$name" + override fun toString() = "occurrence of ${key.replace('.', ' ')}${toMap().let { if (it.size > 1) " with ${it.entries}" else "" }}" + + // Issues with sending worth monitoring + object SelfSend : Issue("self.send") + object TinyAmount : Issue("tiny.amount") + object MicroAmount : Issue("micro.amount") + object MinimumAmount : Issue("minimum.amount") + class TruncatedMemo(memoSize: Int) : Issue("truncated.memo", "memoSize" to memoSize) + class LargeMemo(memoSize: Int) : Issue("large.memo", "memoSize" to memoSize) + class MissingViewkey(recovered: Boolean, needle: String, haystack: String, hasKey: Boolean) : Issue( + "missing.viewkey", "wasAbleToRecover" to recovered, "needle" to needle, "haystack" to haystack, "hasKey" to hasKey + ) + } + + enum class Screen(val id: String? = null) : Feedback.Action { + BACKUP, + HOME, + HISTORY("wallet.history"), + TRANSACTION("wallet.transaction"), + LANDING, + PROFILE, + AWESOME, + FEEDBACK, + RECEIVE, + RESTORE, + SCAN, + AUTO_SHIELD_FINAL("autoshield.final"), + AUTO_SHIELD_AVAILABLE("autoshield.available"), + AUTO_SHIELD_INFORMATION("autoshield.information"), + SEND_ADDRESS("send.address"), + SEND_CONFIRM("send.confirm"), + SEND_FINAL("send.final"), + SEND_MEMO("send.memo"); + + override val key = "screen.${id ?: name.toLowerCase()}" + override fun toString() = "viewed the ${key.substring(7).replace('.', ' ')} screen" + } + + enum class Tap(val id: String) : Feedback.Action { + BACKUP_DONE("backup.done"), + BACKUP_VERIFY("backup.verify"), + DEVELOPER_WALLET_PROMPT("landing.devwallet.prompt"), + DEVELOPER_WALLET_IMPORT("landing.devwallet.import"), + DEVELOPER_WALLET_CANCEL("landing.devwallet.cancel"), + LANDING_RESTORE("landing.restore"), + LANDING_NEW("landing.new"), + LANDING_BACKUP("landing.backup"), + LANDING_BACKUP_SKIPPED_1("landing.backup.skip.1"), + LANDING_BACKUP_SKIPPED_2("landing.backup.skip.2"), + LANDING_BACKUP_SKIPPED_3("landing.backup.skip.3"), + HOME_PROFILE("home.profile"), + HOME_HISTORY("home.history"), + HOME_RECEIVE("home.receive"), + HOME_BALANCE_DETAIL("home.balance.detail"), + TAB_LAYOUT("tab.layout"), + HOME_SCAN("home.scan"), + HOME_SEND("home.send"), + HOME_FUND_NOW("home.fund.now"), + HOME_CLEAR_AMOUNT("home.clear.amount"), + HISTORY_BACK("history.back"), + TRANSACTION_BACK("transaction.back"), + PROFILE_CLOSE("profile.close"), + AWESOME_OPEN("profile.awesome"), + AWESOME_CLOSE("awesome.close"), + AWESOME_SHIELD("awesome.shield"), + PROFILE_BACKUP("profile.backup"), + PROFILE_RESCAN("profile.rescan"), + PROFILE_VIEW_USER_LOGS("profile.view.user.logs"), + PROFILE_VIEW_DEV_LOGS("profile.view.dev.logs"), + PROFILE_SEND_FEEDBACK("profile.send.feedback"), + FEEDBACK_CANCEL("feedback.cancel"), + FEEDBACK_SUBMIT("feedback.submit"), + RECEIVE_BACK("receive.back"), + RESTORE_DONE("restore.done"), + RESTORE_CLEAR("restore.clear"), + RESTORE_SUCCESS("restore.success"), + RESTORE_BACK("restore.back"), + SCAN_BACK("scan.back"), + AUTO_SHIELD_FINAL_CLOSE("autoshield.final.close"), + AUTO_SHIELD_FINAL_DONE("autoshield.final.done"), + SEND_ADDRESS_MAX("send.address.max"), + SEND_ADDRESS_NEXT("send.address.next"), + SEND_ADDRESS_PASTE("send.address.paste"), + SEND_ADDRESS_REUSE("send.address.reuse"), + SEND_ADDRESS_BACK("send.address.back"), + SEND_ADDRESS_DONE_ADDRESS("send.address.done.address"), + SEND_ADDRESS_DONE_AMOUNT("send.address.done.amount"), + SEND_ADDRESS_SCAN("send.address.scan"), + SEND_CONFIRM_BACK("send.confirm.back"), + SEND_CONFIRM_NEXT("send.confirm.next"), + SEND_FINAL_EXIT("send.final.exit"), + SEND_FINAL_RETRY("send.final.retry"), + SEND_FINAL_CLOSE("send.final.close"), + SEND_MEMO_INCLUDE("send.memo.include"), + SEND_MEMO_EXCLUDE("send.memo.exclude"), + SEND_MEMO_NEXT("send.memo.next"), + SEND_MEMO_SKIP("send.memo.skip"), + SEND_MEMO_CLEAR("send.memo.clear"), + SEND_MEMO_BACK("send.memo.back"), + + SEND_SUBMIT("send.submit"), + + // General events + COPY_ADDRESS("copy.address"), + COPY_TRANSPARENT_ADDRESS("copy.address.transparent"); + + override val key = "tap.$id" + override fun toString() = "${key.replace('.', ' ')} button".replace("tap ", "tapped the ") + } + + enum class NonUserAction(override val key: String, val description: String) : Feedback.Action { + FEEDBACK_STARTED("action.feedback.start", "feedback started"), + FEEDBACK_STOPPED("action.feedback.stop", "feedback stopped"), + SYNC_START("action.feedback.synchronizer.start", "sync started"); + + override fun toString(): String = description + } + + enum class MetricType(override val key: String, val description: String) : Feedback.Action { + ENTROPY_CREATED("metric.entropy.created", "entropy created"), + SEED_CREATED("metric.seed.created", "seed created"), + SEED_IMPORTED("metric.seed.imported", "seed imported"), + SEED_PHRASE_CREATED("metric.seedphrase.created", "seed phrase created"), + SEED_PHRASE_LOADED("metric.seedphrase.loaded", "seed phrase loaded"), + WALLET_CREATED("metric.wallet.created", "wallet created"), + WALLET_IMPORTED("metric.wallet.imported", "wallet imported"), + ACCOUNT_CREATED("metric.account.created", "account created"), + + // Transactions + TRANSACTION_INITIALIZED("metric.tx.initialized", "transaction initialized"), + TRANSACTION_CREATED("metric.tx.created", "transaction created successfully"), + TRANSACTION_SUBMITTED("metric.tx.submitted", "transaction submitted successfully"), + TRANSACTION_MINED("metric.tx.mined", "transaction mined") + } +} + +/** + * Creates a metric with a start time of ZcashWalletApp.creationTime and an end time of when this + * instance was created. This can then be passed to [Feedback.report]. + */ +class LaunchMetric private constructor(private val metric: Feedback.TimeMetric) : + Feedback.Metric by metric { + constructor() : this( + Feedback + .TimeMetric( + "metric.app.launch", + "app launched", + mutableListOf(ZcashWalletApp.instance.creationTime) + ) + .markTime() + ) + override fun toString(): String = metric.toString() +} + +inline fun Feedback.measure(type: Report.MetricType, block: () -> T): T = + this.measure(type.key, type.description, block) diff --git a/app/src/main/java/cash/z/ecc/android/preference/PreferenceKeys.kt b/app/src/main/java/cash/z/ecc/android/preference/PreferenceKeys.kt new file mode 100644 index 0000000..76121bd --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/preference/PreferenceKeys.kt @@ -0,0 +1,6 @@ +package cash.z.ecc.android.preference + +internal object PreferenceKeys { + const val IS_AUTOSHIELDING_INFO_ACKNOWLEDGED = "is_autoshielding_info_acknowledged" + const val LAST_AUTOSHIELDING_PROMPT_EPOCH_MILLIS = "last_autoshielding_epoch_millis" +} diff --git a/app/src/main/java/cash/z/ecc/android/preference/Preferences.kt b/app/src/main/java/cash/z/ecc/android/preference/Preferences.kt new file mode 100644 index 0000000..498ab40 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/preference/Preferences.kt @@ -0,0 +1,12 @@ +package cash.z.ecc.android.preference + +import cash.z.ecc.android.preference.model.BooleanDefaultValue +import cash.z.ecc.android.preference.model.LongDefaultValue + +object Preferences { + val isAcknowledgedAutoshieldingInformationPrompt = + BooleanDefaultValue(PreferenceKeys.IS_AUTOSHIELDING_INFO_ACKNOWLEDGED, false) + + val lastAutoshieldingEpochMillis = + LongDefaultValue(PreferenceKeys.LAST_AUTOSHIELDING_PROMPT_EPOCH_MILLIS, 0) +} diff --git a/app/src/main/java/cash/z/ecc/android/preference/SharedPreferenceFactory.kt b/app/src/main/java/cash/z/ecc/android/preference/SharedPreferenceFactory.kt new file mode 100644 index 0000000..05ee3c3 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/preference/SharedPreferenceFactory.kt @@ -0,0 +1,9 @@ +package cash.z.ecc.android.preference + +import android.content.Context + +object SharedPreferenceFactory { + private const val DEFAULT_SHARED_PREFERENCES = "cash.z.ecc.default" + + fun getSharedPreferences(context: Context) = context.getSharedPreferences(DEFAULT_SHARED_PREFERENCES, Context.MODE_PRIVATE) +} diff --git a/app/src/main/java/cash/z/ecc/android/preference/model/BooleanDefaultValue.kt b/app/src/main/java/cash/z/ecc/android/preference/model/BooleanDefaultValue.kt new file mode 100644 index 0000000..c9fa00e --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/preference/model/BooleanDefaultValue.kt @@ -0,0 +1,35 @@ +package cash.z.ecc.android.preference.model + +import android.content.Context +import android.content.SharedPreferences +import cash.z.ecc.android.preference.SharedPreferenceFactory + +/** + * A default value represents a preference key, along with its default value. It does not, by itself, + * know how to read or write values from the preference repository. + */ +data class BooleanDefaultValue(override val key: String, internal val defaultValue: Boolean) : + DefaultValue { + init { + require(key.isNotEmpty()) + } +} + +fun BooleanDefaultValue.get(context: Context) = get( + SharedPreferenceFactory.getSharedPreferences( + context + ) +) + +internal fun BooleanDefaultValue.get(sharedPreferences: SharedPreferences) = + sharedPreferences.getBoolean(key, defaultValue) + +fun BooleanDefaultValue.put(context: Context, newValue: Boolean) = put( + SharedPreferenceFactory.getSharedPreferences( + context + ), + newValue +) + +internal fun BooleanDefaultValue.put(sharedPreferences: SharedPreferences, newValue: Boolean) = + sharedPreferences.edit().putBoolean(key, newValue).apply() diff --git a/app/src/main/java/cash/z/ecc/android/preference/model/DefaultValue.kt b/app/src/main/java/cash/z/ecc/android/preference/model/DefaultValue.kt new file mode 100644 index 0000000..e657a5f --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/preference/model/DefaultValue.kt @@ -0,0 +1,23 @@ +package cash.z.ecc.android.preference.model + +/** + * A key and a default value for a key-value store of preferences. + * + * Use of this interface avoids duplication or accidental variation in default value, because key + * and default are defined together just once. + * + * Note that T is not fully generic and should be one of the supported types: Boolean, ... (other types to be added in the future) + * + * @see BooleanDefaultValue + */ +/* + * Although primitives would be nice, Objects don't increase memory usage much + * because of the autoboxing cache on the JVM. For example, Boolean's true/false values + * are cached. + */ +interface DefaultValue { + // Note: the default value is not available through the public interface in order to prevent + // clients from accidentally using the default value instead of the stored value. + + val key: String +} diff --git a/app/src/main/java/cash/z/ecc/android/preference/model/LongDefaultValue.kt b/app/src/main/java/cash/z/ecc/android/preference/model/LongDefaultValue.kt new file mode 100644 index 0000000..1b5c6e9 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/preference/model/LongDefaultValue.kt @@ -0,0 +1,33 @@ +package cash.z.ecc.android.preference.model + +import android.content.Context +import android.content.SharedPreferences +import cash.z.ecc.android.preference.SharedPreferenceFactory + +/** + * A default value represents a preference key, along with its default value. It does not, by itself, + * know how to read or write values from the preference repository. + */ +data class LongDefaultValue( + override val key: String, + internal val defaultValue: Long +) : DefaultValue { + init { + require(key.isNotEmpty()) + } +} + +fun LongDefaultValue.get(context: Context) = get( + SharedPreferenceFactory.getSharedPreferences(context) +) + +internal fun LongDefaultValue.get(sharedPreferences: SharedPreferences) = + sharedPreferences.getLong(key, defaultValue) + +fun LongDefaultValue.put(context: Context, newValue: Long) = put( + SharedPreferenceFactory.getSharedPreferences(context), + newValue +) + +internal fun LongDefaultValue.put(sharedPreferences: SharedPreferences, newValue: Long) = + sharedPreferences.edit().putLong(key, newValue).apply() diff --git a/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt b/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt new file mode 100644 index 0000000..cd65c76 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt @@ -0,0 +1,669 @@ +package cash.z.ecc.android.ui + +import android.Manifest +import android.app.Dialog +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.media.MediaPlayer +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Vibrator +import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager +import android.widget.TextView +import android.widget.Toast +import androidx.activity.OnBackPressedCallback +import androidx.activity.viewModels +import androidx.annotation.IdRes +import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatActivity +import androidx.biometric.BiometricManager.Authenticators.* +import androidx.biometric.BiometricPrompt +import androidx.biometric.BiometricPrompt.* +import androidx.core.content.ContextCompat +import androidx.core.content.getSystemService +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavController +import androidx.navigation.NavDirections +import androidx.navigation.Navigator +import androidx.navigation.findNavController +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.databinding.DialogFirstUseMessageBinding +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.* +import cash.z.ecc.android.feedback.Feedback +import cash.z.ecc.android.feedback.FeedbackCoordinator +import cash.z.ecc.android.feedback.LaunchMetric +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Error.NonFatal.Reorg +import cash.z.ecc.android.feedback.Report.NonUserAction.FEEDBACK_STOPPED +import cash.z.ecc.android.feedback.Report.NonUserAction.SYNC_START +import cash.z.ecc.android.feedback.Report.Tap.COPY_ADDRESS +import cash.z.ecc.android.feedback.Report.Tap.COPY_TRANSPARENT_ADDRESS +import cash.z.ecc.android.sdk.SdkSynchronizer +import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction +import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException +import cash.z.ecc.android.sdk.ext.BatchMetrics +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress +import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.ui.history.HistoryViewModel +import cash.z.ecc.android.ui.util.MemoUtil +import cash.z.ecc.android.util.twig +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.snackbar.Snackbar +import kotlinx.coroutines.launch + +class MainActivity : AppCompatActivity(R.layout.main_activity) { + + val mainViewModel: MainViewModel by viewModels() + + val feedback: Feedback = DependenciesHolder.feedback + + val feedbackCoordinator: FeedbackCoordinator = DependenciesHolder.feedbackCoordinator + + val clipboard: ClipboardManager = DependenciesHolder.clipboardManager + + val historyViewModel: HistoryViewModel by viewModels() + + private var syncStarted = false + + private val mediaPlayer: MediaPlayer = MediaPlayer() + private var snackbar: Snackbar? = null + private var dialog: Dialog? = null + private var ignoreScanFailure: Boolean = false + + var navController: NavController? = null + private val navInitListeners: MutableList<() -> Unit> = mutableListOf() + + private val hasCameraPermission + get() = ContextCompat.checkSelfPermission( + this, + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + + val latestHeight: BlockHeight? + get() = DependenciesHolder.synchronizer.latestHeight + + override fun onCreate(savedInstanceState: Bundle?) { + lifecycleScope.launch { + feedback.start() + } + super.onCreate(savedInstanceState) + + initNavigation() + initLoadScreen() + + window.statusBarColor = Color.TRANSPARENT + window.navigationBarColor = Color.TRANSPARENT + window.setFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + ) + setWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, false) + setWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, false) + } + + override fun onResume() { + super.onResume() + // keep track of app launch metrics + // (how long does it take the app to open when it is not already in the foreground) + ZcashWalletApp.instance.let { app -> + if (!app.creationMeasured) { + app.creationMeasured = true + feedback.report(LaunchMetric()) + } + } + } + + override fun onDestroy() { + lifecycleScope.launch { + feedback.report(FEEDBACK_STOPPED) + feedback.stop() + } + super.onDestroy() + } + + private fun setWindowFlag(bits: Int, on: Boolean) { + val win = window + val winParams = win.attributes + if (on) { + winParams.flags = winParams.flags or bits + } else { + winParams.flags = winParams.flags and bits.inv() + } + win.attributes = winParams + } + + private fun initNavigation() { + navController = findNavController(R.id.nav_host_fragment) + navController!!.addOnDestinationChangedListener { _, _, _ -> + // hide the keyboard anytime we change destinations + getSystemService()?.hideSoftInputFromWindow( + this@MainActivity.window.decorView.rootView.windowToken, + InputMethodManager.HIDE_NOT_ALWAYS + ) + } + + for (listener in navInitListeners) { + listener() + } + navInitListeners.clear() + } + + private fun initLoadScreen() { + lifecycleScope.launchWhenResumed { + mainViewModel.loadingMessage.collect { message -> + onLoadingMessage(message) + } + } + } + + private fun onLoadingMessage(message: String?) { + twig("Applying loading message: $message") + // TODO: replace with view binding + findViewById(R.id.container_loading).goneIf(message == null) + findViewById(R.id.text_message).text = message + } + + fun popBackTo(@IdRes destination: Int, inclusive: Boolean = false) { + navController?.popBackStack(destination, inclusive) + } + + fun safeNavigate(navDirections: NavDirections) = + safeNavigate(navDirections.actionId, navDirections.arguments, null) + + fun safeNavigate( + @IdRes destination: Int, + args: Bundle? = null, + extras: Navigator.Extras? = null + ) { + if (navController == null) { + navInitListeners.add { + try { + navController?.navigate(destination, args, null, extras) + } catch (t: Throwable) { + twig( + "WARNING: during callback, did not navigate to destination: R.id.${ + resources.getResourceEntryName( + destination + ) + } due to: $t" + ) + } + } + } else { + try { + navController?.navigate(destination, args, null, extras) + } catch (t: Throwable) { + twig( + "WARNING: did not immediately navigate to destination: R.id.${ + resources.getResourceEntryName( + destination + ) + } due to: $t" + ) + } + } + } + + fun startSync(isRestart: Boolean = false) { + twig("MainActivity.startSync") + if (!syncStarted || isRestart) { + syncStarted = true + mainViewModel.setLoading(true) + feedback.report(SYNC_START) + DependenciesHolder.synchronizer.let { synchronizer -> + synchronizer.onProcessorErrorHandler = ::onProcessorError + synchronizer.onChainErrorHandler = ::onChainError + synchronizer.onCriticalErrorHandler = ::onCriticalError + (synchronizer as SdkSynchronizer).processor.onScanMetricCompleteListener = + ::onScanMetricComplete + + synchronizer.start(lifecycleScope) + mainViewModel.setSyncReady(true) + } + } else { + twig("Ignoring request to start sync because sync has already been started!") + } + mainViewModel.setLoading(false) + twig("MainActivity.startSync COMPLETE") + } + + private fun onScanMetricComplete(batchMetrics: BatchMetrics, isComplete: Boolean) { + val reportingThreshold = 100 + if (isComplete) { + if (batchMetrics.cumulativeItems > reportingThreshold) { + val network = DependenciesHolder.synchronizer.network.networkName + reportAction( + Report.Performance.ScanRate( + network, + batchMetrics.cumulativeItems.toInt(), + batchMetrics.cumulativeTime, + batchMetrics.cumulativeIps + ) + ) + } + } + } + + private fun onCriticalError(error: Throwable?): Boolean { + val errorMessage = error?.message + ?: error?.cause?.message + ?: error?.toString() + ?: "A critical error has occurred but no details were provided. Please report and consider submitting logs to help track this one down." + showCriticalMessage( + title = "Unrecoverable Error", + message = errorMessage, + ) { + throw error ?: RuntimeException("A critical error occurred but it was null") + } + return false + } + + fun reportScreen(screen: Report.Screen?) = reportAction(screen) + + fun reportTap(tap: Report.Tap?) = reportAction(tap) + + fun reportFunnel(step: Feedback.Funnel?) = reportAction(step) + + private fun reportAction(action: Feedback.Action?) { + action?.let { feedback.report(it) } + } + + fun setLoading(isLoading: Boolean, message: String? = null) { + mainViewModel.setLoading(isLoading, message) + } + + fun authenticate( + description: String, + title: String = getString(R.string.biometric_prompt_title), + block: () -> Unit + ) { + val callback = object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + twig("Authentication success with type: ${if (result.authenticationType == AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL) "DEVICE_CREDENTIAL" else if (result.authenticationType == AUTHENTICATION_RESULT_TYPE_BIOMETRIC) "BIOMETRIC" else "UNKNOWN"} object: ${result.cryptoObject}") + block() + twig("Done authentication block") + // we probably only need to do this if the type is DEVICE_CREDENTIAL + // but it doesn't hurt to hide the keyboard every time + hideKeyboard() + } + + override fun onAuthenticationFailed() { + twig("Authentication failed!!!!") + showMessage("Authentication failed :(") + } + + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + twig("Authentication Error") + fun doNothing(message: String, interruptUser: Boolean = true) { + if (interruptUser) { + showSnackbar(message) + } else { + showMessage(message, true) + } + } + when (errorCode) { + ERROR_HW_NOT_PRESENT, ERROR_HW_UNAVAILABLE, + ERROR_NO_BIOMETRICS, ERROR_NO_DEVICE_CREDENTIAL -> { + twig("Warning: bypassing authentication because $errString [$errorCode]") + showMessage( + "Please enable screen lock on this device to add security here!", + true + ) + block() + } + ERROR_LOCKOUT -> doNothing("Too many attempts. Try again in 30s.") + ERROR_LOCKOUT_PERMANENT -> doNothing("Whoa. Waaaay too many attempts!") + ERROR_CANCELED -> doNothing("I just can't right now. Please try again.") + ERROR_NEGATIVE_BUTTON -> doNothing("Authentication cancelled", false) + ERROR_USER_CANCELED -> doNothing("Cancelled", false) + ERROR_NO_SPACE -> doNothing("Not enough storage space!") + ERROR_TIMEOUT -> doNothing("Oops. It timed out.") + ERROR_UNABLE_TO_PROCESS -> doNothing(".") + ERROR_VENDOR -> doNothing("We got some weird error and you should report this.") + else -> { + twig("Warning: unrecognized authentication error $errorCode") + doNothing("Authentication failed with error code $errorCode") + } + } + } + } + + BiometricPrompt(this, ContextCompat.getMainExecutor(this), callback).apply { + authenticate( + BiometricPrompt.PromptInfo.Builder() + .setTitle(title) + .setConfirmationRequired(false) + .setDescription(description) + .setAllowedAuthenticators(BIOMETRIC_STRONG or BIOMETRIC_WEAK or DEVICE_CREDENTIAL) + .build() + ) + } + } + + fun playSound(fileName: String) { + mediaPlayer.apply { + if (isPlaying) stop() + try { + reset() + assets.openFd(fileName).let { afd -> + setDataSource(afd.fileDescriptor, afd.startOffset, afd.length) + } + prepare() + start() + } catch (t: Throwable) { + Log.e("SDK_ERROR", "ERROR: unable to play sound due to $t") + } + } + } + + // TODO: spruce this up with API 26 stuff + fun vibrateSuccess() = vibrate(0, 200, 200, 100, 100, 800) + + fun vibrate(initialDelay: Long, vararg durations: Long) { + val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + if (vibrator.hasVibrator()) { + vibrator.vibrate(longArrayOf(initialDelay, *durations), -1) + } + } + + fun copyAddress(view: View? = null) { + reportTap(COPY_ADDRESS) + lifecycleScope.launch { + copyText(DependenciesHolder.synchronizer.getAddress(), "Address") + } + } + + fun copyTransparentAddress(view: View? = null) { + reportTap(COPY_TRANSPARENT_ADDRESS) + lifecycleScope.launch { + copyText(DependenciesHolder.synchronizer.getTransparentAddress(), "T-Address") + } + } + + fun copyText(textToCopy: String, label: String = "ECC Wallet Text") { + clipboard.setPrimaryClip( + ClipData.newPlainText(label, textToCopy) + ) + showMessage("$label copied!") + vibrate(0, 50) + } + + fun shareText(textToShare: String) { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, textToShare) + } + + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) + } + + suspend fun isValidAddress(address: String): Boolean { + try { + return !DependenciesHolder.synchronizer.validateAddress(address).isNotValid + } catch (t: Throwable) { + } + return false + } + + fun preventBackPress(fragment: Fragment) { + onFragmentBackPressed(fragment) {} + } + + fun onFragmentBackPressed(fragment: Fragment, block: () -> Unit) { + onBackPressedDispatcher.addCallback( + fragment, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + block() + } + } + ) + } + + private fun showMessage(message: String, linger: Boolean = false) { + twig("toast: $message") + Toast.makeText(this, message, if (linger) Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show() + } + + fun showSnackbar( + message: String, + actionLabel: String = getString(android.R.string.ok), + action: () -> Unit = {} + ): Snackbar { + return if (snackbar == null) { + val view = findViewById(R.id.main_activity_container) + val snacks = Snackbar + .make(view, "$message", Snackbar.LENGTH_INDEFINITE) + .setAction(actionLabel) { action() } + + val snackBarView = snacks.view as ViewGroup + val navigationBarHeight = resources.getDimensionPixelSize( + resources.getIdentifier( + "navigation_bar_height", + "dimen", + "android" + ) + ) + val params = snackBarView.getChildAt(0).layoutParams as ViewGroup.MarginLayoutParams + params.setMargins( + params.leftMargin, + params.topMargin, + params.rightMargin, + navigationBarHeight + ) + + snackBarView.getChildAt(0).setLayoutParams(params) + snacks + } else { + snackbar!!.setText(message).setAction(actionLabel) { action() } + }.also { + if (!it.isShownOrQueued) it.show() + } + } + + fun showKeyboard(focusedView: View) { + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(focusedView, InputMethodManager.SHOW_FORCED) + } + + fun hideKeyboard() { + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(findViewById(android.R.id.content).windowToken, 0) + } + + /** + * @param popUpToInclusive the destination to remove from the stack before opening the camera. + * This only takes effect in the common case where the permission is granted. + */ + fun maybeOpenScan(popUpToInclusive: Int? = null) { + if (hasCameraPermission) { + openCamera(popUpToInclusive) + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestPermissions(arrayOf(Manifest.permission.CAMERA), 101) + } else { + onNoCamera() + } + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == 101) { + if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) { + openCamera() + } else { + onNoCamera() + } + } + } + + private fun openCamera(popUpToInclusive: Int? = null) { + navController?.navigate(popUpToInclusive ?: R.id.action_global_nav_scan) + } + + private fun onNoCamera() { + showSnackbar(getString(R.string.camera_permission_denied)) + } + + // TODO: clean up this error handling + private var ignoredErrors = 0 + private fun onProcessorError(error: Throwable?): Boolean { + var notified = false + when (error) { + is CompactBlockProcessorException.Uninitialized -> { + if (dialog == null) { + notified = true + runOnUiThread { + dialog = showUninitializedError(error) { + dialog = null + } + } + } + } + is CompactBlockProcessorException.FailedScan -> { + if (dialog == null && !ignoreScanFailure) throttle("scanFailure", 20_000L) { + notified = true + runOnUiThread { + dialog = showScanFailure( + error, + onCancel = { dialog = null }, + onDismiss = { dialog = null } + ) + } + } + } + } + if (!notified) { + ignoredErrors++ + if (ignoredErrors >= ZcashSdk.RETRIES) { + if (dialog == null) { + notified = true + runOnUiThread { + dialog = showCriticalProcessorError(error) { + dialog = null + } + } + } + } + } + twig("MainActivity has received an error${if (notified) " and notified the user" else ""} and reported it to bugsnag and mixpanel.") + feedback.report(error) + return true + } + + private fun onChainError(errorHeight: BlockHeight, rewindHeight: BlockHeight) { + feedback.report(Reorg(errorHeight, rewindHeight)) + } + + // TODO: maybe move this quick helper code somewhere general or throttle the dialogs differently (like with a flow and stream operators, instead) + + private val throttles = mutableMapOf Any>() + private val noWork = {} + private fun throttle(key: String, delay: Long, block: () -> Any) { + // if the key exists, just add the block to run later and exit + if (throttles.containsKey(key)) { + throttles[key] = block + return + } + block() + + // after doing the work, check back in later and if another request came in, throttle it, otherwise exit + throttles[key] = noWork + findViewById(android.R.id.content).postDelayed( + { + throttles[key]?.let { pendingWork -> + throttles.remove(key) + if (pendingWork !== noWork) throttle(key, delay, pendingWork) + } + }, + delay + ) + } + + /* Memo functions that might possibly get moved to MemoUtils */ + + suspend fun getSender(transaction: ConfirmedTransaction?): String { + if (transaction == null) return getString(R.string.unknown) + return MemoUtil.findAddressInMemo(transaction, ::isValidAddress)?.toAbbreviatedAddress() + ?: getString(R.string.unknown) + } + + suspend fun String?.validateAddress(): String? { + if (this == null) return null + return if (isValidAddress(this)) this else null + } + + fun showFirstUseWarning( + prefKey: String, + @StringRes titleResId: Int = R.string.blank, + @StringRes msgResId: Int = R.string.blank, + @StringRes positiveResId: Int = android.R.string.ok, + @StringRes negativeResId: Int = android.R.string.cancel, + action: MainActivity.() -> Unit = {} + ) { + historyViewModel.prefs.getBoolean(prefKey).let { doNotWarnAgain -> + if (doNotWarnAgain) { + action() + return@showFirstUseWarning + } + } + + val dialogViewBinding = DialogFirstUseMessageBinding.inflate(layoutInflater) + + fun savePref() { + dialogViewBinding.dialogFirstUseCheckbox.isChecked.let { wasChecked -> + historyViewModel.prefs.setBoolean(prefKey, wasChecked) + } + } + + dialogViewBinding.dialogMessage.setText(msgResId) + if (dialog != null) dialog?.dismiss() + // TODO: This should be moved to a DialogFragment, otherwise unmanaged dialogs go away during Activity configuration changes + dialog = MaterialAlertDialogBuilder(this) + .setTitle(titleResId) + .setView(dialogViewBinding.root) + .setCancelable(false) + .setPositiveButton(positiveResId) { d, _ -> + d.dismiss() + dialog = null + savePref() + action() + } + .setNegativeButton(negativeResId) { d, _ -> + d.dismiss() + dialog = null + savePref() + } + .show() + } + + fun onLaunchUrl(url: String) { + try { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) + } catch (t: Throwable) { + showMessage(getString(R.string.error_launch_url)) + twig("Warning: failed to open browser due to $t") + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/MainViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/MainViewModel.kt new file mode 100644 index 0000000..a8a9198 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/MainViewModel.kt @@ -0,0 +1,36 @@ +package cash.z.ecc.android.ui + +import androidx.lifecycle.ViewModel +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class MainViewModel : ViewModel() { + private val _loadingMessage = MutableStateFlow("\u23F3 Loading...") + private val _syncReady = MutableStateFlow(false) + val loadingMessage: StateFlow get() = _loadingMessage + val isLoading get() = loadingMessage.value != null + + /** + * A flow of booleans representing whether or not the synchronizer has been started. This is + * useful for views that want to monitor the status of the wallet but don't want to access the + * synchronizer before it is ready to be used. This is also helpful for race conditions where + * the status of the synchronizer is needed before it is created. + */ + val syncReady = _syncReady.asStateFlow() + + fun setLoading(isLoading: Boolean = false, message: String? = null) { + twig("MainViewModel.setLoading: $isLoading") + _loadingMessage.value = if (!isLoading) { + null + } else { + message ?: "\u23F3 Loading..." + } + } + + fun setSyncReady(isReady: Boolean) { + twig("MainViewModel.setSyncReady: $isReady") + _syncReady.value = isReady + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/base/BaseFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/base/BaseFragment.kt new file mode 100644 index 0000000..54abb57 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/base/BaseFragment.kt @@ -0,0 +1,95 @@ +package cash.z.ecc.android.ui.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.NonNull +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.viewbinding.ViewBinding +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.ui.MainActivity +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +abstract class BaseFragment : Fragment() { + // Normally will be of type MainActivity, but will be null when run under automated tests. + // A future enhancement would be to move analytics. For example, refactor it out of the Activity + // so that we don't have to cast. Or at least put analytics into an interface, so that we're more + // explicitly casting to Analytics rather than MainActivity. + val mainActivity: MainActivity? get() = if (activity is MainActivity) { + activity as MainActivity + } else { + null + } + + lateinit var binding: T + + lateinit var resumedScope: CoroutineScope + + open val screen: Report.Screen? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = inflate(inflater) + return binding.root + } + + override fun onResume() { + super.onResume() + mainActivity?.reportScreen(screen) + resumedScope = lifecycleScope.coroutineContext.let { + CoroutineScope(Dispatchers.Main + SupervisorJob(it[Job])) + } + } + + override fun onPause() { + super.onPause() + resumedScope.cancel() + } + + // inflate is static in the ViewBinding class so we can't handle this ourselves + // each fragment must call FragmentMyLayoutBinding.inflate(inflater) + abstract fun inflate(@NonNull inflater: LayoutInflater): T + + fun onBackPressNavTo(navResId: Int, block: (() -> Unit) = {}) { + mainActivity?.onFragmentBackPressed(this) { + block() + mainActivity?.safeNavigate(navResId) + } + } + + fun tapped(tap: Report.Tap) { + mainActivity?.reportTap(tap) + } + + /** + * Launch the given block once, within the 'resumedScope', once the Synchronizer is ready. This + * utility function helps solve the problem of taking action with the synchronizer before it + * is created. This surfaced while loading keys from secure storage: the HomeFragment would + * resume and start monitoring the synchronizer for changes BEFORE the onAttach function + * returned, meaning before the synchronizerComponent is created. So a state variable needed to + * exist with a longer lifecycle than the synchronizer. This function just takes care of all the + * boilerplate of monitoring that state variable until it returns true. + */ + fun launchWhenSyncReady(block: () -> Unit) { + resumedScope.launch { + mainActivity?.let { + it.mainViewModel.syncReady.filter { isReady -> isReady }.onEach { + block() + }.first() + } + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/history/HistoryFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/history/HistoryFragment.kt new file mode 100644 index 0000000..f7bf53a --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/history/HistoryFragment.kt @@ -0,0 +1,103 @@ +package cash.z.ecc.android.ui.history + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import androidx.paging.PagedList +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentHistoryBinding +import cash.z.ecc.android.ext.* +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Tap.HISTORY_BACK +import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction +import cash.z.ecc.android.sdk.ext.collectWith +import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress +import cash.z.ecc.android.sdk.model.WalletBalance +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.launch + +class HistoryFragment : BaseFragment() { + override val screen = Report.Screen.HISTORY + + private val viewModel: HistoryViewModel by activityViewModels() + + private lateinit var transactionAdapter: TransactionAdapter + + override fun inflate(inflater: LayoutInflater): FragmentHistoryBinding = + FragmentHistoryBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + twig("HistoryFragment.onViewCreated") + super.onViewCreated(view, savedInstanceState) + initTransactionUI() + binding.backButtonHitArea.onClickNavUp { tapped(HISTORY_BACK) } + lifecycleScope.launch { + binding.textAddress.text = viewModel.getAddress().toAbbreviatedAddress(10, 10) + } + } + + override fun onResume() { + twig("HistoryFragment.onResume") + super.onResume() + viewModel.balance.filterNotNull().collectWith(resumedScope) { + onBalanceUpdated(it) + } + viewModel.transactions.collectWith(resumedScope) { onTransactionsUpdated(it) } + } + + private fun onBalanceUpdated(balance: WalletBalance) { + if (balance.available.value < 0) { + binding.textBalanceAvailable.text = "Updating" + return + } + + binding.textBalanceAvailable.text = WalletZecFormmatter.toZecStringShort(balance.available) + val change = balance.pending + binding.textBalanceDescription.apply { + goneIf(change.value <= 0L) + val changeString = WalletZecFormmatter.toZecStringFull(change) + val expecting = R.string.home_banner_expecting.toAppString(true) + val symbol = getString(R.string.symbol) + text = "($expecting +$changeString $symbol)".toColoredSpan(R.color.text_light, "+$changeString") + } + } + + private fun initTransactionUI() { + twig("HistoryFragment.initTransactionUI") + transactionAdapter = TransactionAdapter() + transactionAdapter.stateRestorationPolicy = + RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY + + binding.recyclerTransactions.apply { + layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false) + adapter = transactionAdapter + } + } + + private fun onTransactionsUpdated(transactions: List) { + twig("HistoryFragment.onTransactionsUpdated") + transactions.size.let { newCount -> + twig("got a new paged list of transactions of length $newCount") + binding.groupEmptyViews.goneIf(newCount > 0) + + // tricky: we handle two types of lists, empty and PagedLists. It's not easy to construct an empty PagedList so the SDK currently returns an emptyList() but that will not cast to a PagedList + if (newCount == 0) { + transactionAdapter.submitList(null) + } else { + // tricky: for now, explicitly fail (cast exception) if the transactions are not in a PagedList. Otherwise, this would silently fail to show items and be hard to debug if we're ever passed a non-empty list that isn't an instance of PagedList. This awkwardness will go away when we switch to Paging3 + transactionAdapter.submitList(transactions as PagedList) + } + } + } + + // TODO: maybe implement this for better fade behavior. Or do an actual scroll behavior instead, yeah do that. Or an item decoration. + fun onLastItemShown(item: ConfirmedTransaction, position: Int) { + binding.footerFade.alpha = position.toFloat() / (binding.recyclerTransactions.adapter?.itemCount ?: 1) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/history/HistoryViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/history/HistoryViewModel.kt new file mode 100644 index 0000000..b27557a --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/history/HistoryViewModel.kt @@ -0,0 +1,179 @@ +package cash.z.ecc.android.ui.history + +import android.text.format.DateUtils +import androidx.annotation.StringRes +import androidx.lifecycle.ViewModel +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.WalletZecFormmatter +import cash.z.ecc.android.ext.toAppString +import cash.z.ecc.android.ext.toAppStringFormatted +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.SdkSynchronizer +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction +import cash.z.ecc.android.sdk.db.entity.valueInZatoshi +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.ui.util.MemoUtil +import cash.z.ecc.android.ui.util.toUtf8Memo +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map + +class HistoryViewModel : ViewModel() { + + private val synchronizer: Synchronizer = DependenciesHolder.synchronizer + + val prefs: LockBox = DependenciesHolder.prefs + + val selectedTransaction = MutableStateFlow(null) + val uiModels = selectedTransaction.map { it.toUiModel() } + + val transactions get() = synchronizer.clearedTransactions + val balance get() = synchronizer.saplingBalances + val latestHeight get() = synchronizer.latestHeight + + suspend fun getAddress() = synchronizer.getAddress() + + override fun onCleared() { + super.onCleared() + twig("HistoryViewModel cleared!") + } + + // + // History Item UiModel + // + + data class UiModel( + var topLabel: String = "", + var topValue: String = "", + var bottomLabel: String = "", + var bottomValue: String = "", + var minedHeight: String = "", + var timestamp: String = "", + var iconRotation: Float = -1f, + + var fee: String? = null, + var source: String? = null, + var memo: String? = null, + var address: String? = null, + var isInbound: Boolean? = null, + var isMined: Boolean = false, + var confirmation: String? = null, + var txId: String? = null + ) + + private suspend fun ConfirmedTransaction?.toUiModel(latestHeight: Int? = null): UiModel = + UiModel().apply { + this@toUiModel.let { tx -> + txId = toTxId(tx?.rawTransactionId) + isInbound = when { + !(tx?.toAddress.isNullOrEmpty()) -> false + tx != null && tx.toAddress.isNullOrEmpty() && tx.value > 0L && tx.minedHeight > 0 -> true + else -> null + } + isMined = + tx?.minedHeight != null && tx.minedHeight > synchronizer.network.saplingActivationHeight.value + topValue = + if (tx == null) "" else "\$${WalletZecFormmatter.toZecStringFull(tx.valueInZatoshi)}" + minedHeight = String.format("%,d", tx?.minedHeight ?: 0) + val flags = + DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR or DateUtils.FORMAT_ABBREV_MONTH + timestamp = + if (tx == null || tx.blockTimeInSeconds <= 0) getString(R.string.transaction_timestamp_unavailable) else DateUtils.getRelativeDateTimeString( + ZcashWalletApp.instance, + tx.blockTimeInSeconds * 1000, + DateUtils.SECOND_IN_MILLIS, + DateUtils.WEEK_IN_MILLIS, + flags + ).toString() + + // memo logic + val txMemo = tx?.memo.toUtf8Memo() + if (!txMemo.isEmpty()) { + memo = txMemo + } + + // confirmation logic + // TODO: clean all of this up and remove/improve reliance on `isSufficientlyOld` function. Also, add a constant for the number of confirmations we expect. + tx?.let { + val isMined = it.blockTimeInSeconds != 0L + if (isMined) { + val hasLatestHeight = + latestHeight != null && latestHeight > synchronizer.network.saplingActivationHeight.value + if (it.minedHeight > 0 && hasLatestHeight) { + val confirmations = latestHeight!! - it.minedHeight + 1 + confirmation = + if (confirmations >= 10) getString(R.string.transaction_status_confirmed) else "$confirmations ${ + getString( + R.string.transaction_status_confirming + ) + }" + } else { + if (!hasLatestHeight && isSufficientlyOld(tx)) { + twig("Warning: could not load latestheight from server to determine confirmations but this transaction is mined and old enough to be considered confirmed") + confirmation = getString(R.string.transaction_status_confirmed) + } else { + twig("Warning: could not determine confirmation text value so it will be left null!") + confirmation = + getString(R.string.transaction_confirmation_count_unavailable) + } + } + } else { + confirmation = getString(R.string.transaction_status_pending) + } + } + + when (isInbound) { + true -> { + topLabel = getString(R.string.transaction_story_inbound) + bottomLabel = getString(R.string.transaction_story_inbound_total) + bottomValue = "\$${WalletZecFormmatter.toZecStringFull(tx?.valueInZatoshi)}" + iconRotation = 315f + source = getString(R.string.transaction_story_to_shielded) + address = MemoUtil.findAddressInMemo( + tx, + (synchronizer as SdkSynchronizer)::isValidAddress + ) + } + false -> { + topLabel = getString(R.string.transaction_story_outbound) + bottomLabel = getString(R.string.transaction_story_outbound_total) + bottomValue = + "\$${WalletZecFormmatter.toZecStringFull(Zatoshi((tx?.valueInZatoshi?.value ?: 0) + ZcashSdk.MINERS_FEE.value))}" + iconRotation = 135f + fee = "+ 0.00001 network fee" + source = getString(R.string.transaction_story_from_shielded) + address = tx?.toAddress + } + null -> { + twig("Error: transaction appears to be invalid.") + } + } + } + } + + private fun getString(@StringRes id: Int) = id.toAppString() + private fun getString(@StringRes id: Int, vararg args: Any) = id.toAppStringFormatted(args) + + private fun toTxId(tx: ByteArray?): String? { + if (tx == null) return null + val sb = StringBuilder(tx.size * 2) + for (i in (tx.size - 1) downTo 0) { + sb.append(String.format("%02x", tx[i])) + } + return sb.toString() + } + + // TODO: determine this in a more generic and technically correct way. For now, this is good enough. + // the goal is just to improve the edge cases where the latest height isn't known but other + // information suggests that the TX is confirmed. We can improve this, later. + private fun isSufficientlyOld(tx: ConfirmedTransaction): Boolean { + val threshold = 75 * 1000 * 25 // approx 25 blocks + val delta = System.currentTimeMillis() / 1000L - tx.blockTimeInSeconds + return tx.minedHeight > synchronizer.network.saplingActivationHeight.value && + delta < threshold + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/history/TransactionAdapter.kt b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionAdapter.kt new file mode 100644 index 0000000..9da0ab6 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionAdapter.kt @@ -0,0 +1,46 @@ +package cash.z.ecc.android.ui.history + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.paging.PagedListAdapter +import androidx.recyclerview.widget.DiffUtil +import cash.z.ecc.android.R +import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction + +class TransactionAdapter : + PagedListAdapter>( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: T, + newItem: T + ) = oldItem.minedHeight == newItem.minedHeight && oldItem.noteId == newItem.noteId && + // bugfix: distinguish between self-transactions so they don't overwrite each other in the UI // TODO confirm that this is working, as intended + ((oldItem.raw == null && newItem.raw == null) || (oldItem.raw != null && newItem.raw != null && oldItem.raw!!.contentEquals(newItem.raw!!))) + + override fun areContentsTheSame( + oldItem: T, + newItem: T + ) = oldItem == newItem + } + ) { + + init { + setHasStableIds(true) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = TransactionViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false) + ) + + override fun onBindViewHolder( + holder: TransactionViewHolder, + position: Int + ) = holder.bindTo(getItem(position)) + + override fun getItemId(position: Int): Long { + return getItem(position)?.id ?: -1 + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/history/TransactionFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionFragment.kt new file mode 100644 index 0000000..8a408b2 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionFragment.kt @@ -0,0 +1,221 @@ +package cash.z.ecc.android.ui.history + +import android.content.res.ColorStateList +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.os.Bundle +import android.text.method.ScrollingMovementMethod +import android.view.LayoutInflater +import android.view.View +import androidx.core.view.ViewCompat +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import androidx.transition.* +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentTransactionBinding +import cash.z.ecc.android.ext.* +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.history.HistoryViewModel.UiModel +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +class TransactionFragment : BaseFragment() { + override val screen = Report.Screen.TRANSACTION + private val viewModel: HistoryViewModel by activityViewModels() + + var isMemoExpanded: Boolean = false + + override fun inflate(inflater: LayoutInflater): FragmentTransactionBinding = + FragmentTransactionBinding.inflate(inflater) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) +// val transition = TransitionInflater.from(requireContext()).inflateTransition(android.R.transition.move) +// sharedElementEnterTransition = transition +// sharedElementReturnTransition = transition + +// sharedElementEnterTransition = createSharedElementTransition() +// sharedElementReturnTransition = createSharedElementTransition() + +// sharedElementEnterTransition = ChangeBounds().apply { duration = 1500 } +// sharedElementReturnTransition = ChangeBounds().apply { duration = 1500 } +// enterTransition = Fade().apply { +// duration = 1800 +// // slideEdge = Gravity.END +// } + } + + private fun createSharedElementTransition(duration: Long = 800L): Transition { + return TransitionSet().apply { + ordering = TransitionSet.ORDERING_TOGETHER + this.duration = duration +// interpolator = PathInterpolatorCompat.create(0.4f, 0f, 0.2f, 1f) + addTransition(ChangeBounds()) + addTransition(ChangeClipBounds()) + addTransition(ChangeTransform()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.apply { + ViewCompat.setTransitionName( + topBoxValue, + "test_amount_anim_${viewModel.selectedTransaction.value?.id}" + ) + ViewCompat.setTransitionName( + topBoxBackground, + "test_bg_anim_${viewModel.selectedTransaction.value?.id}" + ) + backButtonHitArea.onClickNavBack { tapped(Report.Tap.TRANSACTION_BACK) } + + lifecycleScope.launch { + viewModel.uiModels.stateIn(lifecycleScope).collect { uiModel -> + topBoxLabel.text = uiModel.topLabel + topBoxValue.text = uiModel.topValue + bottomBoxLabel.text = uiModel.bottomLabel + bottomBoxValue.text = uiModel.bottomValue + textBlockHeight.text = uiModel.minedHeight + textTimestamp.text = uiModel.timestamp + if (uiModel.iconRotation < 0) { + topBoxIcon.gone() + } else { + topBoxIcon.rotation = uiModel.iconRotation + topBoxIcon.visible() + } + + if (!uiModel.isMined) { + textBlockHeight.invisible() + textBlockHeightPrefix.invisible() + } + + val exploreOnClick = View.OnClickListener { + uiModel.txId?.let { txId -> + mainActivity?.showFirstUseWarning( + Const.Pref.FIRST_USE_VIEW_TX, + titleResId = R.string.dialog_first_use_view_tx_title, + msgResId = R.string.dialog_first_use_view_tx_message, + positiveResId = R.string.dialog_first_use_view_tx_positive, + negativeResId = R.string.dialog_first_use_view_tx_negative + ) { + onLaunchUrl(txId.toTransactionUrl()) + } + } + } + buttonExplore.setOnClickListener(exploreOnClick) + textBlockHeight.setOnClickListener(exploreOnClick) + + uiModel.fee?.let { + subwaySpotFee.visible(); subwayLabelFee.visible(); subwayLabelFee.text = it + } + uiModel.source?.let { + subwaySpotSource.visible(); subwayLabelSource.visible(); subwayLabelSource.text = + it + } + uiModel.toAddressLabel()?.let { + subwaySpotAddress.visible(); subwayLabelAddress.visible(); subwayLabelAddress.text = + it + } + uiModel.toAddressClickListener() + ?.let { subwayLabelAddress.setOnClickListener(it) } + + // TODO: remove logic from sections below and add more fields or extension functions to UiModel + uiModel.confirmation?.let { + subwaySpotConfirmations.visible(); subwayLabelConfirmations.visible() + subwayLabelConfirmations.text = it + if (it.equals(getString(R.string.transaction_status_confirmed), true)) { + subwayLabelConfirmations.setTextColor(R.color.tx_primary.toAppColor()) + } else { + subwayLabelConfirmations.setTextColor(R.color.tx_text_light_dimmed.toAppColor()) + } + } + + uiModel.memo?.let { + hitAreaMemoSubway.setOnClickListener { _ -> + onToggleMemo( + !isMemoExpanded, + it + ) + } + hitAreaMemoIcon.setOnClickListener { _ -> + onToggleMemo( + !isMemoExpanded, + it + ) + } + subwayLabelMemo.setOnClickListener { _ -> + onToggleMemo( + !isMemoExpanded, + it + ) + } + subwayLabelMemo.setOnLongClickListener { _ -> + mainActivity?.copyText(it, "Memo") + true + } + subwayLabelMemo.movementMethod = ScrollingMovementMethod() + subwaySpotMemoContent.visible() + subwayLabelMemo.visible() + hitAreaMemoSubway.visible() + onToggleMemo(false) + } + } + } + } + } + + val invertingMatrix = ColorMatrixColorFilter(ColorMatrix().apply { setSaturation(0f) }) + private fun onToggleMemo(isExpanded: Boolean, memo: String = "") { + twig("onToggleMemo($isExpanded, $memo)") + if (isExpanded) { + twig("setting memo text to: $memo") + binding.subwayLabelMemo.setText(memo) + binding.subwayLabelMemo.invalidate() + // don't impede the ability to scroll + binding.groupMemoIcon.gone() + binding.subwayLabelMemo.backgroundTintList = + ColorStateList.valueOf(R.color.tx_text_light_dimmed.toAppColor()) + binding.subwaySpotMemoContent.colorFilter = invertingMatrix + binding.subwaySpotMemoContent.rotation = 90.0f + } else { + binding.subwayLabelMemo.setText(getString(R.string.transaction_with_memo)) + binding.subwayLabelMemo.scrollTo(0, 0) + binding.subwayLabelMemo.invalidate() + twig("setting memo text to: with a memo") + binding.groupMemoIcon.visible() + binding.subwayLabelMemo.backgroundTintList = + ColorStateList.valueOf(R.color.tx_primary.toAppColor()) + binding.subwaySpotMemoContent.colorFilter = null + binding.subwaySpotMemoContent.rotation = 0.0f + } + isMemoExpanded = isExpanded + } + + private fun String.toTransactionUrl(): String { + return getString(R.string.api_block_explorer, this) + } + + private fun UiModel?.toAddressClickListener(): View.OnClickListener? { + return this?.address?.let { addr -> + View.OnClickListener { mainActivity?.copyText(addr, "Address") } + } + } + + private fun UiModel?.toAddressLabel(): CharSequence? { + if (this == null || this.address == null || this.isInbound == null) return null + val prefix = getString( + if (isInbound == true) { + R.string.transaction_prefix_from + } else { + R.string.transaction_prefix_to + } + ) + return "$prefix ${address?.toAbbreviatedAddress() ?: "Unknown"}".let { + it.toColoredSpan(R.color.tx_text_light_dimmed, if (address == null) it else prefix) + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/history/TransactionViewHolder.kt b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionViewHolder.kt new file mode 100644 index 0000000..c69899b --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionViewHolder.kt @@ -0,0 +1,168 @@ +package cash.z.ecc.android.ui.history + +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import cash.z.ecc.android.R +import cash.z.ecc.android.ext.WalletZecFormmatter +import cash.z.ecc.android.ext.goneIf +import cash.z.ecc.android.ext.locale +import cash.z.ecc.android.ext.toAppColor +import cash.z.ecc.android.ext.toAppInt +import cash.z.ecc.android.ext.toColoredSpan +import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction +import cash.z.ecc.android.sdk.db.entity.valueInZatoshi +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.ext.isShielded +import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress +import cash.z.ecc.android.ui.MainActivity +import cash.z.ecc.android.ui.util.toUtf8Memo +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat + +class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + private val indicator = itemView.findViewById(R.id.indicator) + private val amountText = itemView.findViewById(R.id.text_transaction_amount) + private val topText = itemView.findViewById(R.id.text_transaction_top) + private val bottomText = itemView.findViewById(R.id.text_transaction_bottom) + private val transactionArrow = itemView.findViewById(R.id.image_transaction_arrow) + private val formatter = SimpleDateFormat(itemView.context.getString(R.string.format_transaction_history_date_time), itemView.context.locale()) + private val iconMemo = itemView.findViewById(R.id.image_memo) + + fun bindTo(transaction: T?) { + val mainActivity = itemView.context as MainActivity + mainActivity.lifecycleScope.launch { + // update view + var lineOne: CharSequence = "" + var lineTwo = "" + var amountZec = "" + var amountDisplay = "" + var amountColor: Int = R.color.text_light + var lineOneColor: Int = R.color.text_light + var lineTwoColor: Int = R.color.text_light_dimmed + var indicatorBackground: Int = R.color.text_light_dimmed + var arrowRotation: Int = R.integer.transaction_arrow_rotation_send + var arrowBackgroundTint: Int = R.color.text_light + var isLineOneSpanned = false + + try { + transaction?.apply { + itemView.setOnClickListener { + onTransactionClicked(this) + } + itemView.setOnLongClickListener { + onTransactionLongPressed(this) + true + } + amountZec = WalletZecFormmatter.toZecStringShort(valueInZatoshi) + // TODO: these might be good extension functions + val timestamp = formatter.format(blockTimeInSeconds * 1000L) + val isMined = blockTimeInSeconds != 0L + when { + !toAddress.isNullOrEmpty() -> { + indicatorBackground = + if (isMined) R.color.zcashRed else R.color.zcashGray + lineOne = "${ + if (isMined) str(R.string.transaction_address_you_paid) else str(R.string.transaction_address_paying) + } ${toAddress?.toAbbreviatedAddress()}" + lineTwo = + if (isMined) "${str(R.string.transaction_status_sent)} $timestamp" else str( + R.string.transaction_status_pending + ) + // TODO: this logic works but is sloppy. Find a more robust solution to displaying information about expiration (such as expires in 1 block, etc). Then if it is way beyond expired, remove it entirely. Perhaps give the user a button for that (swipe to dismiss?) + if (!isMined && (expiryHeight != null) && (expiryHeight!! < mainActivity.latestHeight?.value ?: -1)) lineTwo = + str(R.string.transaction_status_expired) + amountDisplay = "- $amountZec" + if (isMined) { + arrowRotation = R.integer.transaction_arrow_rotation_send + amountColor = R.color.transaction_sent + if (toAddress.isShielded()) { + lineOneColor = R.color.zcashYellow + } else { + toAddress?.toAbbreviatedAddress()?.let { + lineOne = lineOne.toColoredSpan(R.color.zcashBlueDark, it) + } + } + } else { + arrowRotation = R.integer.transaction_arrow_rotation_pending + } + } + toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> { + indicatorBackground = R.color.zcashGreen + val senderAddress = mainActivity.getSender(transaction) + lineOne = "${str(R.string.transaction_received_from)} $senderAddress" + lineTwo = "${str(R.string.transaction_received)} $timestamp" + amountDisplay = "+ $amountZec" + if (senderAddress.isShielded()) { + amountColor = R.color.zcashYellow + lineOneColor = R.color.zcashYellow + } else { + senderAddress.toAbbreviatedAddress().let { + lineOne = + if (senderAddress.equals(str(R.string.unknown), true)) { + lineOne.toColoredSpan(R.color.zcashYellow, it) + } else { + lineOne.toColoredSpan(R.color.zcashBlueDark, it) + } + } + } + arrowRotation = R.integer.transaction_arrow_rotation_received + } + else -> { + lineOne = str(R.string.unknown) + lineTwo = str(R.string.unknown) + amountDisplay = amountZec + amountColor = R.color.text_light + arrowRotation = R.integer.transaction_arrow_rotation_received + } + } + // sanitize amount + if (value < ZcashSdk.MINERS_FEE.value * 10) amountDisplay = "< 0.0001" + else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal + amountDisplay = str(R.string.transaction_instruction_tap) + } + } + + topText.text = lineOne + bottomText.text = lineTwo + amountText.text = amountDisplay + amountText.setTextColor(amountColor.toAppColor()) + if (!isLineOneSpanned) { + topText.setTextColor(lineOneColor.toAppColor()) + } + bottomText.setTextColor(lineTwoColor.toAppColor()) + indicator.setBackgroundColor(indicatorBackground.toAppColor()) + transactionArrow.setColorFilter(arrowBackgroundTint.toAppColor()) + transactionArrow.rotation = arrowRotation.toAppInt().toFloat() + + var bottomTextRightDrawable: Drawable? = null + iconMemo.goneIf(!transaction?.memo.toUtf8Memo().isNotEmpty()) + bottomText.setCompoundDrawablesWithIntrinsicBounds(null, null, bottomTextRightDrawable, null) + } catch (t: Throwable) { + twig("Failed to parse the transaction due to $t") + } + } + } + + private fun onTransactionClicked(transaction: ConfirmedTransaction) { + (itemView.context as MainActivity).apply { + historyViewModel.selectedTransaction.value = transaction + safeNavigate(R.id.action_nav_history_to_nav_transaction) + } + } + + private fun onTransactionLongPressed(transaction: ConfirmedTransaction) { + val mainActivity = itemView.context as MainActivity + transaction.toAddress?.let { + mainActivity.copyText(it, "Transaction Address") + } + } + + private inline fun str(@StringRes resourceId: Int) = itemView.context.getString(resourceId) +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/history/TransactionsDrawableFooter.kt b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionsDrawableFooter.kt new file mode 100644 index 0000000..23a1d81 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionsDrawableFooter.kt @@ -0,0 +1,52 @@ +package cash.z.ecc.android.ui.history +// +// import android.content.Context +// import android.graphics.Canvas +// import android.graphics.Rect +// import android.view.LayoutInflater +// import android.view.View +// import androidx.recyclerview.widget.RecyclerView +// import cash.z.ecc.android.R +// +// +// class TransactionsDrawableFooter(context: Context) : RecyclerView.ItemDecoration() { +// +// private var footer: View = +// LayoutInflater.from(context).inflate(R.layout.footer_transactions, null, false) +// +// override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { +// super.onDraw(c, parent, state!!) +// footer.measure( +// View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.AT_MOST), +// View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) +// ) +// // layout basically just gets drawn on the reserved space on top of the first view +// footer.layout(parent.left, 0, parent.right, footer.measuredHeight) +// for (i in 0 until parent.childCount) { +// val view: View = parent.getChildAt(i) +// if (parent.getChildAdapterPosition(view) == parent.adapter!!.itemCount - 1) { +// c.save() +// val height: Int = footer.measuredHeight +// val top: Int = view.top - height +// c.translate(0.0f, top.toFloat()) +// footer.draw(c) +// c.restore() +// break +// } +// } +// } +// +// override fun getItemOffsets( +// outRect: Rect, +// view: View, +// parent: RecyclerView, +// state: RecyclerView.State +// ) { +// super.getItemOffsets(outRect, view, parent, state) +// if (parent.getChildAdapterPosition(view) == parent.adapter!!.itemCount - 1) { +// outRect.set(0, 0, 0, 150) +// } else { +// outRect.setEmpty() +// } +// } +// } diff --git a/app/src/main/java/cash/z/ecc/android/ui/history/TransactionsFooter.kt b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionsFooter.kt new file mode 100644 index 0000000..59a0981 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionsFooter.kt @@ -0,0 +1,48 @@ +package cash.z.ecc.android.ui.history + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import cash.z.ecc.android.R + +class TransactionsFooter(context: Context) : RecyclerView.ItemDecoration() { + + private var footer: Drawable = context.resources.getDrawable(R.drawable.background_footer) + val bounds = Rect() + + override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { + c.save() + val left: Int = 0 + val right: Int = parent.width + val childCount = parent.childCount + val adapterItemCount = parent.adapter!!.itemCount + for (i in 0 until childCount) { + val child = parent.getChildAt(i) + if (parent.getChildAdapterPosition(child) == adapterItemCount - 1) { + parent.getDecoratedBoundsWithMargins(child, bounds) + val bottom: Int = bounds.bottom + Math.round(child.translationY) + val top: Int = bottom - footer.intrinsicHeight + footer.setBounds(left, top, right, bottom) + footer.draw(c) + } + } + c.restore() + } + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + if (parent.getChildAdapterPosition(view) == parent.adapter!!.itemCount - 1) { + outRect.set(0, 0, 0, footer.intrinsicHeight) + } else { + outRect.setEmpty() + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/AutoshieldingInformationFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/home/AutoshieldingInformationFragment.kt new file mode 100644 index 0000000..77d3ec4 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/home/AutoshieldingInformationFragment.kt @@ -0,0 +1,69 @@ +package cash.z.ecc.android.ui.home + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import cash.z.ecc.android.databinding.FragmentAutoShieldInformationBinding +import cash.z.ecc.android.ext.requireApplicationContext +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.preference.Preferences +import cash.z.ecc.android.preference.model.put +import cash.z.ecc.android.ui.base.BaseFragment + +class AutoshieldingInformationFragment : BaseFragment() { + override val screen = Report.Screen.AUTO_SHIELD_INFORMATION + + private val args: AutoshieldingInformationFragmentArgs by navArgs() + + override fun inflate(inflater: LayoutInflater): FragmentAutoShieldInformationBinding = + FragmentAutoShieldInformationBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + /* + * Once the fragment is displayed, acknowledge it was presented to the user. While it might + * be better to have explicit user interaction (positive/negative button or back), + * this implementation is simpler. Hooking into the positive/negative button is easy, but + * hooking into the back button from a Fragment ends up being gross. + * + * Always acknowledging is necessary, because the HomeFragment will otherwise almost immediately + * re-launch this Fragment when it refreshes the UI (and therefore re-runs the + * check as to whether the preference to display this fragment has been set). + */ + acknowledge() + + binding.buttonAutoshieldDismiss.setOnClickListener { + if (args.isStartAutoshield) { + findNavController().navigate(AutoshieldingInformationFragmentDirections.actionNavAutoshieldingInfoToAutoshield()) + } else { + findNavController().navigate(AutoshieldingInformationFragmentDirections.actionNavAutoshieldingInfoToHome()) + } + } + binding.buttonAutoshieldMoreInfo.setOnClickListener { + try { + findNavController().navigate(AutoshieldingInformationFragmentDirections.actionNavAutoshieldingInfoToBrowser()) + } catch (e: Exception) { + // ActivityNotFoundException could happen on certain devices, like Android TV, Android Things, etc. + + // SecurityException shouldn't occur, but just in case we catch all exceptions to + // prevent another package on the device from crashing us if that package tries to be malicious + // by adding permissions or changing export status dynamically. + + // In the future, it might also be desirable to display a Toast or Snackbar indicating + // that the browser couldn't be launched + + findNavController().navigate(AutoshieldingInformationFragmentDirections.actionNavAutoshieldingInfoToHome()) + } + } + } + + private fun acknowledge() { + Preferences.isAcknowledgedAutoshieldingInformationPrompt.put( + requireApplicationContext(), + true + ) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailFragment.kt new file mode 100644 index 0000000..b27595a --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailFragment.kt @@ -0,0 +1,158 @@ +package cash.z.ecc.android.ui.home + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.Toast +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.databinding.FragmentBalanceDetailBinding +import cash.z.ecc.android.ext.goneIf +import cash.z.ecc.android.ext.onClickNavBack +import cash.z.ecc.android.ext.toAppColor +import cash.z.ecc.android.ext.toSplitColorSpan +import cash.z.ecc.android.feedback.Report.Tap.RECEIVE_BACK +import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString +import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.home.BalanceDetailViewModel.StatusModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +class BalanceDetailFragment : BaseFragment() { + + private val viewModel: BalanceDetailViewModel by viewModels() + private var lastSignal: BlockHeight? = null + + override fun inflate(inflater: LayoutInflater): FragmentBalanceDetailBinding = + FragmentBalanceDetailBinding.inflate(inflater) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.balances.onEach { onBalanceUpdated(it) }.launchIn(this) + viewModel.statuses.onEach { onStatusUpdated(it) }.launchIn(this) + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.hitAreaExit.onClickNavBack() { tapped(RECEIVE_BACK) } + binding.textShieldedHushTitle.text = "SHIELDED ${getString(R.string.symbol)}" + } + + private fun onBalanceUpdated(balanceModel: BalanceDetailViewModel.BalanceModel) { + balanceModel.apply { + if (balanceModel.hasData()) { + setBalances(paddedShielded, paddedTransparent, paddedTotal) + } else { + setBalances(" --", " --", " --") + } + } + } + + private fun onStatusUpdated(status: StatusModel) { + binding.textStatus.text = status.toStatus() + if (status.missingBlocks > 100) { + binding.textBlockHeightPrefix.text = "Processing " + binding.textBlockHeight.text = String.format( + "%,d", + status.info.lastScannedHeight?.value ?: 0 + ) + " of " + String.format("%,d", status.info.networkBlockHeight?.value ?: 0) + } else { + status.info.lastScannedHeight.let { height -> + if (height == null) { + binding.textBlockHeightPrefix.text = "Processing..." + binding.textBlockHeight.text = "" + } else { + binding.textBlockHeightPrefix.text = "Balances as of block " + binding.textBlockHeight.text = + String.format("%,d", status.info.lastScannedHeight?.value ?: 0) + sendNewBlockSignal(status.info.lastScannedHeight) + } + } + } + } + + private fun sendNewBlockSignal(currentHeight: BlockHeight?) { + // prevent a flood of signals while scanning blocks + if (lastSignal != null && (currentHeight?.value ?: 0) > lastSignal!!.value) { + mainActivity?.vibrate(0, 100, 100, 300) + Toast.makeText(mainActivity, "New block!", Toast.LENGTH_SHORT).show() + } + lastSignal = currentHeight + } + + fun setBalances(shielded: String, transparent: String, total: String) { + binding.textShieldAmount.text = shielded.colorize() + } + + private fun String.colorize(): CharSequence { + val dotIndex = indexOf('.') + return if (dotIndex < 0 || length < (dotIndex + 4)) { + this + } else { + toSplitColorSpan(R.color.text_light, R.color.zcashWhite_24, indexOf('.') + 4) + } + } + + private fun StatusModel.toStatus(): String { + fun String.plural(count: Int) = if (count > 1) "${this}s" else this + + if (viewModel.latestBalance?.hasData() == false) { + return "Balance info is not yet available" + } + + var status = "" + if (hasUnmined) { + val count = pendingUnmined.count() + status += "Balance excludes $count unconfirmed ${"transaction".plural(count)}. " + } + + status += when { + hasPendingTransparentBalance && hasPendingShieldedBalance -> { + "Awaiting ${pendingShieldedBalance.convertZatoshiToZecString(8)} ${ + ZcashWalletApp.instance.getString( + R.string.symbol + ) + } in shielded funds and {pendingTransparentBalance.convertZatoshiToZecString(8)} ${ + ZcashWalletApp.instance.getString( + R.string.symbol + ) + } in transparent funds" + } + hasPendingShieldedBalance -> { + "Awaiting ${pendingShieldedBalance.convertZatoshiToZecString(8)} ${ + ZcashWalletApp.instance.getString( + R.string.symbol + ) + } in shielded funds" + } + hasPendingTransparentBalance -> { + "Awaiting ${pendingTransparentBalance.convertZatoshiToZecString(8)} ${ + ZcashWalletApp.instance.getString( + R.string.symbol + ) + } in transparent funds" + } + else -> "" + } + + pendingUnconfirmed.count().takeUnless { it == 0 }?.let { count -> + if (status.contains("Awaiting")) status += " and " + status += "$count outbound ${"transaction".plural(count)}" + remainingConfirmations().firstOrNull()?.let { remaining -> + status += " with $remaining ${"confirmation".plural(remaining.toInt())} remaining" + } + } + + return if (status.isEmpty()) "All funds are available!" else status + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailViewModel.kt new file mode 100644 index 0000000..be0710e --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailViewModel.kt @@ -0,0 +1,147 @@ +package cash.z.ecc.android.ui.home + +import androidx.lifecycle.ViewModel +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.sdk.block.CompactBlockProcessor +import cash.z.ecc.android.sdk.db.entity.PendingTransaction +import cash.z.ecc.android.sdk.db.entity.isMined +import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString +import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.sdk.model.WalletBalance +import cash.z.ecc.android.sdk.model.Zatoshi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combineTransform + +class BalanceDetailViewModel : ViewModel() { + + private val synchronizer: Synchronizer = DependenciesHolder.synchronizer + + private val lockBox: LockBox = DependenciesHolder.lockBox + + var showAvailable: Boolean = true + set(value) { + field = value + latestBalance?.showAvailable = value + } + + var latestBalance: BalanceModel? = null + + val balances: Flow + get() = combineTransform( + synchronizer.saplingBalances, + synchronizer.transparentBalances + ) { saplingBalance, transparentBalance -> + BalanceModel(saplingBalance, transparentBalance, showAvailable).let { + latestBalance = it + emit(it) + } + } + + val statuses: Flow + get() = combineTransform( + balances, + synchronizer.pendingTransactions, + synchronizer.processorInfo + ) { balances, pending, info -> + emit(StatusModel(balances, pending, info)) + } + + data class BalanceModel( + val shieldedBalance: WalletBalance?, + val transparentBalance: WalletBalance?, + var showAvailable: Boolean = false + ) { + /** Whether to make calculations based on total or available zatoshi */ + + val canAutoShield: Boolean = + (transparentBalance?.available?.value ?: 0L) > ZcashSdk.MINERS_FEE.value + + val balanceShielded: String + get() { + return if (showAvailable) shieldedBalance?.available.toDisplay() + else shieldedBalance?.total.toDisplay() + } + + val balanceTransparent: String + get() { + return if (showAvailable) transparentBalance?.available.toDisplay() + else transparentBalance?.total.toDisplay() + } + + val balanceTotal: String + get() { + return if (showAvailable) ((shieldedBalance?.available + ?: Zatoshi(0)) + (transparentBalance?.available ?: Zatoshi(0))).toDisplay() + else ((shieldedBalance?.total ?: Zatoshi(0)) + (transparentBalance?.total + ?: Zatoshi(0))).toDisplay() + } + + val paddedShielded get() = pad(balanceShielded) + val paddedTransparent get() = pad(balanceTransparent) + val paddedTotal get() = pad(balanceTotal) + val maxLength + get() = maxOf( + balanceShielded.length, + balanceTransparent.length, + balanceTotal.length + ) + val hasPending = + (null != shieldedBalance && shieldedBalance.available != shieldedBalance.total) || + (null != transparentBalance && transparentBalance.available != transparentBalance.total) + + private fun Zatoshi?.toDisplay(): String { + return this?.convertZatoshiToZecString(8, 8) ?: "0" + } + + private fun pad(balance: String): String { + var diffLength = maxLength - balance.length + return buildString { + repeat(diffLength) { + append(' ') + } + append(balance) + } + } + + fun hasData(): Boolean { + return shieldedBalance != null || transparentBalance != null + } + } + + data class StatusModel( + val balances: BalanceModel, + val pending: List, + val info: CompactBlockProcessor.ProcessorInfo, + ) { + val pendingUnconfirmed = + pending.filter { it.isSubmitSuccess() && it.isMined() && !it.isConfirmed(info.lastScannedHeight) } + val pendingUnmined = pending.filter { it.isSubmitSuccess() && !it.isMined() } + val pendingShieldedBalance = balances.shieldedBalance?.pending + val pendingTransparentBalance = balances.transparentBalance?.pending + val hasUnconfirmed = pendingUnconfirmed.isNotEmpty() + val hasUnmined = pendingUnmined.isNotEmpty() + val hasPendingShieldedBalance = (pendingShieldedBalance?.value ?: 0L) > 0L + val hasPendingTransparentBalance = (pendingTransparentBalance?.value ?: 0L) > 0L + val missingBlocks = ((info.networkBlockHeight?.value ?: 0) - (info.lastScannedHeight?.value + ?: 0)).coerceAtLeast(0) + + private fun PendingTransaction.isConfirmed(networkBlockHeight: BlockHeight?): Boolean { + return networkBlockHeight?.let { + isMined() && (it.value - minedHeight + 1) > 10 // fix: plus 1 because the mined block counts as the FIRST confirmation + } ?: false + } + + fun remainingConfirmations(confirmationsRequired: Int = 10) = + pendingUnconfirmed + .map { + confirmationsRequired - ((info.lastScannedHeight?.value + ?: -1) - it.minedHeight + 1) + } // fix: plus 1 because the mined block counts as the FIRST confirmation + .filter { it > 0 } + .sortedDescending() + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/HomeFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/home/HomeFragment.kt new file mode 100644 index 0000000..5b944e8 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/home/HomeFragment.kt @@ -0,0 +1,596 @@ +package cash.z.ecc.android.ui.home + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.content.res.AppCompatResources +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.databinding.DialogSolicitFeedbackRatingBinding +import cash.z.ecc.android.databinding.FragmentHomeBinding +import cash.z.ecc.android.ext.* +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Tap.* +import cash.z.ecc.android.preference.Preferences +import cash.z.ecc.android.preference.model.get +import cash.z.ecc.android.sdk.Synchronizer.Status.* +import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString +import cash.z.ecc.android.sdk.ext.convertZecToZatoshi +import cash.z.ecc.android.sdk.ext.onFirstWith +import cash.z.ecc.android.sdk.ext.safelyConvertToBigDecimal +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.home.HomeFragment.BannerAction.* +import cash.z.ecc.android.ui.send.AutoShieldFragment +import cash.z.ecc.android.ui.send.SendViewModel +import cash.z.ecc.android.ui.setup.WalletSetupViewModel +import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED +import cash.z.ecc.android.util.twig +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.runningReduce +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch + +// There are deprecations with the use of BroadcastChannel +@kotlinx.coroutines.ObsoleteCoroutinesApi +class HomeFragment : BaseFragment() { + override val screen = Report.Screen.HOME + + private val walletSetup: WalletSetupViewModel by activityViewModels() + private val sendViewModel: SendViewModel by activityViewModels() + private val viewModel: HomeViewModel by viewModels() + + private lateinit var numberPad: List + private lateinit var uiModel: HomeViewModel.UiModel + + lateinit var snake: MagicSnakeLoader + + override fun inflate(inflater: LayoutInflater): FragmentHomeBinding = + FragmentHomeBinding.inflate(inflater) + + // + // LifeCycle + // + + override fun onAttach(context: Context) { + twig("HomeFragment.onAttach") + twig("ZZZ") + twig("ZZZ") + twig("ZZZ") + twig("ZZZ ===================== HOME FRAGMENT CREATED ==================================") + super.onAttach(context) + + walletSetup.checkSeed().onFirstWith(lifecycleScope) { + if (it == NO_SEED) { + // interact with user to create, backup and verify seed + // leads to a call to startSync(), later (after accounts are created from seed) + twig("Previous wallet not found, therefore, launching seed creation flow") + mainActivity?.setLoading(false) + mainActivity?.safeNavigate(R.id.action_nav_home_to_create_wallet) + } else { + twig("Previous wallet found. Re-opening it.") + mainActivity?.setLoading(true) + try { + walletSetup.openStoredWallet() + mainActivity?.startSync() + } catch (e: UnsatisfiedLinkError) { + mainActivity?.showSharedLibraryCriticalError(e) + } + twig("Done reopening wallet.") + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + twig("HomeFragment.onViewCreated uiModel: ${::uiModel.isInitialized} saved: ${savedInstanceState != null}") + with(binding) { + numberPad = arrayListOf( + buttonNumberPad0.asKey(), + buttonNumberPad1.asKey(), + buttonNumberPad2.asKey(), + buttonNumberPad3.asKey(), + buttonNumberPad4.asKey(), + buttonNumberPad5.asKey(), + buttonNumberPad6.asKey(), + buttonNumberPad7.asKey(), + buttonNumberPad8.asKey(), + buttonNumberPad9.asKey(), + buttonNumberPadDecimal.asKey(), + buttonNumberPadBack.asKey() + ) + hitAreaProfile.onClickNavTo(R.id.action_nav_home_to_nav_profile) { tapped(HOME_PROFILE) } + textHistory.onClickNavTo(R.id.action_nav_home_to_nav_history) { tapped(HOME_HISTORY) } + textSendAmount.onClickNavTo(R.id.action_nav_home_to_nav_balance_detail) { + tapped( + HOME_BALANCE_DETAIL + ) + } + hitAreaBalance.onClickNavTo(R.id.action_nav_home_to_nav_balance_detail) { + tapped( + HOME_BALANCE_DETAIL + ) + } + hitAreaReceive.onClickNavTo(R.id.action_nav_home_to_nav_receive) { tapped(HOME_RECEIVE) } + + textBannerAction.setOnClickListener { + onBannerAction(BannerAction.from((it as? TextView)?.text?.toString())) + } + buttonSendAmount.setOnClickListener { + onSend().also { tapped(HOME_SEND) } + } + setSendAmount("0", false) + + snake = MagicSnakeLoader(binding.lottieButtonLoading) + + // fix: don't start up with just a black screen + buttonSendAmount.text = getString(R.string.home_button_send_disconnected) + buttonSendAmount.setTextColor(R.color.text_light.toAppColor()) + } + + binding.buttonNumberPadBack.setOnLongClickListener { + onClearAmount().also { tapped(HOME_CLEAR_AMOUNT) } + true + } + + if (::uiModel.isInitialized) { + twig("uiModel exists! it has pendingSend=${uiModel.pendingSend} ZEC while the sendViewModel=${sendViewModel.zatoshiAmount} zats") + // if the model already existed, cool but let the sendViewModel be the source of truth for the amount + onModelUpdated( + null, + uiModel.copy( + pendingSend = WalletZecFormmatter.toZecStringFull( + sendViewModel.zatoshiAmount ?: Zatoshi(0L) + ) + ) + ) + } + } + + private fun onClearAmount() { + twig("onClearAmount()") + if (::uiModel.isInitialized) { + resumedScope.launch { + binding.textSendAmount.text.apply { + while (uiModel.pendingSend != "0") { + viewModel.onChar('<') + delay(5) + } + } + } + } + } + + override fun onResume() { + super.onResume() + twig("HomeFragment.onResume resumeScope.isActive: ${resumedScope.isActive} $resumedScope") + + launchWhenSyncReady(::onSyncReady) + } + + private fun onSyncReady() { + twig("Sync ready! Monitoring synchronizer state...") + monitorUiModelChanges() + + twig("HomeFragment.onSyncReady COMPLETE") + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) +// if (::uiModel.isInitialized) { +// outState.putParcelable("uiModel", uiModel) +// } + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + savedInstanceState?.let { inState -> +// onModelUpdated(HomeViewModel.UiModel(), inState.getParcelable("uiModel")!!) + } + } + + // + // Public UI API + // + + var isSendEnabled = false + fun setSendEnabled(enabled: Boolean, isSynced: Boolean) { + isSendEnabled = enabled + binding.buttonSendAmount.apply { + if (enabled || !isSynced) { + isEnabled = true + isClickable = isSynced + binding.lottieButtonLoading.alpha = 1.0f + } else { + isEnabled = false + isClickable = false + binding.lottieButtonLoading.alpha = 0.32f + } + } + } + + fun setProgress(uiModel: HomeViewModel.UiModel) { + if (!uiModel.processorInfo.hasData && !uiModel.isDisconnected) { + twig("Warning: ignoring progress update because the processor is still starting.") + return + } + + snake.isSynced = uiModel.isSynced + if (!uiModel.isSynced) { + snake.downloadProgress = uiModel.downloadProgress + snake.scanProgress = uiModel.scanProgress + } + + val sendText = when { + uiModel.status == DISCONNECTED -> getString(R.string.home_button_send_disconnected) + uiModel.isSynced -> if (uiModel.hasFunds) getString(R.string.home_button_send_has_funds) else getString( + R.string.home_button_send_no_funds + ) + uiModel.status == STOPPED -> getString(R.string.home_button_send_idle) + uiModel.isDownloading -> { + when (snake.downloadProgress) { + 0 -> "Preparing to download..." + else -> getString(R.string.home_button_send_downloading, snake.downloadProgress) + } + } + uiModel.isValidating -> getString(R.string.home_button_send_validating) + uiModel.isScanning -> { + when (snake.scanProgress) { + 0 -> "Preparing to scan..." + 100 -> "Finalizing..." + else -> getString(R.string.home_button_send_scanning, snake.scanProgress) + } + } + else -> getString(R.string.home_button_send_updating) + } + + binding.buttonSendAmount.text = sendText + twig("Send button set to: $sendText") + + val resId = + if (uiModel.isSynced) R.color.selector_button_text_dark else R.color.selector_button_text_light + context?.let { + binding.buttonSendAmount.setTextColor( + AppCompatResources.getColorStateList( + it, + resId + ) + ) + } + binding.lottieButtonLoading.invisibleIf(uiModel.isDisconnected) + } + + /** + * @param amount the amount to send represented as ZEC, without the dollar sign. + */ + fun setSendAmount(amount: String, updateModel: Boolean = true) { + twig("setSendAmount($amount, $updateModel)") + binding.textSendAmount.text = "\$$amount".toColoredSpan(R.color.text_light_dimmed, "$") + if (updateModel) { + sendViewModel.zatoshiAmount = amount.safelyConvertToBigDecimal().convertZecToZatoshi() + twig( + "dBUG: updating model. converting: $amount\tresult: ${sendViewModel.zatoshiAmount}\tprint: ${ + WalletZecFormmatter.toZecStringFull( + sendViewModel.zatoshiAmount + ) + }" + ) + } + binding.buttonSendAmount.disabledIf(amount == "0") + } + + fun setAvailable( + availableBalance: Zatoshi?, + totalBalance: Zatoshi?, + availableTransparentBalance: Zatoshi?, + unminedCount: Int = 0 + ) { + val missingBalance = availableBalance == null + val availableString = + if (missingBalance) getString(R.string.home_button_send_updating) else WalletZecFormmatter.toZecStringFull( + availableBalance + ) + binding.textBalanceAvailable.text = availableString + binding.textBalanceAvailable.transparentIf(missingBalance) + binding.labelBalance.transparentIf(missingBalance) + binding.textBalanceDescription.apply { + goneIf(missingBalance) + text = when { + unminedCount > 0 -> "(excludes $unminedCount unconfirmed ${if (unminedCount > 1) "transactions" else "transaction"})" + availableBalance != null && totalBalance != null && (availableBalance.value < totalBalance.value) -> { + val change = + WalletZecFormmatter.toZecStringFull(totalBalance - availableBalance) + val symbol = getString(R.string.symbol) + "(${getString(R.string.home_banner_expecting)} +$change $symbol)".toColoredSpan( + R.color.text_light, + "+$change" + ) + } + else -> getString(R.string.home_instruction_enter_amount) + } + } + } + + fun setBanner(message: String = "", action: BannerAction = CLEAR) { + with(binding) { + val hasMessage = !message.isEmpty() || action != CLEAR + groupBalance.goneIf(hasMessage) + groupBanner.goneIf(!hasMessage) + //layerLock.goneIf(!hasMessage) + + textBannerMessage.text = message + textBannerAction.text = action.action + } + } + + // + // Private UI Events + // + + private fun onModelUpdated(old: HomeViewModel.UiModel?, new: HomeViewModel.UiModel) { + logUpdate(old, new) + uiModel = new + if (old?.pendingSend != new.pendingSend) { + setSendAmount(new.pendingSend) + } + setProgress(new) // TODO: we may not need to separate anymore +// if (new.status = SYNCING) onSyncing(new) else onSynced(new) + if (new.status == SYNCED) onSynced(new) else onSyncing(new) + setSendEnabled(new.isSendEnabled, new.status == SYNCED) + } + + private fun logUpdate(old: HomeViewModel.UiModel?, new: HomeViewModel.UiModel) { + var message = "" + fun maybeComma() = if (message.length > "UiModel(".length) ", " else "" + message = when { + old == null -> "$new" + new == null -> "null" + else -> { + buildString { + append("UiModel(") + if (old.status != new.status) append("status=${new.status}") + if (old.processorInfo != new.processorInfo) { + append("${maybeComma()}processorInfo=ProcessorInfo(") + val startLength = length + fun innerComma() = if (length > startLength) ", " else "" + if (old.processorInfo.networkBlockHeight != new.processorInfo.networkBlockHeight) append( + "networkBlockHeight=${new.processorInfo.networkBlockHeight}" + ) + if (old.processorInfo.lastScannedHeight != new.processorInfo.lastScannedHeight) append( + "${innerComma()}lastScannedHeight=${new.processorInfo.lastScannedHeight}" + ) + if (old.processorInfo.lastDownloadedHeight != new.processorInfo.lastDownloadedHeight) append( + "${innerComma()}lastDownloadedHeight=${new.processorInfo.lastDownloadedHeight}" + ) + if (old.processorInfo.lastDownloadRange != new.processorInfo.lastDownloadRange) append( + "${innerComma()}lastDownloadRange=${new.processorInfo.lastDownloadRange}" + ) + if (old.processorInfo.lastScanRange != new.processorInfo.lastScanRange) append( + "${innerComma()}lastScanRange=${new.processorInfo.lastScanRange}" + ) + append(")") + } + if (old.saplingBalance?.available != new.saplingBalance?.available) append("${maybeComma()}availableBalance=${new.saplingBalance?.available}") + if (old.saplingBalance?.total != new.saplingBalance?.total) append("${maybeComma()}totalBalance=${new.saplingBalance?.total}") + if (old.pendingSend != new.pendingSend) append("${maybeComma()}pendingSend=${new.pendingSend}") + append(")") + } + } + } + twig("onModelUpdated: $message") + } + + private fun onSyncing(uiModel: HomeViewModel.UiModel) { + setAvailable(null, null, null) + } + + private fun onSynced(uiModel: HomeViewModel.UiModel) { + snake.isSynced = true + if (!uiModel.hasSaplingBalance) { + onNoFunds() + } else { + setBanner("") + setAvailable( + uiModel.saplingBalance?.available, + uiModel.saplingBalance?.total, + uiModel.transparentBalance?.available, + uiModel.unminedCount + ) + } + autoShield(uiModel) + } + + private fun autoShield(uiModel: HomeViewModel.UiModel) { + // TODO: Move the preference read to a suspending function + // First time SharedPreferences are hit, it'll perform disk IO + val isAutoshieldingAcknowledged = + Preferences.isAcknowledgedAutoshieldingInformationPrompt.get(requireApplicationContext()) + val canAutoshield = AutoShieldFragment.canAutoshield(requireApplicationContext()) + + if (uiModel.hasAutoshieldFunds && canAutoshield) { + if (!isAutoshieldingAcknowledged) { + mainActivity?.safeNavigate( + HomeFragmentDirections.actionNavHomeToAutoshieldingInfo( + true + ) + ) + } else { + twig("Autoshielding is available! Let's do this!!!") + mainActivity?.safeNavigate(HomeFragmentDirections.actionNavHomeToNavFundsAvailable()) + } + } else { + if (!isAutoshieldingAcknowledged) { + mainActivity?.safeNavigate( + HomeFragmentDirections.actionNavHomeToAutoshieldingInfo( + false + ) + ) + } + + // troubleshooting logs + if ((uiModel.transparentBalance?.available?.value ?: 0) > 0) { + twig( + "Transparent funds are available but not enough to autoshield. Available: ${ + uiModel.transparentBalance?.available.convertZatoshiToZecString( + 10 + ) + } Required: ${ + Zatoshi(ZcashWalletApp.instance.autoshieldThreshold).convertZatoshiToZecString( + 8 + ) + }" + ) + } else if ((uiModel.transparentBalance?.total?.value ?: 0) > 0) { + twig("Transparent funds have been received but they require 10 confirmations for autoshielding.") + } else if (!canAutoshield) { + twig("Could not autoshield probably because the last one occurred too recently") + } + } + } + + private fun onSend() { + if (isSendEnabled) mainActivity?.safeNavigate(R.id.action_nav_home_to_send) + } + + private fun onBannerAction(action: BannerAction) { + when (action) { + FUND_NOW -> { + MaterialAlertDialogBuilder(requireContext()) + .setMessage(R.string.home_dialog_no_balance_message) + .setTitle(R.string.home_dialog_no_balance_title) + .setCancelable(true) + .setPositiveButton(R.string.home_dialog_no_balance_button_positive) { dialog, _ -> + tapped(HOME_FUND_NOW) + dialog.dismiss() + mainActivity?.safeNavigate(R.id.action_nav_home_to_nav_receive) + } + .show() +// MaterialAlertDialogBuilder(activity) +// .setMessage("To make full use of this wallet, deposit funds to your address or tap the faucet to trigger a tiny automatic deposit.\n\nFaucet funds are made available for the community by the community for testing. So please be kind enough to return what you borrow!") +// .setTitle("No Balance") +// .setCancelable(true) +// .setPositiveButton("Tap Faucet") { dialog, _ -> +// dialog.dismiss() +// setBanner("Tapping faucet...", CANCEL) +// } +// .setNegativeButton("View Address") { dialog, _ -> +// dialog.dismiss() +// mainActivity?.safeNavigate(R.id.action_nav_home_to_nav_receive) +// } +// .show() + } + CANCEL -> { + // TODO: trigger banner / balance update + onNoFunds() + } + BannerAction.NONE -> TODO() + CLEAR -> TODO() + } + } + + private fun onNoFunds() { + setBanner(getString(R.string.home_no_balance), FUND_NOW) + } + + private fun monitorUiModelChanges() { + val existingAmount = sendViewModel.zatoshiAmount ?: Zatoshi(0) + viewModel.initializeMaybe(WalletZecFormmatter.toZecStringFull(existingAmount)) + if (existingAmount.value == 0L) onClearAmount() + viewModel.uiModels.runningReduce { old, new -> + onModelUpdated(old, new) + new + }.onCompletion { + twig("uiModel.scanReduce completed.") + }.catch { e -> + twig("exception while processing uiModels $e") + throw e + }.launchIn(resumedScope) + } + + // + // Inner classes and extensions + // + + enum class BannerAction(val action: String) { + FUND_NOW(""), + CANCEL("Cancel"), + NONE(""), + CLEAR("clear"); + + companion object { + fun from(action: String?): BannerAction { + values().forEach { + if (it.action == action) return it + } + throw IllegalArgumentException("Invalid BannerAction: $action") + } + } + } + + private fun TextView.asKey(): TextView { + val c = text[0] + setOnClickListener { + lifecycleScope.launch { + viewModel.onChar(c) + } + } + return this + } + + // + // User Interruptions + // + + // TODO: Expand this placeholder logic around when to interrupt the user. + // For now, we just need to get this in the app so that we can BEGIN capturing ECC feedback. + var hasInterrupted = false + private fun canInterruptUser(): Boolean { + // requirements: + // - we want occasional random feedback that does not occur too often + return !hasInterrupted && Math.random() < 0.01 + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + } + + override fun onStart() { + super.onStart() + twig("HomeFragment.onStart") + } + + override fun onPause() { + super.onPause() + } + + override fun onStop() { + super.onStop() + } + + override fun onDestroyView() { + super.onDestroyView() + } + + override fun onDestroy() { + super.onDestroy() + } + + override fun onDetach() { + super.onDetach() + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/HomeViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/home/HomeViewModel.kt new file mode 100644 index 0000000..a5a238e --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/home/HomeViewModel.kt @@ -0,0 +1,158 @@ +package cash.z.ecc.android.ui.home + +import androidx.lifecycle.ViewModel +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.toAppString +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.sdk.Synchronizer.Status.* +import cash.z.ecc.android.sdk.block.CompactBlockProcessor +import cash.z.ecc.android.sdk.db.entity.PendingTransaction +import cash.z.ecc.android.sdk.db.entity.isMined +import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess +import cash.z.ecc.android.sdk.ext.ZcashSdk.MINERS_FEE +import cash.z.ecc.android.sdk.model.WalletBalance +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.channels.ConflatedBroadcastChannel +import kotlinx.coroutines.flow.* +import kotlin.math.roundToInt + +// There are deprecations with the use of BroadcastChannel +@kotlinx.coroutines.ObsoleteCoroutinesApi +class HomeViewModel : ViewModel() { + + lateinit var uiModels: Flow + + lateinit var _typedChars: ConflatedBroadcastChannel + + var initialized = false + + fun initializeMaybe(preTypedChars: String = "0") { + twig("init called") + if (initialized) { + twig("Warning already initialized HomeViewModel. Ignoring call to initialize.") + return + } + + if (::_typedChars.isInitialized) { + _typedChars.close() + } + _typedChars = ConflatedBroadcastChannel() + val typedChars = _typedChars.asFlow() + val decimal = '.' // R.string.key_decimal.toAppString()[0] + val backspace = R.string.key_backspace.toAppString()[0] + val zec = typedChars.scan(preTypedChars) { acc, c -> + when { + // no-op cases + acc == "0" && c == '0' || + (c == backspace && acc == "0") + || (c == decimal && acc.contains(decimal)) -> { + acc + } + c == backspace && acc.length <= 1 -> { + "0" + } + c == backspace -> { + acc.substring(0, acc.length - 1) + } + acc == "0" && c != decimal -> { + c.toString() + } + acc.contains(decimal) && acc.length - acc.indexOf(decimal) > 8 -> { + acc + } + else -> { + "$acc$c" + } + } + } + twig("initializing view models stream") + uiModels = DependenciesHolder.synchronizer.run { + combine( + status, + processorInfo, + orchardBalances, + saplingBalances, + transparentBalances, + zec, + pendingTransactions.distinctUntilChanged() + // unfortunately we have to use an untyped array here rather than typed parameters because combine only supports up to 5 typed params + ) { flows -> + val unminedCount = (flows[6] as List).count { + it.isSubmitSuccess() && !it.isMined() + } + UiModel( + status = flows[0] as Synchronizer.Status, + processorInfo = flows[1] as CompactBlockProcessor.ProcessorInfo, + orchardBalance = flows[2] as WalletBalance?, + saplingBalance = flows[3] as WalletBalance?, + transparentBalance = flows[4] as WalletBalance?, + pendingSend = flows[5] as String, + unminedCount = unminedCount + ) + }.onStart { emit(UiModel(orchardBalance = null, saplingBalance = null, transparentBalance = null)) } + }.conflate() + } + + override fun onCleared() { + super.onCleared() + twig("HomeViewModel cleared!") + } + + suspend fun onChar(c: Char) { + _typedChars.send(c) + } + + data class UiModel( + val status: Synchronizer.Status = DISCONNECTED, + val processorInfo: CompactBlockProcessor.ProcessorInfo = CompactBlockProcessor.ProcessorInfo(null, null, null, null, null), + val orchardBalance: WalletBalance?, + val saplingBalance: WalletBalance?, + val transparentBalance: WalletBalance?, + val pendingSend: String = "0", + val unminedCount: Int = 0 + ) { + // Note: the wallet is effectively empty if it cannot cover the miner's fee + val hasFunds: Boolean get() = (saplingBalance?.available?.value ?: 0) > (MINERS_FEE.value.toDouble() / Zatoshi.ZATOSHI_PER_ZEC) // 0.00001 + val hasSaplingBalance: Boolean get() = (saplingBalance?.total?.value ?: 0) > 0L + val hasAutoshieldFunds: Boolean get() = (transparentBalance?.available?.value ?: 0) >= ZcashWalletApp.instance.autoshieldThreshold + val isSynced: Boolean get() = status == SYNCED + val isSendEnabled: Boolean get() = isSynced && hasFunds + + // Processor Info + val isDownloading = status == DOWNLOADING + val isScanning = status == SCANNING + val isValidating = status == VALIDATING + val isDisconnected = status == DISCONNECTED + val downloadProgress: Int get() { + return processorInfo.run { + if (lastDownloadRange?.isEmpty() == true) { + 100 + } else { + val progress = + ((((lastDownloadedHeight?.value ?: 0) - (lastDownloadRange?.start?.value ?: 0) + 1).coerceAtLeast(0).toFloat() / ((lastDownloadRange?.endInclusive?.value ?: 0) - (lastDownloadRange?.start?.value ?: 0) + 1)) * 100.0f).coerceAtMost( + 100.0f + ).roundToInt() + progress + } + } + } + val scanProgress: Int get() { + return processorInfo.run { + if (lastScanRange?.isEmpty() == true) { + 100 + } else { + val progress = ((((lastScannedHeight?.value ?: 0) - (lastScanRange?.start?.value ?: 0) + 1).coerceAtLeast(0).toFloat() / ((lastScanRange?.endInclusive?.value ?: 0) - (lastScanRange?.start?.value ?: 0) + 1)) * 100.0f).coerceAtMost(100.0f).roundToInt() + progress + } + } + } + val totalProgress: Float get() { + val downloadWeighted = 0.40f * (downloadProgress.toFloat() / 100.0f).coerceAtMost(1.0f) + val scanWeighted = 0.60f * (scanProgress.toFloat() / 100.0f).coerceAtMost(1.0f) + return downloadWeighted.coerceAtLeast(0.0f) + scanWeighted.coerceAtLeast(0.0f) + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/MagicSnakeLoader.kt b/app/src/main/java/cash/z/ecc/android/ui/home/MagicSnakeLoader.kt new file mode 100644 index 0000000..8934aaf --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/home/MagicSnakeLoader.kt @@ -0,0 +1,155 @@ +package cash.z.ecc.android.ui.home + +import android.animation.ValueAnimator +import com.airbnb.lottie.LottieAnimationView + +class MagicSnakeLoader( + val lottie: LottieAnimationView, + private val scanningStartFrame: Int = 100, + private val scanningEndFrame: Int = 187, + val totalFrames: Int = 200 +) : ValueAnimator.AnimatorUpdateListener { + private var isPaused: Boolean = true + private var isStarted: Boolean = false + + var isSynced: Boolean = false + set(value) { + if (value && !isStarted) { + lottie.progress = 1.0f + field = value + return + } + + // it is started but it hadn't reached the synced state yet + if (value && !field) { + field = value + playToCompletion() + } else { + field = value + } + } + + var scanProgress: Int = 0 + set(value) { + field = value + if (value > 0) { + startMaybe() + onScanUpdated() + } + } + + var downloadProgress: Int = 0 + set(value) { + field = value + if (value > 0) { + startMaybe() + } else { +// if (!isSynced) { +// lottie.progress = 0.0f +// if(!isStarted) startMaybe() +// } + } + } + + private fun startMaybe() { + + if (!isSynced && !isStarted) lottie.postDelayed( + { + // after some delay, if we're still not synced then we better start animating (unless we already are)! + if (!isSynced && isPaused) { + lottie.resumeAnimation() + isPaused = false + isStarted = true + } + }, + 200L + ) + } + + private val isDownloading get() = downloadProgress in 1..99 + private val isScanning get() = scanProgress in 1..99 + + init { + lottie.addAnimatorUpdateListener(this) + } + + override fun onAnimationUpdate(animation: ValueAnimator) { + if (isSynced || isPaused) { +// playToCompletion() + return + } + + // if we are scanning, then set the animation progress, based on the scan progress + // if we're not scanning, then we're looping + animation.currentFrame().let { frame -> + if (isDownloading) allowLoop(frame) else applyScanProgress(frame) + } + } + + private val acceptablePauseFrames = arrayOf(33, 34, 67, 68, 99) + private fun applyScanProgress(frame: Int) { + // don't hardcode the progress until the loop animation has completed, cleanly + if (isPaused) { + onScanUpdated() + } else { + // once we're ready to show scan progress, do it! Don't do extra loops. + if (frame >= scanningStartFrame || frame in acceptablePauseFrames) { + pause() + } + } + } + + private fun onScanUpdated() { + if (isSynced) { +// playToCompletion() + return + } + + if (isPaused && isStarted) { + // move forward within the scan range, proportionate to how much scanning is complete + val scanRange = scanningEndFrame - scanningStartFrame + val scanRangeProgress = scanProgress.toFloat() / 100.0f * scanRange.toFloat() + lottie.progress = (scanningStartFrame.toFloat() + scanRangeProgress) / totalFrames + } + } + + private fun playToCompletion() { + removeLoops() + unpause() + } + + private fun removeLoops() { + lottie.frame.let { frame -> + if (frame in 33..67) { + lottie.frame = frame + 34 + } else if (frame in 0..33) { + lottie.frame = frame + 67 + } + } + } + + private fun allowLoop(frame: Int) { + unpause() + if (frame >= scanningStartFrame) { + lottie.progress = 0f + } + } + + fun unpause() { + if (isPaused) { + lottie.resumeAnimation() + isPaused = false + } + } + + fun pause() { + if (!isPaused) { + lottie.pauseAnimation() + isPaused = true + } + } + + private fun ValueAnimator.currentFrame(): Int { + return ((animatedValue as Float) * totalFrames).toInt() + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/profile/AwesomeFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/profile/AwesomeFragment.kt new file mode 100644 index 0000000..ad971cc --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/profile/AwesomeFragment.kt @@ -0,0 +1,288 @@ +package cash.z.ecc.android.ui.profile + +import android.annotation.SuppressLint +import android.os.Bundle +import android.text.SpannableString +import android.text.Spanned +import android.view.LayoutInflater +import android.view.View +import android.widget.Toast +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentAwesomeBinding +import cash.z.ecc.android.ext.distribute +import cash.z.ecc.android.ext.invisibleIf +import cash.z.ecc.android.ext.onClickNavBack +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Tap.* +import cash.z.ecc.android.sdk.db.entity.* +import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString +import cash.z.ecc.android.sdk.model.WalletBalance +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.util.AddressPartNumberSpan +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +class AwesomeFragment : BaseFragment() { + override val screen = Report.Screen.AWESOME + + private val viewModel: ProfileViewModel by viewModels() + + private var lastBalance: WalletBalance? = null + + private var initialized: Boolean = false + + override fun inflate(inflater: LayoutInflater): FragmentAwesomeBinding = + FragmentAwesomeBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.hitAreaExit.onClickNavBack() { tapped(AWESOME_CLOSE) } + binding.hitAreaAddress.setOnClickListener { + tapped(COPY_TRANSPARENT_ADDRESS) + onCopyTransparentAddress() + } + binding.buttonAction.setOnClickListener { + onShieldFundsAction() + } + binding.lottieShielding.visibility = View.GONE + setStatus("Checking balance...") + } + + private fun onCopyTransparentAddress() { + resumedScope.launch { + mainActivity?.copyText(viewModel.getTransparentAddress(), "T-Address") + } + } + + override fun onResume() { + super.onResume() + if (!initialized) { + resumedScope.launch { + onAddressLoaded(viewModel.getTransparentAddress()) + updateBalance() + } + initialized = true + } + } + + private fun setStatus(status: String) { + binding.textStatus.text = status + } + + @SuppressLint("SetTextI18n") + private fun appendStatus(status: String) { + binding.textStatus.text = "${binding.textStatus.text}$status" + } + + private suspend fun updateBalance() { + val utxoCount = viewModel.fetchUtxos() + + viewModel.getTransparentBalance().let { balance -> + onBalanceUpdated(balance, utxoCount) + } + } + + private fun onAddressLoaded(address: String) { + twig("t-address loaded: $address length: ${address.length}") +// qrecycler.load(address) +// .withQuietZoneSize(3) +// .withCorrectionLevel(QRecycler.CorrectionLevel.MEDIUM) +// .into(binding.receiveQrCode) + + address.distribute(2) { i, part -> + setAddressPart(i, part) + } + } + + private fun setAddressPart(index: Int, addressPart: String) { + twig("setting t-address for part $index) $addressPart") + + val address = when (index) { + 0 -> binding.textAddressPart1 + 1 -> binding.textAddressPart2 + else -> throw IllegalArgumentException( + "Unexpected address index $index. Unable to split the t-addr into two parts." + + " Ensure that the address is valid." + ) + } + + val thinSpace = "\u2005" // 0.25 em space + val textSpan = SpannableString("${index + 1}$thinSpace$addressPart") + + textSpan.setSpan(AddressPartNumberSpan(), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + + address.text = textSpan + } + + private fun onShieldFundsAction() { + if (binding.buttonAction.isActivated) { + tapped(AWESOME_SHIELD) + mainActivity?.let { main -> + main.authenticate( + "Shield transparent funds", + getString(R.string.biometric_backup_phrase_title) + ) { + onShieldFunds() + } + } + } else { + Toast.makeText(requireContext(), "No balance to shield!", Toast.LENGTH_SHORT).show() + } + } + + private fun onDoneAction() { + viewModel.setEasterEggTriggered() + mainActivity?.safeNavigate(R.id.action_nav_awesome_to_nav_history) + } + + private fun onShieldFunds() { + twig("onShieldFunds") + lifecycleScope.launchWhenResumed { + twig("launching shield funds job") + viewModel.shieldFunds().onEach { + onPendingTxUpdated(it) + }.launchIn(lifecycleScope) + } + } + + private fun onPendingTxUpdated(tx: PendingTransaction) { + twig("shielding transaction updated: $tx") + if (tx == null) return // TODO: maybe log this + + try { + tx.toUiModel().let { model -> + binding.apply { + lottieShielding.invisibleIf(!model.showProgress) + buttonAction.isActivated = !model.showProgress || model.canCancel + buttonAction.isEnabled = true + buttonAction.refreshDrawableState() + setStatus(model.status) + appendStatus(model.details.joinToString("\n", "\n\n")) + buttonAction.apply { + text = model.primaryButtonText + setOnClickListener { model.primaryAction() } + } + } + if (model.updateBalance) { + resumedScope.launch { + delay(1000L) + updateBalance() + } + } + } + } catch (t: Throwable) { + val message = "ERROR: error while handling pending transaction update! $t" + twig(message) + mainActivity?.feedback?.report(Report.Error.NonFatal.TxUpdateFailed(t)) + mainActivity?.feedback?.report(t) + } + } + + private fun onShieldComplete(isSuccess: Boolean) { + binding.lottieShielding.visibility = View.GONE + + if (isSuccess) { + Toast.makeText(mainActivity, "Funds shielded successfully!", Toast.LENGTH_SHORT).show() + binding.buttonAction.isEnabled = true + binding.buttonAction.isActivated = true + binding.buttonAction.text = "See Details" + binding.textStatus.text = "Success!\n\nIt may take a while to show up." + binding.buttonAction.setOnClickListener { + mainActivity?.popBackTo(R.id.nav_home) + } + } else { + Toast.makeText(mainActivity, "Failed to shield funds :(", Toast.LENGTH_SHORT).show() + binding.buttonAction.isEnabled = true + binding.buttonAction.text = "Shield Transparent Funds" + binding.textStatus.text = "Failed!" + binding.buttonAction.visibility = View.GONE + } + } + + private fun onBalanceUpdated( + balance: WalletBalance = WalletBalance(Zatoshi(0), Zatoshi(0)), + utxoCount: Int = 0 + ) { + lastBalance = balance + twig("TRANSPARENT BALANCE: ${balance.available} / ${balance.total}") + binding.textStatus.text = if (balance.available.value > 0L) { + binding.buttonAction.isActivated = true + binding.buttonAction.isEnabled = true + "Balance: ᙇ${balance.available.convertZatoshiToZecString(8)}" + } else { + binding.buttonAction.isActivated = false + binding.buttonAction.isEnabled = true + "No available balance found" + } + + if (utxoCount > 0) { + appendStatus("\n\nDownloaded $utxoCount ") + appendStatus(if (utxoCount == 1) "transaction!" else "transactions!") + } + + balance.pending.takeIf { it.value > 0 }?.let { + appendStatus("\n\n(ᙇ${it.convertZatoshiToZecString()} pending confirmation)") + } + } + + private fun PendingTransaction.toUiModel() = UiModel().also { model -> + when { + isCancelled() -> { + model.status = "Shielding Cancelled!" + model.updateBalance = true + model.primaryAction = { onShieldFundsAction() } + model.details.add("Cancelled!") + } + isSubmitSuccess() -> { + model.status = "Shielding Success!" + model.primaryButtonText = "Done" + model.primaryAction = { onDoneAction() } + } + isFailure() -> { + model.status = if (isFailedEncoding()) { + "${getString(R.string.send_final_error_encoding)}\n\nPlease note:\nShielding requires funds\nto have 10 confirmations." + } else { + "${getString(R.string.send_final_error_submitting)}\n\n${this.errorMessage}" + } + + model.primaryAction = { onShieldFundsAction() } + } + else -> { + model.status = "Shielding ᙇ${lastBalance?.available.convertZatoshiToZecString()}\n\nPlease do not exit this screen!" + model.showProgress = true + if (isCreating()) { + model.canCancel = true + model.details.add("Creating transaction...") + model.primaryButtonText = getString(R.string.send_final_button_primary_cancel) + model.primaryAction = { onCancel(this) } + } else { + model.primaryButtonText = "Shielding Funds..." + if (isCreated()) model.details.add("Submitting transaction...") + } + } + } + } + + private fun onCancel(tx: PendingTransaction) { + resumedScope.launch { + viewModel.cancel(tx.id) + } + } + + // fields are ordered, as they appear, top-to-bottom in the UI because that makes it easier to reason about each screen state + data class UiModel( + var status: String = "", + val details: MutableSet = linkedSetOf(), + var showProgress: Boolean = false, + var primaryButtonText: String = "Shield Transparent Funds", + var primaryAction: () -> Unit = {}, + var canCancel: Boolean = false, + var updateBalance: Boolean = false, + ) +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileFragment.kt new file mode 100644 index 0000000..f352751 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileFragment.kt @@ -0,0 +1,208 @@ +package cash.z.ecc.android.ui.profile + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.Toast +import androidx.core.content.FileProvider.getUriForFile +import androidx.fragment.app.viewModels +import androidx.lifecycle.viewModelScope +import cash.z.ecc.android.BuildConfig +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.databinding.FragmentProfileBinding +import cash.z.ecc.android.ext.* +import cash.z.ecc.android.feedback.FeedbackFile +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback +import cash.z.ecc.android.feedback.Report.Tap.* +import cash.z.ecc.android.sdk.SdkSynchronizer +import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress +import cash.z.ecc.android.ui.MainActivity +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.util.DebugFileTwig +import cash.z.ecc.android.util.Bush +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.launch +import java.io.File + +class ProfileFragment : BaseFragment() { + override val screen = Report.Screen.PROFILE + + private val viewModel: ProfileViewModel by viewModels() + + override fun inflate(inflater: LayoutInflater): FragmentProfileBinding = + FragmentProfileBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.hitAreaSettings.onClickNavTo(R.id.action_nav_profile_to_nav_settings) + binding.hitAreaExit.onClickNavBack() { tapped(PROFILE_CLOSE) } + binding.buttonBackup.setOnClickListener { + tapped(PROFILE_BACKUP) + mainActivity?.let { main -> + main.authenticate( + getString(R.string.biometric_backup_phrase_description), + getString(R.string.biometric_backup_phrase_title) + ) { + main.safeNavigate(R.id.action_nav_profile_to_nav_backup) + } + } + } + binding.buttonRescan.setOnClickListener { + tapped(PROFILE_RESCAN) + onRescanWallet() + } + binding.textVersion.text = BuildConfig.VERSION_NAME + onClick(binding.buttonLogs) { + tapped(PROFILE_VIEW_USER_LOGS) + onViewLogs() + } + binding.buttonLogs.setOnLongClickListener { + tapped(PROFILE_VIEW_DEV_LOGS) + onViewDevLogs() + true + } + binding.iconProfile.setOnLongClickListener { + tapped(AWESOME_OPEN) + onEnterAwesomeMode() + true + } + binding.textBannerMessage.setOnClickListener { + openPlayStoreLink() + } + + if (viewModel.isEasterEggTriggered()) { + binding.iconProfile.setImageResource(R.drawable.ic_profile_zebra_02) + } + } + + private fun openPlayStoreLink() { + getString(R.string.play_store_url).takeUnless { it.isBlank() }?.let { url -> + mainActivity?.onLaunchUrl(url) + } + } + + private fun onEnterAwesomeMode() { + (context as? MainActivity)?.safeNavigate(R.id.action_nav_profile_to_nav_awesome) + ?: throw IllegalStateException( + "Cannot navigate from this activity. " + + "Expected MainActivity but found ${context?.javaClass?.simpleName}" + ) + } + + override fun onResume() { + super.onResume() + resumedScope.launch { + binding.textAddress.text = viewModel.getShieldedAddress().toAbbreviatedAddress(12, 12) + } + } + + // TODO: reduce these to one function + private fun onFullRescan() { + twig("TMP: onFullRescan: CALLED") + (viewModel.synchronizer as SdkSynchronizer).coroutineScope.launch { + try { + twig("TMP: onFullRescan: START") + viewModel.fullRescan() + Toast.makeText(ZcashWalletApp.instance, "Performing full rescan!", Toast.LENGTH_LONG).show() + mainActivity?.navController?.popBackStack() + } catch (t: Throwable) { + mainActivity?.showCriticalMessage( + "Full Rescan Failed", + "Unable to perform full rescan due to error:\n\n${t.message}" + ) + } + } + } + + private fun onQuickRescan() { + twig("TMP: onQuickRescan: CALLED") + viewModel.viewModelScope.launch { + try { + twig("TMP: onQuickRescan: START") + viewModel.quickRescan() + Toast.makeText(ZcashWalletApp.instance, "Performing quick rescan!", Toast.LENGTH_LONG).show() + mainActivity?.navController?.popBackStack() + } catch (t: Throwable) { + mainActivity?.showCriticalMessage("Quick Rescan Failed", "Unable to perform quick rescan due to error:\n\n${t.message}") + } + } + } + + private fun onWipe() { + mainActivity?.showConfirmation( + "Are you sure?", + "Wiping your data will close the app. Since your seed is preserved, " + + "this operation is probably safe but please backup your seed anyway." + + "\n\nContinue?", + "Wipe" + ) { + viewModel.wipe() + mainActivity?.finish() + } + } + + private fun onRescanWallet() { + val quickDistance = viewModel.quickScanDistance() + val fullDistance = viewModel.fullScanDistance() + mainActivity?.showRescanWalletDialog( + String.format("%,d", quickDistance), + viewModel.blocksToMinutesString(quickDistance), + String.format("%,d", fullDistance), + viewModel.blocksToMinutesString(fullDistance), + onFullRescan = ::onFullRescan, + onQuickRescan = ::onQuickRescan, + onWipe = ::onWipe + ) + } + + private fun onViewLogs() { + shareFile(userLogFile()) + } + + private fun onViewDevLogs() { + developerLogFile().let { + if (it == null) { + mainActivity?.showSnackbar("Error: No developer log found!") + } else { + shareFile(it) + } + } + } + + private fun shareFiles(vararg files: File?) { + val uris = arrayListOf().apply { + files.filterNotNull().mapNotNull { + getUriForFile(ZcashWalletApp.instance, "${BuildConfig.APPLICATION_ID}.fileprovider", it) + }.forEach { + add(it) + } + } + val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply { + putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) + type = "text/*" + } + startActivity(Intent.createChooser(intent, getString(R.string.profile_share_log_title))) + } + + fun shareFile(file: File?) { + file ?: return + val uri = getUriForFile(ZcashWalletApp.instance, "${BuildConfig.APPLICATION_ID}.fileprovider", file) + val intent = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, uri) + type = "text/plain" + } + startActivity(Intent.createChooser(intent, getString(R.string.profile_share_log_title))) + } + + private fun userLogFile(): File? { + return mainActivity?.feedbackCoordinator?.findObserver()?.file + } + + private fun developerLogFile(): File? { + return Bush.trunk.find()?.file + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileViewModel.kt new file mode 100644 index 0000000..726f2eb --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileViewModel.kt @@ -0,0 +1,164 @@ +package cash.z.ecc.android.ui.profile + +import android.widget.Toast +import androidx.lifecycle.ViewModel +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.Const +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.Initializer +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.sdk.db.entity.PendingTransaction +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.sdk.model.WalletBalance +import cash.z.ecc.android.sdk.tool.DerivationTool +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import kotlin.time.DurationUnit +import kotlin.time.toDuration + +class ProfileViewModel : ViewModel() { + + val synchronizer: Synchronizer = DependenciesHolder.synchronizer + + private val lockBox: LockBox = DependenciesHolder.lockBox + + private val prefs: LockBox = DependenciesHolder.prefs + + // TODO: track this in the app and then fetch. For now, just estimate the blocks per second. + val bps = 40 + + suspend fun getShieldedAddress(): String = synchronizer.getAddress() + + suspend fun getTransparentAddress(): String { + return synchronizer.getTransparentAddress() + } + + override fun onCleared() { + super.onCleared() + twig("ProfileViewModel cleared!") + } + + suspend fun fetchUtxos(): Int { + val address = getTransparentAddress() + val height: Long = lockBox[Const.Backup.BIRTHDAY_HEIGHT] + ?: synchronizer.network.saplingActivationHeight.value + return synchronizer.refreshUtxos(address, BlockHeight.new(synchronizer.network, height)) + ?: 0 + } + + suspend fun getTransparentBalance(): WalletBalance { + val address = getTransparentAddress() + return synchronizer.getTransparentBalance(address) + } + + fun shieldFunds(): Flow { + return lockBox.getBytes(Const.Backup.SEED)?.let { + val sk = runBlocking { DerivationTool.deriveSpendingKeys(it, synchronizer.network)[0] } + val tsk = + runBlocking { DerivationTool.deriveTransparentSecretKey(it, synchronizer.network) } + val addr = runBlocking { + DerivationTool.deriveTransparentAddressFromPrivateKey( + tsk, + synchronizer.network + ) + } + synchronizer.shieldFunds( + sk, + tsk, + "${ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX}\nAll UTXOs from $addr" + ).onEach { + twig("Received shielding txUpdate: ${it?.toString()}") +// updateMetrics(it) +// reportFailures(it) + } + } ?: throw IllegalStateException("Seed was expected but it was not found!") + } + + fun setEasterEggTriggered() { + lockBox.setBoolean(Const.Pref.EASTER_EGG_TRIGGERED_SHIELDING, true) + } + + fun isEasterEggTriggered(): Boolean { + return lockBox.getBoolean(Const.Pref.EASTER_EGG_TRIGGERED_SHIELDING) + } + + suspend fun cancel(id: Long) { + synchronizer.cancelSpend(id) + } + + fun wipe() { + synchronizer.stop() + Toast.makeText( + ZcashWalletApp.instance, + "SUCCESS! Wallet data cleared. Please relaunch to rescan!", + Toast.LENGTH_LONG + ).show() + runBlocking { + Initializer.erase( + ZcashWalletApp.instance, + ZcashWalletApp.instance.defaultNetwork + ) + } + } + + suspend fun fullRescan() { + synchronizer.latestBirthdayHeight?.let { + rewindTo(it) + } + } + + suspend fun quickRescan() { + synchronizer.latestHeight?.let { + val newHeightValue = + (it.value - 8064L).coerceAtLeast(synchronizer.network.saplingActivationHeight.value) + rewindTo(BlockHeight.new(synchronizer.network, newHeightValue)) + } + } + + private suspend fun rewindTo(targetHeight: BlockHeight) { + twig("TMP: rewinding to targetHeight $targetHeight") + synchronizer.rewindToNearestHeight(targetHeight, true) + } + + fun fullScanDistance(): Long { + synchronizer.latestHeight?.let { latestHeight -> + synchronizer.latestBirthdayHeight?.let { latestBirthdayHeight -> + return (latestHeight.value - latestBirthdayHeight.value).coerceAtLeast(0) + } + } + return 0 + } + + fun quickScanDistance(): Int { + val latest = synchronizer.latestHeight + val oneWeek = 60 * 60 * 24 / 75 * 7 // a week's worth of blocks + val height = BlockHeight.new( + synchronizer.network, + ((latest?.value ?: synchronizer.network.saplingActivationHeight.value) - oneWeek) + .coerceAtLeast(synchronizer.network.saplingActivationHeight.value) + ) + val foo = runBlocking { + synchronizer.getNearestRewindHeight(height) + } + return ((latest?.value ?: 0) - foo.value).toInt().coerceAtLeast(0) + } + + fun blocksToMinutesString(blocks: BlockHeight): String { + val duration = (blocks.value / bps.toDouble()).toDuration(DurationUnit.SECONDS) + return duration.toString(DurationUnit.MINUTES).replace("m", " minutes") + } + + fun blocksToMinutesString(blocks: Int): String { + val duration = (blocks / bps.toDouble()).toDuration(DurationUnit.SECONDS) + return duration.toString(DurationUnit.MINUTES).replace("m", " minutes") + } + + fun blocksToMinutesString(blocks: Long): String { + val duration = (blocks / bps.toDouble()).toDuration(DurationUnit.SECONDS) + return duration.toString(DurationUnit.MINUTES).replace("m", " minutes") + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveTabFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveTabFragment.kt new file mode 100644 index 0000000..74a6250 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveTabFragment.kt @@ -0,0 +1,88 @@ +package cash.z.ecc.android.ui.receive + +import android.content.Context +import android.os.Bundle +import android.text.SpannableString +import android.text.Spanned +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import androidx.fragment.app.viewModels +import cash.z.android.qrecycler.QRecycler +import cash.z.ecc.android.BuildConfig +import cash.z.ecc.android.databinding.FragmentTabReceiveShieldedBinding +import cash.z.ecc.android.ext.distribute +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.util.AddressPartNumberSpan +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.launch + +class ReceiveTabFragment : + BaseFragment() { + override val screen = Report.Screen.RECEIVE + + private val viewModel: ReceiveViewModel by viewModels() + + lateinit var qrecycler: QRecycler + + lateinit var addressParts: Array + + override fun inflate(inflater: LayoutInflater): FragmentTabReceiveShieldedBinding = + FragmentTabReceiveShieldedBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + addressParts = arrayOf( + binding.textAddressPart1, + binding.textAddressPart2, + binding.textAddressPart3, + binding.textAddressPart4, + binding.textAddressPart5, + binding.textAddressPart6, + binding.textAddressPart7, + binding.textAddressPart8 + ) + binding.iconQrLogo.setOnLongClickListener { + mainActivity?.takeIf { BuildConfig.FLAVOR.lowercase().contains("testnet") }?.let { + it.copyAddress(null) + it.onLaunchUrl("https://faucet.testnet.z.cash/") + true + } ?: false + } + } + + override fun onAttach(context: Context) { + qrecycler = QRecycler() // inject! :) + super.onAttach(context) + } + + override fun onResume() { + super.onResume() + resumedScope.launch { + onAddressLoaded(viewModel.getAddress()) + } + } + + private fun onAddressLoaded(address: String) { + twig("address loaded: $address length: ${address.length}") + qrecycler.load(address) + .withQuietZoneSize(3) + .withCorrectionLevel(QRecycler.CorrectionLevel.MEDIUM) + .into(binding.receiveQrCode) + + address.distribute(8) { i, part -> + setAddressPart(i, part) + } + } + + private fun setAddressPart(index: Int, addressPart: String) { + twig("setting address for part $index) $addressPart") + val thinSpace = "\u2005" // 0.25 em space + val textSpan = SpannableString("${index + 1}$thinSpace$addressPart") + + textSpan.setSpan(AddressPartNumberSpan(), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + + addressParts[index].text = textSpan + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveViewModel.kt new file mode 100644 index 0000000..f7fbc4d --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveViewModel.kt @@ -0,0 +1,18 @@ +package cash.z.ecc.android.ui.receive + +import androidx.lifecycle.ViewModel +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.util.twig + +class ReceiveViewModel : ViewModel() { + + private val synchronizer: Synchronizer = DependenciesHolder.synchronizer + + suspend fun getAddress(): String = synchronizer.getAddress() + + override fun onCleared() { + super.onCleared() + twig("ReceiveViewModel cleared!") + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt b/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt new file mode 100644 index 0000000..7e04b37 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt @@ -0,0 +1,66 @@ +package cash.z.ecc.android.ui.scan + +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import cash.z.ecc.android.util.twig +import com.google.zxing.BinaryBitmap +import com.google.zxing.NotFoundException +import com.google.zxing.PlanarYUVLuminanceSource +import com.google.zxing.Reader +import com.google.zxing.common.HybridBinarizer +import com.google.zxing.qrcode.QRCodeReader + +class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Unit) : + ImageAnalysis.Analyzer { + + private val reader = QRCodeReader() + + override fun analyze(image: ImageProxy) { + image.toBinaryBitmap().let { bitmap -> + val qrContent = bitmap.decodeWith(reader) ?: bitmap.flip().decodeWith(reader) + if (qrContent == null) { + image.close() + } else { + onImageScan(qrContent, image) + } + } + } + + private fun ImageProxy.toBinaryBitmap(): BinaryBitmap { + return planes[0].buffer.let { buffer -> + ByteArray(buffer.remaining()).also { buffer.get(it) } + }.let { bytes -> + PlanarYUVLuminanceSource(bytes, width, height, 0, 0, width, height, false) + }.let { source -> + BinaryBitmap(HybridBinarizer(source)) + } + } + + private fun BinaryBitmap.decodeWith(reader: Reader): String? { + return try { + reader.decode(this).toString() + } catch (e: NotFoundException) { + // these happen frequently. Whenever no QR code is found in the frame. No need to log. + null + } catch (e: Throwable) { + twig("Error while scanning QR: $e") + twig(e) + null + } + } + + private fun BinaryBitmap.flip(): BinaryBitmap { + blackMatrix.apply { + repeat(width) { w -> + repeat(height) { h -> + flip(w, h) + } + } + } + return this + } + + private fun onImageScan(result: String, image: ImageProxy) { + scanCallback(result, image) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt new file mode 100644 index 0000000..3f0448f --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt @@ -0,0 +1,216 @@ +package cash.z.ecc.android.ui.scan + +import android.content.Context +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.LayoutInflater +import android.view.View +import androidx.camera.core.* +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.core.content.ContextCompat +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentScanBinding +import cash.z.ecc.android.ext.onClickNavBack +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Tap.SCAN_BACK +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.send.SendViewModel +import cash.z.ecc.android.util.twig +import com.google.common.util.concurrent.ListenableFuture +import kotlinx.coroutines.launch +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class ScanFragment : BaseFragment() { + + override val screen = Report.Screen.SCAN + + private val viewModel: ScanViewModel by viewModels() + + private val sendViewModel: SendViewModel by activityViewModels() + + private lateinit var cameraProviderFuture: ListenableFuture + + private var cameraExecutor: ExecutorService? = null + + override fun inflate(inflater: LayoutInflater): FragmentScanBinding = + FragmentScanBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (cameraExecutor != null) cameraExecutor?.shutdown() + cameraExecutor = Executors.newSingleThreadExecutor() + + binding.backButtonHitArea.onClickNavBack() { tapped(SCAN_BACK) } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (!allPermissionsGranted()) getRuntimePermissions() + } + + override fun onAttach(context: Context) { + super.onAttach(context) + cameraProviderFuture = ProcessCameraProvider.getInstance(context) + cameraProviderFuture.addListener( + Runnable { + bindPreview(cameraProviderFuture.get()) + }, + ContextCompat.getMainExecutor(context) + ) + } + + override fun onDestroyView() { + super.onDestroyView() + cameraExecutor?.shutdown() + cameraExecutor = null + } + + private fun bindPreview(cameraProvider: ProcessCameraProvider) { + // Most of the code here is adapted from: https://github.com/android/camera-samples/blob/master/CameraXBasic/app/src/main/java/com/android/example/cameraxbasic/fragments/CameraFragment.kt + // it's worth keeping tabs on that implementation because they keep making breaking changes to these APIs! + + // Get screen metrics used to setup camera for full screen resolution + val metrics = DisplayMetrics().also { binding.preview.display.getRealMetrics(it) } + val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels) + val rotation = binding.preview.display.rotation + + val preview = + Preview.Builder().setTargetName("Preview").setTargetAspectRatio(screenAspectRatio) + .setTargetRotation(rotation).build() + + val cameraSelector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + + val imageAnalysis = ImageAnalysis.Builder().setTargetAspectRatio(screenAspectRatio) + .setTargetRotation(rotation) + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + + imageAnalysis.setAnalyzer( + cameraExecutor!!, + QrAnalyzer { q, i -> + onQrScanned(q, i) + } + ) + + // Must unbind the use-cases before rebinding them + cameraProvider.unbindAll() + + try { + cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis) + preview.setSurfaceProvider(binding.preview.surfaceProvider) + } catch (t: Throwable) { + // TODO: consider bubbling this up to the user + mainActivity?.feedback?.report(t) + twig("Error while opening the camera: $t") + } + } + + /** + * Adapted from: https://github.com/android/camera-samples/blob/master/CameraXBasic/app/src/main/java/com/android/example/cameraxbasic/fragments/CameraFragment.kt#L350 + */ + private fun aspectRatio(width: Int, height: Int): Int { + val previewRatio = kotlin.math.max(width, height).toDouble() / kotlin.math.min( + width, + height + ) + if (kotlin.math.abs(previewRatio - (4.0 / 3.0)) + <= kotlin.math.abs(previewRatio - (16.0 / 9.0)) + ) { + return AspectRatio.RATIO_4_3 + } + return AspectRatio.RATIO_16_9 + } + + private fun onQrScanned(qrContent: String, image: ImageProxy) { + resumedScope.launch { + val parsed = viewModel.parse(qrContent) + if (parsed == null) { + val network = viewModel.networkName + binding.textScanError.text = getString(R.string.scan_invalid_address, network, qrContent) + image.close() + } else { /* continue scanning*/ + binding.textScanError.text = "" + sendViewModel.toAddress = parsed + mainActivity?.safeNavigate(R.id.action_nav_scan_to_nav_send) + } + } + } + +// private fun updateOverlay(detectedObjects: DetectedObjects) { +// if (detectedObjects.objects.isEmpty()) { +// return +// } +// +// overlay.setSize(detectedObjects.imageWidth, detectedObjects.imageHeight) +// val list = mutableListOf() +// for (obj in detectedObjects.objects) { +// val box = obj.boundingBox +// val name = "${categoryNames[obj.classificationCategory]}" +// val confidence = +// if (obj.classificationCategory != FirebaseVisionObject.CATEGORY_UNKNOWN) { +// val confidence: Int = obj.classificationConfidence!!.times(100).toInt() +// "$confidence%" +// } else { +// "" +// } +// list.add(BoxData("$name $confidence", box)) +// } +// overlay.set(list) +// } + + // + // Permissions + // + + private val requiredPermissions: Array + get() { + return try { + val info = mainActivity?.packageManager + ?.getPackageInfo(mainActivity?.packageName ?: "", PackageManager.GET_PERMISSIONS) + val ps = info?.requestedPermissions + if (ps != null && ps.isNotEmpty()) { + ps + } else { + arrayOfNulls(0) + } + } catch (e: Exception) { + arrayOfNulls(0) + } + } + + private fun allPermissionsGranted(): Boolean { + for (permission in requiredPermissions) { + if (!isPermissionGranted(mainActivity!!, permission!!)) { + return false + } + } + return true + } + + private fun getRuntimePermissions() { + val allNeededPermissions = arrayListOf() + for (permission in requiredPermissions) { + if (!isPermissionGranted(mainActivity!!, permission!!)) { + allNeededPermissions.add(permission) + } + } + + if (allNeededPermissions.isNotEmpty()) { + requestPermissions(allNeededPermissions.toTypedArray(), CAMERA_PERMISSION_REQUEST) + } + } + + companion object { + private const val CAMERA_PERMISSION_REQUEST = 1002 + + private fun isPermissionGranted(context: Context, permission: String): Boolean { + return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/scan/ScanViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/scan/ScanViewModel.kt new file mode 100644 index 0000000..fbe87bd --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/scan/ScanViewModel.kt @@ -0,0 +1,29 @@ +package cash.z.ecc.android.ui.scan + +import androidx.lifecycle.ViewModel +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.util.twig + +class ScanViewModel : ViewModel() { + + private val synchronizer: Synchronizer = DependenciesHolder.synchronizer + + val networkName get() = synchronizer.network.networkName + + suspend fun parse(qrCode: String): String? { + // temporary parse code to allow both plain addresses and those that start with zcash: + // TODO: replace with more robust ZIP-321 handling of QR codes + val address = if (qrCode.startsWith("zcash:")) { + qrCode.substring(6, qrCode.indexOf("?").takeUnless { it == -1 } ?: qrCode.length) + } else { + qrCode + } + return if (synchronizer.validateAddress(address).isNotValid) null else address + } + + override fun onCleared() { + super.onCleared() + twig("${javaClass.simpleName} cleared!") + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldFragment.kt new file mode 100644 index 0000000..7436528 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldFragment.kt @@ -0,0 +1,236 @@ +package cash.z.ecc.android.ui.send + +import android.content.Context +import android.os.Bundle +import android.text.format.DateUtils +import android.view.LayoutInflater +import android.view.View +import android.widget.Toast +import androidx.core.view.isVisible +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentAutoShieldBinding +import cash.z.ecc.android.ext.goneIf +import cash.z.ecc.android.ext.invisibleIf +import cash.z.ecc.android.ext.requireApplicationContext +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.preference.Preferences +import cash.z.ecc.android.preference.model.get +import cash.z.ecc.android.preference.model.put +import cash.z.ecc.android.sdk.db.entity.* +import cash.z.ecc.android.sdk.ext.collectWith +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import java.time.Clock + +class AutoShieldFragment : BaseFragment() { + override val screen = Report.Screen.AUTO_SHIELD_FINAL + + private val viewModel: AutoShieldViewModel by viewModels() + + private val uiModels = MutableStateFlow(UiModel()) + + override fun inflate(inflater: LayoutInflater): FragmentAutoShieldBinding = + FragmentAutoShieldBinding.inflate(inflater) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (null == savedInstanceState) { + setAutoshield(requireApplicationContext()) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.backButtonHitArea.setOnClickListener { + onExit().also { tapped(Report.Tap.AUTO_SHIELD_FINAL_CLOSE) } + } + mainActivity?.preventBackPress(this) + uiModels.collectWith(lifecycleScope, ::updateUi) + } + + override fun onAttach(context: Context) { + super.onAttach(context) + mainActivity?.apply { + viewModel.shieldFunds().onEach { p: PendingTransaction -> + try { + uiModels.value = p.toUiModel() + } catch (t: Throwable) { + val message = "ERROR: error while handling pending transaction update! $t" + twig(message) + mainActivity?.feedback?.report(Report.Error.NonFatal.TxUpdateFailed(t)) + mainActivity?.feedback?.report(t) + } + }.launchIn(lifecycleScope) + } + } + + private fun updateUi(uiModel: UiModel) = uiModel.apply { + if (isResumed) { + // if this is the first success + if (!binding.lottieSuccess.isVisible && showSuccess) { + mainActivity?.vibrateSuccess() + } + + binding.backButton.goneIf(!showCloseIcon) + binding.textTitle.text = title + binding.lottieShielding.invisibleIf(!showShielding) + if (pauseShielding) binding.lottieShielding.pauseAnimation() + binding.lottieSuccess.invisibleIf(!showSuccess) + binding.imageFailed.invisibleIf(!isFailure) + binding.textStatus.text = statusMessage + + binding.textStatus.text = when { + showStatusDetails && showStatusMessage -> statusDetails + showStatusDetails -> statusDetails + showStatusMessage -> statusMessage + else -> "" + } + + binding.buttonPrimary.text = primaryButtonText + binding.buttonPrimary.setOnClickListener { primaryAction() } + binding.buttonMoreInfo.text = moreInfoButtonText + binding.buttonMoreInfo.goneIf(!showMoreInfoButton) + binding.buttonMoreInfo.setOnClickListener { moreInfoAction() } + + if (showSuccess) { + if (viewModel.updateAutoshieldAchievement()) { + mainActivity?.showSnackbar("Achievement unlocked! Golden Zebra.", "View") { + mainActivity?.safeNavigate(R.id.action_nav_shield_final_to_profile) + Toast.makeText(mainActivity, "Your Zebra is now yellow because you are great", Toast.LENGTH_LONG).show() + } + } + } + } + } + + private fun onExit() { + mainActivity?.safeNavigate(R.id.action_nav_shield_final_to_nav_home) + } + + private fun onCancel(tx: PendingTransaction) { + viewModel.cancel(tx.id) + } + + private fun onSeeDetails() { + mainActivity?.safeNavigate(R.id.action_nav_shield_final_to_nav_history) + } + + private fun PendingTransaction.toUiModel() = UiModel().also { model -> + when { + isCancelled() -> { + model.title = getString(R.string.send_final_result_cancelled) + model.pauseShielding = true + model.primaryButtonText = getString(R.string.send_final_button_primary_back) + model.primaryAction = { mainActivity?.navController?.popBackStack() } + } + isSubmitSuccess() -> { + model.showCloseIcon = true + model.title = getString(R.string.send_final_button_primary_sent) + model.showShielding = false + model.showSuccess = true + model.primaryButtonText = getString(R.string.done) + model.primaryAction = ::onExit + model.showMoreInfoButton = true + model.moreInfoButtonText = getString(R.string.send_final_button_primary_details) + model.moreInfoAction = ::onSeeDetails + } + isFailure() -> { + model.showCloseIcon = true + model.title = + if (isFailedEncoding()) getString(R.string.send_final_error_encoding) else getString( + R.string.send_final_error_submitting + ) + model.showShielding = false + model.showSuccess = false + model.isFailure = true + model.showStatusDetails = false + model.primaryButtonText = getString(R.string.translated_button_back) + model.primaryAction = { mainActivity?.navController?.popBackStack() } + model.showMoreInfoButton = errorMessage != null + model.moreInfoButtonText = getString(R.string.send_more_info) + model.moreInfoAction = { + showMoreInfo(errorMessage ?: "No details available") + } + } + isCreating() -> { + model.showCloseIcon = false + model.primaryButtonText = getString(R.string.send_final_button_primary_cancel) + model.showStatusMessage = true + model.statusMessage = "Creating transaction..." + model.primaryAction = { onCancel(this) } + } + isCreated() -> { + model.showStatusMessage = true + model.statusMessage = "Submitting transaction..." + model.primaryButtonText = getString(R.string.translated_button_back) + model.primaryAction = { mainActivity?.navController?.popBackStack() } + } + else -> { + model.primaryButtonText = getString(R.string.translated_button_back) + model.primaryAction = { mainActivity?.navController?.popBackStack() } + } + } + } + + private fun showMoreInfo(info: String) { + val current = uiModels.value + uiModels.value = current.copy( + showMoreInfoButton = true, + moreInfoButtonText = getString(R.string.done), + moreInfoAction = ::onExit, + showStatusMessage = false, + showStatusDetails = true, + statusDetails = info + ) + } + + // fields are ordered, as they appear, top-to-bottom in the UI because that makes it easier to reason about each screen state + data class UiModel( + var showCloseIcon: Boolean = false, + var title: String = "Shielding Now!", + var showShielding: Boolean = true, + var pauseShielding: Boolean = false, + var showSuccess: Boolean = false, + var isFailure: Boolean = false, + var statusMessage: String = "", + var statusDetails: String = "", + var showStatusDetails: Boolean = false, + var showStatusMessage: Boolean = true, + var primaryButtonText: String = "Cancel", + var primaryAction: () -> Unit = {}, + var moreInfoButtonText: String = "", + var showMoreInfoButton: Boolean = false, + var moreInfoAction: () -> Unit = {}, + ) + + companion object { + private const val maxAutoshieldFrequency: Long = 30 * DateUtils.MINUTE_IN_MILLIS + + /** + * @param clock Optionally allows injecting a clock, in order to make this testable. + */ + fun canAutoshield(context: Context, clock: Clock = Clock.systemUTC()): Boolean { + val currentEpochMillis = clock.millis() + val lastAutoshieldEpochMillis = Preferences.lastAutoshieldingEpochMillis.get(context) + + val isLastAutoshieldOld = (currentEpochMillis - lastAutoshieldEpochMillis) > maxAutoshieldFrequency + // Prevent a corner case where a user with a clock in the future during one autoshielding prompt + // could prevent all subsequent autoshielding prompts. + val isTimeTraveling = lastAutoshieldEpochMillis > currentEpochMillis + + return isLastAutoshieldOld || isTimeTraveling + } + + /** + * @param clock Optionally allows injecting a clock, in order to make this testable. + */ + private fun setAutoshield(context: Context, clock: Clock = Clock.systemUTC()) = + Preferences.lastAutoshieldingEpochMillis.put(context, clock.millis()) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldViewModel.kt new file mode 100644 index 0000000..833fd00 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldViewModel.kt @@ -0,0 +1,158 @@ +package cash.z.ecc.android.ui.send + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.Const +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.sdk.db.entity.PendingTransaction +import cash.z.ecc.android.sdk.db.entity.isMined +import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString +import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.sdk.model.WalletBalance +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.sdk.tool.DerivationTool +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combineTransform +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +class AutoShieldViewModel : ViewModel() { + + private val synchronizer: Synchronizer = DependenciesHolder.synchronizer + + private val lockBox: LockBox = DependenciesHolder.lockBox + + var latestBalance: BalanceModel? = null + + val balances + get() = combineTransform( + synchronizer.orchardBalances, + synchronizer.saplingBalances, + synchronizer.transparentBalances, + ) { o, s, t -> + BalanceModel(o, s, t).let { + latestBalance = it + emit(it) + } + } + + val statuses + get() = combineTransform( + synchronizer.saplingBalances, + synchronizer.pendingTransactions, + synchronizer.processorInfo + ) { balance, pending, info -> + val unconfirmed = pending.filter { !it.isConfirmed(info.networkBlockHeight) } + val unmined = pending.filter { it.isSubmitSuccess() && !it.isMined() } + val pending = balance?.pending?.value ?: 0 + emit(StatusModel(unmined, unconfirmed, pending, info.networkBlockHeight)) + } + + private fun PendingTransaction.isConfirmed(networkBlockHeight: BlockHeight?): Boolean { + return networkBlockHeight?.let { height -> + isMined() && (height.value - minedHeight + 1) > 10 + } ?: false + } + + fun cancel(id: Long) { + viewModelScope.launch { + synchronizer.cancelSpend(id) + } + } + + /** + * Update the autoshielding achievement and return true if this is the first time. + */ + fun updateAutoshieldAchievement(): Boolean { + val existingValue = lockBox.getBoolean(Const.Pref.EASTER_EGG_TRIGGERED_SHIELDING) + return if (!existingValue) { + lockBox.setBoolean(Const.Pref.EASTER_EGG_TRIGGERED_SHIELDING, true) + true + } else { + false + } + } + + fun shieldFunds(): Flow { + return lockBox.getBytes(Const.Backup.SEED)?.let { + val sk = runBlocking { DerivationTool.deriveSpendingKeys(it, synchronizer.network)[0] } + val tsk = runBlocking { + DerivationTool.deriveTransparentSecretKey( + it, + synchronizer.network + ) + } + val addr = runBlocking { + DerivationTool.deriveTransparentAddressFromPrivateKey( + tsk, + synchronizer.network + ) + } + synchronizer.shieldFunds( + sk, + tsk, + "${ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX}\nAll UTXOs from $addr" + ).onEach { tx -> + twig("Received shielding txUpdate: ${tx?.toString()}") +// updateMetrics(it) +// reportFailures(it) + } + } ?: throw IllegalStateException("Seed was expected but it was not found!") + } + + data class BalanceModel( + val orchardBalance: WalletBalance?, + val saplingBalance: WalletBalance?, + val transparentBalance: WalletBalance?, + ) { + val balanceShielded: String = saplingBalance?.available.toDisplay() + val balanceTransparent: String = transparentBalance?.available.toDisplay() + val balanceTotal: String = + ((saplingBalance?.available ?: Zatoshi(0)) + (transparentBalance?.available + ?: Zatoshi(0))).toDisplay() + val canAutoShield: Boolean = (transparentBalance?.available?.value ?: 0) > 0L + + val maxLength = + maxOf(balanceShielded.length, balanceTransparent.length, balanceTotal.length) + val paddedShielded = pad(balanceShielded) + val paddedTransparent = pad(balanceTransparent) + val paddedTotal = pad(balanceTotal) + + private fun Zatoshi?.toDisplay(): String { + return convertZatoshiToZecString(8, 8) + } + + private fun pad(balance: String): String { + var diffLength = maxLength - balance.length + return buildString { + repeat(diffLength) { + append(' ') + } + append(balance) + } + } + } + + data class StatusModel( + val pendingUnconfirmed: List = listOf(), + val pendingUnmined: List = listOf(), + val pendingBalance: Long = 0L, + val latestHeight: BlockHeight? = null, + ) { + val hasUnconfirmed = pendingUnconfirmed.isNotEmpty() + val hasUnmined = pendingUnmined.isNotEmpty() + val hasPendingBalance = pendingBalance > 0L + + fun remainingConfirmations(latestHeight: Int, confirmationsRequired: Int = 10) = + pendingUnconfirmed + .map { confirmationsRequired - (latestHeight - it.minedHeight + 1) } + .filter { it > 0 } + .sortedDescending() + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/FundsAvailableFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/FundsAvailableFragment.kt new file mode 100644 index 0000000..ea1f731 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/send/FundsAvailableFragment.kt @@ -0,0 +1,37 @@ +package cash.z.ecc.android.ui.send + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentFundsAvailableBinding +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.ui.base.BaseFragment + +class FundsAvailableFragment : BaseFragment() { + override val screen = Report.Screen.AUTO_SHIELD_AVAILABLE + + override fun inflate(inflater: LayoutInflater): FragmentFundsAvailableBinding = + FragmentFundsAvailableBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.buttonAction.setOnClickListener { + onProceedWithAutoshielding() + } + } + + /** + * This function probably serves no purpose other than to click through to the next screen + */ + private fun onProceedWithAutoshielding() { + mainActivity?.let { main -> + main.authenticate( + "Shield transparent funds", + getString(R.string.biometric_backup_phrase_title) + ) { + main.safeNavigate(R.id.action_nav_funds_available_to_nav_shield_final) + } + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt new file mode 100644 index 0000000..d853c81 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt @@ -0,0 +1,169 @@ +package cash.z.ecc.android.ui.send + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import androidx.fragment.app.activityViewModels +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentSendFinalBinding +import cash.z.ecc.android.ext.WalletZecFormmatter +import cash.z.ecc.android.ext.goneIf +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Tap.SEND_FINAL_CLOSE +import cash.z.ecc.android.sdk.SdkSynchronizer +import cash.z.ecc.android.sdk.db.entity.* +import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +class SendFinalFragment : BaseFragment() { + override val screen = Report.Screen.SEND_FINAL + + private val sendViewModel: SendViewModel by activityViewModels() + + override fun inflate(inflater: LayoutInflater): FragmentSendFinalBinding = + FragmentSendFinalBinding.inflate(inflater) + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.buttonPrimary.setOnClickListener { + onReturnToSend() + } + binding.backButtonHitArea.setOnClickListener { + onExit().also { tapped(SEND_FINAL_CLOSE) } + } + binding.textConfirmation.text = + "${getString(R.string.send_final_sending)} ${WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount)} ${getString(R.string.symbol)}\n${getString(R.string.send_final_to)}\n${sendViewModel.toAddress.toAbbreviatedAddress()}" + mainActivity?.preventBackPress(this) + } + + override fun onAttach(context: Context) { + super.onAttach(context) + mainActivity?.apply { + sendViewModel.send().onEach { + onPendingTxUpdated(it) + }.launchIn((sendViewModel.synchronizer as SdkSynchronizer).coroutineScope) + } + } + + private fun onPendingTxUpdated(tx: PendingTransaction?) { + if (tx == null || !isResumed) return // TODO: maybe log this + + try { + tx.toUiModel().let { model -> + updateUi(model) + } + + // only hold onto the view model if the transaction failed so that the user can retry + if (tx.isSubmitSuccess()) { + sendViewModel.reset() + // celebrate + mainActivity?.vibrate(0, 100, 100, 200, 200, 400) + } + } catch (t: Throwable) { + val message = "ERROR: error while handling pending transaction update! $t" + twig(message) + mainActivity?.feedback?.report(Report.Error.NonFatal.TxUpdateFailed(t)) + mainActivity?.feedback?.report(t) + } + } + + private fun onExit() { + sendViewModel.reset() + mainActivity?.safeNavigate(R.id.action_nav_send_final_to_nav_home) + } + + private fun onCancel(tx: PendingTransaction) { + sendViewModel.cancel(tx.id) + } + + private fun onReturnToSend() { + mainActivity?.safeNavigate(R.id.action_nav_send_final_to_nav_send) + } + + private fun onSeeDetails() { + sendViewModel.reset() + mainActivity?.safeNavigate(R.id.action_nav_send_final_to_nav_history) + } + + private fun updateUi(model: UiModel) { + binding.apply { + backButton.goneIf(!model.showCloseIcon) + backButtonHitArea.goneIf(!model.showCloseIcon) + + textConfirmation.text = model.title + lottieSending.goneIf(!model.showProgress) + if (!model.showProgress) lottieSending.pauseAnimation() else lottieSending.playAnimation() + errorMessage.text = model.errorMessage + buttonPrimary.apply { + text = model.primaryButtonText + setOnClickListener { model.primaryAction() } + } + buttonMoreInfo.apply { + goneIf(!model.showSecondaryButton) + text = getString(R.string.send_more_info) + setOnClickListener { + binding.textMoreInfo.text = model.errorDescription + text = getString(R.string.done) + setOnClickListener { onExit() } + } + } + } + } + + private fun PendingTransaction.toUiModel() = UiModel().also { model -> + when { + isCancelled() -> { + model.title = getString(R.string.send_final_result_cancelled) + model.primaryButtonText = getString(R.string.send_final_button_primary_back) + model.primaryAction = { onReturnToSend() } + } + isSubmitSuccess() -> { + model.title = getString(R.string.send_final_button_primary_sent) + model.primaryButtonText = getString(R.string.send_final_button_primary_details) + model.primaryAction = { onSeeDetails() } + } + isFailure() -> { + model.title = getString(R.string.send_final_button_primary_failed) + model.errorMessage = if (isFailedEncoding()) getString(R.string.send_final_error_encoding) else getString( + R.string.send_final_error_submitting + ) + model.errorDescription = errorMessage.toString() + model.primaryButtonText = getString(R.string.send_final_button_primary_retry) + model.primaryAction = { onReturnToSend() } + model.showSecondaryButton = true + } + else -> { + model.title = "${getString(R.string.send_final_sending)} ${WalletZecFormmatter.toZecStringFull( + Zatoshi(value))} ${getString(R.string.symbol)} ${getString(R.string.send_final_to)}\n${toAddress.toAbbreviatedAddress()}" + model.showProgress = true + if (isCreating()) { + model.showCloseIcon = false + model.primaryButtonText = getString(R.string.send_final_button_primary_cancel) + model.primaryAction = { onCancel(this) } + } else { + model.primaryButtonText = getString(R.string.send_final_button_primary_details) + model.primaryAction = { onSeeDetails() } + } + } + } + } + + // fields are ordered, as they appear, top-to-bottom in the UI because that makes it easier to reason about each screen state + data class UiModel( + var showCloseIcon: Boolean = true, + var title: String = "", + var errorDescription: String = "", + var showProgress: Boolean = false, + var errorMessage: String = "", + var primaryButtonText: String = "See Details", + var primaryAction: () -> Unit = {}, + var showSecondaryButton: Boolean = false, + ) +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendFragment.kt new file mode 100644 index 0000000..5a92373 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendFragment.kt @@ -0,0 +1,373 @@ +package cash.z.ecc.android.ui.send + +import android.content.ClipboardManager +import android.content.Context +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.EditText +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.Group +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.core.widget.ImageViewCompat +import androidx.core.widget.doAfterTextChanged +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.viewModelScope +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentSendBinding +import cash.z.ecc.android.ext.* +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Tap.* +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.ext.collectWith +import cash.z.ecc.android.sdk.ext.onFirstWith +import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress +import cash.z.ecc.android.sdk.model.WalletBalance +import cash.z.ecc.android.sdk.type.AddressType +import cash.z.ecc.android.ui.base.BaseFragment +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +class SendFragment : + BaseFragment(), + ClipboardManager.OnPrimaryClipChangedListener { + override val screen = Report.Screen.SEND_ADDRESS + + private var maxZatoshi: Long? = null + private var availableZatoshi: Long? = null + + val sendViewModel: SendViewModel by activityViewModels() + + override fun inflate(inflater: LayoutInflater): FragmentSendBinding = + FragmentSendBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Apply View Model + applyViewModel(sendViewModel) + updateAddressUi(false) + + // Apply behaviors + + binding.buttonSend.setOnClickListener { + onSubmit().also { tapped(SEND_SUBMIT) } + } + + binding.checkIncludeAddress.setOnCheckedChangeListener { _, _ -> + onIncludeMemo(binding.checkIncludeAddress.isChecked) + } + + binding.inputZcashAddress.apply { + doAfterTextChanged { + val textStr = text.toString() + val trim = textStr.trim() + // bugfix: prevent cursor from moving while backspacing and deleting whitespace + if (text.toString() != trim) { + setText(trim) + setSelection(selectionEnd - (textStr.length - trim.length)) + } + onAddressChanged(trim) + } + } + + binding.backButtonHitArea.onClickNavUp { tapped(SEND_ADDRESS_BACK) } +// +// binding.clearMemo.setOnClickListener { +// onClearMemo().also { tapped(SEND_MEMO_CLEAR) } +// } + + binding.inputZcashMemo.doAfterTextChanged { + sendViewModel.memo = binding.inputZcashMemo.text?.toString() ?: "" + onMemoUpdated() + } + + binding.textLayoutAddress.setEndIconOnClickListener { + mainActivity?.maybeOpenScan().also { tapped(SEND_ADDRESS_SCAN) } + } + + // banners + + binding.backgroundClipboard.setOnClickListener { + onPaste().also { tapped(SEND_ADDRESS_PASTE) } + } + binding.containerClipboard.setOnClickListener { + onPaste().also { tapped(SEND_ADDRESS_PASTE) } + } + binding.backgroundLastUsed.setOnClickListener { + onReuse().also { tapped(SEND_ADDRESS_REUSE) } + } + binding.containerLastUsed.setOnClickListener { + onReuse().also { tapped(SEND_ADDRESS_REUSE) } + } + } + + private fun applyViewModel(model: SendViewModel) { + // apply amount + val roundedAmount = + WalletZecFormmatter.toZecStringFull(model.zatoshiAmount) + binding.textSendAmount.text = "\$$roundedAmount" + // apply address + binding.inputZcashAddress.setText(model.toAddress) + // apply memo + binding.inputZcashMemo.setText(model.memo) + binding.checkIncludeAddress.isChecked = model.includeFromAddress + onMemoUpdated() + } + + private fun onMemoUpdated() { + val totalLength = sendViewModel.createMemoToSend().length + binding.textLayoutMemo.helperText = + "$totalLength/${ZcashSdk.MAX_MEMO_SIZE} ${getString(R.string.send_memo_chars_abbreviation)}" + val color = + if (totalLength > ZcashSdk.MAX_MEMO_SIZE) R.color.zcashRed else R.color.text_light_dimmed + binding.textLayoutMemo.setHelperTextColor(ColorStateList.valueOf(color.toAppColor())) + } + + private fun onClearMemo() { + binding.inputZcashMemo.setText("") + } + + private fun onIncludeMemo(checked: Boolean) { + sendViewModel.afterInitFromAddress { + sendViewModel.includeFromAddress = checked + onMemoUpdated() + tapped(if (checked) SEND_MEMO_INCLUDE else SEND_MEMO_EXCLUDE) + } + } + + private fun onAddressChanged(address: String) { + lifecycleScope.launchWhenResumed { + val validation = sendViewModel.validateAddress(address) + binding.buttonSend.isActivated = !validation.isNotValid + var type = when (validation) { + is AddressType.Transparent -> R.string.send_validation_address_valid_taddr to R.color.zcashGreen + is AddressType.Shielded -> R.string.send_validation_address_valid_zaddr to R.color.zcashGreen + else -> R.string.send_validation_address_invalid to R.color.zcashRed + } + updateAddressUi(validation is AddressType.Transparent) + if (address == sendViewModel.synchronizer.getAddress() || address == sendViewModel.synchronizer.getTransparentAddress()) { + type = R.string.send_validation_address_self to R.color.zcashRed + } + binding.textLayoutAddress.helperText = getString(type.first) + binding.textLayoutAddress.setHelperTextColor(ColorStateList.valueOf(type.second.toAppColor())) + + // if we have the clipboard address but we're changing it, then clear the selection + if (binding.imageClipboardAddressSelected.isVisible) { + loadAddressFromClipboard().let { clipboardAddress -> + if (address != clipboardAddress) { + updateClipboardBanner(clipboardAddress, false) + } + } + } + // if we have the last used address but we're changing it, then clear the selection + if (binding.imageLastUsedAddressSelected.isVisible) { + loadLastUsedAddress().let { lastAddress -> + if (address != lastAddress) { + updateLastUsedBanner(lastAddress, false) + } + } + } + } + } + + /** + * To hide input Memo and reply-to option for T type address and show a info message about memo option availability */ + private fun updateAddressUi(isMemoHidden: Boolean) { + if (isMemoHidden) { + binding.textLayoutMemo.gone() + binding.checkIncludeAddress.gone() + binding.textNoZAddress.visible() + } else { + binding.textLayoutMemo.visible() + binding.checkIncludeAddress.visible() + binding.textNoZAddress.gone() + } + } + + private fun onSubmit(unused: EditText? = null) { + sendViewModel.toAddress = binding.inputZcashAddress.text.toString() + sendViewModel.validate(requireContext(), availableZatoshi, maxZatoshi) + .onFirstWith(resumedScope) { errorMessage -> + if (errorMessage == null) { + val symbol = getString(R.string.symbol) + mainActivity?.authenticate( + "${getString(R.string.send_confirmation_prompt)}\n${ + WalletZecFormmatter.toZecStringFull( + sendViewModel.zatoshiAmount + ) + } $symbol ${getString(R.string.send_final_to)}\n${sendViewModel.toAddress.toAbbreviatedAddress()}" + ) { +// sendViewModel.funnel(Send.AddressPageComplete) + mainActivity?.safeNavigate(R.id.action_nav_send_to_nav_send_final) + } + } else { + resumedScope.launch { + binding.textAddressError.text = errorMessage + delay(2500L) + binding.textAddressError.text = "" + } + } + } + } + + private fun onMax() { + if (maxZatoshi != null) { +// binding.inputZcashAmount.apply { +// setText(WalletZecFormmatter.toZecStringFull(maxZatoshi)) +// postDelayed({ +// requestFocus() +// setSelection(text?.length ?: 0) +// }, 10L) +// } + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + mainActivity?.clipboard?.addPrimaryClipChangedListener(this) + } + + override fun onDetach() { + super.onDetach() + mainActivity?.clipboard?.removePrimaryClipChangedListener(this) + } + + override fun onResume() { + super.onResume() + onPrimaryClipChanged() + sendViewModel.synchronizer.saplingBalances.filterNotNull().collectWith(resumedScope) { + onBalanceUpdated(it) + } + binding.inputZcashAddress.text.toString().let { + if (!it.isNullOrEmpty()) onAddressChanged(it) + } + } + + private fun onBalanceUpdated(balance: WalletBalance) { +// binding.textLayoutAmount.helperText = +// "You have ${WalletZecFormmatter.toZecStringFull(balance.availableZatoshi.coerceAtLeast(0L))} available" + maxZatoshi = (balance.available - ZcashSdk.MINERS_FEE).value + availableZatoshi = balance.available.value + } + + override fun onPrimaryClipChanged() { + resumedScope.launch { + updateClipboardBanner(loadAddressFromClipboard()) + updateLastUsedBanner(loadLastUsedAddress()) + } + } + + private fun updateClipboardBanner(address: String?, selected: Boolean = false) { + binding.apply { + updateAddressBanner( + groupClipboard, + clipboardAddress, + imageClipboardAddressSelected, + imageShield, + clipboardAddressLabel, + selected, + address + ) + } + } + + private suspend fun updateLastUsedBanner( + address: String? = null, + selected: Boolean = false + ) { + val isBoth = address == loadAddressFromClipboard() + binding.apply { + updateAddressBanner( + groupLastUsed, + lastUsedAddress, + imageLastUsedAddressSelected, + imageLastUsedShield, + lastUsedAddressLabel, + selected, + address.takeUnless { isBoth } + ) + } + binding.dividerClipboard.setText(if (isBoth) R.string.send_history_last_and_clipboard else R.string.send_history_clipboard) + } + + private fun updateAddressBanner( + group: Group, + addressTextView: TextView, + checkIcon: ImageView, + shieldIcon: ImageView, + addressLabel: TextView, + selected: Boolean = false, + address: String? = null + ) { + resumedScope.launch { + if (address == null) { + group.gone() + } else { + val userShieldedAddr = sendViewModel.synchronizer.getAddress() + val userTransparentAddr = sendViewModel.synchronizer.getTransparentAddress() + group.visible() + addressTextView.text = address.toAbbreviatedAddress(16, 16) + checkIcon.goneIf(!selected) + ImageViewCompat.setImageTintList( + shieldIcon, + ColorStateList.valueOf(if (selected) R.color.colorPrimary.toAppColor() else R.color.zcashWhite_12.toAppColor()) + ) + addressLabel.setText(if (address == userShieldedAddr) R.string.send_banner_address_user else R.string.send_banner_address_unknown) + if (address == userTransparentAddr) addressLabel.setText("Your Auto-Shielding Address") + addressLabel.setTextColor(if (selected) R.color.colorPrimary.toAppColor() else R.color.text_light.toAppColor()) + addressTextView.setTextColor(if (selected) R.color.text_light.toAppColor() else R.color.text_light_dimmed.toAppColor()) + } + } + } + + private fun onPaste() { + mainActivity?.clipboard?.let { clipboard -> + if (clipboard.hasPrimaryClip()) { + val address = clipboard.text().toString() + val applyValue = binding.imageClipboardAddressSelected.isGone + updateClipboardBanner(address, applyValue) + binding.inputZcashAddress.setText(address.takeUnless { !applyValue }) + } + } + } + + private fun onReuse() { + sendViewModel.viewModelScope.launch { + val address = loadLastUsedAddress() + val applyValue = binding.imageLastUsedAddressSelected.isGone + updateLastUsedBanner(address, applyValue) + binding.inputZcashAddress.setText(address.takeUnless { !applyValue }) + } + } + + private suspend fun loadAddressFromClipboard(): String? { + mainActivity?.clipboard?.apply { + if (hasPrimaryClip()) { + text().toString().let { text -> + if (sendViewModel.isValidAddress(text)) return@loadAddressFromClipboard text + } + } + } + return null + } + + private var lastUsedAddress: String? = null + private suspend fun loadLastUsedAddress(): String? { + if (lastUsedAddress == null) { + lastUsedAddress = sendViewModel.synchronizer.sentTransactions.first() + .firstOrNull { !it.toAddress.isNullOrEmpty() }?.toAddress + updateLastUsedBanner(lastUsedAddress, binding.imageLastUsedAddressSelected.isVisible) + } + return lastUsedAddress + } + + private fun ClipboardManager.text(): CharSequence = + primaryClip!!.getItemAt(0).coerceToText(mainActivity) +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendMemoFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendMemoFragment.kt new file mode 100644 index 0000000..b6269ab --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendMemoFragment.kt @@ -0,0 +1,121 @@ +package cash.z.ecc.android.ui.send + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import androidx.core.widget.doAfterTextChanged +import androidx.fragment.app.activityViewModels +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentSendMemoBinding +import cash.z.ecc.android.ext.gone +import cash.z.ecc.android.ext.goneIf +import cash.z.ecc.android.ext.onEditorActionDone +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Funnel.Send +import cash.z.ecc.android.feedback.Report.Tap.* +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX_STANDARD + +class SendMemoFragment : BaseFragment() { + override val screen = Report.Screen.SEND_MEMO + + val sendViewModel: SendViewModel by activityViewModels() + + override fun inflate(inflater: LayoutInflater): FragmentSendMemoBinding = + FragmentSendMemoBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.buttonNext.setOnClickListener { + onTopButton().also { tapped(SEND_MEMO_NEXT) } + } + binding.buttonSkip.setOnClickListener { + onBottomButton().also { tapped(SEND_MEMO_SKIP) } + } + binding.clearMemo.setOnClickListener { + onClearMemo().also { tapped(SEND_MEMO_CLEAR) } + } + +// R.id.action_nav_send_memo_to_nav_send_address.let { +// binding.backButtonHitArea.onClickNavTo(it) { tapped(SEND_MEMO_BACK) } +// onBackPressNavTo(it) { tapped(SEND_MEMO_BACK) } +// } + + binding.checkIncludeAddress.setOnCheckedChangeListener { _, _ -> + onIncludeMemo(binding.checkIncludeAddress.isChecked) + } + + binding.inputMemo.let { memo -> + memo.onEditorActionDone { + onTopButton().also { tapped(SEND_MEMO_NEXT) } + } + memo.doAfterTextChanged { + binding.clearMemo.goneIf(memo.text.isEmpty()) + } + } + + sendViewModel.afterInitFromAddress { + binding.textIncludedAddress.text = + "$INCLUDE_MEMO_PREFIX_STANDARD ${sendViewModel.fromAddress}" + } + + binding.textIncludedAddress.gone() + + applyModel() + } + + private fun onClearMemo() { + binding.inputMemo.setText("") + } + + private fun applyModel() { + sendViewModel.isShielded.let { isShielded -> + binding.groupShielded.goneIf(!isShielded) + binding.groupTransparent.goneIf(isShielded) + binding.textIncludedAddress.goneIf(!sendViewModel.includeFromAddress) + if (isShielded) { + binding.inputMemo.setText(sendViewModel.memo) + binding.checkIncludeAddress.isChecked = sendViewModel.includeFromAddress + binding.buttonNext.text = "ADD MEMO" + binding.buttonSkip.text = "OMIT MEMO" + } else { + binding.buttonNext.text = "GO BACK" + binding.buttonSkip.text = "PROCEED" + } + } + } + + private fun onIncludeMemo(checked: Boolean) { + + binding.textIncludedAddress.goneIf(!checked) + sendViewModel.includeFromAddress = checked + binding.textInfoShielded.text = if (checked) { + tapped(SEND_MEMO_INCLUDE) + getString(R.string.send_memo_included_message) + } else { + tapped(SEND_MEMO_EXCLUDE) + getString(R.string.send_memo_excluded_message) + } + } + + private fun onTopButton() { + if (sendViewModel.isShielded) { + sendViewModel.memo = binding.inputMemo.text.toString() + onNext() + } else { +// mainActivity?.safeNavigate(R.id.action_nav_send_memo_to_nav_send_address) + } + } + + private fun onBottomButton() { + binding.inputMemo.setText("") + sendViewModel.memo = "" + sendViewModel.includeFromAddress = false + onNext() + } + + private fun onNext() { + sendViewModel.funnel(Send.MemoPageComplete) +// mainActivity?.safeNavigate(R.id.action_nav_send_memo_to_send_confirm) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt new file mode 100644 index 0000000..34e6fe0 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt @@ -0,0 +1,274 @@ +package cash.z.ecc.android.ui.send + +import android.content.Context +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.Const +import cash.z.ecc.android.ext.WalletZecFormmatter +import cash.z.ecc.android.feedback.Feedback +import cash.z.ecc.android.feedback.Feedback.Keyed +import cash.z.ecc.android.feedback.Feedback.TimeMetric +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Funnel.Send +import cash.z.ecc.android.feedback.Report.Funnel.Send.SendSelected +import cash.z.ecc.android.feedback.Report.Funnel.Send.SpendingKeyFound +import cash.z.ecc.android.feedback.Report.Issue +import cash.z.ecc.android.feedback.Report.MetricType +import cash.z.ecc.android.feedback.Report.MetricType.* +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.sdk.db.entity.* +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.model.Zatoshi +import cash.z.ecc.android.sdk.tool.DerivationTool +import cash.z.ecc.android.sdk.type.AddressType +import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX_STANDARD +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext + +class SendViewModel : ViewModel() { + + // note used in testing + val metrics = mutableMapOf() + + private val lockBox: LockBox = DependenciesHolder.lockBox + + val synchronizer: Synchronizer = DependenciesHolder.synchronizer + + private val feedback: Feedback = DependenciesHolder.feedback + + var fromAddress: String = "" + var toAddress: String = "" + var memo: String = "" + var zatoshiAmount: Zatoshi? = null + var includeFromAddress: Boolean = false + set(value) { + require(!value || (value && !fromAddress.isNullOrEmpty())) { + "Error: fromAddress was empty while attempting to include it in the memo. Verify" + + " that initFromAddress() has previously been called on this viewmodel." + } + field = value + } + val isShielded get() = toAddress.startsWith("z") + + fun send(): Flow { + funnel(SendSelected) + val memoToSend = createMemoToSend() + val keys = runBlocking { + DerivationTool.deriveSpendingKeys( + lockBox.getBytes(Const.Backup.SEED)!!, + synchronizer.network + ) + } + funnel(SpendingKeyFound) + reportUserInputIssues(memoToSend) + return synchronizer.sendToAddress( + keys[0], + zatoshiAmount!!, + toAddress, + memoToSend.chunked(ZcashSdk.MAX_MEMO_SIZE).firstOrNull() ?: "" + ).onEach { + twig("Received pending txUpdate: ${it?.toString()}") + updateMetrics(it) + reportFailures(it) + } + } + + fun cancel(pendingId: Long) { + viewModelScope.launch { + synchronizer.cancelSpend(pendingId) + } + } + + fun createMemoToSend() = + if (includeFromAddress) "$memo\n$INCLUDE_MEMO_PREFIX_STANDARD\n$fromAddress" else memo + + suspend fun validateAddress(address: String): AddressType = + synchronizer.validateAddress(address) + + suspend fun isValidAddress(address: String): Boolean = when (validateAddress(address)) { + is AddressType.Shielded, is AddressType.Transparent -> true + else -> false + } + + fun validate(context: Context, availableZatoshi: Long?, maxZatoshi: Long?) = flow { + + when { + synchronizer.validateAddress(toAddress).isNotValid -> { + emit(context.getString(R.string.send_validation_error_address_invalid)) + } + zatoshiAmount?.let { it.value < 1L } ?: false -> { + emit(context.getString(R.string.send_validation_error_amount_minimum)) + } + availableZatoshi == null -> { + emit(context.getString(R.string.send_validation_error_unknown_funds)) + } + availableZatoshi == 0L -> { + emit(context.getString(R.string.send_validation_error_no_available_funds)) + } + availableZatoshi > 0 && availableZatoshi.let { it < ZcashSdk.MINERS_FEE.value } ?: false -> { + emit(context.getString(R.string.send_validation_error_dust)) + } + maxZatoshi != null && zatoshiAmount?.let { it.value > maxZatoshi } ?: false -> { + emit( + context.getString( + R.string.send_validation_error_too_much, + WalletZecFormmatter.toZecStringFull(Zatoshi((maxZatoshi))), + ZcashWalletApp.instance.getString(R.string.symbol) + ) + ) + } + createMemoToSend().length > ZcashSdk.MAX_MEMO_SIZE -> { + emit( + context.getString( + R.string.send_validation_error_memo_length, + ZcashSdk.MAX_MEMO_SIZE + ) + ) + } + else -> emit(null) + } + } + + fun afterInitFromAddress(block: () -> Unit) { + viewModelScope.launch { + fromAddress = synchronizer.getAddress() + block() + } + } + + fun reset() { + fromAddress = "" + toAddress = "" + memo = "" + zatoshiAmount = null + includeFromAddress = false + } + + // + // Analytics + // + + private fun reportFailures(tx: PendingTransaction?) { + if (tx == null) { + // put a stack trace in the logs + twig(IllegalArgumentException("Warning: Could not report failures because tx was null")) + return + } + when { + tx.isCancelled() -> funnel(Send.Cancelled) + tx.isFailedEncoding() -> { + // report that the funnel leaked and also capture a non-fatal app error + funnel(Send.ErrorEncoding(tx.errorCode, tx.errorMessage)) + feedback.report(Report.Error.NonFatal.TxEncodeError(tx.errorCode, tx.errorMessage)) + } + tx.isFailedSubmit() -> { + // report that the funnel leaked and also capture a non-fatal app error + funnel(Send.ErrorSubmitting(tx.errorCode, tx.errorMessage)) + feedback.report(Report.Error.NonFatal.TxSubmitError(tx.errorCode, tx.errorMessage)) + } + } + } + + private fun reportUserInputIssues(memoToSend: String) { + if (toAddress == fromAddress) feedback.report(Issue.SelfSend) + when { + (zatoshiAmount?.value + ?: 0L) < ZcashSdk.MINERS_FEE.value -> feedback.report(Issue.TinyAmount) + (zatoshiAmount?.value ?: 0L) < 100L -> feedback.report(Issue.MicroAmount) + (zatoshiAmount ?: 0L) == 1L -> feedback.report(Issue.MinimumAmount) + } + memoToSend.length.also { + when { + it > ZcashSdk.MAX_MEMO_SIZE -> feedback.report(Issue.TruncatedMemo(it)) + it > (ZcashSdk.MAX_MEMO_SIZE * 0.96) -> feedback.report(Issue.LargeMemo(it)) + } + } + } + + fun updateMetrics(tx: PendingTransaction?) { + if (tx == null) { + // put a stack trace in the logs + twig(IllegalArgumentException("Warning: Could not update metrics because tx was null")) + return + } + try { + when { + tx.isMined() -> TRANSACTION_SUBMITTED to TRANSACTION_MINED by tx.id + tx.isSubmitSuccess() -> TRANSACTION_CREATED to TRANSACTION_SUBMITTED by tx.id + tx.isCreated() -> TRANSACTION_INITIALIZED to TRANSACTION_CREATED by tx.id + tx.isCreating() -> +TRANSACTION_INITIALIZED by tx.id + else -> null + }?.let { metricId -> + report(metricId) + } + } catch (t: Throwable) { + feedback.report(t) + } + } + + fun report(metricId: String?) { + metrics[metricId]?.let { metric -> + metric.takeUnless { (it.elapsedTime ?: 0) <= 0L }?.let { + viewModelScope.launch { + withContext(IO) { + feedback.report(metric) + + // does this metric complete another metric? + metricId!!.toRelatedMetricId().let { relatedId -> + metrics[relatedId]?.let { relatedMetric -> + // then remove the related metric, itself. And the relation. + metrics.remove(relatedMetric.toMetricIdFor(metricId!!.toTxId())) + metrics.remove(relatedId) + } + } + + // remove all top-level metrics + if (metric.key == Report.MetricType.TRANSACTION_MINED.key) metrics.remove( + metricId + ) + } + } + } + } + } + + fun funnel(step: Send?) { + step ?: return + feedback.report(step) + } + + private operator fun MetricType.unaryPlus(): TimeMetric = + TimeMetric(key, description).markTime() + + private infix fun TimeMetric.by(txId: Long) = + this.toMetricIdFor(txId).also { metrics[it] = this } + + private infix fun Pair.by(txId: Long): String? { + val startMetric = first.toMetricIdFor(txId).let { metricId -> + metrics[metricId].also { if (it == null) println("Warning no start metric for id: $metricId") } + } + return startMetric?.endTime?.let { startMetricEndTime -> + TimeMetric(second.key, second.description, mutableListOf(startMetricEndTime)) + .markTime().let { endMetric -> + endMetric.toMetricIdFor(txId).also { metricId -> + metrics[metricId] = endMetric + metrics[metricId.toRelatedMetricId()] = startMetric + } + } + } + } + + private fun Keyed.toMetricIdFor(id: Long): String = "$id.$key" + private fun String.toRelatedMetricId(): String = "$this.related" + private fun String.toTxId(): Long = split('.').first().toLong() +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/settings/SettingsFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/settings/SettingsFragment.kt new file mode 100644 index 0000000..1a7e77c --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/settings/SettingsFragment.kt @@ -0,0 +1,151 @@ +package cash.z.ecc.android.ui.settings + +import android.content.res.ColorStateList +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.Toast +import androidx.core.widget.doAfterTextChanged +import androidx.fragment.app.viewModels +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.databinding.FragmentSettingsBinding +import cash.z.ecc.android.ext.* +import cash.z.ecc.android.sdk.exception.LightWalletException +import cash.z.ecc.android.sdk.ext.collectWith +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.launch + +class SettingsFragment : BaseFragment() { + + private val viewModel: SettingsViewModel by viewModels() + + override fun inflate(inflater: LayoutInflater): FragmentSettingsBinding = + FragmentSettingsBinding.inflate(inflater) + + // + // Lifecycle + // + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mainActivity?.preventBackPress(this) + viewModel.init() + binding.apply { + groupLoading.gone() + hitAreaExit.onClickNavBack() + buttonReset.setOnClickListener(::onResetClicked) + buttonUpdate.setOnClickListener(::onUpdateClicked) + buttonUpdate.isActivated = true + buttonReset.isActivated = true + inputHost.doAfterTextChanged { + viewModel.pendingHost = it.toString() + } + inputPort.doAfterTextChanged { + viewModel.pendingPortText = it.toString() + } + } + } + + override fun onResume() { + super.onResume() + viewModel.uiModels.collectWith(resumedScope, ::onUiModelUpdated) + } + + // + // Event handlers + // + + private fun onResetClicked(unused: View?) { + mainActivity?.hideKeyboard() + context?.showUpdateServerDialog(R.string.settings_buttons_restore) { + resumedScope.launch { + binding.groupLoading.visible() + binding.loadingView.requestFocus() + viewModel.resetServer() + } + } + } + + private fun onUpdateClicked(unused: View?) { + mainActivity?.hideKeyboard() + context?.showUpdateServerDialog { + resumedScope.launch { + binding.groupLoading.visible() + binding.loadingView.requestFocus() + viewModel.submit() + } + } + } + + private fun onUiModelUpdated(uiModel: SettingsViewModel.UiModel) { + twig("onUiModelUpdated:::::$uiModel") + binding.apply { + if (handleCompletion(uiModel)) return@onUiModelUpdated + + // avoid moving the cursor on instances where the change originated from the UI + if (inputHost.text.toString() != uiModel.host) inputHost.setText(uiModel.host) + if (inputPort.text.toString() != uiModel.portText) inputPort.setText(uiModel.portText) + + buttonReset.isEnabled = uiModel.submitEnabled + buttonUpdate.isEnabled = uiModel.submitEnabled && !uiModel.hasError + + uiModel.hostErrorMessage.let { it -> + textInputLayoutHost.helperText = it + ?: R.string.settings_host_helper_text.toAppString() + textInputLayoutHost.setHelperTextColor(it.toHelperTextColor()) + } + uiModel.portErrorMessage.let { it -> + textInputLayoutPort.helperText = it + ?: R.string.settings_port_helper_text.toAppString() + textInputLayoutPort.setHelperTextColor(it.toHelperTextColor()) + } + } + } + + /** + * Handle the exit conditions and return true if we're done here. + */ + private fun handleCompletion(uiModel: SettingsViewModel.UiModel): Boolean { + return if (uiModel.changeError != null) { + binding.groupLoading.gone() + onCriticalError(uiModel.changeError) + true + } else { + if (uiModel.complete) { + binding.groupLoading.gone() + mainActivity?.safeNavigate(R.id.nav_home) + Toast.makeText(ZcashWalletApp.instance, getString(R.string.settings_toast_change_server_success), Toast.LENGTH_SHORT).show() + true + } + false + } + } + + private fun onCriticalError(error: Throwable) { + val details = if (error is LightWalletException.ChangeServerException.StatusException) { + error.status.description + } else { + error.javaClass.simpleName + } + val message = "An error occured while changing servers. Please verify the info" + + " and try again.\n\nError: $details" + twig(message) + Toast.makeText(ZcashWalletApp.instance, getString(R.string.settings_toast_change_server_failure), Toast.LENGTH_SHORT).show() + context?.showUpdateServerCriticalError(message) + } + + // + // Utilities + // + + private fun String?.toHelperTextColor(): ColorStateList { + val color = if (this == null) { + R.color.text_light_dimmed + } else { + R.color.zcashRed + } + return ColorStateList.valueOf(color.toAppColor()) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/settings/SettingsViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/settings/SettingsViewModel.kt new file mode 100644 index 0000000..5a2d6d2 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/settings/SettingsViewModel.kt @@ -0,0 +1,88 @@ +package cash.z.ecc.android.ui.settings + +import androidx.lifecycle.ViewModel +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.Const +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.Synchronizer +import cash.z.ecc.android.util.twig +import kotlinx.coroutines.flow.MutableStateFlow +import kotlin.properties.Delegates.observable +import kotlin.reflect.KProperty + +class SettingsViewModel : ViewModel() { + + private val synchronizer: Synchronizer = DependenciesHolder.synchronizer + + private val prefs: LockBox = DependenciesHolder.prefs + + lateinit var uiModels: MutableStateFlow + + private lateinit var initialServer: UiModel + + var pendingHost by observable("", ::onUpdateModel) + var pendingPortText by observable("", ::onUpdateModel) + + private fun getHost(): String { + return prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST + } + + private fun getPort(): Int { + return prefs[Const.Pref.SERVER_PORT] ?: Const.Default.Server.PORT + } + + fun init() { + initialServer = UiModel(getHost(), getPort().toString()) + uiModels = MutableStateFlow(initialServer) + } + + suspend fun resetServer() { + UiModel( + Const.Default.Server.HOST, + Const.Default.Server.PORT.toString() + ).let { default -> + uiModels.value = default + submit() + } + } + + suspend fun submit() { + // Note: this only takes effect after the app is relaunched + val host = uiModels.value.host + val port = uiModels.value.portInt + prefs[Const.Pref.SERVER_HOST] = host + prefs[Const.Pref.SERVER_PORT] = port + uiModels.value = uiModels.value.copy(changeError = null, complete = true) + } + + private fun onUpdateModel(kProperty: KProperty<*>, old: String, new: String) { + val pendingPort = pendingPortText.toIntOrNull() ?: -1 + uiModels.value = UiModel( + pendingHost, + pendingPortText, + pendingHost != initialServer.host || pendingPortText != initialServer.portText, + if (!pendingHost.isValidHost()) "Please enter a valid host name or IP" else null, + if (pendingPort >= 65535) "Please enter a valid port number below 65535" else null + ).also { + twig("updated model with $it") + } + } + + data class UiModel( + val host: String = "", + val portText: String = "", + val submitEnabled: Boolean = false, + val hostErrorMessage: String? = null, + val portErrorMessage: String? = null, + val changeError: Throwable? = null, + val complete: Boolean = false + ) { + val portInt get() = portText.toIntOrNull() ?: -1 + val hasError get() = hostErrorMessage != null || portErrorMessage != null + } + + // we can beef this up later if we want to but this is enough for now + private fun String.isValidHost(): Boolean { + return !contains("://") + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt new file mode 100644 index 0000000..89eee80 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt @@ -0,0 +1,158 @@ +package cash.z.ecc.android.ui.setup + +import android.content.Context +import android.os.Bundle +import android.text.SpannableString +import android.text.Spanned +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import android.widget.Toast +import androidx.activity.addCallback +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.databinding.FragmentBackupBinding +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.Const +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED +import cash.z.ecc.android.feedback.Report.Tap.BACKUP_DONE +import cash.z.ecc.android.feedback.Report.Tap.BACKUP_VERIFY +import cash.z.ecc.android.feedback.measure +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP +import cash.z.ecc.android.ui.util.AddressPartNumberSpan +import cash.z.ecc.android.util.twig +import cash.z.ecc.kotlin.mnemonic.Mnemonics +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class BackupFragment : BaseFragment() { + override val screen = Report.Screen.BACKUP + + private val walletSetup: WalletSetupViewModel by activityViewModels() + + private var hasBackUp: Boolean = true // TODO: implement backup and then check for it here-ish + + override fun inflate(inflater: LayoutInflater): FragmentBackupBinding = + FragmentBackupBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + with(binding) { + applySpan( + textAddressPart1, textAddressPart2, textAddressPart3, + textAddressPart4, textAddressPart5, textAddressPart6, + textAddressPart7, textAddressPart8, textAddressPart9, + textAddressPart10, textAddressPart11, textAddressPart12, + textAddressPart13, textAddressPart14, textAddressPart15, + textAddressPart16, textAddressPart17, textAddressPart18, + textAddressPart19, textAddressPart20, textAddressPart21, + textAddressPart22, textAddressPart23, textAddressPart24 + ) + } + binding.buttonPositive.setOnClickListener { + onEnterWallet().also { if (hasBackUp) tapped(BACKUP_DONE) else tapped(BACKUP_VERIFY) } + } + if (hasBackUp) { + binding.buttonPositive.text = getString(R.string.backup_button_done) + } + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + mainActivity?.onBackPressedDispatcher?.addCallback(this) { + onEnterWallet(false) + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + walletSetup.checkSeed().onEach { + hasBackUp = when (it) { + SEED_WITH_BACKUP -> true + else -> false + } + }.launchIn(lifecycleScope) + } + + override fun onResume() { + super.onResume() + resumedScope.launch { + binding.textBirtdate.text = + getString(R.string.backup_format_birthday_height, calculateBirthday().value) + } + } + + // TODO: move this into the SDK + private suspend fun calculateBirthday(): BlockHeight { + var storedBirthday: BlockHeight? = null + var oldestTransactionHeight: BlockHeight? = null + var activationHeight: BlockHeight? = null + try { + activationHeight = DependenciesHolder.synchronizer.network.saplingActivationHeight + storedBirthday = walletSetup.loadBirthdayHeight() + oldestTransactionHeight = DependenciesHolder.synchronizer.receivedTransactions.first() + ?.last()?.minedHeight?.let { + BlockHeight.new(ZcashWalletApp.instance.defaultNetwork, it) + } + // to be safe adjust for reorgs (and generally a little cushion is good for privacy) + // so we round down to the nearest 100 and then subtract 100 to ensure that the result is always at least 100 blocks away + oldestTransactionHeight = ZcashSdk.MAX_REORG_SIZE.let { boundary -> + oldestTransactionHeight?.let { it.value - it.value.rem(boundary) - boundary } + ?.let { BlockHeight.new(ZcashWalletApp.instance.defaultNetwork, it) } + } + } catch (t: Throwable) { + twig("failed to calculate birthday due to: $t") + } + return listOfNotNull( + storedBirthday, + oldestTransactionHeight, + activationHeight + ).maxBy { it.value } + } + + private fun onEnterWallet(showMessage: Boolean = !this.hasBackUp) { + if (showMessage) { + Toast.makeText( + activity, + R.string.backup_verification_not_implemented, + Toast.LENGTH_LONG + ).show() + } + mainActivity?.navController?.popBackStack() + } + + private fun applySpan(vararg textViews: TextView) = lifecycleScope.launch { + val words = loadSeedWords() + val thinSpace = "\u2005" // 0.25 em space + textViews.forEachIndexed { index, textView -> + val numLength = "$index".length + val word = words[index] + // TODO: work with a charsequence here, rather than constructing a String + textView.text = SpannableString("${index + 1}$thinSpace${String(word)}").apply { + setSpan(AddressPartNumberSpan(), 0, 1 + numLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + } + + private suspend fun loadSeedWords(): List = withContext(Dispatchers.IO) { + mainActivity!!.feedback.measure(SEED_PHRASE_LOADED) { + val lockBox = LockBox(ZcashWalletApp.instance) + val mnemonics = Mnemonics() + val seedPhrase = lockBox.getCharsUtf8(Const.Backup.SEED_PHRASE) + ?: throw RuntimeException("Seed Phrase expected but not found in storage!!") + val result = mnemonics.toWordList(seedPhrase) + result + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt new file mode 100644 index 0000000..ea61697 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt @@ -0,0 +1,216 @@ +package cash.z.ecc.android.ui.setup + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.Toast +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.databinding.FragmentLandingBinding +import cash.z.ecc.android.ext.locale +import cash.z.ecc.android.ext.showSharedLibraryCriticalError +import cash.z.ecc.android.ext.toAppString +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Funnel.Restore +import cash.z.ecc.android.feedback.Report.Tap.* +import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.sdk.model.ZcashNetwork +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITHOUT_BACKUP +import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP +import cash.z.ecc.android.util.twig +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +class LandingFragment : BaseFragment() { + override val screen = Report.Screen.LANDING + + private val walletSetup: WalletSetupViewModel by activityViewModels() + + private var skipCount: Int = 0 + + override fun inflate(inflater: LayoutInflater): FragmentLandingBinding = + FragmentLandingBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.buttonPositive.setOnClickListener { + when (binding.buttonPositive.text.toString().toLowerCase(locale())) { + R.string.landing_button_primary.toAppString(true) -> onNewWallet().also { + tapped( + LANDING_NEW + ) + } + R.string.landing_button_primary_create_success.toAppString(true) -> onBackupWallet().also { + tapped( + LANDING_BACKUP + ) + } + } + } + binding.buttonNegative.setOnLongClickListener { + tapped(DEVELOPER_WALLET_PROMPT) + if (binding.buttonNegative.text.toString().toLowerCase(locale()) == "restore") { + MaterialAlertDialogBuilder(requireContext()) + .setMessage("Would you like to import the dev wallet?\n\nIf so, please only send 1000 zatoshis at a time and return some later so that the account remains funded.") + .setTitle("Import Dev Wallet?") + .setCancelable(true) + .setPositiveButton("Import") { dialog, _ -> + tapped(DEVELOPER_WALLET_IMPORT) + dialog.dismiss() + onUseDevWallet() + } + .setNegativeButton("Cancel") { dialog, _ -> + tapped(DEVELOPER_WALLET_CANCEL) + dialog.dismiss() + } + .show() + true + } else { + false + } + } + binding.buttonNegative.setOnClickListener { + when (binding.buttonNegative.text.toString().toLowerCase(locale())) { + R.string.landing_button_secondary.toAppString(true) -> onRestoreWallet().also { + mainActivity?.reportFunnel(Restore.Initiated) + tapped(LANDING_RESTORE) + } + else -> onSkip(++skipCount) + } + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + + walletSetup.checkSeed().onEach { + when (it) { + SEED_WITHOUT_BACKUP, SEED_WITH_BACKUP -> { + mainActivity?.safeNavigate(R.id.nav_backup) + } + else -> {} + } + }.launchIn(lifecycleScope) + } + + override fun onResume() { + super.onResume() + view?.postDelayed( + { + mainActivity?.hideKeyboard() + }, + 25L + ) + } + + private fun onSkip(count: Int) { + when (count) { + 1 -> { + tapped(LANDING_BACKUP_SKIPPED_1) + binding.textMessage.setText(R.string.landing_backup_skipped_message_1) + binding.buttonNegative.setText(R.string.landing_button_backup_skipped_1) + } + 2 -> { + tapped(LANDING_BACKUP_SKIPPED_2) + binding.textMessage.setText(R.string.landing_backup_skipped_message_2) + binding.buttonNegative.setText(R.string.landing_button_backup_skipped_2) + } + else -> { + tapped(LANDING_BACKUP_SKIPPED_3) + onEnterWallet() + } + } + } + + private fun onRestoreWallet() { + mainActivity?.safeNavigate(R.id.action_nav_landing_to_nav_restore) + } + + // AKA import wallet + private fun onUseDevWallet() { + val seedPhrase: String + val birthday: BlockHeight + + // new testnet dev wallet + when (ZcashWalletApp.instance.defaultNetwork) { + ZcashNetwork.Mainnet -> { + seedPhrase = + "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" + birthday = BlockHeight.new(ZcashNetwork.Mainnet, 991645) // 663174 + } + ZcashNetwork.Testnet -> { + seedPhrase = + "quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten" + birthday = BlockHeight.new(ZcashNetwork.Testnet, 1330190) + } + else -> throw RuntimeException("No developer wallet exists for network ${ZcashWalletApp.instance.defaultNetwork}") + } + + mainActivity?.apply { + lifecycleScope.launch { + try { + walletSetup.importWallet(seedPhrase, birthday) + mainActivity?.startSync() + binding.buttonPositive.isEnabled = true + binding.textMessage.setText(R.string.landing_import_success_message) + binding.buttonNegative.setText(R.string.landing_button_secondary_import_success) + binding.buttonPositive.setText(R.string.landing_import_success_primary_button) + playSound("sound_receive_small.mp3") + vibrateSuccess() + } catch (e: UnsatisfiedLinkError) { + mainActivity?.showSharedLibraryCriticalError(e) + } + } + } + } + + private fun onNewWallet() { + lifecycleScope.launch { + binding.buttonPositive.setText(R.string.landing_button_progress_create) + binding.buttonPositive.isEnabled = false + + try { + walletSetup.newWallet() + mainActivity?.startSync() + + binding.buttonPositive.isEnabled = true + binding.textMessage.setText(R.string.landing_create_success_message) + binding.buttonNegative.setText(R.string.landing_button_secondary_create_success) + binding.buttonPositive.setText(R.string.landing_button_primary_create_success) + mainActivity?.playSound("sound_receive_small.mp3") + mainActivity?.vibrateSuccess() + } catch (e: UnsatisfiedLinkError) { + // For developer sanity: + // show a nice dialog, rather than a toast, when the rust didn't get compile + // which can happen often when working from a local SDK build + mainActivity?.showSharedLibraryCriticalError(e) + } catch (t: Throwable) { + twig("Failed to create wallet due to: $t") + mainActivity?.feedback?.report(t) + binding.buttonPositive.isEnabled = true + binding.buttonPositive.setText(R.string.landing_button_primary) + Toast.makeText( + context, + "Failed to create wallet. See logs for details. Try restarting the app.\n\nMessage: \n${t.message}", + Toast.LENGTH_LONG + ).show() + } + } + } + + private fun onBackupWallet() { + skipCount = 0 + mainActivity?.safeNavigate(R.id.action_nav_landing_to_nav_backup) + } + + private fun onEnterWallet() { + skipCount = 0 + mainActivity?.navController?.popBackStack() + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/RestoreFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/RestoreFragment.kt new file mode 100644 index 0000000..a718cd4 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/RestoreFragment.kt @@ -0,0 +1,246 @@ +package cash.z.ecc.android.ui.setup + +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.os.SystemClock +import android.text.InputType +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionEvent.ACTION_UP +import android.view.View +import android.widget.TextView +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.databinding.FragmentRestoreBinding +import cash.z.ecc.android.ext.goneIf +import cash.z.ecc.android.ext.showConfirmation +import cash.z.ecc.android.ext.showInvalidSeedPhraseError +import cash.z.ecc.android.ext.showSharedLibraryCriticalError +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.feedback.Report.Funnel.Restore +import cash.z.ecc.android.feedback.Report.Tap.* +import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.ui.base.BaseFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.tylersuehr.chips.Chip +import com.tylersuehr.chips.ChipsAdapter +import com.tylersuehr.chips.SeedWordAdapter +import kotlinx.coroutines.launch + +class RestoreFragment : BaseFragment(), View.OnKeyListener { + override val screen = Report.Screen.RESTORE + + private val walletSetup: WalletSetupViewModel by activityViewModels() + + private lateinit var seedWordRecycler: RecyclerView + private var seedWordAdapter: SeedWordAdapter? = null + + override fun inflate(inflater: LayoutInflater): FragmentRestoreBinding = + FragmentRestoreBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + seedWordRecycler = binding.chipsInput.findViewById(R.id.chips_recycler) + seedWordAdapter = SeedWordAdapter(seedWordRecycler.adapter as ChipsAdapter).onDataSetChanged { + onChipsModified() + }.also { onChipsModified() } + seedWordRecycler.adapter = seedWordAdapter + + binding.chipsInput.apply { + setFilterableChipList(getChips()) + setDelimiter("[ ;,]", true) + } + + binding.buttonDone.setOnClickListener { + onDone().also { tapped(RESTORE_DONE) } + } + + binding.buttonSuccess.setOnClickListener { + onEnterWallet().also { tapped(RESTORE_SUCCESS) } + } + + binding.buttonClear.setOnClickListener { + onClearSeedWords().also { tapped(RESTORE_CLEAR) } + } + } + + private fun onClearSeedWords() { + mainActivity?.showConfirmation( + "Clear All Words", + "Are you sure you would like to clear all the seed words and type them again?", + "Clear", + onPositive = { + binding.chipsInput.clearSelectedChips() + } + ) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + mainActivity?.onFragmentBackPressed(this) { + tapped(RESTORE_BACK) + if (seedWordAdapter == null || seedWordAdapter?.itemCount == 1) { + onExit() + } else { + MaterialAlertDialogBuilder(requireContext()) + .setMessage("Are you sure? For security, the words that you have entered will be cleared!") + .setTitle("Abort?") + .setPositiveButton("Stay") { dialog, _ -> + mainActivity?.reportFunnel(Restore.Stay) + dialog.dismiss() + } + .setNegativeButton("Exit") { dialog, _ -> + dialog.dismiss() + onExit() + } + .show() + } + } + } + + override fun onResume() { + super.onResume() + // Require one less tap to enter the seed words + touchScreenForUser() + } + + private fun onExit() { + mainActivity?.reportFunnel(Restore.Exit) + hideAutoCompleteWords() + mainActivity?.hideKeyboard() + mainActivity?.navController?.popBackStack() + } + + private fun onEnterWallet() { + mainActivity?.reportFunnel(Restore.Success) + mainActivity?.safeNavigate(R.id.action_nav_restore_to_nav_home) + } + + private fun onDone() { + mainActivity?.reportFunnel(Restore.Done) + mainActivity?.hideKeyboard() + val activation = ZcashWalletApp.instance.defaultNetwork.saplingActivationHeight + val seedPhrase = binding.chipsInput.selectedChips.joinToString(" ") { + it.title + } + var birthday = binding.root.findViewById(R.id.input_birthdate).text.toString() + .let { birthdateString -> + if (birthdateString.isNullOrEmpty()) activation.value else birthdateString.toLong() + }.coerceAtLeast(activation.value) + + try { + walletSetup.validatePhrase(seedPhrase) + importWallet(seedPhrase, BlockHeight.new(ZcashWalletApp.instance.defaultNetwork, birthday)) + } catch (t: Throwable) { + mainActivity?.showInvalidSeedPhraseError(t) + } + } + + private fun importWallet(seedPhrase: String, birthday: BlockHeight?) { + mainActivity?.reportFunnel(Restore.ImportStarted) + mainActivity?.hideKeyboard() + mainActivity?.apply { + lifecycleScope.launch { + try { + walletSetup.importWallet(seedPhrase, birthday) + mainActivity?.startSync() + // bugfix: if the user proceeds before the synchronizer is created the app will crash! + binding.buttonSuccess.isEnabled = true + mainActivity?.reportFunnel(Restore.ImportCompleted) + playSound("sound_receive_small.mp3") + vibrateSuccess() + } catch (e: UnsatisfiedLinkError) { + mainActivity?.showSharedLibraryCriticalError(e) + } + } + } + + binding.groupDone.visibility = View.GONE + binding.groupStart.visibility = View.GONE + binding.groupSuccess.visibility = View.VISIBLE + binding.buttonSuccess.isEnabled = false + } + + private fun onChipsModified() { + updateDoneViews() + forceShowKeyboard() + } + + private fun updateDoneViews(): Boolean { + val count = seedWordAdapter?.itemCount ?: 0 + reportWords(count - 1) // subtract 1 for the editText + val isDone = count > 24 + binding.groupDone.goneIf(!isDone) + return !isDone + } + + // forcefully show the keyboard as a hack to fix odd behavior where the keyboard + // sometimes closes randomly and inexplicably in between seed word entries + private fun forceShowKeyboard() { + requireView().postDelayed( + { + val isDone = (seedWordAdapter?.itemCount ?: 0) > 24 + val focusedView = if (isDone) binding.inputBirthdate else seedWordAdapter!!.editText + mainActivity!!.showKeyboard(focusedView) + focusedView.requestFocus() + }, + 500L + ) + } + + private fun reportWords(count: Int) { + mainActivity?.run { +// reportFunnel(Restore.SeedWordCount(count)) + if (count == 1) { + reportFunnel(Restore.SeedWordsStarted) + } else if (count == 24) { + reportFunnel(Restore.SeedWordsCompleted) + } + } + } + + private fun hideAutoCompleteWords() { + seedWordAdapter?.editText?.setText("") + } + + private fun getChips(): List { + return resources.getStringArray(R.array.word_list).map { + SeedWordChip(it) + } + } + + private fun touchScreenForUser() { + seedWordAdapter?.editText?.apply { + postDelayed( + { + seedWordAdapter?.editText?.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + dispatchTouchEvent(motionEvent(ACTION_DOWN)) + dispatchTouchEvent(motionEvent(ACTION_UP)) + }, + 100L + ) + } + } + + private fun motionEvent(action: Int) = SystemClock.uptimeMillis().let { now -> + MotionEvent.obtain(now, now, action, 0f, 0f, 0) + } + + override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean { + return false + } +} + +class SeedWordChip(val word: String, var index: Int = -1) : Chip() { + override fun getSubtitle(): String? = null // "subtitle for $word" + override fun getAvatarDrawable(): Drawable? = null + override fun getId() = index + override fun getTitle() = word + override fun getAvatarUri() = null +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/SeedWordAdapter.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/SeedWordAdapter.kt new file mode 100644 index 0000000..139333c --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/SeedWordAdapter.kt @@ -0,0 +1,103 @@ +package com.tylersuehr.chips + +import android.content.Context +import android.text.TextUtils +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import cash.z.ecc.android.R +import cash.z.ecc.android.ext.toAppColor +import cash.z.ecc.android.ui.setup.SeedWordChip + +class SeedWordAdapter : ChipsAdapter { + + constructor(existingAdapter: ChipsAdapter) : super(existingAdapter.mDataSource, existingAdapter.mEditText, existingAdapter.mOptions) + + val editText = mEditText + private var onDataSetChangedListener: (() -> Unit)? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return if (viewType == CHIP) SeedWordHolder(SeedWordChipView(parent.context)) + else object : RecyclerView.ViewHolder(mEditText) {} + } + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (getItemViewType(position) == CHIP) { // Chips + // Display the chip information on the chip view + (holder as SeedWordHolder).seedChipView.bind(mDataSource.getSelectedChip(position), position) + } else { + val size = mDataSource.selectedChips.size + + // tricky bugfix: + // keep this always enabled otherwise older versions of android crash when this + // view is given focus. As a work around, just hide the cursor when the user is done + // editing. This is not ideal but it's better than a crash during wallet restore! + mEditText.isEnabled = true + mEditText.hint = if (size < 3) { + mEditText.isCursorVisible = true + mEditText.setHintTextColor(R.color.text_light_dimmed.toAppColor()) + val ordinal = when (size) { 2 -> "3rd"; 1 -> "2nd"; else -> "1st" } + "Enter $ordinal seed word" + } else if (size >= 24) { + mEditText.setHintTextColor(R.color.zcashGreen.toAppColor()) + mEditText.isCursorVisible = false + "done" + } else { + mEditText.isCursorVisible = true + mEditText.setHintTextColor(R.color.zcashYellow.toAppColor()) + "${size + 1}" + } + } + } + + override fun onChipDataSourceChanged() { + super.onChipDataSourceChanged() + onDataSetChangedListener?.invoke() + } + + fun onDataSetChanged(block: () -> Unit): SeedWordAdapter { + onDataSetChangedListener = block + return this + } + + override fun onKeyboardActionDone(text: String?) { + if (TextUtils.isEmpty(text)) return + + if (mDataSource.originalChips.firstOrNull { it.title == text } != null) { + mDataSource.addSelectedChip(DefaultCustomChip(text)) + mEditText.apply { + postDelayed( + { + setText("") + requestFocus() + }, + 50L + ) + } + } + } + + // this function is called with the contents of the field, split by the delimiter + override fun onKeyboardDelimiter(text: String) { + val firstMatchingWord = (mDataSource.filteredChips.firstOrNull() as? SeedWordChip)?.word?.takeUnless { + !it.startsWith(text) + } + if (firstMatchingWord != null) { + onKeyboardActionDone(firstMatchingWord) + } else { + onKeyboardActionDone(text) + } + } + + private inner class SeedWordHolder(chipView: SeedWordChipView) : ChipsAdapter.ChipHolder(chipView) { + val seedChipView = super.chipView as SeedWordChipView + } + + private inner class SeedWordChipView(context: Context) : ChipView(context) { + private val indexView: TextView = findViewById(R.id.chip_index) + + fun bind(chip: Chip, index: Int) { + super.inflateFromChip(chip) + indexView.text = (index + 1).toString() + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt new file mode 100644 index 0000000..aae3768 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt @@ -0,0 +1,233 @@ +package cash.z.ecc.android.ui.setup + +import android.content.Context +import androidx.lifecycle.ViewModel +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.di.DependenciesHolder +import cash.z.ecc.android.ext.Const +import cash.z.ecc.android.ext.failWith +import cash.z.ecc.android.feedback.Feedback +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.Initializer +import cash.z.ecc.android.sdk.exception.InitializerException +import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.sdk.model.LightWalletEndpoint +import cash.z.ecc.android.sdk.model.ZcashNetwork +import cash.z.ecc.android.sdk.tool.DerivationTool +import cash.z.ecc.android.sdk.type.UnifiedViewingKey +import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.* +import cash.z.ecc.android.util.twig +import cash.z.ecc.kotlin.mnemonic.Mnemonics +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.withContext + +class WalletSetupViewModel : ViewModel() { + + private val mnemonics: Mnemonics = DependenciesHolder.mnemonics + + private val lockBox: LockBox = DependenciesHolder.lockBox + + private val prefs: LockBox = DependenciesHolder.prefs + + private val feedback: Feedback = DependenciesHolder.feedback + + enum class WalletSetupState { + SEED_WITH_BACKUP, SEED_WITHOUT_BACKUP, NO_SEED + } + + fun checkSeed(): Flow = flow { + when { + lockBox.getBoolean(Const.Backup.HAS_BACKUP) -> emit(SEED_WITH_BACKUP) + lockBox.getBoolean(Const.Backup.HAS_SEED) -> emit(SEED_WITHOUT_BACKUP) + else -> emit(NO_SEED) + } + } + + /** + * Throw an exception if the seed phrase is bad. + */ + fun validatePhrase(seedPhrase: String) { + mnemonics.validate(seedPhrase.toCharArray()) + } + + fun loadBirthdayHeight(): BlockHeight? { + val h: Int? = lockBox[Const.Backup.BIRTHDAY_HEIGHT] + twig("Loaded birthday with key ${Const.Backup.BIRTHDAY_HEIGHT} and found $h") + h?.let { + return BlockHeight.new(ZcashWalletApp.instance.defaultNetwork, it.toLong()) + } + return null + } + + suspend fun newWallet() { + val network = ZcashWalletApp.instance.defaultNetwork + twig("Initializing new ${network.networkName} wallet") + with(mnemonics) { + storeWallet(nextMnemonic(nextEntropy()), network, loadNearestBirthday(network)) + } + openStoredWallet() + } + + suspend fun importWallet(seedPhrase: String, birthdayHeight: BlockHeight?) { + val network = ZcashWalletApp.instance.defaultNetwork + twig("Importing ${network.networkName} wallet. Requested birthday: $birthdayHeight") + storeWallet( + seedPhrase.toCharArray(), + network, + birthdayHeight ?: loadNearestBirthday(network) + ) + openStoredWallet() + } + + suspend fun openStoredWallet() { + DependenciesHolder.initializerComponent.createInitializer(loadConfig()) + } + + /** + * Build a config object by loading in the viewingKey, birthday and server info which is already + * known by this point. + */ + private suspend fun loadConfig(): Initializer.Config { + twig("Loading config variables") + var overwriteVks = false + val network = ZcashWalletApp.instance.defaultNetwork + val vk = + loadUnifiedViewingKey() ?: onMissingViewingKey(network).also { overwriteVks = true } + val birthdayHeight = loadBirthdayHeight() ?: onMissingBirthday(network) + val host = prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST + val port = prefs[Const.Pref.SERVER_PORT] ?: Const.Default.Server.PORT + + twig("Done loading config variables") + return Initializer.Config { + it.importWallet(vk, birthdayHeight, network, LightWalletEndpoint(host, port, true)) + it.setOverwriteKeys(overwriteVks) + } + } + + private fun loadUnifiedViewingKey(): UnifiedViewingKey? { + val extfvk = lockBox.getCharsUtf8(Const.Backup.VIEWING_KEY) + val extpub = lockBox.getCharsUtf8(Const.Backup.PUBLIC_KEY) + return when { + extfvk == null || extpub == null -> { + if (extfvk == null) { + twig("Warning: Shielded key was missing") + } + if (extpub == null) { + twig("Warning: Transparent key was missing") + } + null + } + else -> UnifiedViewingKey(extfvk = String(extfvk), extpub = String(extpub)) + } + } + + private suspend fun onMissingViewingKey(network: ZcashNetwork): UnifiedViewingKey { + twig("Recover VK: Viewing key was missing") + // add some temporary logic to help us troubleshoot this problem. + ZcashWalletApp.instance.getSharedPreferences("SecurePreferences", Context.MODE_PRIVATE) + .all.map { it.key }.joinToString().let { keyNames -> + "${Const.Backup.VIEWING_KEY}, ${Const.Backup.PUBLIC_KEY}".let { missingKeys -> + // is there a typo or change in how the value is labelled? + // for troubleshooting purposes, let's see if we CAN derive the vk from the seed in these situations + var recoveryViewingKey: UnifiedViewingKey? = null + var ableToLoadSeed = false + try { + val seed = lockBox.getBytes(Const.Backup.SEED)!! + ableToLoadSeed = true + twig("Recover UVK: Seed found") + recoveryViewingKey = + DerivationTool.deriveUnifiedViewingKeys(seed, network)[0] + twig("Recover UVK: successfully derived UVK from seed") + } catch (t: Throwable) { + twig("Failed while trying to recover UVK due to: $t") + } + + // this will happen during rare upgrade scenarios when the user migrates from a seed-only wallet to this vk-based version + // or during more common scenarios where the user migrates from a vk only wallet to a unified vk wallet + if (recoveryViewingKey != null) { + storeUnifiedViewingKey(recoveryViewingKey) + return recoveryViewingKey + } else { + feedback.report( + Report.Issue.MissingViewkey( + ableToLoadSeed, + missingKeys, + keyNames, + lockBox.getCharsUtf8(Const.Backup.VIEWING_KEY) != null + ) + ) + } + throw InitializerException.MissingViewingKeyException + } + } + } + + private suspend fun onMissingBirthday(network: ZcashNetwork): BlockHeight = + failWith(InitializerException.MissingBirthdayException) { + twig("Recover Birthday: falling back to sapling birthday") + loadNearestBirthday(network) + } + + private suspend fun loadNearestBirthday(network: ZcashNetwork) = + BlockHeight.ofLatestCheckpoint( + ZcashWalletApp.instance, + network, + ) + + // + // Storage Helpers + // + + /** + * Entry point for all storage. Takes a seed phrase and stores all the parts so that we can + * selectively use them, the next time the app is opened. Although we store everything, we + * primarily only work with the viewing key and spending key. The seed is only accessed when + * presenting backup information to the user. + */ + private suspend fun storeWallet( + seedPhraseChars: CharArray, + network: ZcashNetwork, + birthday: BlockHeight + ) { + check(!lockBox.getBoolean(Const.Backup.HAS_SEED)) { + "Error! Cannot store a seed when one already exists! This would overwrite the" + + " existing seed and could lead to a loss of funds if the user has no backup!" + } + + storeBirthday(birthday) + + mnemonics.toSeed(seedPhraseChars).let { bip39Seed -> + DerivationTool.deriveUnifiedViewingKeys(bip39Seed, network)[0].let { viewingKey -> + storeSeedPhrase(seedPhraseChars) + storeSeed(bip39Seed) + storeUnifiedViewingKey(viewingKey) + } + } + } + + private suspend fun storeBirthday(birthday: BlockHeight) = withContext(IO) { + twig("Storing birthday ${birthday.value} with and key ${Const.Backup.BIRTHDAY_HEIGHT}") + lockBox[Const.Backup.BIRTHDAY_HEIGHT] = birthday.value + } + + private suspend fun storeSeedPhrase(seedPhrase: CharArray) = withContext(IO) { + twig("Storing seedphrase: ${seedPhrase.size}") + lockBox[Const.Backup.SEED_PHRASE] = seedPhrase + lockBox[Const.Backup.HAS_SEED_PHRASE] = true + } + + private suspend fun storeSeed(bip39Seed: ByteArray) = withContext(IO) { + twig("Storing seed: ${bip39Seed.size}") + lockBox.setBytes(Const.Backup.SEED, bip39Seed) + lockBox[Const.Backup.HAS_SEED] = true + } + + private suspend fun storeUnifiedViewingKey(vk: UnifiedViewingKey) = withContext(IO) { + twig("storeViewingKey vk: ${vk.extfvk.length}") + lockBox[Const.Backup.VIEWING_KEY] = vk.extfvk + lockBox[Const.Backup.PUBLIC_KEY] = vk.extpub + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/tab_layout/TabLayoutFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/tab_layout/TabLayoutFragment.kt new file mode 100644 index 0000000..4a3e808 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/tab_layout/TabLayoutFragment.kt @@ -0,0 +1,144 @@ +package cash.z.ecc.android.ui.tab_layout + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import androidx.annotation.ColorRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.viewpager2.widget.ViewPager2 +import cash.z.ecc.android.R +import cash.z.ecc.android.databinding.FragmentTabLayoutBinding +import cash.z.ecc.android.ext.onClickNavBack +import cash.z.ecc.android.feedback.Report +import cash.z.ecc.android.ui.base.BaseFragment +import cash.z.ecc.android.ui.receive.ReceiveTabFragment +import cash.z.ecc.android.ui.receive.ReceiveViewModel +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator +import kotlinx.coroutines.launch + +class TabLayoutFragment : + BaseFragment(), + FragmentCreator, + TabLayout.OnTabSelectedListener { + + private val viewModel: ReceiveViewModel by viewModels() + + override fun inflate(inflater: LayoutInflater): FragmentTabLayoutBinding = + FragmentTabLayoutBinding.inflate(inflater) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.hitAreaExit.onClickNavBack { tapped(Report.Tap.RECEIVE_BACK) } + binding.textTitle.text = "Receive ${getString(R.string.symbol)}" + binding.viewPager.adapter = ViewPagerAdapter(this, this) + binding.viewPager.setPageTransformer(ZoomOutPageTransformer()) + binding.tabLayout.addOnTabSelectedListener(this) + /* + TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = if (position == 0) "Shielded" else "Transparent" + }.attach() + */ + binding.buttonShareAddress.setOnClickListener { + shareActiveAddress() + } + } + + private fun shareActiveAddress() { + mainActivity?.apply { + lifecycleScope.launch { + val address = + viewModel.getAddress() + shareText(address) + } + } + } + + // + // TabLayout.OnTabSelectedListener implementation + // + + override fun onTabSelected(tab: TabLayout.Tab) { + when (tab.position) { + 0 -> setSelectedTab(R.color.zcashYellow) + } + } + + override fun onTabUnselected(tab: TabLayout.Tab) {} + + override fun onTabReselected(tab: TabLayout.Tab) {} + + private fun setSelectedTab(@ColorRes color: Int) { + binding.tabLayout.setSelectedTabIndicatorColor( + ContextCompat.getColor(requireContext(), color) + ) + binding.tabLayout.setTabTextColors( + ContextCompat.getColor(requireContext(), R.color.unselected_tab_grey), + ContextCompat.getColor(requireContext(), color) + ) + } + + // + // FragmentCreator implementation + // + + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> ReceiveTabFragment() + else -> throw IndexOutOfBoundsException("Cannot create a fragment for index $position") + } + } + + override fun getItemCount() = 2 + + interface AddressFragment { + suspend fun getAddress(): String + } +} + +private const val MIN_SCALE = 0.8f +private const val MIN_ALPHA = 0.1f + +class ZoomOutPageTransformer : ViewPager2.PageTransformer { + + override fun transformPage(view: View, position: Float) { + view.apply { + val pageWidth = width + val pageHeight = height + when { + position < -1 -> { // [-Infinity,-1) + // This page is way off-screen to the left. + alpha = 0f + } + position <= 1 -> { // [-1,1] + // Modify the default slide transition to shrink the page as well + val scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)) + val vertMargin = pageHeight * (1 - scaleFactor) / 2 + val horzMargin = pageWidth * (1 - scaleFactor) / 2 + translationX = if (position < 0) { + horzMargin - vertMargin / 2 + } else { + horzMargin + vertMargin / 2 + } + + // Scale the page down (between MIN_SCALE and 1) + scaleX = scaleFactor + scaleY = scaleFactor + + // Fade the page relative to its size. + alpha = ( + MIN_ALPHA + + (((scaleFactor - MIN_SCALE) / (1 - MIN_SCALE)) * (1 - MIN_ALPHA)) + ) + } + else -> { // (1,+Infinity] + // This page is way off-screen to the right. + alpha = 0f + } + } + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/tab_layout/ViewPagerAdapter.kt b/app/src/main/java/cash/z/ecc/android/ui/tab_layout/ViewPagerAdapter.kt new file mode 100644 index 0000000..179c97e --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/tab_layout/ViewPagerAdapter.kt @@ -0,0 +1,13 @@ +package cash.z.ecc.android.ui.tab_layout + +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter + +class ViewPagerAdapter(parent: Fragment, creator: FragmentCreator) : + FragmentStateAdapter(parent), + FragmentCreator by creator + +interface FragmentCreator { + fun createFragment(position: Int): Fragment + fun getItemCount(): Int +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/util/AddressPartNumberSpan.kt b/app/src/main/java/cash/z/ecc/android/ui/util/AddressPartNumberSpan.kt new file mode 100644 index 0000000..3d7b3b5 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/util/AddressPartNumberSpan.kt @@ -0,0 +1,28 @@ +package cash.z.ecc.android.ui.util + +import android.text.TextPaint +import android.text.style.MetricAffectingSpan +import androidx.core.content.ContextCompat +import cash.z.ecc.android.R +import cash.z.ecc.android.ZcashWalletApp + +/** + * A span used for numbering the parts of an address. It combines a [android.text.style.RelativeSizeSpan], + * [android.text.style.SuperscriptSpan], and a [android.text.style.ForegroundColorSpan] into one class for efficiency. + */ +class AddressPartNumberSpan( + val proportion: Float = 0.5f, + val color: Int = ContextCompat.getColor(ZcashWalletApp.instance, R.color.colorPrimary) +) : MetricAffectingSpan() { + + override fun updateMeasureState(textPaint: TextPaint) { + textPaint.baselineShift += (textPaint.ascent() / 2).toInt() // from SuperscriptSpan + textPaint.textSize = textPaint.textSize * proportion // from RelativeSizeSpan + } + + override fun updateDrawState(textPaint: TextPaint) { + textPaint.baselineShift += (textPaint.ascent() / 2).toInt() // from SuperscriptSpan (baseline must shift before resizing or else it will not properly align to the top of the text) + textPaint.textSize = textPaint.textSize * proportion // from RelativeSizeSpan + textPaint.color = color // from ForegroundColorSpan + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/util/DebugFileTwig.kt b/app/src/main/java/cash/z/ecc/android/ui/util/DebugFileTwig.kt new file mode 100644 index 0000000..f4a1fd2 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/util/DebugFileTwig.kt @@ -0,0 +1,22 @@ +package cash.z.ecc.android.ui.util + +import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.util.TroubleshootingTwig +import okio.appendingSink +import okio.buffer +import java.io.File + +class DebugFileTwig(fileName: String = "developer_log.txt") : TroubleshootingTwig(formatter = spiffy(6)) { + val file = File("${ZcashWalletApp.instance.filesDir}/logs", fileName) + + override fun twig(logMessage: String, priority: Int) { + super.twig(logMessage, priority) + appendToFile(formatter(logMessage)) + } + + private fun appendToFile(message: String) { + file.appendingSink().buffer().use { + it.writeUtf8("$message\n") + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/util/MemoUtil.kt b/app/src/main/java/cash/z/ecc/android/ui/util/MemoUtil.kt new file mode 100644 index 0000000..838c9ff --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/util/MemoUtil.kt @@ -0,0 +1,54 @@ +package cash.z.ecc.android.ui.util + +import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction +import java.nio.charset.StandardCharsets + +/** + * The prefix that this wallet uses whenever the user chooses to include their address in the memo. + * This is the one we standardize around. + */ +const val INCLUDE_MEMO_PREFIX_STANDARD = "Reply-To:" + +/** + * The non-standard prefixes that we will parse if other wallets send them our way. + */ +val INCLUDE_MEMO_PREFIXES_RECOGNIZED = arrayOf( + INCLUDE_MEMO_PREFIX_STANDARD, // standard + "reply-to", // standard w/o colon + "reply to:", // space instead of dash + "reply to", // space instead of dash w/o colon + "sent from:", // previous standard + "sent from" // previous standard w/o colon +) + +// TODO: move this to the SDK +inline fun ByteArray?.toUtf8Memo(): String { +// TODO: make this more official but for now, this will do + return if (this == null || this.isEmpty() || this[0] >= 0xF5) "" else try { + // trim empty and "replacement characters" for codes that can't be represented in unicode + String(this, StandardCharsets.UTF_8).trim('\u0000', '\uFFFD') + } catch (t: Throwable) { + "Unable to parse memo." + } +} + +object MemoUtil { + + suspend fun findAddressInMemo(tx: ConfirmedTransaction?, addressValidator: suspend (String) -> Boolean): String? { + // note: t-addr min length is 35, plus we're expecting prefixes + return tx?.memo?.toUtf8Memo()?.takeUnless { it.length < 35 }?.let { memo -> + // start with what we accept as prefixes + INCLUDE_MEMO_PREFIXES_RECOGNIZED.mapNotNull { + val maybeMemo = memo.substringAfterLast(it) + if (addressValidator(maybeMemo)) maybeMemo else null + }.firstOrNull { !it.isNullOrBlank() } + } + } + + // note: cannot use substringAfterLast, directly because we want to ignore case. perhaps submit a feature request to kotlin for adding `ignoreCase` + private fun String.substringAfterLast(prefix: String): String { + return lastIndexOf(prefix, ignoreCase = true).takeUnless { it == -1 }?.let { i -> + substring(i + prefix.length).trimStart() + } ?: "" + } +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/util/PermissionFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/util/PermissionFragment.kt new file mode 100644 index 0000000..334df6d --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/ui/util/PermissionFragment.kt @@ -0,0 +1,47 @@ +package cash.z.ecc.android.ui.util +// +// import android.Manifest +// import android.content.Context +// import android.content.pm.PackageManager +// import android.os.Bundle +// import android.widget.Toast +// import androidx.core.content.ContextCompat +// import androidx.fragment.app.Fragment +// import cash.z.ecc.android.ui.MainActivity +// +// class PermissionFragment : Fragment() { +// +// val activity get() = context as MainActivity +// +// override fun onCreate(savedInstanceState: Bundle?) { +// super.onCreate(savedInstanceState) +// if (!hasPermissions(activity)) { +// requestPermissions(PERMISSIONS, REQUEST_CODE) +// } else { +// activity.openCamera() +// } +// } +// +// override fun onRequestPermissionsResult( +// requestCode: Int, permissions: Array, grantResults: IntArray +// ) { +// super.onRequestPermissionsResult(requestCode, permissions, grantResults) +// +// if (requestCode == REQUEST_CODE) { +// if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) { +// activity.openCamera() +// } else { +// Toast.makeText(context, "Camera request denied", Toast.LENGTH_LONG).show() +// } +// } +// } +// +// companion object { +// private const val REQUEST_CODE = 101 +// private val PERMISSIONS = arrayOf(Manifest.permission.CAMERA) +// +// fun hasPermissions(context: Context) = PERMISSIONS.all { +// ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED +// } +// } +// } diff --git a/app/src/main/java/cash/z/ecc/android/util/Twig.kt b/app/src/main/java/cash/z/ecc/android/util/Twig.kt new file mode 100644 index 0000000..66b4330 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/util/Twig.kt @@ -0,0 +1,200 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package cash.z.ecc.android.util + +import java.util.concurrent.CopyOnWriteArraySet +import kotlin.math.roundToLong + +internal typealias Leaf = String + +/** + * A tiny log. + */ +interface Twig { + + /** + * Log the message. Simple. + */ + fun twig(logMessage: String = "", priority: Int = 0) + + /** + * Bundles twigs together. + */ + operator fun plus(twig: Twig): Twig { + // if the other twig is a composite twig, let it handle the addition + return if (twig is CompositeTwig) twig.plus(this) else CompositeTwig(mutableListOf(this, twig)) + } + companion object { + + /** + * Access the trunk corresponding to this twig. + */ + val trunk get() = Bush.trunk + + /** + * Convenience function to just turn this thing on. Twigs are silent by default so this is + * most useful to enable developer logging at the right time. + */ + fun enabled(isEnabled: Boolean) { + if (isEnabled) plant(TroubleshootingTwig()) else plant(SilentTwig()) + } + + /** + * Plants the twig, making it the one and only bush. Twigs can be bundled together to create + * the appearance of multiple bushes (i.e `Twig.plant(twigA + twigB + twigC)`) even though + * there's only ever one bush. + */ + fun plant(rootTwig: Twig) { + Bush.trunk = rootTwig + } + + /** + * Generate a leaf on the bush. Leaves show up in every log message as tags until they are + * clipped. + */ + fun sprout(leaf: Leaf) = Bush.leaves.add(leaf) + + /** + * Clip a leaf from the bush. Clipped leaves no longer appear in logs. + */ + fun clip(leaf: Leaf) = Bush.leaves.remove(leaf) + + /** + * Clip all leaves from the bush. + */ + fun prune() = Bush.leaves.clear() + } +} + +/** + * A collection of tiny logs (twigs) consisting of one trunk and maybe some leaves. There can only + * ever be one trunk. Trunks are created by planting a twig. Whenever a leaf sprouts, it will appear + * as a tag on every log message until clipped. + * + * @see [Twig.plant] + * @see [Twig.sprout] + * @see [Twig.clip] + */ +object Bush { + var trunk: Twig = SilentTwig() + val leaves: MutableSet = CopyOnWriteArraySet() +} + +/** + * Makes a tiny log. + */ +inline fun twig(message: String, priority: Int = 0) = Bush.trunk.twig(message, priority) + +/** + * Makes an exception. + */ +inline fun twig(t: Throwable) = t.stackTraceToString().lines().forEach { + twig(it) +} + +/** + * Times a tiny log. + */ +inline fun twig(logMessage: String, priority: Int = 0, block: () -> R): R = Bush.trunk.twig(logMessage, priority, block) + +/** + * Meticulously times a tiny task. + */ +inline fun twigTask(logMessage: String, priority: Int = 0, block: () -> R): R = Bush.trunk.twigTask(logMessage, priority, block) + +/** + * A tiny log that does nothing. No one hears this twig fall in the woods. + */ +class SilentTwig : Twig { + + /** + * Shh. + */ + override fun twig(logMessage: String, priority: Int) { + // shh + } +} + +/** + * A tiny log for detecting troubles. Aim at your troubles and pull the twigger. + * + * @param formatter a formatter for the twigs. The default one is pretty spiffy. + * @param printer a printer for the twigs. The default is System.err.println. + */ +open class TroubleshootingTwig( + val formatter: (String) -> String = spiffy(6), + val printer: (String) -> Any = System.err::println, + val minPriority: Int = 0 +) : Twig { + + /** + * Actually print and format the log message, unlike the SilentTwig, which does nothing. + */ + override fun twig(logMessage: String, priority: Int) { + if (priority >= minPriority) printer(formatter(logMessage)) + } + + companion object { + + /** + * A tiny log formatter that makes twigs pretty spiffy. + * + * @param stackFrame the stack frame from which we try to derive the class. This can vary depending + * on how the code is called so we expose it for flexibility. Jiggle the handle on this whenever the + * line numbers appear incorrect. + */ + fun spiffy(stackFrame: Int = 4, tag: String = "@TWIG"): (String) -> String = { logMessage: String -> + val stack = Thread.currentThread().stackTrace[stackFrame] + val time = String.format("$tag %1\$tD %1\$tI:%1\$tM:%1\$tS.%1\$tN", System.currentTimeMillis()) + val className = stack.className.split(".").lastOrNull()?.split("\$")?.firstOrNull() + val tags = Bush.leaves.joinToString(" #", "#") + "$time[$className:${stack.lineNumber}]($tags) $logMessage" + } + } +} + +/** + * Since there can only ever be one trunk on the bush of twigs, this class lets + * you cheat and make that trunk be a bundle of twigs. + */ +open class CompositeTwig(open val twigBundle: MutableList) : + Twig { + override operator fun plus(twig: Twig): Twig { + if (twig is CompositeTwig) twigBundle.addAll(twig.twigBundle) else twigBundle.add(twig) + return this + } + + override fun twig(logMessage: String, priority: Int) { + for (twig in twigBundle) { + twig.twig(logMessage, priority) + } + } +} + +/** + * Times a tiny log. Execute the block of code on the clock. + */ +inline fun Twig.twig(logMessage: String, priority: Int = 0, block: () -> R): R { + val start = System.currentTimeMillis() + val result = block() + val elapsed = (System.currentTimeMillis() - start) + twig("$logMessage | ${elapsed}ms", priority) + return result +} + +/** + * A tiny log task. Execute the block of code with some twigging around the outside. For silent + * twigs, this adds a small amount of overhead at the call site but still avoids logging. + * + * note: being an extension function (i.e. static rather than a member of the Twig interface) allows + * this function to be inlined and simplifies its use with suspend functions + * (otherwise the function and its "block" param would have to suspend) + */ +inline fun Twig.twigTask(logMessage: String, priority: Int = 0, block: () -> R): R { + twig("$logMessage - started | on thread ${Thread.currentThread().name}", priority) + val start = System.nanoTime() + val result = block() + val elapsed = ((System.nanoTime() - start) / 1e5).roundToLong() / 10L + twig("$logMessage - completed | in $elapsed ms" + " on thread ${Thread.currentThread().name}", priority) + return result +} diff --git a/app/src/main/res/anim/anim_enter_from_bottom.xml b/app/src/main/res/anim/anim_enter_from_bottom.xml new file mode 100644 index 0000000..5fada65 --- /dev/null +++ b/app/src/main/res/anim/anim_enter_from_bottom.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_enter_from_left.xml b/app/src/main/res/anim/anim_enter_from_left.xml new file mode 100644 index 0000000..2ceefa4 --- /dev/null +++ b/app/src/main/res/anim/anim_enter_from_left.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_enter_from_right.xml b/app/src/main/res/anim/anim_enter_from_right.xml new file mode 100644 index 0000000..fd4e319 --- /dev/null +++ b/app/src/main/res/anim/anim_enter_from_right.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_exit_to_left.xml b/app/src/main/res/anim/anim_exit_to_left.xml new file mode 100644 index 0000000..3739b76 --- /dev/null +++ b/app/src/main/res/anim/anim_exit_to_left.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_exit_to_right.xml b/app/src/main/res/anim/anim_exit_to_right.xml new file mode 100644 index 0000000..daf23c5 --- /dev/null +++ b/app/src/main/res/anim/anim_exit_to_right.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_fade_in.xml b/app/src/main/res/anim/anim_fade_in.xml new file mode 100644 index 0000000..a9dadf4 --- /dev/null +++ b/app/src/main/res/anim/anim_fade_in.xml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_fade_in_scanner.xml b/app/src/main/res/anim/anim_fade_in_scanner.xml new file mode 100644 index 0000000..58ef2b2 --- /dev/null +++ b/app/src/main/res/anim/anim_fade_in_scanner.xml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_fade_out.xml b/app/src/main/res/anim/anim_fade_out.xml new file mode 100644 index 0000000..6e4e62f --- /dev/null +++ b/app/src/main/res/anim/anim_fade_out.xml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_fade_out_address.xml b/app/src/main/res/anim/anim_fade_out_address.xml new file mode 100644 index 0000000..14e8cd4 --- /dev/null +++ b/app/src/main/res/anim/anim_fade_out_address.xml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/anim/anim_fade_out_medium.xml b/app/src/main/res/anim/anim_fade_out_medium.xml new file mode 100644 index 0000000..4ca3192 --- /dev/null +++ b/app/src/main/res/anim/anim_fade_out_medium.xml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/color/selector_button_text_dark.xml b/app/src/main/res/color/selector_button_text_dark.xml new file mode 100644 index 0000000..916fae4 --- /dev/null +++ b/app/src/main/res/color/selector_button_text_dark.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_button_text_light.xml b/app/src/main/res/color/selector_button_text_light.xml new file mode 100644 index 0000000..4222d84 --- /dev/null +++ b/app/src/main/res/color/selector_button_text_light.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_button_text_light_dimmed.xml b/app/src/main/res/color/selector_button_text_light_dimmed.xml new file mode 100644 index 0000000..73a1f22 --- /dev/null +++ b/app/src/main/res/color/selector_button_text_light_dimmed.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_button_text_light_to_dimmed.xml b/app/src/main/res/color/selector_button_text_light_to_dimmed.xml new file mode 100644 index 0000000..a7a1950 --- /dev/null +++ b/app/src/main/res/color/selector_button_text_light_to_dimmed.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_feedback_button.xml b/app/src/main/res/color/selector_feedback_button.xml new file mode 100644 index 0000000..dcd144b --- /dev/null +++ b/app/src/main/res/color/selector_feedback_button.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_primary_button_activatable.xml b/app/src/main/res/color/selector_primary_button_activatable.xml new file mode 100644 index 0000000..602bc89 --- /dev/null +++ b/app/src/main/res/color/selector_primary_button_activatable.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_secondary_button_activatable.xml b/app/src/main/res/color/selector_secondary_button_activatable.xml new file mode 100644 index 0000000..ff34655 --- /dev/null +++ b/app/src/main/res/color/selector_secondary_button_activatable.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-anydpi/ic_settings.xml b/app/src/main/res/drawable-anydpi/ic_settings.xml new file mode 100644 index 0000000..a03a209 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_settings.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_background.xml b/app/src/main/res/drawable-v24/ic_launcher_background.xml new file mode 100644 index 0000000..912b679 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_background.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..eccf63d --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_balance_detail_amounts_container.xml b/app/src/main/res/drawable/background_balance_detail_amounts_container.xml new file mode 100644 index 0000000..3ef222f --- /dev/null +++ b/app/src/main/res/drawable/background_balance_detail_amounts_container.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_balance_details_total.xml b/app/src/main/res/drawable/background_balance_details_total.xml new file mode 100644 index 0000000..720655e --- /dev/null +++ b/app/src/main/res/drawable/background_balance_details_total.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_balance_details_transparent.xml b/app/src/main/res/drawable/background_balance_details_transparent.xml new file mode 100644 index 0000000..dfc846c --- /dev/null +++ b/app/src/main/res/drawable/background_balance_details_transparent.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_banner.xml b/app/src/main/res/drawable/background_banner.xml new file mode 100644 index 0000000..4d768e9 --- /dev/null +++ b/app/src/main/res/drawable/background_banner.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_banner_large.xml b/app/src/main/res/drawable/background_banner_large.xml new file mode 100644 index 0000000..29a8f3d --- /dev/null +++ b/app/src/main/res/drawable/background_banner_large.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_rounded.xml b/app/src/main/res/drawable/background_button_rounded.xml new file mode 100644 index 0000000..ffb0ecf --- /dev/null +++ b/app/src/main/res/drawable/background_button_rounded.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_circle.xml b/app/src/main/res/drawable/background_circle.xml new file mode 100644 index 0000000..efd64ad --- /dev/null +++ b/app/src/main/res/drawable/background_circle.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_circle_solid.xml b/app/src/main/res/drawable/background_circle_solid.xml new file mode 100644 index 0000000..7c71da7 --- /dev/null +++ b/app/src/main/res/drawable/background_circle_solid.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_footer.xml b/app/src/main/res/drawable/background_footer.xml new file mode 100644 index 0000000..5fc5b96 --- /dev/null +++ b/app/src/main/res/drawable/background_footer.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/background_gradient_balance_details.xml b/app/src/main/res/drawable/background_gradient_balance_details.xml new file mode 100644 index 0000000..8b28d4a --- /dev/null +++ b/app/src/main/res/drawable/background_gradient_balance_details.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/background_gradient_bottom.xml b/app/src/main/res/drawable/background_gradient_bottom.xml new file mode 100644 index 0000000..7cd7c0e --- /dev/null +++ b/app/src/main/res/drawable/background_gradient_bottom.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/background_header.xml b/app/src/main/res/drawable/background_header.xml new file mode 100644 index 0000000..d206fe1 --- /dev/null +++ b/app/src/main/res/drawable/background_header.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/background_home.xml b/app/src/main/res/drawable/background_home.xml new file mode 100644 index 0000000..fea0acb --- /dev/null +++ b/app/src/main/res/drawable/background_home.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_indicator_failed.xml b/app/src/main/res/drawable/background_indicator_failed.xml new file mode 100644 index 0000000..90b45bd --- /dev/null +++ b/app/src/main/res/drawable/background_indicator_failed.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_indicator_inbound.xml b/app/src/main/res/drawable/background_indicator_inbound.xml new file mode 100644 index 0000000..41fd286 --- /dev/null +++ b/app/src/main/res/drawable/background_indicator_inbound.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_indicator_outbound.xml b/app/src/main/res/drawable/background_indicator_outbound.xml new file mode 100644 index 0000000..e25688e --- /dev/null +++ b/app/src/main/res/drawable/background_indicator_outbound.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_indicator_unknown.xml b/app/src/main/res/drawable/background_indicator_unknown.xml new file mode 100644 index 0000000..3747383 --- /dev/null +++ b/app/src/main/res/drawable/background_indicator_unknown.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_send_final.xml b/app/src/main/res/drawable/background_send_final.xml new file mode 100644 index 0000000..5ce6a25 --- /dev/null +++ b/app/src/main/res/drawable/background_send_final.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_title_primary.xml b/app/src/main/res/drawable/background_title_primary.xml new file mode 100644 index 0000000..5c45e99 --- /dev/null +++ b/app/src/main/res/drawable/background_title_primary.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_chip_view.xml b/app/src/main/res/drawable/bg_chip_view.xml new file mode 100644 index 0000000..554253c --- /dev/null +++ b/app/src/main/res/drawable/bg_chip_view.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_details_background.xml b/app/src/main/res/drawable/chip_details_background.xml new file mode 100644 index 0000000..b9f5e5f --- /dev/null +++ b/app/src/main/res/drawable/chip_details_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_account_circle.xml b/app/src/main/res/drawable/ic_account_circle.xml new file mode 100644 index 0000000..38d58a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_circle.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_address_qr.xml b/app/src/main/res/drawable/ic_address_qr.xml new file mode 100644 index 0000000..6fc0bdd --- /dev/null +++ b/app/src/main/res/drawable/ic_address_qr.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml b/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml new file mode 100644 index 0000000..beafea3 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_background_qr.xml b/app/src/main/res/drawable/ic_background_qr.xml new file mode 100644 index 0000000..54612e1 --- /dev/null +++ b/app/src/main/res/drawable/ic_background_qr.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_done_24.xml b/app/src/main/res/drawable/ic_baseline_done_24.xml new file mode 100644 index 0000000..2728880 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_done_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml new file mode 100644 index 0000000..1aeaa99 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_launch_24.xml b/app/src/main/res/drawable/ic_baseline_launch_24.xml new file mode 100644 index 0000000..a73d244 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_launch_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 0000000..d1787e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_shield.xml b/app/src/main/res/drawable/ic_check_shield.xml new file mode 100644 index 0000000..eac8202 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_shield.xml @@ -0,0 +1,14 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_shielded.xml b/app/src/main/res/drawable/ic_check_shielded.xml new file mode 100644 index 0000000..a63c74e --- /dev/null +++ b/app/src/main/res/drawable/ic_check_shielded.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_close_black_24dp.xml b/app/src/main/res/drawable/ic_close_black_24dp.xml new file mode 100644 index 0000000..ede4b71 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_content_copy.xml b/app/src/main/res/drawable/ic_content_copy.xml new file mode 100644 index 0000000..62ad953 --- /dev/null +++ b/app/src/main/res/drawable/ic_content_copy.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_done_24dp.xml b/app/src/main/res/drawable/ic_done_24dp.xml new file mode 100644 index 0000000..cab2aed --- /dev/null +++ b/app/src/main/res/drawable/ic_done_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_expand_memo_enabled.xml b/app/src/main/res/drawable/ic_expand_memo_enabled.xml new file mode 100644 index 0000000..05424cd --- /dev/null +++ b/app/src/main/res/drawable/ic_expand_memo_enabled.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_info_24dp.xml b/app/src/main/res/drawable/ic_info_24dp.xml new file mode 100644 index 0000000..7be0147 --- /dev/null +++ b/app/src/main/res/drawable/ic_info_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_logo_landing.xml b/app/src/main/res/drawable/ic_logo_landing.xml new file mode 100644 index 0000000..22435de --- /dev/null +++ b/app/src/main/res/drawable/ic_logo_landing.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_memo.xml b/app/src/main/res/drawable/ic_memo.xml new file mode 100644 index 0000000..d4ae44f --- /dev/null +++ b/app/src/main/res/drawable/ic_memo.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_profile_zebra_01.xml b/app/src/main/res/drawable/ic_profile_zebra_01.xml new file mode 100644 index 0000000..248f05c --- /dev/null +++ b/app/src/main/res/drawable/ic_profile_zebra_01.xml @@ -0,0 +1,1252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_profile_zebra_02.xml b/app/src/main/res/drawable/ic_profile_zebra_02.xml new file mode 100644 index 0000000..248f05c --- /dev/null +++ b/app/src/main/res/drawable/ic_profile_zebra_02.xml @@ -0,0 +1,1252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_qr_scan.xml b/app/src/main/res/drawable/ic_qr_scan.xml new file mode 100644 index 0000000..8dc41f3 --- /dev/null +++ b/app/src/main/res/drawable/ic_qr_scan.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_qrcode_24dp.xml b/app/src/main/res/drawable/ic_qrcode_24dp.xml new file mode 100644 index 0000000..91b1313 --- /dev/null +++ b/app/src/main/res/drawable/ic_qrcode_24dp.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_receipt_24dp.xml b/app/src/main/res/drawable/ic_receipt_24dp.xml new file mode 100644 index 0000000..5a33b91 --- /dev/null +++ b/app/src/main/res/drawable/ic_receipt_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_receive.xml b/app/src/main/res/drawable/ic_receive.xml new file mode 100644 index 0000000..6ba7640 --- /dev/null +++ b/app/src/main/res/drawable/ic_receive.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_receive_funds.xml b/app/src/main/res/drawable/ic_receive_funds.xml new file mode 100644 index 0000000..7a0fb1d --- /dev/null +++ b/app/src/main/res/drawable/ic_receive_funds.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_sadzebra.xml b/app/src/main/res/drawable/ic_sadzebra.xml new file mode 100644 index 0000000..3c9bb97 --- /dev/null +++ b/app/src/main/res/drawable/ic_sadzebra.xml @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_scan_corner.xml b/app/src/main/res/drawable/ic_scan_corner.xml new file mode 100644 index 0000000..03b8963 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan_corner.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_scan_frame.xml b/app/src/main/res/drawable/ic_scan_frame.xml new file mode 100644 index 0000000..40d2227 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan_frame.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_scan_overlay.xml b/app/src/main/res/drawable/ic_scan_overlay.xml new file mode 100644 index 0000000..be79800 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan_overlay.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_scan_overlay_edited.xml b/app/src/main/res/drawable/ic_scan_overlay_edited.xml new file mode 100644 index 0000000..1dd1512 --- /dev/null +++ b/app/src/main/res/drawable/ic_scan_overlay_edited.xml @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml new file mode 100644 index 0000000..295f00e --- /dev/null +++ b/app/src/main/res/drawable/ic_shield.xml @@ -0,0 +1,14 @@ + + + diff --git a/app/src/main/res/drawable/ic_shield_address.xml b/app/src/main/res/drawable/ic_shield_address.xml new file mode 100644 index 0000000..b04ccb3 --- /dev/null +++ b/app/src/main/res/drawable/ic_shield_address.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_shielded.xml b/app/src/main/res/drawable/ic_shielded.xml new file mode 100644 index 0000000..08f1c83 --- /dev/null +++ b/app/src/main/res/drawable/ic_shielded.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_warning_24dp.xml b/app/src/main/res/drawable/ic_warning_24dp.xml new file mode 100644 index 0000000..29d8a44 --- /dev/null +++ b/app/src/main/res/drawable/ic_warning_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_zcash_primary.xml b/app/src/main/res/drawable/ic_zcash_primary.xml new file mode 100644 index 0000000..248f05c --- /dev/null +++ b/app/src/main/res/drawable/ic_zcash_primary.xml @@ -0,0 +1,1252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_zcash_white.xml b/app/src/main/res/drawable/ic_zcash_white.xml new file mode 100644 index 0000000..bee3638 --- /dev/null +++ b/app/src/main/res/drawable/ic_zcash_white.xml @@ -0,0 +1,1252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_zcashlogo.xml b/app/src/main/res/drawable/ic_zcashlogo.xml new file mode 100644 index 0000000..248f05c --- /dev/null +++ b/app/src/main/res/drawable/ic_zcashlogo.xml @@ -0,0 +1,1252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_zec_symbol.xml b/app/src/main/res/drawable/ic_zec_symbol.xml new file mode 100644 index 0000000..bee3638 --- /dev/null +++ b/app/src/main/res/drawable/ic_zec_symbol.xml @@ -0,0 +1,1252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_zec_symbol_right.xml b/app/src/main/res/drawable/ic_zec_symbol_right.xml new file mode 100644 index 0000000..248f05c --- /dev/null +++ b/app/src/main/res/drawable/ic_zec_symbol_right.xml @@ -0,0 +1,1252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ripple_button_circle.xml b/app/src/main/res/drawable/ripple_button_circle.xml new file mode 100644 index 0000000..e12bc65 --- /dev/null +++ b/app/src/main/res/drawable/ripple_button_circle.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/selector_pressed_ripple_circle.xml b/app/src/main/res/drawable/selector_pressed_ripple_circle.xml new file mode 100644 index 0000000..d8871de --- /dev/null +++ b/app/src/main/res/drawable/selector_pressed_ripple_circle.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/font/inconsolata.ttf b/app/src/main/res/font/inconsolata.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fc981ce7ad6c42d2384f0ef74b73174b9302ee65 GIT binary patch literal 96964 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

bO)#R zarzLak8t`Vr_XWv_sz#`+U$Id(>I;Yv322Nge>_%4srx8xe zIj!Nek<)fgh0a>G=2uO;bqtxZPrJ?Y@O?Hed@@WKJ*a29xL00JQM$&Mq;JFxps0bkailezmU9|0 zQ|R1AOk}7um98>WrW&EL)JT=B+{Ξ29ypp!R(LOYK$fsCU(S z>U{<4GIkXiMy4^s$TCJ6+1Op=8Tm#5Q|uOzBKN9%RiFx02&LSNJx7E3zIqgSM55aH zQ-Sv{1qYJKRe5O5*^t{xoh#}Q+rN|P_==n^cM>(_XYzBoPktfy%P-|u@_u znv_>#>l$l0pvcx2&c*f z`kt?de+yI&M&MIWk2p94+9*6xK~%vtmTB_oFchi(_B24rV_=JdhLF6UMF0_5oCZ zH0(h$z$t~mqMhh7>Tk?<9DxoFC;ensgzr#8Y1 z0ILt4tK}3k8(962T!bYtUYg^nHSYjJiBlw%EwN97)__vp0ZzY4!#6_}sgo7SO{GSu z8hj!K8#_LPUTXmc&eE2RV`YB=Ru{%)f4JT^%VN{EPD5x zdLGpJN*z!MP)cE2?jTx$Qb8jJ)PgpG5qMR;CSRAk(jQ~XVc3G-)95)Wc; zdcZgWc6VXCE9)Veen7Y7qZkuOQb0w@hx5_`thvZ-6Bl20*tnN}R>ST1!3%x6f73*YJ5*`xo!iM)T zGzUXUnSnDECD0y?J>Lhi# znxH1CQZ*BLZGpH(Trba6W7QZnMNL&bs#hJOZj^r&yC7fZt4XSxPF<;c!0pr2WOWYK z-Fom&8+4?@VAuE#EG=81AqiL{9sqCrS&oIBHPwJE7d-D${or~hq=f^PC0IYf4lb-U z0@~ncajL9?Rr^$VvAj%PEpL+B3rs4!Fvop z)ReeU{#M=%4%&lNXFqZGAWGb$9#9Xfmq2AFDB%VrqDG7<(F97QhB8CGP(dghDhXAF zYC_GS_R#pyw9tak;?VlgiJ{X&7lkfkO57NBffAl@PPiZ(W=hnD+n5qtm=dRh5bfnPx!t;l(+0_Wo8&hIcnYS!hmBN&e zObOZlDN}=!{+M_q@x;J)E7u?BpOSd4KbY8;DaW zME}v>KeKn!ep~FW!&OhY*ZX&WXZIDmGhct|^=t6|(d&y}U-vrn)z^Rj`U8Cb22Owa z`pvIj`r5s(&wm}(#n*1bbtbNruN{kY?rX3QyteqY!(OX<4ZGl1KYsQ8SAUJqt6pu` zwQtw8J6{rF=My{E?wq)D{LXQ&T=B}~LcHjBj@zN7CFW~HKA>L1y2^jXQO0`XXk(MH z#W>zL(YV)mP{Yl;_-;I8JZZdSyp{|%@BZD`Y3w%MHuf6t8t)q)8Xp;-7@s*D4v)j< zsC3jinjLWmBsms^xZ@+Fkj{>;QVuSNA&&PM^IZo#8;%bNf8fu76g@*a`0)R)zmR%X>8w(w3z4T*%`cYCEL#ZSoaJ-BZ+fbvtC^osia~ z>3;=j{aZ-Zew8kFLMHo_PX$ypBbsED@-=lm@Qakj>HPHNgX4% zVU_tMtuv6n=Rx-8h<@x2-xIlz*TphTl*$nzCbL8Yp3VyC7M0Kwx@4IcErX(6M#OmR zd~0Qnm?|5@WLYPAWs~T^E_bGE7c*q5SSowPVmVdJkzHbm>=E;!TQ8S0#7a3+&VrZz zFmaSTTpTIqAU&Co`UlS{=GxkQ{OSBM|TQ^j}XT5+k|O6!&QvAj^+EH4&6 zkr#Pz*B`a*rG_UpA1@;%jXuul05bjcT> zQ$7xD??q^qPpM}`iOdvL(krUrOB^RFL|m4OI+-h`$!0N2c8ESij?9x2#CrK1akQK- zj*$z+@rW-vK`s|3;oRx9@_XWk@@#R1JY8HaPZPJuOU2LS<>EGZgScJ(Nc>V>Eq*Pp z6?e!R#qZ_M#eMP@;&1Xn@f2(nFUyC;3-V9mCHX(%RoE8(C0~GE|0Q;)2e6)fg5A|; z*p+_{``UhPCb^XaFCG4jC0=WvQ4Tt3`v%7frHIG{Z*HB8x<$ zED#IjWU)X_66eaz;yekThdfH00nhg~9AQ6G9xYCn>%;}}1aXl(Nn9*X7MI9V#D(%i z@h7=m{15CYe?f%CqjHCM41Tu1;w`f)M9uV=c`5VG%i%D)DpE69>+_dEnlE6QkSV8s7uxP>Oys~x?EkM zu2c`I??SiQhW)@%(6mS!`?LC!`lGs0U9WyfS`2AY2KL2dSCwx-FLZ$-rSV95iZZ5< zB8(}!%7NV^l+0<+l2d`;a=TMpfvV!Ns4MDi@Rl}|$?Liv?V7YFbx-0?H_zU&L!Er! z>~~2pL!aPsVF`&q+a($LwtPJa?zQ3X!sau8f3^vaDuzoJp6iGAEcG@+Zr`}Qp-SWS zFO_e=@2<;^s-3{0ItG{Zd;7js)>{~dn{`c`%Ot@ftD!iE!1_M zF6%tIEbixzlW?(e0{2CAJR&~0-ra+7uS&wz<$fslBJPrL_YcOMY2uctCm`)^LAg(; ziLhN{-=^gDTYyXBN1sV!?KhvnzMN=4JVX5jFIqgq<2np4(Krt9%xaC#eKAmHs#Rwa zPNj|WvV8XtI3_=#oEJd9LFLdq=`ZBA`5hW3j|%USW{zji#cziG;waNE-K1y7+W{moIt+Bi#N{>jn5B+9JQ{R(&*&y zUDk9m%(;{gX&-`Txh`H)oQr?)k>vrelxIHLVqMjitr)jssVy?Rj}#UtXRsQK6MEvweR84FaHZsc4Bemikh;g@%H`VahkR z(?{);uCf;IH@9`cFM?NB9lE{7cgI%F|LWr%>d>vWd8*LnwfBh!(N6efU_IM}Pp{~T zlg-CSRmLi;6HZ5}b5$CM%kibYUJyHFQMyZ|r1(=HBE24WHl7;k)_)o3PlLDEAN4or zt10SgX>dnJ{#h3M^3qFxv1eQJ#4Qp3Ei+{>KEjPJ-mYap9+{D}iWoywc zvTM*AA_~RWc)JV1kg~@qH4Ci5t4GXsxunA(DF72pkQ2-vS&$bj%qjGHM+R^vD4Yrj z?lsvUK+F)0dh3Z1qJH|sN9}EE*VI)tj@xqF_MiP=eD{VEesJ?@wQ*K=&GeC;`Hiy| z$qg$;wfrUV^zo;Cigs1>eqUQN}5}OPG>a2?AeTl1znNGx)?L2FA!+()YS)FWr4C5*)`GMSTtu$ z_l9xZ>pRCU%N-;0;$?kD)b!OJr<^V$+o|d+qLGG%Nf)i#d~xUawgTB$`TdippEzD_ zcN(rt+VErCxxGx^Fwqt33wC?Mz~5mD9`&z-`&WnfiFR>kJg23(sU(X2Ia6Fx8QD^* zkq`xrp5E=*Gv~$2U_b}QZxSgfjwRrbf?k(&I*lb5oYVvCE~0J5ttyxz8is5{s2JHh&}0 z^C#D(R91~?ZK}zQ)z#i>@7M?<=~!0O+M>gpNDyaV~*|Nofw!|?oG;`w3Vd$1=V+1T8lkdL#y+aqG} z=zz#^z#?sloXiaO$P7=WCl)4Ukwi@};CDL9qAm4JnATBu^QF@l6pwYDe*X8iJt-Hi z?o}Hrs%Kw*)sJM${?opfAiI62zaJJ$l6Co_Io{xu(gkC@L>yHL1%NB%>rI7_GZvu* zeq&np$h_Q~V8AyrKRZ8G?9NWZ!t0GTHMmVN2NBnz7dtJC`|9e>KmEkMiLu_(wk}z9 z#Nx?E9w|q)%haD=vFPR^Z~L|rwx0b&`zN47GulBh{Si^dvf>@=Er>o=`xGP|eQ$=| zLUgh4EWM?A&yapg&ye2UhXE{FI}m-Ci)Gj+srnUX&m4ZUtJ6RKB@oLTKc_i z_rcFl`w#g{|I8#BC<7X;-H-d>tFvhEKG70$XmbCj^4FMiVbL0I!YXVONvOg-8E|Aw z#cfB5bgY4RFvSG~hoya#c-)>qu@mcJsjYUjlr=$93;JWEt~bvmc9&tENE)7{yYp9B3WbKy6$UC>7LQvT~r9$nj-~XZ$zXD zc^AerjdltL&u=u%;dhW4lqS+>YC9pY^vs5?=$;|hBwqd3ztpC&e;%87l61c)bgaj* zPb-4`Ek?RuG%_;Fk)jH)JfhDK6~bYpI93g)TO^v3wXGs=Nh}s22_pTv*D-V5$KHhY z0MzZ~mZoS^v#D)8K7Vn~hIZMrzNK|-UY(4zSM@GBVpeE;;F!&6DUNKHl2xCKn{m}Wwet0*Kv%`G z;&%Jp{%m3tZvz%EzaI+1kSYx9!bI{~j zr)-f;r<~Ctzv$>lINP}mTqd_+Hn;6XU`$BiftPgS#oS>d-lMF$7O(q zL@TRoOI{Dfe+zcXfjUWEqb;cGMR=Zv%31v^$!m@CGVEo;v;?sygfho`Ct?uNG3*%Sk*d14W?@@7wwj|u4Wmw!g^BB&h9e~#`(xFZ zxPIF5no0FIFqc1hu3QNQZIz1Zl~@$fc52((%%{tkPxr!hh3<&T{;%YV*xR~AQM_Pe zI!vN;juj;I!N|h%*Hh;9ke$ctf=tj-LIxTd46o~!y52cX$dugtym!A^AX}Rg2|aH= zmL(~Pj}teLSrm0wLbg#I5$xz_{_Y|>A?T&+-i$K0SBusd-U*`6DqbV%oS*W|D%W(1JBC9mYbxqj)x4B)rs@Xvc4FDCzKf9ew_F->Eg4_ow zi{8VY5Wv2~Vxjz5fKK#V-7lui7FdIFMPKjsnwj(N8VOdDJ^Azr9-7aP}{+GG=33S4Vmwho(tBvYPeFxG*pBSnU{>O z*9`;k+42r`G1d(qc0+UHhXn(eT5S69^|G~$^pHTWa5`O!gv%AAL3Av}kokL|m8C98 z2gensPV=MV{&Ig=Ni^4=I|qxr2B{^UJ1U_L zO_a`QPNOC9-UL)tFP}7Zp}h$b4ycdI<+*Y!*Jt^IhQi-5{Ur6^PijX*X#8C!e-6dp zBmbz&M}Ly>?f&#nW*L(s=EbvvWSKP5A*j+YY6fIYHDpZ?gd{OavTKlyF3v$?QmjZq zAh|LEJ;)I`ZeMAP7bjv!2v#glAw_C5=mF);EgyExnmoEZ>deaaj*6;)FCE_XP8n8qbtm2m$vf0%xT zbX9t0z#pdJ1N~t?8S8x?$wrZOn**^~jig?IKQ!){b{lzKn79Gp;vLK~VB><0;2Env zOMlg6(7sH!!}7CfIO(r|qs$l85I-B%a>h}&KsJWOqIh~H_TfHybpz&DKIRw&v||x4 z4E6^QcVc}vcc}SvSE@6Izl-VS_xr>CqH=#pl%|^?r$t0_Z?ID}VLfoEBnFl*Z&|kv zOx)9$o#klv6)iCtSw3?4HMbBmSE=lh@=0UU5p+=uuu?efVkL*>Jk zLw-)+k}M{>jA_FnU#)G+(d8ce4E1-|XP6htn7?um*NHH|xbSJzy2);IYydEW|q@_}8O~;jQkmIVbu&|^s zT3%j4jw`{=vq%|ymL@N@1YIKNWjX30CCvyl$h%6mtm|!zRgWHf+)?8jYb#q4PZvC0 zQa5?#t9{XDW39?@+N^7CshHxMKl?{l+*)!(U`^u2aq{ev^G`@5&fdCdLgKhe9yhd| z<=$08coVXpZDX2W2J_MF<~TJryBiHhP+)XFe2YDhVzKSo#dds#NGE2Q=0OjE_N=V4rvvK3KrOu)GA%8s%6P*!J-7(^u%91uK?8QV3SDsB z&5!2%XP5HbSINrxyc!AZflBVM7;+Wwseqx?A~h{D!;_ui&GfoU!cN}WaU=oUIdC7d@QwOA9$B$`>C*2_`JeHf z@h)hb%V$hq0xfPVdwCJwwWBr^Jf~VA5QrD`qns z-mD$)fz8Q;ecx6L701Uy7O*?{+TlIk-Bw|S~a=#k{RcnH2J}Con7&h)W*egT9#)e z_DZiF;`&YuF=Q3j&$8+!)H4wGH56{?`K@@I+8e={2g0}Xe2q_f6v6*9J*pM@VRBj4 zTCK~X^%G@5UJbW?wnFbtepc`KQbZ$c*nd$c;9U*%q8ZVzU1V1qRUA%B#Z;jknW2_I zjxdK<3Qer4yp)zTULv5Yl(jU~`|7l{0b^Zeh2jKVw)H&_Y-tQMAkZNeZIM1C!5GY1 zXHFcoc+yEdB{Ki;)|Q^&m@y0cR?lmgUtA_bsYgO_hezjZsd#1R3aZrz+Vn zqoZbaV_%n)B~_iH3d>@7B@5fS=Tww7%8GDPtgxyg_gVwK>y#8DaXDr12NGlKxUW2R zuv_xjy_*ET5cDH>6#4?-pyM9sC&yzwamww-TkFZtGTx!sJiF{a*kv%>EaT_8jGuEE zh&OJ8?~!QUh4V2}@g|Ph;?{T|URRr&lj15L9JWhk52<4Ah4=8?VD_+lQ@m|5^$ z7M$i!5>FA&Xnt5a2!AI|7us+epZ0iwgBSTZtP?FL?`d^Cbhxv$w04(7eO_$fQ^6Bt z95s6Kj9HWR6VAk4J_DpNZlZOirPE#^lftc-Kwsu>%dck${0zq3@!nhQc|sg(@wCB4r{*Y z0b>OOrqhBd$*~jY69OyXCM=du-Tl4qO+N8N6s_w1(Uy2q=C}Q$3&o?&+(sTl_)9Q$ zmaH$ryr#BCaJG%$98*B};GJZ+wFVSHo+raCd0qrroea0^bM>%=(0dyF7H!ZGc!t?Z zUdXiZ3!k^lOSFMLWty+p^@%TJ!nu0&45!gKc}mxWbxSjjUoWpFGgjgUGw04dOkSB- zw;8qchRd)+DR>hg~K#C4mGJ#NF`Q5ji^mtpnmw(^)fY>o*&8NQVJ5W$-gi05|7f0}Ug zEeZcE_!I}qA-Vvc=mP)XARn7u7wzA5neWS&lK6u33_Uj1{$11BgriJL|FCIo^Azph z^>|n@$Oq&6)+~Qu|4uwGD6T@8`*)0CeSaDAMG4JyxLDv0g#6Rq1xuoVFxaRU7v%(f zkQ)eLBbPMxCj+6&P-2D-m3+9VE~eds(PCHD3Tx)Tn7MA|+MEGLVd0npIdm$aM!V1X zHoe|6axx1nZi6yOw2kJ|y& zJZO-~+<5P~sW)CV=^2OGk|>tD4qONn3E!WPZ)09*JG>Kic=&Gs$2Q40)r=P)^Rbq_ zu*17OrX8LOrE;T&aooyr6IKRRwRNn>U$+4iIzpb1I7j~Mz@_r4#F0!3u7~-NV(mz8 zerH(tUOG<>+G%`}0~-HLoZ=sV)B3F8Z{eg7^`ipGbD&3tfbSyR<-l&hCJ8u#P5VGZ zu$jl>;l;yiGz44V%8Zt|DsKH*@0n+G-*8*sd1vF?R{eYLC7w$B@pF3PXaUN78T3gN z(Rdie(yR0aI}A{2sz~*~Pa3FdXwszi?|g9PHRtu*_L)2h?>ekWobwsAoy%exykgSe zb?(zpxFtU|oM@o&Em=4e-|`U@ko}ltq{iQA#$^q~w`8P-lPm!Ka5B8-W2epSJhH2;#yM$Xq$@8PUr{^PF{*Leyk+ult55#8d)B13+9_qu z>0NobHSxs!@62}9H}qnhDTd*G*f%41%LK`fC(YOy4Zp~S|Bd!;7QQjcghv&_!Q)TD zCVZy05uR!7>FO|V?#CNcb=d=cmv1WTiDAn^JB=>8EUVrZA-_)3^`16RFX8%?rz>!>a#lPiQj8eMe3|N~&Q@uu*l{3)1I9nhZL;Uq3Y7*I_a>Yf zOigp7u3-}^cIidE=`gSwnC*Ow0)$)&*;ovnQ_F-pdM-^Fr+%9UNk}va98_@)`ygT! z{ARU~G&sC5s8%|>@n6*XUsedYGC%IAtgLLPtS|B6T@DdnG@9Wmta9jhRX^)zjV%pL zI>WI(wt#^}&ksAJEoEkBXTp;aE32HDaj>*mz9zWM6ga*4nc2abU?jhO++eW-IaI9$ z4~SyP62x~~y!D)XZvbvwX~Lt5;VfhRCEf*JJLL9v2lMqW?J}N+ZvlKQ(lLME2mizn z$J{J#*WN^51~&WI^a7-4A)-w}+J_Jy3{KZz?EJ|A85@y?A7zHMSvc0fR3Oz7D21x1 zH!Fj^1)1b6D5R*OD&a^Mj>8FEq^F4Vufv`n57*R`;c!4rb4^oSZP}=@QB@UaYH2j; zK_H87q-D5glYk4t9!6^#96v%qXy1vmPtdH(upN@xXy9yaV#T|mcid4&#wtb?MXRQk zbY#2RLZ?67T3eGJt?rIZ@VUp7z;{&|t6Wexdt9U>qi4+2KF5S{Rn;xUg@wg`iMeXq zyGtvZO2gq&Sr+e_)b-}!a~u_o;eR_M7p2DE+@vcoUJ;_e*?{-8h} zJ;Mqy-(V|oIM~`*gi}ovQ4P--hryXkZt)N#6z*qCV~LYyu3;T}`i9ct-&_z)2pkVX zWKlR!RFH=z`w;$?${YJKmlcc0#&)=i$xmWMI5fQ8os(OG_^3c{+1%C5^Xf)Tnb6kS zy)~mWGDclP`~AJ8)hBMAv9Tq7_!0Bwuk8B`(a5&LP@Dx$i^toABNaa-;eZ}uhg^_& zlISXjT;$|X&_zx(ClV@13cX1Cn$Qc&E=&&Ypc* zsu>Lpvuk9L3-R%=8<5Q4b}|p9QoFGpBdW%R!yhR%{8{BO`_;ky8Z8<`i?}@=XoyD8 zFJ}t;FGd#T^9bnAhdPBv{Q7N9;T;k`eyCG;$xqm)@J5T8SPXXIN<00|C1&!gkXmHdsjcbU;bb#3NU($K&s@R<3`pR!b zYqjm1yeke|&|3E`C~osO^h}e-pTWEK26+Dx8~!-&u@v@LpUFQ$F6ZMM+S(mC>84yJ zgSIV6*_J~z#SuA@%e`i^R-ER|jk2>g3a zQFeAwPOPeH`OI0%=TD!vhIGX8{!b8#cq1yWpflItIoJ^3<@LI=vqofQqzfD%*I{i% zd^p1nYvYDj6qM_RBN&gu+RSDLV;q`+K;Iw&GdNhoS_JeAdw4}h`=W*!b;V7md)C%A z&#En3?>_vPb<0|&&1{ZzmY3p?|Je28$8DO>*w{Cv4!0Lt-)I5k(gxFZ8Q!T+U zX>+8a2VwA75t^y^l507yyqxxzirijwB6>FF|Mio-o7Bz^vrhRQ5w(DOuHO+`avV{Ll#(S7D1 zI(@DKcLaSI!LryW^%Gxq+K8>$)xJ>J>pyVa>S?o<&znIbN@Geq`w3_g6{E$8JA!G@ z_AUQ<3``1#3j)bkL$?P_Qd8Mir?q^!1caiq>-q7@Ln1=a(8@WhBZU-{>48UPbV*Gq zf+oDNQeQTy$1AO$on{~&6}PdaTDuCw_1!VAIKoqserTBRnsEGD*? zdt2K);_m7LpA2v{&F4|rH`Bb0NIH{Ew&#AH=cC}iNQP_w1?`nS!(Qn*#3<#HhT);8 z1la>3AS5G2hh!{4K}8%HZmCopIGyj$&kf=^Z^T!sC$G7JW890$HAU;XmsL#HttYiE z={RhSO8nk2cl?x^^7hp$$2HaWq@`~?rguZz(d#k-bLKXbb<2WyZ}%iqk4L+Bt@d+2 zp0xLr1czNfYWUyGvq#(yMQgZ*Kg(}gq%$BLj6Xs&!IQo}o)_>V>E}~U{q_YQ_J`{p_E~HqQXDIWX#+*^#KXpV{mHCJ@z=(OdX;n ztU7@QdMb~u%B!u;i53?}bE<0-FYpkzWR@rUgXPvuJpREU<4)_89>-@)8zGJFhh$S> zcvLZ*Z7OFR+NNUS?o@@$L&;^aO~on;I|^-6Ie8eH%4hOfvZ+|OG@nrKi|XuQY%1VQ zvZ)Ng{SZ8QuuUbIzsaV;IzMbGGm`k9=CP*3Urk%P=5t!FHT-e;g^3S466Uc5=RQLo zXnfjtY5XS!%_G=aO!$kYF0AokYcb(351L=FwV3c1lE&qJW10n&4;aL4q`fM&_d`tg6`4sl%K2asQ;}eQuxu(Dg9V@A!W?Of8> zye!@|t*3KT%@Occ`%`P%<8`U!lPW9Q(<1HB*6`T%lP0YjJ7)2mzU8GwopORy2*Mdn zGFsCZb_bJ|&zpPU7VM+`#C@}5v7{{&-b6r=YIM|AZ)zcc1|peY3Fi`Cbg>P?D;wxMAfL#A`zk-4XTC>h z&A~V4$J3(}41m}3z@{P1xFrfgG(!S7)FJ3#kk#0>t_uZZuBd6a(Z)2AejYN<>y->2%386x!fG{2nZz5Z1Af`@r*=#zy57wnF(Nxs0LxW` z<`9n;WN<{*V*HdkkcJN%PI>^r4fU*f{#WDEx}xFF;7#i`zEP#&kXwJ3d;34rXBYZE zBM!IAB$=z9eO%(OwGAhksNpYAneb0dMY0__SdJ)*mpCcVg?wvJI?ccZGKR1Q?u&ZM zvg!1sdD>gsjwpx+1O|QG(W!k-BY#%qxSsBsSh%yITU(1ht*l?TBJqy&PV9`1t4w@> zI;mo+Gb}pdt%aC=9DxbzP^!aN4J`$=L!DWYhLdka)=6+Y6Bb4KJb2Wbj))t`jpW)n z@W#|x@kS{$Pp-CFGL@Qk+G%K?K9*b(U7VN>k#IFdj?*2*B!QodSFbix7$zW32g{AL(f>demK6G=M-$UmEDML5 zrOXS(`5-Bv3Wjp4) z{*5<$65adIcWQ?P=a@n)>e$iB2f2NII)flm5IcZ4fi;$RvYJYP1$aqIdOEy7{&Zh< zmi4k0a^}N$U^2N%ng^3MZ`g5dZ6ul#ERE)x-H@K*oSfoFFc_gWVg4t#iRCBs9WVI$ zcC$?t;%7T@f(nr|(Amgw)kZDjqX&WBOtu-(F$AzlYHHddC>NGI&7cPNVk4ZsWQ0sl zFHVO-*D}y6den+|{l+Kf$6+*}t_Lat<#?eVVi^V#A!`s3l7)eyL$E>G+cq(Y5J~18 z*KyX?nWi+@HTX=nNv0&JP%+G*Y^L3+LA0}CJ}rK8i^g~zSqpKT2(K##Ll5+QDD$}m zz1l^emWCmS4r!CfqVw4e{=Rei`p$Ulh$GH!|407#eCcdYyj(n~=+k!ab_~4pBKAQg z;^G~tnBTT{%#Upkjx$2q;Pg(f3-8dScreo|M!%3n5*6=OVAB>hLIVioM?S>U@k2jI z-meT_6e>$el%sQ@vp^v*or?i_8HKI24CG?tv+MIiqnsmJBcn#C@c}=6Ghl+haAc8h zynJ+2Zc(f-R64FPag&_b7Kt?FA*iUcwJg6boOlZKkAZie=RM#N=#RN(fhJQxlivdF z6Q|yV{zJXe4nq$VEFF>)v@|S}9&p-$9tY`PxJ$$s?m2$J#4wM548H>nqL$jmBp9SL zP3g{`T{Ctn6t<$Sif;MntjhXD%eAsrGOhy38p{gq1M38fxNlF=qK`gnIO(q%{;aw0 zp90xJeYW7c%=Z*&d|LN4{^RDpdct-xZ)qjZPql0Z8VuyyU8A8j>Vmd_UW3o&r2FSCCXrfcmn(Mky!bY&c6?F z6l7-$kzJTw5cC5vYp|ohG&0~Mv9+lRG~Bv7DBTjZW+6m=+ydmO2Jzg!N;| zn6Q4#n3`D)Gghpa(J-rq+Q;o+9$Jld4a8q*IO(SvZt15QPI`fcTQNoi=XfQClWk3( zH6JcssSJGe1nU-5#=-GQTnEJ~*<~F%UP3?s=ysC zUWsvSx*V);>-)4$YdTo*O1chd*J0_~FQ6{cw-GyH>)S(dS+7QWNbeo0n={S8FIIil z9%gVm5l>~tL1{SY-nxCPds}>m_vO*~!PR#K$tncNM-vS*Ng8Le7KaJRk%nXt7I1scn92RCqYAeU64(`y5>k?Q=Ao_c<0m+cpVKc2IK;VN8eGHo1&paH$W2 zaCywjSk{oAvSi4)b{4!=Kz(UNY`Amn=D4xIamCmHV2zxtsN%!zlhd@&K{~O0CuEK( zeSG#_X-tPLg{E)G`^BspbM&A`miIphKdKACF!X!l@%Y%xH26(ROG4Pp`6PVzMh~3; znBtdsVY%&5rISNeJZw=>QCU%}q#{PaAVJFvYdKJzur#^&fID`4!2r2a>5zMObx+62 z_E>poQ&~^TqPF^~@*TPPC0(VF3&)m}USAk4oi+KW?oDy`q{68axAbo82=vHDM^w+K zn^rfbvafM=UE*~Fv(q^1_S4>co5^<=H)}ju_UmyT#4iYr*5e;l49C2u*w;Jsp8O7L zPp-##+CjJ^R~T1qAA%b`2$%S1P`RMF6Ta*B5pR=^J$0kFB%YmtALlB~Q4S*)+92%U z+Im@%0aK)bw`bv~y3=VXf*2pt8}XwH=JB%{8x2NU3k#aQl^ycH{CFrDB`0{aG1@SC zQ~^wK;Sl-oquxkKWCT`dM;Qi9?@@xhU-w68#U5}6`e97u*Gbu{UxOT7U)4JHJM$)t ziIjE5yWfz(s>s-Aw6VJb#~erZ6&mSj1-|l@hMLTZ@(tTg*<4ezXhCOpZ(*#eSw<#z zc6YrIa0fdkv~{L%K3+BUDglG`?MY}+K&)sFprZpJ$$)dS6& zF@N5Qr3)6oMMdqa1by$tdzwAagO={d5@w7DMb#30Ee0A`hmz<^28Y4))q>1K(ey3! z7L>rV&My%yg;OTkosQjb48kC)UnDAz_4kZwoLk?#ux0dk&%`N{CmlCt+N6%DWz}Oh zbWh#TRuP+U;F6t-mi%JDcMe+yT}hmP_i8-`J=lYH9u$bvaUWleZR@<(0(Cr}ipu-{`5exh|zL_zfaQ$$U@A)RvFsV{=B;*LRV)MTlvN zX^Y`LvL18+HP(*LUrv2zU%7@qE8l0CcLMepM3acr@Lejv@E*n|8A!NBanPh3Jj_@TQw?Em+FDzKYUf zGL5Iuu(!1E=qH_LMp#J$0xhuT54f6}Y9lbAm9pi`iMPqhx1(*1)1~zER2O0UDg7n6 z#T~J!s-(c>b2&p4-;!AGRLY58ELG>n;!{qmX~@ZIl`_Xy($oBLVnnpekSP$TDX9;T zIgegkVA6Cw(}MH@O(RP$I01WzWE$*NFlB(xq9&iw8z@V}uj83{QaDQ@{v&uYMc+8DFmQ)q=YlARbVQ5K;2l;Weecd~7D^0I|nX27;uG`_95 zvwqyKzB8-y@MwkfPcN@(X`Q+hzu}lgESh*LO&eW59$VKrYsy3^%gf6mg)x8KqNdJh zX}t`W!k1j>?{hj3go+9HHyE^`VjIvQxNkiFt~BSL)-4!rl!J!|9@VkX&>_MC`ds=2iauCa*nIdm&-HV6h3)+Y4 zF}3#LZIHh-7c4lxm(?0;_CaX;$88>1ItYJfGTg?eJvw*>@}HlB4aj<*L6z7T&nkg1 zCU-qaIMN(BauACyIE1YZa@`9lPB@H6C@<)#(wG_=%p&(e7B#wj%IdXhB(yUYL zOiW^>>Z57&noNA&b>JLaqF1YPGpEw5GOb@}o@ZecTUml&j{Q$-mBSqSwUU#9uVEFW zJ!LMw2ixQYG#BE*3o3MC6g*hlGVDKYWe83j3v7xb#uT=GF7^SttX(jm&vRdk3Y%mcx-a{VVttm5qUW^{e> zb<*|8?Nsztag5{N;kOthUlXi_e`CMrD)FLYrB+hNZp$#&Sm2 ziD#Z&w{%!FEL6q11g?%~Ma9N66ZY*??oj9%ZdVjr73I6d5Q@2*vZ+|7`w-bR~dAzq3Y$*{>y zuaLHs{1{~Z<8qOeGDph}R>+EB1LM(=(@02;5i<8LT%6j{XdLoohF2~fK zI=#j8&d*r9{6nmD{vM7?zKw3q$j|b1!Wr`Q3eJ$P6Hbt?S8&4IcDSw@<7mNPp2Ii} z&yRrtC_x%{aaDedLX7fbNG{~ZX!+b7OU^iD;q~_{-+#tCZ{K_G8^3<|VL-qMyZ%#X z(*uqNaS)}H&+D-2V7{m9)8azjEJyFcW;D%RQc%|eZ4s$oAs9<4T4~d~b%DogxS4uQ7W)8a}DaTV2pXzZs)a4gNZH>lE9-jZ4SUOn#dZ{bc%9zR6 z-ou_RU;D(!g7~3day1;6k9c1>e4kU``x}m9-sOGe z@8-H4`FcgSBVQ*N(RHXj@RwjgIh?4k^7R=TdklE|DqR2n1AlPlGR6k;>9(Okl{BbP zImVjxyZ6k;A7F0!fjeG>EzX17A#N>lhtL6;9g50Lg7zQ^jUvV9I70mc{g| z(rLFl-Ii`^&9-DH1URZVPa7$d+WgMbE+xY&=Yc8CHq;J*8Tnq}%)uI&-Rtnj&pdM^ z7F*am5^q(!X?7xp4F1KM#K*?PA!aw13Q;&rva7n_Roy&M@wD@35Gjk`m!JRio7nil@kGb^HePb&;`J7a{YQJ6-Z%4Lsb%_^iuhmYt0FIcq?wIjEuHm5_Mo zzTq#Mg28y7KiIZ%w68zY*^#sQi-nNyROfRV#+qIngBYaGoEICr1;dmC2DrydCLTA$i`1fe7hk z?fKQ&Eh{@$rn?5Zx<^O5x&}X=$C;4O+Q~joz!H2f5k9Bfu?Bc^Y9BQlkxU}n%otQE zl%J`CAzKs6u@L({j)y{o%EBXkBgOUw68XXOF_BtE)(v{$)%RL4Z`yckG9ofmF zA**7xWvjPu+puBV_G62s+E+}uV6h~<;vW=GrL!I=iF)tlXAHD4Yc_Z29~bZIN|xVS zi(v}%Cxr|x8sZ_*CFst%8EpC#B0vO*)o}cY1NkQr3zD<^`+oBmT18JJw(8K;xXTdL zL1zb@3VswGI}s2NC(Rs-u?+S|6GHH@sm%pV*s_VtOKgT-W9CRtiFnwvDe4hs}8KRG6f z0wN>W4>5N-tkkwrivf?5=|iMtg)f)|I)#*07S}A|$PgUB1Azc1%TR7;EaMJFzWE_X zBxhlL<@cDkd+E~h-@bNpj@?<_+I`b2%tw2iWRkC8KZHSjn@P78L@%0?-bTF+5g77; zg`h|rbe!*+1v;#OumD>eoG8dgisbX<6QS?2i1_ zp~c-P=7{+M!#U;{i*G~D4N3q#+UIX;2n{;KrmhIPHn*XtcVoxxmz{Lm(qbE{w& z2*vkN{taY(PA(d58SuA6%(~R_4(!JzSQiH|Ut019r#y6gM+3Pj8xcVOHj9N0G_u+# z^PH#(y|=UcXSU^>i6uB)#yl7+|4SKWOPc#0!0buz_8w>eT7`~MTXV#!;kc`U+Z$)c z@JFM95N(ZS;kbZM_U4%wemUq#*&E6tAl#*pA{YN2BTV4TeQQl-xY*pjddcz?`E-7; zWlb@kZEtC5&37jH#(Oi}0|i}bvL(N<-nk?_v}VOXdYRj^A)j5GW)}@1Mm`rw=6&UV z?QO{{ve_n6EhQRD4g51{{!hi+0aFPrI6-I?oid7(Bh<~cq6SJ%N;v`xChU?LwjcRm z1Pb{lj39^X&sx{XgcnpSkxHh#ac?XXpd}HGMksP$UJ_E3q{lT3GTE{sbCWXWoQ5z2 zY^tqmT29)2OgD&(BNZ54D0#Z0xfLzh<*n(WtERiVy>Bpk8m$X>DW5uY=AtK_5~0>) z9>;}Sov!SvLUBV!`7w9nNqZ&-x<+4+fFa<4B399z|Dm&lZU=Qa7F4EKDix_5p1g+3 zJr#Nv6$7TQsNqaC#$!yuFjl#AF)Iig0$J--~xQ?aUVt#vokcYRhYEfi6+_` z?DciFI;%_*we!$K;SBcdi+)? z@KVX}!u^w{-eWhib8sK;EWc0pRZo=PpT55yZ~inp7x$t0ovNYx;uBLf)9^kA@VA4MS04G-VSN>l|5F z=pJ7>7|6Q(%o`5o)RFKQ$mS7>Vv!OO=8F`5E%L#T1zNE*z0PTxLky-g?Y7W6Fy2>8 zdYQG!8H(2UQkmX?mLY`71pOXI*l;PtViN)N!gXtQeJqy4iZ*xk21Yyb3$N2hn`41s z&}qL8Y#f*~6^4a9TfuRnlKZ!UTSP@C_Q8ApP3-wy%AS8m+4JvwxIK^eSN6Q9*f%Qs zNM!%T`&U_eqN3BQtRqp$V^EnFQL%lWDf|WThUa5ZA>IM)pDi3>&Ei@34IGExd-3~f zexGm{a2D#{$bSQ8;rDvn|1_^pI12s4{R{YSBmaFCexJ+hL-fP_Yf=9cejo1s_>&QI}k!ED3BHu7sWn^?1<+dnd}hVQDCVNidA%nglWJ|=;3sSOV0FDCOP<7 zMP1ORLKSs6G)-OD^t{A{iIO4ia&VnKJl5F)87>asAg&D=<`x(df_n)lHt-OTh`6hQ zpj=(|SRxa%)IhnwAPUb2aP!D-5aDQpv8&7PE?FM;)%!YYka~>dx0HKXuB$)N*{-iQ zd;t<1>n-I6CJL@bue~+|g%!zf@k}Y_FSf;NZTcDwdW89{tS92$T7?}Y3-U6lYT}r- zR4t(i(()+k2U&!}c%TU;N5ZjbRKcg)3tU1+tW!3N!|&6H5TVxgNJLYsoCpEb0dbF8 z{$)bfL9Q0$Az3Bv5igeq;bSMq;=}P{ig0+!#3Gq&OzV^k@ldG{i?^@nM7F~FDsC_A zTiIZs;2&vW0dJt8$uAaIV?!>R>TORoC5@Xa?kyziRrU_adaFvh8Cg#rvU8Tm*TY3V zQb(0$->AXK-&{|{{YR?ALCj|aK4>7{Bo4-M9jx(N|I*gq|FsJa9n=21-{#j{MeZ?} zgUX)ua!z7r$0MP_N41<|qIvxdCRv44o zX)?ws7XktyT#^(?j0t}5{EZ)iHKFKcD-IMQ<|G&dbHe_973)`=7E}VB!aJtO9tSQw_ z^DgpxTxg6ho8c-JW`vk+2YSF&h!hDfE75QriNoe{H^7Yf|55JduOFJb0dE7EL^O)x zijr7UH<{4&S_!AY_eXwmkjCP=%*fMzZ36*B&7o!mp-J~He4E^1Dla~&V$r3Fm&)N zMFLdJuvKrzBM=5ic89ub73uVl386;4NDz{V2v~o+l?!F0ReeY!4BI-mY7?S_OO-Fk zPIN&D6s)4ngW`s$8L;D~MTR{=7xnIo$^K}4pdsVI>m#u>iJ~DKPkF8t4K;7EkN@88 z=_6|ypTE)HD3Kc-_+RzZSJ_XMu{prOZsem|p^VOr z6ih%vNKXq7L1K%HdT(nxuCY-!Y z0;q1DvChKzGb8J~=Ce(0ZI^GHGmM_a-WR?bS$rx?z0_|#@AsGZx-JSm!nwTPr(_x% z%#g*xIDJz@u3B`q2R4^BrqQODPNP8@2iW^-U@EHEIfOc zVN23&K=8YyTh!7b2;E4s(^Bk2*2DH(YZh-!gu;$+)Ci>_O`#S3j}THgT?USnK%xso zqv?BP7L|j%Y&6ZYhF;7>MUARH;!OvlX01kL@HGYN(_U|~ljUGhDw^@$X}Gi} zMXRdbi=zQqTx4#G)fe@(wS}EQsV3%$<&}A_0vPz!GpGIwI*+r#&m-ky`R}rfUjF+u zKmURE75-MF=K;MLe%D3k(vw2M4rB;cR>ihf@Ma^SX4*p)T#37DrctJfEdj=y=QwG? zn2I91S_sU21KN}UXjLMKWH^ki^cPa35NBIhI6N>pIWSB*f9l(`e#a%?9LlQqE4UgV zAM?Dn+GwimLaq>;`&#v3ujTNSbxNX$@zU>UctMQ~gi!e{=X}0q;$RaX)&zP?IHL+; zpx{6nJp7u6WfNe%)A^wfuTKyT(+F!S1}(}Xjd}!Xe8`3`0yPXoUIeaP~kKu19m9H z@SiJ|laOH*%*7}37d%k^dGliHXX@|YUpV2M)TcWJCLeleytnq?4uHXS+!Eu-Ee%*v8n*M~Fcae)bI=sIOj5O^vRm?gqwV5&|$@oG>`yfKq; zgSU0R@G4uy_Cfvk0c}+S^QUdOV5qGv^lED`)Y=vdwo<=D;Stz1lI)9UOIf4XIlxJ> zP(WJ*8P4Zfqt(3>tG3DO;gH)M4!b?*xjP*4u=b$K9Spi%!Sc6|HH3fTW4LQ-Eo&2Y z!n$ZG)gcozp8)s~KtIUWd~9aIrMnsy%+#PoMa$54c?+ z8eL^yNI;Twl4WUuPS z*BjiqhPIFlS8;hLwz7Qo3qcNL7j#&d!>>#|i1)0<_-#VAl*SAKDoogk>BEaw(JYc^ zs}h&+nZ${=Nc}?VjSLgjLcGgnC;gC(Nti--FuUs;a_$C?xH{ybNp%NbD4)GD7GhW9 zwOG^7P3>okao)E{2$q`A4>0#wI3U7&ZIetXsFpY_b~9NzT{0c&s3bsq&Yw&KqKLul zjZU-#0@;pmUnJZM7!gwv4P32 z{3Fl#LP(MdaP;P-Ams`uQizurE;6HYucO+tOs5MC z79TbR6d%#{_Lbws;c%cWmUw2m^XMG)|0481f$?SpWTNx?AhDtUpgNE-_hYkzDZ|po zU{vB7+Gm@=XsMRsIE!=`%E|ecedTC4Qq_Q4B8wE_F=SOKF~T^$Ppxz|bj0C^iAqm@ zd(;787@*6SK&yEo&4ZYE6yp*z6S$5!ei_$;xL#YCV{Tb-a`Qv2P|JM~82T%P2t%?U z653~x)n2@L<&u_cHobHe0Ksu9$LU@nQHpw*IEI}m!pbsDc0iUn*+IH6S~)dy2#@M8 z99%){3#xE&6>=Xu3@u|UhtWnZ({!_9Ptq&iE!&zxQwQ10Q@_RXi*n9crj6C+BY*{X z1g2V9N^!wh8)6SG?d|1otk6|a3~=}*gvUYSSN?Zs{NuA|Jm%_j%*hUUuD-zMYFM7D z7un6MAJ^*yOUa0{dx?f&rXobexG!+KV(ER+q)Rzmt`NI7GaL@*8$7`Yc!UTYQ(M?g z;<-YA>=kZI2o`c>{VGw3{M27LJ&s3B`#d1{6M=AmyAjkx5z#;~B|Oq8)}Di87Ie7m z$h#@QfQ(-(wlS@$NoDM|mW=pV|MCsunpLW<;avDLzR8ho{jHt3&hal7caNSFZ9H$~ zP)Dact~~m6bERKeIyjtXzD2A*PbsFH4P8mwGu&ZDi61 zs2rI3BKy2}2WaW2%;iHd3uGaxw$P#`#jKMQB9vc>_!sD306}a9IKlF2ix8NDdou^g zx-3{naQryHd~dwDPW(&Hq5VipR`s4f`8`&e5+;xv7x(LM5~u4Xbcwgaw*)s=DP3T~ zqIHjf5O8{@4v?E8yre!~e*4Wgm~UNCyr4&h@7eI7z60yV1Xq`%*lJd+0hq0@AGTd-hE+|#-oE@;IG)R7)OPNJi?x{VZZ6vTg)>w zM9+=kxd-`kZqhyh_VRPM#THr2BHZlBe`SpAD&NG8#Ur?3$1`tz*mv34^ZZ@dca=Vh zA`QCw-5;XQ=s~5ON}ols^nc$D@amAz%f1fgM+5z{_EDXLH30wKSO-Z8F8Sl_J8p`d zb^evN--y4x_qwi2eEf>I{DJ4k@%(qDpVvzuTJ${08xEKJadQ0pvtl>8>F-Uw@hd)_ zxXyL2{DJXbgXh=q=aq8_Bl1gG3xBKfJ`8bA-5Sy-Derl*x@q*O7kv25oDUBPwvsty z5i!MB)TcFhn?>NU!?|%Hh!hYz&7w-Z-mt#TU<%YX#&>_o8eCNEnQ4d!I;?yxjh2^2i?kCn9REJSXD26RrOum|A9TCxqJ+L8sdF&mTU-`6UO-rVV(gM zabBprnb;j_h{`iDGj3UMg2kB$ccINT-X>_5B7b3YTmmE7HG763fypxr35;Dy(>Oh? zB!W?U)AF3TWIztq4-)9~IujO5AjAbaZJpL&H+zEVC|au#5>rpISH&;D0uhFWD=mzd zY{^7C;P*Bnzn=+*DIgUHWu^*1FfilY5UY9iNG0Kn8Pg~pkgv<9n?H(}TXOXZa}L?z&mz}<**tN#l!<_B@ zZ%y`;#=)a$B$Zhs7NbqUY@4dDWW3|5n{G0+Wz_@ijVzs1ANu>}u7LK>c+m;>YE)7k z{HpMe&Lw&rxjpwHTLWbz$OyY|23|1G;Bq3o5pLIrqNyQY{yMI1fn^U`!u=SE!1jU} z$43q1T$RdBNn{#9<0|*rg>!Dp(T_gZk6c#df<-C=Y!X=Tq>$|4kVmK&G=V&eK@16x zFnF6$bc7XqihLfHMQdy~kFM<8+GlPxB}biU=1P0h8slfOi44RwW`sW$osG2xXJfJ5 zr@PKxKDfIio^raB45XN^NnO5+owM4(mZ(yTvL{py%2H3yZ!^45JB2=BK)9gf^?Mss zlB%z_rz4Yyhl23h*GOiAURxt@&vlyXAXk>qNNXUVND{YNHqO|ZaFBgNDTv#ungepx z`76N-Ul6h_@UuwTEit<_Qj5@Wd2J{VlEH)}lF#J`P81ZSER+hcuu=fBXiR0DUN8N_ zj`pUNJ-$Y3#9=ggB2B3*8$F?~)(|neHRgytU*E&5w)5?_2RzO*7Oh)ty1-;S4?#eD zA+;sou%6QlEmrF`oEpFI*i*0SYsqO;J(K>vMi#b}Lz;ix7(%G+WoQ0}j{Q=rDo5Bw z(+f*P=KMS1?`VQh8WR?jMvZk^D8vOD{Asa7>Ya9&x>XAe5^zU@LuACMa$P8RBT~@e z0}46_*KA2+BORL*OC?AjQQ(={L7$+OP%>tbQj9MojwE1*BVWbg&PH!B+?i<0COX5N zK5rq6NV2wM;amB9OD3PsJX?&n=3>QAr?(M8ak8x~fe){*b6HoixvQ%=3GZoeB0WLcKkH&xsIOhbekm!rzi|P3F zp5bPwQCcULPOcd#!F0c&vl)&>!QNvQB_`Xtnp=)JzR*%IN(*{Ki`b$4NyCDCVX3YM z{$zS}R5z3t&8oQmTi(&0=vbSkUmQ`Vnu_7_ue!paX6v))IL1AIZa*oW2MUNhHmre8 z!;()HqG4v?c@KXYezcH*;`f=Z7FrgK#$I*Ekf77)HyD{7_6|xSM4JV9a&bTr6Zg-4 zUJ!M)um~OXaf+Q%te1s=4Qf;kDyAz{OdQsJDisbNTv*9py zW-gm+Yo&val5xNUVMcWLk%bPPWR4F${iymVC<^UhPvQV1by2Od7J{*n=G0g$QZU_m zYoA87E7OKo<=ZY|17a~>{+!KYFnDafP-<;o|LSc08$@OBmmYYxGl1HLyJVgK~jk9lZ4OTJ>S@6 zh>JA^L(X}@(&ZfORKu7w50U7hMk-j~(i1@pdiOpK(%GIMsFH@%dr3#o=7C zxXaKO>sr1h(U!w_tjM4LmiRfq`eP-V4G5#r4y_95&lr;gI)IQ=aDFnff$=~F32veV z8R9!n+N4E#ihu;%>%$bo>Qw{H<8)QXT$Kvk=1P1XCNzZjr&>BnR8F^=FHjT|SW6mi zP#G@e!%%5Lxj;f?u$mhqYUf$!O4_Bv!>ItI(u6yn)M(B*-)%kr!BC0W>P7}xbMe@1 z$8`+qT;9@}<;C*Lw85A_a>}EK5wHlYr3`+Pi_>;)!r<-~sH5G0gmlSyAPg zDSrbCaBmRw1Y85UXbG@K%WuB>?p0Tf7K?1Bn0uyoXYtx=i|j5m1I8l+-sO4lE={2C z$CPX#x6Nv*11b(Sc+{|1BMwd8o^JF5d<)EgaKa&P0S-$}!Q7B+EBs64zRbrwZt;06 z7K%4U7zXEJI-(F86*h)eNz8(u=q6&j35&7kSFA0rKc023Yb_Rzad&ET?pUxV(|>;X z_K~j6u|jW6?||j*^G>{CP@BzY3e82WIT%tODysw6_pZLg#{Opb_$f3lCK%yg`D?6s zH))vRl?dq?=$GOiXt85lJBTdXCx|meQk#hjY5hLIay+TLPDF>r;@1&jh z^)auvPGqwyks_$Z(@_8V*S=B1dh+Em8_WHPcHSRYSp6p$h#?EcU^jdXe3K~d;QfK1 zk30Z-!6V{SF+~e&#kqR5T1lfegQzVe(nh-w?2{aOBW%Qz=o|ie1#hE=YuqDn;tg4l zE}K$jQ|eKqg-6b8D$rrDr#^Kyh2AV%Ui%dOZ9ehePdSabyuIE|l)EK|X3t>nU?<8S zCa7`1sSSVr337S8z@-Q9FX#s4?Ut1Xm7_5nF$Y4@`4Q?ov2_f80U9Kw0Qr$QQ+DFI zp=@EOJtIx7T0SK9p0cZaJKMHkWo>yK`=V~eX!%LKRmFB}Jyo}21-)AZotMDg1x#Fc zyO+mU;Rvz@+Ojx;Oj4=!*qZ{Jl6H{SjUD;xxyBrjfD(Ip(bv(U$84#TQ*bOf8^y=a zyyXJ9n5`qk-tBL_{KTF@q4%bhBN|nI`KA>se%r-t$S%T5jwQ;kvjjgJ@y_z&w16Zb z58v%yqi;H7#9J*K3wM_7!ttyqlk)l)=v$O&g-Ir9x2{=K>Qw8cddBqXg`j4He5z|0 z=hUd&G|=O7Zc=guqN1%P260lW5_aMcWUWyrN_wqCcub`8JFxQs3Z$af>t$V=m)kt) z#rf$61*4=juA#?~j^_iPE*1EL2D}m40;6Ow?!?(BAL2>S!C;+!`O<~seLea1mP|T@ zu*RSlXW>~)dTX6EUUjrX0OHw)WNP> zTmG)NcU5W6@rnF%zYKe0S%c~V>mtlyF_?`;m(CRS1)@$* zoOvwnI#R`-g0^OaEv1d{HG+u)PIhbocps@w zBMNY}*bcLg+6XEK7J0jcnLrgx(C6v(6&BM)L?WIztuOCtIG`~|#`wBy7UKkGk8!?? zaXLY13Z-^&7LskM?-@@KlW)N*xaDO{|cgM{@=@y{VB$G7l&7!up2ACdm3#TWb z6pa8M%v#DTCX$*OL$Bn2(oW`DL*aHx^KNlaf-5i^G%FoBu6yNd7BN}i{LyWiQt%=W z7ZgrQ=1HVjUyEt5U20jDohYOn$wZ<}d%lv}dsBY%X@BCc1%TblS%Dq|IP}Di&*YHs1ErDC+?bkPR(ZX{~yHok?Gd zh$f?<2JRJvz$t8c8i5Ofzlo4I@V*QEo-sz6X?22yQvU16A=d`PSr`7QLb(`hlrC5! z-(8e&4|k2!C&mVv(eoaAZ#vwOd^%>^WcR0lMjdR*Sg=*PCC_8Wj57h8G5LW~9Gw9BC9jmRybnqxlx65I#5F86X)O0qJ z#g`;HYs<0l#rQXT1^zv~_?n9rvY4aT-+A0|o&7}zTTp%<`VjK#xD+;@2;iuBL^Gr7 zJdzK`rL*iic>Yt+hHXQ<6I{d|;kwd_-vkMSq38Qb$%eM3{`6fv&B>mImc}H-tmLM4 zum@DN7;lxo-u5;&-gb*!brm>H)QQ6s`8D=kL_BLAG1p>GSTz!KX>GdtHVI6~jt zrD1!@H`IRXa}Vx0r?_+Yq0h0~S1*fyKK}8Y!#j&{@IGl)k3249%|x^W+D!|)*mH=H z(hK}_07RXx!j{Lz$1WgNQVkDhNq-f?t$0NhB)9*}0YvU)PWk@o)@_GRQN(N|y}p(BbxJ4h+c&OPtk)c*Y~`}Suq zIPaQ^K9M?bQRcvb)WsL0XMo(8a0+`6o*H^sLK=ip2@OKPz_Nk)7VJ5#8t%HdhFljR zM8J1(iP=m(4CV$ibQ*Q0I+M|W$8|`xHrsQC1V0UdShm20ep}yQu{PA(o?yGa)!1mY zG}K$!-8f{vp%I77UpX}N#d@o~p1u=qO$$E(ydOgkC1J6AVvSv(bu6m30N2RoCKs8T zP+j(yskS%qim*3Xk!@C}k!rw@qA3;5W|5(%mLXW)4k(`0+j~0zNpo7jobCc%$6C>F z{7EzgumpzU#Uu5(&XOp>II$d$}$c!)!BZMjSYO{)O^t_C-|R z*z&jRn_~|>LvoE^!5)Lf3LKpF(OSiJA-AmpW)HFTc;JhA8r7b`+s+$%AX|Qu(}uav zYGu0#f0scd0sUWyqs7zf%iHkqv)Kp6&b#fletMYZ!UBD1KVKs|WnQkSda+|wXhfV{ z2|5GMPf2o+92%ath9qfLRqAOZOpuei!&DZLyis&>HVQ2yd9(Z{yY<$mX}>)EN$MN+ z<(TkQB+m$G|i7Y~i3_!P%7u>FCOb7|yUfsKg$U?MjEb6hqv@?cA+6Cm0%E zEotGn`vtreAUcuT4t6m24iZ&M00IOMha+K9#*u@kP*DZIzEB}gT&W#mLot`L!R!v3 zTP=M!7T0a}S)5*broJu0bjaUO=cspvQ`j$cI2%0~p!r`f+|KS`>mioHrb=10U67an z^u5%RYO5wfDCiDp+h*Uukv-@l1Y3)R5^O~fO^^B9SU$vtZa=U8>sa_Ny~ z0nDZD@kGe$(lb@IIZR4KnN{T@;p2fTD<{Dqy7@w$zC%Y%>?-@sNXLSf+)&5JXnO?m zURR`JxcuXo#~TWH8{(GNLXGutn=Rq?hW@&6$&v-gup3FnV`E)0e+=pWLm^)*7!C(x zP3Wx(JmN0MNm0;CKWK;U@i2sQrw)B$gz%|P{Frr)puqwZTs1g00DXaNAclxq$}ftY z)U&-OG3hJSmonYkyAz9igP!t z=^KMhjZLY(v)04qdTs9on*Eg5Kf!2hAf!!XxrYc3|^(-pRa- z4+*t&$O_zp>lPKia%%6yzLQTpXF>mIj}48iSt4$}^!yDs1V)D9*B^JsonQFu*6X7~ zBf+b-93aFk0%{DoJ8#CkYk>=pw3Y0b%!XJfn+?2p^nvoC2cN$BI;5q!A)OI-ANqnA zJoE~RG za~t-*Lfe@mU{isLcGy%Mz#KPy|8=&ve9U*gBi`0szNLIa_cFGEttQgPggWFH{S{!L ztvu@@Mig9y>ic`v?cell*S||FR45-3R~+KJj2rppe}|k@VbXB~8XT}hC|ZDG35 zvw1a|hDgqzsj&>jqJ2izci@794O8X+n(VKsSv1UQ%lD7CjCHv}DiDc>gEx}Sf^7H#H*nn;>tr}Y_XTF*jOuOHV$trup0gN z0&l-d>Kxp?*HF`X;;JP}HLg;!bvPhOJuUhzE8NEZy(6m)mil#DjK*&F{xkfE^`F~O zxOqn|)3v@$XYc993TQ;E_|Jg(jF9Vn*x^OiFQ+q1!J%D;P&YLxtD*SSXm^2((}7}w z&^TcPH>lcVkn~6>Ka&A8JI^zmiC6Nr&-WbVSp2B(q8!8@{avN@59}T=8sv%Re;-IR(&=zs?pH>@T^C;)qg{-lbs&yiG;gD zp`LJYLHTlV@1bkhebIP48jZ(d<;h@o7&j;L;qG8tJQ_;JV(~cEpoH_8{{&s}nb?mm zZfnX3{lbLs5Mto!u~R(YLfs5U!a~bQj^(WgwbLP%p+-wojmI!GLd^p44d9{N{1&}g zRa>jxWRi?3wVKo}l*5HG_`~Z6VW@{ceb^K0N^PZ5dwV?I*huueG*MbOI^5ph-q+O` z&&As^DY9n;{dKN7r`3#p>%!JK%%Y44WlI*)3p<1=#yaz5i$>dXqhq;_;dUZTR9^37HJxQs<+}XYJ~Dlu z>V)u*Xjnqn3cRQW=iCH4rta~G-)YtepkaA#-6A|M?|&YiO1&mSoii8XvXv|Zd~;K0 z=cZzDb7$w~VrEeW|EKA1+Is4#iIY#>imUQH{EcT8r{$aJ#hIqF;(PbT&ysl{_A+?L ze#CQ}jrH*yGN*$7d5+i~CUipzd{P{NFJpxl<-xfyAX>F2ye)GG) zRpmiEj%SR}3jS2g@@HymKqCrFibY&1nB$FX`4bw}Y|eDomk-{|me~s-bNP+BgXIVD zoEgvki2VnD4(ep0E#y`M{yAV~3zc^~&c>DZD({N1pWK_CTJX2t?f=!APT*fS0 zTHl>9mtWO9!M>g?KX|aN{DwJHAo7ZFc`>f_{5hG9;h;qqJTeQe9}n$rd%*cr^J(Jx z^59j0-~A?}_)6arw(nXv(kBz}Qmu_40!>G8U{m$d>Vb zx-ho4l(FFuP0(6L06-+aBlcGLarrfDyz*-Pj9%Eyek``mj4dKDm!vAFaoop`v*m*{ zIM%Fr;+8C1cCd~GD`V5)xnHq&xNjPl2^a@rNO@l_#<~tZ9N6&34S|ObhBp3rBYTI> zqcZB#_`AD>PQb^_+du+x$Q2wmJiH9;I122^Sr5H4t3@;cS{DSMUlI2~CbxmsSxtHk z@fePfD<Xsp@^$RQ)X)F@$tTO#qAi^; zgfaa|Y0CtiEVYGkg*qfzD7F=FoDOtz($9a6r(b$0@#K>)zs&yhe8czT#>)q&>BIYF zHoz{8wsL4IMk+K*t^l{T0>DTTJ#iRJ55zkHING*7>5 zMxIF$0u!n)QGFOcI&vH)x|irxU!rHecl_M8`97iflt;^6;e$N>=gQy? zYa4ll_?VOdVI+JUUpk(iL%(P6n8M%9#b4D(Y8FbOaf?;YH({L+4g?J6wlN0>RySFV z_hvOe3**td5MwEHuz!QDV_N5f-5dM++i${_xB*foGmEUwP%HScwgkzfu0?+IN2S zE7YEW8{z+gFeD(3rn@tn3UflB{y-pbGNEWks)-z>ts!zj@q$MJ)KZ(Rt`23t@)_&2 z+Y@O|v#~zYcVr+wB0oeA%B88PzA4w}3KWhsp059i@sQUCx0Y){A9g$kGWEu0PbO}+ zcltiUcs@}GxEga96m+_A(C2^V^qD+za)P0xYmWOH?O32ShtEwlX92lyn#<7S3tUDf zvGW^M)VfoF;is!6AfB0L0{*Q6>&oXRreV&wt>iz%b-*2(N9rk8EtkpRBZDb{*r1UE zAo7%=5P36Q@GNHk(U%@h)J=}5%GatUC-u#v>At2^B$RB7t2){uDc`dLp@j=-tX9>6 z#nFNG)L`1znCOajj3#{Pp%m>Qm}%I5iVLuQHID=gc;|HLxEQ@SRQ=*yMKw>X476X@ zb_avq!IqG!zFze-{Rw3{CW74|wHbeK$1JhS@e%3{`WB6=(24O$??eZ#AVnyDH0b5~ z7+fM`TA^~tyb_9XU8z*fRLxE>BW8i8G^j^F!Uf8F=C zxUT$;ulyzpkZ*hK6tdfkXK-4LJdD8I8WD_(D{n2Z*An+^yQz2mf;|Co-B-)sIpyB= zJv*52ARh<)c@Mc^NS@YBKSvKDGJC*X8+*2|Jc7@54}}I zXDdQ)PjiB#i0qHz>0I|{q;(*Wvl|3g-~jG5<${WdeQI5P@yNoCC0j<;pPj5<>Gm$% z(!w6yaqXVrlP)^$y5c3D+<&R(?DdN`7I~XBQ-?&ec#Yg9@htPU`Pp@aMXM$|2bOHy zwL0lL+38zy%mVhnuFFoH+_Z1?z7^N*`uJz#ryjR@$r7|F3QdqbUcz|e*iZi{>jIr_ z91GHn&}t)NS{V9R6cQ(_#-+J8MEQnd4o*TSfhQGgER@(zkxMGhf)Ml(RfBA{8VydF zfdQJ*60{0<0@)+><4Nd(weX?(pa)AWm1jUPB&|x?Ip4EYuY#cHN+jSDluJNt!7yXd zP&jCh!WIrOe?}^V?nc?0P!DljA@la!B?!U{p>Oia``K9wM^Ea>t;_esx{8xKHraIx zdRv!gd$tt1S{nu?MiOq_z(^vdAHR0@@mG)Kc8tFp%}f*rma(oyou{r`e{Nr(HSzA^ zjYxeAmkx3I0T>>|g+Zt~A?op@V~spbK8G$G zTVU(VXF3wGX82t|Rmim>G$wK}MIQR*yt_g80AnILGbY zX0N6uJ&;?UV%H7_1L=f6J6ZnL$OG=B@dOqwL(J0C(8b*hIV4o_W6-3Z-H((d@F#?7 zi;{gfRpcQQk<`X&WStab1tA|H z-KQENEi^&X=hRo+x2J(S?LX7JEXQlY`LI;H-$D9R;PH3n2UY-hqLW z2L|>Ij*o3Ctj~9>&*#^7;BR;+79R+9jOyBJSgp0rArR$S zGwm(yQFxdH{8d1z`*1*lKZERoLK|0#!4D-AavTXby1~rGzAhgWJd~+Qvc=rnQ6*01Bo( z0E(Zp&h3qS{g4%pr`o7O$g-YwhHd+|zHp>3OaaY(5q~pIK~6O{u~QJ;jH`5`H&SQ` z_*+~3{w(2~A59(48pSKH1G2K(3=E3kjd}-J#Ct#Q5m=zs3`-0|OVdcNJEx-LQPfq3to5-dwt3 z>7vJ%uj}g+dp3T*bOl~IGgg~`Jny993_^hee+T&ak~^6bBdvg#4qB+K_0(e2L3|Tx zFFbNpk-&}@*n}DP4RVZdk-Dx54uFLNu;9=DJYMNxc7jOxH$ATqOlkyLP%lFlr-w&I z7%aq}OFA30@NR@&lj|uR!4(&@bqtQ!wcCLoBw+&rIFM5ookhJ(FpxG?PPYN;V@Cbd zbbYw>!li~kwFz%)oc|fp$ba~Ee6;3CnI4QpJRT&Wjz{9Ls3+_RHF=4iSuM!%L+V`v zc4)NUM%h{?`5Y38yo%p#>U|9l1aiR9@v@QWIu&QoOBSrdai@0 zX4nFVEg*vs(GE&iACw${M|XA%?kW!K>@MHo0E4pF?+dS+INv^$T-Yv5NrQU_mY&$r zebR!PolUJdORV#flja!i2Z#c^4t6w+P|vZKNrM1^8t`2dFh{}1gr96NTMjt+ zsDvTUbYS@Y40`FGW)`x)l%K zUm+hK*R;F_{Fft*vV7cP6bKl6DY=wEE7`_%%Ee948!t-BDwZ#Wbz9o*_K50fwK6ox zvQ{}!%x^8ne-vG8n9lFhb#e0LBgLH`(b(bjmOvtkFoLc6KwpF z&y@elv?ro=aO#KC`a-fyK;HJau_&N8guE>)4$(jAAy*taaw-fs=0KLeISRzb-+WwF zBHn&GS0Ua}e(Tm-Po3cL`6{7TSd9GHI^b%`>Y;nwtfM#^=-q8_%q*ZFkkduJFA{9X zy+{OeahxT~ukBmzSzh^H)Rl+h7((SYZqU4f{Gja@(Ke0Ufwrx6q(4&Hj=9jdl&fm} zVuHbiw=r$`uO~iR)flzUVN`XEx9>#zbZ+or?L#vhu&_c5Q0D!X{dd^L3oitavnQTd z{_B^k+7fkdyuqF>pYYgYka*PMeu&epv4iQ8Vg;Gu`u z9nEF%F?TdWw$Na_TIlb;2YpzVFd{4vRzhq*Wa7G2ix!TLmb!CUgP)V-Fv5 zYyFjLca^{1v21l4@Wiu_!@?r@F;T2GQRAvebtP6CPQrgkImbltE0{Ux1}?`lyYWm= ze0CK3QGE6p`T8bgSH3~K5}JvpMW1Zt@yX6DOn6}GE3A#;5`j-3tN@>&)gPt$#wMvz zvAj=gpMp04#^e&(IL3mC0s)DjkXAnl%H-}+n+}fb-wG8m!R%^?98CHA8#g&0Rys#|Wp{E3}6Z1`Nf@x4}$^7Q}-morD(n^XH58Q@4qqLf@5q z9|j5Lx^ccb;Q__dqvG_2NRToK&|IiEGbbx?^M!4$1)0nOSGzgo^QFw<$5YuXe4PRT z*hIzhL!x?Wm-r}OXCKVhbLX$4ueNH#aQU4=Aso#YB32J#aNKT-sBRC3+jEg{dvk-$ z)<6aD{g~J{^^|x&*$VhdPkP`sXs9C@9!WHyB7qwx1}if1Q6|-hN>~E_t{?@2R1KHA zY*1J^NuVr_Qd<|TrAUBzcBtb7{z|ZAN%3?<+GJ&x2U7~(!Qv4>ylHy|t*sx1+zOkex-%Z-O zRWlZDsmz$Dk)W&rl4^%Cmm^?hAdasX;eVF%*6FQ=pe|xfGz443tque7QAv$`H@ zL?U=D(q_s6{XKl~v@1(V%whSs`9eb54~x;MepLrz#az5?%1y=>cXMTNV?lycU{Z*{ z5FILft12gs$~7y=0pi)&)J^QBsXvQe_#ur_n=%*f`{!U=djZa=BehXXFoiytKlS-pVkv9 z{|uywFz07|4o6cHkSL#reN)FwT{ra&*!aIE2#2C_&c;L0if?22>GE%yYtXH&C(bgMkikD^t=8aAPi zx0^G-JD5`rQk0R`q`Z;ls>b*uR!b;U@tUjw`j|d`86Jf+bLF3BJdw7x2DeY2(j;-- z@^_n(zOdP{s)e7uPO$vKly|CtQ4yZye&rJHKe6$&0)Ct8?eA)crka|5E@dL|r9lf$ zJaziu^h;}WYT49nQ{VmYcn*68t7(2bx1tKoq3uhiwohF&b^C|k*J^oR-%8sW&tJrR zQ`cedegwP_aNsE;^A(XJr@VrBrk>*YTs|~j!SgGWge+qD9P!}PwJHb4)5+3@<^_(( z2iy!EfL84tUN8R?56;U6OsU0(rcO{%#;Z#Hm?Q82;`B1rgnj~sg7$*=5I*q|+7p1m zv*Ptrm!hp%^#7{+;x~)e_l~YZ71VhWbctE`Bxain$U>kq_Oz|^Q*Q;z; z(}3U6==N-yeZp*-{eIPA7R&dGd!~+4HB{CD-oy29q_V;R;ZRgTBQ+1Du3ihXsPx;Q z&=HtGtf0_Vx5pyxAu8P-3Fn&YZT9+lyA5a(us8v0@G+Gav@KowC^T9b+C21m;gRX{ z`}5N0sawD+91>rEwAU`v1dBow6jwPrrJCXiP7V;2Ae9R)ffCp_B>)vr$iNPpMcq{w zjfs0rkz}!(_>lc#%hYGY+re>9l#DLmP2?woc}5TOjC_<2$c%j80S`P@(E$N=%AaAy zQpEpTr*BgZg>X0=APxo5*uYVh!O!p}Tf$jKz+$%QOg5?2HRfot)>&!|R-Jgpm<>rh zyiLZ5Y1OL$q;l z6`Do653@^HEA+&(+MP|jVbhyKyjg1%9TGPmMu?6i6XAX5h#l;#kHTNsfQQdf*eimA zsAChH_gCa5+I-Bjy??E!yCiuXm&cw}WH?A-e2mZFG~HInQU)Q*X<~$~f)K*L5;1Q) z;~}{y%drCn!C<%8D^yV4GcH-^bud+bDaBmDfQx;*FP-ksM37Px+C%i~J9tNyzaz=t zam3u7Xq@LdBMNVmQA%xkT^7w=})I&8E3*1Vul?9o>C=vc-u(o z!MPpbG+7|!O+kG8MQZ(+n7Nks=Qa3^H^IADCHVO>N04PXp8<6#Yg(}2>pwxeKSlIa z7~}X5&oO!~JT}I@#&yd~_#x(ItN0|IRp!PJ>ps!|?^!hWp3gLCx)v<68p zaBv>A0NVX;;v;6CTFUML#7)96e7))5hAq%3D`Yv~@j+NkOn^uOq(q{hxS3KckkEp3 zv`jc~*bO;82xix?o#KONp_04-3k_-8+6oR$i1j6x4z4uUdA;7~!C*t%z(hlx*_Cl& z-tR)p-Ld~&dA@o+syT(9dpL%x4H^oxI0MRETnh|JWF%Y=P$jeV7VoPj!zsJf|+mg>A9T!7q` zysy_(>U<1!UR8yu4!9dhd>!p5hU-7Sm!e*)Qj++rltFy|pkhaz3jF5wXTIMJsQ>?kQj306=Fp4pogYxzasR`X0_xHj zX>A{=%mL1N5wN0PvQD~1vk5U%k9^o~7!b5?73fu5Gw=A6J+T1#o--NR&rFwrZ z!S(7gqWrd1U9a?Y9M|S6!&TQ~`2PQ7p?Rb-_njJCqqRVVaMKw|IZpW`*wHmw11Bio z33nDhLZSH_Md?*O>HFM5b6xs?QW?YC&$-XdZHwxg=P50?=KPRlzCBB@q<00BGKo(r z<0w?0V6+yc3x(DTtt;y55DKj&f;p{;3M|jW_p?#@wX5)(`d+z4_vsy# zLf7d&6th2Bl?sY7w2(6fywbf4Z$&vv6we`uf3C;g^5b}66KE?o{MuvRjyIrsc&i&>Wc}b7KPwVwG*2{V&!Sw8vI*q@oS3%{<#m+1QGS8)Nt7R< z>_s^Z<$RQfP(Fro3Cdk4dr;{5!_X+-gwJPCsGX-#9!2>A3f-sr7on^`IR|ys_gzemZ|eIqDBnSO0_8Q7pQ8K_Wjo5nDEFf5 zLm^!9J(OEes6Dj7XzppPQ{4wpsLl?Q&!PMcS^ZV@JL*5erdFqE)WkJ?n#G!J znzJ=mYwpuLrunw!Ma_>jztNN>vy_yErKQrb(tXm)+KjeKJE~o--KgEI-KYJs_ATvi zw12Iwsm;`O)sEIKuRWpm%-T=Y-coya?ZdUt)V@~xjxMZg*Il6dl1xw$rh80ZHT{$6Wz#QAe>R)VN%Lvum(6dOe`OIY zPD|D*So79>)^}`iTf41fo3!n)onpJtcC}q&x7y?OcKbqy+YxqT9X*b5$6Ckt9Y1vZ z-I;XeorBKB&SRWAoVPgdc0TO|J3#^@KIK0-tRe+Ozwb) z+y?OtlR!u&lbOk6GPww3Zrmcd5MY9WGnttr1CyCBmm~yADMh7}T5DN9*U!4vb={VA z=^M0YUDr};UF*8kx~{d>T5GAL)>^mgQrDvF|6FD=Anx|-@Ar`(^E~IA=RCLboXdOO z_dV~tz4H#uyKCM9^G;wx?WK9|&R;Qq-Tck-WAksD|J3~F=f67t?ELo@s0(}xdKYY8 zuw%j4f&&X~U2yM$6APYL@a}^13yT-dS*R}bEj+XE-G%23vkglOPQwO6*f46i*>H#9 zLBo@VmknnP?=4aml`opVsA`er>Lpj3ul8Ksef0;6^^1=#K6_33HEXWvQ>prx`n38& zrK|G3C8j0Empr!QY2$3;Vq>k*Q8lA#VU?w7Rn_LISk-~5+pF%WI#zYE>dC6pRj*XN zS#`d8cJ<=w{nbaRU#>p8bltKg%goDKm-&|YmxY&&E<3jD{hETBxiw2_%r$*Aftpy& zM9ra^qcz8BPS!kK^FqyQHE-3NTfT1j=H&y+wdId5e`omzwKcUHY6G>g+KJkewNKXd z)dlMAFwHTkrURy1O?R0dFr6?xQNO0XuRc(Jf4x@!c>Oc=uhqX}E-)`NTgo#p}a zg!!QPHuK#Lr44f%mNb|fS{r-~y$#zNb~Nm7xTE1si_g+)*=|W&4qNWE+O0>eXKXIp z!?shl=WH+A&f4B<+}^mOam=o_r|tXgci8Whw8#odeFebJBUU^LFPw&g0G}oTr_yIp1-9(4udd-BQ!?Ov{Tc zueY3QRa^U71Ff;viPnRy$J+|pX5p-)nl^jesB*~`-=7* z?dPxEivuX{T5;csXIIW&*}C$`%5$q~SKZn{J8C*^b?IF$SD))%*Q>6#UFY59?j7!z z-5+?idY<;2_PpeI({s+N_s;On^)B`<^_sm7?+UNayTQBJyWKnJjpOZ{eco4nrM|fD z?bVjm_pGT|<6ra4+O2D2YhPLCSa*8eYwO-wcW%9B{l4`#ufMgkywlOSu5+~Wj?Nc4 z-@ndt-NbbdU-$Zk*oIp-ywla&wY_V9*Hc|Db)D<_sJo`y-rd)IZ}(e0i+j3zl0CQg z9PfFi=SA*L1)dAM87v4c3dVvb2abo5p_8Gf zLa&8B2+s*y!t27r;RE4&!l%M-M2aKkNMB?!a(m=!kyDYEBIgE8gDVC%4<-j+8FCFB z9XdTcYuGjHA5IV7Hhkyslf!REAKbBc$GRP_?i}2CHa08fkL`;+8hdZVJhCtDjE}}| zi{BrAE&f5moVX!zB=JPzmC@~^2S@K2JvRF2=rg;DciDD%c5U7@y6e_m$9J9Hbw0Tw zc`$i@@=WsG)Z)~d)V|c$Qjez2q^r`d^l19Fw3dE8Q=YM9Jekdz(ae#|gPE7cW{>ra z9TJ6H6xS6XA(N6VFVX-Mw^o_wIq+dw1Wx`^@Ch zNz0^bvU_rL^4R3_lW$C(-{agfx#!59d-fdP^TeL#_q?&^y}kOq^Y_;7ZQr|L@7MYM z)-{+jig2Pa+x4nan(#eDMY*M7F76z%y{?61MQ+c_jI`MQBG^aI?uAj*WM7_Q|8V-E zvP$9wxL>zj;)S^1yG!DFJXyEthyX@Xt!Tf~dMI{S3uk@S!^~x;q&i8cwS=i^ZY#4M)_q@lYb3 zO2mTcpxT{G?1+TYYDXqLoJgkfih3hMnOHDsGg~Z{Hg|uow=J)*UY6!%sWNMGB$0J0oM!NZh2lcPAsk*k!VY)9KOHhKBL+adV8*nnQ^Zb+4M2AL4b* zi6o5se<1D~j;2&!BA!+^CI-{v!DK{5Ml2eN#8Z*5nu&)aNi_{OZCu-_c8x~jg1A!< znAEI&RAoi>Tw#^spLKeH}e&Fx{HfI~7VsN7E^DDjH*oH+1qAZia4qg7hCmrtQuvK2LC^(}-;H_+ zbgKwTS3*reh>`iL?HJ`woAgtVLcErRkGWd12s%)e=HYsKpm z4azwGGo$u2Uo-df2)OUX|I5>d^5mMiuSLuK|KZGHAPWDfcoB(30Bh%szy^Vh^EPA= zA}C^jA?}|Pe#6Mmphg&Y5;%(tM!OO1+X>hOPcUERbMCx|1M~8w?Po>%vHv1EFDEyv zO%SaW1V0vO1Ki6hG>&pTROB(a9ZC-%i|1Cri~XH~Ev#RRqBI4GEQVr$EV_r#eqE^F z`DfM1-gmjsgK*5wH9)febU6PK4-4U)BFt3ZC-~|DPE62~B2tX&%o1Gdl#y~$L1rie zWG1e-W|1rK)#@vi5SfF!X1C#Bv}%0S;zQ*Eb}#3Wd1St_O!+lgfRlp_WRX&X>$I!M zVmzty3{goXSwf7Yic~Al;!K8RxVFRT(WI8t5fiB=X3~HYKx&ouiG^5+4M*JBl{(Ty z9Qb&)6P@5W(xUteX~ix|oALo^SAIaQB`e5EvWj$Im%*i&iJN$E-R{Me*=n+etR?Hn zdeVtIau#wOuC+IiF49eUaMiYv^dY2d%JXEC@?0%yB6D!;`W*iE<&+=VxYQzVUdV#cs@ zF-|6wGi0~&M|{_Q57|pTMfQ6~f-iR;!s(-*!?o~X z@_BNEe1Y6bzDRB(Um~}YFXQU+FUTGE+UT8F5wzm#qIcnl;JYz!wJX0NU%_?mJ>;** zz2vLpKJwStF}PoOll%>N0AG6b;DoTRk*||uIpE~eK|3=y=FE>V6-jmmjiMXTvjx{TJ~KJ*s4oYvAhYNGYj zOdAwGp4PD_`*GUZXQ)m2G;O4I+C&|+nL23;ZKZ9ronA{<(3Nx*?Z9NTmAa^#dZ<^q z9_z-psZV)~uBL0~TDp#|r=7|+dY$qsx`B4lZrY=qQof^nMY)Ie(v4W*%~7txIkEHb zcJBh5wQo=s(LTC~Zl+tPpZ3$O^m=*&-A1?58)<+B=>QGUFpbbbIz)$Ql&3US}&VdJA?(K1UB@^%}rA4y&-%Ems~= zPSDTe9r0Q83wZnI>&h|Z3FX_$_mqc~Zz?CTR(*^sDqfCfpK^cVCD{Uv>q{)(QZzou`|-_W<|Z|OVqcl2HQ@AUWdKj?e( zKj}I82l_t!FZu!fBRx+)q#w}>SXQ&gJ@L72oCKlQ73qp~CAv~wnXX({p_`$bshfpw zC(p)n5kFCWjvcL+aoYW>SYrH4c}00$`Kj__-IcmIID&PqZk}$wZh>y0&Y)X_@6s>U zU4z}NO5GBiSyZK~)-Ba7)72Dg*wopn?-)o%#v(V7A*8-Itw`;lu1X5I=ljMl`PIO zcPN?+Wkv>Lk%>}wIFSy9utZMlJ)s~N03;K^w2)(w`dF|nz|GQoIgu!}%2JoK(k-=i zOYPj!N;kI-TG~s!IdOu8OB(3P8t5qUN<+A|R>{(8FY*e*WIZn0Vs$z7tGPyHtEY`B zUyVzOk)SZEboC`7ZI*&H1Hq)=W9ukeGhI>U+4XC4Z3wNUingqkTve;r&GS*-QM6X7 zyjG~FUz?7`!jZCd)11rK=c&ZRSgb9gzSZ5V@8s>H@8p)0cjl26bV7TfRwH+X#VX`l zy^T^2yG_4A)ZZZLZ^)}Js@i3E*&RA>d`RXw^<6nDtzM68Gmo^^Biqg+t9pcVn}`UD z%_bYHG27NM7Tn&#E?jTMg%FR_(VOKasXWq`UQ0#Sa0YRj%#6f>8Ez>&>MiQZ4}vCH zyNS!Ppj(x7rDDOtQ9p#W_IK28zxz<6k z)WMsDj>1jTUiL~sKB<5=>vhS@>#f-IaWA85ZOZp@b4Sr85x<3-*w~@p#G_a=WV2Jh zh5Mvz%d`(lwuGaRWF!?$>9-6egJY4h{^>$d&nX+)XV>?05|Qnb(bOni<#Xz{a`vTL zFX^on@ zgMxBU4NecL!ALGfEH=@XtzJt@S!mh?rJ-oLqjfu+fHkyl6i>XF8KWYcEXa5b@XPrNh*&FD6h35@$uon;wwtz z$4HZ`-Na>K&~=q1J~rq|&UVfj`g!z8;gzKDO7aq~U?h{OH%U{PCA4P6Nmh1`K9%cW z7+a-B9L;5^yw*)!!XJc^zeDnKv=pS-pqu6jiN+LV&_z0`MRo^=G{@o7XL79sGAY}e zh0TSTX>WUF+xetmZ`S*gnb%j5`M9@P56t&AbAudwGkoyP0W9=4esUoQfo) zgN)i{Y359AR-3(KGLlS~Qz?s0U@R$UoO~j}%8B~~kPq{KTp}Q!#sIlT0r_+Q$Tb7x z6B=NP;2e%8MvAejHFG3Ap5RDndKlfAqa}lhOj2UeF-ez-P6)ab*5h%3M%aeAAdE*P zLm?xc8A);(9APpTB4sdC$PgHl!ReR`PA6n=RLJ0jTn59W@3>zq7BM~pa=!pdzcfp~ z;A@-0*U~S|(l5=@FU>9GyxeKw?w`RMg83#d$)xh6AZ-?;xq7ThUcKogug8+PdMv^9 z@mg(jM-d5$FuPvB%pc|>wY#IQBA7xtn%c=ES}S&C66r{IAjVRSvv?|n6-6w7EYWb#SpG&Bh9wn=4l07E6Z=FEM9;j(0gs3{4fGL@W^>L8zpI z$=wCvM0}{go5_Zya5on0o`P6385B&#l?N*G#S#h~`q4-V^<}XPVKF6wjCXgCwUlRV z9toy}AvUWhWFtl-78!w)1e0c0ab#pPy&G(KhSeq-)@BjwAZT97I&Um88q1_+1~J5m zDUG|cD40wp#xtX$YD;q|PxD(pGRs-Qv%-mSzV3z|Eg~lt8I%%R93p2RkscOUW;86R zy~58{v5vG^#gGOlv~&qAZLSs(mM+;YIO>WVu~6l;o~dAmQa?XtZ>c@Yx`*zP(~Nk=Pj=NQ^K<*Vl2OAiendVS16_JT%o|us{SR`{+=aahf6Sn+> zJ3rybPjv9ajGPzp%B+H4*>qWy8)g$7+G5Ktg0ou;*>$8?SE8JET0r4Fo7l~;*xWo! z0QtZG=q%;-aM`7NeIQWYk{qqbPRTqqBR880I_?~fUXi~b;5j91(iddh5RR4!M?2tb zYaVXN!&~z3Mv3FXB}dQjEIq@s^bBvDCHQlCE-){{a%o1(r5TMpgZbL$^LcPz9IqQ) z^vqcHw#vn@6MNn5xK}=kS7wglBroMczwYgiPSY`*0dthRc;VcIUX}(Grz3b$EuW4Y zyzoa!$LnS3!0*F+I=UabqJm!^7Kz@U0~ zlV79N!EfP{T}miq957T?YD&MRxQ(6@u+1HC)oaA0se!?IjhfW3s-5cA3YKm?QA0}I z-jG+*d;OJ~uDZYXdVi&{(s0b zUn?}aG`-OUJNh*esMiWiM(Cx6rwRvLDl1_c8-xbzSJMT&Ax&3Xi9)w}SUn5@Q0{70ir}Rh~T}F7^=n86dU{E6= zXrUF>)@wy3m8ptd$VUs50gyo1Re^p+67X6b!&b0hWWkE zTxtN~zj*}?k(!~nT!#Zwv-Ne_=jseg;HEjS?aI1(?JCn0VQ?<|%-}rJl#apqrl|r3 z7nr6B8C-}{xEM5;rivI`WSS~w@M_ak34_Z`@PhEWrVqfwM%Ap5ZLEXTYqfcWb905e zL}6WC;nG}TQWP#WDOyF{pJCrMux~N+Qehv1m9US&C9sb{BkW_a3idHr4f_~e3i}vb z2KyMSfqe{`OlmvtKJ_NJ^@@P%M%xBhECc~H6N^=|Nvp5Z>d^}u&;eE>QZL&LjX|f8 zjfnpv1;Vgiv*enV%+(rdrwYkcUOxsh)_~T0&wRYtW>TBDzKx(G-j9z47-TN19?Mte zKFp^T&kCb+%0{kY#@XQjXnff^0rZNXvtDa5ndi0FYmWbo6cHQ(c{AEXnOm)z)zz%$ z!Qr*IS7aK^6F(yMUBpZF`!f4iu`y&PqQYYpO z>JEg_yWGJr#xm*-8h{7-F#udzCkVALyc>N%r@;sbKG+AqrGVdMgg_BC;uc_(KqC|) zvM@nhq6s(#QMsBcfnQ8oY^a=;h0tit`ay*Tg-fL$jO}nyTdqhe#%Q6cKI3X8l(k-a zwu~DiJ*@Qk&1yTQT&6u0nP58I1o~>E)}kmkyNkyAc*F&avZbJJy>@L@8uz|zssLL8 zTxxQ*c~@YdFf%Xuv{`O{w*gb8+TL%TGLt#zpervb?KN~?RJ!V-(rn#)ewWthFlo-Z ze1dGHtU#AZYpFYo&|~p+7+w04bVGxhH8WInbCX&4X4`~?r4jwgjD9A}@L=4*6vASi z$N8Tg#H%kCQh!d|G7qyMs@>=`ROUrUWxv$Xi?P*Nm-T}Wsg}A*)=JD1(vqCft6}t2 zqJv|(fxdjDS+k=*ula=hwO~i)T&XpIW}QiE23XJh=7ob*ALiApr#ek6gthf>>~$u* zUE%?<0SIA8m+1uIIo&{bP7kB;qNtbAFk~a6VMrgNVaO)aLm2lufNcgwIJU)vW2!mk z2PX3R869DCTNxe4u4i-{yMfVhY#Wp52HAEdlOZ=UnG6XqnG6Xs8Xu4WM#GR0qhUyx z(J&;!^>P6lf`mX< z0v%;eXJpzcq;_E%1&hJwKW#sz=%E&*Bn(cz$et|NA&j^$e902XLIewqzDbDYpKpFW> z0%hbkqvo}O-z@@V1cwC52tH?es05#hmy1b#w(J8eIeWbN`Y4Pl0U9m`c^cK9e!&9ZM$7h@>IaP29kIa>zw8Hhc zvWoxlv`+C%RgptISl}P>PwB#*siiFSSn+;5KE3Kt2&-I>uus4~(q3FsJf~P!Ui&Dy YaFbT>1t<8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/zboto.otf b/app/src/main/res/font/zboto.otf new file mode 100644 index 0000000000000000000000000000000000000000..4d7c1679d9f294096b42122aaa2534f68969f536 GIT binary patch literal 4988 zcmbVQd2pQ7k$;~g+ih$M15w!Z21*skk_{Cw0Yl=j3C6OG9gM(-49179v1X(>q`7A_ zr!0wGw*X0&=8)$2z8TrNN0;$|WKt#}!Gw_7jh#&`6xr-i8!FzdR4v(WR3l2;@0(BF zG08v4r+VM}b-#Yy{rYvkH~K0M9H?Y#S%4L@joWwbEdRkQL>Vi&!C2`7mHQw5hW3kf z3C09QPj#K4rn#i6imfC7#jY0F^jk|wM`l}f$vNxM7R`P?MlK1IVu9zj$;j7yRj^F;| z?^$v2Z}{l1#y9-u|M$y_MfVjg5M9iQIF}xpQ}o}<-=z1!JR`pV74vQrW8??DS+USA ze9Bgl?5U!8qPz0F@GJI+u%eI8;E&>49y0bKJ?ts+a#FRmz}CLoEb#LV?&n@t`A(D3%JwoBZPrd^ zVn$}&r)jY1Ys_pP)363+V|rG@%&KJh%HS>KP+_88kjV^6Se(*A5?PqXc;g6*JS>|)Qb-E0pN+2r!FqVdm~xBTnnkC(46 zU;86}KVAOAKj*Su@mewXC%$f}&p(_iuo8g@f>65GRk%OIc_8~&Ec>OR>^;TVdrPo= zu`T<_s(VPmN4L{kxcv^KuJnOB**!G*p8Fr%{P>ekZQuF5%KcR@SJ!J!G-!2=O?rdT z?7C$~KeuZtsb+i5%ZQ|#~v#gT6z{0{-!BJFHbiH_g@nXpvCI7SP z#_G#!j;x8meel0)Ew`25c5U6A>+f9u;rfr)|4a60a`A~WMEvXtL_o}LAxu3k=IJGc zp8sqOm&yKem3xz0#Vz0O`mXjR z&>lWu-iGir9!P5!Ks)cecIpO~k=JsW-^G5BS%9g9D?`6PxQ=gIGDkp{TU>F8%iYK2 zt_QE!C&5117(0e=44d5cMsVl?hj52tubj(l>pvhhfTSIFT|hXG<1ujp#L3Y2yM9hC zGWp8l^Q1PyS4!=(XO#X?mCSyqkhzLI`Qbb%zy1l4rP+7ozr99t<^NKpD-@xnLn`^6 zLdqxQIDyPudOSJ=k)hr*couLfFltMIEoBTfAXKByZ!v?#?APIO@*;2jz2sZxX5rlI z+sU6H`~=@}PdCD}E^!PG0PZ@zb<0B}nR_;ORB>e@`?9>S5+2diufsaPy^h0m4N%um z<=TVrJ*WWA=!F^oh$God(fuTQNPdNu^jp>LcE#?4DwU%kk@Ck> zQqGgP4^;a5%BcKqwnYKIQQ1p8T8|8scNYJTtx$4|MDnM@JqNUl_7Bsa79lH>o!h5Hs>`^A3E<)lu83bh)uSY0p&>J(-y4 zKacPpE;y!Z$V;j)@{%OD&{R$Q0PZGdo%`ymp}yutJ8k=JJUCQ)8EW5hypJDpwUb<} zeCOF4S3ZC%AAB_V`&WrO$mdhrrgW{G+V%Gb9kumPS8qMgUIEi@EQs$R{21qLnHt!g z%alnYw6-?^=L0j=5wMOJ1~do;Y;J3Df!XD@2AjFUCa!Q_5}% z_p6tc%FAlFHEM^IE;sYpT?%+O&z5JhOA5V22ZJKV^RG0U$sUp4C=(4K9i9NJbR4PE zLT$q#_inyq%JeCy%Q)td_Gh|(QXGZk=&&?32>Jnw=spXsGi_ry3HVOp()1)uPcFpX zMR*tI-7`8~V@L4?Ky91B)eNp?vt93jDbKJ~G(zrexp^hrsO>k2b`WjDfeh6*4u?bu zM6rJuGk}@Eh+P7kWC}GR=+G3fQ$^T=MsikeSbDfDErrH#7I4NtZb^eV-4w3pnbG98 zSioWl=&>HKKBQ0CAZ48hoXeM5Dy1amlZz!QKFqzC*nyAo=$UzNa+_NIrfM%#J@@UV z@>9fht0B9tM2_+d;ab<*iPbJTc7^@Mn0%Jo_S;a(3=XhO2+acgZ{j<|%!g@}M5|9;*?o z2EPUm10D%!QhJaKW1eY*Q9Ro>6o+&?E``s~f&@8}y{k+#hK_Yr@|Eo5+V%#H*bQJa zG`1=Sd*gs@6zrpc3#WL&p;D?>Ll^Zg-yx-^ zNt(}C`j*lkR7qJ@$nv`U=z`M8U;iQXrP;gW(u*`zZcv4fE2Ft#m7L8Be?{IxKuSlG zeFJcM;PpX_0gCN$Ule>%SFa7B1?}w~4|qK%?a0fjN>MNq9v@1rO+Z$2$Tq2P`Wuz4Pml(fF^`{ z<0t_xSki|Ptmy3U`M~RKccbm*Y9_;H(-I6xqjaqBLn27`l4K7VQ3L4ebo<@lceVN* zZIEh@`6F(M?qADq%3q}=y`$RANB0Gl`kLZJK2nG`6e_o09UNCk`FZ)g0)C*f`C$vW zXZigg->vfG8bzc%pme#7IZ{b!u`Ew0w-ZUx-kIqbqMN%qprd=-YH9M-Ae=yb$eIMJ z=pFt$x*YK`mwerF+7Cdeub~2pqIfF2RqrQ|mUt;8})Ix_*EqUqJSzEx; zqW9Gy)M4X*c?>LLzB!!d`b4hwmxDi=n1hLP3*!4-#OzmX9aBsOIaPbA(QoEwm8bEQ zYDf)@MrhlnaKtCIKwa*svPhiPGy*u%p7O@Q8*}wp`L9NIrzZfdfuOg8-+<`g3U$4v zK|2gk`6(eu^ifEtb}lPrCr_!i4T|mDwxz#RfVz|Nw(Pq60I5>q!LzS4TTJO*aWmar zJ@zguU<0MQou_+HS$I>wS)!jelj(8#Bi*<;UC>ZMr zM<5dIiE+hZN8A?yZ?v_?j$lVOFAJZq!$oBwm*rk7i$;1An93L00C=L#(-s5^I@-M+ z@OavtXaThLI-_2tK!@`67LC&SnLL)=SeDj@tFej)nyz%ft*w=fC!k4l*tv(_4$3~# z*hM99lwJrIx+X*E6#W8>WL}+zfY%pnJqezZ?pK`LuD!<<0$11@Ym*=-cBb(RNblgp}p8ChA{RK7ps#H`) ljrc`dsL^*MP74NV^d(8~Q!`TQrPf4^^EuB%jr6SO{yzb(RiFR> literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/chip_view.xml b/app/src/main/res/layout/chip_view.xml new file mode 100644 index 0000000..ecbf79e --- /dev/null +++ b/app/src/main/res/layout/chip_view.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chip_view_filterable.xml b/app/src/main/res/layout/chip_view_filterable.xml new file mode 100644 index 0000000..b246d24 --- /dev/null +++ b/app/src/main/res/layout/chip_view_filterable.xml @@ -0,0 +1,50 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_first_use_message.xml b/app/src/main/res/layout/dialog_first_use_message.xml new file mode 100644 index 0000000..71d1582 --- /dev/null +++ b/app/src/main/res/layout/dialog_first_use_message.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_solicit_feedback_rating.xml b/app/src/main/res/layout/dialog_solicit_feedback_rating.xml new file mode 100644 index 0000000..dae338c --- /dev/null +++ b/app/src/main/res/layout/dialog_solicit_feedback_rating.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/footer_transactions.xml b/app/src/main/res/layout/footer_transactions.xml new file mode 100644 index 0000000..453df3d --- /dev/null +++ b/app/src/main/res/layout/footer_transactions.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_address.xml b/app/src/main/res/layout/fragment_address.xml new file mode 100644 index 0000000..8b12740 --- /dev/null +++ b/app/src/main/res/layout/fragment_address.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_auto_shield.xml b/app/src/main/res/layout/fragment_auto_shield.xml new file mode 100644 index 0000000..7d17e5e --- /dev/null +++ b/app/src/main/res/layout/fragment_auto_shield.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_auto_shield_information.xml b/app/src/main/res/layout/fragment_auto_shield_information.xml new file mode 100644 index 0000000..ffb1784 --- /dev/null +++ b/app/src/main/res/layout/fragment_auto_shield_information.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_awesome.xml b/app/src/main/res/layout/fragment_awesome.xml new file mode 100644 index 0000000..5003022 --- /dev/null +++ b/app/src/main/res/layout/fragment_awesome.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_backup.xml b/app/src/main/res/layout/fragment_backup.xml new file mode 100644 index 0000000..de2a4cd --- /dev/null +++ b/app/src/main/res/layout/fragment_backup.xml @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_balance_detail.xml b/app/src/main/res/layout/fragment_balance_detail.xml new file mode 100644 index 0000000..721b2fe --- /dev/null +++ b/app/src/main/res/layout/fragment_balance_detail.xml @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_confirm.xml b/app/src/main/res/layout/fragment_confirm.xml new file mode 100644 index 0000000..fc88c16 --- /dev/null +++ b/app/src/main/res/layout/fragment_confirm.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_funds_available.xml b/app/src/main/res/layout/fragment_funds_available.xml new file mode 100644 index 0000000..75e0532 --- /dev/null +++ b/app/src/main/res/layout/fragment_funds_available.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml new file mode 100644 index 0000000..bd705d6 --- /dev/null +++ b/app/src/main/res/layout/fragment_history.xml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 0000000..0ed6d6f --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,470 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_landing.xml b/app/src/main/res/layout/fragment_landing.xml new file mode 100644 index 0000000..45ac0c4 --- /dev/null +++ b/app/src/main/res/layout/fragment_landing.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_memo.xml b/app/src/main/res/layout/fragment_memo.xml new file mode 100644 index 0000000..b7d2264 --- /dev/null +++ b/app/src/main/res/layout/fragment_memo.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml new file mode 100644 index 0000000..fb02445 --- /dev/null +++ b/app/src/main/res/layout/fragment_profile.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_restore.xml b/app/src/main/res/layout/fragment_restore.xml new file mode 100644 index 0000000..c021f44 --- /dev/null +++ b/app/src/main/res/layout/fragment_restore.xml @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml new file mode 100644 index 0000000..ce794f7 --- /dev/null +++ b/app/src/main/res/layout/fragment_scan.xml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml new file mode 100644 index 0000000..47fb544 --- /dev/null +++ b/app/src/main/res/layout/fragment_send.xml @@ -0,0 +1,530 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_send_address.xml b/app/src/main/res/layout/fragment_send_address.xml new file mode 100644 index 0000000..ca33764 --- /dev/null +++ b/app/src/main/res/layout/fragment_send_address.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_send_final.xml b/app/src/main/res/layout/fragment_send_final.xml new file mode 100644 index 0000000..eaf3a2a --- /dev/null +++ b/app/src/main/res/layout/fragment_send_final.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_send_memo.xml b/app/src/main/res/layout/fragment_send_memo.xml new file mode 100644 index 0000000..f9237cd --- /dev/null +++ b/app/src/main/res/layout/fragment_send_memo.xml @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000..b4a4648 --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_tab_layout.xml b/app/src/main/res/layout/fragment_tab_layout.xml new file mode 100644 index 0000000..2f71d04 --- /dev/null +++ b/app/src/main/res/layout/fragment_tab_layout.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_tab_receive_shielded.xml b/app/src/main/res/layout/fragment_tab_receive_shielded.xml new file mode 100644 index 0000000..85dd7ed --- /dev/null +++ b/app/src/main/res/layout/fragment_tab_receive_shielded.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_transaction.xml b/app/src/main/res/layout/fragment_transaction.xml new file mode 100644 index 0000000..b7b9827 --- /dev/null +++ b/app/src/main/res/layout/fragment_transaction.xml @@ -0,0 +1,554 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_wallet_import.xml b/app/src/main/res/layout/fragment_wallet_import.xml new file mode 100644 index 0000000..b7d2264 --- /dev/null +++ b/app/src/main/res/layout/fragment_wallet_import.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_wallet_new.xml b/app/src/main/res/layout/fragment_wallet_new.xml new file mode 100644 index 0000000..b7d2264 --- /dev/null +++ b/app/src/main/res/layout/fragment_wallet_new.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_transaction.xml b/app/src/main/res/layout/item_transaction.xml new file mode 100644 index 0000000..c971ccf --- /dev/null +++ b/app/src/main/res/layout/item_transaction.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml new file mode 100644 index 0000000..a10c22b --- /dev/null +++ b/app/src/main/res/layout/main_activity.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..90f9580 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..43c4175 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..3588b01140f238003aaafa3fd27aeb2867f0efc3 GIT binary patch literal 3547 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_fore.png b/app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 0000000000000000000000000000000000000000..113e795d271be4f03ad7ac9fefe1e54cceed9c0f GIT binary patch literal 6102 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round_adaptive_fore.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round_adaptive_fore.png new file mode 100644 index 0000000000000000000000000000000000000000..113e795d271be4f03ad7ac9fefe1e54cceed9c0f GIT binary patch literal 6102 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 BCEfr4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_back.png b/app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_back.png new file mode 100644 index 0000000000000000000000000000000000000000..8d66ef758d8f46467ddfcb0aa6afe84fa95601c8 GIT binary patch literal 2199 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 N0XFBX8!d6k{{>_*_T&Hn literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..c80c1da6459240948b31cc3bf572bee37f188d94 GIT binary patch literal 3423 zcmV-l4WROgP)Px?8A(JzRA@uhnt60nbr#2OXwyQPkT$_0MV5h5+9C=fh{Avz2Srg96*$O&gCMwp z3;QUDIw-5Su;@`y77+oJ0|FwV3<`>%z!7y+WGO}VrfEWwuIZf5oxB=ik`|cxW8OJA zCCPg)_j~X6`@Q$~dzj?^{4o8O;^#h*uV}Zf6p_f+C53P)0E~cZWoKvGO(s+KU@&-n zMMXvH^78U-m6gOP68_h0Hg7eVO#2fO62A8P{hN;+JLVCQU>K+i26Hh0xTcnfB5V9UC$O{2PRchC+Emo`bpC*%O(vc%a_Jk_(A0Eaz zY63vB-l|orhXR4XT%XS;Wo2a-mGSDpLMw~KA~u_C%%MYv7KW;UMq(b-0U-FK?Ck7W zKA&&2-|zpQ1Rhx}K-lf}d3kwx6GQ|-#RL%#fCitFle56%@eG6KX0ut~JUBd)s#|o>eNw9QH|8p)VK2U^B;}}A{GE6^RvBP?KR-Wzd@K;r0PtB-Zf@?VqN1WXh}Agi z)~ze|-FKgS_~D0g`0(MVl^|e~$)w*kZroTe1#mF<9~Q)_M8k#+W#GVpvV8e+`Th6b ztIk8*PN#ERUS8h(5Qy`H9SuNYR#sNWKp^mCIOmNY@7S@UOqei1UU=aJ*|B3sRbYI7 z*=3hWW@e@|ZQ4}6|NeUwB*)2%8mQwrwj@rc9AH-graS zuV4QspeZRSl9H10*ApjB>ACvC(l0n ztZdq}NwqO_=uqj_t(#__0N;N5ZE4V;fxzkSzWYwbj2S~Fg`|A^@yD`a#R_#Zu6gE} zXJq&8-72z*8bLLD>5rGm_q7DbZzs)UMwjAzuyBBivNB~CpDeFfceI%D( zez{DZJX!xedh}>Ha^#3sg&?_}&r(xUW!ki9Qma-ifr12l<&{^;q)C(H#v5;xxpU`g zGwaZyLtL$pr1bRkhw}6D7lk!D9l(r(>|2{OX|m7j_5Q^O*7!1#y9Dy?yYH&QKq+*A zUX1e)@T;%Bs#%BIXU?3dN(c$)-o3ki52*X^zhB;Y=bdVWZnxVHyWQ?KBH|N~3NA1J zX!f(RvIe?buEjV&WZ;qS$?TjtbL6hO?$V$M2HG4ra6tLpzI}Un|NZy1!wwiQKtB8I zGo@FPCQW4U;KB0PV~;7lKKbMmHHOotPsg=degj+1$jBIa^ytwQMimSIWZ%-PS+g}R zm+Nm~RjIlume#UmOTp@}(p|fD$t}0sq9D@K)0Hyce*3L_@WBUi;=~EfD(6ETL_BNO zEG$ua_39;Ex^z+66c!dL?^mr_C2iWY(fb@feq4U|;RmH*G}m%C99s$s3i^r&Cj<=u z2@Z#&L1JQJzA;HgW}HA;w{9(?MvYSLVs-Q8&C_i1-uN+`hJdfS>MD8Si6_)bAAR&u z`QnQ&6i}l^jkFRRB;mE!UegLRZ{A#mJbU(R{X9~G1~t)w)oN|*a=A|6NXAT`l$n`1 z=o6sl2HGJEdaIi(8$tXZ>0sqx^04@!#`Effeo&tDvjc?#|q z6clKn*d$IgfByWc0Wn_B$jHbTe*F0H<@5yufTd~ErgIAm3rB|2C(?!R`M!Po%Ccq4 zq<8P$^5Tmxs@KDL0=ws)dz9-dSFV(=zWPd*E?uftdfRQcN&WiuW#Yt%(x*=!b)MnrJ_>)gQsrtG3=9|^gjOH1u>7}Qqzkd4k=`mQUu{g11WMr)O`Fx$@RRHHfe9*>f zwW@Pmd+oJSQc|MLYsQQjO0DWbd* z$Rm%a24V$IQd3h?ceveddV*Kyd_HqJoo5Z+MH4Zf-Fxr7(ym=Q88>d6Qh?40sAvGT zZ{My01sF|?o`KyO0O+`MU^@1lciyRVTC!w`zSgc?8y5gNC@U+gCn9bDB#MZ`X0x3# zW|&AC@gDAv9XnR(0tFcCC^y3w0I8GFSeb5TM0^MK^(bgMrD-aKWK_R#dmG~h;FI~HKRkvx?s+9sDNCO<*6#<8Y zS0&>UbJYDIvT;!l?nWtZ@$sbBl}PkcAQrM)qpXx$D-1ZAw%Sb8*Wf1!s^K8 z%P+sI*@a2~pdUaXggABTRHfRjx8ABQ!~l2o)mJNpHg4Rg!exquUbq@np|PQ@SZ+vc zG}T}*SOowVN^>|I>q<&Wu8-FTI(6!#&O(-{05st}^rGV$8bE}5_Uw_7BS%VBR+duF z=mWTr0f5d-H-G;5=e3#93((8YKmQyTK%F{uz77Ney$lxuK;8QF>(B9ey~CmbFx&|d zGWLG{`R6iX#0YgI>=74YM1Wej5ES_7r=Mi;;>8*`O^<3(0RTWDDu8})B{apg=!kKG z`^7sP4#!(Amusxi2Xv5Y*s$T?!ootfGR`|9O9>Uo`rUWmt>XdZ#7(lZv$eb8DgZ!? ztfl#$!HvEFfXv)YDVZGe{)TvWSImQX-6puax=X5$BDkvym6U=Xn63I53t!XeA z{8ua;gx{2)C^=&Q6D0mlMKUrn)H)G03mfJlTxrmtL8=jeqaUo6F3x&_MG*58otUG# z;6uNi?e%)uStv24Q8PM7OG{f-TwL6>ItRe=7z+*^I;0j#GlJ{T0GbgLa}9RM=TANL zl!9VC?eTcDGoulzi7^l~T8L*NMocLwDcgKLUw;uPWK9sh9J}k;7-`#5|9qFwC)j#(tp{cDsFhNlA(Bhr}D~2{N|360 zXu;?6b&Pf6$jVR+8WxraWpJpcpMF}qEsJ|*Ca8l$F~sp1(`rq!Zp2`*Sa$mTez;He z*(%fxuE>2Ms$i>EuU;3w-@i6|PqR9EH9+C<%sAV&ZPT7Xhh^SjQZLilb3Z?RZbd%fO? zq2Sp@`D4Q>RK|GE2LO>m=T1vYd&ckg^IWW|`muKI!f6I~Il`~234KCOYLjZW~RDz>kcR{FCSS}R@Mk^^SYS8$v7{w*_>}Sn_u^MJS#(iGbY4+HfDgRDFDNA zsRj;Z6A`=3X6sp5S=qO=w6v1}0wA#jOR!{{S7^-MVKSN4c)i{YB2pBZZ)j3vz1l~t z(fUA5G^S7KL$ec3Wiy-24vWRorLwZJZAC>zi?Xt^_R(%+Hk)@RBqSU#nM`~Ae*c$c zWn~DIY~wJ@HpbKXn#7^!Tu22XZwe57h8&_7!ywh+1>LAt_(4{o1sNt-{_>un`7ZAB z;8=(*1wc3rjV(%oHh@Lm8%hwQ5$q-XClBMt{|!4c5YM=-YKQ;;002ovPDHLkV1kwc BiRb_T literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round_adaptive_back.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round_adaptive_back.png new file mode 100644 index 0000000000000000000000000000000000000000..8d66ef758d8f46467ddfcb0aa6afe84fa95601c8 GIT binary patch literal 2199 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 N0XFBX8!d6k{{>_*_T&Hn literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..becaab82b7a01ced63ab5009f7e4d406c5c3e01f GIT binary patch literal 5212 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 Q``rt7^v$6)x-iuL01XdU(EtDd literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..6606f745cf9db1b9cb8cf5438ba24a3185bba532 GIT binary patch literal 8131 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 L?L~#0Y0&=wb>C8* literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round_adaptive_back.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round_adaptive_back.png new file mode 100644 index 0000000000000000000000000000000000000000..156f64239290dc630b474411d14f7cf1fab4c225 GIT binary patch literal 6513 zcmXwec|2SB_r8wOj-sulQfo^~EL9pyh|07@Fp7xOPN+!7p!P7fL9Ma0rAUb_ttEER z+G%PlvDDatqGBsz3AJy(^z-`u{GyHF);G4t9*GXVoje-h?sDJM+ZZF09e=CXwL_|X6r3bk&X(5h~=8`yu~ zmFsuAe@f^04C=fa#~64W(*wq6CiiSloSy8>s1x4Y$xTO*MZbg|>{r|}E&qDhb32Es zU#|8JWyhs5q?3vEw&?Vd?x-*aq+X~=TG>TXG7N;4zr&IVz`tOtN#KkVNu!-ZGGqJx z##}zChz3d#@Y^^0QacXg=Pw(0{T!23q+K!w+PN&kPzzA0!W(p;UP{!o1P@QkuU|TB z&Vo7TR8mq6QkQ+vTm4#9slj<6&RcQxKUP76t}j}$fi%cn8mg3CPt_KnV(?XyA9kT@ ziz0ugN6;FZ71-XjyEhZ&&=OgNy}83=du&+KuLh<+SdEuZfs#iDGkSg><04-f48>L;B+I`p5K~}FK za(DV!Y3J`4wU7f}iX=Q{tQCkMUgA5sJFil$z)!N84#BRNyE781H1{q!?42hDl306y79WiKONy9f5Eq-Mn1^99;5!Uit4-Bhyg{GcfDE)p;r;1#q+J4FIk8p~b=I{3i~!F*OxN%4^30WA}; zD(HM=FJSPqh`b#TkQ0(%s(f!1^95!R@`*FZ+!i<(>q1G0uzG=a^!D3|yu1=#@Mpp*YZ@{$2 zBb^J4cg}D^76`>gXzMYrLaiw2^@V9By zRn>SEk)h3J+luIwc`wFaUQK1-jhaahF&c-uH-##{_VDd-`friH#= zL9d0L?$6D(j*l_-92q(^gm9M~$&5nVfB9RUUwU<2fN9hCPB|EI{DoDByYK#yyMV*e z8|q&|Yx;{gBQ^f7C&Vm!0d7?tXR-U2B<`!->-pY7WD)a)yjHLF^)P*9O=;B$b!&@$ zvUjUtR`0I$PKV<@?O)-*IIQrG1FQ&+st^lZF|=`G35eJUs4EU`}KITd{RwB-fu;w zk_)Fqr|}b|y1n@2iP|<3p|0Jdjn?$w3zG;w7d}0QfXbAvGS5;6j&%Gd#%UJQnU17; zeoHiciXPkWVGc@3dViARH~i-XEH1Gl>61o?q+-oykJwWz zP}hw)+kic802c>4K#atN3sWeMj)|c2S!<0MM@e&wRG>h~F&A$~`RVhsUy z{cF2(ziyoToC&k7Gm$m2?Rq*GOWLE4IG*AVvir@QakP>u*??=1-@5jL^^C@{ZA1M9 z70iBoaoOOFaq}1>?!Gdh8Wml<`}WhbDKU!}=xr0AcuoJJOk3Ofk`zZ{h>kfIG#)K| z-tYz8hG`Lw24C-`W?%mn)Zx39$=qJN4f{yR=W6>ln9=RK0GHv$VZ3U+a?ep1xE&m? z?X2Tm597Ix7SO(?e}3U=SDL%hJLw{}m-iv{WGqCj|5ujW(R~{9hpUQEMRAKJS(X$_ zzhD*gD6~b~KGwvnHlqH(lnFfWGzy--Tqb|e-A?UZc z&i9;(;?j$X+;dAU{k0BZCp*Nm7^*yQn-M202f1#3B&S7%@TKFwuZRf=-X!-Ai}lD@ zV`XoUd=7b|`65M?SK$&@Asq$3QN8bhg*xABl9_c|ZJP`^0ODA&cg@*RdSsf4>w#D} zJAsZ&rAe93)i``kwj%oYC!p4tW38shW-ZBvx3GmcqZf?(kC|5CXbnQ0hhpc95x_zr zXeX>bvN+MpVLtg(QDNwS^jcI_jNBUgS?TC0@5Z72!gm~l{jq0Fe`g>4*Hk$40?Lxg zd?RmOV4$nt#@zX--=wg2F>6SsZ6^WXVuDVN9fyn%KhX@bnu*2C60fS5wk`kdn_-6E zN~@g(ND%Q`+_$-+c{w;fjH!_NcB=mTfN-k#RGr10M5zpPs+$Ha=0%)rQSVBGT);KG zM_dZrNa`@ED|OP{@L%u#q8+a%phjeK4K+p7Zxw9T?Mjp_+d!-+rVqs)phI*Rf|*w7$SK?!4HAn|*x)jmJdEP}S<9-&16PHaR7O%*_J!{zEpI{rMCSCAO3n?*&Mf;ah?GR*b}>x zuVkc6J9+Y5Q;hCbF%dW!cscBS#PpWxzBq27)mUJf9^qJ~(T{O~hd$O-%f3-c= z`ms9gm6LLcFh`MG`>TkX4rBRd)A`jY%$3ZJ(PY*ErVn+BM2}7`C_vFx_BG2lSFcWyj@0Ra% zkWFsd0;0ksBk{&#+0cx-H`YxmmW!(QLGa;!cg30aL|*}rA{-9?p9&5#(PiDZ2s_H2 znC;*o4Ertt>TiZVDZxHeRV>h7--ySUrN)|J87B7&O(U!H-he-TGW}0Vnbab9{7hjr zhoYRZdgYI$Po90z4}u!(!icE&n>eJ5Hob4FIK^-$okDB|>57a?2tVmf;aC+?#eHt; zT%bw#s0C$gaX!k#ajrL%d9pLx+yV-hwF`S^{){5Lpp_9;joE0}K0( znhe{2Kx`7LO2at2P_!Upb)i+0vMD=`#@9+I#(uGzD=(l8Pj%@SnX*SrQ$?CnQ){Nb zJX;Dzt)l3wptzdiqG*1`pOLzW@@uB~Rx|MISb`G~HKO;_yX{aS+~?4_4#1r835|N1UjU z0~mf3K3;Q#xsMxQ2PDREPKa=^j_yOWCyV8XG=|9zd^F1hpMyXNz5!Cb}~X9}Y%XK@_=cYOK9vCdoqO;(Lnva_q;LX)&h zBUh~p1sFS@Kf33bUr0k{8FJ`!H>p8E^`Q9nO?iI8EDk8yhFj@wzC53-@swkmgqliv zbHlDmH&D`_^jOe-7F{LfX`G{wZbnS56yor?QYGpU6)#75^Q9-$||`uIgJ8(F8OwQ>oSm;hFa79^QYwZurEUF6eRXh#TyGd2nKqJQ=z_ z8$)Fk2D2UX-tnv!^ht^%VZVF&+(=voh0LEtAuK0#X31xcU485)5{8fPbMKv%>A6~k za2NQck(oFjZ{@@?Cwsw!dig06L$o(3TujzARo?r;1nv=Yz<8o4=OUCJN-YrpBkmIS zGqQA)Oer)TSXPNzzvVsKt2QGAd;{KO@WT2pAK)am7mP|pwgLg{zK($Rz55e$7W-Md z2@X$W?{O_9NbzmF3QHSEeCkdARHF_MQGpN|_VbxV{%-=JGCQ;{C6!4DT1=xx;{943 z3x89_Mi*7yVK_f{eFJZ)exFuVnerwbKk? zvsB!yNYwRd)U!F)aLJa{OI)0L7x@!qoeC#_`-7IFts?0fK_efVDWrAtAg(L$Qie@y z5nnNXi*N-n_BH0c6~I2t#GPBHrJ3@L^y$`TuZ!@cSi292pB=)*`nrb`F0qR+G`o`o zehZQC;b`niX_nFukl7ZRC^ch=U=y^|u~iLz^2%$GS$bgMPy9=mHc0F0 zq^roAoDNnWPpRJ3U<>zRtx4=m2%TCpWK1hVCt;!_)%mj%qdJf*{%cEW331>64Q{*AW{bu{#Q}!A z!v_7vKZz&zm{m~~odfhKu*(1;W0t4DE$t3x+7Lx(A(mFG_CH2cgLM~Z)#75 zRus;FLH^Yk3H*4z%N%@rVb@^<2%nziNxO3eJ;!jA><5~p6INi9Dvv+G zv=)1Q7m_ho=CC9t&sU*I+1n}-)hbbdL){*by^0PTPvkXL+k1ExFP6*yr3kgkjtz-P zTzNzxTVBH!1dX{TyUm0ixgSDk#M;oS9Ht_cgxFXpwaa*mG1=N{qn8>v>G$y*EreY6 z<21F|59Q9)DXue8@a>cw3_pq`xFX^#>Z}xSC8RR$4PDB;^B$A;IlSZ?pXB9Z@kEGx z$|JumnBc#6TvWxc4bU^P`u@e2Q!?IF>VERnz2ub)5w`eWLIeLI`x`GonkY#EfBP@Q zw+j)zlBN^_cH$l+HkQEiL!;Gc@b5k5=KSC3{hK;&5H|q{5Nd?H(OxK$0Kc*H{1V)I zIIei}CWF}3>G2*xHy56dO79>W&THRAZ-~)+BheAgm*wZ$0=vw(81UY~`!EJeP!*^* zO~p+~6Gij1mV-r+qt>|;E=WdVmf5lwjLEV0$F@&$&<($kkWz66-_aDC+LJp=)NbIC zJ5w~s*WK25u8j)>>0e!Meu&*H8k#8XeHC1+MxM39jQ0J~QJ}|+Tr=>$lQWl8sVzJG z$NIi>=vK;t&xCxvso+z!rs^?#RVC#fr39fgM=ibr>q|>iew(5Q88K1S&fFN>og8xr z3u)Luy{dhN!6q7UdUTxeF)M6xeDXYlTWv^{2LF##n2Uo_`9CDHgfg-ZQc2`{ z8W`y(|N6*yevvBYGHj1bb!{?xhv`gH&v+~9_mFg4o9qjpj8{(^48e{UhnhESE4a`N z)d57){@PGrAdGp##^pZgMi5qYq0HfDxlkfj7J%RZqLKSw8ku*JsD3>jz;09cN|1=Q(6>^99CF)|#L9PlM(G*%p4}0r7R&_^Y{@BB1Lu z?Ohy$EH+IFAac5daeX2=$@bBW%+rL#u#FDc&VZh|XKyNOmG8vC`9i=0H6 zI@RfkPh~A{tp;Wesah@(Rz}l(CLneBqVUGx;%1ZRS)S1{eHN8}j;feh!#ld=>~4*s zx54afA_@1i*1K1KE`9Iuc3vCzv97DV4t%bpmnRp>6ZR8mH9PQL)GE95#P!N^&K{A3 z-?8()Wm{`3W;n0m$u@w^x2M|!(6djc9n6H8Za|dpweUZ0=3`5nA@6*`aMa9mjS+oX zswGZ7TQ2q0yVG0NZj-nOf(DPG?s5aUp6)069Y1i&U6s>9(bKx8fs~}a`2cEKP!5XfB(5MDZ#OomqrhG8cQ|`cqcF0|$ zZaLp*i54aa`p8-?EcXqtHaj;%J>xyO8P;xPEL09oB$Pd4LeE&ZCAcV*bz^ ze%A)mVyAkWa~AwvX*GRMyxC6F&yu=vaF2yk*Nj#sK6NUoVWsBdj0%WUp2_P!-gER3 z-HvxM=ZsbdOzi7_04$3vpv!Wk*e5@_FK?hgXUzcO%Ox;LaN#;gNr&Qsh#seFJiS?# zu?PA?mWFRhxzl0&UK_!wZB~iNUL0tu#mBpLY&o!is`)%jlk5xDlvhg)Ul!qZ|Lz|m zoBFyeYVFG>U!IQwC3Cd&?M@x?d6K* wbPP(>GB7?paxW`<1hsMSb8MOl+6;TQ{oqtZ+u^KtfP(lz`+2Nu?wdhT!N_HYIh`Xc!=jlvY6)AvK8siogaE zN=_uCgdrgz_3ZZtJnx?u&x`N(esh1${rOzyTqmv*f7jfQh4BjGg$oy0?ifKWspsT> zAMg_OA1@-eNj+$uSQ_eHs2LJiyKq6U?+#SQ`kB*a0mH{|8=JlfnNjP}v{B0p0k-7) zyPTgy2%MaJfP4L>`pKr!7fIUVip+*x-F4%NB?JMEJmMw#>dc!~ka5KU?ScB6O1^)O zH1}Km=XQtM-RF*njymykr|*a3QopDpi-IFV&nZ7bll|C5MF0kRU55PjbWAj&FmMj{ zOEz8*4af%?pw!65Mrgbg-Gv6CVPdGN;+Pk}3MJ}ws5XEf^ngVU0F{4FBPtK31y7=X zdU=S7pgdWCFF_zSf&t*Z9up17)LR>vgnj{32De;spc-+Y(}h6+P#lQx?W?XfVC)JT ztr%DYl?<$j<)#Jae1Gx(Pyg?L&nm>G0Fej0u0F}PG^mjO$w^Ur1P#l>pRc^RG4eBt z3P08!8kUIN{6#s3@Yaj5aDt9okKGNgM5MWhE#wiCgi+_bZe0oVAVQ7Aq)Oh@<=m-I z@@Z9-MiD4b1$qAU-TD{McVdcCgI4gtjhc6bp~g2rvZ8_@q(Z|YX+xqa43LP4CHJQUm_4J;)~mCg2@GBvRmTEehov@0C%Ah^xdOGuc`F8s;GHh;%C_V4%|8NWPQsuGNJDdrdLJ$;9jz*t{`)!QZ1-B}B%>4< zr+1T5&M5o8_b2s=w&_U$Gi)34U zdQi3NRq%oud|wEHZ^pKNBU{+TFeIWSbEo1d5lacHyPS|vm(h#Amkn)oYHdb!0C@`h zpqKjF%_Qc@e8*akAF!&z(gJlp+By35mH8Gtx#196=8`_JiP)v74@N$tBHJ^fTn>FZvyi6!f!T zhm9f#MD|(sZYSl;S#Pahl_8Z1%5d31c!7EjzG2Knjmq$lSNhNs>zg5p77+tP*6Ere zNpOunToOC=9FCxa9NR(`DHC6~C_mWpK9TNmp~!LgV*I)OE)0rW<)7oVwb1RDw2zSF z(+^PhaRe`b!=mBetNCT$r%v`St~Yg#f1yLeP60D{IAI9hdw*5ys%wv1dN$Dt8YYRi zWE@nxwMpBc`A-36DZ``8wsT5Lu$tWCWd^B#L}5Z3umECh+{>Ve%x_?yyL?;qp^HX7GYj zJS^;t7FjeZ@fZ*g*K>dWHhlj=#05-N?R$O~n7>NHM8L*VBzmI!+?TY$a@gz;d4j_V zu2Y;xXtAj;fVM5V;7867yc%|MMx4M)*!DICqb?4$#}6-eZzteqt!7UPBxIm~XixMB z7gF+F>3uM5O5U@$XRQ8v2k_8I+XX3nK(X z%vfOe>Gd39B&(j@aknstSH^DXvSLG|Lc{%BzGVdB-2!n5-DMW=Y~ONcBH_U$Poy5Q z40}raW z!?)TrZ=mEW!F4_%tqd(s{|88$FvEJU@Mt9|+CFHXc$*1Xb9Z?_tC_>cp!fN;y zoxapG6H|NdhJ05}0XFp}>G%3e2!F4(SWH{yRWkChf#M*V@BZFQ@w%M*__f`Vx5TxT zYq;w}M*^3U9ovq1xcNfKMO=heb~K>CUAGbZul!V35s`)Y&BWOc(~IRTohA&aUZ*T7 z1Ty(_EBlZ!BUPNI_eQ$-luMUq;bO|Z2ePk3%KieiTGHUIR&7qVaqyI7zIrb9rz(;W z#FeOqY(Tn%^vRl+89EBBQUKr^vTGfmCNqV9My@cNH46tF+_-U4qICWO*tA581}@NV zJA!r;En2eMXNiS%MA~FDgtVa#angPv*Vhz-(aji%9?yOP&zYa84K2eve-yX&K2-qu z@Q)P{_j?yXl9Q5l8#v8~cY9xWL34ia@GLC)qU?2f4iEsuVS#omc01F01-4Zy7zuX{ zA)hF1SLn~aF&k#ne%t{V%(7#BTnDFXgO&9AMeEad|20S$pHt5>C-QW03p>KFckQ74K>ex%meyU&tz7Cc` z{i}k<$zr9nCiQv;WY&tVK)nJpApDej)$oV^vzq-34{a0b?Hf zmPD5%kr51Ux4UdWm~Z6!PBUcfTOcNyM=gyrq?FS%>4!$#H_qGfQ{vNf)g4YDE3?UN z58yf*R|G6a7kh{sId1RZeIma~+n-HwX%?LoU%&K$K@yq>Tz6vwS?nDl0FhP``P#Y^ zGja1BI_>#=JPBW9{9~-0Qfcfxmtty zmQlq=H9kj5A;Ko5rUCHi;j1tNPuuZFz}Iw`L%WMU?tR~F@UMP0&Fkg6!^TVmA2VA8 zCAFF)pz+6c|9)w>4hN%t2$LoxrLt9Eg|g&tCJvq~pWa12z7i*x_2#q#K(t@)5c{@9 z&-7W8DX*ZxWHNZ_TaiD!sH#|xgn809-Ur3q!j<#hrhO1U;yo9iYw_skGmQd!q7N2v zTR`cu$Ia3V=~I6P@oS>IXEKbDemTFzP~=Rh?2kR3!*M zs(^9j@9WpUm~qx%#qzmkxIT1jb)KYmzsA9O#3nLgfJE|MB&C)TEYs8hsg{J+83<#pN;6lWssSGu||1^|me|AtRN6&$wBAD{z zsKyd159#^SwbG%}YY>-S`jlR*@i$4@70bzWCjF3p935uwoxRjx-CELEP!^*tjq|2^ z(1vGwOsHdtTN9jqe^-pwN>kmw2)5Sq2L~F=E}2w-{WSF}%Y+@imU4=^7js~RkSpqB zN9pX(;MG6qe5H5hzLbDLr+1oPyf2v!?vFmNFPrGSJ4QdCcKQ(DO+vf_#2mFN3>U3X z#LdmcV+`v!aCsyS%C&Lxd~tmPK>9|`0*@$fId1pGqxyvS3cE~LCK_+QC~nc8Z~bBY z_jg@6R*ZU}E00GMVbCJ;^*2V_6Je8pBS2&sp&Ejek+^;~ zBP7mSd~bt&Lv3}U5U+rgUl080?!C3phyX*YL01}w2RfWgM&n^)fa3^A4{+oQk+Dho zZcDA<#9ejX;FUXq&z+$^C3|pP_aNB{4Al13U)m;{-CO~nWP6X?L;yDN?Dx|J1y9b z7L3jJG=x3^>w2!u40$HKh|hCh?!OD*m%+H&qkQYil%3VUz#Y>2>f^T1xsm6-Kq-_j zdr~RS^4?XS_({DKg95HRFzVTDFzJ2_Y|{>!;wzr8T!$WHPhx$g7(Z#rqy_%+a?l&{ z!%9g*IXfEIbVokqMc{1l9ig2>%GnZMADCP2TiL&z&iy6U*u^8EWUK~QD7`*!_m}dNtF#(nb5Nkt;I2xCW4hJ3-73j$S^LJquRSvX4 z$nU}8MBAf7zcQ)U=VnXrx{lE&*8{CEK!c#i86fS&t|l7z!y_AksHWdE3BZ!2w+BD0 z1J!!S&eL_T5AT&6C~PN$*GnRCnhy_;5kP~2)R|WGa*nGQ-9agiADJeHIK0ZiBzpnk zFJzjajFewEUQHSmx78dW2jZF{>nu#;lO&j2sThqsHZXWnWIiLx>$Q*=@xkMDj`J$(pI5p@UjoDe&l>qs}$qrF*1F9#DLz!^yE1! zPZ56t>~O|>An|LGsXQ9pb6D%ZBoH-xOb%Z-HvyDJxLVal!Im7WUM+n168|4EGV_?~ zZ2^{b?)9teAf#zuU&Zf%dVpAWL0u3*BA^UE7Q(Id;Jz8_S?`mwQPcXe2AzKhd4cK5 z8>2$X`kh~2l*GF)0pmmUlEF~iTXKA<@GC;aZCQJKt>Ye=9rQfu5d)!Pl``%&n>Mam zptir+F?53U*vF5CgXvL5by0f=~Dznnh!2Q+B^=YC_<6ub$8K34=Cf$d^j5I9my zxKEp-l8cr_08(b_x-3ZNRCoPZCR@^#E1aa4M7Ctjflm3A^0cHSgP@+E`DZ3#=w!SF zbR)a1bLlVW7-%3fm}MqEo@@~8mlM9t_^&dM;uH9e63 zTrV%9&1?)86L)%a=Ydyk^{Q`GR#ePhgEAZqb$RmkYBZ(sM-llaC{ zP|WoaMsi8c6qL!yqn)v5{US%2ewmg0O^#`p2r`0n zDI0XfNYPSP{$|$YU?lOk-FbkUs|3*`*uSi*?3mSTyDedySx4TwD^ma0+mdA#(6%z9 zhc*#r-5N+i|9Iaked+|BR3xkxCrJdlzRzgWjZ0^jSAVC!UB`|JCy?`8r2##v-R4>R zj8~6fZ;#Xfy)0>icLA=ZjPv<+JB#cfvzTj|NG#qe$NIm_TESqjM~5u9lwHCU3dKz_ zaDG0>G+`i22ZI7R*!a?$8FP(WOgA3~UR>H$5htkDYrZYeC&SI4jV7}BTNDc}yfRf0 za;Pe{N$~MPt~sc#O@1c7uX?s31(=z1YK}E#xg+oNvL`n;<*EHzCR&w5%zfd32s7sQ zkq&Ggisqn}lB8S#07Y5X-ot^zH+}raYIgkVoI14ERbJC)jZE0qoT5ig!7iev%bUUpICBs5tEPtzIpo?KfDQLXT# zOq_^<(LcJ{we^^SzW?*etmbG|?x+HO58o?pRI+bX^vf~E8?EsHmN1%gadUS8J@3BU z^DVt#vHnLlqvJs=b&N@$zm5pFpE!vYS@@;jU_AR?u<9u^U+Wi0XFjjC_1K!+)A*#| zF?@V|RgC8bUji)YQ+S+)!aqZ+k#~V;6=%2)YP-;09eaa)yWTwdn!Wh%=sx0B?upgj zQFoB7j2?USRzWBVwyJHp{IWb{RkIvfN;vp;(pQy3dA4+|Uw*fg{KL4|IG8QMy>WFf` zHLb*QDCLpM5p2Pp^qJc_7W|JkT&jMcu2!5rc?;!VEp4L>mb}iAKbUlFP5ZU~%W5T$ zwMaY|U)_Q)aUIM-7-o@jRMM8bi2f7+_9W0h45= zkgI+J)K5sIyOiOcGZjqQ@RUxG9}aHlDKbjc@)MTgY0JAc3Y85^jO@0Z$H)KWF{8BcQlEzib#M$8DAJ z?yxZ1(nO`a4xP>#pt4lHBfZIisspJoBkR|Y@W;>OypY4$L{-Fy#Le22P+7pB*0>Jt zEnuWEPxp0&vF%x4%0~fv&+1BZDX|(FP&L~uS{2Lvb`3M!ed>1q*Ido~XbK{GTs4)Q z+zQiZ@#ome*&)blD1LkYtSK7CnJ226y(k0?&&2w!8Dl9KssnTOmI8`ksdDWGAo~>Ou7LvW+A=mXgy1XcW8^iQw6wN1;M#?^k# z%p01?OaSbl^(%Q;R`BXyM66rl;kYq_vIPspC$4gIqCMCEjAE7Rg(=%4@bS05HM=|_ zevzsPUle%^pt}(g`!FXw>65YG!OE+O0YiWihp1NsbADw%f+2Qmy`OTngQ?{Q+38CE z$l!N>b5B=8{N64`mntRezP^c>r&Igzjf`>LQ)yE_Z@+K(jwwQJ^pSD2E1!!4KSpyn z#ab=WdZyfo+K@ew0881txSW*JXCN{WWjrXk*X~+y=A@?cJNnKR(#)O+IAK4`V+y{w z_UD<|oJhPlv{!icz$b{Z@TJ1^!U)f7gK=t(YMj4~Wr7@7J|dtq*G`F)Bo!iKMk4xL z{~GBl3?jVB|7RVLvCO!+2tG|%+cf2b&iMrz1X1>H56p=U*u1Ty14tF%Ct5=BDW~Jd zm}@kVL$v(|Kuh z3$pq5Sn0}^-L1O22;=ojdYTgqa(zl!Rfn>V>m*F5lDZtjgWbBN#Ww$XoYHB*5d7r3O9_-ZAbD}ep?OR9rYofN-J&Wj$vE+zQ$+_=3f}+-GiBcRT zi=n4(bZgDO<3NN&AWMB{_bR>c5$+Oe% z#_r74gzwt*pkKefjQ0>K`oe1BtA}`>tf9~!Kn<)OmnqGEpfz&`26YFa`f*muV^-ip zH8mFPn5(qNBj$9DzP&eS5K5*iEfZO9rM!iYpd2Rv9f&vKVs2N*4zR<<2GsE8rx_3X zg6?u*8j$dX(YU{tA-tIyQ6 zEmjUt?ahWhEZ>=$QP^8c>(37_fG1lHm&F(CF2#FnsCb49(SZVAV$(-a0fs%2A4EYq zCSo^p@^s&{e%`wgs<$tz&O6noVS>8K-J5Lb?o6DR2qi6fK)P2&CuWey^o{j&ed5f_B=l-|f+~`Yec4FYK z(}ZUnfpPBlzCdm@c)pFEFO$oFBSAVoi=VJP{H_drq<&WAh5KK>LfF2tHqn}hfzgd! zEecafmP}%%8%t?a=SXig1*C#yid@Cb7q2E)Jg2DbxNmL%5@G6zksN#$<&{liNFYfy zC5R_ugN-U+v(jiyy#vdevYF;p#xWOj{rcwdQXNsf$ks-gR)FHTu+yB~(0zGNDLc*s zfBmKd&xCsbVV>k`H6AMOSC4ULnygeKsx5xr4hJ=Buw6U~hl8E69>=lea=nYDO6sHR;mr{u{LH7)E) zqTU|@l-A2j$}`6+GN)ZA5%v=AYCq(H?exclL5ehG-SXv4zqD&$6ey1Is`SjKKN$rp zluGNeeTOO;l#TK!DA3y`S@_3L`{JIZ{+_RCV$Soc8C3+$&wnW Q``rt7^v$6)x-iuL01XdU(EtDd literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7ae79034af24431f96ec698d86b2e3d24affa52c GIT binary patch literal 8090 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 BE$RRO literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_back.png new file mode 100644 index 0000000000000000000000000000000000000000..0425f0ad063cd3dbb4c5b5e984f7f07019a6e391 GIT binary patch literal 11194 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..7d3d22a3609c0d74464c4e3968dd17cb71e34779 GIT binary patch literal 12870 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 G0{;UIYnx*L literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_adaptive_back.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_adaptive_back.png new file mode 100644 index 0000000000000000000000000000000000000000..0425f0ad063cd3dbb4c5b5e984f7f07019a6e391 GIT binary patch literal 11194 zcmYLvcQ~8<_kWkCr6@I8B-+qYt1W5=Z4=UIl-PU4LyaJa`Oucw)z+41rL8RpwW%GO z)+jY&rFQJ?dwZVG_51yiD|hZI?{mG+dB4u(Jfa!+I_>@f@$giB7wFHaQ`b*DhCk5tHd`|N{Z6ow#dyxgO;cle_xO0E zuS0LeyJ}S8@+pz*l)0O6MW^1qO@3|byG0*AKH6TGqVKG2E)3rJeHJvtHvPow@nKs1mRMm3`I@!rT48<46>`nCOOekbY8;Y3NMh-iVemE|Tm$c-1R!e=*753paK)89nza`g~eP{Gp9d z?VGeA)d=$a71^W;2`thbnyDeSXxp|c@0{Dv>S_Qy1AeBKvhF<`u5;mwY5R>qtX4LA>ShyY{dOuyhh;GN9?L$bhhxzycLD~f@|@5Q_8hto8gtupMt$c zFHs-@I#;=_CS8q_#Ch^0Mq9dYbkR>ASv>ySN>=Y$;;od za(rwYoc?9qc<>{KZ;17S71=zakD<4v?yMYn526$bc;=dlR?JYe{=v$CH-`nZWMoSg z4F0ui$QY$LNeeH{cz>GNZ37^hpyqB@`~RpM4Q*0ygY3~lUn>9Y&pCVkd7^AY%q zK59K)dNpFM>UY|$J6Cx~8vE|jpYUFt&*!!LHy7{c%NNTMOr@c7}S6>oq-uVP^)|ly_ zd#4U*!)!t9Q(Dz%;)?jMPi&@|dWU0*!*%@_SHjW6sMiX1BR>NqAa%Nn_j7+#(ercu ziEsZNzj~bUk6|)oZZLwLk8r4c63JAedUUuF7l_XDcdERT%^Xo#syNQ*C!dm%5b*ASvVnphd_C;Hi*x>;zg-6EX6%w9ENGW>=~|G2y8-|C#UPJVEeQTw&8@@NT+EQ*clrQYniRfOtv}qv$iW?@9&M{k+3na_#3n}30|iX`t3Aw) zR7V__B<#uL#jr}qH`vIyY%}B|D`OLul~DTlgoC^eW9|?S%adPOcSX#MRPs@WLa)XD zGbTf1xki`t=8TwNi?pR?EnCGNNZsX;WWy_wd{}#ngku5^7tGCLmQN{jc=siC@rrPq z$N#g4Cwjf;jGPxO8@fHs4=WU8h(v1 z=hwr_4n)D^sddd#fxgR%&yBx=%iYbiOKO!GS4QcQHSaC>M#cE?r*fylzZ(}nSlHBX z_IS}n=}tG^^hrEy%^trj?08)5gB>J(rM-v2EArKB7I=|1KP{C zmTSF~e_yNTOGn;luju8qX;X2R`Zp@f)M4KBB@ge-t$c*l^+csP+dSneAi@VD&DHPu z@o%L;4?mOP!h!K)WAf5lD4Sc~gch)fV1g#eY-}^LW|D<&&yfrMT@elCMEHd zsu32+VfJMi@kYxmY$9XZ^vbB8$3xM1GOHx~l91|CGkteIeYES)+|VdnUTFLNbz^Mr zpR$(jY5B7ejqtmf&5v@McUC~8&xR#N{Kp4#s)0Ka>kH=)LKPWx$|N=6hclJ17;gK+ z$X5@SeUiL0`MF1!55=)1XAi&rvpsTmA=X_2jeC@^4HD=(N|SkfLnY-rkt0~`ZvUv| z%mG_9>2;P-gz%mBuo2$VKhV`t=kcrDp}6Vp*(tnh=KK3-dw)YrC#Bonu3FKi|Aaud z#^lHLsuz0aCVXRjkEU4XePz4r!rk0SE-gu^(D$=48nWC`31KiRnXfCL>h6NMjw&U% z%PBkMVLCz*StTChWmUw%(kUm_9k<4ZFC0|5+pWZ^?()=(rqDK=mc3c)kTS+5k+SfI zX6W}!;}&<5?HJj%`Qe8B$x-?GaN&RQ2`I*@M`<>-uiaWD8kv($C%T;Wz9eNd&Kzoi zc9otloJNk)JImaQp;u@D0kyD>fc8|hKF(RM5u9B*dCLu9C2+Oz@yDy2p?#Q}&jzWL z>>rdGt64kc6Ul#NPuL1SVW_foTULmElu`7q;Q^*#qG!EM-r=cP|8{8B_&W4MISek8 zP@=^k*IqZFy%>jnwu%?@a|)qNBi9SCx99^wg|2mGebx(VP zn9BdBy@Nfh|LU=_t!?KsR`* zbECp<#@IJ?Nok(AM_#2DmJEqq59(T6JHDq&>VJnQ$TqKRh>oYFmscynHKkvWMpLdw zo^5k|%{`ai^$N`#Nf=X?Y+(1^vQ`fupqeYYy-4Ttj_ozS%E1>b=_-kWW?a|W8~$}O zsRUBE9GzuyY}}nV#*o2fj9KhO^vMml4>FI}vnipTrEMKb5PnH+Iw8rLi zrHv3)HD{shUEIYZ-;JfTxu}EMkJ7z7r0402j@VF~%8nkp?g0~ji1lCGQRQb?TO}`& zS!5S@5F~IB)#gt~<52PYafqE#W2QIlwXbrO_@!|D32{~{l3DlJs&{AUv`;DKW9 z$b|NndCgfu!l8^cd|h3;(E9*-Ux&k{8y zlsPz^nCHX3kKyqO58;=-)#N_zn*U1qNxZ|+?XK5*m=c@zB|q0EaZh>9LYI6XA-&J- z@L`pPvB@T&MRyFM4ZZIzGnI9KVhy5Ny+U35Cq8-Lh<0DR!FU7WFHy7jjLQL^hdg;| z>$gE=(BxxI{2VfzGuyAtHisXL3#Jv$~uPM8bsACgUr)L-B{T1SL5w6%GQ0N8fJ|dUHVs%2+lclIMexf zaCiE}ieo0_s$J|L>W@=vd&O5181xA!=^s7z44J%k@$81v|cIBDqHH?Mn1ej_OHq8Bz75*}#Ph+did7 zpY1ee?_QrE(t8d!sQK~cp2|cr+ZY>y@gql9eP!ui`1)9{l$RRPbo)iIDJe&Q{btL2 zp#ev`sJ+sTHYG(DB1||^CfX}FO+j)xnh~RyqxkTfnMZArD-B!i7BGN{EmtaSeuTkM zY>K$uzG;B{&@o6ZzKoh<(w(36+j%L0otUnye zl@T2J|B#cUAW+EG@tceTwA&X==+RbbZS`^dZhsvXG@8@U?%_vLOXv6@Fz^lAw)e}A z^tyDpZb9Xvd>{4`U6k6ht}{yaDZY&kz1lx1@etmmM;G-rVEwC>m%CGrW3W(#@KX2U zQCD@PMKl{X1z}U(m)_P@Z690AW2}U2 zFumcns8gFwpVo8joVB3-m)W+nxW=}61E0okv!f87Q{+K)%}C{dZd08P98mW~g0kgi z&g;^?ypF-kE-q;T==?({;K-k>-z-!N;qA7AGe(L@Y2(O#pDwV zT~|cs994Fl_&C%UR2vV~Kz#c!BbyVf)1MBeoXX!$rn6*c}*h-(B0<3$gB>dIG z?VrD2CdpF*e7797Sc6K_gS!M5Z?U66O@D%k6X1*g{7`$Oxox4~uD! z&Zv4{vM6knrjPh``>8~{%uiL_Fi$&f_MTAB#=KeN>dBdqLe$nf8%<>fXhTq+(M7gx zp*Q`I^i~m9+G}#N-g?eX4<5%uyh3Tui+=b-xFcsHG&YDJT|cDv zFDR!*J9%7eE@FTu47Lg19@UAkl~`o!{bTi5)atoMjY9xe8UXUdFoZQxzK=047>0S^ zdLd*9oZx}Wy@hZg3#!N^b!p%d&GknxY zrFzOfOTTVzeZw?bSlq}_$oY-IOY77zw$j$k5gYY1GqPIRP2I@V-7P4YM?p_O%ZeU-K2-S8F6&5;gIjKGX{EzS%^*k!G z?1>pyLU8D$MM30mtAb};w>%k4JfjI_ZpHD=IOEXmk<(oVLVHPq$P3v7@BSU#-CLmU zmk0-iJGo{|cya8qaxM2r?w)u##E}+)I|sJxX_cau)bR6|cji%bRtrGP0Wyn`e!Yno zSnKZI9^HExGr+<}jXi4vvXlvxzuda6s3d7_odwPlRqZSPs4@L4q2utug5HD{BK1>D zx{4-2+cA_yQsAzlvVdD_;uH4GY=*YGOT_u&wTi4muyZ;!5TA%Pf~+>ZuS!%U z9%T!CuN1C^?x+Uwa@0Ytx@fNE-Wg;oee5NRmac8dtbi#EbN-uLBpcq@Y+L!HSVc2tHUA5r0F@RE z;+Jl%ev`B8r;Z+C1TBg8?|#xy!=fmug7*R$SX+Pp*8O$3HPG73r))ky_$MS&!+u3V z-y@Q}cDpBPC*4tZHfO?0!H$~rOS&MZ(PnK!#hkF&5aK<3O4-X#`nYEDQ6!C#yNN_a z(&6&ZK1oTh4zu%(1D*4=PipK5#5w)Ly#h$~*=0I1g_~y=-l*j!S|;jxS0NW*3^g2Ml7hKb6=@X*-bztKRCj` zRx^)R#jW%R?C1}vqPH0?wM%luG^?XCb*x>P#5PJA zMx5Zo<|MtgKHW6yeiNF_7rqX?L<3*1R3!{^I;zUR877%PNgSf0@$dATzD!vD&u9B* zoGoQ-xiv3pAj{9MF5pqGalIUxo!44JY3+cWDnzXzYbRgA=(QLjcE@%(_&0mIWFV=h zvdWQuOavy-Gu9IhC!;CS6pJ=Fcm&Mm+^Q8(_+xS{@3j=osoH=CD@V|>rSerpIQN`C zfBynt!?BJwU9^^#7ZC}r%cBBxGy9)^9m@Q?98DsG=XVg^uwwbDB(LCjO-yyV(LmZC ztGa7>mf#OYmyjx%I@$`{_f(8)&#WkDhI`{UHm9$a%T__M-;?e-rMu%M^xd+{4EVXb z%f3`vYUgRER=61KPP3p>?)tvRPM<@JzbfW#%C#2$EaRyQlDX5~LKOt@Q!TVYDYM=4 zp|g44A&SOnX_=U{5Q>lyI^AZPlMB|GLwx}-gf#uxB9Fm6p>S*83$FqX{%8hPjJhNx z)rL^HQt+!eBV2@P!BvNE)nKGN^TsDQ_{B{Gt@l!%+`OM4NNK>3QO`ISiZl>KalR`gU*O z`QND~%jML{-R8U0D|Fe~4VUBT8{s-sqSmbAp0mRVJ;~tZ*R~ENP;#uJib5mhzUZ=( z0DjakZ}F5lCOrAOnJ}AwLRR+s8EtB;bj)(ZnYnUz8}PmK=YQFihfr`*j&5qrNO!Sa zPJj%4fvz4n7zmlq4(VAYI09_#w{fT*XW$FXC0?E3Zi-P1w?E%L+cb}4R8{2bCJ`${ z7B;7iF-vYNy#mvVB3xkAVDr|jiHbAljnBS96y921Dz_^(!=j`z7p0bzK6nt?p9 zF-f?E$FYG=z?f5-{_7WFCpYzk_j1sMySb4lH(-ai=Z9>t*8&fwu7%dxs)NC6)k5Ds zva1ZoLg4X;q^`c!2ssVLqPr=N0p zXcOKXF`-C(YS6B<(f{7zrLS|p5$nrhfEQG-!#=xxyu@B`i0pjLU zsz-4u(_qlNjWVj@2T;ic1cjs_In7a-k6W|;g{|0B(g=_E`iPB1f(s4_q8jqIMcII3 zntBB^sF4X-X?Y1vF6Cqm#wPNv7`;O!KiISeV)UAz+?zgypo?LVCjVtRv*&>DkNCOb z`s6<>z0}p{s9aD%Wtw2&bOk2^)H({2i! zZKd244fz-IS>NkszfYhn1qp?>x;8?}o`0;acPI6pze>u7}t`1@%467<`4;XgVlhH}iT8zfckbJ2(MeC7~8MAtT zHO`Y-ebDhMju$U;B@8wayjlDEW!#}PzHJhurnjy?717aBn=*bxIkV0R3)`Ls+NBgD z2x~0<2_Y)dM2Q$pOJqNIqH&^7W(;_`XZd$c)ZKtdE#=hEh%bld#vhDL^kU<+8Be{H z3yxS*d~LYqjJ*N>H^LL|c5K;Ra=Q%Nqrpz2k@k!Iz(E6$bQx#Pzd!rR-#5Sjy2q^d z-83I!1hV?NntS%@%rI-L@2iI8?om2%y?l59$W0AdDIloL6KvW_8C zbD5<9`i-$N%ogq4k>S1g)nLuq8ahRsDrB9@)Z21iUWDG=E5a7~gnE2zZWykhS@+xZlV8tV+)fMx5n~m1^(055WpiV9j zB%S!ARuq~IWs@NNBQnm$B|!d(jD0mP@m4s2Qqe#1L?#}QEh&uO+B;xgLsxCO+%m{y4rxOH= zJ-Y|Z5BEJ-_Nsk)EV169q`IPBa&|*>i}b>Yc9d(i`w+P*DvQ=JZfFbF_l6H)t zWo@5RVo@F)7XtpJUiX`!eUo(w)7g>-2MNFOaK)nzK>39=9u zkV+rocHX+T542EYQ&(OqU~tM#Dz~>#;P6~!-2XBk$kp=^X_%Uvrs^snnv7iseM&ym zQrN+sT6yhaKxebQJReS3FHMo#-g$poJ|b#@6i2BQ5E76>_ zwvL-pdgn^tk$DpYt}%qpgUsJ#0H-SdF)6x-swJ>arZs>Dcn;?M*a1-D92TqrWdx_T zy_Z%hJ;Aes2GIWIFr8mB89lwe^Cu&1EU>-mu=Kh5%5kZDSg+~$!X36m^BZs{!k4bh zUoKU>UQcr9Ics?x99Tl~KKV>LfU4J>isz)=qM~`4NV7;flBzxP4s)a*Y3+&QM%iHg zL3laDfhTjURelPtUC&JI(2oB97Ail&s_LgxkTCU;%&QJ&H`Bs%cs1p|yE6cq5)W6b z5X_t2vGW(rlRqz-VT+!AR0GJ=wHI?!VWWIn~X3$^90t*;1i1l(KzOrd`X{@W_U%KCS;5F%+|9)t^*@4TT22$jv zo6?xH3vUUZ(09+?$)*={H<{AgWg4A|ocUyb2Su@VEgtkmFO- zQxf$7AMWQ)@l8sD;y21B>wbjQwCu_G`<@m8)QlBP#6=oLe&~ zhQW1HV#@tt#m+%LZW>rG=e2hTo}f8dr!`~Rs!P+l`y|GC7;oHqL>U=q>ir&ND}Iwk zb@Y+)6I#C%R-FMz(k%E=NVpDEI)i4QeQ<)IcTcecDURkrGrUyU*zRS&+;R6*^es_& z*%|Ps8I5WT_bbES+aBcri;xKQH{y)yRy$=C4 zCjM){35a>>rd}|U$w85l0OU0mURBu3+H$iIMg`9OSpi! zuYTHRuKD&hh@!^1+GL$h{YvDBV5KH2o&RQF{*N^g^z`+%n$fw_wT5(s{t+_O244rOAodIfnyizI98ZEKa24+2Q3+|8B=oA(ydi>+W*2b+X zlQ*Drgd=J?fnEAUaCOQJuLStloS9@{FaODRSd#pN)rFzuN|t`01N0cO%vKpcM0&WIl|!S& z$YZohrS8#8{h#Dbhw~Y;Is?Ndo1BTuY$RxsSYLb+%qI8N@~ttS7$204_QWWpMiIJT z?$zo6kGB#S@CI4I{bFFtfG6uZ{J_CM9+h(5u#LN$eFUj{mn=0O0K`?+;JxP}yL8zaKmn{pJ* zJ9S_|0GV`(yj=4E={JH$>{FC?qsrb6lBY^xaL+7BLnf1ah*Uk&lb!6=j^pDJ3{Oz7 zj>^mX4yYe_#f5DFu}5i3-_>Oh3ildgW86l4l@$YTWHg%~f`HNZ)3&&g`VJGa6%eQC zE=3X;6@3P{{e!)$>@3LX_sh^kuAi+7PWo-wdRkq6!}E1gMwWG)J5C?Jft+va5LDE=7g!p) zFS5GKR$3IJQtj_Qx_3;vp3xMfc2s2j`P0o>_RzV_T<>15SVFSzl0WB1kd-Fw+NCEq zpBK5e;u6@WRAK`4_p-kWhkBpKinF4=PWg_WeOeQ{&+Q+2rSn?Wb9Zyipx-j^CT=~9 z$Pn;mN;~e8JVz&*h2i!%TdZc-Z%6=W+5Y^M&cLXa;vCA0i8U5>uA&O*q&Rc+k;tpG za{G}IF5tmp3}{5Ca)z~Y?@Eo>%P(ID6exLWJEJMKa%jYX0eC`yd7xJlXF@p%9jd92 z5M$=0d2&0#n&%Lc^293uk#^Y^AbZ;c+j(~3cA-IvHYZ(;PFb zKSP^bW2A&Z>r6)n#%?jvy+_h02$<_pE!1m2WL0&>?+L7@{m*6<(+3yaSg_e3Y0<%M zPDM$jay!a}p$gzTdW8AspMzzViVfH8u;?g9% z{reQ%``NJsd9%xfOWS-rz`VSMYx}diGeb1zt>buXF-G5ALq7*q9C+v>ru8`JGAo^? zGtM)-U5buKPHSS@(Ycx6gzdyihwgP+2{d~XAFVCOwT%TN5uAIDc-uL_5C-;4y*Zn&8iPqOcQjxQg-@0{+N=p-AaO)NU z;@=x^8-K-W2ib%FAn-6i!fsU!vv1$J#eGW)p=RW3xt~i~FJ<~@>x}aYHz17prD~X9 z_&SI**)|rH-F@yQ@zx~^G z&42HF@IPqw+Fg`Cn7pa@Et2)Sd?)94>Cp{#B*B#xrV0QMfNP>cC`iHF#OM&9ECm6m zaFO`m7gY#1Y~sUzt^&b396&;R=RKqqbJiw9@e%#c7BUnB$~6Zz22u2?!zGgh`&!_}<5=s%TjnIj3~IRC;c1 znDBqQ|958oKQ)5>QxFEj<#ralaD6{Z4Ir$cihlp8h@2R*`UH2qaEGx-eUOm=WX)HY zh`CwD-5B?~d$-47n)0#1&$j4H#0%=z$k9nUK(H})EJl_3xlG-0(`EXd$K%84Kv25d z8Pr?(sGClsJ2889By-lpP9ozmGm0OUV|hCbef}DCYBjVMi@V+?Piy$jhv>eCyPnF` zlRvW6itMmK1;0SO1%dU1fuPkBLVIH%NW~G=;fR`+s55E4T$-$F*{|k>xUS&DX0Gph z4JO&1FAeRnqfP^SZY(+OW}n?D1{?FVE4B{}CEtg;J(DqKHrP%sX zK(SewN{S6%X6F(=-98B+jRlg1G*+7b`gs+L@@~!is{-{uVb{EO78r)TS!t}P+-TN{ z{3HoIy|9NWEG*o-OZz<($N}jdSPa3O--BXnGnGCME;^wS-Oe67Y8-r+{u+JG>T~m= zNV%yts-f?25p}wGwQ`w;McV1)?z({PMC?EWPvUSrwx^VMn)a5kfg(muO*)7AO&b5G)%-o?t zOKP>2%c#2+zn1OPyT1-mQXL_0s}zs3@r0d^&u7mr4DJ=8P8Fe-9?;8}gn{2=y6DGm z?iwtwVunujdFw!G;nz9J7wf zX0bi*MR`Ai@>FaFp}Y&RH#QcuqmHcjxc#PsqRj3`_h6ng%C_!m=|tb!P?0{90~EUH zUy=ED=2_pCUg->1=ByjaTMhb!Kdzt1<)$v`S>l^u>F{&`7(3;HtLuFl;mGrjKAN~_ z@Yhf8O3r&0Ds27h5Zxxww`b0ze+5}UTUwRC{iFq?9Eeu&7J;i{Cf6- z{xipqw+?|F&rlDBcqHpq3j$313a$K;TwZ{VzTD?&J69WCO8~@3J{9ZMJAa!(8GVnJ zh&28>Tfx^t^6PB?1}JmuMfxX=?&f3o+8V?+`zVm|r1($f>|IBUgA+sfxdZfab|R&w z0ED?B^NG4jh-+;Jch9$6dSIJMW|ucnryWCaj;c%2b;0kSbqtyD5Q^l@JnV7`DtIKY z%z-*BMR{xJ?k;R#{gXCn!JI$`JLlEv3>IoYYwZyC{%+B>FSyZ#YsJ~$zq#rd6|hkK z{3|CWsnz)!WZrl0mptn9W7XdZIA+BCOyLv=Sv)ly+RH&j!o#@24%XXMo}!FMFE)*O z_c5K}_a9UJ>9>y_^7$$c?l)Y>6(oos0O4bTXnumUdLz+rRB7qyh{=b6Y<&%(;Z5^vET~fx)ajpa=kXbN41+E=tU{BS zPbV64oH?sBxM!LB%LPcBMcaK8_py%K;PvGzDAvHy-(EdwHDWOC{%gAf+Iv1g$oeE7 z_HKy?Jq$6ghVPTHp zRAgy|QA9sY7=f!d5_P1!1+X07tg|;=12%GeV}0X7ubzXEnn%ZFavndfN11&k*%B~I zgY>OVrKb7uB)h@fDtfqt({eXepx~=!>OSMmxJyUy_)y1vk|S zrSas?$;{Z94T(>zA^_bgPt{R+iF1JWh_X8daP2)Cz>W4zP>#O?0X+NTI|tI)+L8~V zf3F#@wep7Lq86m3PK+W<=oN0~BEFOxiv^kEx6wjGF(g z7@nB4CL~=UP)&zR?ll}pd{{>V>xSP~_3aM{z%+*k2wFtekv=D+PI}-SEqeBOssGIG zW&K|&pBsIj+6g1MDl2X&n|d2X?99HlX4aal8IKc0Gt1q^31s6~&YpP0N_Ts*0^9cF z6z)Bs(<5!33Q(PPjtl{~L9h6+3QoIjPyDr0x*xK`NLZi>d|GY}NbJ>&*lU9Bj@(AI z*6we&yLFs7cs=}R!NnK>=Gs4}wH0wWbcK#x7~e&!W`*9NA`MzWq_1XDFp$>!{!3N! zaA7QUF3tlGx7oY;VlXa^gjlEgSiu{i|RYGc|tb2^=n0h$g;iw~BFW3yfbd9N5QD1lnv$C6hbW&PekQ)%aI2>yk zZG1oP^M_MRy0K|1n?-JdU1hAkT}85(7xI(Kqc*eQTM#JBV;DSgUxeDao9=-uCGTxz zG_e<{nJ_EF?uzVeyWzJw@yA5nudF#*vD&FpU)l0DaW~?B=L;Ap)$oX!xFH7DPm`8J z&n_G*$z)79rVrk__v-$h(y%)5DXi}>IEzI@!Y%G4FaLUk4L{;M_g<|iBrHR%{Jee% zF8Rqr?E8%N@&j{0>7JXM1n?XkkbC$EdP@$k2TrMrL!s+HIM9KSJ!?qUwlK|9?Nq;9Yg|joL$~I zJ=kZ|EjQ<5V_Rxi$X#V%OEl*3oum6PI4GN-LeU$8^>|`cEK)?ZD0NSD_sMA`(73yy zJgn_wcS#`L(9EuonMx`bY^V}#8S_E~(gG3oz%jvo$88atw`lbYDx`J&w}(h$;@(qD zeINgPi?Qg0zj4yPWT~VFky%S!%tM$?hp^dO*>}t~%GJcLcv6#V$xAM7L}3+H8tQlB z0PN%e-4j2|rLt;rdOe(T3ZFV4e#pA`2KEL>etNs~dkos(qPS;E;Fc;zCewvhjmS3n zmR!5j@ZshF(tTlPRNpo6v*u61AB);$erF7jrtx!WlY-@HZaik0Sx?x?iW2-x9Jo51 zCI0RfHo9Vf|DYeXPVn{cf z_gwK;^49%m@vi6Eq**hhLwg0N)5v>@ec$*K08^On#bV#X&BrXscsu%%4ztH~4|F|^ zADRRqFkIa|k1uqW0|k^4#o5N`q^Jo9-*u@Fo9Fg2*6#C3kwjNJNvLW|C)SbB%IAgb zx}*VPF`sNZw(VSyV^74yOP*sXV2vv8+<|sqFRU5TS2QdoBZQP|UB*R0c~vZ=`1mX< zYkZtKK}#kAri>y4?o#H6pu3juy+hCZBwl&FPUX#A5`fxu_6}RVbWBQ%O8ZlGPbweN$)i(s zaEEqvhSJRd=~ge)wIhp$Cx4@lza!A-2pk+*P=~FeXA|YYQD5WulSsP4(r_KpLESD z>Z)doEp8#Dd<8C_M>Bd#5^5>0ldqLotPfd`a3JlSU(vsexFzk*iRjLSdcAzakc+ag zkEVasHp9RE`Y~`7?zk{H4Jh0G614-P@i}`p8E#DQp<_Ba1n^O>Ij*)G!+n$1Brlnz zdwUL|hbE;F@yn7MfBQtg+Bq!Err1(=-FHO$fSXa$kr-)A3;o;h29DMcx39N$r9xGgtp)CL-K>-+(GgX^`t3SR8{O2< zX>f6%V)n*$nX~Rj#*33=#2k*>hA;#B$R%=*B8oB#^5+XOUYJ>J!?|@Po%N@rX?>MI z$$pIw4xBxv^c(ySZ18JYRsfn06KyzeWKq_aUHzev8tm-eZEY?7AG)k-)#!dxMfv5V z7j*mKDTRlltB=_>emjt>6By6Y3fIlA$T~Pe)9l@guRs#VU3-&Vb1zxPNN5yzD%E;9 zp9YRN&oxYVXGkEt`{DxZ*QfVl{n=)Zmi#X*`8qzz6m5y%^ETO)`D88{PLsI50Tc8L z`E%r)56QDdfAzr7^mO@xLcJzg5+KCiFYSqUrKAkLzpt<`tA7fR2w0}~jR5NO#fQ<# z3fW&ELL}$-8#r{9bTsX%y|$CSR=LsGKB#88usm}p*<&zW=`M^Q$P~K*3Y#!4m()bRSB=h*n zA>nsy25aQPVo#FD3iQOJ>e=DZ+mQt!Gpr0GED%mh?@?)&Pq3Cz@VBeI{cf{mOfN0* z1LYHv=_D2vPDQ-lHjzcQS=xIB7e5V1n^VX}xp2JW`tfYu=gnn5O&kx`9DAd93|0Qv zo5+~M(6NK{>sg9Fr13o&EUdCJStW;tHX&!1e->y|Afu_Izf%^+BLxjlnQ+vmQr+>2 zf_bqyKR>}l22u#f3dd-*5dj-ovqh0zXIR3GPjy7!r#JzcKHTE^VV`P#upCFZ=AiJaxmw}c;q>Jglq ztEAzQoi1(PGFUh_aZ6g2dE`pOK6L9t_d0ABds0%JLa`-I*-(0*gL%@PoN3Jb#gw5h8l zJ&~uFXjsE%IP6R#l}aKUJSP{OMVC%2Jq8e!9I*%Q#OgAiRr7RY^89`ro|u<`aCVlg zTw*hQo+KJ9t98)>gxUU;ed;13rCl@)LQv)=WqR#))%w(9O@=r&MS zN!Xt{omGL}Crx=$l%Xj!jx(>lrj3D`=G)q{@nC7VdtU^Z9{ ztBx&9NO4sIw~j3UOgrVVU6uAGbmmk3>G4ib1SeKGpzxIBAfmL;Q?Tli|aoMh+V8yXi9y^pwqcZNHZ0(jcVQwQtLKK*xVya$Lny&}*v#4N3r+vrsO>oiAP}qlyr2k&qqPK;b2&ls%J zwfRJoL+;n((6!Fad!y)4VLuaUTCjA1+EcandeNVP7+jx5xX@iWjH>6XJR~w5NNXY= z*v7*QJnceZk^~ksDZb3fY?~cyZ+btjjd*(4YZ?~#l1_oO_%rGCwIU(yFpQnI_qe_G zh7Dw$qP!b%@7hIuW=Nk6Qmj76V=DgKmik`qjVKD`xiD|LtkttpTf=K6-(_!d_{K&0 zV9u7Vca|}^e(JCB`{kTBi;4aUjgC^cj zs5eQg*ulNXSi-ypyFfNH z_8y3*=OSSJG%-cIQa7?y(nOb>1RXMuty_^$#OpN-w(9%SYV{E8@rpND@|x_cwsf{= zU=();88~I3_5m~&P;app443c`oQ-e@6;3PCK;%2X>-W28u9o!|_lwF~ln__*9Y}Oj zVBB;b_`{<9VJiFjY5x6*?>{)O(_J9Y^w5=g&b?hqkhPrIx}-{GAq@rKNV*LC>&q0U zEqUeS>yiMi7I#CuL1aUkA}+r(YSu_mzd1ceXD-Yx$VrRwAQZnO@mCT}u|T>d>T4RT zZ+MZfs#ElR5X91O=R%YH8F>HQjt*?_2j@E$4LW!^SOoTE6=kFHz1Uu9pQ9C!pGWSa z-pUFg-}L%F48tuxv-D_;MQU(s?1vrYDRq$Tb#u&6NOv*_F%Sqt8 zEjto=M=)Zq34K^|YKPX`B+rtsB$*4BaD8E-@lC#Z)3u5LjJv(HY6=P4kqQv6-v4nz zqAcnYd|e)8=QgWw#@=kQ(4qtDhQ`WV6|f`?VKr4~R|SI|KEs^Ah!|z|16+o98VEw1 zXcO0YdiqJDuG%ooj;oS3^}3w{ulxLGuR}wgur-E8l>t4e@R#(gS{6$S4s-3E>_C{} z6j-X*RtqNA|7m!hR_Ce1#UnBbubToaVyC6W4y^7R`+(7i&KzZBrC&-?>fDZc=id4sw@=ABxlYjZ=oD zE$!k?C{5)cgq2B&mHVltudFQf6~Na1JXe@TV#x#nnT)@S6N7^Bjqg{cqyb0%rmq0* zC6qIG@ARFhPH!vE-nG9_UzFV#mb`<{)E`H4Kh{9#Gei_IbNN7POrAM)ZPt4hN4~04 z6it^@-%+mihB;4BMCYWqDE}$o4stVn_`?MVaRT$7sNt^57|<9hP;))h7|`0`7CqIW zjk~z?#RL4_FYapscGtVF<>xrwT|MwQQ}6xEbF4x9uG!u`_;qNkoFKXhciO0qv45X> z3$F9To+1t)5s4cu{{G3c}J##j3%qvU@lnpCQy6<(Z z%5G9}V}LsPbHP;9boCcBq7DbDcI*S8Ae%YLpr4}KIN8Dft7FPP!mt_&^%Io|XYSr= zR(QtQ#sf8ce{r9{B&*!U$tlr5?jQwtV2cvKUUoDxwG@^P=sU!hfxSQ4={^{)5!}m0 z#$&i2(O^Y?xzM;zIgpK=4Ho!M2C{MMUlcftN4Scpb3+9AwW+$TTo=NR@(QUvz0469 zTeYV;UtZ6~ZSuf-W~N7##a^InF{4`5FZNP^auNn+ZyR%wfC3oo>h_$K@h^=JHuWDj zZVXF**`ZD|mEB_jW~7RPgQBAZx$MtLo~H+KD>Uh$n~0KhN-==)!q)2Jm8YeCC0Lse zW(W-MMZNv+Gp6_u1AZ$%{DzoGm}|MH+zOa>gcG>lpTz=ued~H(MFQN|*^#fb z9WlY*zela{`n-r*byl=OsK%VQmC))yBY_a)h12ifjM0EDT};%IjrCAr+%E3MARkQY z4)0)ox%!?Qd~IwDFf)4U%>gTBxTCf>^3#b^iufGUH$92z~#<(8{78c+9NJeyk-^Y|f?M7XwHoJwAKw&*H6HEb{+q0ix#|{3dP(Bx9@R#T_@? zA*-7#eeqkF_fmn)>0J%$d+u>A>@xKT4iA0qQ(E>)EKNK>9L}@E7ElJ{mb}J8H014mhqg`AwL;MNr9`Sn9r}n2C>CZeJty$jgFMv{Kcr`#Ds35VzaiIbUt;L@)6)PETYAm>l;9r zovjK(y>x5N5VyuY2X2wNWzl!?m7G_`cFpNxX<6wdOzJkX2{Xff#Mkq2(gpOerqs}OJY2&FZw1KR(nJp2+&77UY zX3aM55P)A;KTd+?V0;;m++v3EeB5kNRcEV#K0Y}Jw)m3}Iyu!J&oBLnF6o%;m|P^w zTshGWlHG-jc3O>+y|cdAyJYwf^UYP*S?Q?=v9@Jb z1Lyl#88|mYH096WK9I_KRiodyKP&cZ*xkrxU83$;%V_RX)XY~+sp5Wyj~IKkSn^4n zROSFz=zP{}9eL6`1GtucPI}qXZ-c+FJ5G&v>i8=6@59`m79X;V7@ijDXy8$@EF{B5 z@eZWVM#H8BwfiddeW~W&fr|tZv8<~B{p>KBexrbZBcR1oOF}q3inAc?T3?Tez6lq90S;u zU_zHOQ|JYdl5-g~YO=AA2~}*;F-IgZMm55|Tq@7ffDw1Tk9u-o=i8GB`eS1h_MG5Z zoZhey!IBnxe7rU#>J4_hKkj<+{^=pD9I{)7_Movc?T(6X#=Qu8BRpyM5%R1U_4OWO zSK&L9|MKGkBgR=~r$o#-OZkB9bc_17CmfK>^Xnr{Z?;wxyxPw{+L~SP*$esGZg@gH zM`XYAOME~ULlEqi)DRgM(j)frx%^Xwe{vp@*dlBZum?zA$;xqgJ_JEBs9UyMeLAU!J1as z(k>w=-TdRI7FlvbrQnSe3Ak4HC0IIHoW&2$6p!)E$sD67by49x#sG5?t3J5lY4uoX z7#_NS0_L0LAm<^1XJ5z4S%~fFqI~PTSn2E*;F9oFw#yv0eO(NYDK<4oNb<1o3nz(8 zT7oD2icy!`RL|c^lsDVEy-L{2td3Y<>F2=7UNk`-&Gw^QiVkf&@mch$f*qtL%>BsN zbzz&V^`|9h)!j(twcgwGW`(RpJlZKXGw{M%48-q}sTUGn2%3uDwIZ~rVZ=LR!}8qF zS5W8^eAZgMKlAp`lXg&Nw(3!__P&z*?-flU7c()~69OmH^fFcHa!;3~ASg8QL90NZ z`&>9<-Fk^l_QZQie4=GX=#ci>Eg-=6%uytn=1>IX9d5U=&qG)(+IaV(#M9hLbp9j2 z?e}E}<;#n!=FGNNT4Jbj8y=z(p?jjTd70fnBZ1bSQ$-lKB8+hU5{l5yl4Zmt{F7knu zc_{?h<>WW>`km$O^eHf@yh(4SQE&d<=H)FjT{bP@2YUxkaqdry!7@Oy&E{W)2fGlT>2!&gaKrfwqnCe z_iiG)_2(Zfe|MxlX17RIbiHFQw;~xhOTrg&mk-xQN(by{ibSkxQ- zZaJ4zVlsHYg2P?!;g%q)@>=ctX+%#NE1JI4KG_8CvS$O2`idr3m|1L8V~S6e61A7J zQ>oZ&?)mjx;PLTvuk*NqL~8hj-(9eBz@yWpf8CPE(stNyy5EOSp8MUu;Fi|P5c0lV&X+51$N#d+RbMy&` zIA$28BK^6+-H#TsZ~g*byecBeIvC}F9*_CvZCU^o&Vkjh=%5M*v`FeKPJ)?g$vT=1 z=xE_*Ot7rsW9Z0>Z}}pmm}9K=_KE3i)x_@;U3_eG4O=8=lZmDkA%iV9Y3^}JYf1L0 zcYm_~1Sd;=1!hY~PoiUGx_N6OfE@iu=9XEnoSwDjfI5xup)Bx=AUz>jdO8c~rs`A> z{)F0B){J&0_si1n3O1z}JDnn>v$nF=-jq0q0g}BBB4j`tuw4~Mc6|9|Ofw$^aAS6j z`c*Z-4%YC48$Qb(&3hEiST=1rY8b&Ir)1Eg^hnn4dIF6;*M<5=Wn)WE* z8c*{jR>b;bb@wHwowq7f^@V)yfBC!7q$N`jA{#~oUTXWx(h22aRM7w$*NDC7v_+7Ml+* zE@pe1*^(B5_@!7AJN_;4>vVaG#4~oa$ksL!XD2U^+CipFDQVD{C0ONR0{9#c z-0TtEBtvoDRWlYsFg0O8%K*qN&SxkwF^7BN_U;SyUU>Lr9_8y#XB)tsXDZoTB|KKh zsdqle@BMLt(V<+gl@lx*+k>yeFhxaL;BK?jX=3!3n6)vAvHDCgFre}m&T1Ew5zs2F z1yi?4@7|uyFlE74hB?pOOQ)i?Xa$?E%BVE&$X)oi;T>}K&CtLbK2wYF!S^CF(*f5u z4it)BbOyFy(9y1g2X;J$kn-+y?#f-bGlp*jXZa;LFDoTjvHmA)HK1&wCwgrUMY{5C zv#-4->@+E2H*W#IfBHjdvuHSjg=ylx;^#p@!Ff)5Yc9>o&4z4rGOy5RGfm}d?tR_G zTwO^#CA^k#KmTk$(lN66g|+mbF>n%=jT-S}%?)*0_$a<~%CxB}_jahR<&J}Kg=r#; zJ5~vJhe#+78%6J+q{?#nEzXk} znQAM4aLcn&{G9rbpLq-5Nkw>f;YP;&5R-AS}|yGA98^y z0X7!a`<$`g=eFZR+qXbRSOc!$0QBn11W!Ow=8A|wLZZzMf4C8>Rv*`p@LXxXX#LUB z9qgJHhfxLD&~dXv+WCq6$dWC*#m5Ig8JO(z2{t4N2u&-&jCE8?L$av3WSUz8eq7zG zCKJ0`pSrUY1*#qWxGP$$J)}ETrjntCOC`7r7v32N_90~zdGm_%gT}EA6A~f1st6BhzB5(XQ@u z`cs(VmhHv~Lj$)}_FVh}_cJl)M#O>z6u%y4y@Phm{R-b=)|958j%}yOB4F$s=%U$)%EHgxy%-gBjk97n6P` zXY%-vIR%cYtb(Wn4csh!8~iyjz2*FwO(##B;pt1~fcXWJ$Sp*-RSJnUiasFM@8uKa zsRg;k(*#P}UbN~0B+C99|QqJb9CQvA6Y#P zBT%w?sn}Mw9`$S>-U)Z~5BElGg!&pFR0)0a50c)P%_-^5`Ez55UWj}(B%4&jXDKCO zki)^khoX0{YABrB(+b2Bcl6SnitjP~=GaF2d?Y~zIG3Xd)DF8@f>0dUJEGEMa*eV0 zObvT({O2UD0j`5)7P4#5H1Ceo5aw$9%9J@<+H(4J@MnI{@*^2Q2MdwFT+p~9y}(#I z2xKx`B-XpG{-#q76)bH-DF-ps(6vF~UFtdiAqrbrND`bOLNv;L>Fn@hyk4N~6V~SW z9_=ti-MR_(F4vjHzzjfSj;ed`6OIF>dHz6wY_rz*9WB(PhsZB5REEFKwYPoC(wGUW$;%H;U&6=Lfu$w30%Ljg_tj+os` zH-2u}-kpfz#+Ul5p4s=)@bdjC%Yy3|T=3E9!DsJER%bUWgpMp+zBFi+{kC&(WF=iL zU!*HJdx^e47hk@qSBSWO@K&8ZfjACHyj$o(z&lR~bFG^CEsi8!*L%c@;e>Rnolt9g zKU^>md~^b;2PH+mKU)zbmOw;V3bN}Q4W>*}+*=%2IFk1vOYUfEfBg~GdHezU$O{r? z$qO7WF9gnz3oUr%=Eca`_C19<=Y{hBk@gW#ZN*C0>dUqi5qQvS`vAdik*u(%T!aur z8;}B|*-J6Z7}g@ks4}*&Gz*sp(yd4Iwy@Zzq%J|&H~adYOIvQ+nL&`ASz3XG9x7-0 z5u25jMKaV(S&juH-ee$&-fvfy_3*FsI}uj+3b}^|NDv`N&AXT8Lh=J91Q@N3U~{_7 z%3Ppi?|La=2_aN~WNkvKrAP7|snl)9uIJF&(c%eXJ_mzdE!gj*W^e%Izxo1Z`Gpzo zB=pQA@DJ2Q{~(&ENY|M~PDg0~_nA;|O0>9r7AXN38>1WbGzzbLX( z=DIqAAa6W>T8&rwh4Y}T>*OPWa5UdCCsHm0n8&H_D`QX8k1<4ehVf@HsaQBFi zjh4qPeqGEkZe<^Jx>8|ru0&#fpxFt{&_K%)fgdz~#n;5(S^k70VKI-m6A=b6DZ37g zgCagH;}&_T9nFbrR>wq{w@>bgLS&V1k~0@v)gGj`H>}>{zsVqM7%{YZ?b!Xwo(Td| ztS_v5VPm=(eTt~a!vaGw`89Vr_WKaq6r&}sEBgz%bJ2M7e*O<2+-~6Cv5zTU zcB9`6o<9I1?S8%CdVgoi@40t8K5|ZDoZ5Nt{}@NzN5dk%lq#g`(2FyvSXxim)A@hm z<7@Id_I}>i(RWYEmXEQ`87*1n3)2PM55?b0J|XqoT&J}?vrRE^$f&oyf?V<^NhUdd zmq>}`1F6(cGPJ#ACrTm*q{m*JnJCqghJhChEK zh1c!_D1s3}jP9Jkw@cGPHkk-v`TuI&CowPz5FzZTBff2@qk2q10M@6%uiTD^f{1_T zn56LSGQRvj5h1|$xA826RN*KLfBGqd6+h$&+R-cgIVPiUJXgi~&>oC$-~Tt!j%4;h z_@h$gYWV%2c)@xgzWv|f|9AHPYdrt2>mD9N22~jXSyW%%!oRfC^$=CCr(ypG9+7?; literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b9581047c62d930b1cc038eb04cc96a60542186d GIT binary patch literal 12322 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 Dq)lZx literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_back.png new file mode 100644 index 0000000000000000000000000000000000000000..8ebe16407e6dc5056659538be5cb46d7842318e2 GIT binary patch literal 16737 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 QGvSJ&yqa9;GmE$X4;Wcrp#T5? literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f0e5ea5780ba0c650950ffb74a574c5105de7a GIT binary patch literal 18635 zcmeIaS5#9`*DeeZq)7=VC{+Ro(j_VoAb>Oj2rAO60zv7$haw^Y1T@r0lcK2fE-iq7 z2n3MctF%ao5PCfuzw@4d{I_SE%lCr87(08fwf5YzJoA}zt;mNu8uYa6v}9yt^oaYa zkIBf$OV0nOF9KJ7>PQL$|HwTaYuqC%>ft1ikzFT4s4744wOCK59CSo@Rn45J+42F67s-(bmjKHhp$`V|6OHD z;U}kX#I&gA@&3Dw+?wIvG)??8*RA3?@51Q+-9~-Y<9u>5iW>+r^3;Wrz8C*iqtujg z;eXbM08x+fN5tr;{JRbGgY$glWT6OVrKTIQj-KrQ9%hnU`nRV4F8#k!|F6RQ*G&Jf zJdAFr67q1yP_;EP@0aBvb?6tcQq!lq&$fM}D5OY2m{@M`*&VFTz$0pHG~geznzQo< zAaAi6@t()%{kaN%9T#3ndcaxT{t;=R^HvGTZQg$qGM~?~1~-4*G|r;bG#rHfv1*0P zc+_7EnQ#AHSz9A!yHv`behUJxtZ|XuNVQ#hA#frJ2~u$Gs;G(uzM>arqQaUU60X`G z&o?g}Fd7r{90rXOA*6vfn`3;l6oMl0@)5aVeDgE`lULqbvH-Kcztmdm$p`I~AjD!e z*uaV=$w3cFd9Z%>5Ny$CX`Rxu)xl+a{fAsrty%}w<2~3;3~r4JaMZ-nVy7jpw{m2o;3ES)l#y9A*a9Rp)Y9KU(*8sA7hM5*8T0GNl@F(rMKw6NuqZ{l#KoS*qW9jEO%p-R2M>og=#4QDTrNNP_wuNjT0syFdGnp zCI8gRBkh*^$fYp>1$ju(oi&7Ja`~ts0#=&a7>D*zrUcaiAqF3*4#FOs@uS8wJZ

  • @}R4_uKN#C%+ACl0TP4cA}DT_(7Qa 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 V&8?aMPA4KmsOhK{-Lrc6zX1I9X@LL$ literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..04048dedcb19a57716caf5d1ec10bd4a3f6af0d4 GIT binary patch literal 18668 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_back.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_back.png new file mode 100644 index 0000000000000000000000000000000000000000..8ebe16407e6dc5056659538be5cb46d7842318e2 GIT binary patch literal 16737 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 QGvSJ&yqa9;GmE$X4;Wcrp#T5? literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_fore.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_fore.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f0e5ea5780ba0c650950ffb74a574c5105de7a GIT binary patch literal 18635 zcmeIaS5#9`*DeeZq)7=VC{+Ro(j_VoAb>Oj2rAO60zv7$haw^Y1T@r0lcK2fE-iq7 z2n3MctF%ao5PCfuzw@4d{I_SE%lCr87(08fwf5YzJoA}zt;mNu8uYa6v}9yt^oaYa zkIBf$OV0nOF9KJ7>PQL$|HwTaYuqC%>ft1ikzFT4s4744wOCK59CSo@Rn45J+42F67s-(bmjKHhp$`V|6OHD z;U}kX#I&gA@&3Dw+?wIvG)??8*RA3?@51Q+-9~-Y<9u>5iW>+r^3;Wrz8C*iqtujg z;eXbM08x+fN5tr;{JRbGgY$glWT6OVrKTIQj-KrQ9%hnU`nRV4F8#k!|F6RQ*G&Jf zJdAFr67q1yP_;EP@0aBvb?6tcQq!lq&$fM}D5OY2m{@M`*&VFTz$0pHG~geznzQo< zAaAi6@t()%{kaN%9T#3ndcaxT{t;=R^HvGTZQg$qGM~?~1~-4*G|r;bG#rHfv1*0P zc+_7EnQ#AHSz9A!yHv`behUJxtZ|XuNVQ#hA#frJ2~u$Gs;G(uzM>arqQaUU60X`G z&o?g}Fd7r{90rXOA*6vfn`3;l6oMl0@)5aVeDgE`lULqbvH-Kcztmdm$p`I~AjD!e z*uaV=$w3cFd9Z%>5Ny$CX`Rxu)xl+a{fAsrty%}w<2~3;3~r4JaMZ-nVy7jpw{m2o;3ES)l#y9A*a9Rp)Y9KU(*8sA7hM5*8T0GNl@F(rMKw6NuqZ{l#KoS*qW9jEO%p-R2M>og=#4QDTrNNP_wuNjT0syFdGnp zCI8gRBkh*^$fYp>1$ju(oi&7Ja`~ts0#=&a7>D*zrUcaiAqF3*4#FOs@uS8wJZ
  • @}R4_uKN#C%+ACl0TP4cA}DT_(7Qa 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 V&8?aMPA4KmsOhK{-Lrc6zX1I9X@LL$ literal 0 HcmV?d00001 diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml new file mode 100644 index 0000000..bc5764c --- /dev/null +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/lottie_button_forever.json b/app/src/main/res/raw/lottie_button_forever.json new file mode 100644 index 0000000..aae4895 --- /dev/null +++ b/app/src/main/res/raw/lottie_button_forever.json @@ -0,0 +1 @@ +{"v":"5.6.0","fr":30,"ip":0,"op":30,"w":324,"h":64,"nm":"BalanceHome","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-16.292],[16.292,0],[0,0],[0,16.292],[-16.292,0],[0,0]],"o":[[0,16.292],[0,0],[-16.292,0],[0,-16.292],[0,0],[16.292,0]],"v":[[160,0],[130.5,29.5],[-130.5,29.5],[-160,0],[-130.5,-29.5],[130.5,-29.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":79,"s":[0]},{"t":90,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,233.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":127,"st":30,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-16.292],[16.292,0],[0,0],[0,16.292],[-16.292,0],[0,0]],"o":[[0,16.292],[0,0],[-16.292,0],[0,-16.292],[0,0],[16.292,0]],"v":[[160,0],[130.5,29.5],[-130.5,29.5],[-160,0],[-130.5,-29.5],[130.5,-29.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[4]},{"t":90,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,233.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[0]},{"t":85,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[15]},{"t":85,"s":[100]}],"ix":2},"o":{"a":0,"k":-90,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":30,"op":127,"st":30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-16.292],[16.292,0],[0,0],[0,16.292],[-16.292,0],[0,0]],"o":[[0,16.292],[0,0],[-16.292,0],[0,-16.292],[0,0],[16.292,0]],"v":[[160,0],[130.5,29.5],[-130.5,29.5],[-160,0],[-130.5,-29.5],[130.5,-29.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,233.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":30,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[15]},{"t":30,"s":[15]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-90]},{"t":30,"s":[270]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":30,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/res/raw/lottie_button_loading.json b/app/src/main/res/raw/lottie_button_loading.json new file mode 100644 index 0000000..2979cab --- /dev/null +++ b/app/src/main/res/raw/lottie_button_loading.json @@ -0,0 +1 @@ +{"v":"5.6.0","fr":30,"ip":0,"op":97,"w":324,"h":64,"nm":"BalanceHome","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-16.292],[16.292,0],[0,0],[0,16.292],[-16.292,0],[0,0]],"o":[[0,16.292],[0,0],[-16.292,0],[0,-16.292],[0,0],[16.292,0]],"v":[[160,0],[130.5,29.5],[-130.5,29.5],[-160,0],[-130.5,-29.5],[130.5,-29.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,233.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":15,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[15]},{"t":15,"s":[15]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-90]},{"t":15,"s":[270]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-16.292],[16.292,0],[0,0],[0,16.292],[-16.292,0],[0,0]],"o":[[0,16.292],[0,0],[-16.292,0],[0,-16.292],[0,0],[16.292,0]],"v":[[160,0],[130.5,29.5],[-130.5,29.5],[-160,0],[-130.5,-29.5],[130.5,-29.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[4]},{"t":90,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,233.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"t":85,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[15]},{"t":85,"s":[100]}],"ix":2},"o":{"a":0,"k":-90,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":15,"op":127,"st":30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"WhiteButton","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-16.292],[16.292,0],[0,0],[0,16.292],[-16.292,0],[0,0]],"o":[[0,16.292],[0,0],[-16.292,0],[0,-16.292],[0,0],[16.292,0]],"v":[[160,0],[130.5,29.5],[-130.5,29.5],[-160,0],[-130.5,-29.5],[130.5,-29.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":79,"s":[0]},{"t":90,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,233.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":15,"op":127,"st":30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"DisabledButton","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-16.292],[16.292,0],[0,0],[0,16.292],[-16.292,0],[0,0]],"o":[[0,16.292],[0,0],[-16.292,0],[0,-16.292],[0,0],[16.292,0]],"v":[[160,0],[130.5,29.5],[-130.5,29.5],[-160,0],[-130.5,-29.5],[130.5,-29.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[30]},{"t":75,"s":[30]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,233.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":97,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/res/raw/lottie_button_loading_new.json b/app/src/main/res/raw/lottie_button_loading_new.json new file mode 100644 index 0000000..70efbc6 --- /dev/null +++ b/app/src/main/res/raw/lottie_button_loading_new.json @@ -0,0 +1 @@ +{"v":"5.6.0","fr":30,"ip":0,"op":200,"w":324,"h":64,"nm":"FullSync","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Stroke2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162.5,-201,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false,"d":1,"s":{"a":0,"k":[320,60],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4}},{"ty":"fl","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false,"r":1},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"bm":0,"nm":"Stroke 3","mn":"ADBE Vector Graphic - Stroke","hd":false,"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":99,"s":[4]},{"t":174,"s":[2]}],"ix":5},"lc":2,"lj":1,"ml":2},{"ty":"tr","p":{"a":0,"k":[0.5,233.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":99,"s":[0]},{"t":192,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":99,"s":[15]},{"t":192,"s":[100]}],"ix":2},"o":{"a":0,"k":1000,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":100,"op":211,"st":99,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Stroke1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false,"d":1,"s":{"a":0,"k":[320,60],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4}},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 2","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.5,233.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":100,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[15]},{"t":100,"s":[15]}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-90]},{"t":100,"s":[990]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":100,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"GoldButton 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false,"d":1,"s":{"a":0,"k":[320,60],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4}},{"ty":"st","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":3},"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.4,0.4,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":186,"s":[0]},{"t":200,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.5,233.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":15,"op":201,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"DisabledButton","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162,-201.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[],"ip":0,"op":249,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/res/raw/lottie_sending.json b/app/src/main/res/raw/lottie_sending.json new file mode 100644 index 0000000..e1dc21b --- /dev/null +++ b/app/src/main/res/raw/lottie_sending.json @@ -0,0 +1 @@ +{"v":"5.5.8","fr":60,"ip":0,"op":90,"w":170,"h":130,"nm":"loader3","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[0]},{"t":89,"s":[180]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.145,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[34,65,0],"to":[0,-2.917,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.588},"o":{"x":0.732,"y":0},"t":20,"s":[34,47.5,0],"to":[0,0,0],"ti":[-55.765,0.067,0]},{"i":{"x":0.096,"y":1},"o":{"x":0.167,"y":0.414},"t":45,"s":[90.504,121.958,0],"to":[42.185,-0.051,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.984,"y":0},"t":70,"s":[139,40,0],"to":[0,0,0],"ti":[0,-0.833,0]},{"t":90,"s":[139,65,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":20,"s":[20,20]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":45,"s":[15,15]},{"t":70,"s":[20,20]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":3,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.125490196078,0.133333333333,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('CHANGE COLORS').layer('CHANGE COLOR').content('CHANGE COLOR HERE').color;"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.408,"y":1},"o":{"x":0.518,"y":0},"t":35,"s":[139,65,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.396,"y":1},"o":{"x":0.461,"y":0},"t":59,"s":[94,65,0],"to":[0,0,0],"ti":[0,0,0]},{"t":83,"s":[104,65,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":3,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.125490196078,0.133333333333,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('CHANGE COLORS').layer('CHANGE COLOR').content('CHANGE COLOR HERE').color;"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.408,"y":1},"o":{"x":0.518,"y":0},"t":32,"s":[104,65,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.396,"y":1},"o":{"x":0.461,"y":0},"t":56,"s":[59,65,0],"to":[0,0,0],"ti":[0,0,0]},{"t":80,"s":[69,65,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":3,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.125490196078,0.133333333333,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('CHANGE COLORS').layer('CHANGE COLOR').content('CHANGE COLOR HERE').color;"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":90,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[0]},{"t":54,"s":[-90]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.408,"y":1},"o":{"x":0.518,"y":0},"t":30,"s":[69,65,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.396,"y":1},"o":{"x":0.461,"y":0},"t":54,"s":[24,65,0],"to":[0,0,0],"ti":[0,0,0]},{"t":78,"s":[34,65,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":3,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.125490196078,0.133333333333,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('CHANGE COLORS').layer('CHANGE COLOR').content('CHANGE COLOR HERE').color;"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":90,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/res/raw/lottie_shielding.json b/app/src/main/res/raw/lottie_shielding.json new file mode 100644 index 0000000..8edfb04 --- /dev/null +++ b/app/src/main/res/raw/lottie_shielding.json @@ -0,0 +1 @@ +{"v":"5.7.6","fr":29.9700012207031,"ip":0,"op":90.0000036657751,"w":500,"h":500,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"checkmark","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":146,"s":[0]},{"t":150.000006109625,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[249.538,250.217,0],"ix":2,"l":2},"a":{"a":0,"k":[-226.962,337.717,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":132,"s":[600,600,100]},{"t":150.000006109625,"s":[145.528,145.528,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-233.582,336.661],[-233.582,336.661],[-207.508,309.217],[-193.462,323.995],[-233.582,366.217],[-260.462,337.93],[-246.416,323.152],[-233.582,336.661]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"line3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[100]},{"t":59.0000024031193,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[249.5,251.5,0],"ix":2,"l":2},"a":{"a":0,"k":[-50.5,-77.5,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":29,"s":[145.528,145.528,100]},{"t":59.0000024031193,"s":[330,330,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[18.962,-10.014],[0,0],[0,0],[0,21.28],[0,0],[0,0],[0,0]],"o":[[0,0],[0,21.28],[0,0],[0,0],[-18.962,-10.014],[0,0],[0,0],[0,0],[0,0]],"v":[[11,-128.642],[11,-68.992],[-19.75,-18.246],[-50.5,-2],[-81.25,-18.246],[-112,-68.992],[-112,-128.642],[-50.5,-153],[11,-128.642]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.721568644047,0.003921568859,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.721568644047,0.003921568859,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[1]},{"t":59.0000024031193,"s":[1.5]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":29.0000011811942,"op":60.0000024438501,"st":29.0000011811942,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"line2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[100]},{"t":44.0000017921567,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250.5,251.5,0],"ix":2,"l":2},"a":{"a":0,"k":[-50.5,-77.5,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":14,"s":[145.528,145.528,100]},{"t":44.0000017921567,"s":[330,330,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[18.962,-10.014],[0,0],[0,0],[0,21.28],[0,0],[0,0],[0,0]],"o":[[0,0],[0,21.28],[0,0],[0,0],[-18.962,-10.014],[0,0],[0,0],[0,0],[0,0]],"v":[[11,-128.642],[11,-68.992],[-19.75,-18.246],[-50.5,-2],[-81.25,-18.246],[-112,-68.992],[-112,-128.642],[-50.5,-153],[11,-128.642]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.721568644047,0.003921568859,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.721568644047,0.003921568859,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[1.5]},{"t":44.0000017921567,"s":[3]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":14.0000005702317,"op":45.0000018328876,"st":14.0000005702317,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"line1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":30.0000012219251,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[249.5,251.5,0],"ix":2,"l":2},"a":{"a":0,"k":[-50.5,-77.5,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[145.528,145.528,100]},{"t":30.0000012219251,"s":[330,330,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[18.962,-10.014],[0,0],[0,0],[0,21.28],[0,0],[0,0],[0,0]],"o":[[0,0],[0,21.28],[0,0],[0,0],[-18.962,-10.014],[0,0],[0,0],[0,0],[0,0]],"v":[[11,-128.642],[11,-68.992],[-19.75,-18.246],[-50.5,-2],[-81.25,-18.246],[-112,-68.992],[-112,-128.642],[-50.5,-153],[11,-128.642]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.721568644047,0.003921568859,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.721568644047,0.003921568859,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[4]},{"t":31.0000012626559,"s":[2]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":62.0000025253118,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[204.75,251.368,0],"ix":2,"l":2},"a":{"a":0,"k":[-81.25,-77.632,0],"ix":1,"l":2},"s":{"a":0,"k":[145.528,145.528,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,21.28],[0,0],[0,0]],"o":[[0,0],[-18.962,-10.014],[0,0],[0,0],[0,0]],"v":[[-50.5,-2.132],[-81.25,-18.378],[-112,-69.124],[-112,-128.774],[-50.5,-153.132]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.639215707779,0.160784319043,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[249.5,251.5,0],"ix":2,"l":2},"a":{"a":0,"k":[-50.5,-77.5,0],"ix":1,"l":2},"s":{"a":0,"k":[145.528,145.528,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[18.962,-10.014],[0,0],[0,0],[0,21.28],[0,0],[0,0],[0,0]],"o":[[0,0],[0,21.28],[0,0],[0,0],[-18.962,-10.014],[0,0],[0,0],[0,0],[0,0]],"v":[[11,-128.642],[11,-68.992],[-19.75,-18.246],[-50.5,-2],[-81.25,-18.246],[-112,-68.992],[-112,-128.642],[-50.5,-153],[11,-128.642]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.721568644047,0.003921568859,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/res/raw/lottie_success.json b/app/src/main/res/raw/lottie_success.json new file mode 100644 index 0000000..a4e2ff7 --- /dev/null +++ b/app/src/main/res/raw/lottie_success.json @@ -0,0 +1 @@ +{"v":"4.6.3","fr":24,"ip":0,"op":21,"w":320,"h":320,"nm":"checklist","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 13","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":300},"p":{"a":0,"k":[160,159.5,0]},"a":{"a":0,"k":[0,-34,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":17}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.7921569,0.4470588,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":6,"s":[-8.142,-92.147],"e":[-7.675,-162.544],"to":[0.07779947668314,-11.7327470779419],"ti":[-0.07779947668314,11.7327470779419]},{"t":17}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[83.981,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"n":["0p667_1_0p167_0p167"],"t":6,"s":[20.367],"e":[6.367]},{"t":17}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":21}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.9070925,0.5119235,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":6,"s":[16.585,-99.759],"e":[28.521,-187.495],"to":[1.9892578125,-14.6227216720581],"ti":[-1.9892578125,14.6227216720581]},{"t":21}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[97.419,116],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[14.733],"e":[8.733]},{"t":21}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"}],"ip":6,"op":22,"st":-21,"bm":0,"sr":1},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 12","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":250},"p":{"a":0,"k":[160,159.5,0]},"a":{"a":0,"k":[0,-34,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":17}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.7921569,0.4470588,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":6,"s":[-8.142,-92.147],"e":[-7.675,-162.544],"to":[0.07779947668314,-11.7327470779419],"ti":[-0.07779947668314,11.7327470779419]},{"t":17}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[83.981,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"n":["0p667_1_0p167_0p167"],"t":6,"s":[20.367],"e":[6.367]},{"t":17}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":21}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.9070925,0.5119235,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":6,"s":[16.585,-99.759],"e":[28.521,-187.495],"to":[1.9892578125,-14.6227216720581],"ti":[-1.9892578125,14.6227216720581]},{"t":21}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[97.419,116],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[14.733],"e":[8.733]},{"t":21}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"}],"ip":6,"op":22,"st":-21,"bm":0,"sr":1},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 11","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":200},"p":{"a":0,"k":[160,159.5,0]},"a":{"a":0,"k":[0,-34,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":17}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.7921569,0.4470588,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":6,"s":[-8.142,-92.147],"e":[-7.675,-162.544],"to":[0.07779947668314,-11.7327470779419],"ti":[-0.07779947668314,11.7327470779419]},{"t":17}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[83.981,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"n":["0p667_1_0p167_0p167"],"t":6,"s":[20.367],"e":[6.367]},{"t":17}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":21}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.9070925,0.5119235,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":6,"s":[16.585,-99.759],"e":[28.521,-187.495],"to":[1.9892578125,-14.6227216720581],"ti":[-1.9892578125,14.6227216720581]},{"t":21}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[97.419,116],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[14.733],"e":[8.733]},{"t":21}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"}],"ip":6,"op":22,"st":-21,"bm":0,"sr":1},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 10","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":150},"p":{"a":0,"k":[160,159.5,0]},"a":{"a":0,"k":[0,-34,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":17}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.7921569,0.4470588,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":6,"s":[-8.142,-92.147],"e":[-7.675,-162.544],"to":[0.07779947668314,-11.7327470779419],"ti":[-0.07779947668314,11.7327470779419]},{"t":17}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[83.981,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"n":["0p667_1_0p167_0p167"],"t":6,"s":[20.367],"e":[6.367]},{"t":17}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":21}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.9070925,0.5119235,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":6,"s":[16.585,-99.759],"e":[28.521,-187.495],"to":[1.9892578125,-14.6227216720581],"ti":[-1.9892578125,14.6227216720581]},{"t":21}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[97.419,116],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[14.733],"e":[8.733]},{"t":21}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"}],"ip":6,"op":22,"st":-21,"bm":0,"sr":1},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 9","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":100},"p":{"a":0,"k":[160,159.5,0]},"a":{"a":0,"k":[0,-34,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":17}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.7921569,0.4470588,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":6,"s":[-8.142,-92.147],"e":[-7.675,-162.544],"to":[0.07779947668314,-11.7327470779419],"ti":[-0.07779947668314,11.7327470779419]},{"t":17}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[83.981,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"n":["0p667_1_0p167_0p167"],"t":6,"s":[20.367],"e":[6.367]},{"t":17}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":21}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.9070925,0.5119235,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":6,"s":[16.585,-99.759],"e":[28.521,-187.495],"to":[1.9892578125,-14.6227216720581],"ti":[-1.9892578125,14.6227216720581]},{"t":21}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[97.419,116],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[14.733],"e":[8.733]},{"t":21}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"}],"ip":6,"op":22,"st":-21,"bm":0,"sr":1},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 8","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":50},"p":{"a":0,"k":[160,159.5,0]},"a":{"a":0,"k":[0,-34,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":17}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.7921569,0.4470588,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":6,"s":[-8.142,-92.147],"e":[-7.675,-162.544],"to":[0.07779947668314,-11.7327470779419],"ti":[-0.07779947668314,11.7327470779419]},{"t":17}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[83.981,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"n":["0p667_1_0p167_0p167"],"t":6,"s":[20.367],"e":[6.367]},{"t":17}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":21}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.9070925,0.5119235,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":6,"s":[16.585,-99.759],"e":[28.521,-187.495],"to":[1.9892578125,-14.6227216720581],"ti":[-1.9892578125,14.6227216720581]},{"t":21}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[97.419,116],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[14.733],"e":[8.733]},{"t":21}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"}],"ip":6,"op":22,"st":-21,"bm":0,"sr":1},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 7","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[160,159.5,0]},"a":{"a":0,"k":[0,-34,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":17}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.7921569,0.4470588,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p667_1_0p167_0p167","t":6,"s":[-8.142,-92.147],"e":[-7.675,-162.544],"to":[0.07779947668314,-11.7327470779419],"ti":[-0.07779947668314,11.7327470779419]},{"t":17}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[83.981,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"n":["0p667_1_0p167_0p167"],"t":6,"s":[20.367],"e":[6.367]},{"t":17}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[15.021,15.021],"e":[0,0]},{"t":21}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.0823529,0.6784314,0.3843137,1],"e":[0,0.9070925,0.5119235,1]},{"t":17}]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":6,"s":[16.585,-99.759],"e":[28.521,-187.495],"to":[1.9892578125,-14.6227216720581],"ti":[-1.9892578125,14.6227216720581]},{"t":21}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[97.419,116],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[14.733],"e":[8.733]},{"t":21}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"}],"ip":6,"op":22,"st":-21,"bm":0,"sr":1},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 5","parent":11,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":-44},"p":{"a":0,"k":[0.378,-0.641,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[7.39,7.39,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[-274.219,-254.097]],"c":false}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-17,-16],[-17,10.5],[41,10.5]],"c":false}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":1,"ml":5,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"}],"ip":7,"op":22,"st":-21,"bm":0,"sr":1},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 6","ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[50],"e":[0]},{"t":14}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[160,160,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0.333]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_0p667_0p333_0p333"],"t":4,"s":[100,100,100],"e":[1085,1085,100]},{"t":14}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.779,19.779]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":0,"k":[0,0.7921569,0.4470588,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[-0.068,0.036],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":4,"op":22,"st":-23,"bm":0,"sr":1},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 4","ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[30],"e":[100]},{"t":9}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[160.312,161.188,0]},"a":{"a":0,"k":[0.812,-0.562,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0.333]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_0p667_0p333_0p333"],"t":6,"s":[100,100,100],"e":[1087,1087,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0.333]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_0p667_0p333_0p333"],"t":11,"s":[1087,1087,100],"e":[866,866,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0.333]},"n":["0p833_0p833_0p333_0","0p833_0p833_0p333_0","0p833_0p833_0p333_0p333"],"t":13,"s":[866,866,100],"e":[878,878,100]},{"t":16}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[10.068,10.068]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","c":{"a":0,"k":[0,0.7921569,0.4470588,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[0.784,-0.716],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":6,"op":22,"st":-19,"bm":0,"sr":1},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 3","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[161,160,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0.333]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_0p667_0p333_0p333"],"t":3,"s":[100,100,100],"e":[224,224,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0.333]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_0p667_0p333_0p333"],"t":4,"s":[224,224,100],"e":[476,476,100]},{"t":8}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[6.009,6.009]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[0.0558609,0.688557,0.3778246,1],"e":[0.1089485,0.6693168,0.3941063,1]},{"t":8}]},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[0],"e":[100]},{"t":5}]},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[3],"e":[0]},{"t":8}]},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"fl","c":{"a":0,"k":[0,0.7921569,0.4470588,1]},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":3,"s":[100],"e":[99]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[99],"e":[0]},{"t":5}]},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[-0.338,0.065],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[649.112,649.112],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":3,"op":22,"st":-21,"bm":0,"sr":1},{"ddd":0,"ind":13,"ty":4,"nm":"Shape Layer 2","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[160.142,159.987,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[377.603,377.603,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[22.315,22.315]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"st","c":{"a":0,"k":[0.8352941,0.8352941,0.8352941,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[-0.038,0.003],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":-21,"op":22,"st":-21,"bm":0,"sr":1}]} \ No newline at end of file diff --git a/app/src/main/res/values-es/custom_translations.xml b/app/src/main/res/values-es/custom_translations.xml new file mode 100644 index 0000000..764bcad --- /dev/null +++ b/app/src/main/res/values-es/custom_translations.xml @@ -0,0 +1,16 @@ + + , + , + d/M HH:mma + + + + Verifica + Fecha inicial: %1$,d + Esperando + Sincronizando…%1$d %% + Escaneando…%1$d %% + Confirma que quieres enviar + caracteres + Enviando + diff --git a/app/src/main/res/values-es/translated.xml b/app/src/main/res/values-es/translated.xml new file mode 100644 index 0000000..0a45931 --- /dev/null +++ b/app/src/main/res/values-es/translated.xml @@ -0,0 +1,74 @@ + + SilentDragon + Cargando + Bienvenido + fiduciario + Tu semilla de respaldo + Te recomendamos hace respaldarla en papel y obtener un administrador de contraseñas + Tu Wallet necesita ser respaldada + Verifica tu frase de respaldo + Ingresa las palabras faltantes para verificar tu frase de respaldo + Restaurando desde frase de respaldo + Deberás ingresar todas las 24 palabras de tu frase semilla en orden + ¡Tus fondos están en riesgo! + Recuerda, en Zcash tu eres el banco. Cualquiera que tenga tu frase semilla podrá acceder a tu wallet + Deberías respaldar tu wallet inmediatamente. Solo tú puedes hacerlo. + ¡Frase semilla aceptada! ¡Escaneando! + Estamos buscando transacciones de tu wallet en la blockchain. Si nos provees una fecha inicial de tu wallet podemos hacerlo más rápido. + Ingresar fecha + Fecha inicial + No lo sé + Respaldo + Omitir por ahora + Pantalla de Balance + Ingresar un monto para enviar + esperando XX ZEC + Disponible + No hay fondos disponibles + Sincronizando .... % + Escaneando .... % + Enviar Monto + Historial de Wallet + Tu dirección + Tu dirección blindada + Tu dirección transparente + ¡Dirección Copiada! + Copiar al portapapeles + Usuario Blindado + Enviar feedback + Respaldar Wallet + Ver Logs de Aplicación + Cerrar + Pantalla de Envío + Enviar + de tu wallet blindada + de tu wallet transparente + desde ambas wallet + A + Ingresa una dirección Zcash Válida + Esta es una dirección blindada válida + Esta es una dirección transparente válida + ¡Cuidado, parece que esta es tu propia dirección! + ¡Cuidado, esta dirección no es válida! + Memo + caracteres + incluir remitente + En Portapapeles + Última Utilizada + Desconocido + Escanear Dirección destinatario + Identifícate para enviar + Confirma que quieres enviar XX ZEC a + Enviando XX ZEC a + Cancelado + ¡Enviado! + Listo + Ver Detalles + Volver + Historial de tu Wallet + + + \ No newline at end of file diff --git a/app/src/main/res/values-it/custom_translations.xml b/app/src/main/res/values-it/custom_translations.xml new file mode 100644 index 0000000..28f0304 --- /dev/null +++ b/app/src/main/res/values-it/custom_translations.xml @@ -0,0 +1,16 @@ + + , + , + d/M HH:mma + + + + Controlla + Birthday: %1$,d + Aspettando + Downloading…%1$d%% + Scanning…%1$d%% + Conferma che vuoi inviare + caratteri + Inviando + diff --git a/app/src/main/res/values-it/translated.xml b/app/src/main/res/values-it/translated.xml new file mode 100644 index 0000000..cdfec42 --- /dev/null +++ b/app/src/main/res/values-it/translated.xml @@ -0,0 +1,63 @@ + + Tuo Indirizzo + Il tuo indirizzo blindato + Il tuo indirizzo trasparente + Inserisci un importo da inviare + Disponibile + aspettando %@ ZEC + Nessun fondo disponibile + Bilancio + Sincronizzando %@%% + Vedere Registri dell\'applicazione + ritornare + Fare Backup + Fare Backup + Chiudere + copia negli appunti + Pronto + non lo so + "Invia feedback + Vedi i dettagli + Invio + Invia importo + Salta per ora + cronologia del wallet + Inserisci un indirizzo Zcash valido + Attenzione, questo indirizzo non è valido! + Attenzione, sembra che questo sia il tuo indirizzo! + Questo è un indirizzo blindato valido + Questo è un indirizzo trasparente valido + La tua Wallet ha bisogno di un backup + caratteri + includere mittente + Per + Caricando + Utente blindato + Storia del tuo Wallet + Frase seme accettata! + Blocco iniziale + Stiamo cercando transazioni dal tuo wallet sulla blockchain. Se ci fornisci una data iniziale del tuo Wallet, possiamo farlo più velocemente. + inserici data + I tuoi fondi sono a rischio! + Ricorda, in Zcash sei tu la banca. Chiunque abbia la tua frase seme può accedere al tuo Wallet + Dovresti eseguire immediatamente il backup del tuo portafoglio. Solo tu puoi farlo + recupero dalla frase di backup + Devi inserire tutte le 24 parole della tua frase seme in ordine + Controlla la tua frase di backup + Inserisci le parole mancanti per vedere la frase di backup + Annullato + dalle due wallet + del tuo wallet blindato + del tuo wallet trasparente + Ultimo uso + Negli appunti + Scansione indirizzo destinatario + Invio + Accedi per inviare + Conferma che vuoi inviare %@ ZEC a + Inviando %@ ZEC a + Inviato! + Sconosciuto + Il tuo seme di backup + Benvenuto + \ No newline at end of file diff --git a/app/src/main/res/values-ko/custom_translations.xml b/app/src/main/res/values-ko/custom_translations.xml new file mode 100644 index 0000000..6b85072 --- /dev/null +++ b/app/src/main/res/values-ko/custom_translations.xml @@ -0,0 +1,16 @@ + + , + , + d/M HH:mma + + + + 확인 + 생일: %1$,d + 기대 + 동기화…%1$d %% + 스캐닝…%1$d %% + 를 보낼 것인지 확인하십시오 + 문자 + 보내기 + diff --git a/app/src/main/res/values-ko/translated.xml b/app/src/main/res/values-ko/translated.xml new file mode 100644 index 0000000..3864bf9 --- /dev/null +++ b/app/src/main/res/values-ko/translated.xml @@ -0,0 +1,74 @@ + + ECC 지갑 + 로딩 + 환영 + ₩ (currency = 통화) + 당신의 백업 시드 + 종이 백업과 비밀번호 보관소를 권장합니다. + 지갑을 백업해야합니다 + 백업 확인 + 누락 된 시드 단어를 입력하여 백업을 확인하십시오. + 백업에서 복원 + 24 개의 시드 단어를 모두 순서대로 입력해야합니다. + 당신의 자금이 위험에 처해 있습니다 + Zcash에서 당신은 은행이라는 것을 기억하십시오. 시드 문구가있는 사람은 누구나 지갑에 액세스 할 수 있습니다. + 다른 사람이 대신 백업 할 수 없으므로 즉시 백업해야합니다. + 시드 수락, 지금 스캔! + 지갑에 대한 거래를 위해 블록 체인을 스캔하고 있습니다. 이 지갑의 생일 날짜를 제공 할 수 있다면 동기화 속도를 높일 수 있습니다. + 날짜 입력 + 생일 + 몰라 + 백업 + 일단은 스킵 + 밸런스 화면 + 보낼 금액 입력 + 기대 XX ZEC + 사용 가능 + 사용할 수있는 자금 없습니다 + 동기화 + 스캐닝 + 보내는 금액 + 지갑 내역 + 당신의 주소 + 당신의 쉴드된 주소 + 당신의 투명 주소 + 주소 복사 + 클립 보드에 복사 + 쉴드된 사용자 + 피드백 보내기 + 지갑 백업 + 애플리케이션 로그보기 + 닫기 + 보내는 화면 + 보내기 + 쉴드된 지갑에서 + 투명한 지갑에서 + 두 지갑에서 + + 유효한 zcash 주소를 입력하십시오 + 유효한 쉴드된 주소입니다. + 유효한 투명 주소입니다. + 경고, 이것은 귀하의 주소 인 것 같습니다! + 경고,이 주소는 유효하지 않습니다! + 메모 + 문자 + 회신 포함 + 클립 보드에서 + 마지막 사용 + 알 수 없는 + 수신자 주소 스캔 + 보내기 인증 + XX ZEC를 보낼 것인지 확인하십시오. + XX ZEC를 보내기 + 취소되었습니다 + 보냈습니다! + + 세부 사항보기 + 돌아가기 + 지갑 내역 + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/custom_translations.xml b/app/src/main/res/values-ru/custom_translations.xml new file mode 100644 index 0000000..f0a6fc6 --- /dev/null +++ b/app/src/main/res/values-ru/custom_translations.xml @@ -0,0 +1,16 @@ + + , + , + d/M HH:mma + + + + Проверьте + Создано: %1$,d + Ожидание + Синхронизация…%1$d%% + Сканирование…%1$d%% + Пожалуйста подтвердите, что хотите отправить + символы + Отправка + diff --git a/app/src/main/res/values-ru/translated.xml b/app/src/main/res/values-ru/translated.xml new file mode 100644 index 0000000..727321a --- /dev/null +++ b/app/src/main/res/values-ru/translated.xml @@ -0,0 +1,69 @@ + + Кошелек ECC + Загрузка + Добро пожаловать! + В валюте + Ваша фраза восстановления + Мы рекомендуем использовать резервную копию на бумаге и хранилище паролей. + Необходимо создать резервную копию вашего кошелька. + Проверьте свою резервную копию. + Пожалуйста, заполните отсутствующие слова фразы восстановления, чтобы проверить свою резервную копию. + Восстановление из резервной копии + Вам необходимо будет ввести 24 слова фразы восстановления по порядку. + Существует риск потерять средства! + Помните, что с Zcash Вы сами находитесь в роли банка. Любой, кто владеет фразой восстановления, получает полный доступ к кошельку. + Вам следует незамедлительно создать резервную копию, так она позволит восстановить доступ к Вашему кошельку. + Фраза восстановления принята, идёт сканирование блокчейна! + Мы сканируем блокчейн на предмет транзакций, которые относятся к вашему кошельку. Если вы можете указать дату создания данного кошелька, процесс ускорится. + Введите дату + Кошелёк создан + Я не знаю + Резервная копия + Пропустить + Экран баланса + Введите сумму для отправки + Ожидание XX ZEC + Доступно + Нет доступных средств + Синхронизация ... % + Сканирование ... % + Отправить сумму + История транзакций + Ваш адрес + Ваш защищённый адрес + Ваш прозрачный адрес + Адрес скопирован! + Скопировать в буфер обмена + Защищенный пользователь + Отправить отзыв + Резервная копия кошелька + Просмотреть журнал операций приложения + Закрыть + Отправить экран + Отправить + с Вашего кошелька + с Вашего прозрачного кошелька + с двух кошельков + на + Введите действующий Zcash-адрес + Это действующий защищённый адрес + Это действующий прозрачный адрес + Внимание, похоже, это ваш адрес! + Внимание, этот адрес недействителен! + Заметка + символы (символы) + добавить "ответ на" + В буфере обмена + Последний используемый + Неизвестно + Сканировать адрес получателя + Подтвердите, чтобы отправить + Пожалуйста подтвердите, что хотите отправить XX ZEC на адрес + Отправка XX ZEC на + Отменено + Отправлено! + Готово + Смотреть подробности + Вернуться назад + История транзакций + \ No newline at end of file diff --git a/app/src/main/res/values-zh/custom_translations.xml b/app/src/main/res/values-zh/custom_translations.xml new file mode 100644 index 0000000..9094876 --- /dev/null +++ b/app/src/main/res/values-zh/custom_translations.xml @@ -0,0 +1,16 @@ + + , + , + d/M HH:mma + + + + 验证 + 创建日期: %1$,d + 预计为 + 正在同步…%1$d%% + 正在扫描…%1$d%% + 请确认,您现在要发送 + 字符 + 正在发送 + diff --git a/app/src/main/res/values-zh/translated.xml b/app/src/main/res/values-zh/translated.xml new file mode 100644 index 0000000..265d318 --- /dev/null +++ b/app/src/main/res/values-zh/translated.xml @@ -0,0 +1,69 @@ + + ECC 钱包 + 准备工作 + 欢迎使用 ECC 钱包! + 法币 + 您的备份种子(助记词) + 我们建议您使用纸笔抄录备份种子,并锁到带密码的保险箱里。 + 您的钱包需要备份。 + 需要验证您的备份。 + 请填入缺失的词语,以验证您的备份无误。 + 从备份助记词中恢复钱包 + 您需要按照顺序输入 24 个助记词。 + 您的资金处在安全风险中! + 请记住,使用 Zcash 的时候,没有其他人在托管您的资金,您 是自己的银行。使用资金的唯一凭证就是助记词。如有他人知道了您的助记词,就有了转移您资金的所有权限。 + 请立即备份,如果您遗失了自己的备份,没有任何人能帮您恢复钱包、找回资金。 + 已接受助记词,正在扫描您的帐户余额。 + 软件正在扫描区块链以发现与您的钱包有关的交易。如果您能提供这个钱包的创建时间,可以加快扫描速度。 + 输入日期 + 创建日期 + 我不知道 / 我忘了 + 备份 + 跳过 + 余额扫描 + 输入您想发送的数额 + 预计为 XX ZEC + 可用 + 暂无可用的款项 + 正在同步 ...% + 正在扫描 ...% + 发送数额 + 钱包历史 + 您的地址 + 您的隐蔽地址 + 您的透明地址 + 已复制地址 + 已复制到剪贴板 + 隐蔽用户 + 发送反馈 + 备份钱包 + 查看应用日志 + 关闭 + 发送资金功能 + 发送 + 从您的隐蔽钱包发送 + 从您的透明钱包发送 + 同时从您的隐蔽钱包和透明钱包拼凑数额 + + 请输入一个有效的 Zcash 地址 + 这是一个有效的隐蔽地址 + 这是一个有效的透明地址 + 警告,该地址似乎是您自己的地址! + 警告,该地址不是一个有效地址! + 备注 + 字符 + + 在剪贴板内 + 上一次使用 + 未知 + 扫描接收人地址 + 授权发送 + 请确认,您现在要发送 XX ZEC 发送给 + 正在发送 XX ZEC 给 + 已取消 + 已发送! + 完成 + 查看细节 + 返回 + 您的钱包历史 + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..e03ab88 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,91 @@ + + + + + + + + + + @color/zcashYellow + #7C7C7C + #454545 + #A1A1A1 + @color/text_light + @color/background_banner + + + + + + + + + + #F5F5F5 + #616161 + #1FFFFFFF + #3DFFFFFF + #66FFFFFF + #80FFFFFF + #A3FFFFFF + #BFFFFFFF + #EDEDED + #2B2B2B + #1F000000 + #66000000 + #8A000000 + #DD000000 + #171717 + #00000000 + + #66BB6A + #BB666A + #26DAB6 + #4A90E2 + + + #B3B3B3 + #666666 + #333333 + + + #727272 + + + + + + + + + #FF3F3F4F + #FF000000 + #FFFFFF + @color/zcashBlack_dark + #282828 + @color/zcashBlack_87 + #1FBB666A + @color/text_light + + + #FFFFFF + @color/zcashWhite_50 + @color/zcashBlack_87 + @color/zcashBlack_54 + @color/zcashBlack_40 + + + + #9B9B9B + #D3D3D3 + #494A4C + #FFFFFF + + + #B8B8B8 + + diff --git a/app/src/main/res/values/custom_translations.xml b/app/src/main/res/values/custom_translations.xml new file mode 100644 index 0000000..fd6c84b --- /dev/null +++ b/app/src/main/res/values/custom_translations.xml @@ -0,0 +1,19 @@ + + . + . + M/d h:mma + + + + Verify + Birthday Height: %1$,d + Expecting + Downloading…%1$d%% + Scanning…%1$d%% + Please confirm that you want to send + "from your " + " wallet" + chars + Sending + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..3b54d2d --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,10 @@ + + + 0dp + + + + 0.381966 + 0.618034 + 0.145631068 + \ No newline at end of file diff --git a/app/src/main/res/values/integer.xml b/app/src/main/res/values/integer.xml new file mode 100644 index 0000000..226c74f --- /dev/null +++ b/app/src/main/res/values/integer.xml @@ -0,0 +1,6 @@ + + + 180 + 135 + 315 + diff --git a/app/src/main/res/values/missing_translation.xml b/app/src/main/res/values/missing_translation.xml new file mode 100644 index 0000000..1b27e54 --- /dev/null +++ b/app/src/main/res/values/missing_translation.xml @@ -0,0 +1,221 @@ + + + + + + + + Cancel + Store these backup words securely. + empowering\neveryone\nwith\neconomic\nfreedom + Backup verification coming soon! + + + + + + + My experience was . . . + My balance was . . . + I\'d like . . . + Any details you\'d like to share? + Was your balance clear? + What feature would you like to see next? + Thanks for the feedback! + + + Shielded address: + Updating + No history yet. + + + Updating + Reconnecting . . . + IDLE + Updating + Validating . . . + View Address + To make full use of this wallet, deposit funds to your address. + Fund Now + + HUSH enforces private z-z transactions at the protocol level to ensure maximum privacy. + All of the transactions you send and receive are completely private. + Great! + Tell me more + + + Are you sure? Without a backup, funds can be lost FOREVER! + You can\'t backup later. You\'re probably going to lose your funds! + Later + I\'ve been warned + New + Creating + Restore + Wallet created! Congratulations! + Wallet imported! Congratulations! + Welcome to SilentDragon! + + + Rescan Wallet + Share Log Files + + + Receive Funds + + + "Invalid Hush %1$s address:\n\n%2$s" + + + Retry + Cancel + Failed. + Retry + The transaction could not be encoded. + Unable to submit transaction to the network. + shielded + Enter a Hush address + "Last Used and On Clipboard" + Your transaction is shielded and your address is not available to the recipient + Memo + Your transaction is shielded but your address will be sent to the recipient via the memo + Cancel + This address appears to be invalid + Please go back and enter at least 1 Zatoshi. + Insufficient funds to cover miner\'s fee. + Memo must be less than %1$d in length. + Please go back and enter no more than %1$s %2$s. + Available funds not found. Please try again in a moment. + + + Restore Defaults + Change Lightwalletd Server + Enter a valid host name or IP address + Enter a valid port number + Reset + Host + Port + Failed to change server! + Successfully changed server! + Update + + + paid you + You paid + Paying + "from block " + Confirmation count temporarily unavailable + Explore + Total Spent + tap to view + reply-to + Received + Received from + Confirmed + of 10 confirmations + Expired + Pending confirmation + Sent + You Received + Total Received + + %1$s network fee + You Sent + Total Sent + to your shielded wallet + Pending + Transaction Details + with a memo + + + + + + + Hush Logo + back + copy + Profile + Scan + back + copy + settings + Hush + QR background + QR code + QR logo + back + scan frame + down arrow + back + scan QR code + selected + shield + back + back + Shield + back + direction + memo + memo icon + + + + + + + Ok + Failed to Change Server + Exit + Retry + Critical error while processing blocks! + Processor Error + Oh no! Invalid Build! + Unfortunately, the Rust libraries are missing and the only remedy is to update to a new version.\n\n(Note to developers: try commenting out mavenLocal). + Retry + That seed phrase appears to be invalid! Please double-check it and try again.\n\n%1$s + Oops! Invalid Seed Phrase + Exit + Retry + Critical error while processing blocks! + Processor Error + Ignore + Retry + Ignore + Retry + Scan Failure + Scan Failure + Clear Data + Exit + This wallet has not been initialized correctly! Perhaps an error occurred during install.\n\nThis can be fixed with a reset. First, locate your backup seed phrase, then CLEAR DATA and reimport it. + Wallet Improperly Initialized + Are you sure you\'d like to leave the app? This could reduce privacy, if you do not trust the destination. + Cancel + View Tx + Potential Privacy Risk + Cancel + Update + WARNING: Entering an invalid or untrusted server might result in misconfiguration or loss of funds! + Modify Lightwalletd Server? + Rescan This Wallet? +
    A QUICK scan will just rewind %1$s and take about %2$s.

    A FULL scan will rewind %3$s blocks to this wallet\'s birthday take around %4$s!

    A WIPE is like a full scan but it first deletes all data then restores it from your backup seed words.]]>
    + Quick + Full + Wipe + Don\'t show me again + Erase Wallet + Cancel + WARNING: Potential Loss of Funds\n\nClearing all wallet data and can result in a loss of funds, if you cannot locate your correct seed phrase.\n\nPlease confirm that you have your 24-word seed phrase available before proceeding. + Nuke Wallet? + + + + + + + Authenticate to proceed + Load backup phrase + Well, this is awkward. You denied permission for the camera. + Failed to open browser. + + . + +
    diff --git a/app/src/main/res/values/missing_translation_urls.xml b/app/src/main/res/values/missing_translation_urls.xml new file mode 100644 index 0000000..887ec58 --- /dev/null +++ b/app/src/main/res/values/missing_translation_urls.xml @@ -0,0 +1,8 @@ + + + + + + + https://hush.is/ + \ No newline at end of file diff --git a/app/src/main/res/values/strings-urls.xml b/app/src/main/res/values/strings-urls.xml new file mode 100644 index 0000000..090a9bd --- /dev/null +++ b/app/src/main/res/values/strings-urls.xml @@ -0,0 +1,4 @@ + + + @string/missing_autoshield_explanation_url + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..aacd614 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,293 @@ + + + @string/translated_button_done + @string/missing_cancel + @string/translated_send_unknown + + + + + + + + + @string/translated_button_done + @string/custom_translation_verify + @string/custom_translation_birthday + @string/missing_backup_instruction_store_words + @string/missing_backup_slogan + @string/missing_backup_verification_not_implemented + + + @string/missing_feedback_hint_1 + @string/missing_feedback_hint_2 + @string/missing_feedback_hint_3 + @string/missing_feedback_question_1 + @string/missing_feedback_question_2 + @string/missing_feedback_question_3 + @string/missing_feedback_thanks + + + @string/missing_history_address_label + @string/translated_balance_available + @string/missing_history_balance_updating + @string/missing_history_empty_text + @string/translated_screen_wallethistory + @string/translated_balance_amounttosend + + + @string/translated_balance_available + @string/missing_home_balance_updating + @string/custom_translation_expecting + @string/missing_home_button_send_disconnected + @string/custom_translation_syncing + @string/translated_button_sendamount + @string/missing_home_button_send_idle + @string/translated_balance_nofunds + @string/custom_translation_scanning + @string/missing_home_button_send_updating + @string/missing_home_button_send_validating + @string/missing_home_dialog_no_balance_button_positive + @string/missing_home_dialog_no_balance_message + @string/translated_balance_nofunds + @string/translated_button_wallethistory + @string/translated_balance_amounttosend + @string/missing_home_instruction_fund_now + @string/translated_balance_nofunds + @string/translated_balance_amounttosend + + + @string/missing_autoshielding_title_text + @string/missing_autoshielding_body_text + @string/missing_autoshielding_button_positive + @string/missing_autoshielding_button_neutral + + + Shield Transparent Funds + = TOTAL + + + + @string/missing_landing_backup_skipped_message_1 + @string/missing_landing_backup_skipped_message_2 + @string/missing_landing_button_backup_skipped_1 + @string/missing_landing_button_backup_skipped_2 + @string/missing_landing_button_primary + @string/translated_button_backup + @string/missing_landing_button_progress_create + @string/missing_landing_button_secondary + @string/translated_button_skip + @string/translated_button_skip + @string/missing_landing_create_success_message + @string/missing_landing_import_success_message + @string/translated_button_backup + @string/missing_landing_title + + + @string/translated_button_backup_wallet + @string/missing_profile_rescan_wallet + @string/translated_app_name + @string/translated_button_applicationlogs + @string/translated_button_feedback + @string/missing_profile_share_log_title + @string/translated_profile_screen + + + @string/translated_address_shielded + @string/missing_receive_address_title + @string/translated_send_scanqr + + + @string/missing_scan_invalid_address + + + @string/translated_label_to + @string/translated_send_unknown + @string/translated_address_shielded + @string/translated_button_send + @string/translated_label_replyto + @string/translated_button_seedetails + @string/custom_translation_send_confirm + @string/missing_send_failed_button_text + @string/translated_button_back + @string/missing_send_final_button_primary_cancel + @string/translated_button_seedetails + @string/missing_send_final_button_primary_failed + @string/missing_send_final_button_primary_retry + @string/translated_send_sent + @string/missing_send_final_error_encoding + @string/missing_send_final_error_submitting + @string/translated_send_cancelled + @string/custom_translation_sending + @string/translated_label_to + @string/missing_send_fund_source_highlight + @string/custom_translation_from_your + @string/custom_translation_wallet + @string/missing_send_hint_input_zcash_address + @string/translated_balance_amounttosend + @string/translated_send_onclipboard + @string/translated_send_lastused + @string/missing_send_history_last_and_clipboard + @string/custom_translation_chars + @string/missing_send_memo_excluded_message + @string/missing_send_memo_hint + @string/missing_send_memo_included_message + @string/missing_send_pending_button_text + @string/missing_send_validation_address_invalid + @string/translated_feedback_sameaddress + @string/translated_feedback_transparentaddress + @string/translated_feedback_shieldedaddress + @string/translated_feedback_default + @string/missing_send_validation_error_amount_minimum + @string/missing_send_validation_error_dust + @string/missing_send_validation_error_memo_length + @string/translated_balance_nofunds + @string/missing_send_validation_error_too_much + @string/missing_send_validation_error_unknown_funds + To add a memo, enter a shielded address. + More Info + + + @string/missing_settings_buttons_restore + @string/missing_settings_change_lightwalletd_server + @string/missing_settings_host_helper_text + @string/missing_settings_port_helper_text + @string/missing_settings_reset + @string/missing_settings_server_address + @string/missing_settings_server_port + @string/missing_settings_toast_change_server_failure + @string/missing_settings_toast_change_server_success + @string/missing_settings_update + + + @string/missing_transaction_address_paid_you + @string/missing_transaction_address_you_paid + @string/missing_transaction_address_paying + @string/missing_transaction_block_height_prefix + @string/missing_transaction_confirmation_count_unavailable + @string/missing_transaction_details_button_explore + @string/missing_transaction_details_total + @string/missing_transaction_instruction_tap + @string/missing_transaction_prefix_from + @string/translated_label_to + @string/missing_transaction_received + @string/missing_transaction_received_from + @string/missing_transaction_status_confirmed + @string/missing_transaction_status_confirming + @string/missing_transaction_status_expired + @string/missing_transaction_status_pending + @string/missing_transaction_status_sent + @string/translated_send_fromshielded + @string/missing_transaction_story_inbound + @string/missing_transaction_story_inbound_total + %1$s network fee + @string/missing_transaction_story_outbound + @string/missing_transaction_story_outbound_total + @string/missing_transaction_story_to_shielded + @string/missing_transaction_timestamp_unavailable + @string/missing_transaction_title + @string/missing_transaction_with_memo + + + + + + + @string/missing_content_description_backup_zcash_logo + @string/missing_content_description_history_back + @string/missing_content_description_history_copy + @string/missing_content_description_home_icon_profile + @string/missing_content_description_home_icon_scan + @string/missing_content_description_profile_back + @string/missing_content_description_profile_copy + @string/missing_content_description_profile_settings + @string/missing_content_description_profile_zebra + @string/translated_button_back + @string/missing_content_description_receive_qr_background + @string/missing_content_description_receive_qr_code + @string/missing_content_description_receive_qr_logo + @string/missing_content_description_scan_back + @string/missing_content_description_scan_frame + @string/missing_content_description_send_arrow + @string/missing_content_description_send_back + @string/missing_content_description_send_scan_qr + @string/missing_content_description_send_selected + @string/missing_content_description_send_shield + @string/missing_content_description_send_final_back + @string/missing_content_description_settings_back + @string/missing_content_description_transaction_shield + @string/missing_content_description_transaction_details_back + @string/missing_content_description_transaction_details_direction + @string/missing_content_description_transaction_details_memo + @string/missing_content_description_transaction_details_memo_icon + + + + + + + @string/missing_dialog_error_change_server_button_positive + @string/missing_dialog_error_change_server_title + @string/missing_dialog_error_critical_processing_button_negative + @string/missing_dialog_error_critical_processing_button_positive + @string/missing_dialog_error_critical_processing_message + @string/missing_dialog_error_critical_processing_title + @string/missing_dialog_error_critical_link_title + @string/missing_dialog_error_critical_link_message + @string/missing_dialog_error_invalid_seed_phrase_button_positive + @string/missing_dialog_error_invalid_seed_phrase_message + @string/missing_dialog_error_invalid_seed_phrase_title + @string/missing_dialog_error_processor_critical_button_negative + @string/missing_dialog_error_processor_critical_button_positive + @string/missing_dialog_error_processor_critical_message + @string/missing_dialog_error_processor_critical_title + @string/missing_dialog_error_scan_button_negative + @string/missing_dialog_error_scan_button_positive + @string/missing_dialog_error_scan_failure_button_negative + @string/missing_dialog_error_scan_failure_button_positive + @string/missing_dialog_error_scan_failure_title + @string/missing_dialog_error_scan_title + @string/missing_dialog_error_uninitialized_button_negative + @string/missing_dialog_error_uninitialized_button_positive + @string/missing_dialog_error_uninitialized_message + @string/missing_dialog_error_uninitialized_title + @string/missing_dialog_first_use_view_tx_message + @string/missing_dialog_first_use_view_tx_negative + @string/missing_dialog_first_use_view_tx_positive + @string/missing_dialog_first_use_view_tx_title + @string/missing_dialog_modify_server_button_negative + @string/missing_dialog_modify_server_button_positive + @string/missing_dialog_modify_server_message + @string/missing_dialog_modify_server_title + @string/missing_dialog_rescan_wallet_title + @string/missing_dialog_rescan_wallet_message + @string/missing_dialog_rescan_wallet_button_negative + @string/missing_dialog_rescan_wallet_button_neutral + @string/missing_dialog_rescan_wallet_button_positive + @string/missing_dialog_not_again + @string/missing_dialog_nuke_wallet_button_negative + @string/missing_dialog_nuke_wallet_button_positive + @string/missing_dialog_nuke_wallet_message + @string/missing_dialog_nuke_wallet_title + + + + + + + @string/translated_app_name + @string/translated_send_securityauth + @string/missing_biometric_backup_phrase_title + @string/missing_biometric_backup_phrase_description + @string/missing_camera_permission_denied + @string/missing_error_launch_url + + . + + a178e1ef062133fc121079cb12fa43c7 + @string/custom_translation_format_date_time_brief + @string/transaction_history_format_date_time_brief + + MM/dd/yy h:mma + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..e2163cc --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/translated.xml b/app/src/main/res/values/translated.xml new file mode 100644 index 0000000..2b25af9 --- /dev/null +++ b/app/src/main/res/values/translated.xml @@ -0,0 +1,75 @@ + + SilentDragon + Loading + Welcome + Fiat + Your backup seed + We recommend a paper backup and a password vault. + Your wallet needs to be backed up. + Verify your backup. + Please fill out the missing seed words to verify your backup. + Restoring from a Backup + You will need to enter all 24 seed words in order. + Your funds are at risk! + Remember, with Zcash YOU are the bank. Anyone with your seed phrase has access to your wallet. + You should back this up immediately as no one else can restore it for you. + Seed accepted, scanning! + We are scanning the blockchain for transactions pertaining to your wallet. If you can provide a birthday date for this wallet we can speed it up. + Enter Date + Birthday + I don\'t know + Backup + Skip for now + Balance Screen + Enter an amount to send + expecting XX ZEC + Available + No funds available + Syncing … % + Scanning … % + Send Amount + Wallet History + Your Address + Your Shielded Address + Your Transparent Address + Address Copied! + Copy to clipboard + Shielded Address + Send Feedback + Backup Wallet + See Application Logs + Close + Send Screen + Send + from your shielded wallet + from your transparent wallet + from both wallets + To + Enter a valid Zcash address + This is a valid shielded address + This is a valid transparent address + Warning, this appears to be your address! + Warning, this address is not valid! + Memo + chars (characters) + include reply-to + On Clipboard + Last Used + Unknown + Scan Recipient Address + Authenticate to send + Please confirm you want to send XX ZEC to + Sending XX ZEC to + Cancelled + Sent! + Done + See Details + Go Back + Your Wallet History + + + + \ No newline at end of file diff --git a/app/src/main/res/values/word_list_bip39.xml b/app/src/main/res/values/word_list_bip39.xml new file mode 100644 index 0000000..e9e44c9 --- /dev/null +++ b/app/src/main/res/values/word_list_bip39.xml @@ -0,0 +1,2053 @@ + + + + abandon + ability + able + about + above + absent + absorb + abstract + absurd + abuse + access + accident + account + accuse + achieve + acid + acoustic + acquire + across + act + action + actor + actress + actual + adapt + add + addict + address + adjust + admit + adult + advance + advice + aerobic + affair + afford + afraid + again + age + agent + agree + ahead + aim + air + airport + aisle + alarm + album + alcohol + alert + alien + all + alley + allow + almost + alone + alpha + already + also + alter + always + amateur + amazing + among + amount + amused + analyst + anchor + ancient + anger + angle + angry + animal + ankle + announce + annual + another + answer + antenna + antique + anxiety + any + apart + apology + appear + apple + approve + april + arch + arctic + area + arena + argue + arm + armed + armor + army + around + arrange + arrest + arrive + arrow + art + artefact + artist + artwork + ask + aspect + assault + asset + assist + assume + asthma + athlete + atom + attack + attend + attitude + attract + auction + audit + august + aunt + author + auto + autumn + average + avocado + avoid + awake + aware + away + awesome + awful + awkward + axis + baby + bachelor + bacon + badge + bag + balance + balcony + ball + bamboo + banana + banner + bar + barely + bargain + barrel + base + basic + basket + battle + beach + bean + beauty + because + become + beef + before + begin + behave + behind + believe + below + belt + bench + benefit + best + betray + better + between + beyond + bicycle + bid + bike + bind + biology + bird + birth + bitter + black + blade + blame + blanket + blast + bleak + bless + blind + blood + blossom + blouse + blue + blur + blush + board + boat + body + boil + bomb + bone + bonus + book + boost + border + boring + borrow + boss + bottom + bounce + box + boy + bracket + brain + brand + brass + brave + bread + breeze + brick + bridge + brief + bright + bring + brisk + broccoli + broken + bronze + broom + brother + brown + brush + bubble + buddy + budget + buffalo + build + bulb + bulk + bullet + bundle + bunker + burden + burger + burst + bus + business + busy + butter + buyer + buzz + cabbage + cabin + cable + cactus + cage + cake + call + calm + camera + camp + can + canal + cancel + candy + cannon + canoe + canvas + canyon + capable + capital + captain + car + carbon + card + cargo + carpet + carry + cart + case + cash + casino + castle + casual + cat + catalog + catch + category + cattle + caught + cause + caution + cave + ceiling + celery + cement + census + century + cereal + certain + chair + chalk + champion + change + chaos + chapter + charge + chase + chat + cheap + check + cheese + chef + cherry + chest + chicken + chief + child + chimney + choice + choose + chronic + chuckle + chunk + churn + cigar + cinnamon + circle + citizen + city + civil + claim + clap + clarify + claw + clay + clean + clerk + clever + click + client + cliff + climb + clinic + clip + clock + clog + close + cloth + cloud + clown + club + clump + cluster + clutch + coach + coast + coconut + code + coffee + coil + coin + collect + color + column + combine + come + comfort + comic + common + company + concert + conduct + confirm + congress + connect + consider + control + convince + cook + cool + copper + copy + coral + core + corn + correct + cost + cotton + couch + country + couple + course + cousin + cover + coyote + crack + cradle + craft + cram + crane + crash + crater + crawl + crazy + cream + credit + creek + crew + cricket + crime + crisp + critic + crop + cross + crouch + crowd + crucial + cruel + cruise + crumble + crunch + crush + cry + crystal + cube + culture + cup + cupboard + curious + current + curtain + curve + cushion + custom + cute + cycle + dad + damage + damp + dance + danger + daring + dash + daughter + dawn + day + deal + debate + debris + decade + december + decide + decline + decorate + decrease + deer + defense + define + defy + degree + delay + deliver + demand + demise + denial + dentist + deny + depart + depend + deposit + depth + deputy + derive + describe + desert + design + desk + despair + destroy + detail + detect + develop + device + devote + diagram + dial + diamond + diary + dice + diesel + diet + differ + digital + dignity + dilemma + dinner + dinosaur + direct + dirt + disagree + discover + disease + dish + dismiss + disorder + display + distance + divert + divide + divorce + dizzy + doctor + document + dog + doll + dolphin + domain + donate + donkey + donor + door + dose + double + dove + draft + dragon + drama + drastic + draw + dream + dress + drift + drill + drink + drip + drive + drop + drum + dry + duck + dumb + dune + during + dust + dutch + duty + dwarf + dynamic + eager + eagle + early + earn + earth + easily + east + easy + echo + ecology + economy + edge + edit + educate + effort + egg + eight + either + elbow + elder + electric + elegant + element + elephant + elevator + elite + else + embark + embody + embrace + emerge + emotion + employ + empower + empty + enable + enact + end + endless + endorse + enemy + energy + enforce + engage + engine + enhance + enjoy + enlist + enough + enrich + enroll + ensure + enter + entire + entry + envelope + episode + equal + equip + era + erase + erode + erosion + error + erupt + escape + essay + essence + estate + eternal + ethics + evidence + evil + evoke + evolve + exact + example + excess + exchange + excite + exclude + excuse + execute + exercise + exhaust + exhibit + exile + exist + exit + exotic + expand + expect + expire + explain + expose + express + extend + extra + eye + eyebrow + fabric + face + faculty + fade + faint + faith + fall + false + fame + family + famous + fan + fancy + fantasy + farm + fashion + fat + fatal + father + fatigue + fault + favorite + feature + february + federal + fee + feed + feel + female + fence + festival + fetch + fever + few + fiber + fiction + field + figure + file + film + filter + final + find + fine + finger + finish + fire + firm + first + fiscal + fish + fit + fitness + fix + flag + flame + flash + flat + flavor + flee + flight + flip + float + flock + floor + flower + fluid + flush + fly + foam + focus + fog + foil + fold + follow + food + foot + force + forest + forget + fork + fortune + forum + forward + fossil + foster + found + fox + fragile + frame + frequent + fresh + friend + fringe + frog + front + frost + frown + frozen + fruit + fuel + fun + funny + furnace + fury + future + gadget + gain + galaxy + gallery + game + gap + garage + garbage + garden + garlic + garment + gas + gasp + gate + gather + gauge + gaze + general + genius + genre + gentle + genuine + gesture + ghost + giant + gift + giggle + ginger + giraffe + girl + give + glad + glance + glare + glass + glide + glimpse + globe + gloom + glory + glove + glow + glue + goat + goddess + gold + good + goose + gorilla + gospel + gossip + govern + gown + grab + grace + grain + grant + grape + grass + gravity + great + green + grid + grief + grit + grocery + group + grow + grunt + guard + guess + guide + guilt + guitar + gun + gym + habit + hair + half + hammer + hamster + hand + happy + harbor + hard + harsh + harvest + hat + have + hawk + hazard + head + health + heart + heavy + hedgehog + height + hello + helmet + help + hen + hero + hidden + high + hill + hint + hip + hire + history + hobby + hockey + hold + hole + holiday + hollow + home + honey + hood + hope + horn + horror + horse + hospital + host + hotel + hour + hover + hub + huge + human + humble + humor + hundred + hungry + hunt + hurdle + hurry + hurt + husband + hybrid + ice + icon + idea + identify + idle + ignore + ill + illegal + illness + image + imitate + immense + immune + impact + impose + improve + impulse + inch + include + income + increase + index + indicate + indoor + industry + infant + inflict + inform + inhale + inherit + initial + inject + injury + inmate + inner + innocent + input + inquiry + insane + insect + inside + inspire + install + intact + interest + into + invest + invite + involve + iron + island + isolate + issue + item + ivory + jacket + jaguar + jar + jazz + jealous + jeans + jelly + jewel + job + join + joke + journey + joy + judge + juice + jump + jungle + junior + junk + just + kangaroo + keen + keep + ketchup + key + kick + kid + kidney + kind + kingdom + kiss + kit + kitchen + kite + kitten + kiwi + knee + knife + knock + know + lab + label + labor + ladder + lady + lake + lamp + language + laptop + large + later + latin + laugh + laundry + lava + law + lawn + lawsuit + layer + lazy + leader + leaf + learn + leave + lecture + left + leg + legal + legend + leisure + lemon + lend + length + lens + leopard + lesson + letter + level + liar + liberty + library + license + life + lift + light + like + limb + limit + link + lion + liquid + list + little + live + lizard + load + loan + lobster + local + lock + logic + lonely + long + loop + lottery + loud + lounge + love + loyal + lucky + luggage + lumber + lunar + lunch + luxury + lyrics + machine + mad + magic + magnet + maid + mail + main + major + make + mammal + man + manage + mandate + mango + mansion + manual + maple + marble + march + margin + marine + market + marriage + mask + mass + master + match + material + math + matrix + matter + maximum + maze + meadow + mean + measure + meat + mechanic + medal + media + melody + melt + member + memory + mention + menu + mercy + merge + merit + merry + mesh + message + metal + method + middle + midnight + milk + million + mimic + mind + minimum + minor + minute + miracle + mirror + misery + miss + mistake + mix + mixed + mixture + mobile + model + modify + mom + moment + monitor + monkey + monster + month + moon + moral + more + morning + mosquito + mother + motion + motor + mountain + mouse + move + movie + much + muffin + mule + multiply + muscle + museum + mushroom + music + must + mutual + myself + mystery + myth + naive + name + napkin + narrow + nasty + nation + nature + near + neck + need + negative + neglect + neither + nephew + nerve + nest + net + network + neutral + never + news + next + nice + night + noble + noise + nominee + noodle + normal + north + nose + notable + note + nothing + notice + novel + now + nuclear + number + nurse + nut + oak + obey + object + oblige + obscure + observe + obtain + obvious + occur + ocean + october + odor + off + offer + office + often + oil + okay + old + olive + olympic + omit + once + one + onion + online + only + open + opera + opinion + oppose + option + orange + orbit + orchard + order + ordinary + organ + orient + original + orphan + ostrich + other + outdoor + outer + output + outside + oval + oven + over + own + owner + oxygen + oyster + ozone + pact + paddle + page + pair + palace + palm + panda + panel + panic + panther + paper + parade + parent + park + parrot + party + pass + patch + path + patient + patrol + pattern + pause + pave + payment + peace + peanut + pear + peasant + pelican + pen + penalty + pencil + people + pepper + perfect + permit + person + pet + phone + photo + phrase + physical + piano + picnic + picture + piece + pig + pigeon + pill + pilot + pink + pioneer + pipe + pistol + pitch + pizza + place + planet + plastic + plate + play + please + pledge + pluck + plug + plunge + poem + poet + point + polar + pole + police + pond + pony + pool + popular + portion + position + possible + post + potato + pottery + poverty + powder + power + practice + praise + predict + prefer + prepare + present + pretty + prevent + price + pride + primary + print + priority + prison + private + prize + problem + process + produce + profit + program + project + promote + proof + property + prosper + protect + proud + provide + public + pudding + pull + pulp + pulse + pumpkin + punch + pupil + puppy + purchase + purity + purpose + purse + push + put + puzzle + pyramid + quality + quantum + quarter + question + quick + quit + quiz + quote + rabbit + raccoon + race + rack + radar + radio + rail + rain + raise + rally + ramp + ranch + random + range + rapid + rare + rate + rather + raven + raw + razor + ready + real + reason + rebel + rebuild + recall + receive + recipe + record + recycle + reduce + reflect + reform + refuse + region + regret + regular + reject + relax + release + relief + rely + remain + remember + remind + remove + render + renew + rent + reopen + repair + repeat + replace + report + require + rescue + resemble + resist + resource + response + result + retire + retreat + return + reunion + reveal + review + reward + rhythm + rib + ribbon + rice + rich + ride + ridge + rifle + right + rigid + ring + riot + ripple + risk + ritual + rival + river + road + roast + robot + robust + rocket + romance + roof + rookie + room + rose + rotate + rough + round + route + royal + rubber + rude + rug + rule + run + runway + rural + sad + saddle + sadness + safe + sail + salad + salmon + salon + salt + salute + same + sample + sand + satisfy + satoshi + sauce + sausage + save + say + scale + scan + scare + scatter + scene + scheme + school + science + scissors + scorpion + scout + scrap + screen + script + scrub + sea + search + season + seat + second + secret + section + security + seed + seek + segment + select + sell + seminar + senior + sense + sentence + series + service + session + settle + setup + seven + shadow + shaft + shallow + share + shed + shell + sheriff + shield + shift + shine + ship + shiver + shock + shoe + shoot + shop + short + shoulder + shove + shrimp + shrug + shuffle + shy + sibling + sick + side + siege + sight + sign + silent + silk + silly + silver + similar + simple + since + sing + siren + sister + situate + six + size + skate + sketch + ski + skill + skin + skirt + skull + slab + slam + sleep + slender + slice + slide + slight + slim + slogan + slot + slow + slush + small + smart + smile + smoke + smooth + snack + snake + snap + sniff + snow + soap + soccer + social + sock + soda + soft + solar + soldier + solid + solution + solve + someone + song + soon + sorry + sort + soul + sound + soup + source + south + space + spare + spatial + spawn + speak + special + speed + spell + spend + sphere + spice + spider + spike + spin + spirit + split + spoil + sponsor + spoon + sport + spot + spray + spread + spring + spy + square + squeeze + squirrel + stable + stadium + staff + stage + stairs + stamp + stand + start + state + stay + steak + steel + stem + step + stereo + stick + still + sting + stock + stomach + stone + stool + story + stove + strategy + street + strike + strong + struggle + student + stuff + stumble + style + subject + submit + subway + success + such + sudden + suffer + sugar + suggest + suit + summer + sun + sunny + sunset + super + supply + supreme + sure + surface + surge + surprise + surround + survey + suspect + sustain + swallow + swamp + swap + swarm + swear + sweet + swift + swim + swing + switch + sword + symbol + symptom + syrup + system + table + tackle + tag + tail + talent + talk + tank + tape + target + task + taste + tattoo + taxi + teach + team + tell + ten + tenant + tennis + tent + term + test + text + thank + that + theme + then + theory + there + they + thing + this + thought + three + thrive + throw + thumb + thunder + ticket + tide + tiger + tilt + timber + time + tiny + tip + tired + tissue + title + toast + tobacco + today + toddler + toe + together + toilet + token + tomato + tomorrow + tone + tongue + tonight + tool + tooth + top + topic + topple + torch + tornado + tortoise + toss + total + tourist + toward + tower + town + toy + track + trade + traffic + tragic + train + transfer + trap + trash + travel + tray + treat + tree + trend + trial + tribe + trick + trigger + trim + trip + trophy + trouble + truck + true + truly + trumpet + trust + truth + try + tube + tuition + tumble + tuna + tunnel + turkey + turn + turtle + twelve + twenty + twice + twin + twist + two + type + typical + ugly + umbrella + unable + unaware + uncle + uncover + under + undo + unfair + unfold + unhappy + uniform + unique + unit + universe + unknown + unlock + until + unusual + unveil + update + upgrade + uphold + upon + upper + upset + urban + urge + usage + use + used + useful + useless + usual + utility + vacant + vacuum + vague + valid + valley + valve + van + vanish + vapor + various + vast + vault + vehicle + velvet + vendor + venture + venue + verb + verify + version + very + vessel + veteran + viable + vibrant + vicious + victory + video + view + village + vintage + violin + virtual + virus + visa + visit + visual + vital + vivid + vocal + voice + void + volcano + volume + vote + voyage + wage + wagon + wait + walk + wall + walnut + want + warfare + warm + warrior + wash + wasp + waste + water + wave + way + wealth + weapon + wear + weasel + weather + web + wedding + weekend + weird + welcome + west + wet + whale + what + wheat + wheel + when + where + whip + whisper + wide + width + wife + wild + will + win + window + wine + wing + wink + winner + winter + wire + wisdom + wise + wish + witness + wolf + woman + wonder + wood + wool + word + work + world + worry + worth + wrap + wreck + wrestle + wrist + write + wrong + yard + year + yellow + you + young + youth + zebra + zero + zone + zoo + + \ No newline at end of file diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..c66b0a5 --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/qa/res/mipmap-hdpi/ic_launcher.png b/app/src/qa/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b424d5008ad4d26ce09e16e5b408f865b0a00b42 GIT binary patch literal 4779 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 FV1n{5#MA%) literal 0 HcmV?d00001 diff --git a/app/src/qa/res/mipmap-hdpi/ic_launcher_round.png b/app/src/qa/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..900868d0d228fa01cb72c0c6ce63405f1a2a8b73 GIT binary patch literal 6310 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 literal 0 HcmV?d00001 diff --git a/app/src/qa/res/mipmap-mdpi/ic_launcher.png b/app/src/qa/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7a075f4ceb0edfea15852d074d681a985f73c511 GIT binary patch literal 3061 zcmVeBM6+RU0RGo1;L#NA3rIjioVG~gq1)*gZC!^ zd5C}V@}%;IvGT|_X^f%5fyc(mcW4g_k)|T?_!n-UCRO&#m#QDd$y0}tLU!6(ddMz3 zpq2RWeERp{-UkzumIN;6s$E$M%ME?Mn5ApZW9_*m5;%irKY^dqu#@n6 zVxa_%B}?Go0txJ&=k9%TSG*)2NtMU%3&P&qK1&wvoFit}Trqp%#q6Ii=1`)Tqe)_p zr-(U~D(2(GVm?h1b1q%X`Ajhvvcz1<7IQgA%+*}A&z6e0<{5wIHGpx~T*TTx;+b<9 zV$LoR^9lR`ggLoT3;>uT*z3RoF?;8U*^L;sOqaz7tOPS(az_TF1K_(nz!a2QAoRiq&Sduu0vFHAHxwBUR+=?G6Kk>&}>W3B2&x}ZU z_Ji3HgznAWxo}|F2P;Ug%M#flYh)ghC}EvU-&ii@^A%!lt`zgHztcWmT5o?o*ChW>tn@??Jj0WNB!I|5mHRh`xXR2Ut6Xm*esm! zvT(|i5o?dneK4y60CvWe25`&+K>4893|gSFpA%xA_a^ri)2^YIRt>~-Zz?9b8)i^X z{p5FE=lv;t5G4G3zEZ(o^=08~y59TR0H70;lFykk^5MqGgT%a66Kwwm8_iTH{e6R& ze-?_lvq{XqHj4R2fwI11mYAW)%BwZ?IoobaWRxC~5lLe=dSC$T0A=-G3II0k)J0pLjD{{R5B-1;Rkqq-=I z)V~GrxAl5$Wwe+iu)K7PwpV6>09e#4bOs)Y=i9uj+5Eo&ID<|4wh}YqO{6|M)P1Pw z%#o_~*K3RU{jbCf>m;Uk3;o^-fU_d8HctUiQW$(@B>cS9TDwqr01hX9#Tu|Pzyin_ zt*o(_Z4u*a;&qxCt|^!#rdM+@zipyLf72`C3z_PVnfk99c#tB# zR~uzITfFExENP>saIZbO%hbNAWy*+BVJ%Qj{N(rIwYq-BTiT82@4DB8Sllfe=w4sB zjk<%3hq!V8Py&5hV*F=@rf~l^5PTmE=9V|a$Uct%Tc_z^kk{-myMdW8{SRW^Zta`K zS?V`;RO#ac0dTHV48ZL2Co!iZ^}K4VHw&=on(<<~G|}%IX4EwU?$bgu#d|GZRE;wb z)(WLV*>uIdH8I|K2;WzYR}gnQN`wty2LSByorMm2!;A+!b{@P3xC4iqlEwTOWs(_S zx|_WqCVqf|W`us z$Yu{tqxVd;7u7BukWR2OuG28`t&3iXs7RaTo|(f4Om003TSg5LXp!xsvhg)v>l{IXu?#fB~O<>&MsyAy%2%ka}ZW@m?t zGT}7_O}3dq`%Fp4N!GbSS-D5)1g%594{V(tE`WYgeH#EkMrPL>aahbY3r*^XdGrx6 zHGd{10`|l2i>XveOpT|+)P7p8S9wxQooXSg>sg&L#`UyXKMxswK3`Q2ek6s&QO2xC zG=uN~AYDPWHM|!))FZP%9kO$@Mf*FPqyhfvqnWbj4y6ob`+P>|yJemS zHr5i`?JIiXYt*lEL5^^qW}2fJqp$ z{}EJxwr&ci06^C;089e_w6`D!5DLfP#KmA!5OZ}7Bn;Fv6U6HA+BwJ;nIsS%so!a* z@@igROw)SWLMhABo@^)&lELSXdL3upJD49WL%}oqQ~}k1<4O1cP(;DaQ=z-5`tS}P zh_vBiI=rei!NMkxciZbcU}h)l9`B$tdM@yo?bP?{g~i9fHsC zd6KbYxB_6tZSee^Dj*SP7)9@e3m|`j1j$^%WO24kRR#%y4(WK&Q0@Pe3e!y>1jZS_ zIl<|=WtuX{EV*rPx*p=1*41P7_8A&Dr9?k3J*U|vNj7-;qzb48NG(hNy`=i;7zwUJ zFJ3=M9A<(b$O0YGF>P=MJyCLV7C3=WV#$M*$$IGZjcaLU*tuT#1+GK$DT5W5+alwY z7}mn@&Kfu~&1)o$Bv1u~lhLilg$ZB+0IUK4XwKxvio;A0#AY7|n~lzmwc~Iz21TX` zhJkU@RCK5LD9nZUES#j*-PY)><#YBA06RQlN`l9Jo3QT&kO(s?qALJkN>QNA`lEb_sy(9bul;uy85Pm3FdCbEO8AxS^Z~F{B1O78#qZKStJb-X7M^C zb6FGsbPN-~(r5{Cz~lh{>cKt4@qk5#HXBT@-H_VmK;sJu|71eFQ_E$I1WfFub<*&8 zJ@j#{^mB=S8Wd(?dZ#CjK_03bV3f*kwE zgabLbM+i#FpAA2dO%@Dnow8+Sy{#|n9AHu1lx_Ot^IJTbY%UYHWu0V^G?KCcB*IL3 zWVisjO7+Z0xy={%oH61ok8%shJ+t_;;m=5c1>@DpCLbuj+3N(&^@TpEtn&gVNhFJ; zk-TLf5&GwxC>q#V|F|1_PXOJe1{Mba!eovThioqMK+s{{I0a%e!cA>fEFfzZSfP*M z4L+tV+a-+0=xZg(awQz4B*M(1VN%q;gVeq&fV?jHPlLg2%_^80vljtxu(@wIbIxBqp;PUvFP&eV_aj`^VvN3Susw1(*M#Z-47~op#3{A&X>UN znoBhoK+|#n`2NN8J#>=1R7~4+ym4g!2tc@h3(6g%*;pPO$_P`v%9j-~nSUQ!6BA;a z&*ph@p-=i}t#B_8=kC226MB3P$wzFDh4VkM%Fp~*B2aki=M9kC34^3KX^<2pzbC~h zL!@}&P$|YyqBsqAOaD|C5@5em91;@MxZAp4@!jDepV;&YU?Hh%WF) zDXOCi>5}p+s)yfa9q$^8x%7MeC=Z3a>7lx{z5A$;8h*ZvuK9c^HPObO@vOG7SOaVQ zuE4AEsKudWH1vyQlzC_=ZDY`t4EpHd<#Ymegg@(N{O*h8Jch?&&Tmcxq4#?^jT%-i zrKcIlRt9^DO))riIA81t+m{6av(GWcj6L-k&;5+o*uRt-)V9aP^Ezv_(C8bVE0)lR z%3M0g;1vU~fP9CSk#l4@IbY_J^A+3I1p&j~c#ii>#0MfA2|XWcVNIJ8IvN)%KMGxm zS+SUIKe&j>ctiCmz~_;}K)i%cQdH_8=k@N{wp4*iSS&OoC6YA z@8Du8!ydKmeZk-lETSj|U(K7Ss$AYQkE-E$Ef*Jffcgl+v6phm%hK^3GMcxZcLV?D8dAq$MFGPz zOT(&!$W!h_cH0DsJY z$KUTPAoWI)7(Rc)fKM1AAnA{UNpM873i+^D_Bp^L_GeN7L#T6JB;3jKUOHubkVWdl zh2*gJ9Zocb#64uB1MgeS13=DLQcw3JHQ)|Xcil|t_LihNv)%bOQV;R7!S|3_G*-yl z+<*fhK@$a?3l$)cO%Cs?-kDEXmc%(Fwyp8|(r65OK!fLyI-e}o)k1s8Y9A6PPa_pQ zkW`x+NcHMKD(N4jUY^XJpGB%{9;w~)<@1VYdDpvxjGZ!w)Vt~8fG-UJI7Sm#qUEYd z0K?mJXq+3{S?m0sd34)*nN+=>z0VQQ;S3_cn|;ckv&N9>)`rxOUZgg}N_3$8f2}9= zpN*t`*(~<+CQ|?2KTcpeUM6Zn|(?W2Vf1twa(HJ-Yj@tkn$ ze$N6b+m}J=y$texzzetu*t-Axqomq4A+_uUA%sLdSxoBGIspsd-YsJP-6AAF?8mh- z4uF?WBy~$uQWHW0c@RW^bMO63^0G74jyd!WLkp>i^;go_Igf_Fo2J1#oWQ=?2t3(? z)cu`B9?&t8*Anio z+lkbD46etmr22Fu75Sjdv(*X&Pykrt>Rw*+#X+)94FYN(%XuH?OKCDy%!{PUe-#DG z==EfJdiOk?m<|(AH3`6@2>H&r5^_`!0Kn&okCAGBBdJmMv&Lg3Isk&G!694XNsZ)p z?QRT62Bp`QQY8E>%}5o+{F(qH!~~pOyl?STiU^8=yEK)i&{izOrOS&7fr(fE3n3@E z-y(_Yu0p61_p$dLzneXoD&B=mep)Y@hivd0xdE?pgw>$Gi)>h;K~)HAV-GkEhZ*+8 zXY9R)F~KP%iS(L37l7R!5F6sC`HoaNwF?ngK#nDVO+Ms>QigFY@5YPfNVTwa`@ycH zBKk;#0Puv#1(0yt4@Uc_Qi#ekedWABRljEC_I*Z1d(%bNE2D!ektGFhAqV_zM3I!$_%KolY+Dk9*m2WN zKsxBD9=Vg$tY?DHfR!(Dj&;;U#4jK0zb8%0(uX`Nm_(!eCDlfNGdvp!wkwsK-G%`2 zz?3k3vRYkZBgmy=3r?&Qk&^8|@poTN-n-k93hyI^&ku0b+CDqDp@YM)26D=@)w=D# zcsxV&u|L!R@9&;V4(1e1pG<2HrP3b9GwRXWFi)rH%fpYtOrdw&59y5 zw2y3j#N-3!-@%8|5TQk70iY`yNJBO=f{iQ5 z)Tc<@b`z;&?l_Z&iJ?lR3=jMbLq`fj^})OuJb!vPsl=gDvEqlwKFBc(9ys7sI2S76 z2%MNWP)e!(@eHQxxe6V{dHPS&L(b7qk0>Q0>^JbDR(ceW5pk{sogSydG zn-i$=^;x>{a<-+2_AwD)2X>e46Ct%EpwSuOvN=G2$cTO-5df^s;AN}};24j2g~m$l%Fr3WWcBEHb&> zL=0ESd)kZqFb-Eu%+aJ4%J||KDG1olCM+vK-P%k$G5|hBr1>g47g!cX(Q?LIPh-!L zR-#TuB#q|m23Q|Y_PI&mH~Rn7k>Wv+K#4`q0svSPvO;5Lr2tu^jV6^I$tUMj^FAw} za}e#)N)jCou?dUh?%zfB1^7|}o;~k_ZQ`yrA)Lkob5JWW?h$IoN%;eJdur=+a^L|s z32cNT9$rLZ+3JAnWY2aI9Y||mMj%gWntg{o5S_t1cbB<0V?C^mJ-As5@;qb%Wq>q0 z=EtymsIx9Uh=8k|ZkQiUZvY5K!V9nJq)rkNAk@2~T*h^Bn=)%9(}_#yvWWr+rz<3} z*Jbz{VwQ_mQ z7T1wl`hqwt2t3Ogwr(1qHv_eN>IPjaJ?47G;Qq?hqXPAPQvy|kF-QP}U5w8R)xNn@ zA$wi`1Ww38PUxtIxby5Hhgzo=(rqG7pliHC(%TZP52rbD#vpNVNWLMZTgs0}k-Z1)OVmL*EDJP4MC;4w>FjQrUeg2!Ex9ZYJe4a_&z>}Vt}#M?}uEBuHkT&uaDIyo)^rK z7h7G?;DpjLx#4QL-{N~7B%CF4CMz|`iE4s~G-)LA0%(|Cf4a^?y)Uor#pQ@4xtFp1 z1pq@sPxCdSVRmUmZyK<23jG2S>td;TlOa*!B7(@Ex53wjsPnNyL_)T5)n)*Q8y9i_ z=L9ZExNDRo27nu{qu){MJIgn06iZn-!|ObL|P17egJa*vqF+fv5*KjfAUn=s z@IGMR#%(4ro%O3lU7GlSV5$hwwKJZf{MCF2aZWgEJ>+OvSt2IHcH{Naq_ceX0U^<) zwR}y2uUb&s(MI5VA#|%4TVg<4saW7x9}eTa9Dt7tqZPmcOp4C&Uu{f2AXH6~hS7R> ziWgPI)2JE=4kCyMY_o~VmV|(>wZ3 zhNH08w87f*Hv)sF`#DIb)=0=ikZ3&Nv5=Wuhku5ocq6@8QH;(HNK9EmZnNZSKG(&; z%&}|XHzJqIdyIc1WES?kAn*kyM5;7>hYPx20L*GOX#L-|+uTcmn@t0%R^X zK#+K?uBcBPS~jM;xryry!)H9hJ2U2LGe_U5Jiq0C_NX;KxKcp3CQod1@c$qFWUPUC jRu5yew|UOR(E0xWz0nw1_pm)^00000NkvXXu0mjfb?gSo literal 0 HcmV?d00001 diff --git a/app/src/qa/res/mipmap-xhdpi/ic_launcher.png b/app/src/qa/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a9c4c564f935452e2452b1fc06e2eea884ab35 GIT binary patch literal 7010 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 AU;qFB literal 0 HcmV?d00001 diff --git a/app/src/qa/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/qa/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..cd38cedc55b1ee72d810c3e0f525b08344ac961d GIT binary patch literal 9263 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 literal 0 HcmV?d00001 diff --git a/app/src/qa/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/qa/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc974d250dade850eedc3804a8f9f05a05fea15 GIT binary patch literal 15478 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 literal 0 HcmV?d00001 diff --git a/app/src/qa/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/qa/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4c865bd4c0bcee12db196ad1fd12c60ba24df29b GIT binary patch literal 16339 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 literal 0 HcmV?d00001 diff --git a/app/src/qa/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/qa/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..020a355dc6bfea569c806fd847263fa4e9b72474 GIT binary patch literal 22654 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 literal 0 HcmV?d00001 diff --git a/app/src/qa/res/values/colors.xml b/app/src/qa/res/values/colors.xml new file mode 100644 index 0000000..279a45f --- /dev/null +++ b/app/src/qa/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #FFB900 + #664B00 + #FF000000 + diff --git a/app/src/qa/res/values/strings.xml b/app/src/qa/res/values/strings.xml new file mode 100644 index 0000000..a04a322 --- /dev/null +++ b/app/src/qa/res/values/strings.xml @@ -0,0 +1,3 @@ + + ECC (QA) + diff --git a/app/src/test/java/cash/z/ecc/android/ScratchPad.kt b/app/src/test/java/cash/z/ecc/android/ScratchPad.kt new file mode 100644 index 0000000..6601b8e --- /dev/null +++ b/app/src/test/java/cash/z/ecc/android/ScratchPad.kt @@ -0,0 +1,54 @@ +package cash.z.ecc.android + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.scanReduce +import kotlinx.coroutines.runBlocking +import org.junit.Test + +class ScratchPad { + + val t get() = System.currentTimeMillis() + var t0 = 0L + val Δt get() = t - t0 + + @Test + fun testMarblesCombine() = runBlocking { + var started = false + val flow = flowOf(1, 2, 3, 4, 5, 6, 7, 8, 9).onEach { + delay(100) + if (!started) { + t0 = t + started = true + } + println("$Δt\temitting $it") + } + val flow2 = flowOf("a", "b", "c", "d", "e", "f").onEach { delay(150); println("$Δt\temitting $it") } + val flow3 = flowOf("A", "B").onEach { delay(450); println("$Δt\temitting $it") } + combine(flow, flow2, flow3) { i, s, t -> "$i$s$t" }.onStart { + t0 = t + }.collect { +// if (!started) { +// println("$Δt until first emission") +// t0 = t +// started = true +// } + println("$Δt\t$it") // Will print "1a 2a 2b 2c" + } + } + + @Test + fun testMarblesScan() = runBlocking { + val flow = flowOf(1, 2, 3, 4, 5) + + flow.scanReduce { accumulator, value -> + println("was: $accumulator now: $value") + value + }.collect { + println("got $it") + } + } +} diff --git a/app/src/test/java/cash/z/ecc/android/SendViewModelTest.kt b/app/src/test/java/cash/z/ecc/android/SendViewModelTest.kt new file mode 100644 index 0000000..ab692e6 --- /dev/null +++ b/app/src/test/java/cash/z/ecc/android/SendViewModelTest.kt @@ -0,0 +1,112 @@ +package cash.z.ecc.android + +import cash.z.ecc.android.feedback.Feedback +import cash.z.ecc.android.sdk.db.entity.PendingTransaction +import cash.z.ecc.android.sdk.db.entity.isCreated +import cash.z.ecc.android.sdk.db.entity.isCreating +import cash.z.ecc.android.sdk.db.entity.isMined +import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess +import cash.z.ecc.android.ui.send.SendViewModel +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.test.setMain +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.Spy + +class SendViewModelTest { + + @Mock lateinit var creatingTx: PendingTransaction + @Mock lateinit var createdTx: PendingTransaction + @Mock lateinit var submittedTx: PendingTransaction + @Mock lateinit var minedTx: PendingTransaction + + @Mock + lateinit var feedback: Feedback + + @Spy + lateinit var sendViewModel: SendViewModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + Dispatchers.setMain(newSingleThreadContext("Main thread")) + + whenever(creatingTx.id).thenReturn(7) + whenever(creatingTx.submitAttempts).thenReturn(0) + + whenever(createdTx.id).thenReturn(7) + whenever(createdTx.raw).thenReturn(byteArrayOf(0x1)) + + whenever(submittedTx.id).thenReturn(7) + whenever(submittedTx.raw).thenReturn(byteArrayOf(0x1)) + whenever(submittedTx.submitAttempts).thenReturn(1) + + whenever(minedTx.id).thenReturn(7) + whenever(minedTx.raw).thenReturn(byteArrayOf(0x1)) + whenever(minedTx.submitAttempts).thenReturn(1) + whenever(minedTx.minedHeight).thenReturn(500_001) + + sendViewModel.feedback = feedback + } + + @Test + fun testUpdateMetrics_creating() { +// doNothing().whenever(sendViewModel).report(any()) + +// assertEquals(true, creatingTx.isCreating()) +// sendViewModel.updateMetrics(creatingTx) +// +// verify(sendViewModel).report("7.metric.tx.initialized") +// assertEquals(1, sendViewModel.metrics.size) +// verifyZeroInteractions(feedback) + } + + @Test + fun testUpdateMetrics_created() { + assertEquals(false, createdTx.isCreating()) + assertEquals(true, createdTx.isCreated()) +// sendViewModel.updateMetrics(creatingTx) +// sendViewModel.updateMetrics(createdTx) +// Thread.sleep(100) +// println(sendViewModel.metrics) +// +// verify(sendViewModel).report("7.metric.tx.created") +// assertEquals(1, sendViewModel.metrics.size) + } + + @Test + fun testUpdateMetrics_submitted() { + assertEquals(false, submittedTx.isCreating()) + assertEquals(false, submittedTx.isCreated()) + assertEquals(true, submittedTx.isSubmitSuccess()) +// sendViewModel.updateMetrics(creatingTx) +// sendViewModel.updateMetrics(createdTx) +// sendViewModel.updateMetrics(submittedTx) + assertEquals(5, sendViewModel.metrics.size) + + Thread.sleep(100) + assertEquals(1, sendViewModel.metrics.size) + + verify(feedback).report(sendViewModel.metrics.values.first()) + } + + @Test + fun testUpdateMetrics_mined() { + assertEquals(true, minedTx.isMined()) + assertEquals(true, minedTx.isSubmitSuccess()) +// sendViewModel.updateMetrics(creatingTx) +// sendViewModel.updateMetrics(createdTx) +// sendViewModel.updateMetrics(submittedTx) +// sendViewModel.updateMetrics(minedTx) +// assertEquals(7, sendViewModel.metrics.size) +// +// Thread.sleep(100) +// assertEquals(0, sendViewModel.metrics.size) + } +} diff --git a/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..ca6ee9c --- /dev/null +++ b/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/app/src/zcashmainnet/res/values/integers.xml b/app/src/zcashmainnet/res/values/integers.xml new file mode 100644 index 0000000..87ef8a1 --- /dev/null +++ b/app/src/zcashmainnet/res/values/integers.xml @@ -0,0 +1,4 @@ + + + 1 + \ No newline at end of file diff --git a/app/src/zcashmainnet/res/values/strings.xml b/app/src/zcashmainnet/res/values/strings.xml new file mode 100644 index 0000000..9630eb5 --- /dev/null +++ b/app/src/zcashmainnet/res/values/strings.xml @@ -0,0 +1,5 @@ + + https://explorer.hush.is/tx/%1$s + HUSH + https://hush.is + diff --git a/app/src/zcashtestnet/res/values/integers.xml b/app/src/zcashtestnet/res/values/integers.xml new file mode 100644 index 0000000..ef436b6 --- /dev/null +++ b/app/src/zcashtestnet/res/values/integers.xml @@ -0,0 +1,4 @@ + + + 0 + \ No newline at end of file diff --git a/app/src/zcashtestnet/res/values/strings.xml b/app/src/zcashtestnet/res/values/strings.xml new file mode 100644 index 0000000..1c8a964 --- /dev/null +++ b/app/src/zcashtestnet/res/values/strings.xml @@ -0,0 +1,6 @@ + + + https://explorer.testnet.z.cash/tx/%1$s + TAZ + https://play.google.com/apps/internaltest/4699113723491212355 + diff --git a/app/src/zcashtestnetQa/res/mipmap-hdpi/ic_launcher.png b/app/src/zcashtestnetQa/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b424d5008ad4d26ce09e16e5b408f865b0a00b42 GIT binary patch literal 4779 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 FV1n{5#MA%) literal 0 HcmV?d00001 diff --git a/app/src/zcashtestnetQa/res/mipmap-hdpi/ic_launcher_round.png b/app/src/zcashtestnetQa/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..900868d0d228fa01cb72c0c6ce63405f1a2a8b73 GIT binary patch literal 6310 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 literal 0 HcmV?d00001 diff --git a/app/src/zcashtestnetQa/res/mipmap-mdpi/ic_launcher.png b/app/src/zcashtestnetQa/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7a075f4ceb0edfea15852d074d681a985f73c511 GIT binary patch literal 3061 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 literal 0 HcmV?d00001 diff --git a/app/src/zcashtestnetQa/res/mipmap-xhdpi/ic_launcher.png b/app/src/zcashtestnetQa/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a9c4c564f935452e2452b1fc06e2eea884ab35 GIT binary patch literal 7010 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 AU;qFB literal 0 HcmV?d00001 diff --git a/app/src/zcashtestnetQa/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/zcashtestnetQa/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..cd38cedc55b1ee72d810c3e0f525b08344ac961d GIT binary patch literal 9263 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 literal 0 HcmV?d00001 diff --git a/app/src/zcashtestnetQa/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/zcashtestnetQa/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc974d250dade850eedc3804a8f9f05a05fea15 GIT binary patch literal 15478 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 literal 0 HcmV?d00001 diff --git a/app/src/zcashtestnetQa/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/zcashtestnetQa/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4c865bd4c0bcee12db196ad1fd12c60ba24df29b GIT binary patch literal 16339 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 literal 0 HcmV?d00001 diff --git a/app/src/zcashtestnetQa/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/zcashtestnetQa/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..020a355dc6bfea569c806fd847263fa4e9b72474 GIT binary patch literal 22654 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 literal 0 HcmV?d00001 diff --git a/app/src/zcashtestnetQa/res/values/colors.xml b/app/src/zcashtestnetQa/res/values/colors.xml new file mode 100644 index 0000000..279a45f --- /dev/null +++ b/app/src/zcashtestnetQa/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #FFB900 + #664B00 + #FF000000 + diff --git a/app/src/zcashtestnetQa/res/values/strings.xml b/app/src/zcashtestnetQa/res/values/strings.xml new file mode 100644 index 0000000..fd994c6 --- /dev/null +++ b/app/src/zcashtestnetQa/res/values/strings.xml @@ -0,0 +1,3 @@ + + ECC Testnet + diff --git a/build-convention/build.gradle.kts b/build-convention/build.gradle.kts new file mode 100644 index 0000000..252db19 --- /dev/null +++ b/build-convention/build.gradle.kts @@ -0,0 +1,23 @@ +import org.jetbrains.kotlin.konan.properties.loadProperties + +plugins { + `kotlin-dsl` +} + +buildscript { + dependencyLocking { + lockAllConfigurations() + } +} + +dependencyLocking { + lockAllConfigurations() +} + +// Per conversation in the KotlinLang Slack, Gradle uses Java 8 compatibility internally +// for all build scripts. +// https://kotlinlang.slack.com/archives/C19FD9681/p1636632870122900?thread_ts=1636572288.117000&cid=C19FD9681 +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} diff --git a/build-convention/buildscript-gradle.lockfile b/build-convention/buildscript-gradle.lockfile new file mode 100644 index 0000000..8f2c701 --- /dev/null +++ b/build-convention/buildscript-gradle.lockfile @@ -0,0 +1,45 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +com.github.gundy:semver4j:0.16.4=classpath +com.google.code.findbugs:jsr305:3.0.2=classpath +com.google.code.gson:gson:2.8.9=classpath +com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.guava:failureaccess:1.0.1=classpath +com.google.guava:guava:29.0-jre=classpath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath +com.google.j2objc:j2objc-annotations:1.3=classpath +de.undercouch:gradle-download-task:4.1.1=classpath +net.java.dev.jna:jna:5.6.0=classpath +org.checkerframework:checker-qual:2.11.1=classpath +org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:2.3.3=classpath +org.gradle.kotlin:gradle-kotlin-dsl-plugins:2.3.3=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.6.21=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.6.21=classpath +org.jetbrains.kotlin:kotlin-build-common:1.6.21=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.6.21=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.6.21=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.6.21=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.6.21=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.6.21=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.6.21=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.6.21=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.6.21=classpath +org.jetbrains.kotlin:kotlin-project-model:1.6.21=classpath +org.jetbrains.kotlin:kotlin-sam-with-receiver:1.6.21=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.6.21=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.6.21=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.6.21=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.6.21=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.21=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.6.21=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.6.21=classpath +org.jetbrains.kotlin:kotlin-util-io:1.6.21=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.6.21=classpath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath +org.jetbrains:annotations:13.0=classpath +empty= diff --git a/build-convention/gradle.lockfile b/build-convention/gradle.lockfile new file mode 100644 index 0000000..531ee73 --- /dev/null +++ b/build-convention/gradle.lockfile @@ -0,0 +1,25 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.6.21=kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.6.21=kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-native-utils:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-project-model:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-reflect:1.6.21=compileClasspath,kotlinCompilerClasspath +org.jetbrains.kotlin:kotlin-sam-with-receiver:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-script-runtime:1.6.21=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-scripting-common:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-scripting-jvm:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.21=compileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21=compileClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.6.21=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain +org.jetbrains.kotlin:kotlin-util-io:1.6.21=kotlinCompilerPluginClasspathMain +org.jetbrains:annotations:13.0=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain +empty=annotationProcessor,runtimeClasspath diff --git a/build-convention/settings.gradle.kts b/build-convention/settings.gradle.kts new file mode 100644 index 0000000..f283ba7 --- /dev/null +++ b/build-convention/settings.gradle.kts @@ -0,0 +1,31 @@ +@Suppress("UnstableApiUsage") +dependencyResolutionManagement { + repositories { + val isRepoRestrictionEnabled = true + + google { + if (isRepoRestrictionEnabled) { + content { + includeGroup("androidx.navigation") + includeGroup("com.android.tools") + includeGroup("com.google.testing.platform") + includeGroupByRegex("androidx.*") + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.android\\.tools.*") + } + } + } + mavenCentral { + if (isRepoRestrictionEnabled) { + content { + excludeGroup("androidx.navigation") + excludeGroup("com.android.tools") + excludeGroup("com.google.testing.platform") + excludeGroupByRegex("androidx.*") + excludeGroupByRegex("com\\.android.*") + excludeGroupByRegex("com\\.android\\.tools.*") + } + } + } + } +} diff --git a/build-convention/src/main/kotlin/zcash.ktlint-conventions.gradle.kts b/build-convention/src/main/kotlin/zcash.ktlint-conventions.gradle.kts new file mode 100644 index 0000000..54fc469 --- /dev/null +++ b/build-convention/src/main/kotlin/zcash.ktlint-conventions.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("java") +} + +val ktlint by configurations.creating + +dependencies { + ktlint("com.pinterest:ktlint:${project.property("KTLINT_VERSION")}") { + attributes { + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL)) + } + } +} + +tasks { + val editorConfigFile = rootProject.file(".editorconfig") + val ktlintArgs = listOf("**/src/**/*.kt", "!**/build/**.kt", "--editorconfig=$editorConfigFile") + + register("ktlint", org.gradle.api.tasks.JavaExec::class) { + description = "Check code style with ktlint" + classpath = ktlint + mainClass.set("com.pinterest.ktlint.Main") + args = ktlintArgs + } + + register("ktlintFormat", org.gradle.api.tasks.JavaExec::class) { + // Workaround for ktlint bug; force to run on an older JDK + // https://github.com/pinterest/ktlint/issues/1274 + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_11.majorVersion)) + }) + + description = "Apply code style formatting with ktlint" + classpath = ktlint + mainClass.set("com.pinterest.ktlint.Main") + args = listOf("-F") + ktlintArgs + } +} + +java { + val javaVersion = JavaVersion.toVersion(project.property("ANDROID_JVM_TARGET").toString()) + sourceCompatibility = javaVersion + targetCompatibility = javaVersion +} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..5da59e1 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,47 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:7.3.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${cash.z.ecc.android.Deps.kotlinVersion}") + classpath("com.bugsnag:bugsnag-android-gradle-plugin:4.7.5") + classpath("androidx.navigation:navigation-safe-args-gradle-plugin:${cash.z.ecc.android.Deps.navigationVersion}") + } +} + +plugins { + id("com.github.ben-manes.versions") + id("zcash.ktlint-conventions") +} + +defaultTasks("clean", "installZcashmainnetRelease") + +tasks { +// named("clean") { +// rootProject.buildDir.deleteRecursively() +// } + + withType { + gradleReleaseChannel = "current" + + resolutionStrategy { + componentSelection { + all { + if (isNonStable(candidate.version) && !isNonStable(currentVersion)) { + reject("Unstable") + } + } + } + } + } +} + +val unstableKeywords = listOf("alpha", "beta", "rc", "m", "ea", "build") + +fun isNonStable(version: String): Boolean { + val versionLowerCase = version.toLowerCase() + + return unstableKeywords.any { versionLowerCase.contains(it) } +} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..13496d5 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + `kotlin-dsl-base` +} + +repositories { + mavenCentral() +} + +// Per conversation in the KotlinLang Slack, Gradle uses Java 8 compatibility internally +// for all build scripts. +// https://kotlinlang.slack.com/archives/C19FD9681/p1636632870122900?thread_ts=1636572288.117000&cid=C19FD9681 +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt new file mode 100644 index 0000000..b60fe9c --- /dev/null +++ b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt @@ -0,0 +1,117 @@ +package cash.z.ecc.android + +object Deps { + // For use in the top-level build.gradle which gives an error when provided + // `Deps.Kotlin.version` directly + const val kotlinVersion = "1.7.20" + const val navigationVersion = "2.5.2" + + const val compileSdkVersion = 31 + const val minSdkVersion = 21 + const val targetSdkVersion = 30 + const val versionName = "1.0.0-alpha74" + const val versionCode = 1_00_00_174 // last digits are alpha(0XX) beta(2XX) rc(4XX) release(8XX). Ex: 1_08_04_401 is an release candidate build of version 1.8.4 and 1_08_04_800 would be the final release. + const val packageName = "cash.z.ecc.android" + + + object AndroidX { + const val ANNOTATION = "androidx.annotation:annotation:1.3.0-alpha01" + const val APPCOMPAT = "androidx.appcompat:appcompat:1.4.0-alpha02" + const val BIOMETRICS = "androidx.biometric:biometric:1.2.0-alpha03" + const val CONSTRAINT_LAYOUT = "androidx.constraintlayout:constraintlayout:2.1.0-beta02" + const val CORE_KTX = "androidx.core:core-ktx:1.6.0" + const val FRAGMENT_KTX = "androidx.fragment:fragment-ktx:1.3.6" + const val LEGACY = "androidx.legacy:legacy-support-v4:1.0.0" + const val MULTIDEX = "androidx.multidex:multidex:2.0.1" + const val PAGING = "androidx.paging:paging-runtime-ktx:2.1.2" + const val RECYCLER = "androidx.recyclerview:recyclerview:1.2.1" + object CameraX : Version("1.1.0-alpha05") { + val CAMERA2 = "androidx.camera:camera-camera2:$version" + val CORE = "androidx.camera:camera-core:$version" + val LIFECYCLE = "androidx.camera:camera-lifecycle:$version" + object View : Version("1.0.0-alpha27") { + val EXT = "androidx.camera:camera-extensions:$version" + val VIEW = "androidx.camera:camera-view:$version" + } + } + object Lifecycle : Version("2.4.0-alpha02") { + val LIFECYCLE_RUNTIME_KTX = "androidx.lifecycle:lifecycle-runtime-ktx:$version" + } + object Navigation : Version(navigationVersion) { + val FRAGMENT_KTX = "androidx.navigation:navigation-fragment-ktx:$version" + val UI_KTX = "androidx.navigation:navigation-ui-ktx:$version" + } + object Room : Version("2.3.0") { + val ROOM_COMPILER = "androidx.room:room-compiler:$version" + val ROOM_KTX = "androidx.room:room-ktx:$version" + } + } + + object Google { + // solves error: Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules jetified-guava-26.0-android.jar (com.google.guava:guava:26.0-android) and listenablefuture-1.0.jar (com.google.guava:listenablefuture:1.0) + // per this recommendation from Chris Povirk, given guava's decision to split ListenableFuture away from Guava: https://groups.google.com/d/msg/guava-discuss/GghaKwusjcY/bCIAKfzOEwAJ + const val GUAVA = "com.google.guava:guava:27.0.1-android" + const val MATERIAL = "com.google.android.material:material:1.4.0-rc01" + } + object Grpc : Version("1.37.0") { + val ANDROID = "io.grpc:grpc-android:$version" + val OKHTTP = "io.grpc:grpc-okhttp:$version" + val PROTOBUG = "io.grpc:grpc-protobuf-lite:$version" + val STUB = "io.grpc:grpc-stub:$version" + } + object Analytics { // for dogfooding/crash-reporting/feedback only on internal team builds + val BUGSNAG = "com.bugsnag:bugsnag-android:5.9.4" + val MIXPANEL = "com.mixpanel.android:mixpanel-android:5.6.3" + } + object JavaX { + const val INJECT = "javax.inject:javax.inject:1" + const val JAVA_ANNOTATION = "javax.annotation:javax.annotation-api:1.3.2" + } + object Kotlin : Version(kotlinVersion) { + val STDLIB = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version" + val REFLECT = "org.jetbrains.kotlin:kotlin-reflect:$version" + object Coroutines : Version("1.6.4") { + val ANDROID = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version" + val CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" + val TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version" + } + } + object Zcash { + const val ANDROID_WALLET_PLUGINS = "cash.z.ecc.android:zcash-android-wallet-plugins:1.0.0" + const val KOTLIN_BIP39 = "cash.z.ecc.android:kotlin-bip39:1.0.4" + + /* SDK uses mavenLocal build with HUSH customizations for now + Run the following from Android SDK path to publish SDK locally + ./gradlew clean + ./gradlew build + ./gradlew build publishToMavenLocal + */ + const val SDK = "cash.z.ecc.android:zcash-android-sdk:1.9.0-beta01-SNAPSHOT" + } + object Misc { + const val LOTTIE = "com.airbnb.android:lottie:3.7.0" + const val CHIPS = "com.github.gmale:chips-input-layout:2.3.4" + object Plugins { + const val SECURE_STORAGE = "com.github.gmale:secure-storage-android:0.0.3"//"de.adorsys.android:securestoragelibrary:1.2.2" + const val QR_SCANNER = "com.google.zxing:core:3.4.1" + } + } + + object Test { + const val JUNIT = "junit:junit:4.13.2" + const val MOKITO = "org.mockito:mockito-android:3.12.4" + const val MOKITO_KOTLIN = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" + object Android { + const val CORE = "androidx.test:core:1.4.0" + const val RULES = "androidx.test:rules:1.4.0" + const val JUNIT = "androidx.test.ext:junit:1.1.3" + const val FRAGMENT = "androidx.fragment:fragment-testing:1.4.0-alpha08" + const val ESPRESSO = "androidx.test.espresso:espresso-core:3.4.0" + const val ESPRESSO_INTENTS = "androidx.test.espresso:espresso-intents:3.4.0" + const val NAVIGATION = "androidx.navigation:navigation-testing:2.3.0-alpha01" + } + } +} + +open class Version(@JvmField val version: String) + diff --git a/feedback/.gitignore b/feedback/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/feedback/.gitignore @@ -0,0 +1 @@ +/build diff --git a/feedback/build.gradle b/feedback/build.gradle new file mode 100644 index 0000000..c71cfef --- /dev/null +++ b/feedback/build.gradle @@ -0,0 +1,43 @@ +import cash.z.ecc.android.Deps + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion Deps.compileSdkVersion + + defaultConfig { + minSdkVersion Deps.minSdkVersion + targetSdkVersion Deps.targetSdkVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + kotlinOptions { + freeCompilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + namespace 'cash.z.ecc.android.feedback' + +} + +dependencies { + implementation Deps.Kotlin.STDLIB + implementation Deps.AndroidX.APPCOMPAT + implementation Deps.AndroidX.CORE_KTX + implementation Deps.Kotlin.Coroutines.CORE + implementation Deps.Kotlin.Coroutines.TEST + testImplementation Deps.Test.JUNIT +} diff --git a/feedback/consumer-rules.pro b/feedback/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/feedback/proguard-rules.pro b/feedback/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/feedback/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/feedback/src/main/AndroidManifest.xml b/feedback/src/main/AndroidManifest.xml new file mode 100644 index 0000000..75bdb7d --- /dev/null +++ b/feedback/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/feedback/src/main/java/cash/z/ecc/android/feedback/Feedback.kt b/feedback/src/main/java/cash/z/ecc/android/feedback/Feedback.kt new file mode 100644 index 0000000..9e3e941 --- /dev/null +++ b/feedback/src/main/java/cash/z/ecc/android/feedback/Feedback.kt @@ -0,0 +1,284 @@ +package cash.z.ecc.android.feedback + +import cash.z.ecc.android.feedback.util.CompositeJob +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BroadcastChannel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import java.io.PrintWriter +import java.io.StringWriter +import kotlin.coroutines.coroutineContext + +// There are deprecations with the use of BroadcastChannel +@OptIn(ObsoleteCoroutinesApi::class) +class Feedback(capacity: Int = 256) { + lateinit var scope: CoroutineScope + private set + + private val _metrics = BroadcastChannel(capacity) + private val _actions = BroadcastChannel(capacity) + private var onStartListeners: MutableList<() -> Unit> = mutableListOf() + + private val jobs = CompositeJob() + + val metrics: Flow = _metrics.asFlow() + val actions: Flow = _actions.asFlow() + + /** + * Verifies that this class is not leaking anything. Checks that all underlying channels are + * closed and all launched reporting jobs are inactive. + */ + val isStopped get() = ensureScope() && _metrics.isClosedForSend && _actions.isClosedForSend && !scope.isActive && !jobs.isActive() + + /** + * Starts this feedback as a child of the calling coroutineContext, meaning when that context + * ends, this feedback's scope and anything it launced will cancel. Note that the [metrics] and + * [actions] channels will remain open unless [stop] is also called on this instance. + */ + suspend fun start(): Feedback { + if(::scope.isInitialized) { + return this + } + scope = CoroutineScope(Dispatchers.IO + SupervisorJob(coroutineContext[Job])) + invokeOnCompletion { + _metrics.close() + _actions.close() + } + onStartListeners.forEach { it() } + onStartListeners.clear() + return this + } + + fun invokeOnCompletion(block: CompletionHandler) { + ensureScope() + scope.coroutineContext[Job]!!.invokeOnCompletion(block) + } + + /** + * Invokes the given callback after the scope has been initialized or immediately, if the scope + * has already been initialized. This is used by [FeedbackCoordinator] and things like it that + * want to immediately begin collecting the metrics/actions flows because any emissions that + * occur before subscription are dropped. + */ + fun onStart(onStartListener: () -> Unit) { + if (::scope.isInitialized) { + onStartListener() + } else { + onStartListeners.add(onStartListener) + } + } + + /** + * Stop this instance and close all reporting channels. This function will first wait for all + * in-flight reports to complete. + */ + suspend fun stop() { + // expose instances where stop is being called before start occurred. + ensureScope() + await() + scope.cancel() + } + + /** + * Suspends until all in-flight reports have completed. This is automatically called before + * [stop]. + */ + suspend fun await() { + jobs.await() + } + + /** + * Measures the given block of code by surrounding it with time metrics and the reporting the + * result. + * + * Don't measure code that launches coroutines, instead measure the code within the coroutine + * that gets launched. Otherwise, the timing will be incorrect because the launched coroutine + * will run concurrently--meaning a "happens before" relationship between the measurer and the + * measured cannot be established and thereby the concurrent action cannot be timed. + */ + inline fun measure(key: String = "measurement.generic", description: Any = "measurement", block: () -> T): T { + ensureScope() + val metric = TimeMetric(key, description.toString()).markTime() + val result = block() + metric.markTime() + report(metric) + return result + } + + /** + * Add the given metric to the stream of metric events. + * + * @param metric the metric to add. + */ + fun report(metric: Metric): Feedback { + jobs += scope.launch { + _metrics.send(metric) + } + return this + } + + /** + * Add the given action to the stream of action events. + * + * @param action the action to add. + */ + fun report(action: Action): Feedback { + jobs += scope.launch { + _actions.send(action) + } + return this + } + + /** + * Report the given error to everything that is tracking feedback. Converts it to a Crash object + * which is intended for use in property-based analytics. + * + * @param error the uncaught exception that occurred. + */ + fun report(error: Throwable?, isFatal: Boolean = false): Feedback { + return if (isFatal) report(Crash(error)) else report(NonFatal(error, "reported")) + } + + /** + * Ensures that the scope for this instance has been initialized. + */ + fun ensureScope(): Boolean { + check(::scope.isInitialized) { + "Error: feedback has not been initialized. Before attempting to use this feedback" + + " object, ensure feedback.start() has been called." + } + return true + } + + fun ensureStopped(): Boolean { + val errors = mutableListOf() + + if (!_metrics.isClosedForSend && !_actions.isClosedForSend) errors += "both channels are still open" + else if (!_actions.isClosedForSend) errors += "the actions channel is still open" + else if (!_metrics.isClosedForSend) errors += "the metrics channel is still open" + + if (scope.isActive) errors += "the scope is still active" + if (jobs.isActive()) errors += "reporting jobs are still active" + if (errors.isEmpty()) return true + throw IllegalStateException("Feedback is still active because ${errors.joinToString(", ")}.") + } + + + interface Metric : Mappable, Keyed { + override val key: String + val startTime: Long? + val endTime: Long? + val elapsedTime: Long? + val description: String + + override fun toMap(): Map { + return mapOf( + "key" to key, + "description" to description, + "startTime" to (startTime ?: 0), + "endTime" to (endTime ?: 0), + "elapsedTime" to (elapsedTime ?: 0) + ) + } + } + + interface Action : Feedback.Mappable, Keyed { + override val key: String + override fun toMap(): Map { + return mapOf("key" to key) + } + } + + abstract class MappedAction private constructor(protected val propertyMap: MutableMap = mutableMapOf()) : Feedback.Action { + constructor(vararg properties: Pair) : this(mutableMapOf(*properties)) + + override fun toMap(): Map { + return propertyMap.apply { putAll(super.toMap()) } + } + } + + abstract class Funnel(funnelName: String, stepName: String, step: Int, vararg properties: Pair) : MappedAction( + "funnelName" to funnelName, + "stepName" to stepName, + "step" to step, + *properties + ) { + override fun toString() = key + override val key: String = "funnel.$funnelName.$stepName.$step" + } + + interface Keyed { + val key: T + } + + interface Mappable { + fun toMap(): Map + } + + data class TimeMetric( + override val key: String, + override val description: String, + val times: MutableList = mutableListOf() + ) : Metric { + override val startTime: Long? get() = times.firstOrNull() + override val endTime: Long? get() = times.lastOrNull() + override val elapsedTime: Long? get() = endTime?.minus(startTime ?: 0) + fun markTime(): TimeMetric { + times.add(System.currentTimeMillis()) + return this + } + + override fun toString(): String { + return "$description in ${elapsedTime}ms" + } + } + + open class AppError(name: String = "unknown", description: String? = null, isFatal: Boolean = false, vararg properties: Pair) : MappedAction( + "isError" to true, + "isFatal" to isFatal, + "errorName" to name, + "message" to (description ?: "None"), + "description" to describe(name, description, isFatal), + *properties + ) { + val isFatal: Boolean by propertyMap + val errorName: String by propertyMap + val description: String by propertyMap + constructor(name: String, exception: Throwable? = null, isFatal: Boolean = false) : this( + name, exception?.toString(), isFatal, + "exceptionString" to (exception?.toString() ?: "None"), + "message" to (exception?.message ?: "None"), + "cause" to (exception?.cause?.toString() ?: "None"), + "cause.cause" to (exception?.cause?.cause?.toString() ?: "None"), + "cause.cause.cause" to (exception?.cause?.cause?.cause?.toString() ?: "None") + ) { + propertyMap.putAll(exception.stacktraceToMap()) + } + + override val key = "error.${if (isFatal) "fatal" else "nonfatal"}.$name" + override fun toString() = description + + companion object { + fun describe(name: String, description: String?, isFatal: Boolean) = + "${if (isFatal) "Error: FATAL" else "Error: non-fatal"} $name error due to: ${description ?: "unknown error"}" + } + } + + class Crash(val exception: Throwable? = null) : AppError( "crash", exception, true) + class NonFatal(val exception: Throwable? = null, name: String) : AppError(name, exception, false) +} + + + +private fun Throwable?.stacktraceToMap(chunkSize: Int = 250): Map { + val properties = mutableMapOf("stacktrace.0" to "None") + if (this == null) return properties + val stringWriter = StringWriter() + + printStackTrace(PrintWriter(stringWriter)) + + stringWriter.toString().chunked(chunkSize).forEachIndexed { index, chunk -> + properties["stacktrace.$index"] = chunk + } + return properties +} diff --git a/feedback/src/main/java/cash/z/ecc/android/feedback/FeedbackCoordinator.kt b/feedback/src/main/java/cash/z/ecc/android/feedback/FeedbackCoordinator.kt new file mode 100644 index 0000000..d478a7c --- /dev/null +++ b/feedback/src/main/java/cash/z/ecc/android/feedback/FeedbackCoordinator.kt @@ -0,0 +1,128 @@ +package cash.z.ecc.android.feedback + +import cash.z.ecc.android.feedback.util.CompositeJob +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.coroutines.coroutineContext + +/** + * Takes care of the boilerplate involved in processing feedback emissions. Simply provide callbacks + * and emissions will occur in a mutually exclusive way, across all processors, so that things like + * writing to a file can occur without clobbering changes. This class also provides a mechanism for + * waiting for any in-flight emissions to complete. Lastly, all monitoring will cleanly complete + * whenever the feedback is stopped or its parent scope is cancelled. + */ +class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set = setOf()) { + + init { + feedback.apply { + onStart { + invokeOnCompletion { + flush() + } + } + } + defaultObservers.forEach { + addObserver(it) + } + } + + private var contextMetrics = Dispatchers.IO + private var contextActions = Dispatchers.IO + private val jobs = CompositeJob() + val observers = mutableSetOf() + + /** + * Wait for any in-flight listeners to complete. + */ + suspend fun await() { + jobs.await() + flush() + } + + /** + * Cancel all in-flight observer functions. + */ + fun cancel() { + jobs.cancel() + flush() + } + + /** + * Flush all observers so they can clear all pending buffers. + */ + fun flush() { + observers.forEach { it.flush() } + } + + /** + * Inject the context on which to observe metrics, mostly for testing purposes. + */ + fun metricsOn(dispatcher: CoroutineDispatcher): FeedbackCoordinator { + contextMetrics = dispatcher + return this + } + + /** + * Inject the context on which to observe actions, mostly for testing purposes. + */ + fun actionsOn(dispatcher: CoroutineDispatcher): FeedbackCoordinator { + contextActions = dispatcher + return this + } + + /** + * Add a coordinated observer that will not clobber all other observers because their actions + * are coordinated via a global mutex. + */ + fun addObserver(observer: FeedbackObserver) { + feedback.onStart { + observers += observer.initialize() + observeMetrics(observer::onMetric) + observeActions(observer::onAction) + } + } + + inline fun findObserver(): T? { + return observers.firstOrNull { it::class == T::class } as T? + } + + private fun observeMetrics(onMetricListener: (Feedback.Metric) -> Unit) { + feedback.metrics.onEach { + jobs += feedback.scope.launch { + withContext(contextMetrics) { + mutex.withLock { + onMetricListener(it) + } + } + } + }.launchIn(feedback.scope) + } + + private fun observeActions(onActionListener: (Feedback.Action) -> Unit) { + feedback.actions.onEach { + val id = coroutineContext.hashCode() + jobs += feedback.scope.launch { + withContext(contextActions) { + mutex.withLock { + onActionListener(it) + } + } + } + }.launchIn(feedback.scope) + } + + interface FeedbackObserver { + fun initialize(): FeedbackObserver { return this } + fun onMetric(metric: Feedback.Metric) {} + fun onAction(action: Feedback.Action) {} + fun flush() {} + } + + companion object { + private val mutex: Mutex = Mutex() + } +} diff --git a/feedback/src/main/java/cash/z/ecc/android/feedback/util/CompositeJob.kt b/feedback/src/main/java/cash/z/ecc/android/feedback/util/CompositeJob.kt new file mode 100644 index 0000000..57dddaa --- /dev/null +++ b/feedback/src/main/java/cash/z/ecc/android/feedback/util/CompositeJob.kt @@ -0,0 +1,45 @@ +package cash.z.ecc.android.feedback.util + +import kotlinx.coroutines.Job + +class CompositeJob { + + private val activeJobs = mutableListOf() + val size: Int get() = activeJobs.size + + fun add(job: Job) { + activeJobs.add(job) + job.invokeOnCompletion { + remove(job) + } + } + + fun remove(job: Job): Boolean { + return activeJobs.remove(job) + } + + fun isActive(): Boolean { + return activeJobs.any { isActive() } + } + + suspend fun await() { + // allow for concurrent modification since the list isn't coroutine or thread safe + do { + val job = activeJobs.firstOrNull() + if (job?.isActive == true) { + job.join() + } else { + // prevents an infinite loop in the extreme edge case where the list has a null item + try { activeJobs.remove(job) } catch (t: Throwable) {} + } + } while (size > 0) + } + + fun cancel() { + activeJobs.filter { isActive() }.forEach { it.cancel() } + } + + operator fun plusAssign(also: Job) { + add(also) + } +} \ No newline at end of file diff --git a/feedback/src/test/java/cash/z/ecc/android/feedback/CoroutineTestRule.kt b/feedback/src/test/java/cash/z/ecc/android/feedback/CoroutineTestRule.kt new file mode 100644 index 0000000..8e53444 --- /dev/null +++ b/feedback/src/test/java/cash/z/ecc/android/feedback/CoroutineTestRule.kt @@ -0,0 +1,31 @@ +package cash.z.ecc.android.feedback + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +class CoroutinesTestRule( + val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() +) : TestWatcher() { + + lateinit var testScope: TestCoroutineScope + + override fun starting(description: Description?) { + super.starting(description) + Dispatchers.setMain(testDispatcher) + testScope = TestCoroutineScope() + } + + override fun finished(description: Description?) { + super.finished(description) + Dispatchers.resetMain() + testDispatcher.cleanupTestCoroutines() + if (testScope.coroutineContext[Job]?.isActive == true) testScope.cancel() + } +} \ No newline at end of file diff --git a/feedback/src/test/java/cash/z/ecc/android/feedback/FeedbackObserverTest.kt b/feedback/src/test/java/cash/z/ecc/android/feedback/FeedbackObserverTest.kt new file mode 100644 index 0000000..422af12 --- /dev/null +++ b/feedback/src/test/java/cash/z/ecc/android/feedback/FeedbackObserverTest.kt @@ -0,0 +1,55 @@ +package cash.z.ecc.android.feedback + +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test + +class FeedbackObserverTest { + + private val feedback: Feedback = Feedback() + private val feedbackCoordinator: FeedbackCoordinator = FeedbackCoordinator(feedback) + + private var counter: Int = 0 + private val simpleAction = object : Feedback.Action { + override val key = "ButtonClick" + } + + @Test + fun testConcurrency() = runBlocking { + val actionCount = 50 + val processorCount = 50 + val expectedTotal = actionCount * processorCount + + repeat(processorCount) { + addObserver() + } + + feedback.start() + repeat(actionCount) { + sendAction() + } + + feedback.await() // await sends + feedbackCoordinator.await() // await processing + feedback.stop() + + assertEquals( + "Concurrent modification happened ${expectedTotal - counter} times", + expectedTotal, + counter + ) + } + + private fun addObserver() { + feedbackCoordinator.addObserver(object : FeedbackCoordinator.FeedbackObserver { + override fun onAction(action: Feedback.Action) { + counter++ + } + }) + } + + private fun sendAction() { + feedback.report(simpleAction) + } + +} \ No newline at end of file diff --git a/feedback/src/test/java/cash/z/ecc/android/feedback/FeedbackTest.kt b/feedback/src/test/java/cash/z/ecc/android/feedback/FeedbackTest.kt new file mode 100644 index 0000000..b55a992 --- /dev/null +++ b/feedback/src/test/java/cash/z/ecc/android/feedback/FeedbackTest.kt @@ -0,0 +1,156 @@ +package cash.z.ecc.android.feedback + +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.Assert.* +import org.junit.Test +import java.lang.RuntimeException + +class FeedbackTest { + + @Test + fun testMeasure_blocking() = runBlocking { + val duration = 1_100L + val feedback = Feedback().start() + verifyDuration(feedback, duration) + + feedback.measure { + workBlocking(duration) + } + } + + @Test + fun testMeasure_suspending() = runBlocking { + val duration = 1_100L + val feedback = Feedback().start() + verifyDuration(feedback, duration) + + feedback.measure { + workSuspending(duration) + } + } + + @Test + fun testTrack() = runBlocking { + val simpleAction = object : Feedback.Action { + override val key = "ButtonClick" + } + val feedback = Feedback().start() + verifyAction(feedback, simpleAction.key) + + feedback.report(simpleAction) + Unit + } + + @Test + fun testCancellation_stop() = runBlocking { + verifyFeedbackCancellation { feedback, _ -> + feedback.stop() + } + } + + @Test + fun testCancellation_cancel() = runBlocking { + verifyFeedbackCancellation { _, parentJob -> + parentJob.cancel() + } + } + + @Test(expected = IllegalStateException::class) + fun testCancellation_noCancel() = runBlocking { + verifyFeedbackCancellation { _, _ -> } + } + + @Test + fun testCrash() { + val rushing = RuntimeException("rushing") + val speeding = RuntimeException("speeding", rushing) + val runlight = RuntimeException("Run light", speeding) + val crash = Feedback.Crash(RuntimeException("BOOM", runlight)) + val map = crash.toMap() + printMap(map) + + assertNotNull(map["cause"]) + assertNotNull(map["cause.cause"]) + assertNotNull(map["cause.cause"]) + } + + @Test + fun testAppError_exception() { + val rushing = RuntimeException("rushing") + val speeding = RuntimeException("speeding", rushing) + val runlight = RuntimeException("Run light", speeding) + val error = Feedback.AppError("reported", RuntimeException("BOOM", runlight)) + val map = error.toMap() + printMap(map) + + assertFalse(error.isFatal) + assertNotNull(map["cause"]) + assertNotNull(map["cause.cause"]) + assertNotNull(map["cause.cause"]) + } + + @Test + fun testAppError_description() { + val error = Feedback.AppError("reported", "The server was down while downloading blocks!") + val map = error.toMap() + printMap(map) + + assertFalse(error.isFatal) + } + + private fun printMap(map: Map) { + for (entry in map) { + println("%-20s = %s".format(entry.key, entry.value)) + } + } + + private fun verifyFeedbackCancellation(testBlock: suspend (Feedback, Job) -> Unit) = runBlocking { + val feedback = Feedback() + var counter = 0 + val parentJob = launch { + feedback.start() + feedback.scope.launch { + delay(50) + counter = 1 + } + } + // give feedback.start a chance to happen before cancelling + delay(25) + // stop or cancel things here + testBlock(feedback, parentJob) + delay(75) + feedback.ensureStopped() + assertEquals(0, counter) + } + + private fun verifyDuration(feedback: Feedback, duration: Long) { + feedback.metrics.onEach { + val metric = (it as? Feedback.TimeMetric)?.elapsedTime + assertTrue( + "Measured time did not match duration. Expected $duration but was $metric", + metric ?: 0 >= duration + ) + feedback.stop() + }.launchIn(feedback.scope) + } + + private fun verifyAction(feedback: Feedback, name: String) { + feedback.actions.onEach { + assertTrue("Action did not match. Expected $name but was ${it.key}", name == it.key) + feedback.stop() + }.launchIn(feedback.scope) + } + + private fun workBlocking(duration: Long) { + Thread.sleep(duration) + } + + private suspend fun workSuspending(duration: Long) { + delay(duration) + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..f5e6189 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# org.gradle.parallel=true +kotlin.code.style=official +android.enableJetifier=true +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m +android.useAndroidX=true +dagger.fastInit=enabled +android.builder.sdkDownload=true + +# Optionally configures test orchestrator. While it provides isolated tests, it also nearly doubles +# the time it takes for test to run. +isUseTestOrchestrator=false + +# Toggles between using the SDK Maven artifact versus an included build +IS_SDK_INCLUDED_BUILD=false + +KTLINT_VERSION=0.45.2 + + +ANDROID_JVM_TARGET=1.8 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f127cfd --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lockbox/.gitignore b/lockbox/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/lockbox/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lockbox/build.gradle b/lockbox/build.gradle new file mode 100644 index 0000000..9aa0da2 --- /dev/null +++ b/lockbox/build.gradle @@ -0,0 +1,49 @@ +import cash.z.ecc.android.Deps + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion Deps.compileSdkVersion + + useLibrary 'android.test.runner' + + defaultConfig { + minSdkVersion Deps.minSdkVersion + targetSdkVersion Deps.targetSdkVersion + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments clearPackageData: 'true' + + consumerProguardFiles 'consumer-rules.pro' + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + namespace 'cash.z.ecc.android.lockbox' + +} + +dependencies { + implementation Deps.JavaX.INJECT + implementation Deps.Kotlin.STDLIB + implementation Deps.AndroidX.APPCOMPAT + implementation Deps.AndroidX.CORE_KTX + + // Zcash + implementation Deps.Zcash.ANDROID_WALLET_PLUGINS + + implementation Deps.Misc.Plugins.SECURE_STORAGE + + androidTestImplementation Deps.Test.Android.JUNIT +} diff --git a/lockbox/consumer-rules.pro b/lockbox/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/lockbox/proguard-rules.pro b/lockbox/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/lockbox/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/lockbox/src/androidTest/java/cash/z/ecc/android/lockbox/LockBoxText.kt b/lockbox/src/androidTest/java/cash/z/ecc/android/lockbox/LockBoxText.kt new file mode 100644 index 0000000..b1b08ed --- /dev/null +++ b/lockbox/src/androidTest/java/cash/z/ecc/android/lockbox/LockBoxText.kt @@ -0,0 +1,52 @@ +package cash.z.ecc.android.lockbox + +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class LockBoxText { + + private lateinit var appContext: Context + private lateinit var lockBox: LockBoxProvider + + @Before + fun start() { + appContext = InstrumentationRegistry.getInstrumentation().targetContext + lockBox = LockBox(appContext) + } + + @Test + fun testSeed_store() { + val testMessage = "Some Bytes To Test" + val testBytes = testMessage.toByteArray() + lockBox.setBytes("seed", testBytes) + assertEquals(testMessage, String(lockBox.getBytes("seed")!!)) + } + + @Test + fun testSeed_storeNegatives() { + val testBytes = byteArrayOf(0x00, 0x00, -0x0F, -0x0B) + lockBox.setBytes("seed", testBytes) + assertTrue(testBytes.contentEquals(lockBox.getBytes("seed")!!)) + } + + @Test + fun testSeed_storeLeadingZeros() { + val testBytes = byteArrayOf(0x00, 0x00, 0x0F, 0x0B) + lockBox.setBytes("seed", testBytes) + assertTrue(testBytes.contentEquals(lockBox.getBytes("seed")!!)) + } + + @Test + fun testPrivateKey_retrieve() { + val testMessage = "Some Bytes To Test" + lockBox.setCharsUtf8("spendingKey", testMessage.toCharArray()) + assertEquals(testMessage, String(lockBox.getCharsUtf8("spendingKey")!!)) + } +} diff --git a/lockbox/src/main/AndroidManifest.xml b/lockbox/src/main/AndroidManifest.xml new file mode 100644 index 0000000..75bdb7d --- /dev/null +++ b/lockbox/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/lockbox/src/main/java/cash/z/ecc/android/lockbox/LockBox.kt b/lockbox/src/main/java/cash/z/ecc/android/lockbox/LockBox.kt new file mode 100644 index 0000000..29e26e7 --- /dev/null +++ b/lockbox/src/main/java/cash/z/ecc/android/lockbox/LockBox.kt @@ -0,0 +1,133 @@ +package cash.z.ecc.android.lockbox + +import android.content.Context +import cash.z.android.plugin.LockBoxPlugin +import de.adorsys.android.securestoragelibrary.SecurePreferences +import java.nio.ByteBuffer +import java.nio.CharBuffer +import java.nio.charset.StandardCharsets +import java.util.* + +class LockBox(private val appContext: Context) : LockBoxPlugin { + + private val maxLength: Int = 50 + + override fun setBoolean(key: String, value: Boolean) { + setChunkedString(key, value.toString()) + } + + override fun getBoolean(key: String): Boolean { + return getChunkedString(key)?.toBoolean() ?: false + } + + override fun setBytes(key: String, value: ByteArray) { + // using hex here because this library doesn't really work well for byte arrays + // but hopefully we can code to arrays and then change the underlying library, later + setChunkedString(key, value.toHex()) + } + + override fun getBytes(key: String): ByteArray? { + return getChunkedString(key)?.fromHex() + } + + override fun setCharsUtf8(key: String, value: CharArray) { + // Using string here because this library doesn't work well for char arrays + // but hopefully we can code to arrays and then change the underlying library, later + setChunkedString(key, String(value)) + } + + override fun getCharsUtf8(key: String): CharArray? { + return getChunkedString(key)?.toCharArray() + } + + fun delete(key: String) { + return SecurePreferences.removeValue(appContext, key) + } + fun clear() { + SecurePreferences.clearAllValues(appContext) + } + + inline operator fun set(key: String, value: T) { + when (T::class) { + Boolean::class -> setBoolean(key, value as Boolean) + ByteArray::class -> setBytes(key, value as ByteArray) + CharArray::class -> setCharsUtf8(key, value as CharArray) + Double::class, Float::class, Integer::class, Long::class, String::class -> setChunkedString(key, value.toString()) + else -> throw UnsupportedOperationException("Lockbox does not yet support setting ${T::class.java.simpleName} objects but it can easily be added.") + } + } + + inline operator fun get(key: String): T? = when (T::class) { + Boolean::class -> getBoolean(key) + ByteArray::class -> getBytes(key) + CharArray::class -> getCharsUtf8(key) + Double::class -> getChunkedString(key)?.let { it.toDoubleOrNull() } + Float::class -> getChunkedString(key)?.let { it.toFloatOrNull() } + Integer::class -> getChunkedString(key)?.let { it.toIntOrNull() } + Long::class -> getChunkedString(key)?.let { it.toLongOrNull() } + String::class -> getChunkedString(key) + else -> throw UnsupportedOperationException("Lockbox does not yet support getting ${T::class.simpleName} objects but it can easily be added") + } as T + + /** + * Splits a string value into smaller pieces so as not to exceed the limit on the length of + * String that can be stored. + */ + fun setChunkedString(key: String, value: String) { + if (value.length > maxLength) { + SecurePreferences.setValue(appContext, key, value.chunked(maxLength)) + } else { + SecurePreferences.setValue(appContext, key, value) + } + } + + /** + * Returns a string value from storage by first fetching the key, directly. If that is missing, + * it checks for a chunked version of the key. If that exists, it will be merged and returned. + * If not, then null will be returned. + * + * @return the key if found and null otherwise. + */ + fun getChunkedString(key: String): String? { + return SecurePreferences.getStringValue(appContext, key, null) + ?: SecurePreferences.getStringListValue(appContext, key, listOf()).let { result -> + if (result.size == 0) null else result.joinToString("") + } + } + + + // + // Extensions (TODO: find library that works better with arrays of bytes and chars) + // + + private fun ByteArray.toHex(): String { + val sb = StringBuilder(size * 2) + for (b in this) + sb.append(String.format("%02x", b)) + return sb.toString() + } + + private fun String.fromHex(): ByteArray { + val len = length + val data = ByteArray(len / 2) + var i = 0 + while (i < len) { + data[i / 2] = + ((Character.digit(this[i], 16) shl 4) + Character.digit(this[i + 1], 16)).toByte() + i += 2 + } + return data + } + + private fun CharArray.toBytes(): ByteArray { + val byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(this)) + return Arrays.copyOf(byteBuffer.array(), byteBuffer.limit()); + } + + private fun ByteArray.fromBytes(): CharArray { + val charBuffer = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(this)) + return Arrays.copyOf(charBuffer.array(), charBuffer.limit()) + } +} + + diff --git a/mnemonic/.gitignore b/mnemonic/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/mnemonic/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mnemonic/build.gradle b/mnemonic/build.gradle new file mode 100644 index 0000000..9cb2b03 --- /dev/null +++ b/mnemonic/build.gradle @@ -0,0 +1,14 @@ +import cash.z.ecc.android.Deps + +apply plugin: 'kotlin' + +dependencies { + implementation Deps.JavaX.INJECT + implementation Deps.Kotlin.STDLIB + + // Zcash + implementation Deps.Zcash.ANDROID_WALLET_PLUGINS + implementation Deps.Zcash.KOTLIN_BIP39 + + testImplementation Deps.Test.JUNIT +} diff --git a/mnemonic/src/main/java/cash/z/ecc/kotlin/mnemonic/MnemonicExt.kt b/mnemonic/src/main/java/cash/z/ecc/kotlin/mnemonic/MnemonicExt.kt new file mode 100644 index 0000000..d727781 --- /dev/null +++ b/mnemonic/src/main/java/cash/z/ecc/kotlin/mnemonic/MnemonicExt.kt @@ -0,0 +1,18 @@ +package cash.z.ecc.kotlin.mnemonic + +import java.util.* + + +/** + * Clears out the given char array in memory, for security purposes. + */ +fun CharArray.clear() { + Arrays.fill(this, '0') +} + +/** + * Clears out the given byte array in memory, for security purposes. + */ +fun ByteArray.clear() { + Arrays.fill(this, 0.toByte()) +} \ No newline at end of file diff --git a/mnemonic/src/main/java/cash/z/ecc/kotlin/mnemonic/Mnemonics.kt b/mnemonic/src/main/java/cash/z/ecc/kotlin/mnemonic/Mnemonics.kt new file mode 100644 index 0000000..3d27b32 --- /dev/null +++ b/mnemonic/src/main/java/cash/z/ecc/kotlin/mnemonic/Mnemonics.kt @@ -0,0 +1,25 @@ +package cash.z.ecc.kotlin.mnemonic + +import cash.z.android.plugin.MnemonicPlugin +import cash.z.ecc.android.bip39.Mnemonics +import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode +import cash.z.ecc.android.bip39.Mnemonics.WordCount +import cash.z.ecc.android.bip39.toEntropy +import cash.z.ecc.android.bip39.toSeed +import java.util.* +import java.util.Locale.ENGLISH + +class Mnemonics : MnemonicPlugin { + override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language) + override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy() + override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars + override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars + override fun nextMnemonicList(): List = MnemonicCode(WordCount.COUNT_24).words + override fun nextMnemonicList(entropy: ByteArray): List = MnemonicCode(entropy).words + override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed() + override fun toWordList(mnemonic: CharArray): List = MnemonicCode(mnemonic).words + + fun validate(mnemonic: CharArray) { + MnemonicCode(mnemonic).validate() + } +} diff --git a/mnemonic/src/test/java/cash/z/ecc/android/util/MnemonicTest.kt b/mnemonic/src/test/java/cash/z/ecc/android/util/MnemonicTest.kt new file mode 100644 index 0000000..184242a --- /dev/null +++ b/mnemonic/src/test/java/cash/z/ecc/android/util/MnemonicTest.kt @@ -0,0 +1,90 @@ +package cash.z.ecc.android.util + +import cash.z.android.plugin.MnemonicPlugin +import cash.z.ecc.kotlin.mnemonic.Mnemonics +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.lang.Math.max +import java.util.* + + +class MnemonicTest { + + lateinit var mnemonics: MnemonicPlugin + + @Before + fun start() { + mnemonics = Mnemonics() + } + + @Test + fun testSeed_fromMnemonic() { + val seed = mnemonics.run { + toSeed(nextMnemonic()) + } + assertEquals(64, seed.size) + } + + @Test + fun testMnemonic_create() { + val words = String(mnemonics.nextMnemonic()).split(' ') + assertEquals(24, words.size) + validate(words) + } + + @Test + fun testMnemonic_createList() { + val words = mnemonics.nextMnemonicList() + assertEquals(24, words.size) + validate(words.map { String(it) }) + } + + @Test + fun testMnemonic_toList() { + val words = mnemonics.run { + toWordList(nextMnemonic()) + } + assertEquals(24, words.size) + validate(words.map { String(it) }) + } + + @Test + fun testMnemonic_longestWord() { + var max = 0 + val englishWordList = mnemonics.fullWordList(Locale.ENGLISH.language) + repeat(2048) { + max = max(max, englishWordList[it].length) + } + assertEquals(8, max) + } + + private fun validate(words: List) { + val englishWordList = mnemonics.fullWordList(Locale.ENGLISH.language) + // return or crash! + words.forEach { word -> + var i = 0 + while (true) { + if (englishWordList[i++] == word) { + println(word) + break + } + + } + } + } +} + +private fun CharSequence.toWords(): List { + return mutableListOf().let { result -> + var index = 0 + repeat(length) { + if (this[it] == ' ') { + result.add(subSequence(index, it)) + index = it + 1 + } + } + result.add(subSequence(index, length)) + result + } +} diff --git a/placeholder.keystore b/placeholder.keystore new file mode 100644 index 0000000000000000000000000000000000000000..2d3e2f2c799c45405c8e961d82dfc0db0e50d44f GIT binary patch literal 2473 zcmY+Ec{mh`8pdbL7)!EDj58EM*=sB#O2~4Ah(t|B!q^)zwu>wcjrCZvXF8U#g=}NX z5M?*Al_4Rrjb+GWiyGIx&%Nh7=a27szvq45@B8zIz;hL`0YL~n*L?_=JmETF?<9}| zn1|=O3C44o9@|C;JbTDLBKC)1JiFVmjX9nNkQ4vA;^79e<>A4H2t0TXAqV09zkU2% z6fC$r))kCZkZ@W!oWJR~)->XZid|;|0Xi(ecyKSvlEW}M$FCnNS(J(v03G(9p{YoZ88{fG zzIAKYR(qt8rr+pwbpmJ^x~|>7V4<#)7%B4$XqcJz*#ep%oTZ6f96cw z`K{Oj@qr25%0oIV<2|>!f7RsF?bV6h2krq!)9~6zask@CGUzeAay9-lrF?MSa-vyt zY#V-I`VYd|=5v#{$W?yhfdOsA{FOs?s-{8SYMeA%NJOVSC`C(%yxwia%s32fWWo z7y8nhzU-9e-MhgrKK=5P#sYn8J?^m{%dBo@)1sn<`o7*JrJd4p03`=vO4g~h-&TB) z-z}uv-uy{N8P`Y6jTAtR3Lq}V*D706?25g?JK*)EU1;m>ow@eRr{+e4)3Xj($&@`y zr%~?Q+{0e2a7_x6dU;FlB`}*`;0^Z`lC#sLSCv1~uxqro(O;Fw{lpp(MHs-iEofgT zs>AoiM@@qF-SV8%wt62&n?GV$h6$c0hK3vJ)N&s+`geAFv<|Z^YAYXA?q!S5I^Ot~OJ4_d^e6(H*31XzLZyn%IPYR*gTA98^1DB+SnC8-(5eWJ z9JYn4eg(&?;+*72GUi^t&Un)J3!e|Vui8xl{mf{YzFLzrsx*nLSd^M4Y{Mac*to$_ zfmt-fy9qTQXS1@8vXm~W#C$Ng7HH0C;_p%9UA7U+o~ldY7Y!h(y&ddrdvTfB zL;Lc1Tlp43=`U1r2`Wp7i2)n{UH}XL2Y3WJS%z9os%c5{zjIq`(vG(p0~qD4DMn3qt*W z$64HKdj0cEXwBteG*-7{dd# z&o2beNsiCFp*!G|6e?BCZzWy|ZMy_7IRz%~+sT9)hEAcTT}zaq&;bAMM-Y5;4dCW{ zg^>Nv9gGyV-QDbt7kYLLV{4r3w|;|-_3RB>bzH`dRvc-luVQ4&-2k4d%zefnCSF3f zzjdUex*_GhMDBM+5t3p_r>eo>tz6~j&PIqZTTSgZ4|;5CW;zwiR#5}b_%eF(-bE}Y zy7iFuD}AyUjnS^FqQ%WRiToZ(G3%5X*d~{0b@<#+F|i&y0%=hn5qdh5kyo4R1CJGV zJ3{5&InSXNNzP-B=e`rpg5;nx1oUlqpPaofl;%{Di4^u2TS~B?ZK86i?|xdx)2*ln z{aMq|3^{qHMHyZT71-jbdu`B~a++i@Qs0*5^yOdHwt*Rqp(mAG#X1N-pMO2;%o9AN z==+@xq+XmI*2egM7x zK}{flGw^>)hjps}Wx_-8)7g{oG2x7C|{pYJg>d}LiQueAI-btgN6C2@hVIqzw zbSpBqb~JK)l9-IpMw~@(LD&^|L2M%I0I;B2z5BO!alGz(sVk)jF~I|}fQdd>@EEm$ Zu8P(tMV-_vbNw^Qw#lS=HV}}I_ist#ptb-2 literal 0 HcmV?d00001 diff --git a/qrecycler/.gitignore b/qrecycler/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/qrecycler/.gitignore @@ -0,0 +1 @@ +/build diff --git a/qrecycler/build.gradle b/qrecycler/build.gradle new file mode 100644 index 0000000..c6148ba --- /dev/null +++ b/qrecycler/build.gradle @@ -0,0 +1,39 @@ +import cash.z.ecc.android.Deps + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion Deps.compileSdkVersion + + defaultConfig { + minSdkVersion Deps.minSdkVersion + targetSdkVersion Deps.targetSdkVersion + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + namespace 'cash.z.android.qrecycler' + +} + +dependencies { + implementation Deps.AndroidX.CORE_KTX + implementation Deps.Kotlin.STDLIB + + // dependencies specific to this module, not shared with other modules + implementation Deps.Misc.Plugins.QR_SCANNER +} diff --git a/qrecycler/proguard-rules.pro b/qrecycler/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/qrecycler/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/qrecycler/src/main/AndroidManifest.xml b/qrecycler/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2f3ca54 --- /dev/null +++ b/qrecycler/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/qrecycler/src/main/java/cash/z/android/qrecycler/QRecycler.kt b/qrecycler/src/main/java/cash/z/android/qrecycler/QRecycler.kt new file mode 100644 index 0000000..938547e --- /dev/null +++ b/qrecycler/src/main/java/cash/z/android/qrecycler/QRecycler.kt @@ -0,0 +1,62 @@ +package cash.z.android.qrecycler + +import android.graphics.Bitmap +import android.graphics.Color +import android.widget.ImageView +import androidx.core.view.doOnLayout +import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType.ERROR_CORRECTION +import com.google.zxing.EncodeHintType.MARGIN +import com.google.zxing.qrcode.QRCodeWriter +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.* + + +class QRecycler { + fun load(content: String): Builder { + return Builder(content) + } + + // TODO: make this call async such that action can be taken once it is complete + fun encode(builder: Builder) { + builder.target.doOnLayout { measuredView -> + val w = measuredView.width + val h = measuredView.height + val hints = mapOf(ERROR_CORRECTION to builder.errorCorrection, MARGIN to builder.quietZone) + val bitMatrix = QRCodeWriter().encode(builder.content, BarcodeFormat.QR_CODE, w, h, hints) + val pixels = IntArray(w * h) + for (y in 0 until h) { + val offset = y * w + for (x in 0 until w) { + pixels[offset + x] = if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE + } + } + // TODO: RECYCLE THIS BITMAP MEMORY!!! Do it in a way that is lifecycle-aware and disposes of the memory when the fragment is off-screen + val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) + bitmap.setPixels(pixels, 0, w, 0, 0, w, h) + (measuredView as ImageView).setImageBitmap(bitmap) + } + } + + inner class Builder(val content: String) { + lateinit var target: ImageView + var errorCorrection: ErrorCorrectionLevel = Q + var quietZone: Int = 4 + fun into(imageView: ImageView) { + target = imageView + encode(this) + } + fun withQuietZoneSize(customQuietZone: Int): Builder { + quietZone = customQuietZone + return this + } + fun withCorrectionLevel(level: CorrectionLevel): Builder { + errorCorrection = level.errorCorrectionLevel + return this + } + } + + enum class CorrectionLevel(val errorCorrectionLevel: ErrorCorrectionLevel) { + LOW(L), DEFAULT(M), MEDIUM(Q), HIGH(H); + } +} diff --git a/qrecycler/src/main/java/cash/z/android/qrecycler/QScanner.kt b/qrecycler/src/main/java/cash/z/android/qrecycler/QScanner.kt new file mode 100644 index 0000000..738c330 --- /dev/null +++ b/qrecycler/src/main/java/cash/z/android/qrecycler/QScanner.kt @@ -0,0 +1,8 @@ +package cash.z.android.qrecycler + +/** + * An interface to allow for plugging in any scanner + */ +interface QScanner { + fun scanBarcode(callback: (Result) -> Unit) +} \ No newline at end of file diff --git a/qrecycler/src/main/res/layout/texture_view.xml b/qrecycler/src/main/res/layout/texture_view.xml new file mode 100644 index 0000000..91953b7 --- /dev/null +++ b/qrecycler/src/main/res/layout/texture_view.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/qrecycler/src/main/res/values/attrs.xml b/qrecycler/src/main/res/values/attrs.xml new file mode 100644 index 0000000..7ac245d --- /dev/null +++ b/qrecycler/src/main/res/values/attrs.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qrecycler/src/main/res/values/public.xml b/qrecycler/src/main/res/values/public.xml new file mode 100644 index 0000000..84966fa --- /dev/null +++ b/qrecycler/src/main/res/values/public.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/qrecycler/src/main/res/values/strings.xml b/qrecycler/src/main/res/values/strings.xml new file mode 100644 index 0000000..8542005 --- /dev/null +++ b/qrecycler/src/main/res/values/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/qrecycler/src/main/res/values/styles.xml b/qrecycler/src/main/res/values/styles.xml new file mode 100644 index 0000000..6ded716 --- /dev/null +++ b/qrecycler/src/main/res/values/styles.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/responsible_disclosure.md b/responsible_disclosure.md new file mode 100644 index 0000000..12ae947 --- /dev/null +++ b/responsible_disclosure.md @@ -0,0 +1 @@ +Please see https://github.com/zcash/zcash-android-wallet-sdk/blob/master/responsible_disclosure.md diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..051c6ff --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,53 @@ +pluginManagement { + repositories { + gradlePluginPortal() + } + + plugins { + id("com.github.ben-manes.versions") version ("0.39.0") apply (false) + } +} + +dependencyResolutionManagement { + @Suppress("UnstableApiUsage") + repositories { + val isRepoRestrictionEnabled = false + + mavenLocal() + google() + mavenCentral() + maven("https://jitpack.io") + jcenter() + + /* + // Uncomment to use a snapshot version of the SDK, e.g. when the SDK version ends in -SNAPSHOT + maven("https://oss.sonatype.org/content/repositories/snapshots") { + if (isRepoRestrictionEnabled) { + content { + includeGroup("cash.z.ecc.android") + } + } + }*/ + } +} + +rootProject.name = "ecc-wallet" + +includeBuild("build-convention") + +include(":app") +include(":qrecycler") +include(":feedback") +include(":mnemonic") +include(":lockbox") + +if (extra["IS_SDK_INCLUDED_BUILD"].toString().toBoolean()) { + // Currently assume the SDK is up one level with a hardcoded directory name + // If this becomes problematic, `IS_SDK_INCLUDED_BUILD` could be turned into a path + // instead. + includeBuild("../zcash-android-sdk") { + dependencySubstitution { + substitute(module("cash.z.ecc.android:zcash-android-sdk")).using(project(":sdk-lib")) + } + } +}