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'
group = 'cash.z.ecc.android'
version = '1.0.0-alpha11'
version = '1.0.0-dev12'
android {
compileSdkVersion Deps.compileSdkVersion
@ -21,7 +21,7 @@ android {
applicationId 'cash.z.ecc.android'
minSdkVersion Deps.minSdkVersion
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.
versionName = "$version"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@ -43,6 +43,17 @@ android {
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 {
release {
minifyEnabled true

View File

@ -14,6 +14,8 @@ class TransactionAdapter<T : ConfirmedTransaction> :
oldItem: T,
newItem: T
) = oldItem.minedHeight == newItem.minedHeight && oldItem.noteId == newItem.noteId
// bugfix: distinguish between self-transactions so they don't overwrite each other in the UI // TODO confirm that this is working, as intended
&& ((oldItem.raw == null && newItem.raw == null) || (oldItem.raw != null && newItem.raw != null && oldItem.raw!!.contentEquals(newItem.raw!!)))
override fun areContentsTheSame(
oldItem: T,

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.ui.MainActivity
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.isShielded
import cash.z.wallet.sdk.ext.toAbbreviatedAddress
@ -60,8 +61,13 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
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
bottomText.text = lineTwo
amountText.text = amount
@ -74,7 +80,10 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
private fun onTransactionClicked(transaction: ConfirmedTransaction) {
val txId = transaction.rawTransactionId.toTxId()
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)
.setMessage(detailsMessage)
.setTitle("Transaction Details")

View File

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

View File

@ -96,13 +96,10 @@ class HomeViewModel @Inject constructor() : ViewModel() {
if (lastDownloadRange.isEmpty()) {
100
} 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 =
(((lastDownloadedHeight - lastDownloadRange.first + 1).coerceAtLeast(0).toFloat() / (lastDownloadRange.last - lastDownloadRange.first + 1)) * 100.0f).coerceAtMost(
100.0f
).roundToInt()
twig("RESULT: $progress")
progress
}
}
@ -112,10 +109,7 @@ class HomeViewModel @Inject constructor() : ViewModel() {
if (lastScanRange.isEmpty()) {
100
} 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()
twig("RESULT: $progress")
progress
}
}

View File

@ -15,9 +15,7 @@ class MagicSnakeLoader(
var isSynced: Boolean = false
set(value) {
twig("ZZZ isSynced=$value isStarted=$isStarted")
if (value && !isStarted) {
twig("ZZZ isSynced=$value TURBO sync")
lottie.progress = 1.0f
field = value
return
@ -25,19 +23,16 @@ class MagicSnakeLoader(
// it is started but it hadn't reached the synced state yet
if (value && !field) {
twig("ZZZ synced was $field but now is $value so playing to completion since we are now synced")
field = value
playToCompletion()
} else {
field = value
twig("ZZZ isSynced=$value and lottie.progress=${lottie.progress}")
}
}
var scanProgress: Int = 0
set(value) {
field = value
twig("ZZZ scanProgress=$value")
if (value > 0) {
startMaybe()
onScanUpdated()
@ -47,7 +42,6 @@ class MagicSnakeLoader(
var downloadProgress: Int = 0
set(value) {
field = value
twig("ZZZ downloadProgress=$value")
if (value > 0) startMaybe()
}
@ -56,25 +50,12 @@ class MagicSnakeLoader(
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) {
twig("ZZZ yes start!")
lottie.resumeAnimation()
isPaused = false
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 isScanning get() = scanProgress in 1..99
@ -83,25 +64,11 @@ class MagicSnakeLoader(
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) {
if (isSynced || isPaused) {
// playToCompletion()
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're not scanning, then we're looping
@ -112,7 +79,6 @@ class MagicSnakeLoader(
private val acceptablePauseFrames = arrayOf(33,34,67,68,99)
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
if (isPaused) {
onScanUpdated()
@ -126,7 +92,6 @@ class MagicSnakeLoader(
}
private fun onScanUpdated() {
twig("ZZZ onScanUpdated : isPaused=$isPaused")
if (isSynced) {
// playToCompletion()
return
@ -137,7 +102,6 @@ class MagicSnakeLoader(
val scanRange = scanningEndFrame - scanningStartFrame
val scanRangeProgress = scanProgress.toFloat() / 100.0f * scanRange.toFloat()
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) {
twig("ZZZ allowLoop($frame) : isPaused=$isPaused")
unpause()
if (frame >= scanningStartFrame) {
twig("ZZZ resetting to 0f (LOOPING)")
lottie.progress = 0f
}
}
fun unpause() {
if (isPaused) {
twig("ZZZ unpausing")
lottie.resumeAnimation()
isPaused = false
}
@ -178,7 +139,6 @@ class MagicSnakeLoader(
fun pause() {
if (!isPaused) {
twig("ZZZ pausing")
lottie.pauseAnimation()
isPaused = true
}

View File

@ -81,8 +81,8 @@ class SendViewModel @Inject constructor() : ViewModel() {
synchronizer.validateAddress(toAddress).isNotValid -> {
emit("Please enter a valid address")
}
zatoshiAmount < ZcashSdk.MINERS_FEE_ZATOSHI -> {
emit("Too little! Please enter at least 0.0001")
zatoshiAmount <= 1 -> {
emit("Too little! Please enter at least 1 Zatoshi.")
}
maxZatoshi != null && zatoshiAmount > maxZatoshi -> {
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 {
twig("Importing wallet")
twig("Importing wallet. Requested birthday: $birthdayHeight")
return ZcashWalletApp.component.initializerSubcomponent().create(Initializer.DefaultBirthdayStore(ZcashWalletApp.instance, birthdayHeight)).run {
initializer().apply {
import(importWallet(seedPhrase.toCharArray()), birthdayStore().getBirthday())

View File

@ -2,6 +2,6 @@
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<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"/>
</shape>

View File

@ -58,6 +58,7 @@
but have a more useful name for use in code -->
<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="spacer">#1FBB666A</color>
<color name="text_send_amount_disabled">@color/text_light</color>

View File

@ -1,23 +1,18 @@
# Project-wide Gradle settings.
# 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
## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# 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.
# 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
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# 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":
#Wed Jan 29 09:45:08 EST 2020
kotlin.code.style=official
dagger.fastInit=enabled
android.enableJetifier=true
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
android.useAndroidX=true
dagger.fastInit=enabled