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.ui
|
||||||
implementation deps.androidx.navigation.uiKtx
|
implementation deps.androidx.navigation.uiKtx
|
||||||
implementation deps.material
|
implementation deps.material
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation deps.androidx.multidex
|
||||||
|
|
||||||
// Kotlin
|
// Kotlin
|
||||||
implementation deps.kotlin.stdlib
|
implementation deps.kotlin.stdlib
|
||||||
|
implementation deps.kotlin.reflect
|
||||||
implementation deps.kotlin.coroutines.core
|
implementation deps.kotlin.coroutines.core
|
||||||
implementation deps.kotlin.coroutines.android
|
implementation deps.kotlin.coroutines.android
|
||||||
|
|
||||||
// Zcash
|
// Zcash
|
||||||
implementation deps.zcash.walletSdk
|
implementation deps.zcash.walletSdk
|
||||||
|
compile project(path: ':qrecycler')
|
||||||
|
|
||||||
// TODO: get the AAR to provide these
|
// TODO: get the AAR to provide these
|
||||||
implementation "io.grpc:grpc-okhttp:1.17.1"
|
implementation "io.grpc:grpc-okhttp:1.17.1"
|
||||||
|
@ -75,11 +77,10 @@ dependencies {
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
implementation deps.speeddial
|
implementation deps.speeddial
|
||||||
implementation "com.airbnb.android:lottie:2.7.0"
|
implementation deps.lottie
|
||||||
compile 'com.facebook.stetho:stetho:1.5.0'
|
debugImplementation deps.stetho
|
||||||
|
|
||||||
testImplementation deps.junit
|
testImplementation deps.junit
|
||||||
androidTestImplementation deps.androidx.test.runner
|
androidTestImplementation deps.androidx.test.runner
|
||||||
androidTestImplementation deps.androidx.test.espresso
|
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
|
package cash.z.android.wallet.data
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import cash.z.android.wallet.vo.WalletTransaction
|
import cash.z.wallet.sdk.dao.WalletTransaction
|
||||||
import cash.z.android.wallet.vo.WalletTransactionStatus
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.channels.ReceiveChannel
|
import kotlinx.coroutines.channels.ReceiveChannel
|
||||||
import kotlinx.coroutines.channels.produce
|
import kotlinx.coroutines.channels.produce
|
||||||
|
@ -11,6 +10,8 @@ import kotlinx.coroutines.isActive
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import kotlin.random.nextInt
|
||||||
|
import kotlin.random.nextLong
|
||||||
|
|
||||||
class SampleTransactionRepository(val scope: CoroutineScope) : TransactionRepository {
|
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)
|
var oldestTimestamp = System.currentTimeMillis() - (4 * DateUtils.WEEK_IN_MILLIS)
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(1500L)
|
delay(1500L)
|
||||||
send(createSampleTransaction(oldestTimestamp).also { oldestTimestamp = it.timestamp })
|
send(createSampleTransaction(oldestTimestamp).also { oldestTimestamp = it.timeInSeconds * 1000 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSampleTransaction(): WalletTransaction {
|
private fun createSampleTransaction(oldestTimestamp: Long): WalletTransaction {
|
||||||
return createSampleTransaction(System.currentTimeMillis() - (4 * DateUtils.WEEK_IN_MILLIS))
|
// 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))
|
||||||
private fun createSampleTransaction(after: Long): WalletTransaction {
|
val value = Random.nextLong(1L..1_500_000_000L) - 750_000_000L
|
||||||
val now = System.currentTimeMillis()
|
val height = Random.nextInt(0..(Int.MAX_VALUE - 1))
|
||||||
val delta = now - after
|
val isSend = value > 0L
|
||||||
val window = after + (0.05 * delta).roundToLong()
|
val time = Random.nextLong(oldestTimestamp..upperBound)
|
||||||
val amount = BigDecimal(Random.nextDouble(0.1, 15.0) * arrayOf(-1, 1).random())
|
val isMined = Random.nextBoolean()
|
||||||
val status = if (amount > BigDecimal.ZERO) WalletTransactionStatus.SENT else WalletTransactionStatus.RECEIVED
|
|
||||||
val timestamp = Random.nextLong(after, window)
|
|
||||||
return WalletTransaction(
|
return WalletTransaction(
|
||||||
timestamp.toInt(),
|
txId = txId,
|
||||||
status,
|
value = value,
|
||||||
timestamp,
|
height = height,
|
||||||
amount
|
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
|
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 kotlinx.coroutines.channels.ReceiveChannel
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package cash.z.android.wallet.di.module
|
package cash.z.android.wallet.di.module
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import cash.z.android.wallet.BuildConfig
|
import cash.z.android.wallet.BuildConfig
|
||||||
import cash.z.android.wallet.ZcashWalletApplication
|
import cash.z.android.wallet.ZcashWalletApplication
|
||||||
import cash.z.android.wallet.di.module.Properties.CACHE_DB_NAME
|
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.COMPACT_BLOCK_SERVER
|
||||||
import cash.z.android.wallet.di.module.Properties.DATA_DB_NAME
|
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.SEED_PROVIDER
|
||||||
|
import cash.z.android.wallet.di.module.Properties.SPENDING_KEY_PROVIDER
|
||||||
import cash.z.wallet.sdk.data.*
|
import cash.z.wallet.sdk.data.*
|
||||||
import cash.z.wallet.sdk.jni.JniConverter
|
import cash.z.wallet.sdk.jni.JniConverter
|
||||||
import cash.z.wallet.sdk.secure.Wallet
|
import cash.z.wallet.sdk.secure.Wallet
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import okio.ByteString
|
||||||
|
import java.nio.charset.Charset
|
||||||
import javax.inject.Singleton
|
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
|
* Module that contributes all the objects necessary for the synchronizer, which is basically everything that has
|
||||||
|
@ -51,7 +57,7 @@ internal object SynchronizerModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideWallet(application: ZcashWalletApplication, converter: JniConverter, twigger: Twig): Wallet {
|
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
|
@JvmStatic
|
||||||
|
@ -64,7 +70,11 @@ internal object SynchronizerModule {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideJniConverter(): JniConverter = JniConverter()
|
fun provideJniConverter(): JniConverter {
|
||||||
|
return JniConverter().also {
|
||||||
|
if (BuildConfig.DEBUG) it.initLogs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -85,11 +95,12 @@ internal object SynchronizerModule {
|
||||||
|
|
||||||
// TODO: load most of these properties in later, perhaps from settings
|
// TODO: load most of these properties in later, perhaps from settings
|
||||||
object Properties {
|
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 COMPACT_BLOCK_PORT = 9067
|
||||||
const val CACHE_DB_NAME = "wallet_cache21.db"
|
const val CACHE_DB_NAME = "wallet_cache50.db"
|
||||||
const val DATA_DB_NAME = "wallet_data21.db"
|
const val DATA_DB_NAME = "wallet_data50.db"
|
||||||
val SEED_PROVIDER = SampleSeedProvider("dummyseed")
|
val SEED_PROVIDER = SampleSeedProvider("dummyseed")
|
||||||
|
val SPENDING_KEY_PROVIDER = SampleSpendingKeyProvider("dummyseed")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Servers(val host: String) {
|
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"),
|
BOLT_TESTNET("ec2-34-228-10-162.compute-1.amazonaws.com"),
|
||||||
ZCASH_TESTNET("lightwalletd.z.cash")
|
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.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.AsyncListDiffer
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import cash.z.android.wallet.R
|
import cash.z.android.wallet.R
|
||||||
import cash.z.android.wallet.extention.toAppColor
|
import cash.z.android.wallet.extention.toAppColor
|
||||||
import cash.z.android.wallet.vo.WalletTransaction
|
import cash.z.wallet.sdk.dao.WalletTransaction
|
||||||
import java.math.BigDecimal
|
import cash.z.wallet.sdk.ext.toZec
|
||||||
import java.math.MathContext
|
|
||||||
import java.math.RoundingMode
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
|
||||||
class TransactionAdapter : ListAdapter<WalletTransaction, TransactionViewHolder>(DIFF_CALLBACK) {
|
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 status = itemView.findViewById<View>(R.id.view_transaction_status)
|
||||||
private val timestamp = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
|
private val timestamp = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
|
||||||
private val amount = itemView.findViewById<TextView>(R.id.text_transaction_amount)
|
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())
|
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
||||||
|
|
||||||
fun bind(tx: WalletTransaction) {
|
fun bind(tx: WalletTransaction) {
|
||||||
val sign = if(tx.amount > BigDecimal.ZERO) "+" else "-"
|
val sign = if(tx.isSend) "-" else "+"
|
||||||
val amountColor = if(tx.amount > BigDecimal.ZERO) R.color.colorPrimary else R.color.text_dark_dimmed
|
val amountColor = if (tx.isSend) R.color.text_dark_dimmed else R.color.colorPrimary
|
||||||
status.setBackgroundColor(tx.status.color.toAppColor())
|
val transactionColor = if(tx.isSend) R.color.send_associated else R.color.receive_associated
|
||||||
timestamp.text = formatter.format(tx.timestamp)
|
val zecAbsoluteValue = tx.value.absoluteValue.toZec(3)
|
||||||
amount.text = String.format("$sign %,.3f", tx.amount.round(MathContext(3, RoundingMode.HALF_EVEN )).abs())
|
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())
|
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
|
package cash.z.android.wallet.ui.fragment
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
|
@ -10,16 +9,17 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AccelerateInterpolator
|
import android.view.animation.AccelerateInterpolator
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
import androidx.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.transition.Transition
|
import androidx.transition.Transition
|
||||||
import androidx.transition.TransitionInflater
|
import androidx.transition.TransitionInflater
|
||||||
import cash.z.android.wallet.R
|
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.toAppColor
|
||||||
import cash.z.android.wallet.extention.toAppString
|
import cash.z.android.wallet.extention.toAppString
|
||||||
import cash.z.android.wallet.extention.tryIgnore
|
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.presenter.HomePresenter
|
||||||
import cash.z.android.wallet.ui.util.AlternatingRowColorDecoration
|
import cash.z.android.wallet.ui.util.AlternatingRowColorDecoration
|
||||||
import cash.z.android.wallet.ui.util.TopAlignedSpan
|
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.ActiveSendTransaction
|
||||||
import cash.z.wallet.sdk.data.ActiveTransaction
|
import cash.z.wallet.sdk.data.ActiveTransaction
|
||||||
import cash.z.wallet.sdk.data.TransactionState
|
import cash.z.wallet.sdk.data.TransactionState
|
||||||
import cash.z.wallet.sdk.ext.toZec
|
import cash.z.wallet.sdk.ext.toZec
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
import com.leinardi.android.speeddial.SpeedDialActionItem
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.android.ContributesAndroidInjector
|
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.fragment_home.*
|
||||||
import kotlinx.android.synthetic.main.include_home_content.*
|
import kotlinx.android.synthetic.main.include_home_content.*
|
||||||
import kotlinx.android.synthetic.main.include_home_header.*
|
import kotlinx.android.synthetic.main.include_home_header.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.random.Random
|
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
|
import kotlin.random.nextLong
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,6 +55,7 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
|
||||||
lateinit var homePresenter: HomePresenter
|
lateinit var homePresenter: HomePresenter
|
||||||
lateinit var transactionAdapter: TransactionAdapter
|
lateinit var transactionAdapter: TransactionAdapter
|
||||||
private var viewsInitialized = false
|
private var viewsInitialized = false
|
||||||
|
private var snackbar: Snackbar? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
@ -97,15 +97,16 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
|
||||||
// forceRedraw()
|
// forceRedraw()
|
||||||
// toggleViews(false)
|
// toggleViews(false)
|
||||||
|
|
||||||
} else {
|
|
||||||
forceRedraw()
|
|
||||||
toggleViews(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button_active_transaction_cancel.setOnClickListener {
|
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 {
|
refresh_layout.setOnRefreshListener {
|
||||||
|
@ -167,6 +168,7 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
|
||||||
//
|
//
|
||||||
// View API
|
// 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) {
|
override fun updateBalance(old: Long, new: Long) {
|
||||||
//TODO: remove this kind of thing
|
//TODO: remove this kind of thing
|
||||||
|
@ -184,18 +186,19 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
|
||||||
override fun setTransactions(transactions: List<WalletTransaction>) {
|
override fun setTransactions(transactions: List<WalletTransaction>) {
|
||||||
Log.e("TWIG-t", "submitList called with ${transactions.size} transactions")
|
Log.e("TWIG-t", "submitList called with ${transactions.size} transactions")
|
||||||
transactionAdapter.submitList(transactions)
|
transactionAdapter.submitList(transactions)
|
||||||
|
recycler_transactions.postDelayed({
|
||||||
recycler_transactions.smoothScrollToPosition(0)
|
recycler_transactions.smoothScrollToPosition(0)
|
||||||
|
}, 100L)
|
||||||
if (transactions.isNotEmpty()) setFirstRunShown(false)
|
if (transactions.isNotEmpty()) setFirstRunShown(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
var snackbar: Snackbar? = null
|
|
||||||
override fun showProgress(progress: Int) {
|
override fun showProgress(progress: Int) {
|
||||||
Log.e("TWIG", "showing progress of $progress")
|
Log.e("TWIG", "showing progress of $progress")
|
||||||
// TODO: improve this with Lottie animation. but for now just use the empty view for downloading...
|
// 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
|
// var hasEmptyViews = group_empty_view_items.visibility == View.VISIBLE
|
||||||
// if(!viewsInitialized) toggleViews(true)
|
// 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
|
// text_wallet_message.text = message
|
||||||
|
|
||||||
if (snackbar == null && progress <= 50) {
|
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>) {
|
override fun setActiveTransactions(activeTransactionMap: Map<ActiveTransaction, TransactionState>) {
|
||||||
if (activeTransactionMap.isEmpty()) {
|
if (activeTransactionMap.isEmpty()) {
|
||||||
setActiveTransactionsShown(false)
|
setActiveTransactionsShown(false)
|
||||||
|
@ -224,7 +240,12 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
|
||||||
// TODO: update remaining transactions
|
// TODO: update remaining transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCancelledTooLate() {
|
||||||
|
showOkSnack("Oops! It was too late to cancel!")
|
||||||
|
}
|
||||||
|
|
||||||
private fun updatePrimaryTransaction(transaction: ActiveTransaction, transactionState: TransactionState) {
|
private fun updatePrimaryTransaction(transaction: ActiveTransaction, transactionState: TransactionState) {
|
||||||
|
Log.e("TWIG", "setting transaction state to ${transactionState::class.simpleName}")
|
||||||
var title = "Active Transaction"
|
var title = "Active Transaction"
|
||||||
var subtitle = "Processing..."
|
var subtitle = "Processing..."
|
||||||
when (transactionState) {
|
when (transactionState) {
|
||||||
|
@ -232,13 +253,14 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
|
||||||
header_active_transaction.visibility = View.VISIBLE
|
header_active_transaction.visibility = View.VISIBLE
|
||||||
title = "Preparing ${transaction.value.toZec(3)} ZEC"
|
title = "Preparing ${transaction.value.toZec(3)} ZEC"
|
||||||
subtitle = "to ${(transaction as ActiveSendTransaction).toAddress}"
|
subtitle = "to ${(transaction as ActiveSendTransaction).toAddress}"
|
||||||
button_active_transaction_cancel.text = "cancel"
|
setTransactionActive(transaction, true)
|
||||||
setActiveTransactionRaised(true)
|
|
||||||
}
|
}
|
||||||
TransactionState.SendingToNetwork -> {
|
TransactionState.SendingToNetwork -> {
|
||||||
title = "Sending Transaction"
|
title = "Sending Transaction"
|
||||||
subtitle = "to ${(transaction as ActiveSendTransaction).toAddress}"
|
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 -> {
|
is TransactionState.Failure -> {
|
||||||
lottie_active_transaction.setAnimation(R.raw.lottie_send_failure)
|
lottie_active_transaction.setAnimation(R.raw.lottie_send_failure)
|
||||||
|
@ -250,18 +272,27 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
|
||||||
else -> "Unrecoginzed error"
|
else -> "Unrecoginzed error"
|
||||||
}
|
}
|
||||||
button_active_transaction_cancel.visibility = View.GONE
|
button_active_transaction_cancel.visibility = View.GONE
|
||||||
onCancelActiveTransaction()
|
text_active_transaction_value.visibility = View.GONE
|
||||||
|
setTransactionActive(transaction, false)
|
||||||
}
|
}
|
||||||
is TransactionState.AwaitingConfirmations -> {
|
is TransactionState.AwaitingConfirmations -> {
|
||||||
if (transactionState.confirmationCount < 1) {
|
if (transactionState.confirmationCount < 1) {
|
||||||
lottie_active_transaction.setAnimation(R.raw.lottie_send_success)
|
lottie_active_transaction.setAnimation(R.raw.lottie_send_success)
|
||||||
lottie_active_transaction.playAnimation()
|
lottie_active_transaction.playAnimation()
|
||||||
title = "ZEC Sent"
|
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 {
|
} else {
|
||||||
// play confirmation counting animation
|
// 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_title.text = title
|
||||||
text_active_transaction_subtitle.text = subtitle
|
text_active_transaction_subtitle.text = subtitle
|
||||||
|
@ -347,6 +378,7 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onActiveTransactionTransitionEnd() {
|
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
|
// 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
|
header_active_transaction.translationZ = 10.0f
|
||||||
button_active_transaction_cancel.apply {
|
button_active_transaction_cancel.apply {
|
||||||
|
@ -355,38 +387,30 @@ class HomeFragment : BaseFragment(), HomePresenter.HomeView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onCancelActiveTransaction() {
|
private fun setTransactionActive(transaction: ActiveTransaction, isActive: Boolean) {
|
||||||
setActiveTransactionRaised(false)
|
// TODO: get view for transaction, mostly likely keep a sparse array of these or something
|
||||||
button_active_transaction_cancel.text = "cancel"
|
if (isActive) {
|
||||||
}
|
button_active_transaction_cancel.setText(R.string.cancel)
|
||||||
|
button_active_transaction_cancel.isEnabled = true
|
||||||
private fun setActiveTransactionRaised(isRaised: Boolean) {
|
button_active_transaction_cancel.tag = transaction
|
||||||
button_active_transaction_cancel.isEnabled = isRaised
|
|
||||||
header_active_transaction.animate().apply {
|
header_active_transaction.animate().apply {
|
||||||
translationZ(if (isRaised) 10f else 0f)
|
translationZ(10f)
|
||||||
duration = 200L
|
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()
|
interpolator = AccelerateInterpolator()
|
||||||
setListener(object : Animator.AnimatorListener {
|
|
||||||
override fun onAnimationRepeat(animation: Animator?) {
|
|
||||||
}
|
}
|
||||||
|
lottie_active_transaction.cancelAnimation()
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
header_active_transaction.apply {
|
|
||||||
if(translationZ == 0f) setBackgroundResource(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAnimationCancel(animation: Animator?) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationStart(animation: Animator?) {
|
|
||||||
}
|
|
||||||
|
|
||||||
} )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
inner class HomeTransitionListener : Transition.TransitionListener {
|
inner class HomeTransitionListener : Transition.TransitionListener {
|
||||||
override fun onTransitionStart(transition: Transition) {
|
override fun onTransitionStart(transition: Transition) {
|
||||||
onActiveTransactionTransitionStart()
|
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.activity.MainActivity
|
||||||
import cash.z.android.wallet.ui.util.AddressPartNumberSpan
|
import cash.z.android.wallet.ui.util.AddressPartNumberSpan
|
||||||
import cash.z.wallet.sdk.jni.JniConverter
|
import cash.z.wallet.sdk.jni.JniConverter
|
||||||
|
import cash.z.wallet.sdk.secure.Wallet
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.android.ContributesAndroidInjector
|
import dagger.android.ContributesAndroidInjector
|
||||||
import kotlinx.android.synthetic.main.fragment_receive.*
|
import kotlinx.android.synthetic.main.fragment_receive.*
|
||||||
|
@ -27,7 +28,7 @@ class ReceiveFragment : BaseFragment() {
|
||||||
lateinit var qrecycler: QRecycler
|
lateinit var qrecycler: QRecycler
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var converter: JniConverter
|
lateinit var wallet: Wallet
|
||||||
|
|
||||||
lateinit var addressParts: Array<TextView>
|
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
|
// 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 {
|
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.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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.core.text.toSpannable
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.navigation.fragment.FragmentNavigatorExtras
|
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 cash.z.android.wallet.ui.presenter.SendPresenter
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.android.ContributesAndroidInjector
|
import dagger.android.ContributesAndroidInjector
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
@ -195,6 +199,10 @@ class SendFragment : BaseFragment(), SendPresenter.SendView {
|
||||||
//
|
//
|
||||||
|
|
||||||
private fun showSendDialog() {
|
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
|
setSendEnabled(false) // partially because we need to lower the button elevation
|
||||||
binding.groupDialogSend.visibility = View.VISIBLE
|
binding.groupDialogSend.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
@ -208,10 +216,10 @@ class SendFragment : BaseFragment(), SendPresenter.SendView {
|
||||||
binding.buttonSendZec.isEnabled = isEnabled
|
binding.buttonSendZec.isEnabled = isEnabled
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
binding.buttonSendZec.text = "send zec"
|
binding.buttonSendZec.text = "send zec"
|
||||||
binding.progressSend.visibility = View.GONE
|
// binding.progressSend.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
binding.buttonSendZec.text = "sending..."
|
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
|
package cash.z.android.wallet.ui.presenter
|
||||||
|
|
||||||
import android.util.Log
|
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.ui.presenter.Presenter.PresenterView
|
||||||
import cash.z.android.wallet.vo.WalletTransaction
|
import cash.z.wallet.sdk.dao.WalletTransaction
|
||||||
import cash.z.android.wallet.vo.WalletTransactionStatus.RECEIVED
|
import cash.z.wallet.sdk.data.ActiveSendTransaction
|
||||||
import cash.z.android.wallet.vo.WalletTransactionStatus.SENT
|
|
||||||
import cash.z.wallet.sdk.data.ActiveTransaction
|
import cash.z.wallet.sdk.data.ActiveTransaction
|
||||||
import cash.z.wallet.sdk.data.Synchronizer
|
import cash.z.wallet.sdk.data.Synchronizer
|
||||||
import cash.z.wallet.sdk.data.TransactionState
|
import cash.z.wallet.sdk.data.TransactionState
|
||||||
import cash.z.wallet.sdk.vo.NoteQuery
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.channels.ReceiveChannel
|
import kotlinx.coroutines.channels.ReceiveChannel
|
||||||
import java.math.BigDecimal
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class HomePresenter(
|
class HomePresenter(
|
||||||
|
@ -29,6 +25,7 @@ class HomePresenter(
|
||||||
fun updateBalance(old: Long, new: Long)
|
fun updateBalance(old: Long, new: Long)
|
||||||
fun showProgress(progress: Int)
|
fun showProgress(progress: Int)
|
||||||
fun setActiveTransactions(activeTransactionMap: Map<ActiveTransaction, TransactionState>)
|
fun setActiveTransactions(activeTransactionMap: Map<ActiveTransaction, TransactionState>)
|
||||||
|
fun onCancelledTooLate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun start() {
|
override suspend fun start() {
|
||||||
|
@ -54,20 +51,15 @@ class HomePresenter(
|
||||||
Log.e("@TWIG", "balance binder exiting!")
|
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!")
|
Log.e("@TWIG", "transaction binder starting!")
|
||||||
for (noteQueryList in channel) {
|
for (walletTransactionList in channel) {
|
||||||
Log.e("@TWIG", "received ${noteQueryList.size} transactions for presenting")
|
Log.e("@TWIG", "received ${walletTransactionList.size} transactions for presenting")
|
||||||
bind(noteQueryList.map {
|
bind(walletTransactionList)
|
||||||
val time = updateTimeStamp(it)
|
|
||||||
it.toWalletTransaction(time)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
Log.e("@TWIG", "transaction binder exiting!")
|
Log.e("@TWIG", "transaction binder exiting!")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateTimeStamp(noteQuery: NoteQuery) = synchronizer.updateTimeStamp(noteQuery.height)
|
|
||||||
|
|
||||||
private fun CoroutineScope.launchProgressMonitor(channel: ReceiveChannel<Int>) = launch {
|
private fun CoroutineScope.launchProgressMonitor(channel: ReceiveChannel<Int>) = launch {
|
||||||
Log.e("@TWIG", "progress monitor starting on thread ${Thread.currentThread().name}!")
|
Log.e("@TWIG", "progress monitor starting on thread ${Thread.currentThread().name}!")
|
||||||
for (i in channel) {
|
for (i in channel) {
|
||||||
|
@ -109,9 +101,12 @@ class HomePresenter(
|
||||||
if (activeTransactionMap.isNotEmpty()) view.setActiveTransactions(activeTransactionMap)
|
if (activeTransactionMap.isNotEmpty()) view.setActiveTransactions(activeTransactionMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCancelActiveTransaction() {
|
fun onCancelActiveTransaction(transaction: ActiveSendTransaction) {
|
||||||
// TODO: hold a reference to the job and cancel it
|
Log.e("@TWIG", "requesting to cancel send for transaction ${transaction.internalId}")
|
||||||
Toaster.short("Cancelled transaction!")
|
val isTooLate = !synchronizer.cancelSend(transaction)
|
||||||
|
if (isTooLate) {
|
||||||
|
view.onCancelledTooLate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onMain(block: () -> Unit) = launch {
|
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 android.util.Log
|
||||||
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
|
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
|
||||||
import cash.z.wallet.sdk.data.Synchronizer
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cash.z.android.wallet.ui.view
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ class CollapsingMotionToolbar @JvmOverloads constructor(
|
||||||
|
|
||||||
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
|
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
|
||||||
progress = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
|
progress = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
|
||||||
|
Log.e("MotionL", "progress: $progress verticalOffset: $verticalOffset scrollRange: ${appBarLayout.totalScrollRange.toFloat()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
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:id="@+id/header_active_transaction"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="70dp"
|
android:layout_height="70dp"
|
||||||
android:background="@drawable/background_rounded_corners_opaque"
|
android:background="@drawable/background_rounded_corners_slight"
|
||||||
android:clipToPadding="false"
|
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"
|
android:transitionName="@string/transition_active_transaction_card"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
@ -57,8 +62,10 @@
|
||||||
android:textSize="@dimen/text_size_body_1"
|
android:textSize="@dimen/text_size_body_1"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:transitionName="@string/transition_active_transaction_title"
|
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_constraintStart_toEndOf="@id/lottie_active_transaction"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_active_transaction_subtitle"
|
android:id="@+id/text_active_transaction_subtitle"
|
||||||
|
@ -67,9 +74,23 @@
|
||||||
android:text="to zsapling...123456789"
|
android:text="to zsapling...123456789"
|
||||||
android:textSize="@dimen/text_size_caption"
|
android:textSize="@dimen/text_size_caption"
|
||||||
android:transitionName="@string/transition_active_transaction_address"
|
android:transitionName="@string/transition_active_transaction_address"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@id/text_active_transaction_title"
|
app:layout_constraintStart_toStartOf="@id/text_active_transaction_title"
|
||||||
app:layout_constraintTop_toBottomOf="@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
|
<Button
|
||||||
android:id="@+id/button_active_transaction_cancel"
|
android:id="@+id/button_active_transaction_cancel"
|
||||||
android:layout_width="wrap_content"
|
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 -->
|
<!-- 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 -->
|
<!-- icons -->
|
||||||
<color name="icon_send">@color/colorPrimary</color>
|
<color name="icon_send">@color/send_associated</color>
|
||||||
<color name="icon_receive">@color/colorAccent</color>
|
<color name="icon_receive">@color/receive_associated</color>
|
||||||
<color name="icon_request">@color/zcashBlue</color>
|
<color name="icon_request">@color/request_associated</color>
|
||||||
<color name="fab_icon_color">@color/zcashWhite</color>
|
<color name="fab_icon_color">@color/zcashWhite</color>
|
||||||
<color name="fab_closed_color">@color/colorPrimary</color>
|
<color name="fab_closed_color">@color/colorPrimary</color>
|
||||||
<color name="fab_open_color">@color/colorPrimaryDark</color>
|
<color name="fab_open_color">@color/colorPrimaryDark</color>
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
<string name="nav_header_desc">Navigation header</string>
|
<string name="nav_header_desc">Navigation header</string>
|
||||||
<string name="action_settings">Settings</string>
|
<string name="action_settings">Settings</string>
|
||||||
|
|
||||||
|
<!-- General -->
|
||||||
|
<string name="cancel">cancel</string>
|
||||||
|
<string name="cancelled">cancelled</string>
|
||||||
|
|
||||||
<!-- Destinations -->
|
<!-- Destinations -->
|
||||||
<string name="destination_title_home" />
|
<string name="destination_title_home" />
|
||||||
<string name="destination_title_send">Send Zcash</string>
|
<string name="destination_title_send">Send Zcash</string>
|
||||||
|
|
|
@ -13,9 +13,10 @@ buildscript {
|
||||||
ext.deps = [
|
ext.deps = [
|
||||||
'androidx': [
|
'androidx': [
|
||||||
'appcompat': 'androidx.appcompat:appcompat:1.0.0',
|
'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',
|
'core': 'androidx.core:core:1.0.0',
|
||||||
'coreKtx': 'androidx.core:core-ktx:1.0.0',
|
'coreKtx': 'androidx.core:core-ktx:1.0.0',
|
||||||
|
'multidex': 'androidx.multidex:multidex:2.0.1',
|
||||||
'navigation': [
|
'navigation': [
|
||||||
'fragment': "android.arch.navigation:navigation-fragment:${versions.navigation}",
|
'fragment': "android.arch.navigation:navigation-fragment:${versions.navigation}",
|
||||||
'fragmentKtx': "android.arch.navigation:navigation-fragment-ktx:${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}",
|
'core': "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutines}",
|
||||||
'android': "org.jetbrains.kotlinx:kotlinx-coroutines-android:${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}"
|
'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',
|
'material': 'com.google.android.material:material:1.0.0',
|
||||||
'speeddial': 'com.leinardi.android:speed-dial:2.0.0',
|
'speeddial': 'com.leinardi.android:speed-dial:2.0.0',
|
||||||
|
'stetho': 'com.facebook.stetho:stetho:1.5.0',
|
||||||
'zcash': [
|
'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 {
|
repositories {
|
||||||
|
|
Loading…
Reference in New Issue