General fixes and cleanup.

- Allow tiny transaction amounts and improve display
- show toAddress and memo when we know it
- Bugfix: self transactions are not duplicated
- Turned Developer logs back on and cleaned up output a bit
This commit is contained in:
Kevin Gorham 2020-01-31 11:32:36 -05:00
parent 899e48b9f3
commit a357afe09a
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
11 changed files with 42 additions and 84 deletions

View File

@ -11,7 +11,7 @@ apply plugin: 'com.google.firebase.firebase-perf'
archivesBaseName = 'zcash-android-wallet' archivesBaseName = 'zcash-android-wallet'
group = 'cash.z.ecc.android' group = 'cash.z.ecc.android'
version = '1.0.0-alpha11' version = '1.0.0-dev12'
android { android {
compileSdkVersion Deps.compileSdkVersion compileSdkVersion Deps.compileSdkVersion
@ -21,7 +21,7 @@ android {
applicationId 'cash.z.ecc.android' applicationId 'cash.z.ecc.android'
minSdkVersion Deps.minSdkVersion minSdkVersion Deps.minSdkVersion
targetSdkVersion Deps.targetSdkVersion targetSdkVersion Deps.targetSdkVersion
versionCode = 1_00_00_011 versionCode = 1_00_00_112
// 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. // 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.
versionName = "$version" versionName = "$version"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@ -43,6 +43,17 @@ android {
matchingFallbacks = ['zcashmainnet', 'release'] matchingFallbacks = ['zcashmainnet', 'release']
} }
} }
// TODO: delete this test code
variantFilter { variant ->
def names = variant.flavors*.name
// To check for a certain build type, use variant.buildType.name == "<buildType>"
if (names.contains("zcashtestnet") || names.contains("Zcashtestnet") || variant.buildType.name == "release") {
// Gradle ignores any variants that satisfy the conditions above.
setIgnore(true)
}
}
buildTypes { buildTypes {
release { release {
minifyEnabled true minifyEnabled true

View File

@ -14,6 +14,8 @@ class TransactionAdapter<T : ConfirmedTransaction> :
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
&& ((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,

View File

@ -8,6 +8,7 @@ import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.toAppColor import cash.z.ecc.android.ext.toAppColor
import cash.z.ecc.android.ui.MainActivity import cash.z.ecc.android.ui.MainActivity
import cash.z.wallet.sdk.entity.ConfirmedTransaction import cash.z.wallet.sdk.entity.ConfirmedTransaction
import cash.z.wallet.sdk.ext.ZcashSdk
import cash.z.wallet.sdk.ext.convertZatoshiToZecString import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.isShielded import cash.z.wallet.sdk.ext.isShielded
import cash.z.wallet.sdk.ext.toAbbreviatedAddress import cash.z.wallet.sdk.ext.toAbbreviatedAddress
@ -60,8 +61,13 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
lineTwo = "Unknown" lineTwo = "Unknown"
} }
} }
// sanitize amount
if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amount = "< 0.001"
else if (amount.length > 8) amount = "tap to view"
} }
topText.text = lineOne topText.text = lineOne
bottomText.text = lineTwo bottomText.text = lineTwo
amountText.text = amount amountText.text = amount
@ -74,7 +80,10 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
private fun onTransactionClicked(transaction: ConfirmedTransaction) { private fun onTransactionClicked(transaction: ConfirmedTransaction) {
val txId = transaction.rawTransactionId.toTxId() val txId = transaction.rawTransactionId.toTxId()
val detailsMessage: String = "Zatoshi amount: ${transaction.value}\n\n" + val detailsMessage: String = "Zatoshi amount: ${transaction.value}\n\n" +
"Transaction: $txId" "Transaction: $txId" +
"${if (transaction.toAddress != null) "\nto: ${transaction.toAddress}" else ""}" +
"${if (transaction.memo != null) "\nmemo: ${transaction.toAddress}" else ""}"
MaterialAlertDialogBuilder(itemView.context) MaterialAlertDialogBuilder(itemView.context)
.setMessage(detailsMessage) .setMessage(detailsMessage)
.setTitle("Transaction Details") .setTitle("Transaction Details")

View File

@ -137,9 +137,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
super.onResume() super.onResume()
twig("HomeFragment.onResume resumeScope.isActive: ${resumedScope.isActive} $resumedScope") twig("HomeFragment.onResume resumeScope.isActive: ${resumedScope.isActive} $resumedScope")
viewModel.initializeMaybe() viewModel.initializeMaybe()
twig("onResume (A)")
onClearAmount() onClearAmount()
twig("onResume (B)")
viewModel.uiModels.scanReduce { old, new -> viewModel.uiModels.scanReduce { old, new ->
onModelUpdated(old, new) onModelUpdated(old, new)
new new
@ -149,18 +147,14 @@ twig("onResume (B)")
twig("exception while processing uiModels $e") twig("exception while processing uiModels $e")
throw e throw e
}.launchIn(resumedScope) }.launchIn(resumedScope)
twig("onResume (C)")
// TODO: see if there is a better way to trigger a refresh of the uiModel on resume // TODO: see if there is a better way to trigger a refresh of the uiModel on resume
// the latest one should just be in the viewmodel and we should just "resubscribe" // the latest one should just be in the viewmodel and we should just "resubscribe"
// but for some reason, this doesn't always happen, which kind of defeats the purpose // but for some reason, this doesn't always happen, which kind of defeats the purpose
// of having a cold stream in the view model // of having a cold stream in the view model
resumedScope.launch { resumedScope.launch {
twig("onResume (pre-fresh)")
viewModel.refreshBalance() viewModel.refreshBalance()
twig("onResume (post-fresh)")
} }
twig("onResume (D)")
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@ -280,22 +274,14 @@ twig("onResume (D)")
twig("onModelUpdated: $new") twig("onModelUpdated: $new")
if (binding.lottieButtonLoading.visibility != View.VISIBLE) binding.lottieButtonLoading.visibility = View.VISIBLE if (binding.lottieButtonLoading.visibility != View.VISIBLE) binding.lottieButtonLoading.visibility = View.VISIBLE
uiModel = new uiModel = new
twig("onModelUpdated (A)")
if (old?.pendingSend != new.pendingSend) { if (old?.pendingSend != new.pendingSend) {
twig("onModelUpdated (B)")
setSendAmount(new.pendingSend) setSendAmount(new.pendingSend)
twig("onModelUpdated (C)")
} }
twig("onModelUpdated (D)")
// TODO: handle stopped and disconnected flows // TODO: handle stopped and disconnected flows
setProgress(uiModel) // TODO: we may not need to separate anymore setProgress(uiModel) // TODO: we may not need to separate anymore
twig("onModelUpdated (E)")
// if (new.status = SYNCING) onSyncing(new) else onSynced(new) // if (new.status = SYNCING) onSyncing(new) else onSynced(new)
if (new.status == SYNCED) onSynced(new) else onSyncing(new) if (new.status == SYNCED) onSynced(new) else onSyncing(new)
twig("onModelUpdated (F)")
setSendEnabled(new.isSendEnabled) setSendEnabled(new.isSendEnabled)
twig("onModelUpdated (G) sendEnabled? ${new.isSendEnabled}")
twig("DONE onModelUpdated")
} }
private fun onSyncing(uiModel: HomeViewModel.UiModel) { private fun onSyncing(uiModel: HomeViewModel.UiModel) {
@ -359,7 +345,7 @@ twig("onModelUpdated (G) sendEnabled? ${new.isSendEnabled}")
// //
enum class BannerAction(val action: String) { enum class BannerAction(val action: String) {
FUND_NOW("Fund Now"), FUND_NOW(""),
CANCEL("Cancel"), CANCEL("Cancel"),
NONE(""), NONE(""),
CLEAR("clear"); CLEAR("clear");

View File

@ -96,13 +96,10 @@ class HomeViewModel @Inject constructor() : ViewModel() {
if (lastDownloadRange.isEmpty()) { if (lastDownloadRange.isEmpty()) {
100 100
} else { } else {
twig("NUMERATOR: $lastDownloadedHeight - ${lastDownloadRange.first} + 1 = ${lastDownloadedHeight - lastDownloadRange.first + 1} block(s) downloaded")
twig("DENOMINATOR: ${lastDownloadRange.last} - ${lastDownloadRange.first} + 1 = ${lastDownloadRange.last - lastDownloadRange.first + 1} block(s) to download")
val progress = val progress =
(((lastDownloadedHeight - lastDownloadRange.first + 1).coerceAtLeast(0).toFloat() / (lastDownloadRange.last - lastDownloadRange.first + 1)) * 100.0f).coerceAtMost( (((lastDownloadedHeight - lastDownloadRange.first + 1).coerceAtLeast(0).toFloat() / (lastDownloadRange.last - lastDownloadRange.first + 1)) * 100.0f).coerceAtMost(
100.0f 100.0f
).roundToInt() ).roundToInt()
twig("RESULT: $progress")
progress progress
} }
} }
@ -112,10 +109,7 @@ class HomeViewModel @Inject constructor() : ViewModel() {
if (lastScanRange.isEmpty()) { if (lastScanRange.isEmpty()) {
100 100
} else { } else {
twig("NUMERATOR: ${lastScannedHeight - lastScanRange.first + 1} block(s) scanned")
twig("DENOMINATOR: ${lastScanRange.last - lastScanRange.first + 1} block(s) to scan")
val progress = (((lastScannedHeight - lastScanRange.first + 1).coerceAtLeast(0).toFloat() / (lastScanRange.last - lastScanRange.first + 1)) * 100.0f).coerceAtMost(100.0f).roundToInt() val progress = (((lastScannedHeight - lastScanRange.first + 1).coerceAtLeast(0).toFloat() / (lastScanRange.last - lastScanRange.first + 1)) * 100.0f).coerceAtMost(100.0f).roundToInt()
twig("RESULT: $progress")
progress progress
} }
} }

View File

@ -15,9 +15,7 @@ class MagicSnakeLoader(
var isSynced: Boolean = false var isSynced: Boolean = false
set(value) { set(value) {
twig("ZZZ isSynced=$value isStarted=$isStarted")
if (value && !isStarted) { if (value && !isStarted) {
twig("ZZZ isSynced=$value TURBO sync")
lottie.progress = 1.0f lottie.progress = 1.0f
field = value field = value
return return
@ -25,19 +23,16 @@ class MagicSnakeLoader(
// it is started but it hadn't reached the synced state yet // it is started but it hadn't reached the synced state yet
if (value && !field) { if (value && !field) {
twig("ZZZ synced was $field but now is $value so playing to completion since we are now synced")
field = value field = value
playToCompletion() playToCompletion()
} else { } else {
field = value field = value
twig("ZZZ isSynced=$value and lottie.progress=${lottie.progress}")
} }
} }
var scanProgress: Int = 0 var scanProgress: Int = 0
set(value) { set(value) {
field = value field = value
twig("ZZZ scanProgress=$value")
if (value > 0) { if (value > 0) {
startMaybe() startMaybe()
onScanUpdated() onScanUpdated()
@ -47,7 +42,6 @@ class MagicSnakeLoader(
var downloadProgress: Int = 0 var downloadProgress: Int = 0
set(value) { set(value) {
field = value field = value
twig("ZZZ downloadProgress=$value")
if (value > 0) startMaybe() if (value > 0) startMaybe()
} }
@ -56,25 +50,12 @@ class MagicSnakeLoader(
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)! // after some delay, if we're still not synced then we better start animating (unless we already are)!
if (!isSynced && isPaused) { if (!isSynced && isPaused) {
twig("ZZZ yes start!")
lottie.resumeAnimation() lottie.resumeAnimation()
isPaused = false isPaused = false
isStarted = true isStarted = true
} else {
twig("ZZZ I would have started but we're already synced!")
} }
}, 200L).also { twig("ZZZ startMaybe???") } }, 200L)
} }
// set(value) {
// field = value
// if (value in 1..99 && isStopped) {
// lottie.playAnimation()
// isStopped = false
// } else if (value >= 100) {
// isStopped = true
// }
// }
private val isDownloading get() = downloadProgress in 1..99 private val isDownloading get() = downloadProgress in 1..99
private val isScanning get() = scanProgress in 1..99 private val isScanning get() = scanProgress in 1..99
@ -83,25 +64,11 @@ class MagicSnakeLoader(
lottie.addAnimatorUpdateListener(this) lottie.addAnimatorUpdateListener(this)
} }
// downloading = true
// lottieAnimationView.playAnimation()
// lottieAnimationView.addAnimatorUpdateListener { valueAnimator ->
// // Set animation progress
// val progress = (valueAnimator.animatedValue as Float * 100).toInt()
// progressTv.text = "Progress: $progress%"
//
// if (downloading && progress >= 40) {
// lottieAnimationView.progress = 0f
// }
// }
override fun onAnimationUpdate(animation: ValueAnimator) { override fun onAnimationUpdate(animation: ValueAnimator) {
if (isSynced || isPaused) { if (isSynced || isPaused) {
// playToCompletion() // playToCompletion()
return return
} }
twig("ZZZ")
twig("ZZZ\t\tonAnimationUpdate(${animation.animatedValue})")
// if we are scanning, then set the animation progress, based on the scan progress // if we are scanning, then set the animation progress, based on the scan progress
// if we're not scanning, then we're looping // if we're not scanning, then we're looping
@ -112,7 +79,6 @@ 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) {
twig("ZZZ applyScanProgress($frame) : isPaused=$isPaused isStarted=$isStarted min=${lottie.minFrame} max=${lottie.maxFrame}")
// 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) {
onScanUpdated() onScanUpdated()
@ -126,7 +92,6 @@ class MagicSnakeLoader(
} }
private fun onScanUpdated() { private fun onScanUpdated() {
twig("ZZZ onScanUpdated : isPaused=$isPaused")
if (isSynced) { if (isSynced) {
// playToCompletion() // playToCompletion()
return return
@ -137,7 +102,6 @@ class MagicSnakeLoader(
val scanRange = scanningEndFrame - scanningStartFrame val scanRange = scanningEndFrame - scanningStartFrame
val scanRangeProgress = scanProgress.toFloat() / 100.0f * scanRange.toFloat() val scanRangeProgress = scanProgress.toFloat() / 100.0f * scanRange.toFloat()
lottie.progress = (scanningStartFrame.toFloat() + scanRangeProgress) / totalFrames lottie.progress = (scanningStartFrame.toFloat() + scanRangeProgress) / totalFrames
twig("ZZZ onScanUpdated : scanRange=$scanRange scanRangeProgress=$scanRangeProgress lottie.progress=${(scanningStartFrame.toFloat() + scanRangeProgress)}/$totalFrames=${lottie.progress}")
} }
} }
@ -160,17 +124,14 @@ class MagicSnakeLoader(
} }
private fun allowLoop(frame: Int) { private fun allowLoop(frame: Int) {
twig("ZZZ allowLoop($frame) : isPaused=$isPaused")
unpause() unpause()
if (frame >= scanningStartFrame) { if (frame >= scanningStartFrame) {
twig("ZZZ resetting to 0f (LOOPING)")
lottie.progress = 0f lottie.progress = 0f
} }
} }
fun unpause() { fun unpause() {
if (isPaused) { if (isPaused) {
twig("ZZZ unpausing")
lottie.resumeAnimation() lottie.resumeAnimation()
isPaused = false isPaused = false
} }
@ -178,7 +139,6 @@ class MagicSnakeLoader(
fun pause() { fun pause() {
if (!isPaused) { if (!isPaused) {
twig("ZZZ pausing")
lottie.pauseAnimation() lottie.pauseAnimation()
isPaused = true isPaused = true
} }

View File

@ -81,8 +81,8 @@ class SendViewModel @Inject constructor() : ViewModel() {
synchronizer.validateAddress(toAddress).isNotValid -> { synchronizer.validateAddress(toAddress).isNotValid -> {
emit("Please enter a valid address") emit("Please enter a valid address")
} }
zatoshiAmount < ZcashSdk.MINERS_FEE_ZATOSHI -> { zatoshiAmount <= 1 -> {
emit("Too little! Please enter at least 0.0001") emit("Too little! Please enter at least 1 Zatoshi.")
} }
maxZatoshi != null && zatoshiAmount > maxZatoshi -> { maxZatoshi != null && zatoshiAmount > maxZatoshi -> {
emit( "Too much! Please enter no more than ${maxZatoshi.convertZatoshiToZecString(8)}") emit( "Too much! Please enter no more than ${maxZatoshi.convertZatoshiToZecString(8)}")

View File

@ -62,7 +62,7 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
} }
suspend fun importWallet(seedPhrase: String, birthdayHeight: Int): Initializer { suspend fun importWallet(seedPhrase: String, birthdayHeight: Int): Initializer {
twig("Importing wallet") twig("Importing wallet. Requested birthday: $birthdayHeight")
return ZcashWalletApp.component.initializerSubcomponent().create(Initializer.DefaultBirthdayStore(ZcashWalletApp.instance, birthdayHeight)).run { return ZcashWalletApp.component.initializerSubcomponent().create(Initializer.DefaultBirthdayStore(ZcashWalletApp.instance, birthdayHeight)).run {
initializer().apply { initializer().apply {
import(importWallet(seedPhrase.toCharArray()), birthdayStore().getBirthday()) import(importWallet(seedPhrase.toCharArray()), birthdayStore().getBirthday())

View File

@ -2,6 +2,6 @@
<shape <shape
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="10dp" /> <corners android:radius="10dp" />
<stroke android:width="1dp" android:color="#282828"/> <stroke android:width="1dp" android:color="@color/background_banner_stroke"/>
<solid android:color="@color/background_banner"/> <solid android:color="@color/background_banner"/>
</shape> </shape>

View File

@ -58,6 +58,7 @@
but have a more useful name for use in code --> but have a more useful name for use in code -->
<color name="background_banner">@color/zcashBlack_dark</color> <color name="background_banner">@color/zcashBlack_dark</color>
<color name="background_banner_stroke">#282828</color>
<color name="scan_overlay_background">@color/zcashBlack_87</color> <color name="scan_overlay_background">@color/zcashBlack_87</color>
<color name="spacer">#1FBB666A</color> <color name="spacer">#1FBB666A</color>
<color name="text_send_amount_disabled">@color/text_light</color> <color name="text_send_amount_disabled">@color/text_light</color>

View File

@ -1,23 +1,18 @@
# Project-wide Gradle settings. ## For more details on how to configure your build environment visit
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m # Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the #Wed Jan 29 09:45:08 EST 2020
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
android.enableJetifier=true
dagger.fastInit=enabled org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
android.useAndroidX=true
dagger.fastInit=enabled