From 695bffab9a8e6dd748242a2d0ac74792b2437e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=AD?= Date: Mon, 3 May 2021 11:22:16 -0400 Subject: [PATCH] #247 Add ktlint (#248) * Add ktlint Upgrade Gradle Fix formatting for ktlint * Upgrade to Gradle 7.0 Bump up NDK, targetSDKVersion & kotlinVersion Fix expected type issue in ScanFragment getPackageInfo() * Revert SDK version. Delete unused file SendAddressFragment.kt Fix comment formatting. Co-authored-by: Kevin Gorham --- .editorconfig | 14 +++ app/build.gradle | 6 +- .../java/cash/z/ecc/android/MemoTest.kt | 10 +-- .../android/integration/ConversionsTest.kt | 10 +-- .../android/integration/IntegrationTest.kt | 12 ++- .../z/ecc/android/integration/LockBoxTest.kt | 6 +- .../java/cash/z/ecc/android/ZcashWalletApp.kt | 14 +-- .../ecc/android/di/annotation/ViewModelKey.kt | 2 +- .../ecc/android/di/component/AppComponent.kt | 2 +- .../di/component/InitializerSubcomponent.kt | 2 +- .../di/component/MainActivitySubcomponent.kt | 2 +- .../di/component/SynchronizerSubcomponent.kt | 2 +- .../cash/z/ecc/android/di/module/AppModule.kt | 11 +-- .../android/di/module/MainActivityModule.kt | 4 +- .../android/di/module/SynchronizerModule.kt | 1 - .../di/module/ViewModelsActivityModule.kt | 5 +- .../di/module/ViewModelsSynchronizerModule.kt | 5 +- .../ecc/android/di/viewmodel/ViewModelExt.kt | 3 +- .../android/di/viewmodel/ViewModelFactory.kt | 4 +- .../z/ecc/android/ext/CurrencyFormatter.kt | 8 +- .../java/cash/z/ecc/android/ext/Dialogs.kt | 14 +-- .../java/cash/z/ecc/android/ext/EditText.kt | 4 +- .../java/cash/z/ecc/android/ext/Extensions.kt | 4 +- .../main/java/cash/z/ecc/android/ext/Int.kt | 7 +- .../cash/z/ecc/android/ext/LifeCycleOwner.kt | 4 +- .../java/cash/z/ecc/android/ext/Spannable.kt | 2 +- .../main/java/cash/z/ecc/android/ext/View.kt | 16 ++-- .../z/ecc/android/feedback/FeedbackBugsnag.kt | 4 +- .../z/ecc/android/feedback/FeedbackConsole.kt | 2 +- .../z/ecc/android/feedback/FeedbackFile.kt | 2 +- .../ecc/android/feedback/FeedbackMixpanel.kt | 3 +- .../cash/z/ecc/android/feedback/Report.kt | 13 +-- .../cash/z/ecc/android/ui/MainActivity.kt | 87 +++++++++---------- .../cash/z/ecc/android/ui/MainViewModel.kt | 2 +- .../z/ecc/android/ui/base/BaseFragment.kt | 9 +- .../ecc/android/ui/history/HistoryFragment.kt | 22 +++-- .../android/ui/history/HistoryViewModel.kt | 16 ++-- .../android/ui/history/TransactionAdapter.kt | 6 +- .../android/ui/history/TransactionFragment.kt | 35 +++----- .../ui/history/TransactionViewHolder.kt | 15 ++-- .../ui/history/TransactionsDrawableFooter.kt | 18 ++-- .../android/ui/history/TransactionsFooter.kt | 1 - .../z/ecc/android/ui/home/HomeFragment.kt | 22 ++--- .../z/ecc/android/ui/home/HomeViewModel.kt | 25 ++++-- .../z/ecc/android/ui/home/MagicSnakeLoader.kt | 25 +++--- .../ecc/android/ui/profile/AwesomeFragment.kt | 15 +--- .../android/ui/profile/FeedbackFragment.kt | 8 +- .../ecc/android/ui/profile/ProfileFragment.kt | 16 ++-- .../android/ui/profile/ProfileViewModel.kt | 4 +- .../ecc/android/ui/receive/ReceiveFragment.kt | 10 +-- .../cash/z/ecc/android/ui/scan/QrAnalyzer.kt | 2 - .../z/ecc/android/ui/scan/ScanFragment.kt | 43 +++++---- .../z/ecc/android/ui/scan/ScanViewModel.kt | 3 +- .../android/ui/send/SendConfirmFragment.kt | 9 +- .../ecc/android/ui/send/SendFinalFragment.kt | 19 ++-- .../z/ecc/android/ui/send/SendFragment.kt | 41 +++++---- .../z/ecc/android/ui/send/SendMemoFragment.kt | 12 ++- .../z/ecc/android/ui/send/SendViewModel.kt | 48 +++++----- .../android/ui/settings/SettingsFragment.kt | 17 ++-- .../android/ui/settings/SettingsViewModel.kt | 2 - .../z/ecc/android/ui/setup/BackupFragment.kt | 10 +-- .../z/ecc/android/ui/setup/LandingFragment.kt | 17 ++-- .../z/ecc/android/ui/setup/RestoreFragment.kt | 38 ++++---- .../z/ecc/android/ui/setup/SeedWordAdapter.kt | 31 +++---- .../android/ui/setup/WalletSetupViewModel.kt | 6 +- .../android/ui/util/AddressPartNumberSpan.kt | 12 +-- .../z/ecc/android/ui/util/DebugFileTwig.kt | 2 +- .../cash/z/ecc/android/ui/util/MemoUtil.kt | 14 +-- .../ecc/android/ui/util/PermissionFragment.kt | 20 ++--- .../java/cash/z/ecc/android/ScratchPad.kt | 20 ++--- .../cash/z/ecc/android/SendViewModelTest.kt | 12 +-- build.gradle | 8 +- .../java/cash/z/ecc/android/Dependencies.kt | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- ktlint.gradle | 21 +++++ 75 files changed, 485 insertions(+), 466 deletions(-) create mode 100644 .editorconfig create mode 100644 ktlint.gradle diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e562c00 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# Comma-separated list of rules to disable (Since 0.34.0) +# Note that rules in any ruleset other than the standard ruleset will need to be prefixed +# by the ruleset identifier. +disabled_rules=import-ordering,no-wildcard-imports + +# Defines the imports layout. The layout can be composed by the following symbols: +# "*" - wildcard. There must be at least one entry of a single wildcard to match all other imports. Matches anything after a specified symbol/import as well. +# "|" - blank line. Supports only single blank lines between imports. No blank line is allowed in the beginning or end of the layout. +# "^" - alias import, e.g. "^android.*" will match all android alias imports, "^" will match all other alias imports. +# import paths - these can be full paths, e.g. "java.util.List.*" as well as wildcard paths, e.g. "kotlin.**" +# Examples (we use ij_kotlin_imports_layout to set an imports layout for both ktlint and IDEA via a single property): +ij_kotlin_imports_layout=* # alphabetical with capital letters before lower case letters (e.g. Z before a), no blank lines +ij_kotlin_imports_layout=*,java.**,javax.**,kotlin.**,^ # default IntelliJ IDEA style, same as alphabetical, but with "java", "javax", "kotlin" and alias imports in the end of the imports list +ij_kotlin_imports_layout=android.**,|,^org.junit.**,kotlin.io.Closeable.*,|,*,^ # custom imports layout diff --git a/app/build.gradle b/app/build.gradle index a646e11..8e1b5fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -184,4 +184,8 @@ dependencies { } -defaultTasks 'clean', 'assembleZcashmainnetRelease' \ No newline at end of file +defaultTasks 'clean', 'assembleZcashmainnetRelease' + +apply from: "$rootDir/ktlint.gradle" +preBuild.dependsOn('ktlintFormat') +preBuild.dependsOn('ktlint') diff --git a/app/src/androidTest/java/cash/z/ecc/android/MemoTest.kt b/app/src/androidTest/java/cash/z/ecc/android/MemoTest.kt index 47a9e7e..89007a0 100644 --- a/app/src/androidTest/java/cash/z/ecc/android/MemoTest.kt +++ b/app/src/androidTest/java/cash/z/ecc/android/MemoTest.kt @@ -1,16 +1,16 @@ package cash.z.ecc.android import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized import cash.z.ecc.android.ui.util.MemoUtil import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized @RunWith(AndroidJUnit4::class) -//@RunWith(Parameterized::class) +// @RunWith(Parameterized::class) class MemoTest(val input: String, val output: String) { @Test @@ -42,4 +42,4 @@ class MemoTest(val input: String, val output: String) { ) ) } -} \ No newline at end of file +} 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 index ed11e19..a1f6949 100644 --- a/app/src/androidTest/java/cash/z/ecc/android/integration/ConversionsTest.kt +++ b/app/src/androidTest/java/cash/z/ecc/android/integration/ConversionsTest.kt @@ -14,7 +14,6 @@ class ConversionsTest { @Before fun setUp() { - } @Test @@ -73,7 +72,6 @@ class ConversionsTest { Assert.assertEquals(1000, result.longValueExact()) } - @Test fun testToBigDecimal_thousandCommaWithDecimal() { val input = "1,000.00" @@ -109,12 +107,6 @@ class ConversionsTest { Assert.assertEquals(1, result.longValueExact()) } - - - - - - @Test fun testToZecString_full() { val input = 112_341_123L @@ -149,4 +141,4 @@ class ConversionsTest { val result = WalletZecFormmatter.toZecStringFull(input) Assert.assertEquals("1.12350004", result) } -} \ No newline at end of file +} 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 index 9ba5e39..2f024a9 100644 --- a/app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt +++ b/app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt @@ -4,7 +4,6 @@ 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.kotlin.mnemonic.Mnemonics import cash.z.ecc.android.sdk.Initializer import cash.z.ecc.android.sdk.type.ZcashNetwork import okio.Buffer @@ -24,7 +23,7 @@ class IntegrationTest { 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" + " under absorb spirit hurdle animal original honey owner upper empower describe" @Before fun start() { @@ -37,7 +36,7 @@ class IntegrationTest { assertEquals( "Generated incorrect BIP-39 seed!", "f4e3d38d9c244da7d0407e19a93c80429614ee82dcf62c141235751c9f1228905d12a1f275f" + - "5c22f6fb7fcd9e0a97f1676e0eec53fdeeeafe8ce8aa39639b9fe", + "5c22f6fb7fcd9e0a97f1676e0eec53fdeeeafe8ce8aa39639b9fe", seed.toHex() ) } @@ -72,10 +71,10 @@ class IntegrationTest { acceptedSize-- } - val maxSeedPhraseLength = 8 * 24 + 23 //215 (max length of each word is 8) + 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", + " Expected: $maxSeedPhraseLength but was: $acceptedSize", acceptedSize > maxSeedPhraseLength ) } @@ -94,7 +93,6 @@ class IntegrationTest { initializer.erase() } - private fun ByteArray.toHex(): String { val sb = StringBuilder(size * 2) for (b in this) @@ -117,4 +115,4 @@ class IntegrationTest { .map { allowedChars.random() } .joinToString("") } -} \ No newline at end of file +} 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 index 641b403..2bd557b 100644 --- a/app/src/androidTest/java/cash/z/ecc/android/integration/LockBoxTest.kt +++ b/app/src/androidTest/java/cash/z/ecc/android/integration/LockBoxTest.kt @@ -29,7 +29,7 @@ class LockBoxTest { lockBox["longStr"] = sampleHex val actual: String = lockBox["longStr"]!! - if(sampleHex == actual) successCount++ + if (sampleHex == actual) successCount++ lockBox.clear() } assertEquals(iterations, successCount) @@ -43,7 +43,7 @@ class LockBoxTest { lockBox["shortStr"] = sampleHex val actual: String = lockBox["shortStr"]!! - if(sampleHex == actual) successCount++ + if (sampleHex == actual) successCount++ lockBox.clear() } assertEquals(iterations, successCount) @@ -57,7 +57,7 @@ class LockBoxTest { lockBox["giantStr"] = sampleHex val actual: String = lockBox["giantStr"]!! - if(sampleHex == actual) successCount++ + if (sampleHex == actual) successCount++ lockBox.clear() } assertEquals(iterations, successCount) diff --git a/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt index 5660fa5..d7849c9 100644 --- a/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt +++ b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt @@ -11,10 +11,13 @@ import cash.z.ecc.android.ext.tryWithWarning import cash.z.ecc.android.feedback.FeedbackCoordinator import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.type.ZcashNetwork -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import javax.inject.Inject - class ZcashWalletApp : Application(), CameraXConfig.Provider { @Inject @@ -102,10 +105,9 @@ class ZcashWalletApp : Application(), CameraXConfig.Provider { } } - fun ZcashWalletApp.isEmulator(): Boolean { - val goldfish = Build.HARDWARE.contains("goldfish"); - val emu = (System.getProperty("ro.kernel.qemu", "")?.length ?: 0) > 0; + val goldfish = Build.HARDWARE.contains("goldfish") + val emu = (System.getProperty("ro.kernel.qemu", "")?.length ?: 0) > 0 val sdk = Build.MODEL.toLowerCase().contains("sdk") - return goldfish || emu || sdk; + return goldfish || emu || sdk } diff --git a/app/src/main/java/cash/z/ecc/android/di/annotation/ViewModelKey.kt b/app/src/main/java/cash/z/ecc/android/di/annotation/ViewModelKey.kt index e6f6b43..ad66772 100644 --- a/app/src/main/java/cash/z/ecc/android/di/annotation/ViewModelKey.kt +++ b/app/src/main/java/cash/z/ecc/android/di/annotation/ViewModelKey.kt @@ -11,4 +11,4 @@ import kotlin.reflect.KClass ) @Retention(AnnotationRetention.RUNTIME) @MapKey -annotation class ViewModelKey(val value: KClass) \ No newline at end of file +annotation class ViewModelKey(val value: KClass) diff --git a/app/src/main/java/cash/z/ecc/android/di/component/AppComponent.kt b/app/src/main/java/cash/z/ecc/android/di/component/AppComponent.kt index 00ec50f..c79b66b 100644 --- a/app/src/main/java/cash/z/ecc/android/di/component/AppComponent.kt +++ b/app/src/main/java/cash/z/ecc/android/di/component/AppComponent.kt @@ -20,4 +20,4 @@ interface AppComponent { interface Factory { fun create(@BindsInstance application: ZcashWalletApp): AppComponent } -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/di/component/InitializerSubcomponent.kt b/app/src/main/java/cash/z/ecc/android/di/component/InitializerSubcomponent.kt index 0c1c1c7..9fb3e05 100644 --- a/app/src/main/java/cash/z/ecc/android/di/component/InitializerSubcomponent.kt +++ b/app/src/main/java/cash/z/ecc/android/di/component/InitializerSubcomponent.kt @@ -17,4 +17,4 @@ interface InitializerSubcomponent { interface Factory { fun create(@BindsInstance config: Initializer.Config): InitializerSubcomponent } -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/di/component/MainActivitySubcomponent.kt b/app/src/main/java/cash/z/ecc/android/di/component/MainActivitySubcomponent.kt index dc528b8..a62dbe1 100644 --- a/app/src/main/java/cash/z/ecc/android/di/component/MainActivitySubcomponent.kt +++ b/app/src/main/java/cash/z/ecc/android/di/component/MainActivitySubcomponent.kt @@ -22,4 +22,4 @@ interface MainActivitySubcomponent { interface Factory { fun create(@BindsInstance activity: FragmentActivity): MainActivitySubcomponent } -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/di/component/SynchronizerSubcomponent.kt b/app/src/main/java/cash/z/ecc/android/di/component/SynchronizerSubcomponent.kt index 1b983cb..ec8f5ea 100644 --- a/app/src/main/java/cash/z/ecc/android/di/component/SynchronizerSubcomponent.kt +++ b/app/src/main/java/cash/z/ecc/android/di/component/SynchronizerSubcomponent.kt @@ -22,4 +22,4 @@ interface SynchronizerSubcomponent { interface Factory { fun create(@BindsInstance initializer: Initializer): SynchronizerSubcomponent } -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt index 2d70e81..98a4d8e 100644 --- a/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt +++ b/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt @@ -5,10 +5,14 @@ import android.content.Context import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.di.component.MainActivitySubcomponent import cash.z.ecc.android.ext.Const -import cash.z.ecc.android.feedback.* +import cash.z.ecc.android.feedback.Feedback +import cash.z.ecc.android.feedback.FeedbackBugsnag +import cash.z.ecc.android.feedback.FeedbackConsole +import cash.z.ecc.android.feedback.FeedbackCoordinator +import cash.z.ecc.android.feedback.FeedbackFile +import cash.z.ecc.android.feedback.FeedbackMixpanel import cash.z.ecc.android.lockbox.LockBox import cash.z.ecc.android.sdk.ext.SilentTwig -import cash.z.ecc.android.sdk.ext.TroubleshootingTwig import cash.z.ecc.android.sdk.ext.Twig import cash.z.ecc.android.ui.util.DebugFileTwig import dagger.Module @@ -35,12 +39,10 @@ class AppModule { return LockBox(appContext) } - // // Feedback // - @Provides @Singleton fun provideFeedback(): Feedback = Feedback() @@ -59,7 +61,6 @@ class AppModule { } } - // // Default Feedback Observer Set // diff --git a/app/src/main/java/cash/z/ecc/android/di/module/MainActivityModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/MainActivityModule.kt index 6b7104a..4294547 100644 --- a/app/src/main/java/cash/z/ecc/android/di/module/MainActivityModule.kt +++ b/app/src/main/java/cash/z/ecc/android/di/module/MainActivityModule.kt @@ -8,6 +8,4 @@ import dagger.Module includes = [ViewModelsActivityModule::class], subcomponents = [SynchronizerSubcomponent::class, InitializerSubcomponent::class] ) -class MainActivityModule { - -} +class MainActivityModule diff --git a/app/src/main/java/cash/z/ecc/android/di/module/SynchronizerModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/SynchronizerModule.kt index 93248d9..e7e39af 100644 --- a/app/src/main/java/cash/z/ecc/android/di/module/SynchronizerModule.kt +++ b/app/src/main/java/cash/z/ecc/android/di/module/SynchronizerModule.kt @@ -19,5 +19,4 @@ class SynchronizerModule { fun provideSynchronizer(appContext: Context, initializer: Initializer): Synchronizer { return Synchronizer(initializer) } - } diff --git a/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsActivityModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsActivityModule.kt index 2fd8c46..f0e462f 100644 --- a/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsActivityModule.kt +++ b/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsActivityModule.kt @@ -1,6 +1,5 @@ package cash.z.ecc.android.di.module - import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import cash.z.ecc.android.di.annotation.ActivityScope @@ -26,7 +25,6 @@ abstract class ViewModelsActivityModule { @ViewModelKey(WalletSetupViewModel::class) abstract fun bindWalletSetupViewModel(implementation: WalletSetupViewModel): ViewModel - /** * Factory for view models that are created until before the Synchronizer exists. This is a * little tricky because we cannot make them all in one place or else they won't be available @@ -38,5 +36,4 @@ abstract class ViewModelsActivityModule { @Named(Const.Name.BEFORE_SYNCHRONIZER) @Binds abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory - -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsSynchronizerModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsSynchronizerModule.kt index bebc16c..cc32a86 100644 --- a/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsSynchronizerModule.kt +++ b/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsSynchronizerModule.kt @@ -1,6 +1,5 @@ package cash.z.ecc.android.di.module - import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import cash.z.ecc.android.di.annotation.SynchronizerScope @@ -65,7 +64,7 @@ abstract class ViewModelsSynchronizerModule { @IntoMap @ViewModelKey(SettingsViewModel::class) abstract fun bindSettingsViewModel(implementation: SettingsViewModel): ViewModel - + /** * Factory for view models that are not created until the Synchronizer exists. Only VMs that * require the Synchronizer should wait until it is created. In other words, these are the VMs @@ -75,4 +74,4 @@ abstract class ViewModelsSynchronizerModule { @Named(Const.Name.SYNCHRONIZER) @Binds abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelExt.kt b/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelExt.kt index 17f5e14..960f523 100644 --- a/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelExt.kt +++ b/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelExt.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModelProvider import cash.z.ecc.android.ui.MainActivity import cash.z.ecc.android.ui.base.BaseFragment - inline fun BaseFragment<*>.viewModel() = object : Lazy { val cached: VM? = null override fun isInitialized(): Boolean = cached != null @@ -58,4 +57,4 @@ inline fun MainActivity.activityViewModel() = object : } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelFactory.kt b/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelFactory.kt index 2921bb5..ffd0fea 100644 --- a/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelFactory.kt +++ b/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelFactory.kt @@ -13,9 +13,9 @@ class ViewModelFactory @Inject constructor( modelClass.isAssignableFrom(it.key) }?.value ?: throw IllegalArgumentException( "No map entry found for ${modelClass.canonicalName}. Verify that this ViewModel has" + - " been added to the ViewModelModule. ${creators.keys}" + " been added to the ViewModelModule. ${creators.keys}" ) @Suppress("UNCHECKED_CAST") return creator.get() as T } -} \ No newline at end of file +} 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 index c1471f4..692fa0b 100644 --- a/app/src/main/java/cash/z/ecc/android/ext/CurrencyFormatter.kt +++ b/app/src/main/java/cash/z/ecc/android/ext/CurrencyFormatter.kt @@ -5,15 +5,12 @@ 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.ext.convertZatoshiToZec -import cash.z.ecc.android.sdk.ext.toZec import java.math.BigDecimal import java.math.MathContext import java.math.RoundingMode import java.text.DecimalFormat import java.text.NumberFormat -import java.util.* - +import java.util.Locale /** * Do the necessary conversions in one place @@ -75,5 +72,4 @@ object WalletZecFormmatter { BigDecimal(this ?: 0L, MathContext.DECIMAL128) .divide(ConversionsUniform.ONE_ZEC_IN_ZATOSHI) .setScale(LONG_SCALE, ConversionsUniform.roundingMode) - -} \ No newline at end of file +} 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 index e0bb9f7..cb8e755 100644 --- a/app/src/main/java/cash/z/ecc/android/ext/Dialogs.kt +++ b/app/src/main/java/cash/z/ecc/android/ext/Dialogs.kt @@ -9,7 +9,6 @@ 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) @@ -39,10 +38,13 @@ fun Context.showUninitializedError(error: Throwable? = null, onDismiss: () -> Un 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) - }) + showClearDataConfirmation( + onDismiss, + onCancel = { + // do not let the user back into the app because we cannot recover from this case + showUninitializedError(error, onDismiss) + } + ) } .show() } @@ -187,4 +189,4 @@ fun Context.showSharedLibraryCriticalError(e: Throwable): Dialog = showCriticalM titleResId = R.string.dialog_error_critical_link_title, messageResId = R.string.dialog_error_critical_link_message, onDismiss = { throw e } -) \ No newline at end of file +) 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 index 629ac51..35d26db 100644 --- a/app/src/main/java/cash/z/ecc/android/ext/EditText.kt +++ b/app/src/main/java/cash/z/ecc/android/ext/EditText.kt @@ -55,7 +55,7 @@ inline fun EditText.limitDecimalPlaces(max: Int) { // Restore the cursor position if (oldText != textStr) { - val cursorPosition = editText.selectionEnd; + val cursorPosition = editText.selectionEnd editText.setText(textStr) editText.setSelection( (cursorPosition - (oldText.length - textStr.length)).coerceIn( @@ -68,12 +68,10 @@ inline fun EditText.limitDecimalPlaces(max: Int) { } override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { - } }) } - fun TextView.convertZecToZatoshi(): Long? { return try { text.toString().safelyConvertToBigDecimal()?.convertZecToZatoshi() ?: 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 index 963b351..77fea51 100644 --- a/app/src/main/java/cash/z/ecc/android/ext/Extensions.kt +++ b/app/src/main/java/cash/z/ecc/android/ext/Extensions.kt @@ -7,7 +7,7 @@ import cash.z.ecc.android.sdk.ext.Bush import cash.z.ecc.android.sdk.ext.Twig import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.type.WalletBalance -import java.util.* +import java.util.Locale import kotlin.math.roundToInt /** @@ -67,4 +67,4 @@ inline fun Context.locale(): Locale { inline fun Twig.find(): T? { return if (Bush.trunk::class.java.isAssignableFrom(T::class.java)) Bush.trunk as T else null -} \ No newline at end of file +} 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 index 69454fa..da8d5c6 100644 --- a/app/src/main/java/cash/z/ecc/android/ext/Int.kt +++ b/app/src/main/java/cash/z/ecc/android/ext/Int.kt @@ -32,16 +32,15 @@ internal inline fun @receiver:StringRes Int.toAppStringFormatted(vararg formatAr 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)} - + 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() \ No newline at end of file +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 index 9bc8d60..5f4284c 100644 --- a/app/src/main/java/cash/z/ecc/android/ext/LifeCycleOwner.kt +++ b/app/src/main/java/cash/z/ecc/android/ext/LifeCycleOwner.kt @@ -7,8 +7,8 @@ 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) { +fun LifecycleOwner.onClick(view: T, throttle: Long = 250L, block: (T) -> Unit) { view.clicks().debounce(throttle).onEach { block(view) }.launchIn(this.lifecycleScope) -} \ No newline at end of file +} 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 index ed68dba..6f8665b 100644 --- a/app/src/main/java/cash/z/ecc/android/ext/Spannable.kt +++ b/app/src/main/java/cash/z/ecc/android/ext/Spannable.kt @@ -9,4 +9,4 @@ fun CharSequence.toColoredSpan(colorResId: Int, coloredPortion: String): CharSeq val start = this@toColoredSpan.indexOf(coloredPortion) setSpan(ForegroundColorSpan(colorResId.toAppColor()), start, start + coloredPortion.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } -} \ No newline at end of file +} 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 index 3947181..f7598b1 100644 --- a/app/src/main/java/cash/z/ecc/android/ext/View.kt +++ b/app/src/main/java/cash/z/ecc/android/ext/View.kt @@ -1,7 +1,9 @@ package cash.z.ecc.android.ext import android.view.View -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 @@ -40,8 +42,10 @@ 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}") + ?: throw IllegalStateException( + "Cannot navigate from this activity. " + + "Expected MainActivity but found ${context.javaClass.simpleName}" + ) } } @@ -51,7 +55,7 @@ fun View.onClickNavUp(block: (() -> Any) = {}) { (context as? MainActivity)?.navController?.navigateUp() ?: throw IllegalStateException( "Cannot navigate from this activity. " + - "Expected MainActivity but found ${context.javaClass.simpleName}" + "Expected MainActivity but found ${context.javaClass.simpleName}" ) } } @@ -62,7 +66,7 @@ fun View.onClickNavBack(block: (() -> Any) = {}) { (context as? MainActivity)?.navController?.popBackStack() ?: throw IllegalStateException( "Cannot navigate from this activity. " + - "Expected MainActivity but found ${context.javaClass.simpleName}" + "Expected MainActivity but found ${context.javaClass.simpleName}" ) } } @@ -74,4 +78,4 @@ fun View.clicks() = channelFlow { awaitClose { setOnClickListener(null) } -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackBugsnag.kt b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackBugsnag.kt index 0c6dd46..446eeb2 100644 --- a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackBugsnag.kt +++ b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackBugsnag.kt @@ -58,7 +58,7 @@ class FeedbackBugsnag : FeedbackCoordinator.FeedbackObserver { private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) : Throwable(reorgMesssage) - private class SendException(errorCode: Int?, errorMessage: String?): RuntimeException( + private class SendException(errorCode: Int?, errorMessage: String?) : RuntimeException( "Non-fatal error while sending transaction. code: $errorCode message: $errorMessage" ) -} \ No newline at end of file +} 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 index 50a9fa0..2acbdbd 100644 --- a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackConsole.kt +++ b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackConsole.kt @@ -19,4 +19,4 @@ class FeedbackConsole : FeedbackCoordinator.FeedbackObserver { private fun log(message: String) { Log.d("@TWIG", message) } -} \ No newline at end of file +} 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 index 20d06e8..4ff58d4 100644 --- a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackFile.kt +++ b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackFile.kt @@ -34,4 +34,4 @@ class FeedbackFile(fileName: String = "user_log.txt") : it.writeUtf8("${format.format(System.currentTimeMillis())}|\t$message\n") } } -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackMixpanel.kt b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackMixpanel.kt index df38c36..8134136 100644 --- a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackMixpanel.kt +++ b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackMixpanel.kt @@ -29,5 +29,4 @@ class FeedbackMixpanel : FeedbackCoordinator.FeedbackObserver { private fun track(eventName: String, properties: Map) { mixpanel.trackMap(eventName, properties) } - -} \ No newline at end of file +} 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 index bf6ffd5..f497f97 100644 --- a/app/src/main/java/cash/z/ecc/android/feedback/Report.kt +++ b/app/src/main/java/cash/z/ecc/android/feedback/Report.kt @@ -22,11 +22,13 @@ object Report { // 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, + 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, + class ErrorSubmitting(errorCode: Int? = null, errorMessage: String? = null) : Error( + "submit", 81, errorCode, errorMessage, "errorCode" to (errorCode ?: -1), "errorMessage" to (errorMessage ?: "None") ) @@ -82,7 +84,7 @@ object Report { *properties ) { override val key = "performance.$name" - override fun toString() = "$key: ${toMap().let { if(it.size > 1) "${it.entries}" else "" }}" + 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) } @@ -94,7 +96,7 @@ object Report { *properties ) { override val key = "issue.$name" - override fun toString() = "occurrence of ${key.replace('.', ' ')}${toMap().let { if(it.size > 1) " with ${it.entries}" else "" }}" + 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") @@ -241,6 +243,5 @@ class LaunchMetric private constructor(private val metric: Feedback.TimeMetric) override fun toString(): String = metric.toString() } - inline fun Feedback.measure(type: Report.MetricType, block: () -> T): T = - this.measure(type.key, type.description, block) \ No newline at end of file + this.measure(type.key, type.description, block) 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 index 63ed2d1..7bc0146 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt @@ -85,7 +85,6 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import javax.inject.Inject - class MainActivity : AppCompatActivity() { @Inject @@ -224,9 +223,9 @@ class MainActivity : AppCompatActivity() { } catch (t: Throwable) { twig( "WARNING: during callback, did not navigate to destination: R.id.${ - resources.getResourceEntryName( - destination - ) + resources.getResourceEntryName( + destination + ) } due to: $t" ) } @@ -237,9 +236,9 @@ class MainActivity : AppCompatActivity() { } catch (t: Throwable) { twig( "WARNING: did not immediately navigate to destination: R.id.${ - resources.getResourceEntryName( - destination - ) + resources.getResourceEntryName( + destination + ) } due to: $t" ) } @@ -310,7 +309,7 @@ class MainActivity : AppCompatActivity() { } fun authenticate(description: String, title: String = getString(R.string.biometric_prompt_title), block: () -> Unit) { - val callback = object : BiometricPrompt.AuthenticationCallback() { + 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() @@ -348,7 +347,7 @@ class MainActivity : AppCompatActivity() { 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 -> { + else -> { twig("Warning: unrecognized authentication error $errorCode") doNothing("Authentication failed with error code $errorCode") } @@ -417,15 +416,18 @@ class MainActivity : AppCompatActivity() { } fun preventBackPress(fragment: Fragment) { - onFragmentBackPressed(fragment){} + onFragmentBackPressed(fragment) {} } fun onFragmentBackPressed(fragment: Fragment, block: () -> Unit) { - onBackPressedDispatcher.addCallback(fragment, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - block() + onBackPressedDispatcher.addCallback( + fragment, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + block() + } } - }) + ) } private fun showMessage(message: String, linger: Boolean = false) { @@ -440,26 +442,26 @@ class MainActivity : AppCompatActivity() { .make(view, "$message", Snackbar.LENGTH_INDEFINITE) .setAction(action) { /*auto-close*/ } - 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 + 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) + snackBarView.getChildAt(0).setLayoutParams(params) snacks } else { - snackbar!!.setText(message).setAction(action) {/*auto-close*/} + snackbar!!.setText(message).setAction(action) { /*auto-close*/ } }.also { if (!it.isShownOrQueued) it.show() } @@ -534,7 +536,8 @@ class MainActivity : AppCompatActivity() { if (dialog == null && !ignoreScanFailure) throttle("scanFailure", 20_000L) { notified = true runOnUiThread { - dialog = showScanFailure(error, + dialog = showScanFailure( + error, onCancel = { dialog = null }, onDismiss = { dialog = null } ) @@ -564,7 +567,6 @@ class MainActivity : AppCompatActivity() { 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>() @@ -579,21 +581,19 @@ class MainActivity : AppCompatActivity() { // 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) + 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 */ -// private val addressRegex = """zs\d\w{65,}""".toRegex() - suspend fun getSender(transaction: ConfirmedTransaction?): String { if (transaction == null) return getString(R.string.unknown) return MemoUtil.findAddressInMemo(transaction, ::isValidAddress)?.toAbbreviatedAddress() ?: getString(R.string.unknown) @@ -655,5 +655,4 @@ class MainActivity : AppCompatActivity() { 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 index f74b663..b4c6fec 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/MainViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/MainViewModel.kt @@ -34,4 +34,4 @@ class MainViewModel @Inject constructor() : ViewModel() { twig("MainViewModel.setSyncReady: $isReady") _syncReady.value = isReady } -} \ No newline at end of file +} 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 index e44de08..be9ec53 100644 --- 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 @@ -10,10 +10,15 @@ 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.* +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() { val mainActivity: MainActivity? get() = activity as MainActivity? @@ -79,4 +84,4 @@ abstract class BaseFragment : Fragment() { } } } -} \ No newline at end of file +} 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 index a43b7ff..30362c2 100644 --- 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 @@ -10,7 +10,12 @@ import androidx.recyclerview.widget.RecyclerView import cash.z.ecc.android.R import cash.z.ecc.android.databinding.FragmentHistoryBinding import cash.z.ecc.android.di.viewmodel.activityViewModel -import cash.z.ecc.android.ext.* +import cash.z.ecc.android.ext.WalletZecFormmatter +import cash.z.ecc.android.ext.goneIf +import cash.z.ecc.android.ext.onClickNavUp +import cash.z.ecc.android.ext.pending +import cash.z.ecc.android.ext.toAppString +import cash.z.ecc.android.ext.toColoredSpan 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 @@ -21,7 +26,6 @@ import cash.z.ecc.android.sdk.type.WalletBalance import cash.z.ecc.android.ui.base.BaseFragment import kotlinx.coroutines.launch - class HistoryFragment : BaseFragment() { override val screen = Report.Screen.HISTORY @@ -34,7 +38,6 @@ class HistoryFragment : BaseFragment() { override fun inflate(inflater: LayoutInflater): FragmentHistoryBinding = FragmentHistoryBinding.inflate(inflater) - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { twig("HistoryFragment.onViewCreated") super.onViewCreated(view, savedInstanceState) @@ -61,7 +64,7 @@ class HistoryFragment : BaseFragment() { goneIf(change <= 0L) val changeString = WalletZecFormmatter.toZecStringFull(change) val expecting = R.string.home_banner_expecting.toAppString(true) - text = "($expecting +$changeString ZEC)".toColoredSpan(R.color.text_light, "+${changeString}") + text = "($expecting +$changeString ZEC)".toColoredSpan(R.color.text_light, "+$changeString") } } @@ -90,9 +93,12 @@ class HistoryFragment : BaseFragment() { private fun scrollToTop() { twig("scrolling to the top") binding.recyclerTransactions.apply { - postDelayed({ - smoothScrollToPosition(0) - }, 5L) + postDelayed( + { + smoothScrollToPosition(0) + }, + 5L + ) } } @@ -100,4 +106,4 @@ class HistoryFragment : BaseFragment() { fun onLastItemShown(item: ConfirmedTransaction, position: Int) { binding.footerFade.alpha = position.toFloat() / (binding.recyclerTransactions.adapter?.itemCount ?: 1) } -} \ No newline at end of file +} 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 index 9ec4a20..988ec77 100644 --- 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 @@ -68,7 +68,6 @@ class HistoryViewModel @Inject constructor() : ViewModel() { var txId: String? = null ) - private suspend fun ConfirmedTransaction?.toUiModel(latestHeight: Int? = null): UiModel = UiModel().apply { this@toUiModel.let { tx -> txId = toTxId(tx?.rawTransactionId) @@ -105,7 +104,8 @@ class HistoryViewModel @Inject constructor() : ViewModel() { 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)}" + 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") @@ -118,11 +118,8 @@ class HistoryViewModel @Inject constructor() : ViewModel() { } else { confirmation = getString(R.string.transaction_status_pending) } - } -// val mainActivity = (context as MainActivity) - // inbound v. outbound values when (isInbound) { true -> { topLabel = getString(R.string.transaction_story_inbound) @@ -154,7 +151,7 @@ class HistoryViewModel @Inject constructor() : ViewModel() { 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) { + for (i in (tx.size - 1) downTo 0) { sb.append(String.format("%02x", tx[i])) } return sb.toString() @@ -164,10 +161,9 @@ class HistoryViewModel @Inject constructor() : ViewModel() { // 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 threshold = 75 * 1000 * 25 // approx 25 blocks val delta = System.currentTimeMillis() / 1000L - tx.blockTimeInSeconds - return tx.minedHeight > synchronizer.network.saplingActivationHeight - && delta < threshold + return tx.minedHeight > synchronizer.network.saplingActivationHeight && + 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 index 4b413a5..9da0ab6 100644 --- 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 @@ -13,9 +13,9 @@ class TransactionAdapter : 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!!))) + ) = 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, 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 index be05244..a781888 100644 --- 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 @@ -4,32 +4,34 @@ import android.content.res.ColorStateList import android.graphics.ColorMatrix import android.graphics.ColorMatrixColorFilter import android.os.Bundle -import android.text.format.DateUtils import android.text.method.ScrollingMovementMethod import android.view.LayoutInflater import android.view.View import androidx.core.view.ViewCompat import androidx.lifecycle.lifecycleScope -import androidx.transition.* +import androidx.transition.ChangeBounds +import androidx.transition.ChangeClipBounds +import androidx.transition.ChangeTransform +import androidx.transition.Transition +import androidx.transition.TransitionSet import cash.z.ecc.android.R -import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.databinding.FragmentTransactionBinding import cash.z.ecc.android.di.viewmodel.activityViewModel -import cash.z.ecc.android.ext.* +import cash.z.ecc.android.ext.Const +import cash.z.ecc.android.ext.gone +import cash.z.ecc.android.ext.invisible +import cash.z.ecc.android.ext.onClickNavBack +import cash.z.ecc.android.ext.toAppColor +import cash.z.ecc.android.ext.toColoredSpan +import cash.z.ecc.android.ext.visible import cash.z.ecc.android.feedback.Report -import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction -import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.twig -import cash.z.ecc.android.ui.MainActivity import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.history.HistoryViewModel.UiModel -import cash.z.ecc.android.ui.util.toUtf8Memo import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import java.util.* - class TransactionFragment : BaseFragment() { override val screen = Report.Screen.TRANSACTION @@ -53,7 +55,7 @@ class TransactionFragment : BaseFragment() { // sharedElementReturnTransition = ChangeBounds().apply { duration = 1500 } // enterTransition = Fade().apply { // duration = 1800 -//// slideEdge = Gravity.END +// // slideEdge = Gravity.END // } } @@ -117,7 +119,6 @@ class TransactionFragment : BaseFragment() { 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() @@ -162,7 +163,7 @@ class TransactionFragment : BaseFragment() { binding.subwaySpotMemoContent.rotation = 90.0f } else { binding.subwayLabelMemo.setText(getString(R.string.transaction_with_memo)) - binding.subwayLabelMemo.scrollTo(0,0) + binding.subwayLabelMemo.scrollTo(0, 0) binding.subwayLabelMemo.invalidate() twig("setting memo text to: with a memo") binding.groupMemoIcon.visible() @@ -196,12 +197,4 @@ class TransactionFragment : BaseFragment() { 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 index 9a399bd..2cbd2a1 100644 --- 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 @@ -4,10 +4,7 @@ import android.graphics.drawable.Drawable import android.view.View import android.widget.ImageView import android.widget.TextView -import androidx.annotation.ColorRes -import androidx.annotation.IntegerRes import androidx.annotation.StringRes -import androidx.appcompat.content.res.AppCompatResources import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import cash.z.ecc.android.R @@ -22,12 +19,12 @@ 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.MemoUtil import cash.z.ecc.android.ui.util.toUtf8Memo 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) @@ -40,7 +37,7 @@ class TransactionViewHolder(itemView: View) : Recycler val mainActivity = itemView.context as MainActivity mainActivity.lifecycleScope.launch { // update view - var lineOne:CharSequence = "" + var lineOne: CharSequence = "" var lineTwo = "" var amountZec = "" var amountDisplay = "" @@ -70,7 +67,7 @@ class TransactionViewHolder(itemView: View) : Recycler 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 ?: -1)) lineTwo = str(R.string.transaction_status_expired) + if (!isMined && (expiryHeight != null) && (expiryHeight!! < mainActivity.latestHeight ?: -1)) lineTwo = str(R.string.transaction_status_expired) amountDisplay = "- $amountZec" if (isMined) { arrowRotation = R.integer.transaction_arrow_rotation_send @@ -132,7 +129,8 @@ class TransactionViewHolder(itemView: View) : Recycler indicator.setBackgroundColor(indicatorBackground.toAppColor()) transactionArrow.setColorFilter(arrowBackgroundTint.toAppColor()) transactionArrow.rotation = arrowRotation.toAppInt().toFloat() - var bottomTextRightDrawable:Drawable? = null + + var bottomTextRightDrawable: Drawable? = null iconMemo.goneIf(!transaction?.memo.toUtf8Memo().isNotEmpty()) bottomText.setCompoundDrawablesWithIntrinsicBounds(null, null, bottomTextRightDrawable, null) } @@ -153,7 +151,4 @@ class TransactionViewHolder(itemView: View) : Recycler } 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 index fc36ae2..23a1d81 100644 --- 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 @@ -1,15 +1,15 @@ 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 +// 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() { +// class TransactionsDrawableFooter(context: Context) : RecyclerView.ItemDecoration() { // // private var footer: View = // LayoutInflater.from(context).inflate(R.layout.footer_transactions, null, false) @@ -49,4 +49,4 @@ package cash.z.ecc.android.ui.history // 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 index c317907..59a0981 100644 --- 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 @@ -8,7 +8,6 @@ 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) 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 index 4a5ab00..ce51825 100644 --- 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 @@ -68,7 +68,6 @@ class HomeFragment : BaseFragment() { override fun inflate(inflater: LayoutInflater): FragmentHomeBinding = FragmentHomeBinding.inflate(inflater) - // // LifeCycle // @@ -144,7 +143,6 @@ class HomeFragment : BaseFragment() { // 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.coerceAtLeast(0)))) } - } private fun onClearAmount() { @@ -190,7 +188,6 @@ class HomeFragment : BaseFragment() { } } - // // Public UI API // @@ -277,7 +274,7 @@ class HomeFragment : BaseFragment() { val change = WalletZecFormmatter.toZecStringFull(totalBalance - availableBalance) "(${getString(R.string.home_banner_expecting)} +$change ZEC)".toColoredSpan(R.color.text_light, "+$change") } else { - getString(R.string.home_instruction_enter_amount) + getString(R.string.home_instruction_enter_amount) } } } @@ -294,7 +291,6 @@ class HomeFragment : BaseFragment() { } } - // // Private UI Events // @@ -320,9 +316,9 @@ class HomeFragment : BaseFragment() { else -> { buildString { append("UiModel(") - if (old.status != new.status) append ("status=${new.status}") + if (old.status != new.status) append("status=${new.status}") if (old.processorInfo != new.processorInfo) { - append ("${maybeComma()}processorInfo=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}") @@ -332,9 +328,9 @@ class HomeFragment : BaseFragment() { if (old.processorInfo.lastScanRange != new.processorInfo.lastScanRange) append("${innerComma()}lastScanRange=${new.processorInfo.lastScanRange}") append(")") } - if (old.availableBalance != new.availableBalance) append ("${maybeComma()}availableBalance=${new.availableBalance}") - if (old.totalBalance != new.totalBalance) append ("${maybeComma()}totalBalance=${new.totalBalance}") - if (old.pendingSend != new.pendingSend) append ("${maybeComma()}pendingSend=${new.pendingSend}") + if (old.availableBalance != new.availableBalance) append("${maybeComma()}availableBalance=${new.availableBalance}") + if (old.totalBalance != new.totalBalance) append("${maybeComma()}totalBalance=${new.totalBalance}") + if (old.pendingSend != new.pendingSend) append("${maybeComma()}pendingSend=${new.pendingSend}") append(")") } } @@ -413,7 +409,6 @@ class HomeFragment : BaseFragment() { }.launchIn(resumedScope) } - // // Inner classes and extensions // @@ -444,11 +439,10 @@ class HomeFragment : BaseFragment() { return this } - // // User Interruptions // - //TODO: Expand this placeholder logic around when to interrupt the user. + // 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 { @@ -536,4 +530,4 @@ class HomeFragment : BaseFragment() { override fun onDetach() { super.onDetach() } -} \ No newline at end of file +} 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 index 4f46df6..d3a79d3 100644 --- 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 @@ -5,14 +5,23 @@ import cash.z.ecc.android.R import cash.z.ecc.android.ext.toAppString import cash.z.ecc.android.sdk.SdkSynchronizer import cash.z.ecc.android.sdk.Synchronizer -import cash.z.ecc.android.sdk.Synchronizer.Status.* +import cash.z.ecc.android.sdk.Synchronizer.Status.DISCONNECTED +import cash.z.ecc.android.sdk.Synchronizer.Status.DOWNLOADING +import cash.z.ecc.android.sdk.Synchronizer.Status.SCANNING +import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED +import cash.z.ecc.android.sdk.Synchronizer.Status.VALIDATING import cash.z.ecc.android.sdk.block.CompactBlockProcessor import cash.z.ecc.android.sdk.exception.RustLayerException import cash.z.ecc.android.sdk.ext.ZcashSdk.MINERS_FEE_ZATOSHI import cash.z.ecc.android.sdk.ext.ZcashSdk.ZATOSHI_PER_ZEC import cash.z.ecc.android.sdk.ext.twig import kotlinx.coroutines.channels.ConflatedBroadcastChannel -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.scan import javax.inject.Inject import kotlin.math.roundToInt @@ -39,14 +48,14 @@ class HomeViewModel @Inject constructor() : ViewModel() { } _typedChars = ConflatedBroadcastChannel() val typedChars = _typedChars.asFlow() - val decimal = '.'// R.string.key_decimal.toAppString()[0] + 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 == "0" && c == '0' || + (c == backspace && acc == "0") + || (c == decimal && acc.contains(decimal)) -> { acc } c == backspace && acc.length <= 1 -> { @@ -68,9 +77,9 @@ class HomeViewModel @Inject constructor() : ViewModel() { } twig("initializing view models stream") uiModels = synchronizer.run { - combine(status, processorInfo, balances, zec) { s, p, b, z-> + combine(status, processorInfo, balances, zec) { s, p, b, z -> UiModel(s, p, b.availableZatoshi, b.totalZatoshi, z) - }.onStart{ emit(UiModel()) } + }.onStart { emit(UiModel()) } }.conflate() } 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 index abd129a..8934aaf 100644 --- 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 @@ -1,7 +1,6 @@ package cash.z.ecc.android.ui.home import android.animation.ValueAnimator -import cash.z.ecc.android.sdk.ext.twig import com.airbnb.lottie.LottieAnimationView class MagicSnakeLoader( @@ -54,14 +53,17 @@ class MagicSnakeLoader( 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) + 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 @@ -84,7 +86,7 @@ class MagicSnakeLoader( } } - private val acceptablePauseFrames = arrayOf(33,34,67,68,99) + 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) { @@ -117,7 +119,7 @@ class MagicSnakeLoader( } private fun removeLoops() { - lottie.frame.let {frame -> + lottie.frame.let { frame -> if (frame in 33..67) { lottie.frame = frame + 34 } else if (frame in 0..33) { @@ -151,4 +153,3 @@ class MagicSnakeLoader( 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 index 679426c..00a7bc7 100644 --- 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 @@ -19,7 +19,6 @@ import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report.Tap.AWESOME_CLOSE import cash.z.ecc.android.feedback.Report.Tap.AWESOME_SHIELD 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.PendingTransaction import cash.z.ecc.android.sdk.db.entity.isCancelled import cash.z.ecc.android.sdk.db.entity.isCreated @@ -37,7 +36,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch - class AwesomeFragment : BaseFragment() { override val screen = Report.Screen.AWESOME @@ -96,7 +94,6 @@ class AwesomeFragment : BaseFragment() { viewModel.getTransparentBalance().let { balance -> onBalanceUpdated(balance, utxoCount) } - } private fun onAddressLoaded(address: String) { @@ -119,7 +116,7 @@ class AwesomeFragment : BaseFragment() { 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." + " Ensure that the address is valid." ) } @@ -200,7 +197,6 @@ class AwesomeFragment : BaseFragment() { } } - private fun onShieldComplete(isSuccess: Boolean) { binding.lottieShielding.visibility = View.GONE @@ -248,9 +244,6 @@ class AwesomeFragment : BaseFragment() { } } - - - private fun PendingTransaction.toUiModel() = UiModel().also { model -> when { isCancelled() -> { @@ -283,7 +276,7 @@ class AwesomeFragment : BaseFragment() { model.primaryAction = { onCancel(this) } } else { model.primaryButtonText = "Shielding Funds..." - if(isCreated()) model.details.add("Submitting transaction...") + if (isCreated()) model.details.add("Submitting transaction...") } } } @@ -301,8 +294,8 @@ class AwesomeFragment : BaseFragment() { val details: MutableSet = linkedSetOf(), var showProgress: Boolean = false, var primaryButtonText: String = "Shield Transparent Funds", - var primaryAction: () -> Unit = {}, + var primaryAction: () -> Unit = {}, var canCancel: Boolean = false, var updateBalance: Boolean = false, ) -} \ No newline at end of file +} diff --git a/app/src/main/java/cash/z/ecc/android/ui/profile/FeedbackFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/profile/FeedbackFragment.kt index 9c9040a..bb2620e 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/profile/FeedbackFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/profile/FeedbackFragment.kt @@ -3,26 +3,21 @@ package cash.z.ecc.android.ui.profile import android.os.Bundle import android.view.LayoutInflater import android.view.View -import android.view.ViewTreeObserver -import android.view.WindowManager import android.widget.Toast -import androidx.core.view.doOnLayout import androidx.navigation.fragment.navArgs import cash.z.ecc.android.R import cash.z.ecc.android.databinding.FragmentFeedbackBinding -import cash.z.ecc.android.di.viewmodel.viewModel import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback import cash.z.ecc.android.feedback.Report.Tap.FEEDBACK_CANCEL import cash.z.ecc.android.feedback.Report.Tap.FEEDBACK_SUBMIT import cash.z.ecc.android.ui.base.BaseFragment - /** * Fragment representing the home screen of the app. This is the screen most often seen by the user when launching the * application. */ -class FeedbackFragment : BaseFragment() { +class FeedbackFragment : BaseFragment() { override val screen = Report.Screen.FEEDBACK val args: FeedbackFragmentArgs by navArgs() @@ -68,7 +63,6 @@ class FeedbackFragment : BaseFragment() { } } - // // Private API // 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 index 59c7489..55999b9 100644 --- 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 @@ -40,7 +40,6 @@ import cash.z.ecc.android.ui.util.DebugFileTwig import kotlinx.coroutines.launch import java.io.File - class ProfileFragment : BaseFragment() { override val screen = Report.Screen.PROFILE @@ -93,13 +92,14 @@ class ProfileFragment : BaseFragment() { if (viewModel.isEasterEggTriggered()) { binding.iconProfile.setImageResource(R.drawable.ic_profile_zebra_02) } - } 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}") + ?: throw IllegalStateException( + "Cannot navigate from this activity. " + + "Expected MainActivity but found ${context?.javaClass?.simpleName}" + ) } override fun onResume() { @@ -135,7 +135,7 @@ class ProfileFragment : BaseFragment() { viewModel.quickRescan() Toast.makeText(ZcashWalletApp.instance, "Performing quick rescan!", Toast.LENGTH_LONG).show() mainActivity?.navController?.popBackStack() - } catch(t: Throwable) { + } catch (t: Throwable) { mainActivity?.showCriticalMessage("Quick Rescan Failed", "Unable to perform quick rescan due to error:\n\n${t.message}") } } @@ -145,8 +145,8 @@ class ProfileFragment : BaseFragment() { 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?", + "this operation is probably safe but please backup your seed anyway." + + "\n\nContinue?", "Wipe" ) { viewModel.wipe() @@ -214,4 +214,4 @@ class ProfileFragment : BaseFragment() { private fun developerLogFile(): File? { return Bush.trunk.find()?.file } -} \ No newline at end of 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 index 8b9a79f..cb3aa0f 100644 --- 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 @@ -6,7 +6,6 @@ import cash.z.ecc.android.ZcashWalletApp 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.SdkSynchronizer import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.db.entity.PendingTransaction import cash.z.ecc.android.sdk.ext.ZcashSdk @@ -15,7 +14,6 @@ import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.sdk.type.WalletBalance import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import javax.inject.Inject import javax.inject.Named @@ -108,7 +106,7 @@ class ProfileViewModel @Inject constructor() : ViewModel() { fun quickScanDistance(): Int { val latest = synchronizer.latestHeight - val oneWeek = 60*60*24/75 * 7 // a week's worth of blocks + val oneWeek = 60 * 60 * 24 / 75 * 7 // a week's worth of blocks var foo = 0 runBlocking { foo = synchronizer.getNearestRewindHeight(latest - oneWeek) diff --git a/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveFragment.kt index 2b0d5b6..c58d728 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveFragment.kt @@ -8,20 +8,16 @@ import android.view.LayoutInflater import android.view.View import android.widget.TextView import cash.z.android.qrecycler.QRecycler -import cash.z.ecc.android.R import cash.z.ecc.android.databinding.FragmentReceiveNewBinding import cash.z.ecc.android.di.viewmodel.viewModel import cash.z.ecc.android.ext.distribute import cash.z.ecc.android.ext.onClickNavBack -import cash.z.ecc.android.ext.onClickNavTo import cash.z.ecc.android.feedback.Report -import cash.z.ecc.android.feedback.Report.Tap.* -import cash.z.ecc.android.ui.base.BaseFragment -import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress +import cash.z.ecc.android.feedback.Report.Tap.RECEIVE_BACK import cash.z.ecc.android.sdk.ext.twig +import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.util.AddressPartNumberSpan import kotlinx.coroutines.launch -import kotlin.math.roundToInt class ReceiveFragment : BaseFragment() { override val screen = Report.Screen.RECEIVE @@ -84,4 +80,4 @@ class ReceiveFragment : BaseFragment() { addressParts[index].text = textSpan } -} \ No newline at end of file +} 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 index c69796e..1e3a982 100644 --- 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 @@ -10,7 +10,6 @@ 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 { @@ -63,5 +62,4 @@ class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Uni 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 index 9d0adfa..5a3df1a 100644 --- 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 @@ -6,7 +6,11 @@ import android.os.Bundle import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.View -import androidx.camera.core.* +import androidx.camera.core.AspectRatio +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat import cash.z.ecc.android.R @@ -16,7 +20,6 @@ import cash.z.ecc.android.di.viewmodel.viewModel 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.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.send.SendViewModel @@ -26,7 +29,9 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.Executors class ScanFragment : BaseFragment() { + override val screen = Report.Screen.SCAN + private val viewModel: ScanViewModel by viewModel() private val sendViewModel: SendViewModel by activityViewModel() @@ -54,9 +59,12 @@ class ScanFragment : BaseFragment() { override fun onAttach(context: Context) { super.onAttach(context) cameraProviderFuture = ProcessCameraProvider.getInstance(context) - cameraProviderFuture.addListener(Runnable { - bindPreview(cameraProviderFuture.get()) - }, ContextCompat.getMainExecutor(context)) + cameraProviderFuture.addListener( + Runnable { + bindPreview(cameraProviderFuture.get()) + }, + ContextCompat.getMainExecutor(context) + ) } override fun onDestroyView() { @@ -87,9 +95,12 @@ class ScanFragment : BaseFragment() { .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() - imageAnalysis.setAnalyzer(cameraExecutor!!, QrAnalyzer { q, i -> - onQrScanned(q, i) - }) + imageAnalysis.setAnalyzer( + cameraExecutor!!, + QrAnalyzer { q, i -> + onQrScanned(q, i) + } + ) // Must unbind the use-cases before rebinding them cameraProvider.unbindAll() @@ -102,7 +113,6 @@ class ScanFragment : BaseFragment() { mainActivity?.feedback?.report(t) twig("Error while opening the camera: $t") } - } /** @@ -114,7 +124,8 @@ class ScanFragment : BaseFragment() { height ) if (kotlin.math.abs(previewRatio - (4.0 / 3.0)) - <= kotlin.math.abs(previewRatio - (16.0 / 9.0))) { + <= kotlin.math.abs(previewRatio - (16.0 / 9.0)) + ) { return AspectRatio.RATIO_4_3 } return AspectRatio.RATIO_16_9 @@ -127,7 +138,7 @@ class ScanFragment : BaseFragment() { val network = viewModel.networkName binding.textScanError.text = getString(R.string.scan_invalid_address, network, qrContent) image.close() - } else { /* continue scanning*/ + } else { /* continue scanning*/ binding.textScanError.text = "" sendViewModel.toAddress = parsed mainActivity?.safeNavigate(R.id.action_nav_scan_to_nav_send) @@ -157,12 +168,6 @@ class ScanFragment : BaseFragment() { // overlay.set(list) // } - - - - - - // // Permissions // @@ -171,7 +176,7 @@ class ScanFragment : BaseFragment() { get() { return try { val info = mainActivity?.packageManager - ?.getPackageInfo(mainActivity?.packageName, PackageManager.GET_PERMISSIONS) + ?.getPackageInfo(mainActivity?.packageName ?: "", PackageManager.GET_PERMISSIONS) val ps = info?.requestedPermissions if (ps != null && ps.isNotEmpty()) { ps @@ -212,4 +217,4 @@ class ScanFragment : BaseFragment() { return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED } } -} \ No newline at end of file +} 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 index 785d2d3..9419a0a 100644 --- 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 @@ -13,7 +13,7 @@ class ScanViewModel @Inject constructor() : ViewModel() { 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: + // 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) @@ -27,5 +27,4 @@ class ScanViewModel @Inject constructor() : ViewModel() { super.onCleared() twig("${javaClass.simpleName} cleared!") } - } diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendConfirmFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendConfirmFragment.kt index 6fc7432..fd02ea9 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/send/SendConfirmFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendConfirmFragment.kt @@ -4,18 +4,15 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import androidx.lifecycle.lifecycleScope -import cash.z.ecc.android.R import cash.z.ecc.android.databinding.FragmentSendConfirmBinding import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.ext.WalletZecFormmatter import cash.z.ecc.android.ext.goneIf -import cash.z.ecc.android.ext.onClickNavTo 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.feedback.Report.Tap.SEND_CONFIRM_NEXT import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress -import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString +import cash.z.ecc.android.ui.base.BaseFragment import kotlinx.coroutines.launch class SendConfirmFragment : BaseFragment() { @@ -49,4 +46,4 @@ class SendConfirmFragment : BaseFragment() { sendViewModel.funnel(Send.ConfirmPageComplete) // mainActivity?.safeNavigate(R.id.action_nav_send_confirm_to_send_final) } -} \ No newline at end of file +} 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 index f94bb80..c5339d0 100644 --- 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 @@ -13,7 +13,12 @@ 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.db.entity.* +import cash.z.ecc.android.sdk.db.entity.PendingTransaction +import cash.z.ecc.android.sdk.db.entity.isCancelled +import cash.z.ecc.android.sdk.db.entity.isCreating +import cash.z.ecc.android.sdk.db.entity.isFailedEncoding +import cash.z.ecc.android.sdk.db.entity.isFailure +import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.ui.base.BaseFragment @@ -70,13 +75,13 @@ class SendFinalFragment : BaseFragment() { } buttonMoreInfo.apply { - setOnClickListener{ + setOnClickListener { val moreInfoMsg = """${getString(R.string.more_info)} : ${model.errorDescription}""" txtMoreInfo.run { text = moreInfoMsg } - if(model.errorDescription.isNotEmpty()) + if (model.errorDescription.isNotEmpty()) buttonMoreInfo.text = getString(R.string.translated_button_done) } } @@ -115,7 +120,7 @@ class SendFinalFragment : BaseFragment() { private fun PendingTransaction.toUiModel() = UiModel().also { model -> when { - isCancelled() -> { + isCancelled() -> { model.title = getString(R.string.send_final_result_cancelled) model.primaryButtonText = getString(R.string.send_final_button_primary_back) model.primaryAction = { onReturnToSend() } @@ -128,7 +133,8 @@ class SendFinalFragment : BaseFragment() { 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) + R.string.send_final_error_submitting + ) model.errorDescription = errorMessage.toString() model.primaryButtonText = getString(R.string.send_final_button_primary_retry) model.primaryAction = { onReturnToSend() } @@ -156,7 +162,6 @@ class SendFinalFragment : BaseFragment() { var showProgress: Boolean = false, var errorMessage: String = "", var primaryButtonText: String = "See Details", - var primaryAction: () -> Unit = {} + var primaryAction: () -> Unit = {} ) - } 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 index 5ee1cd2..fe78c6f 100644 --- 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 @@ -19,10 +19,24 @@ import androidx.lifecycle.viewModelScope import cash.z.ecc.android.R import cash.z.ecc.android.databinding.FragmentSendBinding import cash.z.ecc.android.di.viewmodel.activityViewModel -import cash.z.ecc.android.ext.* +import cash.z.ecc.android.ext.WalletZecFormmatter +import cash.z.ecc.android.ext.gone +import cash.z.ecc.android.ext.goneIf +import cash.z.ecc.android.ext.onClickNavUp +import cash.z.ecc.android.ext.toAppColor +import cash.z.ecc.android.ext.visible import cash.z.ecc.android.feedback.Report -import cash.z.ecc.android.feedback.Report.Tap.* -import cash.z.ecc.android.sdk.ext.* +import cash.z.ecc.android.feedback.Report.Tap.SEND_ADDRESS_BACK +import cash.z.ecc.android.feedback.Report.Tap.SEND_ADDRESS_PASTE +import cash.z.ecc.android.feedback.Report.Tap.SEND_ADDRESS_REUSE +import cash.z.ecc.android.feedback.Report.Tap.SEND_ADDRESS_SCAN +import cash.z.ecc.android.feedback.Report.Tap.SEND_MEMO_EXCLUDE +import cash.z.ecc.android.feedback.Report.Tap.SEND_MEMO_INCLUDE +import cash.z.ecc.android.feedback.Report.Tap.SEND_SUBMIT +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.type.AddressType import cash.z.ecc.android.sdk.type.WalletBalance import cash.z.ecc.android.ui.base.BaseFragment @@ -30,7 +44,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -class SendFragment : BaseFragment(), +class SendFragment : + BaseFragment(), ClipboardManager.OnPrimaryClipChangedListener { override val screen = Report.Screen.SEND_ADDRESS @@ -49,14 +64,13 @@ class SendFragment : BaseFragment(), applyViewModel(sendViewModel) updateAddressUi(false) - // Apply behaviors binding.buttonSend.setOnClickListener { onSubmit().also { tapped(SEND_SUBMIT) } } - binding.checkIncludeAddress.setOnCheckedChangeListener { _, _-> + binding.checkIncludeAddress.setOnCheckedChangeListener { _, _ -> onIncludeMemo(binding.checkIncludeAddress.isChecked) } @@ -172,8 +186,7 @@ class SendFragment : BaseFragment(), } /** - * To hide input Memo and reply-to option for T type address and show a info message about memo option availability - * */ + * 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() @@ -186,7 +199,6 @@ class SendFragment : BaseFragment(), } } - private fun onSubmit(unused: EditText? = null) { sendViewModel.toAddress = binding.inputZcashAddress.text.toString() sendViewModel.validate(requireContext(), availableZatoshi, maxZatoshi).onFirstWith(resumedScope) { errorMessage -> @@ -217,7 +229,6 @@ class SendFragment : BaseFragment(), } } - override fun onAttach(context: Context) { super.onAttach(context) mainActivity?.clipboard?.addPrimaryClipChangedListener(this) @@ -280,7 +291,8 @@ class SendFragment : BaseFragment(), imageLastUsedShield, lastUsedAddressLabel, selected, - address.takeUnless { isBoth }) + address.takeUnless { isBoth } + ) } binding.dividerClipboard.setText(if (isBoth) R.string.send_history_last_and_clipboard else R.string.send_history_clipboard) } @@ -306,8 +318,8 @@ class SendFragment : BaseFragment(), 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()) + 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()) } } } @@ -352,7 +364,6 @@ class SendFragment : BaseFragment(), return lastUsedAddress } - private fun ClipboardManager.text(): CharSequence = primaryClip!!.getItemAt(0).coerceToText(mainActivity) -} \ No newline at end of file +} 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 index 10884dd..f825266 100644 --- 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 @@ -12,7 +12,11 @@ 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.feedback.Report.Tap.SEND_MEMO_CLEAR +import cash.z.ecc.android.feedback.Report.Tap.SEND_MEMO_EXCLUDE +import cash.z.ecc.android.feedback.Report.Tap.SEND_MEMO_INCLUDE +import cash.z.ecc.android.feedback.Report.Tap.SEND_MEMO_NEXT +import cash.z.ecc.android.feedback.Report.Tap.SEND_MEMO_SKIP import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX_STANDARD @@ -41,7 +45,7 @@ class SendMemoFragment : BaseFragment() { // onBackPressNavTo(it) { tapped(SEND_MEMO_BACK) } // } - binding.checkIncludeAddress.setOnCheckedChangeListener { _, _-> + binding.checkIncludeAddress.setOnCheckedChangeListener { _, _ -> onIncludeMemo(binding.checkIncludeAddress.isChecked) } @@ -49,7 +53,7 @@ class SendMemoFragment : BaseFragment() { memo.onEditorActionDone { onTopButton().also { tapped(SEND_MEMO_NEXT) } } - memo.doAfterTextChanged { + memo.doAfterTextChanged { binding.clearMemo.goneIf(memo.text.isEmpty()) } } @@ -117,4 +121,4 @@ class SendMemoFragment : BaseFragment() { sendViewModel.funnel(Send.MemoPageComplete) // mainActivity?.safeNavigate(R.id.action_nav_send_memo_to_send_confirm) } -} \ No newline at end of file +} 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 index 04dc370..e57c8d7 100644 --- 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 @@ -15,10 +15,20 @@ 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.feedback.Report.MetricType.TRANSACTION_CREATED +import cash.z.ecc.android.feedback.Report.MetricType.TRANSACTION_INITIALIZED +import cash.z.ecc.android.feedback.Report.MetricType.TRANSACTION_MINED +import cash.z.ecc.android.feedback.Report.MetricType.TRANSACTION_SUBMITTED 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.db.entity.PendingTransaction +import cash.z.ecc.android.sdk.db.entity.isCancelled +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.isFailedEncoding +import cash.z.ecc.android.sdk.db.entity.isFailedSubmit +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.twig import cash.z.ecc.android.sdk.tool.DerivationTool @@ -54,12 +64,12 @@ class SendViewModel @Inject constructor() : ViewModel() { 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." + " 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() @@ -92,7 +102,7 @@ class SendViewModel @Inject constructor() : ViewModel() { suspend fun validateAddress(address: String): AddressType = synchronizer.validateAddress(address) - suspend fun isValidAddress(address: String): Boolean = when(validateAddress(address)) { + suspend fun isValidAddress(address: String): Boolean = when (validateAddress(address)) { is AddressType.Shielded, is AddressType.Transparent -> true else -> false } @@ -140,7 +150,6 @@ class SendViewModel @Inject constructor() : ViewModel() { includeFromAddress = false } - // // Analytics // @@ -176,7 +185,7 @@ class SendViewModel @Inject constructor() : ViewModel() { } } - private fun updateMetrics(tx: PendingTransaction) { + fun updateMetrics(tx: PendingTransaction) { try { when { tx.isMined() -> TRANSACTION_SUBMITTED to TRANSACTION_MINED by tx.id @@ -192,7 +201,7 @@ class SendViewModel @Inject constructor() : ViewModel() { } } - private fun report(metricId: String?) { + fun report(metricId: String?) { metrics[metricId]?.let { metric -> metric.takeUnless { (it.elapsedTime ?: 0) <= 0L }?.let { viewModelScope.launch { @@ -228,26 +237,17 @@ class SendViewModel @Inject constructor() : ViewModel() { 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 - } + 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 index 89d8412..a87d8b0 100644 --- 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 @@ -10,7 +10,13 @@ import cash.z.ecc.android.R import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.databinding.FragmentSettingsBinding import cash.z.ecc.android.di.viewmodel.viewModel -import cash.z.ecc.android.ext.* +import cash.z.ecc.android.ext.gone +import cash.z.ecc.android.ext.onClickNavBack +import cash.z.ecc.android.ext.showUpdateServerCriticalError +import cash.z.ecc.android.ext.showUpdateServerDialog +import cash.z.ecc.android.ext.toAppColor +import cash.z.ecc.android.ext.toAppString +import cash.z.ecc.android.ext.visible import cash.z.ecc.android.sdk.exception.LightWalletException import cash.z.ecc.android.sdk.ext.collectWith import cash.z.ecc.android.sdk.ext.twig @@ -53,7 +59,6 @@ class SettingsFragment : BaseFragment() { viewModel.uiModels.collectWith(resumedScope, ::onUiModelUpdated) } - // // Event handlers // @@ -131,24 +136,22 @@ class SettingsFragment : BaseFragment() { error.javaClass.simpleName } val message = "An error occured while changing servers. Please verify the info" + - " and try again.\n\nError: $details" + " 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) { + val color = if (this == null) { R.color.text_light_dimmed } else { R.color.zcashRed } - return ColorStateList.valueOf(color.toAppColor()) + 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 index 180c64e..f828117 100644 --- 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 @@ -6,7 +6,6 @@ import cash.z.ecc.android.lockbox.LockBox import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.ext.twig import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.cancellable import javax.inject.Inject import javax.inject.Named import kotlin.properties.Delegates.observable @@ -28,7 +27,6 @@ class SettingsViewModel @Inject constructor() : ViewModel() { var pendingHost by observable("", ::onUpdateModel) var pendingPortText by observable("", ::onUpdateModel) - private fun getHost(): String { return prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST } 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 index 44e5e2a..ced8993 100644 --- 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 @@ -40,7 +40,7 @@ class BackupFragment : BaseFragment() { private val walletSetup: WalletSetupViewModel by activityViewModel(false) - private var hasBackUp: Boolean = true //TODO: implement backup and then check for it here-ish + private var hasBackUp: Boolean = true // TODO: implement backup and then check for it here-ish override fun inflate(inflater: LayoutInflater): FragmentBackupBinding = FragmentBackupBinding.inflate(inflater) @@ -76,7 +76,7 @@ class BackupFragment : BaseFragment() { override fun onAttach(context: Context) { super.onAttach(context) walletSetup.checkSeed().onEach { - hasBackUp = when(it) { + hasBackUp = when (it) { SEED_WITH_BACKUP -> true else -> false } @@ -134,9 +134,9 @@ class BackupFragment : BaseFragment() { 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) + val seedPhrase = lockBox.getCharsUtf8(Const.Backup.SEED_PHRASE) ?: throw RuntimeException("Seed Phrase expected but not found in storage!!") + val result = mnemonics.toWordList(seedPhrase) result } } -} \ No newline at end of file +} 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 index df7b602..7dddfec 100644 --- 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 @@ -89,7 +89,7 @@ class LandingFragment : BaseFragment() { super.onAttach(context) walletSetup.checkSeed().onEach { - when(it) { + when (it) { SEED_WITHOUT_BACKUP, SEED_WITH_BACKUP -> { mainActivity?.safeNavigate(R.id.nav_backup) } @@ -100,9 +100,12 @@ class LandingFragment : BaseFragment() { override fun onResume() { super.onResume() - view?.postDelayed({ - mainActivity?.hideKeyboard() - }, 25L) + view?.postDelayed( + { + mainActivity?.hideKeyboard() + }, + 25L + ) } private fun onSkip(count: Int) { @@ -134,10 +137,10 @@ class LandingFragment : BaseFragment() { val birthday: Int // new testnet dev wallet - when(ZcashWalletApp.instance.defaultNetwork) { + 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 = 991645 //663174 + birthday = 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" @@ -211,4 +214,4 @@ class LandingFragment : BaseFragment() { skipCount = 0 mainActivity?.navController?.popBackStack() } -} \ No newline at end of file +} 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 index b51fa0e..55b1488 100644 --- 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 @@ -10,7 +10,6 @@ import android.view.MotionEvent import android.view.MotionEvent.ACTION_DOWN import android.view.MotionEvent.ACTION_UP import android.view.View -import android.widget.RelativeLayout import android.widget.TextView import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView @@ -28,7 +27,6 @@ import cash.z.ecc.android.feedback.Report.Tap.RESTORE_BACK import cash.z.ecc.android.feedback.Report.Tap.RESTORE_CLEAR import cash.z.ecc.android.feedback.Report.Tap.RESTORE_DONE import cash.z.ecc.android.feedback.Report.Tap.RESTORE_SUCCESS -import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.ui.base.BaseFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tylersuehr.chips.Chip @@ -36,7 +34,6 @@ 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 @@ -57,7 +54,6 @@ class RestoreFragment : BaseFragment(), View.OnKeyListen }.also { onChipsModified() } seedWordRecycler.adapter = seedWordAdapter - binding.chipsInput.apply { setFilterableChipList(getChips()) setDelimiter("[ ;,]", true) @@ -116,7 +112,6 @@ class RestoreFragment : BaseFragment(), View.OnKeyListen touchScreenForUser() } - private fun onExit() { mainActivity?.reportFunnel(Restore.Exit) hideAutoCompleteWords() @@ -189,12 +184,15 @@ class RestoreFragment : BaseFragment(), View.OnKeyListen // 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) + 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) { @@ -220,11 +218,14 @@ class RestoreFragment : BaseFragment(), View.OnKeyListen 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) + 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 + ) } } @@ -235,13 +236,12 @@ class RestoreFragment : BaseFragment(), View.OnKeyListen 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 getSubtitle(): String? = null // "subtitle for $word" override fun getAvatarDrawable(): Drawable? = null override fun getId() = index override fun getTitle() = word override fun getAvatarUri() = null -} \ No newline at end of file +} 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 index 5e60a61..139333c 100644 --- 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 @@ -7,10 +7,9 @@ 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.sdk.ext.twig import cash.z.ecc.android.ui.setup.SeedWordChip -class SeedWordAdapter : ChipsAdapter { +class SeedWordAdapter : ChipsAdapter { constructor(existingAdapter: ChipsAdapter) : super(existingAdapter.mDataSource, existingAdapter.mEditText, existingAdapter.mOptions) @@ -22,23 +21,23 @@ class SeedWordAdapter : ChipsAdapter { else object : RecyclerView.ViewHolder(mEditText) {} } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (getItemViewType(position) == CHIP) { // Chips + if (getItemViewType(position) == CHIP) { // Chips // Display the chip information on the chip view - (holder as SeedWordHolder).seedChipView.bind(mDataSource.getSelectedChip(position), position); + (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! + // 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"} + val ordinal = when (size) { 2 -> "3rd"; 1 -> "2nd"; else -> "1st" } "Enter $ordinal seed word" - } else if(size >= 24) { + } else if (size >= 24) { mEditText.setHintTextColor(R.color.zcashGreen.toAppColor()) mEditText.isCursorVisible = false "done" @@ -66,10 +65,13 @@ class SeedWordAdapter : ChipsAdapter { if (mDataSource.originalChips.firstOrNull { it.title == text } != null) { mDataSource.addSelectedChip(DefaultCustomChip(text)) mEditText.apply { - postDelayed({ - setText("") - requestFocus() - }, 50L) + postDelayed( + { + setText("") + requestFocus() + }, + 50L + ) } } } @@ -99,4 +101,3 @@ class SeedWordAdapter : ChipsAdapter { } } } - 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 index 213f855..9a5e377 100644 --- 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 @@ -175,7 +175,6 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() { private fun loadNearestBirthday(network: ZcashNetwork, birthdayHeight: Int? = null) = WalletBirthdayTool.loadNearest(ZcashWalletApp.instance, network, birthdayHeight) - // // Storage Helpers // @@ -193,7 +192,7 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() { ) { 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!" + " existing seed and could lead to a loss of funds if the user has no backup!" } storeBirthday(birthday) @@ -229,5 +228,4 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() { lockBox[Const.Backup.VIEWING_KEY] = vk.extfvk lockBox[Const.Backup.PUBLIC_KEY] = vk.extpub } - -} \ No newline at end of file +} 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 index a7613a3..3d7b3b5 100644 --- 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 @@ -16,13 +16,13 @@ class AddressPartNumberSpan( ) : MetricAffectingSpan() { override fun updateMeasureState(textPaint: TextPaint) { - textPaint.baselineShift += (textPaint.ascent() / 2).toInt() // from SuperscriptSpan - textPaint.textSize = textPaint.textSize * proportion // from RelativeSizeSpan + 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 + 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 } -} \ No newline at end of file +} 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 index b7fe65e..1da5226 100644 --- 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 @@ -19,4 +19,4 @@ class DebugFileTwig(fileName: String = "developer_log.txt") : TroubleshootingTwi it.writeUtf8("$message\n") } } -} \ No newline at end of file +} 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 index fdeb4f8..a04fea0 100644 --- 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 @@ -13,12 +13,12 @@ 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 + 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 @@ -41,7 +41,7 @@ object MemoUtil { INCLUDE_MEMO_PREFIXES_RECOGNIZED.mapNotNull { val maybeMemo = memo.substringAfterLast(it) if (addressValidator(maybeMemo)) maybeMemo else null - }.firstOrNull{ !it.isNullOrBlank() } + }.firstOrNull { !it.isNullOrBlank() } } } 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 index dd598ed..334df6d 100644 --- 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 @@ -1,15 +1,15 @@ 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 +// 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() { +// class PermissionFragment : Fragment() { // // val activity get() = context as MainActivity // @@ -44,4 +44,4 @@ package cash.z.ecc.android.ui.util // ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED // } // } -//} \ No newline at end of file +// } diff --git a/app/src/test/java/cash/z/ecc/android/ScratchPad.kt b/app/src/test/java/cash/z/ecc/android/ScratchPad.kt index 562d7ad..6601b8e 100644 --- a/app/src/test/java/cash/z/ecc/android/ScratchPad.kt +++ b/app/src/test/java/cash/z/ecc/android/ScratchPad.kt @@ -1,16 +1,17 @@ package cash.z.ecc.android import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* +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.Assert.assertTrue import org.junit.Test -import kotlin.math.round -import kotlin.math.roundToInt class ScratchPad { - val t get() = System.currentTimeMillis() + val t get() = System.currentTimeMillis() var t0 = 0L val Δt get() = t - t0 @@ -23,10 +24,10 @@ class ScratchPad { t0 = t started = true } - println("$Δt\temitting $it"); + 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")} + 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 { @@ -50,5 +51,4 @@ class ScratchPad { println("got $it") } } - -} \ No newline at end of file +} diff --git a/app/src/test/java/cash/z/ecc/android/SendViewModelTest.kt b/app/src/test/java/cash/z/ecc/android/SendViewModelTest.kt index e835ca3..ab692e6 100644 --- a/app/src/test/java/cash/z/ecc/android/SendViewModelTest.kt +++ b/app/src/test/java/cash/z/ecc/android/SendViewModelTest.kt @@ -2,9 +2,13 @@ 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.* +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.* +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.test.setMain @@ -92,7 +96,6 @@ class SendViewModelTest { verify(feedback).report(sendViewModel.metrics.values.first()) } - @Test fun testUpdateMetrics_mined() { assertEquals(true, minedTx.isMined()) @@ -106,5 +109,4 @@ class SendViewModelTest { // Thread.sleep(100) // assertEquals(0, sendViewModel.metrics.size) } - -} \ No newline at end of file +} diff --git a/build.gradle b/build.gradle index 4d15268..4489785 100644 --- a/build.gradle +++ b/build.gradle @@ -4,22 +4,19 @@ buildscript { repositories { google() jcenter() - maven { - url 'https://maven.fabric.io/public' - } } dependencies { classpath 'com.android.tools.build:gradle:4.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}" - classpath 'io.fabric.tools:gradle:1.31.2' classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.7.5' + classpath 'com.google.gms:google-services:4.3.5' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.2' classpath "androidx.navigation:navigation-safe-args-gradle-plugin:${Deps.navigationVersion}" } } allprojects { repositories { -// mavenLocal() google() mavenCentral() jcenter() @@ -32,4 +29,3 @@ task clean(type: Delete) { } defaultTasks 'clean', 'installZcashmainnetRelease' - diff --git a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt index 887a552..629a64a 100644 --- a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt +++ b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt @@ -6,12 +6,10 @@ object Deps { const val kotlinVersion = "1.4.32" const val navigationVersion = "2.3.0" - const val compileSdkVersion = 29 - const val buildToolsVersion = "29.0.2" + const val compileSdkVersion = 30 + const val buildToolsVersion = "30.0.3" const val minSdkVersion = 21 - const val targetSdkVersion = 29 - const val versionName = "1.0.0-alpha69" - const val versionCode = 1_00_00_169 // 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 targetSdkVersion = 30 const val packageName = "cash.z.ecc.android" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 81b2834..8db9ed6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Apr 02 00:54:33 EDT 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/ktlint.gradle b/ktlint.gradle new file mode 100644 index 0000000..c9a346a --- /dev/null +++ b/ktlint.gradle @@ -0,0 +1,21 @@ +configurations { + ktlint +} + +dependencies { + ktlint "com.pinterest:ktlint:0.41.0" +} + +task ktlint(type: org.gradle.api.tasks.JavaExec, group: "verification") { + description = "Verifying Kotlin code style.." + classpath = configurations.ktlint + main = "com.pinterest.ktlint.Main" + args "-F", "src/**/*.kt", "--editorconfig=${rootProject.file(".editorconfig")}" +} + +task ktlintFormat(type: org.gradle.api.tasks.JavaExec, group: "formatting") { + description = "Format Kotlin code style deviations." + classpath = configurations.ktlint + main = "com.pinterest.ktlint.Main" + args "-F", "src/**/*.kt", "--editorconfig=${rootProject.file(".editorconfig")}" +}