[#367] Fix Kotlin compiler warnings

* Fix missing room schema export directory
* Fix returned unused fields from cursor
* Fix missing db column index
* Improved TODO. Added suppress warning
* Changed parameters name to correspond to their supertype
* Changed type to Kotlin variant
* Use priority parameter
* Unified parameter names
* Suppress unchecked type warning
* Removed inline function
* Suppress obsolete coroutine warnings
* Improve previous commit
* Fix unnecessary safe call warning
* Remove unused parameter
* Unreachable code
* toLowerCase where possible
* Changed parameter name
* Suppress several "unused" warnings
* Fixed fromHtml() deprecation
* Suppress intentionally unused parameter warning
* Remove redundant initializer
* Remove inline function
* Suppress intentionally used deprecated code
* Unreachable code
* Suppress obsolete coroutine warnings
* Suppress intentionally unused parameter
* Remove unused expression
* Supertype parameter name
* Warnings of GetBlockRangeFragment.kt
* Deprecated onActivityCreated
* Suppress obsolete coroutine/flow warnings
* Unnecessary null check
* Suppress intentionally unused parameter
* Suppress intentionally unused parameters
* Deprecated onActivityCreated
* Predetermined type
* ListUtxosFragment clean code
* Suppress intentionally unused parameter
* Lint checks warnings fix
* Add data db migration
* Enable treating Kotlin compiler warnings as errors
* Solve several darkside-test-lib tests warnings
* Solve several demo-app tests warnings
* Solve several sdk-lib tests warnings
* Ktlint check result fix
* Remove parentheses now that Synchronizer is not cast
* Remove wildcard imports for java.util
* Revert "Add data db migration"
* Revert "Fix missing db column index"
* Suppress missing indexes on data db entities

Co-authored-by: Carter Jernigan <git@carterjernigan.com>
This commit is contained in:
Honza Rychnovsky 2022-08-17 15:48:02 +02:00 committed by GitHub
parent 10d0652a8d
commit 150778f008
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 256 additions and 192 deletions

View File

@ -1,6 +1,11 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />

View File

@ -43,9 +43,9 @@ class InboundTxTests : ScopedTest() {
} }
private fun addTransactions(targetHeight: BlockHeight, vararg txs: String) { private fun addTransactions(targetHeight: BlockHeight, vararg txs: String) {
val overwriteBlockCount = 5 // val overwriteBlockCount = 5
chainMaker chainMaker
// .stageEmptyBlocks(targetHeight, overwriteBlockCount) // .stageEmptyBlocks(targetHeight, overwriteBlockCount)
.stageTransactions(targetHeight, *txs) .stageTransactions(targetHeight, *txs)
.applyTipHeight(targetHeight) .applyTipHeight(targetHeight)
} }

View File

@ -1,6 +1,6 @@
package cash.z.ecc.android.sdk.darkside.test package cash.z.ecc.android.sdk.darkside.test
open class DarksideTest(name: String = javaClass.simpleName) : ScopedTest() { open class DarksideTest : ScopedTest() {
val sithLord = DarksideTestCoordinator() val sithLord = DarksideTestCoordinator()
val validator = sithLord.validator val validator = sithLord.validator

View File

@ -1,7 +1,6 @@
package cash.z.ecc.android.sdk.darkside.test package cash.z.ecc.android.sdk.darkside.test
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.internal.twig import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
@ -101,13 +100,13 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
twig("got processor status $it") twig("got processor status $it")
if (it == Synchronizer.Status.DISCONNECTED) { if (it == Synchronizer.Status.DISCONNECTED) {
twig("waiting a bit before giving up on connection...") twig("waiting a bit before giving up on connection...")
} else if (targetHeight != null && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) { } else if (targetHeight != null && synchronizer.processor.getLastScannedHeight() < targetHeight) {
twig("awaiting new blocks from server...") twig("awaiting new blocks from server...")
} }
}.map { }.map {
// whenever we're waiting for a target height, for simplicity, if we're sleeping, // whenever we're waiting for a target height, for simplicity, if we're sleeping,
// and in between polls, then consider it that we're not synced // and in between polls, then consider it that we're not synced
if (targetHeight != null && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) { if (targetHeight != null && synchronizer.processor.getLastScannedHeight() < targetHeight) {
twig("switching status to DOWNLOADING because we're still waiting for height $targetHeight") twig("switching status to DOWNLOADING because we're still waiting for height $targetHeight")
Synchronizer.Status.DOWNLOADING Synchronizer.Status.DOWNLOADING
} else { } else {
@ -145,8 +144,8 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
fun validateHasBlock(height: BlockHeight) { fun validateHasBlock(height: BlockHeight) {
runBlocking { runBlocking {
assertTrue((synchronizer as SdkSynchronizer).findBlockHashAsHex(height) != null) assertTrue(synchronizer.findBlockHashAsHex(height) != null)
assertTrue((synchronizer as SdkSynchronizer).findBlockHash(height)?.size ?: 0 > 0) assertTrue(synchronizer.findBlockHash(height)?.size ?: 0 > 0)
} }
} }
@ -193,7 +192,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
} }
fun validateBlockHash(height: BlockHeight, expectedHash: String) { fun validateBlockHash(height: BlockHeight, expectedHash: String) {
val hash = runBlocking { (synchronizer as SdkSynchronizer).findBlockHashAsHex(height) } val hash = runBlocking { synchronizer.findBlockHashAsHex(height) }
assertEquals(expectedHash, hash) assertEquals(expectedHash, hash)
} }
@ -202,7 +201,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
} }
fun validateTxCount(count: Int) { fun validateTxCount(count: Int) {
val txCount = runBlocking { (synchronizer as SdkSynchronizer).getTransactionCount() } val txCount = runBlocking { synchronizer.getTransactionCount() }
assertEquals("Expected $count transactions but found $txCount instead!", count, txCount) assertEquals("Expected $count transactions but found $txCount instead!", count, txCount)
} }
@ -216,7 +215,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
} }
} }
suspend fun validateBalance(available: Long = -1, total: Long = -1, accountIndex: Int = 0) { suspend fun validateBalance(available: Long = -1, total: Long = -1, accountIndex: Int = 0) {
val balance = (synchronizer as SdkSynchronizer).processor.getBalanceInfo(accountIndex) val balance = synchronizer.processor.getBalanceInfo(accountIndex)
if (available > 0) { if (available > 0) {
assertEquals("invalid available balance", available, balance.available) assertEquals("invalid available balance", available, balance.available)
} }

View File

@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.twig import cash.z.ecc.android.sdk.internal.twig
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@ -19,6 +20,7 @@ import org.junit.Before
import org.junit.BeforeClass import org.junit.BeforeClass
import java.util.concurrent.TimeoutException import java.util.concurrent.TimeoutException
@OptIn(DelicateCoroutinesApi::class)
open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisites() { open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisites() {
protected lateinit var testScope: CoroutineScope protected lateinit var testScope: CoroutineScope
@ -60,7 +62,7 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisi
fun createScope() { fun createScope() {
twig("======================= CLASS STARTED ===============================") twig("======================= CLASS STARTED ===============================")
classScope = CoroutineScope( classScope = CoroutineScope(
SupervisorJob() + newFixedThreadPoolContext(2, this.javaClass.simpleName) SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName)
) )
} }

View File

@ -12,9 +12,9 @@ class SimpleMnemonics : MnemonicPlugin {
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language) override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy() override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> = MnemonicCode(entropy).words override fun nextMnemonicList(seed: ByteArray): List<CharArray> = MnemonicCode(seed).words
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed() override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
} }

View File

@ -18,6 +18,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -34,6 +35,7 @@ import java.util.concurrent.TimeoutException
* A simple wallet that connects to testnet for integration testing. The intention is that it is * A simple wallet that connects to testnet for integration testing. The intention is that it is
* easy to drive and nice to use. * easy to drive and nice to use.
*/ */
@OptIn(DelicateCoroutinesApi::class)
class TestWallet( class TestWallet(
val seedPhrase: String, val seedPhrase: String,
val alias: String = "TestWallet", val alias: String = "TestWallet",

View File

@ -70,14 +70,14 @@ class SampleCodeTest {
) )
} }
assertEquals(1, spendingKeys.size) assertEquals(1, spendingKeys.size)
log("Spending Key: ${spendingKeys?.get(0)}") log("Spending Key: ${spendingKeys[0]}")
} }
// /////////////////////////////////////////////////// // ///////////////////////////////////////////////////
// Get Address // Get Address
@Test fun getAddress() = runBlocking { @Test fun getAddress() = runBlocking {
val address = synchronizer.getAddress() val address = synchronizer.getAddress()
assertFalse(address.isNullOrBlank()) assertFalse(address.isBlank())
log("Address: $address") log("Address: $address")
} }

View File

@ -17,11 +17,11 @@ import com.google.android.material.snackbar.Snackbar
abstract class BaseDemoFragment<T : ViewBinding> : Fragment() { abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
/** /**
* Since the lightwalletservice is not a component that apps typically use, directly, we provide * Since the lightWalletService is not a component that apps typically use, directly, we provide
* this from one place. Everything that can be done with the service can/should be done with the * this from one place. Everything that can be done with the service can/should be done with the
* synchronizer because it wraps the service. * synchronizer because it wraps the service.
*/ */
val lightwalletService get() = mainActivity()?.lightwalletService val lightWalletService get() = mainActivity()?.lightWalletService
// contains view information provided by the user // contains view information provided by the user
val sharedViewModel: SharedViewModel by activityViewModels() val sharedViewModel: SharedViewModel by activityViewModels()
@ -76,9 +76,8 @@ abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
* Convenience function to the given text to the clipboard. * Convenience function to the given text to the clipboard.
*/ */
open fun copyToClipboard(text: String, description: String = "Copied to clipboard!") { open fun copyToClipboard(text: String, description: String = "Copied to clipboard!") {
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)?.let { cm -> (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
cm.setPrimaryClip(ClipData.newPlainText("DemoAppClip", text)) .setPrimaryClip(ClipData.newPlainText("DemoAppClip", text))
}
toast(description) toast(description)
} }

View File

@ -41,7 +41,7 @@ class MainActivity :
* this object because it would utilize the synchronizer, instead, which exposes APIs that * this object because it would utilize the synchronizer, instead, which exposes APIs that
* automatically sync with the server. * automatically sync with the server.
*/ */
var lightwalletService: LightWalletService? = null var lightWalletService: LightWalletService? = null
private set private set
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -53,8 +53,8 @@ class MainActivity :
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
val fab: FloatingActionButton = findViewById(R.id.fab) val fab: FloatingActionButton = findViewById(R.id.fab)
fab.setOnClickListener { view -> fab.setOnClickListener {
onFabClicked(view) onFabClicked()
} }
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view) val navView: NavigationView = findViewById(R.id.nav_view)
@ -78,7 +78,7 @@ class MainActivity :
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
lightwalletService?.shutdown() lightWalletService?.shutdown()
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -107,17 +107,17 @@ class MainActivity :
// //
private fun initService() { private fun initService() {
if (lightwalletService != null) { if (lightWalletService != null) {
lightwalletService?.shutdown() lightWalletService?.shutdown()
} }
val network = ZcashNetwork.fromResources(applicationContext) val network = ZcashNetwork.fromResources(applicationContext)
lightwalletService = LightWalletGrpcService.new( lightWalletService = LightWalletGrpcService.new(
applicationContext, applicationContext,
LightWalletEndpoint.defaultForNetwork(network) LightWalletEndpoint.defaultForNetwork(network)
) )
} }
private fun onFabClicked(view: View) { private fun onFabClicked() {
fabListener?.onActionButtonClicked() fabListener?.onActionButtonClicked()
} }
@ -126,8 +126,10 @@ class MainActivity :
// //
fun getClipboardText(): String? { fun getClipboardText(): String? {
return with(clipboard) { with(clipboard) {
if (!hasPrimaryClip()) return null if (!hasPrimaryClip()) {
return null
}
return primaryClip!!.getItemAt(0)?.coerceToText(this@MainActivity)?.toString() return primaryClip!!.getItemAt(0)?.coerceToText(this@MainActivity)?.toString()
} }
} }

View File

@ -1,9 +1,9 @@
package cash.z.ecc.android.sdk.demoapp.demos.getblock package cash.z.ecc.android.sdk.demoapp.demos.getblock
import android.os.Bundle import android.os.Bundle
import android.text.Html
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.text.HtmlCompat
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockBinding import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockBinding
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
@ -26,10 +26,10 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
private fun setBlockHeight(blockHeight: BlockHeight) { private fun setBlockHeight(blockHeight: BlockHeight) {
val blocks = val blocks =
lightwalletService?.getBlockRange(blockHeight..blockHeight) lightWalletService?.getBlockRange(blockHeight..blockHeight)
val block = blocks?.firstOrNull() val block = blocks?.firstOrNull()
binding.textInfo.visibility = View.VISIBLE binding.textInfo.visibility = View.VISIBLE
binding.textInfo.text = Html.fromHtml( binding.textInfo.text = HtmlCompat.fromHtml(
""" """
<b>block height:</b> ${block?.height.withCommas()} <b>block height:</b> ${block?.height.withCommas()}
<br/><b>block time:</b> ${block?.time.toRelativeTime(requireApplicationContext())} <br/><b>block time:</b> ${block?.time.toRelativeTime(requireApplicationContext())}
@ -37,10 +37,12 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
<br/><b>hash:</b> ${block?.hash?.toByteArray()?.toHex()} <br/><b>hash:</b> ${block?.hash?.toByteArray()?.toHex()}
<br/><b>prevHash:</b> ${block?.prevHash?.toByteArray()?.toHex()} <br/><b>prevHash:</b> ${block?.prevHash?.toByteArray()?.toHex()}
${block?.vtxList.toHtml()} ${block?.vtxList.toHtml()}
""".trimIndent() """.trimIndent(),
HtmlCompat.FROM_HTML_MODE_LEGACY
) )
} }
@Suppress("UNUSED_PARAMETER")
private fun onApply(_unused: View? = null) { private fun onApply(_unused: View? = null) {
val network = ZcashNetwork.fromResources(requireApplicationContext()) val network = ZcashNetwork.fromResources(requireApplicationContext())
val newHeight = min(binding.textBlockHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value) val newHeight = min(binding.textBlockHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)

View File

@ -1,9 +1,9 @@
package cash.z.ecc.android.sdk.demoapp.demos.getblockrange package cash.z.ecc.android.sdk.demoapp.demos.getblockrange
import android.os.Bundle import android.os.Bundle
import android.text.Html
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.text.HtmlCompat
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.R import cash.z.ecc.android.sdk.demoapp.R
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockRangeBinding import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockRangeBinding
@ -27,13 +27,13 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
private fun setBlockRange(blockRange: ClosedRange<BlockHeight>) { private fun setBlockRange(blockRange: ClosedRange<BlockHeight>) {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val blocks = val blocks =
lightwalletService?.getBlockRange(blockRange) lightWalletService?.getBlockRange(blockRange)
val fetchDelta = System.currentTimeMillis() - start val fetchDelta = System.currentTimeMillis() - start
// Note: This is a demo so we won't worry about iterating efficiently over these blocks // Note: This is a demo so we won't worry about iterating efficiently over these blocks
// Note: Converting the blocks sequence to a list can consume a lot of memory and may // Note: Converting the blocks sequence to a list can consume a lot of memory and may
// cause OOM. // cause OOM.
binding.textInfo.text = Html.fromHtml( binding.textInfo.text = HtmlCompat.fromHtml(
blocks?.toList()?.run { blocks?.toList()?.run {
val count = size val count = size
val emptyCount = count { it.vtxCount == 0 } val emptyCount = count { it.vtxCount == 0 }
@ -66,13 +66,15 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
<br/><b>avg OUTs [per block / per TX]:</b> ${"%.1f / %.1f".format(outCount.toDouble() / (count - emptyCount), outCount.toDouble() / txCount)} <br/><b>avg OUTs [per block / per TX]:</b> ${"%.1f / %.1f".format(outCount.toDouble() / (count - emptyCount), outCount.toDouble() / txCount)}
<br/><b>avg INs [per block / per TX]:</b> ${"%.1f / %.1f".format(inCount.toDouble() / (count - emptyCount), inCount.toDouble() / txCount)} <br/><b>avg INs [per block / per TX]:</b> ${"%.1f / %.1f".format(inCount.toDouble() / (count - emptyCount), inCount.toDouble() / txCount)}
<br/><b>most shielded TXs:</b> ${if (maxTxs == null) "none" else "${maxTxs.vtxCount} in block ${maxTxs.height.withCommas()}"} <br/><b>most shielded TXs:</b> ${if (maxTxs == null) "none" else "${maxTxs.vtxCount} in block ${maxTxs.height.withCommas()}"}
<br/><b>most shielded INs:</b> ${if (maxInTx == null) "none" else "${maxInTx.spendsCount} in block ${maxIns?.height.withCommas()} at tx index ${maxInTx.index}"} <br/><b>most shielded INs:</b> ${if (maxInTx == null) "none" else "${maxInTx.spendsCount} in block ${maxIns.height.withCommas()} at tx index ${maxInTx.index}"}
<br/><b>most shielded OUTs:</b> ${if (maxOutTx == null) "none" else "${maxOutTx?.outputsCount} in block ${maxOuts?.height.withCommas()} at tx index ${maxOutTx?.index}"} <br/><b>most shielded OUTs:</b> ${if (maxOutTx == null) "none" else "${maxOutTx.outputsCount} in block ${maxOuts.height.withCommas()} at tx index ${maxOutTx.index}"}
""".trimIndent() """.trimIndent()
} ?: "No blocks found in that range." } ?: "No blocks found in that range.",
HtmlCompat.FROM_HTML_MODE_LEGACY
) )
} }
@Suppress("UNUSED_PARAMETER")
private fun onApply(_unused: View) { private fun onApply(_unused: View) {
val network = ZcashNetwork.fromResources(requireApplicationContext()) val network = ZcashNetwork.fromResources(requireApplicationContext())
val start = max(binding.textStartHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value) val start = max(binding.textStartHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)

View File

@ -15,7 +15,7 @@ class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>
private fun displayLatestHeight() { private fun displayLatestHeight() {
// note: this is a blocking call, a real app wouldn't do this on the main thread // note: this is a blocking call, a real app wouldn't do this on the main thread
// instead, a production app would leverage the synchronizer like in the other demos // instead, a production app would leverage the synchronizer like in the other demos
binding.textInfo.text = lightwalletService?.getLatestBlockHeight().toString() binding.textInfo.text = lightWalletService?.getLatestBlockHeight().toString()
} }
// //

View File

@ -2,6 +2,8 @@ package cash.z.ecc.android.sdk.demoapp.demos.getprivatekey
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed import cash.z.ecc.android.bip39.toSeed
@ -61,9 +63,14 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
// Android Lifecycle overrides // Android Lifecycle overrides
// //
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onCreateView(
super.onActivityCreated(savedInstanceState) inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
setup() setup()
return view
} }
override fun onResume() { override fun onResume() {

View File

@ -49,12 +49,14 @@ class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
mainActivity()?.removeClipboardListener() mainActivity()?.removeClipboardListener()
} }
@Suppress("UNUSED_PARAMETER")
private fun onEditSeedPhrase(unused: View) { private fun onEditSeedPhrase(unused: View) {
setEditShown(true) setEditShown(true)
binding.inputSeedPhrase.setText(sharedViewModel.seedPhrase.value) binding.inputSeedPhrase.setText(sharedViewModel.seedPhrase.value)
binding.textLayoutSeedPhrase.helperText = "" binding.textLayoutSeedPhrase.helperText = ""
} }
@Suppress("UNUSED_PARAMETER")
private fun onAcceptSeedPhrase(unused: View) { private fun onAcceptSeedPhrase(unused: View) {
if (applySeedPhrase()) { if (applySeedPhrase()) {
setEditShown(false) setEditShown(false)
@ -62,10 +64,12 @@ class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
} }
} }
@Suppress("UNUSED_PARAMETER")
private fun onCancelSeedPhrase(unused: View) { private fun onCancelSeedPhrase(unused: View) {
setEditShown(false) setEditShown(false)
} }
@Suppress("UNUSED_PARAMETER")
private fun onPasteSeedPhrase(unused: View) { private fun onPasteSeedPhrase(unused: View) {
mainActivity()?.getClipboardText().let { clipboardText -> mainActivity()?.getClipboardText().let { clipboardText ->
binding.inputSeedPhrase.setText(clipboardText) binding.inputSeedPhrase.setText(clipboardText)

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.demoapp.demos.listtransactions
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.bip39.Mnemonics
@ -33,7 +34,7 @@ import kotlinx.coroutines.runBlocking
class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() { class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() {
private lateinit var initializer: Initializer private lateinit var initializer: Initializer
private lateinit var synchronizer: Synchronizer private lateinit var synchronizer: Synchronizer
private lateinit var adapter: TransactionAdapter<ConfirmedTransaction> private lateinit var adapter: TransactionAdapter
private lateinit var address: String private lateinit var address: String
private var status: Synchronizer.Status? = null private var status: Synchronizer.Status? = null
private val isSynced get() = status == Synchronizer.Status.SYNCED private val isSynced get() = status == Synchronizer.Status.SYNCED
@ -134,9 +135,14 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
// Android Lifecycle overrides // Android Lifecycle overrides
// //
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onCreateView(
super.onActivityCreated(savedInstanceState) inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
setup() setup()
return view
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@ -10,30 +10,29 @@ import cash.z.ecc.android.sdk.demoapp.R
/** /**
* Simple adapter implementation that knows how to bind a recyclerview to ClearedTransactions. * Simple adapter implementation that knows how to bind a recyclerview to ClearedTransactions.
*/ */
class TransactionAdapter<T : ConfirmedTransaction> : class TransactionAdapter : ListAdapter<ConfirmedTransaction, TransactionViewHolder>(
ListAdapter<T, TransactionViewHolder<T>>( object : DiffUtil.ItemCallback<ConfirmedTransaction>() {
object : DiffUtil.ItemCallback<T>() { override fun areItemsTheSame(
override fun areItemsTheSame( oldItem: ConfirmedTransaction,
oldItem: T, newItem: ConfirmedTransaction
newItem: T ) = oldItem.minedHeight == newItem.minedHeight
) = oldItem.minedHeight == newItem.minedHeight
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: T, oldItem: ConfirmedTransaction,
newItem: T newItem: ConfirmedTransaction
) = oldItem == newItem ) = oldItem == newItem
} }
) { ) {
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
) = TransactionViewHolder<T>( ) = TransactionViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false) LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false)
) )
override fun onBindViewHolder( override fun onBindViewHolder(
holder: TransactionViewHolder<T>, holder: TransactionViewHolder,
position: Int position: Int
) = holder.bindTo(getItem(position)) ) = holder.bindTo(getItem(position))
} }

View File

@ -15,18 +15,18 @@ import java.util.Locale
/** /**
* Simple view holder for displaying confirmed transactions in the recyclerview. * Simple view holder for displaying confirmed transactions in the recyclerview.
*/ */
class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) { class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount) private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
private val infoText = itemView.findViewById<TextView>(R.id.text_transaction_info) private val infoText = itemView.findViewById<TextView>(R.id.text_transaction_info)
private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp) private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
private val icon = itemView.findViewById<ImageView>(R.id.image_transaction_type) private val icon = itemView.findViewById<ImageView>(R.id.image_transaction_type)
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault()) private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
fun bindTo(transaction: T?) { fun bindTo(transaction: ConfirmedTransaction?) {
val isInbound = transaction?.toAddress.isNullOrEmpty() val isInbound = transaction?.toAddress.isNullOrEmpty()
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString() amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
timeText.text = timeText.text =
if (transaction == null || transaction?.blockTimeInSeconds == 0L) "Pending" if (transaction == null || transaction.blockTimeInSeconds == 0L) "Pending"
else formatter.format(transaction.blockTimeInSeconds * 1000L) else formatter.format(transaction.blockTimeInSeconds * 1000L)
infoText.text = getMemoString(transaction) infoText.text = getMemoString(transaction)
@ -35,7 +35,7 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
icon.setColorFilter(ContextCompat.getColor(itemView.context, if (isInbound) R.color.tx_inbound else R.color.tx_outbound)) icon.setColorFilter(ContextCompat.getColor(itemView.context, if (isInbound) R.color.tx_inbound else R.color.tx_outbound))
} }
private fun getMemoString(transaction: T?): String { private fun getMemoString(transaction: ConfirmedTransaction?): String {
return transaction?.memo?.takeUnless { it[0] < 0 }?.let { String(it) } ?: "no memo" return transaction?.memo?.takeUnless { it[0] < 0 }?.let { String(it) } ?: "no memo"
} }
} }

View File

@ -48,7 +48,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
private lateinit var seed: ByteArray private lateinit var seed: ByteArray
private lateinit var initializer: Initializer private lateinit var initializer: Initializer
private lateinit var synchronizer: Synchronizer private lateinit var synchronizer: Synchronizer
private lateinit var adapter: UtxoAdapter<ConfirmedTransaction> private lateinit var adapter: UtxoAdapter
private val address: String = "t1RwbKka1CnktvAJ1cSqdn7c6PXWG4tZqgd" private val address: String = "t1RwbKka1CnktvAJ1cSqdn7c6PXWG4tZqgd"
private var status: Synchronizer.Status? = null private var status: Synchronizer.Status? = null
@ -86,7 +86,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
setup() setup()
} }
fun initUi() { private fun initUi() {
binding.inputAddress.setText(address) binding.inputAddress.setText(address)
binding.inputRangeStart.setText(ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString()) binding.inputRangeStart.setText(ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString())
binding.inputRangeEnd.setText(getUxtoEndHeight(requireApplicationContext()).value.toString()) binding.inputRangeEnd.setText(getUxtoEndHeight(requireApplicationContext()).value.toString())
@ -99,7 +99,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
initTransactionUi() initTransactionUi()
} }
fun downloadTransactions() { private fun downloadTransactions() {
binding.textStatus.text = "loading..." binding.textStatus.text = "loading..."
binding.textStatus.post { binding.textStatus.post {
val network = ZcashNetwork.fromResources(requireApplicationContext()) val network = ZcashNetwork.fromResources(requireApplicationContext())
@ -114,7 +114,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
?: getUxtoEndHeight(requireApplicationContext()).value ?: getUxtoEndHeight(requireApplicationContext()).value
var allStart = now var allStart = now
twig("loading transactions in range $startToUse..$endToUse") twig("loading transactions in range $startToUse..$endToUse")
val txids = lightwalletService?.getTAddressTransactions( val txids = lightWalletService?.getTAddressTransactions(
addressToUse, addressToUse,
BlockHeight.new(network, startToUse)..BlockHeight.new(network, endToUse) BlockHeight.new(network, startToUse)..BlockHeight.new(network, endToUse)
) )
@ -131,18 +131,16 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
// twig("failed to decrypt and store transaction due to: $t") // twig("failed to decrypt and store transaction due to: $t")
// } // }
// } // }
}?.let { txData -> }?.let { _ ->
// Disabled during migration to newer SDK version; this appears to have been // Disabled during migration to newer SDK version; this appears to have been
// leveraging non-public APIs in the SDK so perhaps should be removed // leveraging non-public APIs in the SDK so perhaps should be removed
// val parseStart = now // val parseStart = now
// val tList = LocalRpcTypes.TransactionDataList.newBuilder().addAllData(txData).build() // val tList = LocalRpcTypes.TransactionDataList.newBuilder().addAllData(txData).build()
// val parsedTransactions = initializer.rustBackend.parseTransactionDataList(tList) // val parsedTransactions = initializer.rustBackend.parseTransactionDataList(tList)
// delta = now - parseStart // delta = now - parseStart
// updateStatus("parsed txs in ${delta}ms.") // updateStatus("parsed txs in ${delta}ms.")
} }
(synchronizer as SdkSynchronizer).refreshTransactions() (synchronizer as SdkSynchronizer).refreshTransactions()
// val finalCount = (synchronizer as SdkSynchronizer).getTransactionCount()
// "found ${finalCount - initialCount} shielded outputs.
delta = now - allStart delta = now - allStart
updateStatus("Total time ${delta}ms.") updateStatus("Total time ${delta}ms.")
@ -199,7 +197,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
} }
} }
synchronizer.clearedTransactions.collectWith(lifecycleScope, ::onTransactionsUpdated) synchronizer.clearedTransactions.collectWith(lifecycleScope, ::onTransactionsUpdated)
// synchronizer.receivedTransactions.collectWith(lifecycleScope, ::onTransactionsUpdated) // synchronizer.receivedTransactions.collectWith(lifecycleScope, ::onTransactionsUpdated)
} catch (t: Throwable) { } catch (t: Throwable) {
twig("failed to start the synchronizer!!! due to : $t") twig("failed to start the synchronizer!!! due to : $t")
} }
@ -220,12 +218,12 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false) LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
adapter = UtxoAdapter() adapter = UtxoAdapter()
binding.recyclerTransactions.adapter = adapter binding.recyclerTransactions.adapter = adapter
// lifecycleScope.launch { // lifecycleScope.launch {
// // address = synchronizer.getAddress() // address = synchronizer.getAddress()
// synchronizer.receivedTransactions.onEach { // synchronizer.receivedTransactions.onEach {
// onTransactionsUpdated(it) // onTransactionsUpdated(it)
// }.launchIn(this) // }.launchIn(this)
// } // }
} }
private fun startSynchronizer() { private fun startSynchronizer() {

View File

@ -10,30 +10,29 @@ import cash.z.ecc.android.sdk.demoapp.R
/** /**
* Simple adapter implementation that knows how to bind a recyclerview to ClearedTransactions. * Simple adapter implementation that knows how to bind a recyclerview to ClearedTransactions.
*/ */
class UtxoAdapter<T : ConfirmedTransaction> : class UtxoAdapter : ListAdapter<ConfirmedTransaction, UtxoViewHolder>(
ListAdapter<T, UtxoViewHolder<T>>( object : DiffUtil.ItemCallback<ConfirmedTransaction>() {
object : DiffUtil.ItemCallback<T>() { override fun areItemsTheSame(
override fun areItemsTheSame( oldItem: ConfirmedTransaction,
oldItem: T, newItem: ConfirmedTransaction
newItem: T ) = oldItem.minedHeight == newItem.minedHeight
) = oldItem.minedHeight == newItem.minedHeight
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: T, oldItem: ConfirmedTransaction,
newItem: T newItem: ConfirmedTransaction
) = oldItem == newItem ) = oldItem == newItem
} }
) { ) {
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
) = UtxoViewHolder<T>( ) = UtxoViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false) LayoutInflater.from(parent.context).inflate(R.layout.item_transaction, parent, false)
) )
override fun onBindViewHolder( override fun onBindViewHolder(
holder: UtxoViewHolder<T>, holder: UtxoViewHolder,
position: Int position: Int
) = holder.bindTo(getItem(position)) ) = holder.bindTo(getItem(position))
} }

View File

@ -13,21 +13,21 @@ import java.util.Locale
/** /**
* Simple view holder for displaying confirmed transactions in the recyclerview. * Simple view holder for displaying confirmed transactions in the recyclerview.
*/ */
class UtxoViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) { class UtxoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount) private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
private val infoText = itemView.findViewById<TextView>(R.id.text_transaction_info) private val infoText = itemView.findViewById<TextView>(R.id.text_transaction_info)
private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp) private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault()) private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
fun bindTo(transaction: T?) { fun bindTo(transaction: ConfirmedTransaction?) {
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString() amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
timeText.text = timeText.text =
if (transaction == null || transaction?.blockTimeInSeconds == 0L) "Pending" if (transaction == null || transaction.blockTimeInSeconds == 0L) "Pending"
else formatter.format(transaction.blockTimeInSeconds * 1000L) else formatter.format(transaction.blockTimeInSeconds * 1000L)
infoText.text = getMemoString(transaction) infoText.text = getMemoString(transaction)
} }
private fun getMemoString(transaction: T?): String { private fun getMemoString(transaction: ConfirmedTransaction?): String {
return transaction?.memo?.takeUnless { it[0] < 0 }?.let { String(it) } ?: "no memo" return transaction?.memo?.takeUnless { it[0] < 0 }?.let { String(it) } ?: "no memo"
} }
} }

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.demoapp.demos.send
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.bip39.Mnemonics import cash.z.ecc.android.bip39.Mnemonics
@ -162,6 +163,7 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
} }
} }
@Suppress("UNUSED_PARAMETER")
private fun onSend(unused: View) { private fun onSend(unused: View) {
isSending = true isSending = true
val amount = amountInput.text.toString().toDouble().convertZecToZatoshi() val amount = amountInput.text.toString().toDouble().convertZecToZatoshi()
@ -221,9 +223,14 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
// Android Lifecycle overrides // Android Lifecycle overrides
// //
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onCreateView(
super.onActivityCreated(savedInstanceState) inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
setup() setup()
return view
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@ -5,7 +5,7 @@ package cash.z.ecc.android.sdk.demoapp.util
import android.content.Context import android.content.Context
import cash.z.ecc.android.sdk.demoapp.R import cash.z.ecc.android.sdk.demoapp.R
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import java.util.* import java.util.Locale
fun ZcashNetwork.Companion.fromResources(context: Context): ZcashNetwork { fun ZcashNetwork.Companion.fromResources(context: Context): ZcashNetwork {
val networkNameFromResources = context.getString(R.string.network_name).lowercase(Locale.ROOT) val networkNameFromResources = context.getString(R.string.network_name).lowercase(Locale.ROOT)

View File

@ -31,6 +31,7 @@ class SampleStorage(context: Context) {
* the SDK. This class delegates to the storage object. For demo purposes, we're using an insecure * the SDK. This class delegates to the storage object. For demo purposes, we're using an insecure
* SampleStorage implementation but this can easily be swapped for a truly secure storage solution. * SampleStorage implementation but this can easily be swapped for a truly secure storage solution.
*/ */
@Suppress("deprecation")
class SampleStorageBridge(context: Context) { class SampleStorageBridge(context: Context) {
private val delegate = SampleStorage(context.applicationContext) private val delegate = SampleStorage(context.applicationContext)

View File

@ -19,9 +19,9 @@ class SimpleMnemonics : MnemonicPlugin {
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language) override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy() override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> = MnemonicCode(entropy).words override fun nextMnemonicList(seed: ByteArray): List<CharArray> = MnemonicCode(seed).words
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed() override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
} }

View File

@ -25,8 +25,7 @@ IS_SNAPSHOT=true
LIBRARY_VERSION=1.9.0-beta03 LIBRARY_VERSION=1.9.0-beta03
# Kotlin compiler warnings can be considered errors, failing the build. # Kotlin compiler warnings can be considered errors, failing the build.
# Currently set to false, because this project has a lot of warnings to fix first. ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=true
ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=false
# Optionally configure code coverage, as historically Jacoco has at times been buggy with respect to new Kotlin versions # Optionally configure code coverage, as historically Jacoco has at times been buggy with respect to new Kotlin versions
IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED=false IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED=false

View File

@ -122,10 +122,8 @@ android {
useLibrary("android.test.runner") useLibrary("android.test.runner")
defaultConfig { defaultConfig {
javaCompileOptions { ksp {
annotationProcessorOptions { arg("room.schemaLocation", "$projectDir/schemas")
argument("room.schemaLocation", "$projectDir/schemas")
}
} }
consumerProguardFiles("proguard-consumer.txt") consumerProguardFiles("proguard-consumer.txt")
@ -301,6 +299,7 @@ dependencies {
androidTestImplementation(libs.androidx.test.junit) androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.test.core) androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.coroutines.okhttp) androidTestImplementation(libs.coroutines.okhttp)
androidTestImplementation(libs.kotlin.test)
androidTestImplementation(libs.kotlinx.coroutines.test) androidTestImplementation(libs.kotlinx.coroutines.test)
// used by 'ru.gildor.corutines.okhttp.await' (to make simple suspended requests) and breaks on versions higher than 3.8.0 // used by 'ru.gildor.corutines.okhttp.await' (to make simple suspended requests) and breaks on versions higher than 3.8.0
androidTestImplementation(libs.okhttp) androidTestImplementation(libs.okhttp)

View File

@ -8,6 +8,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONObject import org.json.JSONObject
import ru.gildor.coroutines.okhttp.await import ru.gildor.coroutines.okhttp.await
import kotlin.test.assertNotNull
fun Initializer.Config.seedPhrase(seedPhrase: String, network: ZcashNetwork) { fun Initializer.Config.seedPhrase(seedPhrase: String, network: ZcashNetwork) {
runBlocking { setSeed(SimpleMnemonics().toSeed(seedPhrase.toCharArray()), network) } runBlocking { setSeed(SimpleMnemonics().toSeed(seedPhrase.toCharArray()), network) }
@ -21,6 +22,7 @@ object BlockExplorer {
.build() .build()
val result = client.newCall(request).await() val result = client.newCall(request).await()
val body = result.body?.string() val body = result.body?.string()
assertNotNull(body, "Body can not be null.")
return JSONObject(body).getJSONArray("data").getJSONObject(0).getLong("id") return JSONObject(body).getJSONArray("data").getJSONObject(0).getLong("id")
} }
} }

View File

@ -9,11 +9,11 @@ import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.TestWallet import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.Parameterized import org.junit.runners.Parameterized
import kotlin.test.DefaultAsserter.assertEquals
import kotlin.test.DefaultAsserter.assertTrue
// TODO [#650]: https://github.com/zcash/zcash-android-wallet-sdk/issues/650 // TODO [#650]: https://github.com/zcash/zcash-android-wallet-sdk/issues/650
@ -110,7 +110,7 @@ class SanityTest(
twig(it) twig(it)
}.getOrElse { return@runBlocking } }.getOrElse { return@runBlocking }
val downloaderHeight = runCatching { runCatching {
wallet.service.getLatestBlockHeight() wallet.service.getLatestBlockHeight()
}.getOrNull() ?: return@runBlocking }.getOrNull() ?: return@runBlocking
assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height == height.value) assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height == height.value)

View File

@ -25,12 +25,12 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class TestnetIntegrationTest : ScopedTest() { class TestnetIntegrationTest : ScopedTest() {
@ -50,7 +50,7 @@ class TestnetIntegrationTest : ScopedTest() {
@Test @Test
fun testLoadBirthday() { fun testLoadBirthday() {
val (height, hash, time, tree) = runBlocking { val (height) = runBlocking {
CheckpointTool.loadNearest( CheckpointTool.loadNearest(
context, context,
synchronizer.network, synchronizer.network,
@ -81,8 +81,8 @@ class TestnetIntegrationTest : ScopedTest() {
} }
assertTrue( assertTrue(
"No funds available when we expected a balance greater than zero!", availableBalance!!.value > 0,
availableBalance!!.value > 0 "No funds available when we expected a balance greater than zero!"
) )
} }
@ -105,7 +105,7 @@ class TestnetIntegrationTest : ScopedTest() {
ZcashSdk.MINERS_FEE, ZcashSdk.MINERS_FEE,
toAddress, toAddress,
"first mainnet tx from the SDK" "first mainnet tx from the SDK"
).filter { it?.isSubmitSuccess() == true }.onFirst { ).filter { it.isSubmitSuccess() }.onFirst {
log("DONE SENDING!!!") log("DONE SENDING!!!")
} }
log("returning true from sendFunds") log("returning true from sendFunds")

View File

@ -24,10 +24,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Test import org.junit.Test
@ -35,6 +31,10 @@ import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import java.io.File import java.io.File
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
@MaintainedTest(TestPurpose.REGRESSION) @MaintainedTest(TestPurpose.REGRESSION)
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -69,11 +69,11 @@ class PersistentTransactionManagerTest : ScopedTest() {
} }
private fun initMocks() { private fun initMocks() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.openMocks(this)
mockEncoder.stub { mockEncoder.stub {
onBlocking { onBlocking {
createTransaction(any(), any(), any(), any(), any()) createTransaction(any(), any(), any(), any(), any())
}.thenAnswer { invocation -> }.thenAnswer {
runBlocking { runBlocking {
delay(200) delay(200)
EncodedTransaction(byteArrayOf(1, 2, 3), byteArrayOf(8, 9), 5_000_000) EncodedTransaction(byteArrayOf(1, 2, 3), byteArrayOf(8, 9), 5_000_000)
@ -100,7 +100,7 @@ class PersistentTransactionManagerTest : ScopedTest() {
txFlow.drop(2).onEach { txFlow.drop(2).onEach {
twig("found tx: $it") twig("found tx: $it")
assertTrue("Expected the encoded tx to be cancelled but it wasn't", it.isCancelled()) assertTrue(it.isCancelled(), "Expected the encoded tx to be cancelled but it wasn't")
twig("found it to be successfully cancelled") twig("found it to be successfully cancelled")
testScope.cancel() testScope.cancel()
}.launchIn(testScope).join() }.launchIn(testScope).join()
@ -112,16 +112,16 @@ class PersistentTransactionManagerTest : ScopedTest() {
assertFalse(tx.isCancelled()) assertFalse(tx.isCancelled())
manager.cancel(tx.id) manager.cancel(tx.id)
tx = manager.findById(tx.id)!! tx = manager.findById(tx.id)!!
assertTrue("Transaction was not cancelled", tx.isCancelled()) assertTrue(tx.isCancelled(), "Transaction was not cancelled")
} }
@Test @Test
fun testAbort() = runBlocking { fun testAbort() = runBlocking {
var tx: PendingTransaction? = manager.initSpend(Zatoshi(1234), "a", "b", 0) var tx: PendingTransaction? = manager.initSpend(Zatoshi(1234), "a", "b", 0)
assertNotNull(tx) assertNotNull(tx)
manager.abort(tx!!) manager.abort(tx)
tx = manager.findById(tx.id) tx = manager.findById(tx.id)
assertNull("Transaction was not removed from the DB", tx) assertNull(tx, "Transaction was not removed from the DB")
} }
companion object { companion object {

View File

@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.twig import cash.z.ecc.android.sdk.internal.twig
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@ -19,6 +20,7 @@ import org.junit.Before
import org.junit.BeforeClass import org.junit.BeforeClass
import java.util.concurrent.TimeoutException import java.util.concurrent.TimeoutException
@OptIn(DelicateCoroutinesApi::class)
open class ScopedTest(val defaultTimeout: Long = 2000L) { open class ScopedTest(val defaultTimeout: Long = 2000L) {
protected lateinit var testScope: CoroutineScope protected lateinit var testScope: CoroutineScope
@ -60,7 +62,7 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) {
fun createScope() { fun createScope() {
twig("======================= CLASS STARTED ===============================") twig("======================= CLASS STARTED ===============================")
classScope = CoroutineScope( classScope = CoroutineScope(
SupervisorJob() + newFixedThreadPoolContext(2, this.javaClass.simpleName) SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName)
) )
} }

View File

@ -12,9 +12,9 @@ class SimpleMnemonics : MnemonicPlugin {
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language) override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy() override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> = MnemonicCode(entropy).words override fun nextMnemonicList(seed: ByteArray): List<CharArray> = MnemonicCode(seed).words
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed() override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
} }

View File

@ -18,6 +18,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -34,6 +35,7 @@ import java.util.concurrent.TimeoutException
* A simple wallet that connects to testnet for integration testing. The intention is that it is * A simple wallet that connects to testnet for integration testing. The intention is that it is
* easy to drive and nice to use. * easy to drive and nice to use.
*/ */
@OptIn(DelicateCoroutinesApi::class)
class TestWallet( class TestWallet(
val seedPhrase: String, val seedPhrase: String,
val alias: String = "TestWallet", val alias: String = "TestWallet",

View File

@ -20,7 +20,7 @@ import java.io.File
/** /**
* Simplified Initializer focused on starting from a ViewingKey. * Simplified Initializer focused on starting from a ViewingKey.
*/ */
@Suppress("LongParameterList") @Suppress("LongParameterList", "unused")
class Initializer private constructor( class Initializer private constructor(
val context: Context, val context: Context,
internal val rustBackend: RustBackend, internal val rustBackend: RustBackend,
@ -147,12 +147,10 @@ class Initializer private constructor(
/** /**
* Set the server and the network property at the same time to prevent them from getting out * Set the server and the network property at the same time to prevent them from getting out
* of sync. Ultimately, this determines which host a synchronizer will use in order to * of sync. Ultimately, this determines which host a synchronizer will use in order to
* connect to lightwalletd. In most cases, the default host is sufficient but an override * connect to lightwalletd.
* can be provided. The host cannot be changed without explicitly setting the network.
* *
* @param network the Zcash network to use. Either testnet or mainnet. * @param network the Zcash network to use. Either testnet or mainnet.
* @param host the lightwalletd host to use. * @param lightWalletEndpoint the light wallet endpoint to use.
* @param port the lightwalletd port to use.
*/ */
fun setNetwork( fun setNetwork(
network: ZcashNetwork, network: ZcashNetwork,
@ -311,6 +309,7 @@ class Initializer private constructor(
block: (Config) -> Unit block: (Config) -> Unit
) = new(appContext, onCriticalErrorHandler, Config(block)) ) = new(appContext, onCriticalErrorHandler, Config(block))
@Suppress("UNUSED_PARAMETER")
suspend fun new( suspend fun new(
context: Context, context: Context,
onCriticalErrorHandler: ((Throwable?) -> Boolean)?, onCriticalErrorHandler: ((Throwable?) -> Boolean)?,

View File

@ -62,7 +62,6 @@ import io.grpc.ManagedChannel
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -96,7 +95,7 @@ import kotlin.coroutines.EmptyCoroutineContext
* @property processor saves the downloaded compact blocks to the cache and then scans those blocks for * @property processor saves the downloaded compact blocks to the cache and then scans those blocks for
* data related to this wallet. * data related to this wallet.
*/ */
@ExperimentalCoroutinesApi @OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
@FlowPreview @FlowPreview
class SdkSynchronizer internal constructor( class SdkSynchronizer internal constructor(
private val storage: TransactionRepository, private val storage: TransactionRepository,
@ -109,6 +108,8 @@ class SdkSynchronizer internal constructor(
private val _saplingBalances = MutableStateFlow<WalletBalance?>(null) private val _saplingBalances = MutableStateFlow<WalletBalance?>(null)
private val _transparentBalances = MutableStateFlow<WalletBalance?>(null) private val _transparentBalances = MutableStateFlow<WalletBalance?>(null)
// TODO [#288]: Remove Deprecated Usage of ConflatedBroadcastChannel
// TODO [#288]: https://github.com/zcash/zcash-android-wallet-sdk/issues/288
private val _status = ConflatedBroadcastChannel<Synchronizer.Status>(DISCONNECTED) private val _status = ConflatedBroadcastChannel<Synchronizer.Status>(DISCONNECTED)
/** /**
@ -171,6 +172,9 @@ class SdkSynchronizer internal constructor(
* processor is finished scanning, the synchronizer updates transaction and balance info and * processor is finished scanning, the synchronizer updates transaction and balance info and
* then emits a [SYNCED] status. * then emits a [SYNCED] status.
*/ */
// TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
@Suppress("DEPRECATION")
override val status = _status.asFlow() override val status = _status.asFlow()
/** /**
@ -414,6 +418,7 @@ class SdkSynchronizer internal constructor(
twig("Synchronizer onReady complete. Processor start has exited!") twig("Synchronizer onReady complete. Processor start has exited!")
} }
@Suppress("UNUSED_PARAMETER")
private fun onCriticalError(unused: CoroutineContext?, error: Throwable) { private fun onCriticalError(unused: CoroutineContext?, error: Throwable) {
twig("********") twig("********")
twig("******** ERROR: $error") twig("******** ERROR: $error")
@ -641,8 +646,9 @@ class SdkSynchronizer internal constructor(
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX. // only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
if (encodedTx.isCancelled()) { if (encodedTx.isCancelled()) {
twig("[cleanup] this tx has been cancelled so we will cleanup instead of submitting") twig("[cleanup] this tx has been cancelled so we will cleanup instead of submitting")
if (cleanupCancelledTx(encodedTx)) refreshAllBalances() if (cleanupCancelledTx(encodedTx)) {
encodedTx refreshAllBalances()
}
} else { } else {
txManager.submit(encodedTx) txManager.submit(encodedTx)
} }
@ -673,8 +679,9 @@ class SdkSynchronizer internal constructor(
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX. // only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
if (encodedTx.isCancelled()) { if (encodedTx.isCancelled()) {
twig("[cleanup] this shielding tx has been cancelled so we will cleanup instead of submitting") twig("[cleanup] this shielding tx has been cancelled so we will cleanup instead of submitting")
if (cleanupCancelledTx(encodedTx)) refreshAllBalances() if (cleanupCancelledTx(encodedTx)) {
encodedTx refreshAllBalances()
}
} else { } else {
txManager.submit(encodedTx) txManager.submit(encodedTx)
} }
@ -685,8 +692,8 @@ class SdkSynchronizer internal constructor(
txManager.monitorById(it.id) txManager.monitorById(it.id)
}.distinctUntilChanged() }.distinctUntilChanged()
override suspend fun refreshUtxos(address: String, startHeight: BlockHeight): Int? { override suspend fun refreshUtxos(tAddr: String, since: BlockHeight): Int? {
return processor.refreshUtxos(address, startHeight) return processor.refreshUtxos(tAddr, since)
} }
override suspend fun getTransparentBalance(tAddr: String): WalletBalance { override suspend fun getTransparentBalance(tAddr: String): WalletBalance {

View File

@ -76,6 +76,7 @@ import kotlin.math.roundToInt
* in when considering initial range to download. In most cases, this should be the birthday height * in when considering initial range to download. In most cases, this should be the birthday height
* of the current wallet--the height before which we do not need to scan for transactions. * of the current wallet--the height before which we do not need to scan for transactions.
*/ */
@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
@OpenForTesting @OpenForTesting
class CompactBlockProcessor internal constructor( class CompactBlockProcessor internal constructor(
val downloader: CompactBlockDownloader, val downloader: CompactBlockDownloader,
@ -126,6 +127,8 @@ class CompactBlockProcessor internal constructor(
) )
) )
// TODO [#288]: Remove Deprecated Usage of ConflatedBroadcastChannel
// TODO [#288]: https://github.com/zcash/zcash-android-wallet-sdk/issues/288
private val _state: ConflatedBroadcastChannel<State> = ConflatedBroadcastChannel(Initialized) private val _state: ConflatedBroadcastChannel<State> = ConflatedBroadcastChannel(Initialized)
private val _progress = ConflatedBroadcastChannel(0) private val _progress = ConflatedBroadcastChannel(0)
private val _processorInfo = private val _processorInfo =
@ -161,18 +164,27 @@ class CompactBlockProcessor internal constructor(
* The flow of state values so that a wallet can monitor the state of this class without needing * The flow of state values so that a wallet can monitor the state of this class without needing
* to poll. * to poll.
*/ */
// TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
@Suppress("DEPRECATION")
val state = _state.asFlow() val state = _state.asFlow()
/** /**
* The flow of progress values so that a wallet can monitor how much downloading remains * The flow of progress values so that a wallet can monitor how much downloading remains
* without needing to poll. * without needing to poll.
*/ */
// TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
@Suppress("DEPRECATION")
val progress = _progress.asFlow() val progress = _progress.asFlow()
/** /**
* The flow of detailed processorInfo like the range of blocks that shall be downloaded and * The flow of detailed processorInfo like the range of blocks that shall be downloaded and
* scanned. This gives the wallet a lot of insight into the work of this processor. * scanned. This gives the wallet a lot of insight into the work of this processor.
*/ */
// TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
@Suppress("DEPRECATION")
val processorInfo = _processorInfo.asFlow() val processorInfo = _processorInfo.asFlow()
/** /**
@ -393,12 +405,8 @@ class CompactBlockProcessor internal constructor(
} }
} }
newTxs?.onEach { newTransaction -> newTxs.onEach { newTransaction ->
if (newTransaction == null) { enhance(newTransaction)
twig("somehow, new transaction was null!!!")
} else {
enhance(newTransaction)
}
} }
twig("Done enhancing transaction details") twig("Done enhancing transaction details")
BlockProcessingResult.Success BlockProcessingResult.Success
@ -864,6 +872,7 @@ class CompactBlockProcessor internal constructor(
* when we unexpectedly lose server connection or are waiting for an event to happen on the * when we unexpectedly lose server connection or are waiting for an event to happen on the
* chain. We can pass this desire along now and later figure out how to handle it, privately. * chain. We can pass this desire along now and later figure out how to handle it, privately.
*/ */
@Suppress("UNUSED_PARAMETER")
private fun calculatePollInterval(fastIntervalDesired: Boolean = false): Long { private fun calculatePollInterval(fastIntervalDesired: Boolean = false): Long {
val interval = POLL_INTERVAL val interval = POLL_INTERVAL
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
@ -1119,7 +1128,7 @@ class CompactBlockProcessor internal constructor(
} }
private fun Service.LightdInfo.matchingNetwork(network: String): Boolean { private fun Service.LightdInfo.matchingNetwork(network: String): Boolean {
fun String.toId() = toLowerCase(Locale.US).run { fun String.toId() = lowercase(Locale.US).run {
when { when {
contains("main") -> "mainnet" contains("main") -> "mainnet"
contains("test") -> "testnet" contains("test") -> "testnet"
@ -1140,7 +1149,6 @@ class CompactBlockProcessor internal constructor(
twig("$name MUTEX: releasing lock", -1) twig("$name MUTEX: releasing lock", -1)
} }
} }
twig("$name MUTEX: withLock complete", -1)
} }
} }

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.db.entity
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.RoomWarnings
@Entity( @Entity(
tableName = "received_notes", tableName = "received_notes",
@ -25,6 +26,7 @@ import androidx.room.ForeignKey
) )
] ]
) )
@SuppressWarnings(RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD)
data class Received( data class Received(
@ColumnInfo(name = "id_note") @ColumnInfo(name = "id_note")
val id: Int? = 0, val id: Int? = 0,

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.db.entity
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.RoomWarnings
@Entity( @Entity(
tableName = "sent_notes", tableName = "sent_notes",
@ -19,6 +20,7 @@ import androidx.room.ForeignKey
) )
] ]
) )
@SuppressWarnings(RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD)
data class Sent( data class Sent(
@ColumnInfo(name = "id_note") @ColumnInfo(name = "id_note")
val id: Int? = 0, val id: Int? = 0,

View File

@ -5,6 +5,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import androidx.room.RoomWarnings
import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.Zatoshi
@ -23,6 +24,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
) )
] ]
) )
@SuppressWarnings(RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD)
data class TransactionEntity( data class TransactionEntity(
@ColumnInfo(name = "id_tx") @ColumnInfo(name = "id_tx")
val id: Long?, val id: Long?,

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.db.entity
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.RoomWarnings
@Entity( @Entity(
tableName = "utxos", tableName = "utxos",
@ -15,6 +16,7 @@ import androidx.room.ForeignKey
) )
] ]
) )
@SuppressWarnings(RoomWarnings.MISSING_INDEX_ON_FOREIGN_KEY_CHILD)
data class Utxo( data class Utxo(
@ColumnInfo(name = "id_utxo") @ColumnInfo(name = "id_utxo")
val id: Long? = 0L, val id: Long? = 0L,

View File

@ -10,8 +10,8 @@ class BatchMetrics(val range: ClosedRange<BlockHeight>, val batchSize: Int, priv
private var batchStartTime = 0L private var batchStartTime = 0L
private var batchEndTime = 0L private var batchEndTime = 0L
private var rangeSize = range.endInclusive.value - range.start.value + 1 private var rangeSize = range.endInclusive.value - range.start.value + 1
private inline fun now() = System.currentTimeMillis() private fun now() = System.currentTimeMillis()
private inline fun ips(blocks: Long, time: Long) = 1000.0f * blocks / time private fun ips(blocks: Long, time: Long) = 1000.0f * blocks / time
val isComplete get() = completedBatches * batchSize >= rangeSize val isComplete get() = completedBatches * batchSize >= rangeSize
val isBatchComplete get() = batchEndTime > batchStartTime val isBatchComplete get() = batchEndTime > batchStartTime
@ -29,8 +29,6 @@ class BatchMetrics(val range: ClosedRange<BlockHeight>, val batchSize: Int, priv
fun endBatch() { fun endBatch() {
completedBatches++ completedBatches++
batchEndTime = now() batchEndTime = now()
onMetricComplete?.let { onMetricComplete?.invoke(this, isComplete)
it.invoke(this, isComplete)
}
} }
} }

View File

@ -24,7 +24,7 @@ enum class ConsensusBranchId(val displayName: String, val id: Long, val hexId: S
fun fromId(id: Long): ConsensusBranchId? = values().firstOrNull { it.id == id } fun fromId(id: Long): ConsensusBranchId? = values().firstOrNull { it.id == id }
fun fromHex(hex: String): ConsensusBranchId? = values().firstOrNull { branch -> fun fromHex(hex: String): ConsensusBranchId? = values().firstOrNull { branch ->
hex.toLowerCase(Locale.US).replace("_", "").replaceFirst("0x", "").let { sanitized -> hex.lowercase(Locale.US).replace("_", "").replaceFirst("0x", "").let { sanitized ->
branch.hexId.equals(sanitized, true) branch.hexId.equals(sanitized, true)
} }
} }

View File

@ -5,6 +5,7 @@ import androidx.room.Dao
import androidx.room.Database import androidx.room.Database
import androidx.room.Query import androidx.room.Query
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.RoomWarnings
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
@ -302,6 +303,7 @@ interface TransactionDao {
LIMIT :limit LIMIT :limit
""" """
) )
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
fun getSentTransactions(limit: Int = Int.MAX_VALUE): DataSource.Factory<Int, ConfirmedTransaction> fun getSentTransactions(limit: Int = Int.MAX_VALUE): DataSource.Factory<Int, ConfirmedTransaction>
/** /**
@ -328,6 +330,7 @@ interface TransactionDao {
LIMIT :limit LIMIT :limit
""" """
) )
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
fun getReceivedTransactions(limit: Int = Int.MAX_VALUE): DataSource.Factory<Int, ConfirmedTransaction> fun getReceivedTransactions(limit: Int = Int.MAX_VALUE): DataSource.Factory<Int, ConfirmedTransaction>
/** /**
@ -430,7 +433,6 @@ interface TransactionDao {
var success = false var success = false
try { try {
var hasInitialMatch = false var hasInitialMatch = false
var hasFinalMatch = true
twig("[cleanup] cleanupCancelledTx starting...") twig("[cleanup] cleanupCancelledTx starting...")
findUnminedTransactionIds(rawTransactionId).also { findUnminedTransactionIds(rawTransactionId).also {
twig("[cleanup] cleanupCancelledTx found ${it.size} matching transactions to cleanup") twig("[cleanup] cleanupCancelledTx found ${it.size} matching transactions to cleanup")
@ -438,7 +440,7 @@ interface TransactionDao {
hasInitialMatch = true hasInitialMatch = true
removeInvalidOutboundTransaction(transactionId) removeInvalidOutboundTransaction(transactionId)
} }
hasFinalMatch = findMatchingTransactionId(rawTransactionId) != null val hasFinalMatch = findMatchingTransactionId(rawTransactionId) != null
success = hasInitialMatch && !hasFinalMatch success = hasInitialMatch && !hasFinalMatch
twig("[cleanup] cleanupCancelledTx Done. success? $success") twig("[cleanup] cleanupCancelledTx Done. success? $success")
} catch (t: Throwable) { } catch (t: Throwable) {

View File

@ -28,11 +28,11 @@ internal inline fun <R> tryWarn(
} catch (t: Throwable) { } catch (t: Throwable) {
val shouldThrowAnyway = ( val shouldThrowAnyway = (
unlessContains != null && unlessContains != null &&
(t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true) (t.message?.lowercase()?.contains(unlessContains.lowercase()) == true)
) || ) ||
( (
ifContains != null && ifContains != null &&
(t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false) (t.message?.lowercase()?.contains(ifContains.lowercase()) == false)
) )
if (shouldThrowAnyway) { if (shouldThrowAnyway) {
throw t throw t

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.internal.ext.android
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.channels.ConflatedBroadcastChannel
@ -12,6 +13,9 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/* Adapted from ComputableLiveData */ /* Adapted from ComputableLiveData */
// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
@Suppress("DEPRECATION")
@OptIn(ObsoleteCoroutinesApi::class)
abstract class ComputableFlow<T>(dispatcher: CoroutineDispatcher = Dispatchers.IO) { abstract class ComputableFlow<T>(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
private val computationScope: CoroutineScope = CoroutineScope(dispatcher + SupervisorJob()) private val computationScope: CoroutineScope = CoroutineScope(dispatcher + SupervisorJob())
private val computationChannel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel() private val computationChannel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel()

View File

@ -49,8 +49,7 @@ fun <Key, Value> DataSource.Factory<Key, Value>.toFlowPagedList(
* *
* @see FlowPagedListBuilder * @see FlowPagedListBuilder
*/ */
@SuppressLint("RestrictedApi") fun <Key, Value> DataSource.Factory<Key, Value>.toFlowPagedList(
inline fun <Key, Value> DataSource.Factory<Key, Value>.toFlowPagedList(
pageSize: Int, pageSize: Int,
initialLoadKey: Key? = null, initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null, boundaryCallback: PagedList.BoundaryCallback<Value>? = null,

View File

@ -1,6 +1,5 @@
package cash.z.ecc.android.sdk.internal.ext.android package cash.z.ecc.android.sdk.internal.ext.android
import android.annotation.SuppressLint
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.paging.Config import androidx.paging.Config
@ -29,7 +28,7 @@ class FlowPagedListBuilder<Key, Value>(
* Creates a FlowPagedListBuilder with required parameters. * Creates a FlowPagedListBuilder with required parameters.
* *
* @param dataSourceFactory DataSource factory providing DataSource generations. * @param dataSourceFactory DataSource factory providing DataSource generations.
* @param config Paging configuration. * @param pageSize List page size.
*/ */
constructor(dataSourceFactory: DataSource.Factory<Key, Value>, pageSize: Int) : this( constructor(dataSourceFactory: DataSource.Factory<Key, Value>, pageSize: Int) : this(
dataSourceFactory, dataSourceFactory,
@ -44,7 +43,6 @@ class FlowPagedListBuilder<Key, Value>(
* *
* @return The Flow of PagedLists * @return The Flow of PagedLists
*/ */
@SuppressLint("RestrictedApi")
fun build(): Flow<List<Value>> { fun build(): Flow<List<Value>> {
return object : ComputableFlow<List<Value>>(fetchContext) { return object : ComputableFlow<List<Value>>(fetchContext) {
private lateinit var dataSource: DataSource<Key, Value> private lateinit var dataSource: DataSource<Key, Value>
@ -56,6 +54,7 @@ class FlowPagedListBuilder<Key, Value>(
var initializeKey = initialLoadKey var initializeKey = initialLoadKey
if (::list.isInitialized) { if (::list.isInitialized) {
twig("list is initialized") twig("list is initialized")
@Suppress("UNCHECKED_CAST")
initializeKey = list.lastKey as Key initializeKey = list.lastKey as Key
} }

View File

@ -73,14 +73,14 @@ class PersistentTransactionManager(
// //
override suspend fun initSpend( override suspend fun initSpend(
value: Zatoshi, zatoshi: Zatoshi,
toAddress: String, toAddress: String,
memo: String, memo: String,
fromAccountIndex: Int fromAccountIndex: Int
): PendingTransaction = withContext(Dispatchers.IO) { ): PendingTransaction = withContext(Dispatchers.IO) {
twig("constructing a placeholder transaction") twig("constructing a placeholder transaction")
var tx = PendingTransactionEntity( var tx = PendingTransactionEntity(
value = value.value, value = zatoshi.value,
toAddress = toAddress, toAddress = toAddress,
memo = memo.toByteArray(), memo = memo.toByteArray(),
accountIndex = fromAccountIndex accountIndex = fromAccountIndex
@ -264,12 +264,14 @@ class PersistentTransactionManager(
/** /**
* Remove a transaction and pretend it never existed. * Remove a transaction and pretend it never existed.
* *
* @param transaction the transaction to be processed.
*
* @return the final number of transactions that were removed from the database. * @return the final number of transactions that were removed from the database.
*/ */
override suspend fun abort(existingTransaction: PendingTransaction): Int { override suspend fun abort(transaction: PendingTransaction): Int {
return pendingTransactionDao { return pendingTransactionDao {
twig("[cleanup] Deleting pendingTxId: ${existingTransaction.id}") twig("[cleanup] Deleting pendingTxId: ${transaction.id}")
delete(existingTransaction as PendingTransactionEntity) delete(transaction as PendingTransactionEntity)
} }
} }
@ -286,13 +288,14 @@ class PersistentTransactionManager(
*/ */
private suspend fun <R> safeUpdate(logMessage: String = "", priority: Int = 0, block: suspend PendingTransactionDao.() -> R): R? { private suspend fun <R> safeUpdate(logMessage: String = "", priority: Int = 0, block: suspend PendingTransactionDao.() -> R): R? {
return try { return try {
twig(logMessage) twig(logMessage, priority)
pendingTransactionDao { block() } pendingTransactionDao { block() }
} catch (t: Throwable) { } catch (t: Throwable) {
val stacktrace = StringWriter().also { t.printStackTrace(PrintWriter(it)) }.toString() val stacktrace = StringWriter().also { t.printStackTrace(PrintWriter(it)) }.toString()
twig( twig(
"Unknown error while attempting to '$logMessage':" + "Unknown error while attempting to '$logMessage':" +
" ${t.message} caused by: ${t.cause} stacktrace: $stacktrace" " ${t.message} caused by: ${t.cause} stacktrace: $stacktrace",
priority
) )
null null
} }

View File

@ -109,9 +109,11 @@ interface OutboundTransactionManager {
/** /**
* Delete the given transaction but return 0 if it did not exist. * Delete the given transaction but return 0 if it did not exist.
* *
* @param transaction the transaction to be processed.
*
* @return the total number of transactions successfully removed from storage. * @return the total number of transactions successfully removed from storage.
*/ */
suspend fun abort(it: PendingTransaction): Int suspend fun abort(transaction: PendingTransaction): Int
/** /**
* Get all pending transactions known to this wallet as a flow that is updated anytime the list * Get all pending transactions known to this wallet as a flow that is updated anytime the list

View File

@ -12,7 +12,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.BufferedReader import java.io.BufferedReader
import java.io.IOException import java.io.IOException
import java.util.* import java.util.Locale
/** /**
* Tool for loading checkpoints for the wallet, based on the height at which the wallet was born. * Tool for loading checkpoints for the wallet, based on the height at which the wallet was born.
@ -65,11 +65,7 @@ internal object CheckpointTool {
*/ */
@VisibleForTesting @VisibleForTesting
internal fun checkpointDirectory(network: ZcashNetwork) = internal fun checkpointDirectory(network: ZcashNetwork) =
"co.electriccoin.zcash/checkpoint/${ "co.electriccoin.zcash/checkpoint/${network.networkName.lowercase(Locale.ROOT)}"
(network.networkName as java.lang.String).toLowerCase(
Locale.ROOT
)
}"
internal fun checkpointHeightFromFilename(zcashNetwork: ZcashNetwork, fileName: String) = internal fun checkpointHeightFromFilename(zcashNetwork: ZcashNetwork, fileName: String) =
BlockHeight.new(zcashNetwork, fileName.split('.').first().toLong()) BlockHeight.new(zcashNetwork, fileName.split('.').first().toLong())

View File

@ -83,20 +83,21 @@ class DerivationTool {
deriveTransparentAddressFromSeed(seed, account, index, networkId = network.id) deriveTransparentAddressFromSeed(seed, account, index, networkId = network.id)
} }
override suspend fun deriveTransparentAddressFromPublicKey(transparentPublicKey: String, network: ZcashNetwork): String = withRustBackendLoaded { override suspend fun deriveTransparentAddressFromPublicKey(publicKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
deriveTransparentAddressFromPubKey(transparentPublicKey, networkId = network.id) deriveTransparentAddressFromPubKey(pk = publicKey, networkId = network.id)
} }
override suspend fun deriveTransparentAddressFromPrivateKey(transparentPrivateKey: String, network: ZcashNetwork): String = withRustBackendLoaded { override suspend fun deriveTransparentAddressFromPrivateKey(privateKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
deriveTransparentAddressFromPrivKey(transparentPrivateKey, networkId = network.id) deriveTransparentAddressFromPrivKey(sk = privateKey, networkId = network.id)
} }
override suspend fun deriveTransparentSecretKey(seed: ByteArray, network: ZcashNetwork, account: Int, index: Int): String = withRustBackendLoaded { override suspend fun deriveTransparentSecretKey(seed: ByteArray, network: ZcashNetwork, account: Int, index: Int): String = withRustBackendLoaded {
deriveTransparentSecretKeyFromSeed(seed, account, index, networkId = network.id) deriveTransparentSecretKeyFromSeed(seed, account, index, networkId = network.id)
} }
@Suppress("UNUSED_PARAMETER")
fun validateUnifiedViewingKey(viewingKey: UnifiedViewingKey, networkId: Int = ZcashNetwork.Mainnet.id) { fun validateUnifiedViewingKey(viewingKey: UnifiedViewingKey, networkId: Int = ZcashNetwork.Mainnet.id) {
// TODO // TODO [#654] https://github.com/zcash/zcash-android-wallet-sdk/issues/654
} }
/** /**