checkpoint: send is functional and shows up in active transactions.
This commit is contained in:
parent
3987fe820b
commit
78c18faee0
|
@ -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')
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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}")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,6 +168,7 @@ 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
|
||||
override fun updateBalance(old: Long, new: Long) {
|
||||
//TODO: remove this kind of thing
|
||||
|
@ -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.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
|
||||
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(if (isRaised) 10f else 0f)
|
||||
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()
|
||||
setListener(object : Animator.AnimatorListener {
|
||||
override fun onAnimationRepeat(animation: Animator?) {
|
||||
}
|
||||
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
header_active_transaction.apply {
|
||||
if(translationZ == 0f) setBackgroundResource(0)
|
||||
lottie_active_transaction.cancelAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator?) {
|
||||
}
|
||||
|
||||
override fun onAnimationStart(animation: Animator?) {
|
||||
}
|
||||
|
||||
} )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inner class HomeTransitionListener : Transition.TransitionListener {
|
||||
override fun onTransitionStart(transition: Transition) {
|
||||
onActiveTransactionTransitionStart()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue