* 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 <kevin.gorham@electriccoin.co>
This commit is contained in:
parent
3a45ac5db2
commit
695bffab9a
|
@ -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
|
|
@ -184,4 +184,8 @@ dependencies {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultTasks 'clean', 'assembleZcashmainnetRelease'
|
defaultTasks 'clean', 'assembleZcashmainnetRelease'
|
||||||
|
|
||||||
|
apply from: "$rootDir/ktlint.gradle"
|
||||||
|
preBuild.dependsOn('ktlintFormat')
|
||||||
|
preBuild.dependsOn('ktlint')
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package cash.z.ecc.android
|
package cash.z.ecc.android
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
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 cash.z.ecc.android.ui.util.MemoUtil
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.Parameterized
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
//@RunWith(Parameterized::class)
|
// @RunWith(Parameterized::class)
|
||||||
class MemoTest(val input: String, val output: String) {
|
class MemoTest(val input: String, val output: String) {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -42,4 +42,4 @@ class MemoTest(val input: String, val output: String) {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ class ConversionsTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -73,7 +72,6 @@ class ConversionsTest {
|
||||||
Assert.assertEquals(1000, result.longValueExact())
|
Assert.assertEquals(1000, result.longValueExact())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testToBigDecimal_thousandCommaWithDecimal() {
|
fun testToBigDecimal_thousandCommaWithDecimal() {
|
||||||
val input = "1,000.00"
|
val input = "1,000.00"
|
||||||
|
@ -109,12 +107,6 @@ class ConversionsTest {
|
||||||
Assert.assertEquals(1, result.longValueExact())
|
Assert.assertEquals(1, result.longValueExact())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testToZecString_full() {
|
fun testToZecString_full() {
|
||||||
val input = 112_341_123L
|
val input = 112_341_123L
|
||||||
|
@ -149,4 +141,4 @@ class ConversionsTest {
|
||||||
val result = WalletZecFormmatter.toZecStringFull(input)
|
val result = WalletZecFormmatter.toZecStringFull(input)
|
||||||
Assert.assertEquals("1.12350004", result)
|
Assert.assertEquals("1.12350004", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import cash.z.ecc.android.lockbox.LockBox
|
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.Initializer
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
|
@ -24,7 +23,7 @@ class IntegrationTest {
|
||||||
private val mnemonics = Mnemonics()
|
private val mnemonics = Mnemonics()
|
||||||
private val phrase =
|
private val phrase =
|
||||||
"human pulse approve subway climb stairs mind gentle raccoon warfare fog roast sponsor" +
|
"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
|
@Before
|
||||||
fun start() {
|
fun start() {
|
||||||
|
@ -37,7 +36,7 @@ class IntegrationTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Generated incorrect BIP-39 seed!",
|
"Generated incorrect BIP-39 seed!",
|
||||||
"f4e3d38d9c244da7d0407e19a93c80429614ee82dcf62c141235751c9f1228905d12a1f275f" +
|
"f4e3d38d9c244da7d0407e19a93c80429614ee82dcf62c141235751c9f1228905d12a1f275f" +
|
||||||
"5c22f6fb7fcd9e0a97f1676e0eec53fdeeeafe8ce8aa39639b9fe",
|
"5c22f6fb7fcd9e0a97f1676e0eec53fdeeeafe8ce8aa39639b9fe",
|
||||||
seed.toHex()
|
seed.toHex()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -72,10 +71,10 @@ class IntegrationTest {
|
||||||
acceptedSize--
|
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(
|
assertTrue(
|
||||||
"LockBox does not support the maximum length seed phrase." +
|
"LockBox does not support the maximum length seed phrase." +
|
||||||
" Expected: $maxSeedPhraseLength but was: $acceptedSize",
|
" Expected: $maxSeedPhraseLength but was: $acceptedSize",
|
||||||
acceptedSize > maxSeedPhraseLength
|
acceptedSize > maxSeedPhraseLength
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +93,6 @@ class IntegrationTest {
|
||||||
initializer.erase()
|
initializer.erase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun ByteArray.toHex(): String {
|
private fun ByteArray.toHex(): String {
|
||||||
val sb = StringBuilder(size * 2)
|
val sb = StringBuilder(size * 2)
|
||||||
for (b in this)
|
for (b in this)
|
||||||
|
@ -117,4 +115,4 @@ class IntegrationTest {
|
||||||
.map { allowedChars.random() }
|
.map { allowedChars.random() }
|
||||||
.joinToString("")
|
.joinToString("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class LockBoxTest {
|
||||||
|
|
||||||
lockBox["longStr"] = sampleHex
|
lockBox["longStr"] = sampleHex
|
||||||
val actual: String = lockBox["longStr"]!!
|
val actual: String = lockBox["longStr"]!!
|
||||||
if(sampleHex == actual) successCount++
|
if (sampleHex == actual) successCount++
|
||||||
lockBox.clear()
|
lockBox.clear()
|
||||||
}
|
}
|
||||||
assertEquals(iterations, successCount)
|
assertEquals(iterations, successCount)
|
||||||
|
@ -43,7 +43,7 @@ class LockBoxTest {
|
||||||
|
|
||||||
lockBox["shortStr"] = sampleHex
|
lockBox["shortStr"] = sampleHex
|
||||||
val actual: String = lockBox["shortStr"]!!
|
val actual: String = lockBox["shortStr"]!!
|
||||||
if(sampleHex == actual) successCount++
|
if (sampleHex == actual) successCount++
|
||||||
lockBox.clear()
|
lockBox.clear()
|
||||||
}
|
}
|
||||||
assertEquals(iterations, successCount)
|
assertEquals(iterations, successCount)
|
||||||
|
@ -57,7 +57,7 @@ class LockBoxTest {
|
||||||
|
|
||||||
lockBox["giantStr"] = sampleHex
|
lockBox["giantStr"] = sampleHex
|
||||||
val actual: String = lockBox["giantStr"]!!
|
val actual: String = lockBox["giantStr"]!!
|
||||||
if(sampleHex == actual) successCount++
|
if (sampleHex == actual) successCount++
|
||||||
lockBox.clear()
|
lockBox.clear()
|
||||||
}
|
}
|
||||||
assertEquals(iterations, successCount)
|
assertEquals(iterations, successCount)
|
||||||
|
|
|
@ -11,10 +11,13 @@ import cash.z.ecc.android.ext.tryWithWarning
|
||||||
import cash.z.ecc.android.feedback.FeedbackCoordinator
|
import cash.z.ecc.android.feedback.FeedbackCoordinator
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
import cash.z.ecc.android.sdk.ext.twig
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
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
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
class ZcashWalletApp : Application(), CameraXConfig.Provider {
|
class ZcashWalletApp : Application(), CameraXConfig.Provider {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -102,10 +105,9 @@ class ZcashWalletApp : Application(), CameraXConfig.Provider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun ZcashWalletApp.isEmulator(): Boolean {
|
fun ZcashWalletApp.isEmulator(): Boolean {
|
||||||
val goldfish = Build.HARDWARE.contains("goldfish");
|
val goldfish = Build.HARDWARE.contains("goldfish")
|
||||||
val emu = (System.getProperty("ro.kernel.qemu", "")?.length ?: 0) > 0;
|
val emu = (System.getProperty("ro.kernel.qemu", "")?.length ?: 0) > 0
|
||||||
val sdk = Build.MODEL.toLowerCase().contains("sdk")
|
val sdk = Build.MODEL.toLowerCase().contains("sdk")
|
||||||
return goldfish || emu || sdk;
|
return goldfish || emu || sdk
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,4 +11,4 @@ import kotlin.reflect.KClass
|
||||||
)
|
)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@MapKey
|
@MapKey
|
||||||
annotation class ViewModelKey(val value: KClass<out ViewModel>)
|
annotation class ViewModelKey(val value: KClass<out ViewModel>)
|
||||||
|
|
|
@ -20,4 +20,4 @@ interface AppComponent {
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(@BindsInstance application: ZcashWalletApp): AppComponent
|
fun create(@BindsInstance application: ZcashWalletApp): AppComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,4 +17,4 @@ interface InitializerSubcomponent {
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(@BindsInstance config: Initializer.Config): InitializerSubcomponent
|
fun create(@BindsInstance config: Initializer.Config): InitializerSubcomponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,4 @@ interface MainActivitySubcomponent {
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(@BindsInstance activity: FragmentActivity): MainActivitySubcomponent
|
fun create(@BindsInstance activity: FragmentActivity): MainActivitySubcomponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,4 @@ interface SynchronizerSubcomponent {
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(@BindsInstance initializer: Initializer): SynchronizerSubcomponent
|
fun create(@BindsInstance initializer: Initializer): SynchronizerSubcomponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,14 @@ import android.content.Context
|
||||||
import cash.z.ecc.android.ZcashWalletApp
|
import cash.z.ecc.android.ZcashWalletApp
|
||||||
import cash.z.ecc.android.di.component.MainActivitySubcomponent
|
import cash.z.ecc.android.di.component.MainActivitySubcomponent
|
||||||
import cash.z.ecc.android.ext.Const
|
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.lockbox.LockBox
|
||||||
import cash.z.ecc.android.sdk.ext.SilentTwig
|
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.sdk.ext.Twig
|
||||||
import cash.z.ecc.android.ui.util.DebugFileTwig
|
import cash.z.ecc.android.ui.util.DebugFileTwig
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
@ -35,12 +39,10 @@ class AppModule {
|
||||||
return LockBox(appContext)
|
return LockBox(appContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Feedback
|
// Feedback
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideFeedback(): Feedback = Feedback()
|
fun provideFeedback(): Feedback = Feedback()
|
||||||
|
@ -59,7 +61,6 @@ class AppModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Default Feedback Observer Set
|
// Default Feedback Observer Set
|
||||||
//
|
//
|
||||||
|
|
|
@ -8,6 +8,4 @@ import dagger.Module
|
||||||
includes = [ViewModelsActivityModule::class],
|
includes = [ViewModelsActivityModule::class],
|
||||||
subcomponents = [SynchronizerSubcomponent::class, InitializerSubcomponent::class]
|
subcomponents = [SynchronizerSubcomponent::class, InitializerSubcomponent::class]
|
||||||
)
|
)
|
||||||
class MainActivityModule {
|
class MainActivityModule
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,5 +19,4 @@ class SynchronizerModule {
|
||||||
fun provideSynchronizer(appContext: Context, initializer: Initializer): Synchronizer {
|
fun provideSynchronizer(appContext: Context, initializer: Initializer): Synchronizer {
|
||||||
return Synchronizer(initializer)
|
return Synchronizer(initializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package cash.z.ecc.android.di.module
|
package cash.z.ecc.android.di.module
|
||||||
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import cash.z.ecc.android.di.annotation.ActivityScope
|
import cash.z.ecc.android.di.annotation.ActivityScope
|
||||||
|
@ -26,7 +25,6 @@ abstract class ViewModelsActivityModule {
|
||||||
@ViewModelKey(WalletSetupViewModel::class)
|
@ViewModelKey(WalletSetupViewModel::class)
|
||||||
abstract fun bindWalletSetupViewModel(implementation: WalletSetupViewModel): ViewModel
|
abstract fun bindWalletSetupViewModel(implementation: WalletSetupViewModel): ViewModel
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for view models that are created until before the Synchronizer exists. This is a
|
* 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
|
* 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)
|
@Named(Const.Name.BEFORE_SYNCHRONIZER)
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
|
abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package cash.z.ecc.android.di.module
|
package cash.z.ecc.android.di.module
|
||||||
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import cash.z.ecc.android.di.annotation.SynchronizerScope
|
import cash.z.ecc.android.di.annotation.SynchronizerScope
|
||||||
|
@ -65,7 +64,7 @@ abstract class ViewModelsSynchronizerModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(SettingsViewModel::class)
|
@ViewModelKey(SettingsViewModel::class)
|
||||||
abstract fun bindSettingsViewModel(implementation: SettingsViewModel): ViewModel
|
abstract fun bindSettingsViewModel(implementation: SettingsViewModel): ViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for view models that are not created until the Synchronizer exists. Only VMs that
|
* 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
|
* 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)
|
@Named(Const.Name.SYNCHRONIZER)
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
|
abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import cash.z.ecc.android.ui.MainActivity
|
import cash.z.ecc.android.ui.MainActivity
|
||||||
import cash.z.ecc.android.ui.base.BaseFragment
|
import cash.z.ecc.android.ui.base.BaseFragment
|
||||||
|
|
||||||
|
|
||||||
inline fun <reified VM : ViewModel> BaseFragment<*>.viewModel() = object : Lazy<VM> {
|
inline fun <reified VM : ViewModel> BaseFragment<*>.viewModel() = object : Lazy<VM> {
|
||||||
val cached: VM? = null
|
val cached: VM? = null
|
||||||
override fun isInitialized(): Boolean = cached != null
|
override fun isInitialized(): Boolean = cached != null
|
||||||
|
@ -58,4 +57,4 @@ inline fun <reified VM : ViewModel> MainActivity.activityViewModel() = object :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@ class ViewModelFactory @Inject constructor(
|
||||||
modelClass.isAssignableFrom(it.key)
|
modelClass.isAssignableFrom(it.key)
|
||||||
}?.value ?: throw IllegalArgumentException(
|
}?.value ?: throw IllegalArgumentException(
|
||||||
"No map entry found for ${modelClass.canonicalName}. Verify that this ViewModel has" +
|
"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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return creator.get() as T
|
return creator.get() as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.ext.ConversionsUniform.SHORT_FORMATTER
|
||||||
import cash.z.ecc.android.sdk.ext.Conversions
|
import cash.z.ecc.android.sdk.ext.Conversions
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
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.BigDecimal
|
||||||
import java.math.MathContext
|
import java.math.MathContext
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do the necessary conversions in one place
|
* Do the necessary conversions in one place
|
||||||
|
@ -75,5 +72,4 @@ object WalletZecFormmatter {
|
||||||
BigDecimal(this ?: 0L, MathContext.DECIMAL128)
|
BigDecimal(this ?: 0L, MathContext.DECIMAL128)
|
||||||
.divide(ConversionsUniform.ONE_ZEC_IN_ZATOSHI)
|
.divide(ConversionsUniform.ONE_ZEC_IN_ZATOSHI)
|
||||||
.setScale(LONG_SCALE, ConversionsUniform.roundingMode)
|
.setScale(LONG_SCALE, ConversionsUniform.roundingMode)
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import androidx.core.content.getSystemService
|
||||||
import cash.z.ecc.android.R
|
import cash.z.ecc.android.R
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
||||||
|
|
||||||
fun Context.showClearDataConfirmation(onDismiss: () -> Unit = {}, onCancel: () -> Unit = {}): Dialog {
|
fun Context.showClearDataConfirmation(onDismiss: () -> Unit = {}, onCancel: () -> Unit = {}): Dialog {
|
||||||
return MaterialAlertDialogBuilder(this)
|
return MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.dialog_nuke_wallet_title)
|
.setTitle(R.string.dialog_nuke_wallet_title)
|
||||||
|
@ -39,10 +38,13 @@ fun Context.showUninitializedError(error: Throwable? = null, onDismiss: () -> Un
|
||||||
if (error != null) throw error
|
if (error != null) throw error
|
||||||
}
|
}
|
||||||
.setNegativeButton(getString(R.string.dialog_error_uninitialized_button_negative)) { dialog, _ ->
|
.setNegativeButton(getString(R.string.dialog_error_uninitialized_button_negative)) { dialog, _ ->
|
||||||
showClearDataConfirmation(onDismiss, onCancel = {
|
showClearDataConfirmation(
|
||||||
// do not let the user back into the app because we cannot recover from this case
|
onDismiss,
|
||||||
showUninitializedError(error, onDismiss)
|
onCancel = {
|
||||||
})
|
// do not let the user back into the app because we cannot recover from this case
|
||||||
|
showUninitializedError(error, onDismiss)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
@ -187,4 +189,4 @@ fun Context.showSharedLibraryCriticalError(e: Throwable): Dialog = showCriticalM
|
||||||
titleResId = R.string.dialog_error_critical_link_title,
|
titleResId = R.string.dialog_error_critical_link_title,
|
||||||
messageResId = R.string.dialog_error_critical_link_message,
|
messageResId = R.string.dialog_error_critical_link_message,
|
||||||
onDismiss = { throw e }
|
onDismiss = { throw e }
|
||||||
)
|
)
|
||||||
|
|
|
@ -55,7 +55,7 @@ inline fun EditText.limitDecimalPlaces(max: Int) {
|
||||||
|
|
||||||
// Restore the cursor position
|
// Restore the cursor position
|
||||||
if (oldText != textStr) {
|
if (oldText != textStr) {
|
||||||
val cursorPosition = editText.selectionEnd;
|
val cursorPosition = editText.selectionEnd
|
||||||
editText.setText(textStr)
|
editText.setText(textStr)
|
||||||
editText.setSelection(
|
editText.setSelection(
|
||||||
(cursorPosition - (oldText.length - textStr.length)).coerceIn(
|
(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) {
|
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun TextView.convertZecToZatoshi(): Long? {
|
fun TextView.convertZecToZatoshi(): Long? {
|
||||||
return try {
|
return try {
|
||||||
text.toString().safelyConvertToBigDecimal()?.convertZecToZatoshi() ?: null
|
text.toString().safelyConvertToBigDecimal()?.convertZecToZatoshi() ?: null
|
||||||
|
|
|
@ -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.ext.twig
|
import cash.z.ecc.android.sdk.ext.twig
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
import cash.z.ecc.android.sdk.type.WalletBalance
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,4 +67,4 @@ inline fun Context.locale(): Locale {
|
||||||
inline fun <reified T> Twig.find(): T? {
|
inline fun <reified T> Twig.find(): T? {
|
||||||
return if (Bush.trunk::class.java.isAssignableFrom(T::class.java)) Bush.trunk as T
|
return if (Bush.trunk::class.java.isAssignableFrom(T::class.java)) Bush.trunk as T
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,16 +32,15 @@ internal inline fun @receiver:StringRes Int.toAppStringFormatted(vararg formatAr
|
||||||
return ZcashWalletApp.instance.getString(this, *formatArgs)
|
return ZcashWalletApp.instance.getString(this, *formatArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grab an integer from the application resources
|
* Grab an integer from the application resources
|
||||||
*/
|
*/
|
||||||
internal inline fun @receiver:IntegerRes Int.toAppInt(): Int {
|
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 Float.toPx() = this * Resources.getSystem().displayMetrics.density
|
||||||
|
|
||||||
fun Int.toPx() = (this * Resources.getSystem().displayMetrics.density + 0.5f).toInt()
|
fun Int.toPx() = (this * Resources.getSystem().displayMetrics.density + 0.5f).toInt()
|
||||||
|
|
||||||
fun Int.toDp() = (this / Resources.getSystem().displayMetrics.density + 0.5f).toInt()
|
fun Int.toDp() = (this / Resources.getSystem().displayMetrics.density + 0.5f).toInt()
|
||||||
|
|
|
@ -7,8 +7,8 @@ import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
fun <T: View> LifecycleOwner.onClick(view: T, throttle: Long = 250L, block: (T) -> Unit) {
|
fun <T : View> LifecycleOwner.onClick(view: T, throttle: Long = 250L, block: (T) -> Unit) {
|
||||||
view.clicks().debounce(throttle).onEach {
|
view.clicks().debounce(throttle).onEach {
|
||||||
block(view)
|
block(view)
|
||||||
}.launchIn(this.lifecycleScope)
|
}.launchIn(this.lifecycleScope)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,4 +9,4 @@ fun CharSequence.toColoredSpan(colorResId: Int, coloredPortion: String): CharSeq
|
||||||
val start = this@toColoredSpan.indexOf(coloredPortion)
|
val start = this@toColoredSpan.indexOf(coloredPortion)
|
||||||
setSpan(ForegroundColorSpan(colorResId.toAppColor()), start, start + coloredPortion.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
setSpan(ForegroundColorSpan(colorResId.toAppColor()), start, start + coloredPortion.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package cash.z.ecc.android.ext
|
package cash.z.ecc.android.ext
|
||||||
|
|
||||||
import android.view.View
|
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 cash.z.ecc.android.ui.MainActivity
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
|
@ -40,8 +42,10 @@ fun View.onClickNavTo(navResId: Int, block: (() -> Any) = {}) {
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
block()
|
block()
|
||||||
(context as? MainActivity)?.safeNavigate(navResId)
|
(context as? MainActivity)?.safeNavigate(navResId)
|
||||||
?: throw IllegalStateException("Cannot navigate from this activity. " +
|
?: throw IllegalStateException(
|
||||||
"Expected MainActivity but found ${context.javaClass.simpleName}")
|
"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()
|
(context as? MainActivity)?.navController?.navigateUp()
|
||||||
?: throw IllegalStateException(
|
?: throw IllegalStateException(
|
||||||
"Cannot navigate from this activity. " +
|
"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()
|
(context as? MainActivity)?.navController?.popBackStack()
|
||||||
?: throw IllegalStateException(
|
?: throw IllegalStateException(
|
||||||
"Cannot navigate from this activity. " +
|
"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<View> {
|
||||||
awaitClose {
|
awaitClose {
|
||||||
setOnClickListener(null)
|
setOnClickListener(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ class FeedbackBugsnag : FeedbackCoordinator.FeedbackObserver {
|
||||||
private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) :
|
private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) :
|
||||||
Throwable(reorgMesssage)
|
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"
|
"Non-fatal error while sending transaction. code: $errorCode message: $errorMessage"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,4 @@ class FeedbackConsole : FeedbackCoordinator.FeedbackObserver {
|
||||||
private fun log(message: String) {
|
private fun log(message: String) {
|
||||||
Log.d("@TWIG", message)
|
Log.d("@TWIG", message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,4 +34,4 @@ class FeedbackFile(fileName: String = "user_log.txt") :
|
||||||
it.writeUtf8("${format.format(System.currentTimeMillis())}|\t$message\n")
|
it.writeUtf8("${format.format(System.currentTimeMillis())}|\t$message\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,5 +29,4 @@ class FeedbackMixpanel : FeedbackCoordinator.FeedbackObserver {
|
||||||
private fun track(eventName: String, properties: Map<String, Any>) {
|
private fun track(eventName: String, properties: Map<String, Any>) {
|
||||||
mixpanel.trackMap(eventName, properties)
|
mixpanel.trackMap(eventName, properties)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -22,11 +22,13 @@ object Report {
|
||||||
// Errors
|
// Errors
|
||||||
abstract class Error(stepName: String, step: Int, val errorCode: Int?, val errorMessage: String?, vararg properties: Pair<String, Any>) : Send("error.$stepName", step, "isError" to true, *properties)
|
abstract class Error(stepName: String, step: Int, val errorCode: Int?, val errorMessage: String?, vararg properties: Pair<String, Any>) : Send("error.$stepName", step, "isError" to true, *properties)
|
||||||
object ErrorNotFound : Error("notfound", 51, null, "Key not found")
|
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),
|
"errorCode" to (errorCode ?: -1),
|
||||||
"errorMessage" to (errorMessage ?: "None")
|
"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),
|
"errorCode" to (errorCode ?: -1),
|
||||||
"errorMessage" to (errorMessage ?: "None")
|
"errorMessage" to (errorMessage ?: "None")
|
||||||
)
|
)
|
||||||
|
@ -82,7 +84,7 @@ object Report {
|
||||||
*properties
|
*properties
|
||||||
) {
|
) {
|
||||||
override val key = "performance.$name"
|
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)
|
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
|
*properties
|
||||||
) {
|
) {
|
||||||
override val key = "issue.$name"
|
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
|
// Issues with sending worth monitoring
|
||||||
object SelfSend : Issue("self.send")
|
object SelfSend : Issue("self.send")
|
||||||
|
@ -241,6 +243,5 @@ class LaunchMetric private constructor(private val metric: Feedback.TimeMetric)
|
||||||
override fun toString(): String = metric.toString()
|
override fun toString(): String = metric.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline fun <T> Feedback.measure(type: Report.MetricType, block: () -> T): T =
|
inline fun <T> Feedback.measure(type: Report.MetricType, block: () -> T): T =
|
||||||
this.measure(type.key, type.description, block)
|
this.measure(type.key, type.description, block)
|
||||||
|
|
|
@ -85,7 +85,6 @@ import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -224,9 +223,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
twig(
|
twig(
|
||||||
"WARNING: during callback, did not navigate to destination: R.id.${
|
"WARNING: during callback, did not navigate to destination: R.id.${
|
||||||
resources.getResourceEntryName(
|
resources.getResourceEntryName(
|
||||||
destination
|
destination
|
||||||
)
|
)
|
||||||
} due to: $t"
|
} due to: $t"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -237,9 +236,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
twig(
|
twig(
|
||||||
"WARNING: did not immediately navigate to destination: R.id.${
|
"WARNING: did not immediately navigate to destination: R.id.${
|
||||||
resources.getResourceEntryName(
|
resources.getResourceEntryName(
|
||||||
destination
|
destination
|
||||||
)
|
)
|
||||||
} due to: $t"
|
} due to: $t"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -310,7 +309,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun authenticate(description: String, title: String = getString(R.string.biometric_prompt_title), block: () -> Unit) {
|
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) {
|
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}")
|
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()
|
block()
|
||||||
|
@ -348,7 +347,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
ERROR_TIMEOUT -> doNothing("Oops. It timed out.")
|
ERROR_TIMEOUT -> doNothing("Oops. It timed out.")
|
||||||
ERROR_UNABLE_TO_PROCESS -> doNothing(".")
|
ERROR_UNABLE_TO_PROCESS -> doNothing(".")
|
||||||
ERROR_VENDOR -> doNothing("We got some weird error and you should report this.")
|
ERROR_VENDOR -> doNothing("We got some weird error and you should report this.")
|
||||||
else -> {
|
else -> {
|
||||||
twig("Warning: unrecognized authentication error $errorCode")
|
twig("Warning: unrecognized authentication error $errorCode")
|
||||||
doNothing("Authentication failed with error code $errorCode")
|
doNothing("Authentication failed with error code $errorCode")
|
||||||
}
|
}
|
||||||
|
@ -417,15 +416,18 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun preventBackPress(fragment: Fragment) {
|
fun preventBackPress(fragment: Fragment) {
|
||||||
onFragmentBackPressed(fragment){}
|
onFragmentBackPressed(fragment) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onFragmentBackPressed(fragment: Fragment, block: () -> Unit) {
|
fun onFragmentBackPressed(fragment: Fragment, block: () -> Unit) {
|
||||||
onBackPressedDispatcher.addCallback(fragment, object : OnBackPressedCallback(true) {
|
onBackPressedDispatcher.addCallback(
|
||||||
override fun handleOnBackPressed() {
|
fragment,
|
||||||
block()
|
object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
block()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showMessage(message: String, linger: Boolean = false) {
|
private fun showMessage(message: String, linger: Boolean = false) {
|
||||||
|
@ -440,26 +442,26 @@ class MainActivity : AppCompatActivity() {
|
||||||
.make(view, "$message", Snackbar.LENGTH_INDEFINITE)
|
.make(view, "$message", Snackbar.LENGTH_INDEFINITE)
|
||||||
.setAction(action) { /*auto-close*/ }
|
.setAction(action) { /*auto-close*/ }
|
||||||
|
|
||||||
val snackBarView = snacks.view as ViewGroup
|
val snackBarView = snacks.view as ViewGroup
|
||||||
val navigationBarHeight = resources.getDimensionPixelSize(
|
val navigationBarHeight = resources.getDimensionPixelSize(
|
||||||
resources.getIdentifier(
|
resources.getIdentifier(
|
||||||
"navigation_bar_height",
|
"navigation_bar_height",
|
||||||
"dimen",
|
"dimen",
|
||||||
"android"
|
"android"
|
||||||
)
|
|
||||||
)
|
|
||||||
val params = snackBarView.getChildAt(0).layoutParams as ViewGroup.MarginLayoutParams
|
|
||||||
params.setMargins(
|
|
||||||
params.leftMargin,
|
|
||||||
params.topMargin,
|
|
||||||
params.rightMargin,
|
|
||||||
navigationBarHeight
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
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
|
snacks
|
||||||
} else {
|
} else {
|
||||||
snackbar!!.setText(message).setAction(action) {/*auto-close*/}
|
snackbar!!.setText(message).setAction(action) { /*auto-close*/ }
|
||||||
}.also {
|
}.also {
|
||||||
if (!it.isShownOrQueued) it.show()
|
if (!it.isShownOrQueued) it.show()
|
||||||
}
|
}
|
||||||
|
@ -534,7 +536,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (dialog == null && !ignoreScanFailure) throttle("scanFailure", 20_000L) {
|
if (dialog == null && !ignoreScanFailure) throttle("scanFailure", 20_000L) {
|
||||||
notified = true
|
notified = true
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
dialog = showScanFailure(error,
|
dialog = showScanFailure(
|
||||||
|
error,
|
||||||
onCancel = { dialog = null },
|
onCancel = { dialog = null },
|
||||||
onDismiss = { dialog = null }
|
onDismiss = { dialog = null }
|
||||||
)
|
)
|
||||||
|
@ -564,7 +567,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
feedback.report(Reorg(errorHeight, rewindHeight))
|
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)
|
// 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<String, () -> Any>()
|
private val throttles = mutableMapOf<String, () -> 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
|
// after doing the work, check back in later and if another request came in, throttle it, otherwise exit
|
||||||
throttles[key] = noWork
|
throttles[key] = noWork
|
||||||
findViewById<View>(android.R.id.content).postDelayed({
|
findViewById<View>(android.R.id.content).postDelayed(
|
||||||
throttles[key]?.let { pendingWork ->
|
{
|
||||||
throttles.remove(key)
|
throttles[key]?.let { pendingWork ->
|
||||||
if (pendingWork !== noWork) throttle(key, delay, pendingWork)
|
throttles.remove(key)
|
||||||
}
|
if (pendingWork !== noWork) throttle(key, delay, pendingWork)
|
||||||
}, delay)
|
}
|
||||||
|
},
|
||||||
|
delay
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Memo functions that might possibly get moved to MemoUtils */
|
/* Memo functions that might possibly get moved to MemoUtils */
|
||||||
|
|
||||||
// private val addressRegex = """zs\d\w{65,}""".toRegex()
|
|
||||||
|
|
||||||
suspend fun getSender(transaction: ConfirmedTransaction?): String {
|
suspend fun getSender(transaction: ConfirmedTransaction?): String {
|
||||||
if (transaction == null) return getString(R.string.unknown)
|
if (transaction == null) return getString(R.string.unknown)
|
||||||
return MemoUtil.findAddressInMemo(transaction, ::isValidAddress)?.toAbbreviatedAddress() ?: 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")
|
twig("Warning: failed to open browser due to $t")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,4 +34,4 @@ class MainViewModel @Inject constructor() : ViewModel() {
|
||||||
twig("MainViewModel.setSyncReady: $isReady")
|
twig("MainViewModel.setSyncReady: $isReady")
|
||||||
_syncReady.value = isReady
|
_syncReady.value = isReady
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,15 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import cash.z.ecc.android.feedback.Report
|
import cash.z.ecc.android.feedback.Report
|
||||||
import cash.z.ecc.android.ui.MainActivity
|
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.filter
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
abstract class BaseFragment<T : ViewBinding> : Fragment() {
|
abstract class BaseFragment<T : ViewBinding> : Fragment() {
|
||||||
val mainActivity: MainActivity? get() = activity as MainActivity?
|
val mainActivity: MainActivity? get() = activity as MainActivity?
|
||||||
|
@ -79,4 +84,4 @@ abstract class BaseFragment<T : ViewBinding> : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,12 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import cash.z.ecc.android.R
|
import cash.z.ecc.android.R
|
||||||
import cash.z.ecc.android.databinding.FragmentHistoryBinding
|
import cash.z.ecc.android.databinding.FragmentHistoryBinding
|
||||||
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
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
|
||||||
import cash.z.ecc.android.feedback.Report.Tap.HISTORY_BACK
|
import cash.z.ecc.android.feedback.Report.Tap.HISTORY_BACK
|
||||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
|
@ -21,7 +26,6 @@ import cash.z.ecc.android.sdk.type.WalletBalance
|
||||||
import cash.z.ecc.android.ui.base.BaseFragment
|
import cash.z.ecc.android.ui.base.BaseFragment
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class HistoryFragment : BaseFragment<FragmentHistoryBinding>() {
|
class HistoryFragment : BaseFragment<FragmentHistoryBinding>() {
|
||||||
override val screen = Report.Screen.HISTORY
|
override val screen = Report.Screen.HISTORY
|
||||||
|
|
||||||
|
@ -34,7 +38,6 @@ class HistoryFragment : BaseFragment<FragmentHistoryBinding>() {
|
||||||
override fun inflate(inflater: LayoutInflater): FragmentHistoryBinding =
|
override fun inflate(inflater: LayoutInflater): FragmentHistoryBinding =
|
||||||
FragmentHistoryBinding.inflate(inflater)
|
FragmentHistoryBinding.inflate(inflater)
|
||||||
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
twig("HistoryFragment.onViewCreated")
|
twig("HistoryFragment.onViewCreated")
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
@ -61,7 +64,7 @@ class HistoryFragment : BaseFragment<FragmentHistoryBinding>() {
|
||||||
goneIf(change <= 0L)
|
goneIf(change <= 0L)
|
||||||
val changeString = WalletZecFormmatter.toZecStringFull(change)
|
val changeString = WalletZecFormmatter.toZecStringFull(change)
|
||||||
val expecting = R.string.home_banner_expecting.toAppString(true)
|
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<FragmentHistoryBinding>() {
|
||||||
private fun scrollToTop() {
|
private fun scrollToTop() {
|
||||||
twig("scrolling to the top")
|
twig("scrolling to the top")
|
||||||
binding.recyclerTransactions.apply {
|
binding.recyclerTransactions.apply {
|
||||||
postDelayed({
|
postDelayed(
|
||||||
smoothScrollToPosition(0)
|
{
|
||||||
}, 5L)
|
smoothScrollToPosition(0)
|
||||||
|
},
|
||||||
|
5L
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,4 +106,4 @@ class HistoryFragment : BaseFragment<FragmentHistoryBinding>() {
|
||||||
fun onLastItemShown(item: ConfirmedTransaction, position: Int) {
|
fun onLastItemShown(item: ConfirmedTransaction, position: Int) {
|
||||||
binding.footerFade.alpha = position.toFloat() / (binding.recyclerTransactions.adapter?.itemCount ?: 1)
|
binding.footerFade.alpha = position.toFloat() / (binding.recyclerTransactions.adapter?.itemCount ?: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,6 @@ class HistoryViewModel @Inject constructor() : ViewModel() {
|
||||||
var txId: String? = null
|
var txId: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
private suspend fun ConfirmedTransaction?.toUiModel(latestHeight: Int? = null): UiModel = UiModel().apply {
|
private suspend fun ConfirmedTransaction?.toUiModel(latestHeight: Int? = null): UiModel = UiModel().apply {
|
||||||
this@toUiModel.let { tx ->
|
this@toUiModel.let { tx ->
|
||||||
txId = toTxId(tx?.rawTransactionId)
|
txId = toTxId(tx?.rawTransactionId)
|
||||||
|
@ -105,7 +104,8 @@ class HistoryViewModel @Inject constructor() : ViewModel() {
|
||||||
if (it.minedHeight > 0 && hasLatestHeight) {
|
if (it.minedHeight > 0 && hasLatestHeight) {
|
||||||
val confirmations = latestHeight!! - it.minedHeight + 1
|
val confirmations = latestHeight!! - it.minedHeight + 1
|
||||||
confirmation = if (confirmations >= 10) getString(R.string.transaction_status_confirmed) else "$confirmations ${getString(
|
confirmation = if (confirmations >= 10) getString(R.string.transaction_status_confirmed) else "$confirmations ${getString(
|
||||||
R.string.transaction_status_confirming)}"
|
R.string.transaction_status_confirming
|
||||||
|
)}"
|
||||||
} else {
|
} else {
|
||||||
if (!hasLatestHeight && isSufficientlyOld(tx)) {
|
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")
|
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 {
|
} else {
|
||||||
confirmation = getString(R.string.transaction_status_pending)
|
confirmation = getString(R.string.transaction_status_pending)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// val mainActivity = (context as MainActivity)
|
|
||||||
// inbound v. outbound values
|
|
||||||
when (isInbound) {
|
when (isInbound) {
|
||||||
true -> {
|
true -> {
|
||||||
topLabel = getString(R.string.transaction_story_inbound)
|
topLabel = getString(R.string.transaction_story_inbound)
|
||||||
|
@ -154,7 +151,7 @@ class HistoryViewModel @Inject constructor() : ViewModel() {
|
||||||
private fun toTxId(tx: ByteArray?): String? {
|
private fun toTxId(tx: ByteArray?): String? {
|
||||||
if (tx == null) return null
|
if (tx == null) return null
|
||||||
val sb = StringBuilder(tx.size * 2)
|
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]))
|
sb.append(String.format("%02x", tx[i]))
|
||||||
}
|
}
|
||||||
return sb.toString()
|
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
|
// 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.
|
// information suggests that the TX is confirmed. We can improve this, later.
|
||||||
private fun isSufficientlyOld(tx: ConfirmedTransaction): Boolean {
|
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
|
val delta = System.currentTimeMillis() / 1000L - tx.blockTimeInSeconds
|
||||||
return tx.minedHeight > synchronizer.network.saplingActivationHeight
|
return tx.minedHeight > synchronizer.network.saplingActivationHeight &&
|
||||||
&& delta < threshold
|
delta < threshold
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@ class TransactionAdapter<T : ConfirmedTransaction> :
|
||||||
override fun areItemsTheSame(
|
override fun areItemsTheSame(
|
||||||
oldItem: T,
|
oldItem: T,
|
||||||
newItem: T
|
newItem: T
|
||||||
) = oldItem.minedHeight == newItem.minedHeight && oldItem.noteId == newItem.noteId
|
) = 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
|
// 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.raw == null && newItem.raw == null) || (oldItem.raw != null && newItem.raw != null && oldItem.raw!!.contentEquals(newItem.raw!!)))
|
||||||
|
|
||||||
override fun areContentsTheSame(
|
override fun areContentsTheSame(
|
||||||
oldItem: T,
|
oldItem: T,
|
||||||
|
|
|
@ -4,32 +4,34 @@ import android.content.res.ColorStateList
|
||||||
import android.graphics.ColorMatrix
|
import android.graphics.ColorMatrix
|
||||||
import android.graphics.ColorMatrixColorFilter
|
import android.graphics.ColorMatrixColorFilter
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.format.DateUtils
|
|
||||||
import android.text.method.ScrollingMovementMethod
|
import android.text.method.ScrollingMovementMethod
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
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.R
|
||||||
import cash.z.ecc.android.ZcashWalletApp
|
|
||||||
import cash.z.ecc.android.databinding.FragmentTransactionBinding
|
import cash.z.ecc.android.databinding.FragmentTransactionBinding
|
||||||
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
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.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.toAbbreviatedAddress
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
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.base.BaseFragment
|
||||||
import cash.z.ecc.android.ui.history.HistoryViewModel.UiModel
|
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.collect
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
||||||
override val screen = Report.Screen.TRANSACTION
|
override val screen = Report.Screen.TRANSACTION
|
||||||
|
@ -53,7 +55,7 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
||||||
// sharedElementReturnTransition = ChangeBounds().apply { duration = 1500 }
|
// sharedElementReturnTransition = ChangeBounds().apply { duration = 1500 }
|
||||||
// enterTransition = Fade().apply {
|
// enterTransition = Fade().apply {
|
||||||
// duration = 1800
|
// duration = 1800
|
||||||
//// slideEdge = Gravity.END
|
// // slideEdge = Gravity.END
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +119,6 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
||||||
uiModel.toAddressLabel()?.let { subwaySpotAddress.visible(); subwayLabelAddress.visible(); subwayLabelAddress.text = it }
|
uiModel.toAddressLabel()?.let { subwaySpotAddress.visible(); subwayLabelAddress.visible(); subwayLabelAddress.text = it }
|
||||||
uiModel.toAddressClickListener()?.let { subwayLabelAddress.setOnClickListener(it) }
|
uiModel.toAddressClickListener()?.let { subwayLabelAddress.setOnClickListener(it) }
|
||||||
|
|
||||||
|
|
||||||
// TODO: remove logic from sections below and add more fields or extension functions to UiModel
|
// TODO: remove logic from sections below and add more fields or extension functions to UiModel
|
||||||
uiModel.confirmation?.let {
|
uiModel.confirmation?.let {
|
||||||
subwaySpotConfirmations.visible(); subwayLabelConfirmations.visible()
|
subwaySpotConfirmations.visible(); subwayLabelConfirmations.visible()
|
||||||
|
@ -162,7 +163,7 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
||||||
binding.subwaySpotMemoContent.rotation = 90.0f
|
binding.subwaySpotMemoContent.rotation = 90.0f
|
||||||
} else {
|
} else {
|
||||||
binding.subwayLabelMemo.setText(getString(R.string.transaction_with_memo))
|
binding.subwayLabelMemo.setText(getString(R.string.transaction_with_memo))
|
||||||
binding.subwayLabelMemo.scrollTo(0,0)
|
binding.subwayLabelMemo.scrollTo(0, 0)
|
||||||
binding.subwayLabelMemo.invalidate()
|
binding.subwayLabelMemo.invalidate()
|
||||||
twig("setting memo text to: with a memo")
|
twig("setting memo text to: with a memo")
|
||||||
binding.groupMemoIcon.visible()
|
binding.groupMemoIcon.visible()
|
||||||
|
@ -196,12 +197,4 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
||||||
it.toColoredSpan(R.color.tx_text_light_dimmed, if (address == null) it else prefix)
|
it.toColoredSpan(R.color.tx_text_light_dimmed, if (address == null) it else prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,7 @@ import android.graphics.drawable.Drawable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.ColorRes
|
|
||||||
import androidx.annotation.IntegerRes
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import cash.z.ecc.android.R
|
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.isShielded
|
||||||
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
|
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
|
||||||
import cash.z.ecc.android.ui.MainActivity
|
import cash.z.ecc.android.ui.MainActivity
|
||||||
import cash.z.ecc.android.ui.util.MemoUtil
|
|
||||||
import cash.z.ecc.android.ui.util.toUtf8Memo
|
import cash.z.ecc.android.ui.util.toUtf8Memo
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
private val indicator = itemView.findViewById<View>(R.id.indicator)
|
private val indicator = itemView.findViewById<View>(R.id.indicator)
|
||||||
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
|
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
|
||||||
private val topText = itemView.findViewById<TextView>(R.id.text_transaction_top)
|
private val topText = itemView.findViewById<TextView>(R.id.text_transaction_top)
|
||||||
|
@ -40,7 +37,7 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
val mainActivity = itemView.context as MainActivity
|
val mainActivity = itemView.context as MainActivity
|
||||||
mainActivity.lifecycleScope.launch {
|
mainActivity.lifecycleScope.launch {
|
||||||
// update view
|
// update view
|
||||||
var lineOne:CharSequence = ""
|
var lineOne: CharSequence = ""
|
||||||
var lineTwo = ""
|
var lineTwo = ""
|
||||||
var amountZec = ""
|
var amountZec = ""
|
||||||
var amountDisplay = ""
|
var amountDisplay = ""
|
||||||
|
@ -70,7 +67,7 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
lineOne = "${if (isMined) str(R.string.transaction_address_you_paid) else str(R.string.transaction_address_paying)} ${toAddress?.toAbbreviatedAddress()}"
|
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)
|
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?)
|
// 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"
|
amountDisplay = "- $amountZec"
|
||||||
if (isMined) {
|
if (isMined) {
|
||||||
arrowRotation = R.integer.transaction_arrow_rotation_send
|
arrowRotation = R.integer.transaction_arrow_rotation_send
|
||||||
|
@ -132,7 +129,8 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
indicator.setBackgroundColor(indicatorBackground.toAppColor())
|
indicator.setBackgroundColor(indicatorBackground.toAppColor())
|
||||||
transactionArrow.setColorFilter(arrowBackgroundTint.toAppColor())
|
transactionArrow.setColorFilter(arrowBackgroundTint.toAppColor())
|
||||||
transactionArrow.rotation = arrowRotation.toAppInt().toFloat()
|
transactionArrow.rotation = arrowRotation.toAppInt().toFloat()
|
||||||
var bottomTextRightDrawable:Drawable? = null
|
|
||||||
|
var bottomTextRightDrawable: Drawable? = null
|
||||||
iconMemo.goneIf(!transaction?.memo.toUtf8Memo().isNotEmpty())
|
iconMemo.goneIf(!transaction?.memo.toUtf8Memo().isNotEmpty())
|
||||||
bottomText.setCompoundDrawablesWithIntrinsicBounds(null, null, bottomTextRightDrawable, null)
|
bottomText.setCompoundDrawablesWithIntrinsicBounds(null, null, bottomTextRightDrawable, null)
|
||||||
}
|
}
|
||||||
|
@ -153,7 +151,4 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun str(@StringRes resourceId: Int) = itemView.context.getString(resourceId)
|
private inline fun str(@StringRes resourceId: Int) = itemView.context.getString(resourceId)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
package cash.z.ecc.android.ui.history
|
package cash.z.ecc.android.ui.history
|
||||||
//
|
//
|
||||||
//import android.content.Context
|
// import android.content.Context
|
||||||
//import android.graphics.Canvas
|
// import android.graphics.Canvas
|
||||||
//import android.graphics.Rect
|
// import android.graphics.Rect
|
||||||
//import android.view.LayoutInflater
|
// import android.view.LayoutInflater
|
||||||
//import android.view.View
|
// import android.view.View
|
||||||
//import androidx.recyclerview.widget.RecyclerView
|
// import androidx.recyclerview.widget.RecyclerView
|
||||||
//import cash.z.ecc.android.R
|
// import cash.z.ecc.android.R
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//class TransactionsDrawableFooter(context: Context) : RecyclerView.ItemDecoration() {
|
// class TransactionsDrawableFooter(context: Context) : RecyclerView.ItemDecoration() {
|
||||||
//
|
//
|
||||||
// private var footer: View =
|
// private var footer: View =
|
||||||
// LayoutInflater.from(context).inflate(R.layout.footer_transactions, null, false)
|
// LayoutInflater.from(context).inflate(R.layout.footer_transactions, null, false)
|
||||||
|
@ -49,4 +49,4 @@ package cash.z.ecc.android.ui.history
|
||||||
// outRect.setEmpty()
|
// outRect.setEmpty()
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//}
|
// }
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import cash.z.ecc.android.R
|
import cash.z.ecc.android.R
|
||||||
|
|
||||||
|
|
||||||
class TransactionsFooter(context: Context) : RecyclerView.ItemDecoration() {
|
class TransactionsFooter(context: Context) : RecyclerView.ItemDecoration() {
|
||||||
|
|
||||||
private var footer: Drawable = context.resources.getDrawable(R.drawable.background_footer)
|
private var footer: Drawable = context.resources.getDrawable(R.drawable.background_footer)
|
||||||
|
|
|
@ -68,7 +68,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
override fun inflate(inflater: LayoutInflater): FragmentHomeBinding =
|
override fun inflate(inflater: LayoutInflater): FragmentHomeBinding =
|
||||||
FragmentHomeBinding.inflate(inflater)
|
FragmentHomeBinding.inflate(inflater)
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// LifeCycle
|
// LifeCycle
|
||||||
//
|
//
|
||||||
|
@ -144,7 +143,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
// if the model already existed, cool but let the sendViewModel be the source of truth for the amount
|
// 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))))
|
onModelUpdated(null, uiModel.copy(pendingSend = WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount.coerceAtLeast(0))))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onClearAmount() {
|
private fun onClearAmount() {
|
||||||
|
@ -190,7 +188,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Public UI API
|
// Public UI API
|
||||||
//
|
//
|
||||||
|
@ -277,7 +274,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
val change = WalletZecFormmatter.toZecStringFull(totalBalance - availableBalance)
|
val change = WalletZecFormmatter.toZecStringFull(totalBalance - availableBalance)
|
||||||
"(${getString(R.string.home_banner_expecting)} +$change ZEC)".toColoredSpan(R.color.text_light, "+$change")
|
"(${getString(R.string.home_banner_expecting)} +$change ZEC)".toColoredSpan(R.color.text_light, "+$change")
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.home_instruction_enter_amount)
|
getString(R.string.home_instruction_enter_amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,7 +291,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Private UI Events
|
// Private UI Events
|
||||||
//
|
//
|
||||||
|
@ -320,9 +316,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
else -> {
|
else -> {
|
||||||
buildString {
|
buildString {
|
||||||
append("UiModel(")
|
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) {
|
if (old.processorInfo != new.processorInfo) {
|
||||||
append ("${maybeComma()}processorInfo=ProcessorInfo(")
|
append("${maybeComma()}processorInfo=ProcessorInfo(")
|
||||||
val startLength = length
|
val startLength = length
|
||||||
fun innerComma() = if (length > startLength) ", " else ""
|
fun innerComma() = if (length > startLength) ", " else ""
|
||||||
if (old.processorInfo.networkBlockHeight != new.processorInfo.networkBlockHeight) append("networkBlockHeight=${new.processorInfo.networkBlockHeight}")
|
if (old.processorInfo.networkBlockHeight != new.processorInfo.networkBlockHeight) append("networkBlockHeight=${new.processorInfo.networkBlockHeight}")
|
||||||
|
@ -332,9 +328,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
if (old.processorInfo.lastScanRange != new.processorInfo.lastScanRange) append("${innerComma()}lastScanRange=${new.processorInfo.lastScanRange}")
|
if (old.processorInfo.lastScanRange != new.processorInfo.lastScanRange) append("${innerComma()}lastScanRange=${new.processorInfo.lastScanRange}")
|
||||||
append(")")
|
append(")")
|
||||||
}
|
}
|
||||||
if (old.availableBalance != new.availableBalance) append ("${maybeComma()}availableBalance=${new.availableBalance}")
|
if (old.availableBalance != new.availableBalance) append("${maybeComma()}availableBalance=${new.availableBalance}")
|
||||||
if (old.totalBalance != new.totalBalance) append ("${maybeComma()}totalBalance=${new.totalBalance}")
|
if (old.totalBalance != new.totalBalance) append("${maybeComma()}totalBalance=${new.totalBalance}")
|
||||||
if (old.pendingSend != new.pendingSend) append ("${maybeComma()}pendingSend=${new.pendingSend}")
|
if (old.pendingSend != new.pendingSend) append("${maybeComma()}pendingSend=${new.pendingSend}")
|
||||||
append(")")
|
append(")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,7 +409,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
}.launchIn(resumedScope)
|
}.launchIn(resumedScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Inner classes and extensions
|
// Inner classes and extensions
|
||||||
//
|
//
|
||||||
|
@ -444,11 +439,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// User Interruptions
|
// 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.
|
// For now, we just need to get this in the app so that we can BEGIN capturing ECC feedback.
|
||||||
var hasInterrupted = false
|
var hasInterrupted = false
|
||||||
private fun canInterruptUser(): Boolean {
|
private fun canInterruptUser(): Boolean {
|
||||||
|
@ -536,4 +530,4 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||||
override fun onDetach() {
|
override fun onDetach() {
|
||||||
super.onDetach()
|
super.onDetach()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,23 @@ import cash.z.ecc.android.R
|
||||||
import cash.z.ecc.android.ext.toAppString
|
import cash.z.ecc.android.ext.toAppString
|
||||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
import cash.z.ecc.android.sdk.SdkSynchronizer
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
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.block.CompactBlockProcessor
|
||||||
import cash.z.ecc.android.sdk.exception.RustLayerException
|
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.MINERS_FEE_ZATOSHI
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk.ZATOSHI_PER_ZEC
|
import cash.z.ecc.android.sdk.ext.ZcashSdk.ZATOSHI_PER_ZEC
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
import cash.z.ecc.android.sdk.ext.twig
|
||||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
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 javax.inject.Inject
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@ -39,14 +48,14 @@ class HomeViewModel @Inject constructor() : ViewModel() {
|
||||||
}
|
}
|
||||||
_typedChars = ConflatedBroadcastChannel()
|
_typedChars = ConflatedBroadcastChannel()
|
||||||
val typedChars = _typedChars.asFlow()
|
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 backspace = R.string.key_backspace.toAppString()[0]
|
||||||
val zec = typedChars.scan(preTypedChars) { acc, c ->
|
val zec = typedChars.scan(preTypedChars) { acc, c ->
|
||||||
when {
|
when {
|
||||||
// no-op cases
|
// no-op cases
|
||||||
acc == "0" && c == '0'
|
acc == "0" && c == '0' ||
|
||||||
|| (c == backspace && acc == "0")
|
(c == backspace && acc == "0")
|
||||||
|| (c == decimal && acc.contains(decimal)) -> {
|
|| (c == decimal && acc.contains(decimal)) -> {
|
||||||
acc
|
acc
|
||||||
}
|
}
|
||||||
c == backspace && acc.length <= 1 -> {
|
c == backspace && acc.length <= 1 -> {
|
||||||
|
@ -68,9 +77,9 @@ class HomeViewModel @Inject constructor() : ViewModel() {
|
||||||
}
|
}
|
||||||
twig("initializing view models stream")
|
twig("initializing view models stream")
|
||||||
uiModels = synchronizer.run {
|
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)
|
UiModel(s, p, b.availableZatoshi, b.totalZatoshi, z)
|
||||||
}.onStart{ emit(UiModel()) }
|
}.onStart { emit(UiModel()) }
|
||||||
}.conflate()
|
}.conflate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cash.z.ecc.android.ui.home
|
package cash.z.ecc.android.ui.home
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
|
||||||
import com.airbnb.lottie.LottieAnimationView
|
import com.airbnb.lottie.LottieAnimationView
|
||||||
|
|
||||||
class MagicSnakeLoader(
|
class MagicSnakeLoader(
|
||||||
|
@ -54,14 +53,17 @@ class MagicSnakeLoader(
|
||||||
|
|
||||||
private fun startMaybe() {
|
private fun startMaybe() {
|
||||||
|
|
||||||
if (!isSynced && !isStarted) lottie.postDelayed({
|
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) {
|
// after some delay, if we're still not synced then we better start animating (unless we already are)!
|
||||||
lottie.resumeAnimation()
|
if (!isSynced && isPaused) {
|
||||||
isPaused = false
|
lottie.resumeAnimation()
|
||||||
isStarted = true
|
isPaused = false
|
||||||
}
|
isStarted = true
|
||||||
}, 200L)
|
}
|
||||||
|
},
|
||||||
|
200L
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val isDownloading get() = downloadProgress in 1..99
|
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) {
|
private fun applyScanProgress(frame: Int) {
|
||||||
// don't hardcode the progress until the loop animation has completed, cleanly
|
// don't hardcode the progress until the loop animation has completed, cleanly
|
||||||
if (isPaused) {
|
if (isPaused) {
|
||||||
|
@ -117,7 +119,7 @@ class MagicSnakeLoader(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeLoops() {
|
private fun removeLoops() {
|
||||||
lottie.frame.let {frame ->
|
lottie.frame.let { frame ->
|
||||||
if (frame in 33..67) {
|
if (frame in 33..67) {
|
||||||
lottie.frame = frame + 34
|
lottie.frame = frame + 34
|
||||||
} else if (frame in 0..33) {
|
} else if (frame in 0..33) {
|
||||||
|
@ -151,4 +153,3 @@ class MagicSnakeLoader(
|
||||||
return ((animatedValue as Float) * totalFrames).toInt()
|
return ((animatedValue as Float) * totalFrames).toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_CLOSE
|
||||||
import cash.z.ecc.android.feedback.Report.Tap.AWESOME_SHIELD
|
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.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.PendingTransaction
|
||||||
import cash.z.ecc.android.sdk.db.entity.isCancelled
|
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.isCreated
|
||||||
|
@ -37,7 +36,6 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class AwesomeFragment : BaseFragment<FragmentAwesomeBinding>() {
|
class AwesomeFragment : BaseFragment<FragmentAwesomeBinding>() {
|
||||||
override val screen = Report.Screen.AWESOME
|
override val screen = Report.Screen.AWESOME
|
||||||
|
|
||||||
|
@ -96,7 +94,6 @@ class AwesomeFragment : BaseFragment<FragmentAwesomeBinding>() {
|
||||||
viewModel.getTransparentBalance().let { balance ->
|
viewModel.getTransparentBalance().let { balance ->
|
||||||
onBalanceUpdated(balance, utxoCount)
|
onBalanceUpdated(balance, utxoCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onAddressLoaded(address: String) {
|
private fun onAddressLoaded(address: String) {
|
||||||
|
@ -119,7 +116,7 @@ class AwesomeFragment : BaseFragment<FragmentAwesomeBinding>() {
|
||||||
1 -> binding.textAddressPart2
|
1 -> binding.textAddressPart2
|
||||||
else -> throw IllegalArgumentException(
|
else -> throw IllegalArgumentException(
|
||||||
"Unexpected address index $index. Unable to split the t-addr into two parts." +
|
"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<FragmentAwesomeBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun onShieldComplete(isSuccess: Boolean) {
|
private fun onShieldComplete(isSuccess: Boolean) {
|
||||||
binding.lottieShielding.visibility = View.GONE
|
binding.lottieShielding.visibility = View.GONE
|
||||||
|
|
||||||
|
@ -248,9 +244,6 @@ class AwesomeFragment : BaseFragment<FragmentAwesomeBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun PendingTransaction.toUiModel() = UiModel().also { model ->
|
private fun PendingTransaction.toUiModel() = UiModel().also { model ->
|
||||||
when {
|
when {
|
||||||
isCancelled() -> {
|
isCancelled() -> {
|
||||||
|
@ -283,7 +276,7 @@ class AwesomeFragment : BaseFragment<FragmentAwesomeBinding>() {
|
||||||
model.primaryAction = { onCancel(this) }
|
model.primaryAction = { onCancel(this) }
|
||||||
} else {
|
} else {
|
||||||
model.primaryButtonText = "Shielding Funds..."
|
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<FragmentAwesomeBinding>() {
|
||||||
val details: MutableSet<String> = linkedSetOf(),
|
val details: MutableSet<String> = linkedSetOf(),
|
||||||
var showProgress: Boolean = false,
|
var showProgress: Boolean = false,
|
||||||
var primaryButtonText: String = "Shield Transparent Funds",
|
var primaryButtonText: String = "Shield Transparent Funds",
|
||||||
var primaryAction: () -> Unit = {},
|
var primaryAction: () -> Unit = {},
|
||||||
var canCancel: Boolean = false,
|
var canCancel: Boolean = false,
|
||||||
var updateBalance: Boolean = false,
|
var updateBalance: Boolean = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,21 @@ package cash.z.ecc.android.ui.profile
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewTreeObserver
|
|
||||||
import android.view.WindowManager
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.view.doOnLayout
|
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import cash.z.ecc.android.R
|
import cash.z.ecc.android.R
|
||||||
import cash.z.ecc.android.databinding.FragmentFeedbackBinding
|
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
|
||||||
import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback
|
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_CANCEL
|
||||||
import cash.z.ecc.android.feedback.Report.Tap.FEEDBACK_SUBMIT
|
import cash.z.ecc.android.feedback.Report.Tap.FEEDBACK_SUBMIT
|
||||||
import cash.z.ecc.android.ui.base.BaseFragment
|
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
|
* Fragment representing the home screen of the app. This is the screen most often seen by the user when launching the
|
||||||
* application.
|
* application.
|
||||||
*/
|
*/
|
||||||
class FeedbackFragment : BaseFragment<FragmentFeedbackBinding>() {
|
class FeedbackFragment : BaseFragment<FragmentFeedbackBinding>() {
|
||||||
override val screen = Report.Screen.FEEDBACK
|
override val screen = Report.Screen.FEEDBACK
|
||||||
val args: FeedbackFragmentArgs by navArgs()
|
val args: FeedbackFragmentArgs by navArgs()
|
||||||
|
|
||||||
|
@ -68,7 +63,6 @@ class FeedbackFragment : BaseFragment<FragmentFeedbackBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Private API
|
// Private API
|
||||||
//
|
//
|
||||||
|
|
|
@ -40,7 +40,6 @@ import cash.z.ecc.android.ui.util.DebugFileTwig
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
||||||
override val screen = Report.Screen.PROFILE
|
override val screen = Report.Screen.PROFILE
|
||||||
|
|
||||||
|
@ -93,13 +92,14 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
||||||
if (viewModel.isEasterEggTriggered()) {
|
if (viewModel.isEasterEggTriggered()) {
|
||||||
binding.iconProfile.setImageResource(R.drawable.ic_profile_zebra_02)
|
binding.iconProfile.setImageResource(R.drawable.ic_profile_zebra_02)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onEnterAwesomeMode() {
|
private fun onEnterAwesomeMode() {
|
||||||
(context as? MainActivity)?.safeNavigate(R.id.action_nav_profile_to_nav_awesome)
|
(context as? MainActivity)?.safeNavigate(R.id.action_nav_profile_to_nav_awesome)
|
||||||
?: throw IllegalStateException("Cannot navigate from this activity. " +
|
?: throw IllegalStateException(
|
||||||
"Expected MainActivity but found ${context?.javaClass?.simpleName}")
|
"Cannot navigate from this activity. " +
|
||||||
|
"Expected MainActivity but found ${context?.javaClass?.simpleName}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -135,7 +135,7 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
||||||
viewModel.quickRescan()
|
viewModel.quickRescan()
|
||||||
Toast.makeText(ZcashWalletApp.instance, "Performing quick rescan!", Toast.LENGTH_LONG).show()
|
Toast.makeText(ZcashWalletApp.instance, "Performing quick rescan!", Toast.LENGTH_LONG).show()
|
||||||
mainActivity?.navController?.popBackStack()
|
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}")
|
mainActivity?.showCriticalMessage("Quick Rescan Failed", "Unable to perform quick rescan due to error:\n\n${t.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,8 +145,8 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
||||||
mainActivity?.showConfirmation(
|
mainActivity?.showConfirmation(
|
||||||
"Are you sure?",
|
"Are you sure?",
|
||||||
"Wiping your data will close the app. Since your seed is preserved, " +
|
"Wiping your data will close the app. Since your seed is preserved, " +
|
||||||
"this operation is probably safe but please backup your seed anyway." +
|
"this operation is probably safe but please backup your seed anyway." +
|
||||||
"\n\nContinue?",
|
"\n\nContinue?",
|
||||||
"Wipe"
|
"Wipe"
|
||||||
) {
|
) {
|
||||||
viewModel.wipe()
|
viewModel.wipe()
|
||||||
|
@ -214,4 +214,4 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
||||||
private fun developerLogFile(): File? {
|
private fun developerLogFile(): File? {
|
||||||
return Bush.trunk.find<DebugFileTwig>()?.file
|
return Bush.trunk.find<DebugFileTwig>()?.file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import cash.z.ecc.android.ZcashWalletApp
|
||||||
import cash.z.ecc.android.ext.Const
|
import cash.z.ecc.android.ext.Const
|
||||||
import cash.z.ecc.android.lockbox.LockBox
|
import cash.z.ecc.android.lockbox.LockBox
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
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.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
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 cash.z.ecc.android.sdk.type.WalletBalance
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
|
@ -108,7 +106,7 @@ class ProfileViewModel @Inject constructor() : ViewModel() {
|
||||||
|
|
||||||
fun quickScanDistance(): Int {
|
fun quickScanDistance(): Int {
|
||||||
val latest = synchronizer.latestHeight
|
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
|
var foo = 0
|
||||||
runBlocking {
|
runBlocking {
|
||||||
foo = synchronizer.getNearestRewindHeight(latest - oneWeek)
|
foo = synchronizer.getNearestRewindHeight(latest - oneWeek)
|
||||||
|
|
|
@ -8,20 +8,16 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import cash.z.android.qrecycler.QRecycler
|
import cash.z.android.qrecycler.QRecycler
|
||||||
import cash.z.ecc.android.R
|
|
||||||
import cash.z.ecc.android.databinding.FragmentReceiveNewBinding
|
import cash.z.ecc.android.databinding.FragmentReceiveNewBinding
|
||||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||||
import cash.z.ecc.android.ext.distribute
|
import cash.z.ecc.android.ext.distribute
|
||||||
import cash.z.ecc.android.ext.onClickNavBack
|
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
|
||||||
import cash.z.ecc.android.feedback.Report.Tap.*
|
import cash.z.ecc.android.feedback.Report.Tap.RECEIVE_BACK
|
||||||
import cash.z.ecc.android.ui.base.BaseFragment
|
|
||||||
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
|
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
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 cash.z.ecc.android.ui.util.AddressPartNumberSpan
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class ReceiveFragment : BaseFragment<FragmentReceiveNewBinding>() {
|
class ReceiveFragment : BaseFragment<FragmentReceiveNewBinding>() {
|
||||||
override val screen = Report.Screen.RECEIVE
|
override val screen = Report.Screen.RECEIVE
|
||||||
|
@ -84,4 +80,4 @@ class ReceiveFragment : BaseFragment<FragmentReceiveNewBinding>() {
|
||||||
|
|
||||||
addressParts[index].text = textSpan
|
addressParts[index].text = textSpan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import com.google.zxing.Reader
|
||||||
import com.google.zxing.common.HybridBinarizer
|
import com.google.zxing.common.HybridBinarizer
|
||||||
import com.google.zxing.qrcode.QRCodeReader
|
import com.google.zxing.qrcode.QRCodeReader
|
||||||
|
|
||||||
|
|
||||||
class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Unit) :
|
class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Unit) :
|
||||||
ImageAnalysis.Analyzer {
|
ImageAnalysis.Analyzer {
|
||||||
|
|
||||||
|
@ -63,5 +62,4 @@ class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Uni
|
||||||
private fun onImageScan(result: String, image: ImageProxy) {
|
private fun onImageScan(result: String, image: ImageProxy) {
|
||||||
scanCallback(result, image)
|
scanCallback(result, image)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,11 @@ import android.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
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.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import cash.z.ecc.android.R
|
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.ext.onClickNavBack
|
||||||
import cash.z.ecc.android.feedback.Report
|
import cash.z.ecc.android.feedback.Report
|
||||||
import cash.z.ecc.android.feedback.Report.Tap.SCAN_BACK
|
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.sdk.ext.twig
|
||||||
import cash.z.ecc.android.ui.base.BaseFragment
|
import cash.z.ecc.android.ui.base.BaseFragment
|
||||||
import cash.z.ecc.android.ui.send.SendViewModel
|
import cash.z.ecc.android.ui.send.SendViewModel
|
||||||
|
@ -26,7 +29,9 @@ import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
||||||
|
|
||||||
override val screen = Report.Screen.SCAN
|
override val screen = Report.Screen.SCAN
|
||||||
|
|
||||||
private val viewModel: ScanViewModel by viewModel()
|
private val viewModel: ScanViewModel by viewModel()
|
||||||
|
|
||||||
private val sendViewModel: SendViewModel by activityViewModel()
|
private val sendViewModel: SendViewModel by activityViewModel()
|
||||||
|
@ -54,9 +59,12 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
||||||
cameraProviderFuture.addListener(Runnable {
|
cameraProviderFuture.addListener(
|
||||||
bindPreview(cameraProviderFuture.get())
|
Runnable {
|
||||||
}, ContextCompat.getMainExecutor(context))
|
bindPreview(cameraProviderFuture.get())
|
||||||
|
},
|
||||||
|
ContextCompat.getMainExecutor(context)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@ -87,9 +95,12 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
||||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
imageAnalysis.setAnalyzer(cameraExecutor!!, QrAnalyzer { q, i ->
|
imageAnalysis.setAnalyzer(
|
||||||
onQrScanned(q, i)
|
cameraExecutor!!,
|
||||||
})
|
QrAnalyzer { q, i ->
|
||||||
|
onQrScanned(q, i)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Must unbind the use-cases before rebinding them
|
// Must unbind the use-cases before rebinding them
|
||||||
cameraProvider.unbindAll()
|
cameraProvider.unbindAll()
|
||||||
|
@ -102,7 +113,6 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
||||||
mainActivity?.feedback?.report(t)
|
mainActivity?.feedback?.report(t)
|
||||||
twig("Error while opening the camera: $t")
|
twig("Error while opening the camera: $t")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -114,7 +124,8 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
||||||
height
|
height
|
||||||
)
|
)
|
||||||
if (kotlin.math.abs(previewRatio - (4.0 / 3.0))
|
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_4_3
|
||||||
}
|
}
|
||||||
return AspectRatio.RATIO_16_9
|
return AspectRatio.RATIO_16_9
|
||||||
|
@ -127,7 +138,7 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
||||||
val network = viewModel.networkName
|
val network = viewModel.networkName
|
||||||
binding.textScanError.text = getString(R.string.scan_invalid_address, network, qrContent)
|
binding.textScanError.text = getString(R.string.scan_invalid_address, network, qrContent)
|
||||||
image.close()
|
image.close()
|
||||||
} else { /* continue scanning*/
|
} else { /* continue scanning*/
|
||||||
binding.textScanError.text = ""
|
binding.textScanError.text = ""
|
||||||
sendViewModel.toAddress = parsed
|
sendViewModel.toAddress = parsed
|
||||||
mainActivity?.safeNavigate(R.id.action_nav_scan_to_nav_send)
|
mainActivity?.safeNavigate(R.id.action_nav_scan_to_nav_send)
|
||||||
|
@ -157,12 +168,6 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
||||||
// overlay.set(list)
|
// overlay.set(list)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Permissions
|
// Permissions
|
||||||
//
|
//
|
||||||
|
@ -171,7 +176,7 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
||||||
get() {
|
get() {
|
||||||
return try {
|
return try {
|
||||||
val info = mainActivity?.packageManager
|
val info = mainActivity?.packageManager
|
||||||
?.getPackageInfo(mainActivity?.packageName, PackageManager.GET_PERMISSIONS)
|
?.getPackageInfo(mainActivity?.packageName ?: "", PackageManager.GET_PERMISSIONS)
|
||||||
val ps = info?.requestedPermissions
|
val ps = info?.requestedPermissions
|
||||||
if (ps != null && ps.isNotEmpty()) {
|
if (ps != null && ps.isNotEmpty()) {
|
||||||
ps
|
ps
|
||||||
|
@ -212,4 +217,4 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
||||||
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
|
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ class ScanViewModel @Inject constructor() : ViewModel() {
|
||||||
val networkName get() = synchronizer.network.networkName
|
val networkName get() = synchronizer.network.networkName
|
||||||
|
|
||||||
suspend fun parse(qrCode: String): String? {
|
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
|
// TODO: replace with more robust ZIP-321 handling of QR codes
|
||||||
val address = if (qrCode.startsWith("zcash:")) {
|
val address = if (qrCode.startsWith("zcash:")) {
|
||||||
qrCode.substring(6, qrCode.indexOf("?").takeUnless { it == -1 } ?: qrCode.length)
|
qrCode.substring(6, qrCode.indexOf("?").takeUnless { it == -1 } ?: qrCode.length)
|
||||||
|
@ -27,5 +27,4 @@ class ScanViewModel @Inject constructor() : ViewModel() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
twig("${javaClass.simpleName} cleared!")
|
twig("${javaClass.simpleName} cleared!")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,15 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import cash.z.ecc.android.R
|
|
||||||
import cash.z.ecc.android.databinding.FragmentSendConfirmBinding
|
import cash.z.ecc.android.databinding.FragmentSendConfirmBinding
|
||||||
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
||||||
import cash.z.ecc.android.ext.WalletZecFormmatter
|
import cash.z.ecc.android.ext.WalletZecFormmatter
|
||||||
import cash.z.ecc.android.ext.goneIf
|
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
|
||||||
import cash.z.ecc.android.feedback.Report.Funnel.Send
|
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_CONFIRM_NEXT
|
||||||
import cash.z.ecc.android.ui.base.BaseFragment
|
|
||||||
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
|
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
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {
|
class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {
|
||||||
|
@ -49,4 +46,4 @@ class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {
|
||||||
sendViewModel.funnel(Send.ConfirmPageComplete)
|
sendViewModel.funnel(Send.ConfirmPageComplete)
|
||||||
// mainActivity?.safeNavigate(R.id.action_nav_send_confirm_to_send_final)
|
// mainActivity?.safeNavigate(R.id.action_nav_send_confirm_to_send_final)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,12 @@ import cash.z.ecc.android.ext.WalletZecFormmatter
|
||||||
import cash.z.ecc.android.ext.goneIf
|
import cash.z.ecc.android.ext.goneIf
|
||||||
import cash.z.ecc.android.feedback.Report
|
import cash.z.ecc.android.feedback.Report
|
||||||
import cash.z.ecc.android.feedback.Report.Tap.SEND_FINAL_CLOSE
|
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.toAbbreviatedAddress
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
import cash.z.ecc.android.sdk.ext.twig
|
||||||
import cash.z.ecc.android.ui.base.BaseFragment
|
import cash.z.ecc.android.ui.base.BaseFragment
|
||||||
|
@ -70,13 +75,13 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonMoreInfo.apply {
|
buttonMoreInfo.apply {
|
||||||
setOnClickListener{
|
setOnClickListener {
|
||||||
val moreInfoMsg = """${getString(R.string.more_info)} : ${model.errorDescription}"""
|
val moreInfoMsg = """${getString(R.string.more_info)} : ${model.errorDescription}"""
|
||||||
txtMoreInfo.run {
|
txtMoreInfo.run {
|
||||||
text = moreInfoMsg
|
text = moreInfoMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
if(model.errorDescription.isNotEmpty())
|
if (model.errorDescription.isNotEmpty())
|
||||||
buttonMoreInfo.text = getString(R.string.translated_button_done)
|
buttonMoreInfo.text = getString(R.string.translated_button_done)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +120,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
||||||
|
|
||||||
private fun PendingTransaction.toUiModel() = UiModel().also { model ->
|
private fun PendingTransaction.toUiModel() = UiModel().also { model ->
|
||||||
when {
|
when {
|
||||||
isCancelled() -> {
|
isCancelled() -> {
|
||||||
model.title = getString(R.string.send_final_result_cancelled)
|
model.title = getString(R.string.send_final_result_cancelled)
|
||||||
model.primaryButtonText = getString(R.string.send_final_button_primary_back)
|
model.primaryButtonText = getString(R.string.send_final_button_primary_back)
|
||||||
model.primaryAction = { onReturnToSend() }
|
model.primaryAction = { onReturnToSend() }
|
||||||
|
@ -128,7 +133,8 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
||||||
isFailure() -> {
|
isFailure() -> {
|
||||||
model.title = getString(R.string.send_final_button_primary_failed)
|
model.title = getString(R.string.send_final_button_primary_failed)
|
||||||
model.errorMessage = if (isFailedEncoding()) getString(R.string.send_final_error_encoding) else getString(
|
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.errorDescription = errorMessage.toString()
|
||||||
model.primaryButtonText = getString(R.string.send_final_button_primary_retry)
|
model.primaryButtonText = getString(R.string.send_final_button_primary_retry)
|
||||||
model.primaryAction = { onReturnToSend() }
|
model.primaryAction = { onReturnToSend() }
|
||||||
|
@ -156,7 +162,6 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
||||||
var showProgress: Boolean = false,
|
var showProgress: Boolean = false,
|
||||||
var errorMessage: String = "",
|
var errorMessage: String = "",
|
||||||
var primaryButtonText: String = "See Details",
|
var primaryButtonText: String = "See Details",
|
||||||
var primaryAction: () -> Unit = {}
|
var primaryAction: () -> Unit = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,24 @@ import androidx.lifecycle.viewModelScope
|
||||||
import cash.z.ecc.android.R
|
import cash.z.ecc.android.R
|
||||||
import cash.z.ecc.android.databinding.FragmentSendBinding
|
import cash.z.ecc.android.databinding.FragmentSendBinding
|
||||||
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
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
|
||||||
import cash.z.ecc.android.feedback.Report.Tap.*
|
import cash.z.ecc.android.feedback.Report.Tap.SEND_ADDRESS_BACK
|
||||||
import cash.z.ecc.android.sdk.ext.*
|
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.AddressType
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
import cash.z.ecc.android.sdk.type.WalletBalance
|
||||||
import cash.z.ecc.android.ui.base.BaseFragment
|
import cash.z.ecc.android.ui.base.BaseFragment
|
||||||
|
@ -30,7 +44,8 @@ import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SendFragment : BaseFragment<FragmentSendBinding>(),
|
class SendFragment :
|
||||||
|
BaseFragment<FragmentSendBinding>(),
|
||||||
ClipboardManager.OnPrimaryClipChangedListener {
|
ClipboardManager.OnPrimaryClipChangedListener {
|
||||||
override val screen = Report.Screen.SEND_ADDRESS
|
override val screen = Report.Screen.SEND_ADDRESS
|
||||||
|
|
||||||
|
@ -49,14 +64,13 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
||||||
applyViewModel(sendViewModel)
|
applyViewModel(sendViewModel)
|
||||||
updateAddressUi(false)
|
updateAddressUi(false)
|
||||||
|
|
||||||
|
|
||||||
// Apply behaviors
|
// Apply behaviors
|
||||||
|
|
||||||
binding.buttonSend.setOnClickListener {
|
binding.buttonSend.setOnClickListener {
|
||||||
onSubmit().also { tapped(SEND_SUBMIT) }
|
onSubmit().also { tapped(SEND_SUBMIT) }
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.checkIncludeAddress.setOnCheckedChangeListener { _, _->
|
binding.checkIncludeAddress.setOnCheckedChangeListener { _, _ ->
|
||||||
onIncludeMemo(binding.checkIncludeAddress.isChecked)
|
onIncludeMemo(binding.checkIncludeAddress.isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,8 +186,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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) {
|
private fun updateAddressUi(isMemoHidden: Boolean) {
|
||||||
if (isMemoHidden) {
|
if (isMemoHidden) {
|
||||||
binding.textLayoutMemo.gone()
|
binding.textLayoutMemo.gone()
|
||||||
|
@ -186,7 +199,6 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun onSubmit(unused: EditText? = null) {
|
private fun onSubmit(unused: EditText? = null) {
|
||||||
sendViewModel.toAddress = binding.inputZcashAddress.text.toString()
|
sendViewModel.toAddress = binding.inputZcashAddress.text.toString()
|
||||||
sendViewModel.validate(requireContext(), availableZatoshi, maxZatoshi).onFirstWith(resumedScope) { errorMessage ->
|
sendViewModel.validate(requireContext(), availableZatoshi, maxZatoshi).onFirstWith(resumedScope) { errorMessage ->
|
||||||
|
@ -217,7 +229,6 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
mainActivity?.clipboard?.addPrimaryClipChangedListener(this)
|
mainActivity?.clipboard?.addPrimaryClipChangedListener(this)
|
||||||
|
@ -280,7 +291,8 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
||||||
imageLastUsedShield,
|
imageLastUsedShield,
|
||||||
lastUsedAddressLabel,
|
lastUsedAddressLabel,
|
||||||
selected,
|
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)
|
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<FragmentSendBinding>(),
|
||||||
ImageViewCompat.setImageTintList(shieldIcon, ColorStateList.valueOf(if (selected) R.color.colorPrimary.toAppColor() else R.color.zcashWhite_12.toAppColor()))
|
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)
|
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")
|
if (address == userTransparentAddr) addressLabel.setText("Your Auto-Shielding Address")
|
||||||
addressLabel.setTextColor(if(selected) R.color.colorPrimary.toAppColor() else R.color.text_light.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())
|
addressTextView.setTextColor(if (selected) R.color.text_light.toAppColor() else R.color.text_light_dimmed.toAppColor())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,7 +364,6 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
||||||
return lastUsedAddress
|
return lastUsedAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun ClipboardManager.text(): CharSequence =
|
private fun ClipboardManager.text(): CharSequence =
|
||||||
primaryClip!!.getItemAt(0).coerceToText(mainActivity)
|
primaryClip!!.getItemAt(0).coerceToText(mainActivity)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,11 @@ import cash.z.ecc.android.ext.goneIf
|
||||||
import cash.z.ecc.android.ext.onEditorActionDone
|
import cash.z.ecc.android.ext.onEditorActionDone
|
||||||
import cash.z.ecc.android.feedback.Report
|
import cash.z.ecc.android.feedback.Report
|
||||||
import cash.z.ecc.android.feedback.Report.Funnel.Send
|
import cash.z.ecc.android.feedback.Report.Funnel.Send
|
||||||
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.base.BaseFragment
|
||||||
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX_STANDARD
|
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX_STANDARD
|
||||||
|
|
||||||
|
@ -41,7 +45,7 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
|
||||||
// onBackPressNavTo(it) { tapped(SEND_MEMO_BACK) }
|
// onBackPressNavTo(it) { tapped(SEND_MEMO_BACK) }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
binding.checkIncludeAddress.setOnCheckedChangeListener { _, _->
|
binding.checkIncludeAddress.setOnCheckedChangeListener { _, _ ->
|
||||||
onIncludeMemo(binding.checkIncludeAddress.isChecked)
|
onIncludeMemo(binding.checkIncludeAddress.isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +53,7 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
|
||||||
memo.onEditorActionDone {
|
memo.onEditorActionDone {
|
||||||
onTopButton().also { tapped(SEND_MEMO_NEXT) }
|
onTopButton().also { tapped(SEND_MEMO_NEXT) }
|
||||||
}
|
}
|
||||||
memo.doAfterTextChanged {
|
memo.doAfterTextChanged {
|
||||||
binding.clearMemo.goneIf(memo.text.isEmpty())
|
binding.clearMemo.goneIf(memo.text.isEmpty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,4 +121,4 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
|
||||||
sendViewModel.funnel(Send.MemoPageComplete)
|
sendViewModel.funnel(Send.MemoPageComplete)
|
||||||
// mainActivity?.safeNavigate(R.id.action_nav_send_memo_to_send_confirm)
|
// mainActivity?.safeNavigate(R.id.action_nav_send_memo_to_send_confirm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.Funnel.Send.SpendingKeyFound
|
||||||
import cash.z.ecc.android.feedback.Report.Issue
|
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.*
|
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.lockbox.LockBox
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
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.ZcashSdk
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
import cash.z.ecc.android.sdk.ext.twig
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
|
@ -54,12 +64,12 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
||||||
set(value) {
|
set(value) {
|
||||||
require(!value || (value && !fromAddress.isNullOrEmpty())) {
|
require(!value || (value && !fromAddress.isNullOrEmpty())) {
|
||||||
"Error: fromAddress was empty while attempting to include it in the memo. Verify" +
|
"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
|
field = value
|
||||||
}
|
}
|
||||||
val isShielded get() = toAddress.startsWith("z")
|
val isShielded get() = toAddress.startsWith("z")
|
||||||
|
|
||||||
fun send(): Flow<PendingTransaction> {
|
fun send(): Flow<PendingTransaction> {
|
||||||
funnel(SendSelected)
|
funnel(SendSelected)
|
||||||
val memoToSend = createMemoToSend()
|
val memoToSend = createMemoToSend()
|
||||||
|
@ -92,7 +102,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
||||||
suspend fun validateAddress(address: String): AddressType =
|
suspend fun validateAddress(address: String): AddressType =
|
||||||
synchronizer.validateAddress(address)
|
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
|
is AddressType.Shielded, is AddressType.Transparent -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -140,7 +150,6 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
||||||
includeFromAddress = false
|
includeFromAddress = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Analytics
|
// Analytics
|
||||||
//
|
//
|
||||||
|
@ -176,7 +185,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMetrics(tx: PendingTransaction) {
|
fun updateMetrics(tx: PendingTransaction) {
|
||||||
try {
|
try {
|
||||||
when {
|
when {
|
||||||
tx.isMined() -> TRANSACTION_SUBMITTED to TRANSACTION_MINED by tx.id
|
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 ->
|
metrics[metricId]?.let { metric ->
|
||||||
metric.takeUnless { (it.elapsedTime ?: 0) <= 0L }?.let {
|
metric.takeUnless { (it.elapsedTime ?: 0) <= 0L }?.let {
|
||||||
viewModelScope.launch {
|
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") }
|
metrics[metricId].also { if (it == null) println("Warning no start metric for id: $metricId") }
|
||||||
}
|
}
|
||||||
return startMetric?.endTime?.let { startMetricEndTime ->
|
return startMetric?.endTime?.let { startMetricEndTime ->
|
||||||
TimeMetric(second.key, second.description, mutableListOf(startMetricEndTime))
|
TimeMetric(second.key, second.description, mutableListOf(startMetricEndTime))
|
||||||
.markTime().let { endMetric ->
|
.markTime().let { endMetric ->
|
||||||
endMetric.toMetricIdFor(txId).also { metricId ->
|
endMetric.toMetricIdFor(txId).also { metricId ->
|
||||||
metrics[metricId] = endMetric
|
metrics[metricId] = endMetric
|
||||||
metrics[metricId.toRelatedMetricId()] = startMetric
|
metrics[metricId.toRelatedMetricId()] = startMetric
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Keyed<String>.toMetricIdFor(id: Long): String = "$id.$key"
|
private fun Keyed<String>.toMetricIdFor(id: Long): String = "$id.$key"
|
||||||
private fun String.toRelatedMetricId(): String = "$this.related"
|
private fun String.toRelatedMetricId(): String = "$this.related"
|
||||||
private fun String.toTxId(): Long = split('.').first().toLong()
|
private fun String.toTxId(): Long = split('.').first().toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,13 @@ import cash.z.ecc.android.R
|
||||||
import cash.z.ecc.android.ZcashWalletApp
|
import cash.z.ecc.android.ZcashWalletApp
|
||||||
import cash.z.ecc.android.databinding.FragmentSettingsBinding
|
import cash.z.ecc.android.databinding.FragmentSettingsBinding
|
||||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
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.exception.LightWalletException
|
||||||
import cash.z.ecc.android.sdk.ext.collectWith
|
import cash.z.ecc.android.sdk.ext.collectWith
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
import cash.z.ecc.android.sdk.ext.twig
|
||||||
|
@ -53,7 +59,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
|
||||||
viewModel.uiModels.collectWith(resumedScope, ::onUiModelUpdated)
|
viewModel.uiModels.collectWith(resumedScope, ::onUiModelUpdated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Event handlers
|
// Event handlers
|
||||||
//
|
//
|
||||||
|
@ -131,24 +136,22 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
|
||||||
error.javaClass.simpleName
|
error.javaClass.simpleName
|
||||||
}
|
}
|
||||||
val message = "An error occured while changing servers. Please verify the info" +
|
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)
|
twig(message)
|
||||||
Toast.makeText(ZcashWalletApp.instance, getString(R.string.settings_toast_change_server_failure), Toast.LENGTH_SHORT).show()
|
Toast.makeText(ZcashWalletApp.instance, getString(R.string.settings_toast_change_server_failure), Toast.LENGTH_SHORT).show()
|
||||||
context?.showUpdateServerCriticalError(message)
|
context?.showUpdateServerCriticalError(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Utilities
|
// Utilities
|
||||||
//
|
//
|
||||||
|
|
||||||
private fun String?.toHelperTextColor(): ColorStateList {
|
private fun String?.toHelperTextColor(): ColorStateList {
|
||||||
val color = if (this == null) {
|
val color = if (this == null) {
|
||||||
R.color.text_light_dimmed
|
R.color.text_light_dimmed
|
||||||
} else {
|
} else {
|
||||||
R.color.zcashRed
|
R.color.zcashRed
|
||||||
}
|
}
|
||||||
return ColorStateList.valueOf(color.toAppColor())
|
return ColorStateList.valueOf(color.toAppColor())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import cash.z.ecc.android.lockbox.LockBox
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
import cash.z.ecc.android.sdk.ext.twig
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.cancellable
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
import kotlin.properties.Delegates.observable
|
import kotlin.properties.Delegates.observable
|
||||||
|
@ -28,7 +27,6 @@ class SettingsViewModel @Inject constructor() : ViewModel() {
|
||||||
var pendingHost by observable("", ::onUpdateModel)
|
var pendingHost by observable("", ::onUpdateModel)
|
||||||
var pendingPortText by observable("", ::onUpdateModel)
|
var pendingPortText by observable("", ::onUpdateModel)
|
||||||
|
|
||||||
|
|
||||||
private fun getHost(): String {
|
private fun getHost(): String {
|
||||||
return prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST
|
return prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
||||||
|
|
||||||
private val walletSetup: WalletSetupViewModel by activityViewModel(false)
|
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 =
|
override fun inflate(inflater: LayoutInflater): FragmentBackupBinding =
|
||||||
FragmentBackupBinding.inflate(inflater)
|
FragmentBackupBinding.inflate(inflater)
|
||||||
|
@ -76,7 +76,7 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
walletSetup.checkSeed().onEach {
|
walletSetup.checkSeed().onEach {
|
||||||
hasBackUp = when(it) {
|
hasBackUp = when (it) {
|
||||||
SEED_WITH_BACKUP -> true
|
SEED_WITH_BACKUP -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -134,9 +134,9 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
||||||
mainActivity!!.feedback.measure(SEED_PHRASE_LOADED) {
|
mainActivity!!.feedback.measure(SEED_PHRASE_LOADED) {
|
||||||
val lockBox = LockBox(ZcashWalletApp.instance)
|
val lockBox = LockBox(ZcashWalletApp.instance)
|
||||||
val mnemonics = Mnemonics()
|
val mnemonics = Mnemonics()
|
||||||
val seedPhrase = lockBox.getCharsUtf8(Const.Backup.SEED_PHRASE) ?: throw RuntimeException("Seed Phrase expected but not found in storage!!")
|
val seedPhrase = lockBox.getCharsUtf8(Const.Backup.SEED_PHRASE) ?: throw RuntimeException("Seed Phrase expected but not found in storage!!")
|
||||||
val result = mnemonics.toWordList(seedPhrase)
|
val result = mnemonics.toWordList(seedPhrase)
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
|
|
||||||
walletSetup.checkSeed().onEach {
|
walletSetup.checkSeed().onEach {
|
||||||
when(it) {
|
when (it) {
|
||||||
SEED_WITHOUT_BACKUP, SEED_WITH_BACKUP -> {
|
SEED_WITHOUT_BACKUP, SEED_WITH_BACKUP -> {
|
||||||
mainActivity?.safeNavigate(R.id.nav_backup)
|
mainActivity?.safeNavigate(R.id.nav_backup)
|
||||||
}
|
}
|
||||||
|
@ -100,9 +100,12 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
view?.postDelayed({
|
view?.postDelayed(
|
||||||
mainActivity?.hideKeyboard()
|
{
|
||||||
}, 25L)
|
mainActivity?.hideKeyboard()
|
||||||
|
},
|
||||||
|
25L
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSkip(count: Int) {
|
private fun onSkip(count: Int) {
|
||||||
|
@ -134,10 +137,10 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
||||||
val birthday: Int
|
val birthday: Int
|
||||||
|
|
||||||
// new testnet dev wallet
|
// new testnet dev wallet
|
||||||
when(ZcashWalletApp.instance.defaultNetwork) {
|
when (ZcashWalletApp.instance.defaultNetwork) {
|
||||||
ZcashNetwork.Mainnet -> {
|
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"
|
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 -> {
|
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"
|
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<FragmentLandingBinding>() {
|
||||||
skipCount = 0
|
skipCount = 0
|
||||||
mainActivity?.navController?.popBackStack()
|
mainActivity?.navController?.popBackStack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import android.view.MotionEvent
|
||||||
import android.view.MotionEvent.ACTION_DOWN
|
import android.view.MotionEvent.ACTION_DOWN
|
||||||
import android.view.MotionEvent.ACTION_UP
|
import android.view.MotionEvent.ACTION_UP
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.RelativeLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
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_CLEAR
|
||||||
import cash.z.ecc.android.feedback.Report.Tap.RESTORE_DONE
|
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.feedback.Report.Tap.RESTORE_SUCCESS
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
|
||||||
import cash.z.ecc.android.ui.base.BaseFragment
|
import cash.z.ecc.android.ui.base.BaseFragment
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.tylersuehr.chips.Chip
|
import com.tylersuehr.chips.Chip
|
||||||
|
@ -36,7 +34,6 @@ import com.tylersuehr.chips.ChipsAdapter
|
||||||
import com.tylersuehr.chips.SeedWordAdapter
|
import com.tylersuehr.chips.SeedWordAdapter
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class RestoreFragment : BaseFragment<FragmentRestoreBinding>(), View.OnKeyListener {
|
class RestoreFragment : BaseFragment<FragmentRestoreBinding>(), View.OnKeyListener {
|
||||||
override val screen = Report.Screen.RESTORE
|
override val screen = Report.Screen.RESTORE
|
||||||
|
|
||||||
|
@ -57,7 +54,6 @@ class RestoreFragment : BaseFragment<FragmentRestoreBinding>(), View.OnKeyListen
|
||||||
}.also { onChipsModified() }
|
}.also { onChipsModified() }
|
||||||
seedWordRecycler.adapter = seedWordAdapter
|
seedWordRecycler.adapter = seedWordAdapter
|
||||||
|
|
||||||
|
|
||||||
binding.chipsInput.apply {
|
binding.chipsInput.apply {
|
||||||
setFilterableChipList(getChips())
|
setFilterableChipList(getChips())
|
||||||
setDelimiter("[ ;,]", true)
|
setDelimiter("[ ;,]", true)
|
||||||
|
@ -116,7 +112,6 @@ class RestoreFragment : BaseFragment<FragmentRestoreBinding>(), View.OnKeyListen
|
||||||
touchScreenForUser()
|
touchScreenForUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun onExit() {
|
private fun onExit() {
|
||||||
mainActivity?.reportFunnel(Restore.Exit)
|
mainActivity?.reportFunnel(Restore.Exit)
|
||||||
hideAutoCompleteWords()
|
hideAutoCompleteWords()
|
||||||
|
@ -189,12 +184,15 @@ class RestoreFragment : BaseFragment<FragmentRestoreBinding>(), View.OnKeyListen
|
||||||
// forcefully show the keyboard as a hack to fix odd behavior where the keyboard
|
// forcefully show the keyboard as a hack to fix odd behavior where the keyboard
|
||||||
// sometimes closes randomly and inexplicably in between seed word entries
|
// sometimes closes randomly and inexplicably in between seed word entries
|
||||||
private fun forceShowKeyboard() {
|
private fun forceShowKeyboard() {
|
||||||
requireView().postDelayed({
|
requireView().postDelayed(
|
||||||
val isDone = (seedWordAdapter?.itemCount ?: 0) > 24
|
{
|
||||||
val focusedView = if (isDone) binding.inputBirthdate else seedWordAdapter!!.editText
|
val isDone = (seedWordAdapter?.itemCount ?: 0) > 24
|
||||||
mainActivity!!.showKeyboard(focusedView)
|
val focusedView = if (isDone) binding.inputBirthdate else seedWordAdapter!!.editText
|
||||||
focusedView.requestFocus()
|
mainActivity!!.showKeyboard(focusedView)
|
||||||
}, 500L)
|
focusedView.requestFocus()
|
||||||
|
},
|
||||||
|
500L
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reportWords(count: Int) {
|
private fun reportWords(count: Int) {
|
||||||
|
@ -220,11 +218,14 @@ class RestoreFragment : BaseFragment<FragmentRestoreBinding>(), View.OnKeyListen
|
||||||
|
|
||||||
private fun touchScreenForUser() {
|
private fun touchScreenForUser() {
|
||||||
seedWordAdapter?.editText?.apply {
|
seedWordAdapter?.editText?.apply {
|
||||||
postDelayed({
|
postDelayed(
|
||||||
seedWordAdapter?.editText?.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
{
|
||||||
dispatchTouchEvent(motionEvent(ACTION_DOWN))
|
seedWordAdapter?.editText?.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||||
dispatchTouchEvent(motionEvent(ACTION_UP))
|
dispatchTouchEvent(motionEvent(ACTION_DOWN))
|
||||||
}, 100L)
|
dispatchTouchEvent(motionEvent(ACTION_UP))
|
||||||
|
},
|
||||||
|
100L
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,13 +236,12 @@ class RestoreFragment : BaseFragment<FragmentRestoreBinding>(), View.OnKeyListen
|
||||||
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SeedWordChip(val word: String, var index: Int = -1) : Chip() {
|
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 getAvatarDrawable(): Drawable? = null
|
||||||
override fun getId() = index
|
override fun getId() = index
|
||||||
override fun getTitle() = word
|
override fun getTitle() = word
|
||||||
override fun getAvatarUri() = null
|
override fun getAvatarUri() = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,9 @@ import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import cash.z.ecc.android.R
|
import cash.z.ecc.android.R
|
||||||
import cash.z.ecc.android.ext.toAppColor
|
import cash.z.ecc.android.ext.toAppColor
|
||||||
import cash.z.ecc.android.sdk.ext.twig
|
|
||||||
import cash.z.ecc.android.ui.setup.SeedWordChip
|
import cash.z.ecc.android.ui.setup.SeedWordChip
|
||||||
|
|
||||||
class SeedWordAdapter : ChipsAdapter {
|
class SeedWordAdapter : ChipsAdapter {
|
||||||
|
|
||||||
constructor(existingAdapter: ChipsAdapter) : super(existingAdapter.mDataSource, existingAdapter.mEditText, existingAdapter.mOptions)
|
constructor(existingAdapter: ChipsAdapter) : super(existingAdapter.mDataSource, existingAdapter.mEditText, existingAdapter.mOptions)
|
||||||
|
|
||||||
|
@ -22,23 +21,23 @@ class SeedWordAdapter : ChipsAdapter {
|
||||||
else object : RecyclerView.ViewHolder(mEditText) {}
|
else object : RecyclerView.ViewHolder(mEditText) {}
|
||||||
}
|
}
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
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
|
// 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 {
|
} else {
|
||||||
val size = mDataSource.selectedChips.size
|
val size = mDataSource.selectedChips.size
|
||||||
|
|
||||||
// tricky bugfix:
|
// tricky bugfix:
|
||||||
// keep this always enabled otherwise older versions of android crash when this
|
// 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
|
// 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!
|
// editing. This is not ideal but it's better than a crash during wallet restore!
|
||||||
mEditText.isEnabled = true
|
mEditText.isEnabled = true
|
||||||
mEditText.hint = if (size < 3) {
|
mEditText.hint = if (size < 3) {
|
||||||
mEditText.isCursorVisible = true
|
mEditText.isCursorVisible = true
|
||||||
mEditText.setHintTextColor(R.color.text_light_dimmed.toAppColor())
|
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"
|
"Enter $ordinal seed word"
|
||||||
} else if(size >= 24) {
|
} else if (size >= 24) {
|
||||||
mEditText.setHintTextColor(R.color.zcashGreen.toAppColor())
|
mEditText.setHintTextColor(R.color.zcashGreen.toAppColor())
|
||||||
mEditText.isCursorVisible = false
|
mEditText.isCursorVisible = false
|
||||||
"done"
|
"done"
|
||||||
|
@ -66,10 +65,13 @@ class SeedWordAdapter : ChipsAdapter {
|
||||||
if (mDataSource.originalChips.firstOrNull { it.title == text } != null) {
|
if (mDataSource.originalChips.firstOrNull { it.title == text } != null) {
|
||||||
mDataSource.addSelectedChip(DefaultCustomChip(text))
|
mDataSource.addSelectedChip(DefaultCustomChip(text))
|
||||||
mEditText.apply {
|
mEditText.apply {
|
||||||
postDelayed({
|
postDelayed(
|
||||||
setText("")
|
{
|
||||||
requestFocus()
|
setText("")
|
||||||
}, 50L)
|
requestFocus()
|
||||||
|
},
|
||||||
|
50L
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,4 +101,3 @@ class SeedWordAdapter : ChipsAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,6 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
||||||
private fun loadNearestBirthday(network: ZcashNetwork, birthdayHeight: Int? = null) =
|
private fun loadNearestBirthday(network: ZcashNetwork, birthdayHeight: Int? = null) =
|
||||||
WalletBirthdayTool.loadNearest(ZcashWalletApp.instance, network, birthdayHeight)
|
WalletBirthdayTool.loadNearest(ZcashWalletApp.instance, network, birthdayHeight)
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Storage Helpers
|
// Storage Helpers
|
||||||
//
|
//
|
||||||
|
@ -193,7 +192,7 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
||||||
) {
|
) {
|
||||||
check(!lockBox.getBoolean(Const.Backup.HAS_SEED)) {
|
check(!lockBox.getBoolean(Const.Backup.HAS_SEED)) {
|
||||||
"Error! Cannot store a seed when one already exists! This would overwrite the" +
|
"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)
|
storeBirthday(birthday)
|
||||||
|
@ -229,5 +228,4 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
||||||
lockBox[Const.Backup.VIEWING_KEY] = vk.extfvk
|
lockBox[Const.Backup.VIEWING_KEY] = vk.extfvk
|
||||||
lockBox[Const.Backup.PUBLIC_KEY] = vk.extpub
|
lockBox[Const.Backup.PUBLIC_KEY] = vk.extpub
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -16,13 +16,13 @@ class AddressPartNumberSpan(
|
||||||
) : MetricAffectingSpan() {
|
) : MetricAffectingSpan() {
|
||||||
|
|
||||||
override fun updateMeasureState(textPaint: TextPaint) {
|
override fun updateMeasureState(textPaint: TextPaint) {
|
||||||
textPaint.baselineShift += (textPaint.ascent() / 2).toInt() // from SuperscriptSpan
|
textPaint.baselineShift += (textPaint.ascent() / 2).toInt() // from SuperscriptSpan
|
||||||
textPaint.textSize = textPaint.textSize * proportion // from RelativeSizeSpan
|
textPaint.textSize = textPaint.textSize * proportion // from RelativeSizeSpan
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateDrawState(textPaint: TextPaint) {
|
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.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.textSize = textPaint.textSize * proportion // from RelativeSizeSpan
|
||||||
textPaint.color = color // from ForegroundColorSpan
|
textPaint.color = color // from ForegroundColorSpan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,4 @@ class DebugFileTwig(fileName: String = "developer_log.txt") : TroubleshootingTwi
|
||||||
it.writeUtf8("$message\n")
|
it.writeUtf8("$message\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
* The non-standard prefixes that we will parse if other wallets send them our way.
|
||||||
*/
|
*/
|
||||||
val INCLUDE_MEMO_PREFIXES_RECOGNIZED = arrayOf(
|
val INCLUDE_MEMO_PREFIXES_RECOGNIZED = arrayOf(
|
||||||
INCLUDE_MEMO_PREFIX_STANDARD, // standard
|
INCLUDE_MEMO_PREFIX_STANDARD, // standard
|
||||||
"reply-to", // standard w/o colon
|
"reply-to", // standard w/o colon
|
||||||
"reply to:", // space instead of dash
|
"reply to:", // space instead of dash
|
||||||
"reply to", // space instead of dash w/o colon
|
"reply to", // space instead of dash w/o colon
|
||||||
"sent from:", // previous standard
|
"sent from:", // previous standard
|
||||||
"sent from" // previous standard w/o colon
|
"sent from" // previous standard w/o colon
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: move this to the SDK
|
// TODO: move this to the SDK
|
||||||
|
@ -41,7 +41,7 @@ object MemoUtil {
|
||||||
INCLUDE_MEMO_PREFIXES_RECOGNIZED.mapNotNull {
|
INCLUDE_MEMO_PREFIXES_RECOGNIZED.mapNotNull {
|
||||||
val maybeMemo = memo.substringAfterLast(it)
|
val maybeMemo = memo.substringAfterLast(it)
|
||||||
if (addressValidator(maybeMemo)) maybeMemo else null
|
if (addressValidator(maybeMemo)) maybeMemo else null
|
||||||
}.firstOrNull{ !it.isNullOrBlank() }
|
}.firstOrNull { !it.isNullOrBlank() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
package cash.z.ecc.android.ui.util
|
package cash.z.ecc.android.ui.util
|
||||||
//
|
//
|
||||||
//import android.Manifest
|
// import android.Manifest
|
||||||
//import android.content.Context
|
// import android.content.Context
|
||||||
//import android.content.pm.PackageManager
|
// import android.content.pm.PackageManager
|
||||||
//import android.os.Bundle
|
// import android.os.Bundle
|
||||||
//import android.widget.Toast
|
// import android.widget.Toast
|
||||||
//import androidx.core.content.ContextCompat
|
// import androidx.core.content.ContextCompat
|
||||||
//import androidx.fragment.app.Fragment
|
// import androidx.fragment.app.Fragment
|
||||||
//import cash.z.ecc.android.ui.MainActivity
|
// import cash.z.ecc.android.ui.MainActivity
|
||||||
//
|
//
|
||||||
//class PermissionFragment : Fragment() {
|
// class PermissionFragment : Fragment() {
|
||||||
//
|
//
|
||||||
// val activity get() = context as MainActivity
|
// val activity get() = context as MainActivity
|
||||||
//
|
//
|
||||||
|
@ -44,4 +44,4 @@ package cash.z.ecc.android.ui.util
|
||||||
// ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
|
// ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//}
|
// }
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package cash.z.ecc.android
|
package cash.z.ecc.android
|
||||||
|
|
||||||
import kotlinx.coroutines.delay
|
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 kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.math.round
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class ScratchPad {
|
class ScratchPad {
|
||||||
|
|
||||||
val t get() = System.currentTimeMillis()
|
val t get() = System.currentTimeMillis()
|
||||||
var t0 = 0L
|
var t0 = 0L
|
||||||
val Δt get() = t - t0
|
val Δt get() = t - t0
|
||||||
|
|
||||||
|
@ -23,10 +24,10 @@ class ScratchPad {
|
||||||
t0 = t
|
t0 = t
|
||||||
started = true
|
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 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 flow3 = flowOf("A", "B").onEach { delay(450); println("$Δt\temitting $it") }
|
||||||
combine(flow, flow2, flow3) { i, s, t -> "$i$s$t" }.onStart {
|
combine(flow, flow2, flow3) { i, s, t -> "$i$s$t" }.onStart {
|
||||||
t0 = t
|
t0 = t
|
||||||
}.collect {
|
}.collect {
|
||||||
|
@ -50,5 +51,4 @@ class ScratchPad {
|
||||||
println("got $it")
|
println("got $it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -2,9 +2,13 @@ package cash.z.ecc.android
|
||||||
|
|
||||||
import cash.z.ecc.android.feedback.Feedback
|
import cash.z.ecc.android.feedback.Feedback
|
||||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
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 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.Dispatchers
|
||||||
import kotlinx.coroutines.newSingleThreadContext
|
import kotlinx.coroutines.newSingleThreadContext
|
||||||
import kotlinx.coroutines.test.setMain
|
import kotlinx.coroutines.test.setMain
|
||||||
|
@ -92,7 +96,6 @@ class SendViewModelTest {
|
||||||
verify(feedback).report(sendViewModel.metrics.values.first())
|
verify(feedback).report(sendViewModel.metrics.values.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testUpdateMetrics_mined() {
|
fun testUpdateMetrics_mined() {
|
||||||
assertEquals(true, minedTx.isMined())
|
assertEquals(true, minedTx.isMined())
|
||||||
|
@ -106,5 +109,4 @@ class SendViewModelTest {
|
||||||
// Thread.sleep(100)
|
// Thread.sleep(100)
|
||||||
// assertEquals(0, sendViewModel.metrics.size)
|
// assertEquals(0, sendViewModel.metrics.size)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -4,22 +4,19 @@ buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven {
|
|
||||||
url 'https://maven.fabric.io/public'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}"
|
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.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}"
|
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:${Deps.navigationVersion}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
// mavenLocal()
|
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
@ -32,4 +29,3 @@ task clean(type: Delete) {
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultTasks 'clean', 'installZcashmainnetRelease'
|
defaultTasks 'clean', 'installZcashmainnetRelease'
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,10 @@ object Deps {
|
||||||
const val kotlinVersion = "1.4.32"
|
const val kotlinVersion = "1.4.32"
|
||||||
const val navigationVersion = "2.3.0"
|
const val navigationVersion = "2.3.0"
|
||||||
|
|
||||||
const val compileSdkVersion = 29
|
const val compileSdkVersion = 30
|
||||||
const val buildToolsVersion = "29.0.2"
|
const val buildToolsVersion = "30.0.3"
|
||||||
const val minSdkVersion = 21
|
const val minSdkVersion = 21
|
||||||
const val targetSdkVersion = 29
|
const val targetSdkVersion = 30
|
||||||
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 packageName = "cash.z.ecc.android"
|
const val packageName = "cash.z.ecc.android"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#Fri Apr 02 00:54:33 EDT 2021
|
#Fri Apr 02 00:54:33 EDT 2021
|
||||||
distributionBase=GRADLE_USER_HOME
|
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
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -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")}"
|
||||||
|
}
|
Loading…
Reference in New Issue