checkpoint: send is functional and shows up in active transactions.

This commit is contained in:
Kevin Gorham 2019-02-07 22:18:32 -05:00 committed by Kevin Gorham
parent 3987fe820b
commit 78c18faee0
20 changed files with 250 additions and 235 deletions

View File

@ -49,15 +49,17 @@ dependencies {
implementation deps.androidx.navigation.ui
implementation deps.androidx.navigation.uiKtx
implementation deps.material
implementation 'androidx.multidex:multidex:2.0.1'
implementation deps.androidx.multidex
// Kotlin
implementation deps.kotlin.stdlib
implementation deps.kotlin.reflect
implementation deps.kotlin.coroutines.core
implementation deps.kotlin.coroutines.android
// Zcash
implementation deps.zcash.walletSdk
compile project(path: ':qrecycler')
// TODO: get the AAR to provide these
implementation "io.grpc:grpc-okhttp:1.17.1"
@ -75,11 +77,10 @@ dependencies {
// Other
implementation deps.speeddial
implementation "com.airbnb.android:lottie:2.7.0"
compile 'com.facebook.stetho:stetho:1.5.0'
implementation deps.lottie
debugImplementation deps.stetho
testImplementation deps.junit
androidTestImplementation deps.androidx.test.runner
androidTestImplementation deps.androidx.test.espresso
compile project(path: ':qrecycler')
}

View File

@ -1,89 +0,0 @@
package cash.z.android.wallet.data
import androidx.room.Room
import androidx.room.RoomDatabase
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.android.wallet.vo.WalletTransaction
import cash.z.android.wallet.vo.WalletTransactionStatus
import cash.z.wallet.sdk.dao.BlockDao
import cash.z.wallet.sdk.dao.NoteDao
import cash.z.wallet.sdk.dao.TransactionDao
import cash.z.wallet.sdk.db.DerivedDataDb
import cash.z.wallet.sdk.vo.NoteQuery
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import java.math.BigDecimal
class ReceivedTransactionRepository(val scope: CoroutineScope) : TransactionRepository {
private var db = injectDb()
private lateinit var transactions: TransactionDao
private lateinit var blocks: BlockDao
private lateinit var notes: NoteDao
private var existingTransactions = linkedSetOf<WalletTransaction>()
private var existingBalance = BigDecimal.ZERO
private var balanceChannel = BroadcastChannel<BigDecimal>(100)
private fun injectDb() = Room
.databaseBuilder(ZcashWalletApplication.instance, DerivedDataDb::class.java, "tmp")
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.fallbackToDestructiveMigration()
.build()
.apply {
transactions = transactionDao()
blocks = blockDao()
notes = noteDao()
}
/**
* Just send a sample stream of balances, every so often
*/
override fun balance() = balanceChannel.openSubscription()
/**
* Just send a sample stream of transactions, every so often
*/
override fun transactions(): ReceiveChannel<WalletTransaction> = scope.produce {
while (isActive) {
delay(1500L)
val newTransactions = checkForNewTransactions()
newTransactions?.forEach {
existingTransactions.add(it)
send(it)
updateBalance(it)
}
}
}
private suspend fun updateBalance(tx: WalletTransaction) {
val multiplier = when (tx.status) {
WalletTransactionStatus.SENT -> -1.0
WalletTransactionStatus.RECEIVED -> 1.0
}
existingBalance += tx.amount.multiply(BigDecimal(multiplier))
balanceChannel.send(existingBalance)
}
private fun checkForNewTransactions(): Set<WalletTransaction>? {
val count = notes.count()
if(count == existingTransactions.size) return null
val notes = notes.getAll().map { toWalletTransaction(it) }
return notes.subtract(existingTransactions)
}
private fun toWalletTransaction(note: NoteQuery): WalletTransaction {
return WalletTransaction(
height = note.height,
status = WalletTransactionStatus.RECEIVED,
amount = BigDecimal(note.value / 1e8),
timestamp = note.time
)
}
}

View File

@ -1,8 +1,7 @@
package cash.z.android.wallet.data
import android.text.format.DateUtils
import cash.z.android.wallet.vo.WalletTransaction
import cash.z.android.wallet.vo.WalletTransactionStatus
import cash.z.wallet.sdk.dao.WalletTransaction
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.produce
@ -11,6 +10,8 @@ import kotlinx.coroutines.isActive
import java.math.BigDecimal
import kotlin.math.roundToLong
import kotlin.random.Random
import kotlin.random.nextInt
import kotlin.random.nextLong
class SampleTransactionRepository(val scope: CoroutineScope) : TransactionRepository {
/**
@ -32,34 +33,27 @@ class SampleTransactionRepository(val scope: CoroutineScope) : TransactionReposi
var oldestTimestamp = System.currentTimeMillis() - (4 * DateUtils.WEEK_IN_MILLIS)
while (isActive) {
delay(1500L)
send(createSampleTransaction(oldestTimestamp).also { oldestTimestamp = it.timestamp })
send(createSampleTransaction(oldestTimestamp).also { oldestTimestamp = it.timeInSeconds * 1000 })
}
}
private fun createSampleTransaction(): WalletTransaction {
return createSampleTransaction(System.currentTimeMillis() - (4 * DateUtils.WEEK_IN_MILLIS))
}
private fun createSampleTransaction(after: Long): WalletTransaction {
val now = System.currentTimeMillis()
val delta = now - after
val window = after + (0.05 * delta).roundToLong()
val amount = BigDecimal(Random.nextDouble(0.1, 15.0) * arrayOf(-1, 1).random())
val status = if (amount > BigDecimal.ZERO) WalletTransactionStatus.SENT else WalletTransactionStatus.RECEIVED
val timestamp = Random.nextLong(after, window)
private fun createSampleTransaction(oldestTimestamp: Long): WalletTransaction {
// up to 20% of the delta
val upperBound = System.currentTimeMillis() + Math.round(0.2 * (System.currentTimeMillis() - oldestTimestamp))
val txId = Random.nextInt(0..(Int.MAX_VALUE - 1))
val value = Random.nextLong(1L..1_500_000_000L) - 750_000_000L
val height = Random.nextInt(0..(Int.MAX_VALUE - 1))
val isSend = value > 0L
val time = Random.nextLong(oldestTimestamp..upperBound)
val isMined = Random.nextBoolean()
return WalletTransaction(
timestamp.toInt(),
status,
timestamp,
amount
txId = txId,
value = value,
height = height,
isSend = isSend,
timeInSeconds = time/1000,
isMined = isMined
)
}
private fun createSampleTransactions(size: Int): MutableList<WalletTransaction> {
val transactions = mutableListOf<WalletTransaction>()
repeat(size) {
transactions.add(createSampleTransaction())
}
return transactions
}
}

View File

@ -1,6 +1,6 @@
package cash.z.android.wallet.data
import cash.z.android.wallet.vo.WalletTransaction
import cash.z.wallet.sdk.dao.WalletTransaction
import kotlinx.coroutines.channels.ReceiveChannel
import java.math.BigDecimal

View File

@ -1,5 +1,6 @@
package cash.z.android.wallet.di.module
import android.util.Log
import cash.z.android.wallet.BuildConfig
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.android.wallet.di.module.Properties.CACHE_DB_NAME
@ -7,12 +8,17 @@ import cash.z.android.wallet.di.module.Properties.COMPACT_BLOCK_PORT
import cash.z.android.wallet.di.module.Properties.COMPACT_BLOCK_SERVER
import cash.z.android.wallet.di.module.Properties.DATA_DB_NAME
import cash.z.android.wallet.di.module.Properties.SEED_PROVIDER
import cash.z.android.wallet.di.module.Properties.SPENDING_KEY_PROVIDER
import cash.z.wallet.sdk.data.*
import cash.z.wallet.sdk.jni.JniConverter
import cash.z.wallet.sdk.secure.Wallet
import dagger.Module
import dagger.Provides
import okio.ByteString
import java.nio.charset.Charset
import javax.inject.Singleton
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
/**
* Module that contributes all the objects necessary for the synchronizer, which is basically everything that has
@ -51,7 +57,7 @@ internal object SynchronizerModule {
@Provides
@Singleton
fun provideWallet(application: ZcashWalletApplication, converter: JniConverter, twigger: Twig): Wallet {
return Wallet(converter, application.getDatabasePath(DATA_DB_NAME).absolutePath, "${application.cacheDir.absolutePath}/params", seedProvider = SEED_PROVIDER, logger = twigger)
return Wallet(converter, application.getDatabasePath(DATA_DB_NAME).absolutePath, "${application.cacheDir.absolutePath}/params", seedProvider = SEED_PROVIDER, spendingKeyProvider = SPENDING_KEY_PROVIDER, logger = twigger)
}
@JvmStatic
@ -64,7 +70,11 @@ internal object SynchronizerModule {
@JvmStatic
@Provides
@Singleton
fun provideJniConverter(): JniConverter = JniConverter()
fun provideJniConverter(): JniConverter {
return JniConverter().also {
if (BuildConfig.DEBUG) it.initLogs()
}
}
@JvmStatic
@Provides
@ -85,11 +95,12 @@ internal object SynchronizerModule {
// TODO: load most of these properties in later, perhaps from settings
object Properties {
val COMPACT_BLOCK_SERVER = Servers.ZCASH_TESTNET.host
val COMPACT_BLOCK_SERVER = Servers.EMULATOR.host
const val COMPACT_BLOCK_PORT = 9067
const val CACHE_DB_NAME = "wallet_cache21.db"
const val DATA_DB_NAME = "wallet_data21.db"
const val CACHE_DB_NAME = "wallet_cache50.db"
const val DATA_DB_NAME = "wallet_data50.db"
val SEED_PROVIDER = SampleSeedProvider("dummyseed")
val SPENDING_KEY_PROVIDER = SampleSpendingKeyProvider("dummyseed")
}
enum class Servers(val host: String) {
@ -98,3 +109,52 @@ enum class Servers(val host: String) {
BOLT_TESTNET("ec2-34-228-10-162.compute-1.amazonaws.com"),
ZCASH_TESTNET("lightwalletd.z.cash")
}
class SampleImportedSeedProvider(private val seedHex: String) : ReadOnlyProperty<Any?, ByteArray> {
override fun getValue(thisRef: Any?, property: KProperty<*>): ByteArray {
val bytes = ByteString.decodeHex(seedHex).toByteArray()
val stringBytes = String(bytes, Charset.forName("UTF-8"))
Log.e("TWIG-x", "byteString: $stringBytes")
return decodeHex(seedHex).also { Log.e("TWIG-x", "$it") }
}
fun decodeHex(hex: String): ByteArray {
val result = ByteArray(hex.length / 2)
for (i in result.indices) {
val d1 = decodeHexDigit(hex[i * 2]) shl 4
val d2 = decodeHexDigit(hex[i * 2 + 1])
result[i] = (d1 + d2).toByte()
}
return result
}
private fun decodeHexDigit(c: Char): Int {
if (c in '0'..'9') return c - '0'
if (c in 'a'..'f') return c - 'a' + 10
if (c in 'A'..'F') return c - 'A' + 10
throw IllegalArgumentException("Unexpected hex digit: $c")
}
}
class SampleSpendingKeyProvider2(private val seedValue: String) : ReadOnlyProperty<Any?, String> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
// dynamically generating keyes, based on seed is out of scope for this sample
return "secret-extended-key-test1q0f0urnmqqqqpqxlree5urprcmg9pdgvr2c88qhm862etv65eu84r9zwannpz4g88299xyhv7wf9xke5cag653jlwwwyxrymfraqsnz8qfgds70qjammscxxyl7s7p9xz9w906epdpy8ztsjd7ez7phcd5vj7syx68sjskqs8j9lef2uuacghsh8puuvsy9u25pfvcdznta33qe6xh5lrlnhdkgymnpdug4jm6tpf803cad6tqa9c0ewq9l03fqxatevm97jmuv8u0ccxjews5"
}
}

View File

@ -5,18 +5,16 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import cash.z.android.wallet.R
import cash.z.android.wallet.extention.toAppColor
import cash.z.android.wallet.vo.WalletTransaction
import java.math.BigDecimal
import java.math.MathContext
import java.math.RoundingMode
import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.ext.toZec
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.absoluteValue
class TransactionAdapter : ListAdapter<WalletTransaction, TransactionViewHolder>(DIFF_CALLBACK) {
@ -36,18 +34,18 @@ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val status = itemView.findViewById<View>(R.id.view_transaction_status)
private val timestamp = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
private val amount = itemView.findViewById<TextView>(R.id.text_transaction_amount)
private val background = itemView.findViewById<View>(R.id.container_transaction)
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
fun bind(tx: WalletTransaction) {
val sign = if(tx.amount > BigDecimal.ZERO) "+" else "-"
val amountColor = if(tx.amount > BigDecimal.ZERO) R.color.colorPrimary else R.color.text_dark_dimmed
status.setBackgroundColor(tx.status.color.toAppColor())
timestamp.text = formatter.format(tx.timestamp)
amount.text = String.format("$sign %,.3f", tx.amount.round(MathContext(3, RoundingMode.HALF_EVEN )).abs())
val sign = if(tx.isSend) "-" else "+"
val amountColor = if (tx.isSend) R.color.text_dark_dimmed else R.color.colorPrimary
val transactionColor = if(tx.isSend) R.color.send_associated else R.color.receive_associated
val zecAbsoluteValue = tx.value.absoluteValue.toZec(3)
status.setBackgroundColor(transactionColor.toAppColor())
timestamp.text = if (!tx.isMined || tx.timeInSeconds == 0L) "Pending" else formatter.format(tx.timeInSeconds * 1000)
Log.e("TWIG-z", "TimeInSeconds: ${tx.timeInSeconds}")
amount.text = "$sign$zecAbsoluteValue"
amount.setTextColor(amountColor.toAppColor())
Log.e("TWIG-u", "formatted timestamp: ${tx.timestamp} for value ${amount.text}")
}
}

View File

@ -1,6 +1,5 @@
package cash.z.android.wallet.ui.fragment
import android.animation.Animator
import android.app.Activity
import android.os.Bundle
import android.text.SpannableString
@ -10,16 +9,17 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import androidx.drawerlayout.widget.DrawerLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.Transition
import androidx.transition.TransitionInflater
import cash.z.android.wallet.R
import cash.z.android.wallet.extention.Toaster
import cash.z.android.wallet.extention.toAppColor
import cash.z.android.wallet.extention.toAppString
import cash.z.android.wallet.extention.tryIgnore
@ -28,22 +28,21 @@ import cash.z.android.wallet.ui.adapter.TransactionAdapter
import cash.z.android.wallet.ui.presenter.HomePresenter
import cash.z.android.wallet.ui.util.AlternatingRowColorDecoration
import cash.z.android.wallet.ui.util.TopAlignedSpan
import cash.z.android.wallet.vo.WalletTransaction
import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.data.ActiveSendTransaction
import cash.z.wallet.sdk.data.ActiveTransaction
import cash.z.wallet.sdk.data.TransactionState
import cash.z.wallet.sdk.ext.toZec
import com.google.android.material.snackbar.Snackbar
import com.leinardi.android.speeddial.SpeedDialActionItem
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.android.synthetic.main.activity_main_first_run.*
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.include_home_content.*
import kotlinx.android.synthetic.main.include_home_header.*
import kotlinx.coroutines.launch
import kotlin.random.Random
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main_first_run.*
import kotlin.random.nextLong
@ -56,6 +55,7 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
lateinit var homePresenter: HomePresenter
lateinit var transactionAdapter: TransactionAdapter
private var viewsInitialized = false
private var snackbar: Snackbar? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@ -97,15 +97,16 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
// forceRedraw()
// toggleViews(false)
} else {
forceRedraw()
toggleViews(false)
}
}
button_active_transaction_cancel.setOnClickListener {
onCancelActiveTransaction()
val transaction = button_active_transaction_cancel.tag as? ActiveSendTransaction
if (transaction != null) {
homePresenter.onCancelActiveTransaction(transaction)
} else {
Toaster.short("Error: unable to find transaction to cancel!")
}
}
refresh_layout.setOnRefreshListener {
@ -167,7 +168,8 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
//
// View API
//
// TODO: pull some of this logic into the presenter, particularly the part that deals with ZEC <-> USD price conversion
//TODO: pull some of this logic into the presenter, particularly the part that deals with ZEC <-> USD price conversion
override fun updateBalance(old: Long, new: Long) {
//TODO: remove this kind of thing
snackbar?.dismiss()
@ -184,18 +186,19 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
override fun setTransactions(transactions: List<WalletTransaction>) {
Log.e("TWIG-t", "submitList called with ${transactions.size} transactions")
transactionAdapter.submitList(transactions)
recycler_transactions.smoothScrollToPosition(0)
recycler_transactions.postDelayed({
recycler_transactions.smoothScrollToPosition(0)
}, 100L)
if (transactions.isNotEmpty()) setFirstRunShown(false)
}
var snackbar: Snackbar? = null
override fun showProgress(progress: Int) {
Log.e("TWIG", "showing progress of $progress")
// TODO: improve this with Lottie animation. but for now just use the empty view for downloading...
// var hasEmptyViews = group_empty_view_items.visibility == View.VISIBLE
// if(!viewsInitialized) toggleViews(true)
//
val message = if(progress >= 100) "Download complete! Processing...\n(this may take a while)" else "Downloading blocks ($progress%)"
val message = if(progress >= 100) "Download complete! Processing blocks..." else "Downloading blocks ($progress%)"
// text_wallet_message.text = message
if (snackbar == null && progress <= 50) {
@ -210,6 +213,19 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
}
}
fun showOkSnack(message: String) {
if (snackbar == null) {
snackbar = Snackbar.make(view!!, "$message", Snackbar.LENGTH_INDEFINITE).setAction("OK") {
snackbar?.dismiss()
snackbar = null
}
snackbar?.show()
} else {
snackbar?.setText(message)
if (snackbar?.isShownOrQueued != true) snackbar?.show()
}
}
override fun setActiveTransactions(activeTransactionMap: Map<ActiveTransaction, TransactionState>) {
if (activeTransactionMap.isEmpty()) {
setActiveTransactionsShown(false)
@ -224,7 +240,12 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
// TODO: update remaining transactions
}
override fun onCancelledTooLate() {
showOkSnack("Oops! It was too late to cancel!")
}
private fun updatePrimaryTransaction(transaction: ActiveTransaction, transactionState: TransactionState) {
Log.e("TWIG", "setting transaction state to ${transactionState::class.simpleName}")
var title = "Active Transaction"
var subtitle = "Processing..."
when (transactionState) {
@ -232,13 +253,14 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
header_active_transaction.visibility = View.VISIBLE
title = "Preparing ${transaction.value.toZec(3)} ZEC"
subtitle = "to ${(transaction as ActiveSendTransaction).toAddress}"
button_active_transaction_cancel.text = "cancel"
setActiveTransactionRaised(true)
setTransactionActive(transaction, true)
}
TransactionState.SendingToNetwork -> {
title = "Sending Transaction"
subtitle = "to ${(transaction as ActiveSendTransaction).toAddress}"
button_active_transaction_cancel.text = "${transaction.value/1000L}"
text_active_transaction_value.text = "${transaction.value/1000L}"
text_active_transaction_value.visibility = View.VISIBLE
button_active_transaction_cancel.visibility = View.GONE
}
is TransactionState.Failure -> {
lottie_active_transaction.setAnimation(R.raw.lottie_send_failure)
@ -250,18 +272,27 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
else -> "Unrecoginzed error"
}
button_active_transaction_cancel.visibility = View.GONE
onCancelActiveTransaction()
text_active_transaction_value.visibility = View.GONE
setTransactionActive(transaction, false)
}
is TransactionState.AwaitingConfirmations -> {
if (transactionState.confirmationCount < 1) {
lottie_active_transaction.setAnimation(R.raw.lottie_send_success)
lottie_active_transaction.playAnimation()
title = "ZEC Sent"
subtitle = "Awaiting Confirmations (${transactionState.confirmationCount}/10)"
subtitle = "Today at 4:46pm"
text_active_transaction_value.text = transaction.value.toZec(3).toString()
text_active_transaction_value.visibility = View.VISIBLE
button_active_transaction_cancel.visibility = View.GONE
} else {
// play confirmation counting animation
}
}
is TransactionState.Cancelled -> {
title = text_active_transaction_title.text.toString()
subtitle = text_active_transaction_subtitle.text.toString()
setTransactionActive(transaction, false)
}
}
text_active_transaction_title.text = title
text_active_transaction_subtitle.text = subtitle
@ -347,6 +378,7 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
}
private fun onActiveTransactionTransitionEnd() {
// TODO: investigate if this fix is still required after getting transition animation working again
// fixes a bug where the translation gets lost, during animation. As a nice side effect, visually, it makes the view appear to settle in to position
header_active_transaction.translationZ = 10.0f
button_active_transaction_cancel.apply {
@ -355,38 +387,30 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
}
}
private fun onCancelActiveTransaction() {
setActiveTransactionRaised(false)
button_active_transaction_cancel.text = "cancel"
}
private fun setActiveTransactionRaised(isRaised: Boolean) {
button_active_transaction_cancel.isEnabled = isRaised
header_active_transaction.animate().apply {
translationZ(if (isRaised) 10f else 0f)
duration = 200L
interpolator = AccelerateInterpolator()
setListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
header_active_transaction.apply {
if(translationZ == 0f) setBackgroundResource(0)
}
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
} )
private fun setTransactionActive(transaction: ActiveTransaction, isActive: Boolean) {
// TODO: get view for transaction, mostly likely keep a sparse array of these or something
if (isActive) {
button_active_transaction_cancel.setText(R.string.cancel)
button_active_transaction_cancel.isEnabled = true
button_active_transaction_cancel.tag = transaction
header_active_transaction.animate().apply {
translationZ(10f)
duration = 200L
interpolator = DecelerateInterpolator()
}
} else {
button_active_transaction_cancel.setText(R.string.cancelled)
button_active_transaction_cancel.isEnabled = false
button_active_transaction_cancel.tag = null
header_active_transaction.animate().apply {
translationZ(2f)
duration = 300L
interpolator = AccelerateInterpolator()
}
lottie_active_transaction.cancelAnimation()
}
}
inner class HomeTransitionListener : Transition.TransitionListener {
override fun onTransitionStart(transition: Transition) {
onActiveTransactionTransitionStart()

View File

@ -13,6 +13,7 @@ import cash.z.android.wallet.R
import cash.z.android.wallet.ui.activity.MainActivity
import cash.z.android.wallet.ui.util.AddressPartNumberSpan
import cash.z.wallet.sdk.jni.JniConverter
import cash.z.wallet.sdk.secure.Wallet
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.android.synthetic.main.fragment_receive.*
@ -27,7 +28,7 @@ class ReceiveFragment : BaseFragment() {
lateinit var qrecycler: QRecycler
@Inject
lateinit var converter: JniConverter
lateinit var wallet: Wallet
lateinit var addressParts: Array<TextView>
@ -87,7 +88,7 @@ class ReceiveFragment : BaseFragment() {
// TODO: replace with tiered load. First check memory reference (textview contents?) then check DB, then load from JNI and write to DB
private fun loadAddress(): String {
return converter.getAddress("dummyseed".toByteArray())
return wallet.getAddress()
}
}

View File

@ -8,6 +8,9 @@ import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.core.content.ContextCompat.getSystemService
import androidx.core.content.getSystemService
import androidx.core.text.toSpannable
import androidx.databinding.DataBindingUtil
import androidx.navigation.fragment.FragmentNavigatorExtras
@ -21,6 +24,7 @@ import cash.z.android.wallet.ui.activity.MainActivity
import cash.z.android.wallet.ui.presenter.SendPresenter
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.launch
import java.text.DecimalFormat
@ -195,6 +199,10 @@ class SendFragment : BaseFragment(), SendPresenter.SendView {
//
private fun showSendDialog() {
// hide soft keyboard
mainActivity.getSystemService<InputMethodManager>()
?.hideSoftInputFromWindow(view?.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
setSendEnabled(false) // partially because we need to lower the button elevation
binding.groupDialogSend.visibility = View.VISIBLE
}
@ -208,10 +216,10 @@ class SendFragment : BaseFragment(), SendPresenter.SendView {
binding.buttonSendZec.isEnabled = isEnabled
if (isEnabled) {
binding.buttonSendZec.text = "send zec"
binding.progressSend.visibility = View.GONE
// binding.progressSend.visibility = View.GONE
} else {
binding.buttonSendZec.text = "sending..."
binding.progressSend.visibility = View.VISIBLE
// binding.progressSend.visibility = View.VISIBLE
}
}
}

View File

@ -1,19 +1,15 @@
package cash.z.android.wallet.ui.presenter
import android.util.Log
import cash.z.android.wallet.extention.Toaster
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
import cash.z.android.wallet.vo.WalletTransaction
import cash.z.android.wallet.vo.WalletTransactionStatus.RECEIVED
import cash.z.android.wallet.vo.WalletTransactionStatus.SENT
import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.data.ActiveSendTransaction
import cash.z.wallet.sdk.data.ActiveTransaction
import cash.z.wallet.sdk.data.Synchronizer
import cash.z.wallet.sdk.data.TransactionState
import cash.z.wallet.sdk.vo.NoteQuery
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.channels.ReceiveChannel
import java.math.BigDecimal
import kotlin.coroutines.CoroutineContext
class HomePresenter(
@ -29,6 +25,7 @@ class HomePresenter(
fun updateBalance(old: Long, new: Long)
fun showProgress(progress: Int)
fun setActiveTransactions(activeTransactionMap: Map<ActiveTransaction, TransactionState>)
fun onCancelledTooLate()
}
override suspend fun start() {
@ -54,20 +51,15 @@ class HomePresenter(
Log.e("@TWIG", "balance binder exiting!")
}
private fun CoroutineScope.launchTransactionBinder(channel: ReceiveChannel<List<NoteQuery>>) = launch {
private fun CoroutineScope.launchTransactionBinder(channel: ReceiveChannel<List<WalletTransaction>>) = launch {
Log.e("@TWIG", "transaction binder starting!")
for (noteQueryList in channel) {
Log.e("@TWIG", "received ${noteQueryList.size} transactions for presenting")
bind(noteQueryList.map {
val time = updateTimeStamp(it)
it.toWalletTransaction(time)
})
for (walletTransactionList in channel) {
Log.e("@TWIG", "received ${walletTransactionList.size} transactions for presenting")
bind(walletTransactionList)
}
Log.e("@TWIG", "transaction binder exiting!")
}
private suspend fun updateTimeStamp(noteQuery: NoteQuery) = synchronizer.updateTimeStamp(noteQuery.height)
private fun CoroutineScope.launchProgressMonitor(channel: ReceiveChannel<Int>) = launch {
Log.e("@TWIG", "progress monitor starting on thread ${Thread.currentThread().name}!")
for (i in channel) {
@ -109,9 +101,12 @@ class HomePresenter(
if (activeTransactionMap.isNotEmpty()) view.setActiveTransactions(activeTransactionMap)
}
fun onCancelActiveTransaction() {
// TODO: hold a reference to the job and cancel it
Toaster.short("Cancelled transaction!")
fun onCancelActiveTransaction(transaction: ActiveSendTransaction) {
Log.e("@TWIG", "requesting to cancel send for transaction ${transaction.internalId}")
val isTooLate = !synchronizer.cancelSend(transaction)
if (isTooLate) {
view.onCancelledTooLate()
}
}
private fun onMain(block: () -> Unit) = launch {
@ -122,11 +117,5 @@ class HomePresenter(
}
}
private fun NoteQuery.toWalletTransaction(timeOverride: Long? = null): WalletTransaction {
// convert time from seconds to milliseconds
val timestamp = if (timeOverride == null) time * 1000 else timeOverride * 1000
Log.e("@TWIG-u", "setting timestamp to $timestamp for value $value")
return WalletTransaction(height, if (sent) SENT else RECEIVED, timestamp, BigDecimal(value / 1e8))
}
}

View File

@ -3,7 +3,7 @@ package cash.z.android.wallet.ui.presenter
import android.util.Log
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
import cash.z.wallet.sdk.data.Synchronizer
import cash.z.wallet.sdk.vo.Transaction
import cash.z.wallet.sdk.entity.Transaction
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job

View File

@ -2,6 +2,7 @@ package cash.z.android.wallet.ui.view
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import androidx.constraintlayout.motion.widget.MotionLayout
import com.google.android.material.appbar.AppBarLayout
@ -11,6 +12,7 @@ class CollapsingMotionToolbar @JvmOverloads constructor(
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
progress = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
Log.e("MotionL", "progress: $progress verticalOffset: $verticalOffset scrollRange: ${appBarLayout.totalScrollRange.toFloat()}")
}
override fun onAttachedToWindow() {

View File

@ -1,12 +0,0 @@
package cash.z.android.wallet.vo
import cash.z.android.wallet.R
import androidx.annotation.ColorRes
import java.math.BigDecimal
data class WalletTransaction(val height: Int, val status: WalletTransactionStatus, val timestamp: Long, val amount: BigDecimal)
enum class WalletTransactionStatus(@ColorRes val color: Int) {
SENT(R.color.colorPrimary),
RECEIVED(R.color.colorAccent);
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="2dp" />
<solid android:color="@color/zcashWhite" />
</shape>

View File

@ -29,9 +29,14 @@
android:id="@+id/header_active_transaction"
android:layout_width="match_parent"
android:layout_height="70dp"
android:background="@drawable/background_rounded_corners_opaque"
android:background="@drawable/background_rounded_corners_slight"
android:clipToPadding="false"
android:padding="16dp"
android:paddingStart="16dp"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:paddingBottom="16dp"
android:transitionName="@string/transition_active_transaction_card"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -57,8 +62,10 @@
android:textSize="@dimen/text_size_body_1"
android:textStyle="bold"
android:transitionName="@string/transition_active_transaction_title"
app:layout_constraintBottom_toTopOf="@id/text_active_transaction_subtitle"
app:layout_constraintStart_toEndOf="@id/lottie_active_transaction"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/text_active_transaction_subtitle"
@ -67,9 +74,23 @@
android:text="to zsapling...123456789"
android:textSize="@dimen/text_size_caption"
android:transitionName="@string/transition_active_transaction_address"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/text_active_transaction_title"
app:layout_constraintTop_toBottomOf="@id/text_active_transaction_title" />
<TextView
android:id="@+id/text_active_transaction_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:textColor="@color/text_dark_dimmed"
android:textSize="@dimen/text_size_h5"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_active_transaction_cancel"
android:layout_width="wrap_content"

View File

@ -40,10 +40,15 @@
<!-- every color here should be a reference to a palette color but have a more useful name for use in code -->
<!-- association -->
<color name="send_associated">@color/colorAccent</color>
<color name="receive_associated">@color/colorPrimary</color> <!-- design decision: when you get money it should be associated with green -->
<color name="request_associated">@color/zcashBlue</color>
<!-- icons -->
<color name="icon_send">@color/colorPrimary</color>
<color name="icon_receive">@color/colorAccent</color>
<color name="icon_request">@color/zcashBlue</color>
<color name="icon_send">@color/send_associated</color>
<color name="icon_receive">@color/receive_associated</color>
<color name="icon_request">@color/request_associated</color>
<color name="fab_icon_color">@color/zcashWhite</color>
<color name="fab_closed_color">@color/colorPrimary</color>
<color name="fab_open_color">@color/colorPrimaryDark</color>

View File

@ -7,6 +7,10 @@
<string name="nav_header_desc">Navigation header</string>
<string name="action_settings">Settings</string>
<!-- General -->
<string name="cancel">cancel</string>
<string name="cancelled">cancelled</string>
<!-- Destinations -->
<string name="destination_title_home" />
<string name="destination_title_send">Send Zcash</string>

View File

@ -13,9 +13,10 @@ buildscript {
ext.deps = [
'androidx': [
'appcompat': 'androidx.appcompat:appcompat:1.0.0',
'constraintLayout': 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2',
'constraintLayout': 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3',
'core': 'androidx.core:core:1.0.0',
'coreKtx': 'androidx.core:core-ktx:1.0.0',
'multidex': 'androidx.multidex:multidex:2.0.1',
'navigation': [
'fragment': "android.arch.navigation:navigation-fragment:${versions.navigation}",
'fragmentKtx': "android.arch.navigation:navigation-fragment-ktx:${versions.navigation}",
@ -40,12 +41,15 @@ buildscript {
'core': "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutines}",
'android': "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutines}"
],
'reflect': "org.jetbrains.kotlin:kotlin-reflect:${versions.kotlin}",
'stdlib': "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlin}"
],
'lottie': "com.airbnb.android:lottie:2.7.0",
'material': 'com.google.android.material:material:1.0.0',
'speeddial': 'com.leinardi.android:speed-dial:2.0.0',
'stetho': 'com.facebook.stetho:stetho:1.5.0',
'zcash': [
'walletSdk': "cash.z.android.wallet:zcash-android-wallet-sdk:1.4.0@aar"
'walletSdk': "cash.z.android.wallet:zcash-android-wallet-sdk:1.6.0@aar"
]
]
repositories {