diff --git a/zcash-android-wallet-app/app/build.gradle b/zcash-android-wallet-app/app/build.gradle index 370a5a0..8d2ee68 100644 --- a/zcash-android-wallet-app/app/build.gradle +++ b/zcash-android-wallet-app/app/build.gradle @@ -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') } diff --git a/zcash-android-wallet-app/app/libs/zcash-android-wallet-sdk-1.4.0.aar b/zcash-android-wallet-app/app/libs/zcash-android-wallet-sdk-1.4.0.aar deleted file mode 100644 index f757707..0000000 Binary files a/zcash-android-wallet-app/app/libs/zcash-android-wallet-sdk-1.4.0.aar and /dev/null differ diff --git a/zcash-android-wallet-app/app/libs/zcash-android-wallet-sdk-1.6.0.aar b/zcash-android-wallet-app/app/libs/zcash-android-wallet-sdk-1.6.0.aar new file mode 100644 index 0000000..c38dae4 Binary files /dev/null and b/zcash-android-wallet-app/app/libs/zcash-android-wallet-sdk-1.6.0.aar differ diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/ReceivedTransactionRepository.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/ReceivedTransactionRepository.kt deleted file mode 100644 index 57366c7..0000000 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/ReceivedTransactionRepository.kt +++ /dev/null @@ -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() - private var existingBalance = BigDecimal.ZERO - private var balanceChannel = BroadcastChannel(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 = 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? { - 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 - ) - } -} \ No newline at end of file diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/SampleTransactionRepository.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/SampleTransactionRepository.kt index 147ceea..cb99580 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/SampleTransactionRepository.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/SampleTransactionRepository.kt @@ -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 { - val transactions = mutableListOf() - repeat(size) { - transactions.add(createSampleTransaction()) - } - return transactions - } } \ No newline at end of file diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/TransactionRepository.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/TransactionRepository.kt index 201e764..5f196fc 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/TransactionRepository.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/data/TransactionRepository.kt @@ -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 diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/di/module/SynchronizerModule.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/di/module/SynchronizerModule.kt index e29cef5..2a89f40 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/di/module/SynchronizerModule.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/di/module/SynchronizerModule.kt @@ -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 { + 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 { + 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" + } +} \ No newline at end of file diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/adapter/TransactionAdapter.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/adapter/TransactionAdapter.kt index 9b8dd62..620fb0b 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/adapter/TransactionAdapter.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/adapter/TransactionAdapter.kt @@ -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(DIFF_CALLBACK) { @@ -36,18 +34,18 @@ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) private val status = itemView.findViewById(R.id.view_transaction_status) private val timestamp = itemView.findViewById(R.id.text_transaction_timestamp) private val amount = itemView.findViewById(R.id.text_transaction_amount) - private val background = itemView.findViewById(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}") } - } diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/HomeFragment.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/HomeFragment.kt index ac68841..c0e6040 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/HomeFragment.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/HomeFragment.kt @@ -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) { 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) { 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() diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/ReceiveFragment.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/ReceiveFragment.kt index 882e7cb..c5dbde0 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/ReceiveFragment.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/ReceiveFragment.kt @@ -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 @@ -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() } } diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/SendFragment.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/SendFragment.kt index a260be4..50c6900 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/SendFragment.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/SendFragment.kt @@ -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() + ?.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 } } } diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/presenter/HomePresenter.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/presenter/HomePresenter.kt index 7d5d185..4f4e13b 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/presenter/HomePresenter.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/presenter/HomePresenter.kt @@ -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) + fun onCancelledTooLate() } override suspend fun start() { @@ -54,20 +51,15 @@ class HomePresenter( Log.e("@TWIG", "balance binder exiting!") } - private fun CoroutineScope.launchTransactionBinder(channel: ReceiveChannel>) = launch { + private fun CoroutineScope.launchTransactionBinder(channel: ReceiveChannel>) = 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) = 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)) - } } diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/presenter/SendPresenter.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/presenter/SendPresenter.kt index 3c57627..74eb9ae 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/presenter/SendPresenter.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/presenter/SendPresenter.kt @@ -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 diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/view/CollapsingMotionToolbar.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/view/CollapsingMotionToolbar.kt index ee3b62f..8f61a78 100644 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/view/CollapsingMotionToolbar.kt +++ b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/view/CollapsingMotionToolbar.kt @@ -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() { diff --git a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/vo/WalletTransaction.kt b/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/vo/WalletTransaction.kt deleted file mode 100644 index 6568107..0000000 --- a/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/vo/WalletTransaction.kt +++ /dev/null @@ -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); -} diff --git a/zcash-android-wallet-app/app/src/main/res/drawable/background_rounded_corners_slight.xml b/zcash-android-wallet-app/app/src/main/res/drawable/background_rounded_corners_slight.xml new file mode 100644 index 0000000..bbeb0b2 --- /dev/null +++ b/zcash-android-wallet-app/app/src/main/res/drawable/background_rounded_corners_slight.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/zcash-android-wallet-app/app/src/main/res/layout/include_home_content.xml b/zcash-android-wallet-app/app/src/main/res/layout/include_home_content.xml index 97c741c..8b5d23a 100644 --- a/zcash-android-wallet-app/app/src/main/res/layout/include_home_content.xml +++ b/zcash-android-wallet-app/app/src/main/res/layout/include_home_content.xml @@ -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" /> + +