[#1327] Ktlint 1.1.0

* [#1327] Ktlint 1.1.0

Closes #1327

* [#1327] Autoformatting with Ktlint

* Ktlint formatting warnings fix
This commit is contained in:
Honza Rychnovský 2024-01-04 21:21:32 +01:00 committed by GitHub
parent e550cd9943
commit 73d7afc2a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
240 changed files with 4723 additions and 3814 deletions

View File

@ -6,7 +6,6 @@ import org.junit.Test
import kotlin.test.assertContentEquals
class RustDerivationToolTest {
companion object {
private const val SEED_PHRASE =
"kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan" +
@ -14,7 +13,8 @@ class RustDerivationToolTest {
}
@Test
fun create_spending_key_does_not_mutate_passed_bytes() = runTest {
fun create_spending_key_does_not_mutate_passed_bytes() =
runTest {
val bytesOne = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()
val bytesTwo = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()

View File

@ -14,7 +14,6 @@ import cash.z.ecc.android.sdk.internal.model.JniWalletSummary
*/
@Suppress("TooManyFunctions")
interface Backend {
val networkId: Int
suspend fun initBlockMetaDb(): Int
@ -53,7 +52,11 @@ interface Backend {
* @throws RuntimeException as a common indicator of the operation failure
*/
@Throws(RuntimeException::class)
suspend fun createAccount(seed: ByteArray, treeState: ByteArray, recoverUntil: Long?): JniUnifiedSpendingKey
suspend fun createAccount(
seed: ByteArray,
treeState: ByteArray,
recoverUntil: Long?
): JniUnifiedSpendingKey
fun isValidShieldedAddr(addr: String): Boolean
@ -75,7 +78,10 @@ interface Backend {
* @throws RuntimeException as a common indicator of the operation failure
*/
@Throws(RuntimeException::class)
suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int): String?
suspend fun getMemoAsUtf8(
txId: ByteArray,
outputIndex: Int
): String?
suspend fun getNearestRewindHeight(height: Long): Long
@ -139,7 +145,10 @@ interface Backend {
* @throws RuntimeException as a common indicator of the operation failure
*/
@Throws(RuntimeException::class)
suspend fun scanBlocks(fromHeight: Long, limit: Long)
suspend fun scanBlocks(
fromHeight: Long,
limit: Long
)
/**
* @throws RuntimeException as a common indicator of the operation failure
@ -157,6 +166,7 @@ interface Backend {
suspend fun rewindBlockMetadataToHeight(height: Long)
suspend fun getVerifiedTransparentBalance(address: String): Long
suspend fun getTotalTransparentBalance(address: String): Long
/**

View File

@ -6,14 +6,14 @@ import java.util.concurrent.Executors
internal object SdkExecutors {
/**
* Executor used for database IO that's shared with the Rust native library.
*/
/*
*
* Based on internal discussion, keep the SDK internals confined to a single IO thread.
*
* We don't expect things to break, but we don't have the WAL enabled for SQLite so this
* is a simple solution.
*/
val DATABASE_IO = Executors.newSingleThreadExecutor {
val DATABASE_IO =
Executors.newSingleThreadExecutor {
Thread(it, "zc-io").apply { isDaemon = true }
}
}
@ -21,14 +21,14 @@ internal object SdkExecutors {
object SdkDispatchers {
/**
* Dispatcher used for database IO that's shared with the Rust native library.
*/
/*
*
* Based on internal discussion, keep the SDK internals confined to a single IO thread.
*
* We don't expect things to break, but we don't have the WAL enabled for SQLite so this
* is a simple solution.
*
* Don't use `Dispatchers.IO.limitedParallelism(1)`.
* While it executes serially, each dispatch can be on a different thread.
*/
// Don't use `Dispatchers.IO.limitedParallelism(1)`.
// While it executes serially, each dispatch can be on a different thread.
val DATABASE_IO = SdkExecutors.DATABASE_IO.asCoroutineDispatcher()
}

View File

@ -46,6 +46,7 @@ internal class NativeLibraryLoader(private val libraryName: String) {
}
}
private suspend fun loadLibrarySuspend(libraryName: String) = withContext(Dispatchers.IO) {
private suspend fun loadLibrarySuspend(libraryName: String) =
withContext(Dispatchers.IO) {
System.loadLibrary(libraryName)
}

View File

@ -25,7 +25,6 @@ class RustBackend private constructor(
private val saplingSpendFile: File,
private val saplingOutputFile: File,
) : Backend {
/**
* This function deletes the data database file and the cache directory (compact blocks files) if set by input
* parameters.
@ -35,7 +34,10 @@ class RustBackend private constructor(
*
* @return false in case of any required and failed deletion, true otherwise.
*/
suspend fun clear(clearCache: Boolean = true, clearDataDb: Boolean = true): Boolean {
suspend fun clear(
clearCache: Boolean = true,
clearDataDb: Boolean = true
): Boolean {
var cacheClearResult = true
var dataClearResult = true
if (clearCache) {
@ -55,13 +57,15 @@ class RustBackend private constructor(
// Wrapper Functions
//
override suspend fun initBlockMetaDb() = withContext(SdkDispatchers.DATABASE_IO) {
override suspend fun initBlockMetaDb() =
withContext(SdkDispatchers.DATABASE_IO) {
initBlockMetaDb(
fsBlockDbRoot.absolutePath,
)
}
override suspend fun initDataDb(seed: ByteArray?) = withContext(SdkDispatchers.DATABASE_IO) {
override suspend fun initDataDb(seed: ByteArray?) =
withContext(SdkDispatchers.DATABASE_IO) {
initDataDb(
dataDbFile.absolutePath,
seed,
@ -108,8 +112,10 @@ class RustBackend private constructor(
}
}
override suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int) =
withContext(SdkDispatchers.DATABASE_IO) {
override suspend fun getMemoAsUtf8(
txId: ByteArray,
outputIndex: Int
) = withContext(SdkDispatchers.DATABASE_IO) {
getMemoAsUtf8(
dataDbFile.absolutePath,
txId,
@ -217,7 +223,8 @@ class RustBackend private constructor(
override suspend fun getFullyScannedHeight() =
withContext(SdkDispatchers.DATABASE_IO) {
val height = getFullyScannedHeight(
val height =
getFullyScannedHeight(
dataDbFile.absolutePath,
networkId = networkId
)
@ -231,7 +238,8 @@ class RustBackend private constructor(
override suspend fun getMaxScannedHeight() =
withContext(SdkDispatchers.DATABASE_IO) {
val height = getMaxScannedHeight(
val height =
getMaxScannedHeight(
dataDbFile.absolutePath,
networkId = networkId
)
@ -260,7 +268,10 @@ class RustBackend private constructor(
}
}
override suspend fun scanBlocks(fromHeight: Long, limit: Long) {
override suspend fun scanBlocks(
fromHeight: Long,
limit: Long
) {
return withContext(SdkDispatchers.DATABASE_IO) {
scanBlocks(
fsBlockDbRoot.absolutePath,
@ -287,7 +298,8 @@ class RustBackend private constructor(
to: String,
value: Long,
memo: ByteArray?
): ByteArray = withContext(SdkDispatchers.DATABASE_IO) {
): ByteArray =
withContext(SdkDispatchers.DATABASE_IO) {
createToAddress(
dataDbFile.absolutePath,
unifiedSpendingKey,
@ -339,17 +351,13 @@ class RustBackend private constructor(
)
}
override fun isValidShieldedAddr(addr: String) =
isValidShieldedAddress(addr, networkId = networkId)
override fun isValidShieldedAddr(addr: String) = isValidShieldedAddress(addr, networkId = networkId)
override fun isValidTransparentAddr(addr: String) =
isValidTransparentAddress(addr, networkId = networkId)
override fun isValidTransparentAddr(addr: String) = isValidTransparentAddress(addr, networkId = networkId)
override fun isValidUnifiedAddr(addr: String) =
isValidUnifiedAddress(addr, networkId = networkId)
override fun isValidUnifiedAddr(addr: String) = isValidUnifiedAddress(addr, networkId = networkId)
override fun getBranchIdForHeight(height: Long): Long =
branchIdForHeight(height, networkId = networkId)
override fun getBranchIdForHeight(height: Long): Long = branchIdForHeight(height, networkId = networkId)
/**
* Exposes all of the librustzcash functions along with helpers for loading the static library.
@ -397,7 +405,11 @@ class RustBackend private constructor(
private external fun initBlockMetaDb(fsBlockDbRoot: String): Int
@JvmStatic
private external fun initDataDb(dbDataPath: String, seed: ByteArray?, networkId: Int): Int
private external fun initDataDb(
dbDataPath: String,
seed: ByteArray?,
networkId: Int
): Int
@JvmStatic
private external fun createAccount(
@ -422,7 +434,11 @@ class RustBackend private constructor(
private external fun getSaplingReceiverForUnifiedAddress(ua: String): String?
@JvmStatic
private external fun listTransparentReceivers(dbDataPath: String, account: Int, networkId: Int): Array<String>
private external fun listTransparentReceivers(
dbDataPath: String,
account: Int,
networkId: Int
): Array<String>
fun validateUnifiedSpendingKey(bytes: ByteArray) = isValidSpendingKey(bytes)
@ -430,13 +446,22 @@ class RustBackend private constructor(
private external fun isValidSpendingKey(bytes: ByteArray): Boolean
@JvmStatic
private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean
private external fun isValidShieldedAddress(
addr: String,
networkId: Int
): Boolean
@JvmStatic
private external fun isValidTransparentAddress(addr: String, networkId: Int): Boolean
private external fun isValidTransparentAddress(
addr: String,
networkId: Int
): Boolean
@JvmStatic
private external fun isValidUnifiedAddress(addr: String, networkId: Int): Boolean
private external fun isValidUnifiedAddress(
addr: String,
networkId: Int
): Boolean
@JvmStatic
private external fun getMemoAsUtf8(
@ -563,7 +588,10 @@ class RustBackend private constructor(
): ByteArray
@JvmStatic
private external fun branchIdForHeight(height: Long, networkId: Int): Long
private external fun branchIdForHeight(
height: Long,
networkId: Int
): Long
@JvmStatic
@Suppress("LongParameterList")

View File

@ -4,19 +4,16 @@ import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
class RustDerivationTool private constructor() : Derivation {
override fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
networkId: Int,
numberOfAccounts: Int
): Array<String> =
deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = networkId)
): Array<String> = deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = networkId)
override fun deriveUnifiedFullViewingKey(
usk: JniUnifiedSpendingKey,
networkId: Int
): String =
deriveUnifiedFullViewingKey(usk.bytes, networkId = networkId)
): String = deriveUnifiedFullViewingKey(usk.bytes, networkId = networkId)
override fun deriveUnifiedSpendingKey(
seed: ByteArray,
@ -24,8 +21,11 @@ class RustDerivationTool private constructor() : Derivation {
accountIndex: Int
): JniUnifiedSpendingKey = deriveSpendingKey(seed, accountIndex, networkId = networkId)
override fun deriveUnifiedAddress(seed: ByteArray, networkId: Int, accountIndex: Int): String =
deriveUnifiedAddressFromSeed(seed, accountIndex = accountIndex, networkId = networkId)
override fun deriveUnifiedAddress(
seed: ByteArray,
networkId: Int,
accountIndex: Int
): String = deriveUnifiedAddressFromSeed(seed, accountIndex = accountIndex, networkId = networkId)
/**
* Given a Unified Full Viewing Key string, return the associated Unified Address.
@ -38,8 +38,7 @@ class RustDerivationTool private constructor() : Derivation {
override fun deriveUnifiedAddress(
viewingKey: String,
networkId: Int
): String =
deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId)
): String = deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId)
companion object {
suspend fun new(): Derivation {
@ -63,7 +62,10 @@ class RustDerivationTool private constructor() : Derivation {
): Array<String>
@JvmStatic
private external fun deriveUnifiedFullViewingKey(usk: ByteArray, networkId: Int): String
private external fun deriveUnifiedFullViewingKey(
usk: ByteArray,
networkId: Int
): String
@JvmStatic
private external fun deriveUnifiedAddressFromSeed(
@ -73,6 +75,9 @@ class RustDerivationTool private constructor() : Derivation {
): String
@JvmStatic
private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String
private external fun deriveUnifiedAddressFromViewingKey(
key: String,
networkId: Int
): String
}
}

View File

@ -24,7 +24,10 @@ class JniSubtreeRoot(
}
companion object {
fun new(rootHash: ByteArray, completingBlockHeight: Long): JniSubtreeRoot {
fun new(
rootHash: ByteArray,
completingBlockHeight: Long
): JniSubtreeRoot {
return JniSubtreeRoot(
rootHash = rootHash,
completingBlockHeight = completingBlockHeight

View File

@ -24,7 +24,6 @@ class JniUnifiedSpendingKey(
*/
val bytes: ByteArray
) {
// Override to prevent leaking key to logs
override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)"

View File

@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.internal.fixture
import cash.z.ecc.android.sdk.internal.model.JniAccountBalance
object JniAccountBalanceFixture {
const val ACCOUNT_ID: Int = 0
const val SAPLING_TOTAL_BALANCE: Long = 0L
const val SAPLING_VERIFIED_BALANCE: Long = 0L
@ -12,7 +11,6 @@ object JniAccountBalanceFixture {
account: Int = ACCOUNT_ID,
saplingTotalBalance: Long = SAPLING_TOTAL_BALANCE,
saplingVerifiedBalance: Long = SAPLING_VERIFIED_BALANCE,
) = JniAccountBalance(
account = account,
saplingTotalBalance = saplingTotalBalance,

View File

@ -7,7 +7,8 @@ import kotlin.test.assertIs
class JniBlockMetaTest {
@Test
fun attributes_within_constraints() {
val instance = JniBlockMeta(
val instance =
JniBlockMeta(
height = UInt.MAX_VALUE.toLong(),
hash = byteArrayOf(),
time = 0L,

View File

@ -7,7 +7,8 @@ import kotlin.test.assertIs
class JniScanRangeTest {
@Test
fun attributes_within_constraints() {
val instance = JniScanRange(
val instance =
JniScanRange(
startHeight = UInt.MIN_VALUE.toLong(),
endHeight = UInt.MAX_VALUE.toLong(),
priority = 10

View File

@ -7,7 +7,8 @@ import kotlin.test.assertIs
class JniSubtreeRootTest {
@Test
fun attributes_within_constraints() {
val instance = JniSubtreeRoot(
val instance =
JniSubtreeRoot(
rootHash = byteArrayOf(),
completingBlockHeight = UInt.MAX_VALUE.toLong()
)

View File

@ -8,7 +8,8 @@ import kotlin.test.assertIs
class JniWalletSummaryTest {
@Test
fun both_attribute_within_constraints() {
val instance = JniWalletSummary(
val instance =
JniWalletSummary(
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
progressNumerator = 1L,
progressDenominator = 100L

View File

@ -5,7 +5,7 @@ plugins {
val ktlint by configurations.creating
dependencies {
ktlint("com.pinterest:ktlint:${project.property("KTLINT_VERSION")}") {
ktlint("com.pinterest.ktlint:ktlint-cli:${project.property("KTLINT_VERSION")}") {
attributes {
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named<Bundling>(Bundling.EXTERNAL))
}

View File

@ -7,15 +7,17 @@ import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
// TODO [#1224]: Refactor and re-enable disabled darkside tests
// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224
/**
* Integration test to run in order to catch any regressions in transparent behavior.
*/
// TODO [#1224]: Refactor and re-enable disabled darkside tests
// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224
@RunWith(AndroidJUnit4::class)
class TransparentIntegrationTest : DarksideTest() {
@Before
fun setup() = runOnce {
fun setup() =
runOnce {
// sithLord.await()
}

View File

@ -14,7 +14,6 @@ import org.junit.runner.RunWith
// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224
@RunWith(AndroidJUnit4::class)
class InboundTxTests : ScopedTest() {
@Test
@Ignore("Temporarily disabled")
fun testTargetBlock_synced() {
@ -35,12 +34,15 @@ class InboundTxTests : ScopedTest() {
@Ignore("Temporarily disabled")
fun testTxCountAfter() {
// add 2 transactions to block 663188 and 'mine' that block
addTransactions(targetTxBlock, tx663174, tx663188)
addTransactions(targetTxBlock, TX_663174, TX_663188)
// sithLord.await(timeout = 30_000L, targetHeight = targetTxBlock)
validator.validateTxCount(2)
}
private fun addTransactions(targetHeight: BlockHeight, vararg txs: String) {
private fun addTransactions(
targetHeight: BlockHeight,
vararg txs: String
) {
// val overwriteBlockCount = 5
chainMaker
// .stageEmptyBlocks(targetHeight, overwriteBlockCount)
@ -48,13 +50,14 @@ class InboundTxTests : ScopedTest() {
.applyTipHeight(targetHeight)
}
@Suppress("MaxLineLength")
@Suppress("MaxLineLength", "UnusedPrivateProperty", "ktlint:standard:max-line-length")
companion object {
private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
private const val tx663174 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt"
private const val tx663188 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt"
private const val txIndexReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/t1.txt"
private val txSend = arrayOf(
private const val BLOCKS_URL = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
private const val TX_663174 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt"
private const val TX_663188 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt"
private const val TX_INDEX_REORG = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/t1.txt"
private val txSend =
arrayOf(
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt",
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495.txt",
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt",
@ -66,7 +69,8 @@ class InboundTxTests : ScopedTest() {
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt"
)
private val txRecv = arrayOf(
private val txRecv =
arrayOf(
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt",
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt",
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt",
@ -81,7 +85,7 @@ class InboundTxTests : ScopedTest() {
private val firstBlock = BlockHeight.new(ZcashNetwork.Mainnet, 663150L)
private val targetTxBlock = BlockHeight.new(ZcashNetwork.Mainnet, 663188L)
private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
private const val LAST_BLOCK_HASH = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
private val sithLord = DarksideTestCoordinator()
private val validator = sithLord.validator
private val chainMaker = sithLord.chainMaker
@ -92,7 +96,7 @@ class InboundTxTests : ScopedTest() {
sithLord.enterTheDarkside()
chainMaker
.resetBlocks(blocksUrl, startHeight = firstBlock, tipHeight = targetTxBlock)
.resetBlocks(BLOCKS_URL, startHeight = firstBlock, tipHeight = targetTxBlock)
.stageEmptyBlocks(firstBlock + 1, 100)
.applyTipHeight(BlockHeight.new(ZcashNetwork.Mainnet, targetTxBlock.value - 1))

View File

@ -13,7 +13,6 @@ import org.junit.runner.RunWith
// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224
@RunWith(AndroidJUnit4::class)
class ReorgSetupTest : ScopedTest() {
/*
private val birthdayHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663150)
private val targetHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663250)
@ -26,20 +25,21 @@ class ReorgSetupTest : ScopedTest() {
@Test
@Ignore("Temporarily disabled")
fun testBeforeReorg_minHeight() = timeout(30_000L) {
fun testBeforeReorg_minHeight() =
timeout(30_000L) {
// validate that we are synced, at least to the birthday height
// validator.validateMinHeightSynced(birthdayHeight)
}
@Test
@Ignore("Temporarily disabled")
fun testBeforeReorg_maxHeight() = timeout(30_000L) {
fun testBeforeReorg_maxHeight() =
timeout(30_000L) {
// validate that we are not synced beyond the target height
// validator.validateMaxHeightSynced(targetHeight)
}
companion object {
private val sithLord = DarksideTestCoordinator()
private val validator = sithLord.validator

View File

@ -12,7 +12,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ReorgSmallTest : ScopedTest() {
/*
private val targetHeight = BlockHeight.new(
ZcashNetwork.Mainnet,
@ -29,13 +28,15 @@ class ReorgSmallTest : ScopedTest() {
@Test
@Ignore("Temporarily disabled")
fun testBeforeReorg_latestBlockHash() = timeout(30_000L) {
fun testBeforeReorg_latestBlockHash() =
timeout(30_000L) {
// validator.validateBlockHash(targetHeight, hashBeforeReorg)
}
@Test
@Ignore("Temporarily disabled")
fun testAfterReorg_callbackTriggered() = timeout(30_000L) {
fun testAfterReorg_callbackTriggered() =
timeout(30_000L) {
hadReorg = false
// sithLord.triggerSmallReorg()
// sithLord.await()
@ -44,12 +45,12 @@ class ReorgSmallTest : ScopedTest() {
@Test
@Ignore("Temporarily disabled")
fun testAfterReorg_latestBlockHash() = timeout(30_000L) {
fun testAfterReorg_latestBlockHash() =
timeout(30_000L) {
// validator.validateBlockHash(targetHeight, hashAfterReorg)
}
companion object {
private val sithLord = DarksideTestCoordinator()
private val validator = sithLord.validator
private var hadReorg = false

View File

@ -12,7 +12,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SetupTest : ScopedTest() {
// @Test
// fun testFirstBlockExists() {
// validator.validateHasBlock(
@ -38,7 +37,8 @@ class SetupTest : ScopedTest() {
@Test
@Ignore("This test is broken")
fun tempTest() {
val phrase = "still champion voice habit trend flight survey between bitter process artefact blind" +
val phrase =
"still champion voice habit trend flight survey between bitter process artefact blind" +
" carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
val result = SimpleMnemonics().toSeed(phrase.toCharArray()).toHex()
assertEquals("abc", result)
@ -54,12 +54,12 @@ class SetupTest : ScopedTest() {
assertEquals("a", "${ent.toHex()}|${String(phrase)}")
}
@Suppress("MaxLineLength", "UnusedPrivateProperty", "ktlint:standard:max-line-length")
companion object {
@Suppress("MaxLineLength")
private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
private const val firstBlock = 663150
private const val lastBlock = 663200
private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
private const val BLOCKS_URL = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
private const val FIRST_BLOCK = 663150
private const val LAST_BLOCK = 663200
private const val LAST_BLOCK_HASH = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
private val sithLord = DarksideTestCoordinator()
private val validator = sithLord.validator

View File

@ -11,6 +11,7 @@ open class DarksideTest : ScopedTest() {
ranOnce = true
}
}
companion object {
private var ranOnce = false
}

View File

@ -49,7 +49,8 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
/**
* Setup dependencies, including the synchronizer and the darkside API connection
*/
fun enterTheDarkside(): DarksideTestCoordinator = runBlocking {
fun enterTheDarkside(): DarksideTestCoordinator =
runBlocking {
// verify that we are on the darkside
try {
initiate()
@ -89,6 +90,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
// }
// redo this as a call to wallet but add delay time to wallet join() function
/**
* Waits for, at most, the given amount of time for the synchronizer to download and scan blocks
* and reach a 'SYNCED' status.
@ -125,7 +127,9 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
// wallet.send(toAddress, memo, zatoshi, fromAccountIndex)
// }
fun stall(delay: Long = 5000L) = runBlocking {
@Suppress("ktlint:standard:no-consecutive-comments")
fun stall(delay: Long = 5000L) =
runBlocking {
delay(delay)
}
@ -134,8 +138,8 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
//
inner class DarksideTestValidator {
fun validateLatestHeight(height: BlockHeight) = runBlocking<Unit> {
fun validateLatestHeight(height: BlockHeight) =
runBlocking<Unit> {
val info = synchronizer.processorInfo.first()
val networkBlockHeight = info.networkBlockHeight
assertTrue(
@ -182,7 +186,10 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
assertEquals("Expected $count transactions but found $txCount instead!", count, txCount)
}
fun validateMinBalance(available: Long = -1, total: Long = -1) {
fun validateMinBalance(
available: Long = -1,
total: Long = -1
) {
val balance = synchronizer.saplingBalances.value
if (available > 0) {
assertTrue(
@ -198,7 +205,11 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
}
}
suspend fun validateBalance(available: Long = -1, total: Long = -1, account: Account) {
suspend fun validateBalance(
available: Long = -1,
total: Long = -1,
account: Account
) {
val balance = synchronizer.processor.getBalanceInfo(account)
if (available > 0) {
assertEquals("invalid available balance", available, balance.available)
@ -224,30 +235,44 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
blocksUrl: String,
startHeight: BlockHeight = DEFAULT_START_HEIGHT,
tipHeight: BlockHeight = startHeight + 100
): DarksideChainMaker = apply {
): DarksideChainMaker =
apply {
darkside
.reset(BlockHeightUnsafe(startHeight.value))
.stageBlocks(blocksUrl)
applyTipHeight(tipHeight)
}
fun stageTransaction(url: String, targetHeight: BlockHeight): DarksideChainMaker = apply {
fun stageTransaction(
url: String,
targetHeight: BlockHeight
): DarksideChainMaker =
apply {
darkside.stageTransactions(url, BlockHeightUnsafe(targetHeight.value))
}
fun stageTransactions(targetHeight: BlockHeight, vararg urls: String): DarksideChainMaker = apply {
fun stageTransactions(
targetHeight: BlockHeight,
vararg urls: String
): DarksideChainMaker =
apply {
urls.forEach {
darkside.stageTransactions(it, BlockHeightUnsafe(targetHeight.value))
}
}
fun stageEmptyBlocks(startHeight: BlockHeight, count: Int = 10): DarksideChainMaker = apply {
fun stageEmptyBlocks(
startHeight: BlockHeight,
count: Int = 10
): DarksideChainMaker =
apply {
darkside.stageEmptyBlocks(BlockHeightUnsafe(startHeight.value), count)
}
fun stageEmptyBlock() = stageEmptyBlocks(lastTipHeight!! + 1, 1)
fun applyTipHeight(tipHeight: BlockHeight): DarksideChainMaker = apply {
fun applyTipHeight(tipHeight: BlockHeight): DarksideChainMaker =
apply {
darkside.applyBlocks(BlockHeightUnsafe(tipHeight.value))
lastTipHeight = tipHeight
}
@ -281,7 +306,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
}
}
@Suppress("MaxLineLength")
@Suppress("MaxLineLength", "UnusedPrivateProperty")
companion object {
/**
* This is a special localhost value on the Android emulator, which allows it to contact
@ -290,14 +315,15 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
const val COMPUTER_LOCALHOST = "10.0.2.2"
// Block URLS
private const val beforeReorg =
private const val BEFORE_REORG =
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
private const val smallReorg =
private const val SMALL_REORG =
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt"
private const val largeReorg =
private const val LARGE_REORG =
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-large-reorg.txt"
private val DEFAULT_START_HEIGHT = BlockHeight.new(ZcashNetwork.Mainnet, 663150)
private const val DEFAULT_SEED_PHRASE =
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly" +
" provide dizzy crush flush breeze blouse charge solid fish spread"
}
}

View File

@ -39,7 +39,8 @@ open class DarksideTestPrerequisites {
* ApplicationInfo object (`BuildInfo` is useless for libraries.)
*/
private fun isDebuggable(context: Context): Boolean {
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val packageInfo =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.getPackageInfo(
context.packageName,
PackageManager.PackageInfoFlags.of(0L)

View File

@ -26,8 +26,10 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisi
@Before
fun start() {
testScope = CoroutineScope(
Job(classScope.coroutineContext[Job]!!) + newFixedThreadPoolContext(
testScope =
CoroutineScope(
Job(classScope.coroutineContext[Job]!!) +
newFixedThreadPoolContext(
5,
this.javaClass.simpleName
)
@ -35,12 +37,16 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisi
}
@After
fun end() = runBlocking<Unit> {
fun end() =
runBlocking<Unit> {
testScope.cancel()
testScope.coroutineContext[Job]?.join()
}
fun timeout(duration: Long, block: suspend () -> Unit) = timeoutWith(testScope, duration, block)
fun timeout(
duration: Long,
block: suspend () -> Unit
) = timeoutWith(testScope, duration, block)
companion object {
@JvmStatic
@ -49,20 +55,26 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisi
@BeforeClass
@JvmStatic
fun createScope() {
classScope = CoroutineScope(
classScope =
CoroutineScope(
SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName)
)
}
@AfterClass
@JvmStatic
fun destroyScope() = runBlocking<Unit> {
fun destroyScope() =
runBlocking<Unit> {
classScope.cancel()
classScope.coroutineContext[Job]?.join()
}
@JvmStatic
fun timeoutWith(scope: CoroutineScope, duration: Long, block: suspend () -> Unit) {
fun timeoutWith(
scope: CoroutineScope,
duration: Long,
block: suspend () -> Unit
) {
scope.launch {
delay(duration)
val message = "ERROR: Test timed out after ${duration}ms"

View File

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

View File

@ -48,7 +48,8 @@ class TestWallet(
alias = alias
)
val walletScope = CoroutineScope(
val walletScope =
CoroutineScope(
SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName)
)
@ -60,7 +61,8 @@ class TestWallet(
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey =
runBlocking { DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, network = network, account) }
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
val synchronizer: SdkSynchronizer =
Synchronizer.newBlocking(
context,
network,
alias,
@ -85,7 +87,8 @@ class TestWallet(
}
suspend fun sync(timeout: Long = -1): TestWallet {
val killSwitch = walletScope.launch {
val killSwitch =
walletScope.launch {
if (timeout > 0) {
delay(timeout)
throw TimeoutException("Failed to sync wallet within ${timeout}ms")

View File

@ -20,6 +20,9 @@ import java.util.regex.Pattern
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
// TODO [#809]: Enable macrobenchmark on CI
// TODO [#809]: https://github.com/zcash/zcash-android-wallet-sdk/issues/809
/**
* Purpose of this class is to provide a basic startup measurements, and captured system traces for investigating the
* app's performance. It navigates to the device's home screen, and launches the default activity.
@ -29,11 +32,7 @@ import kotlin.time.Duration.Companion.seconds
* We ideally run this against a physical device with Android SDK level 29, at least, as profiling is provided by this
* version and later on.
*/
// TODO [#809]: Enable macrobenchmark on CI
// TODO [#809]: https://github.com/zcash/zcash-android-wallet-sdk/issues/809
class StartupBenchmark : UiTestPrerequisites() {
companion object {
private const val APP_TARGET_PACKAGE_NAME = "cash.z.ecc.android.sdk.demoapp.mainnet" // NON-NLS
private const val APP_TARGET_ACTIVITY_NAME = "cash.z.ecc.android.sdk.demoapp.MainActivity" // NON-NLS
@ -55,7 +54,8 @@ class StartupBenchmark : UiTestPrerequisites() {
* This test starts the Demo-app on Home screen and measures its metrics.
*/
@Test
fun appStartup() = benchmarkRule.measureRepeated(
fun appStartup() =
benchmarkRule.measureRepeated(
packageName = APP_TARGET_PACKAGE_NAME,
metrics = listOf(StartupTimingMetric()),
iterations = 5,
@ -74,9 +74,11 @@ class StartupBenchmark : UiTestPrerequisites() {
*/
@Test
@OptIn(ExperimentalMetricApi::class)
fun tracesSdkStartup() = benchmarkRule.measureRepeated(
fun tracesSdkStartup() =
benchmarkRule.measureRepeated(
packageName = APP_TARGET_PACKAGE_NAME,
metrics = listOf(
metrics =
listOf(
TraceSectionMetric(ADDRESS_SCREEN_SECTION, TraceSectionMetric.Mode.First, false),
TraceSectionMetric(UNIFIED_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false),
TraceSectionMetric(SAPLING_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false),
@ -109,7 +111,10 @@ class StartupBenchmark : UiTestPrerequisites() {
}
}
private fun MacrobenchmarkScope.waitForAddressAppear(addressPattern: Pattern, timeout: Duration): Boolean {
private fun MacrobenchmarkScope.waitForAddressAppear(
addressPattern: Pattern,
timeout: Duration
): Boolean {
return device.waitFor(Until.hasObject(By.text(addressPattern)), timeout)
}
@ -125,7 +130,8 @@ class StartupBenchmark : UiTestPrerequisites() {
}
private fun MacrobenchmarkScope.startLegacyActivityAndWait() {
val intent = Intent(Intent.ACTION_MAIN).apply {
val intent =
Intent(Intent.ACTION_MAIN).apply {
component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME)
}

View File

@ -18,6 +18,9 @@ import org.junit.Test
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
// TODO [#809]: Enable macrobenchmark on CI
// TODO [#809]: https://github.com/zcash/zcash-android-wallet-sdk/issues/809
/**
* This benchmark class provides measurements and captured custom traces for investigating SDK syncing mechanisms
* with restricted blockchain range. It always resets the SDK before the next sync iteration. It uses UIAutomator to
@ -29,11 +32,7 @@ import kotlin.time.Duration.Companion.seconds
* We ideally run this on a physical device with Android SDK level 29, at least, as profiling is provided by this
* version and later on.
*/
// TODO [#809]: Enable macrobenchmark on CI
// TODO [#809]: https://github.com/zcash/zcash-android-wallet-sdk/issues/809
class SyncBlockchainBenchmark : UiTestPrerequisites() {
companion object {
private const val APP_TARGET_PACKAGE_NAME = "cash.z.ecc.android.sdk.demoapp.mainnet" // NON-NLS
private const val APP_TARGET_ACTIVITY_NAME = "cash.z.ecc.android.sdk.demoapp.MainActivity" // NON-NLS
@ -54,9 +53,11 @@ class SyncBlockchainBenchmark : UiTestPrerequisites() {
*/
@Test
@OptIn(ExperimentalMetricApi::class)
fun tracesSyncBlockchain() = benchmarkRule.measureRepeated(
fun tracesSyncBlockchain() =
benchmarkRule.measureRepeated(
packageName = APP_TARGET_PACKAGE_NAME,
metrics = listOf(
metrics =
listOf(
TraceSectionMetric(BALANCE_SCREEN_SECTION, TraceSectionMetric.Mode.First, false),
TraceSectionMetric(BLOCKCHAIN_SYNC_SECTION, TraceSectionMetric.Mode.First, false),
TraceSectionMetric(DOWNLOAD_SECTION, TraceSectionMetric.Mode.First, false),
@ -103,7 +104,8 @@ class SyncBlockchainBenchmark : UiTestPrerequisites() {
}
private fun MacrobenchmarkScope.startLegacyActivityAndWait() {
val intent = Intent(Intent.ACTION_MAIN).apply {
val intent =
Intent(Intent.ACTION_MAIN).apply {
component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME)
}

View File

@ -6,7 +6,11 @@ import androidx.test.core.app.ApplicationProvider
fun getAppContext(): Context = ApplicationProvider.getApplicationContext()
fun getStringResource(@StringRes resId: Int) = getAppContext().getString(resId)
fun getStringResource(
@StringRes resId: Int
) = getAppContext().getString(resId)
fun getStringResourceWithArgs(@StringRes resId: Int, vararg formatArgs: String) =
getAppContext().getString(resId, *formatArgs)
fun getStringResourceWithArgs(
@StringRes resId: Int,
vararg formatArgs: String
) = getAppContext().getString(resId, *formatArgs)

View File

@ -6,10 +6,16 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import kotlin.time.Duration
fun UiDevice.waitFor(condition: SearchCondition<Boolean>, timeout: Duration): Boolean {
fun UiDevice.waitFor(
condition: SearchCondition<Boolean>,
timeout: Duration
): Boolean {
return wait(condition, timeout.inWholeMilliseconds)
}
fun UiObject2.clickAndWaitFor(condition: EventCondition<Boolean>, timeout: Duration): Boolean {
fun UiObject2.clickAndWaitFor(
condition: EventCondition<Boolean>,
timeout: Duration
): Boolean {
return clickAndWait(condition, timeout.inWholeMilliseconds)
}

View File

@ -25,7 +25,8 @@ open class UiTestPrerequisites {
}
private fun isScreenOn(): Boolean {
val powerService = ApplicationProvider.getApplicationContext<Context>()
val powerService =
ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.POWER_SERVICE) as PowerManager
return powerService.isInteractive
}

View File

@ -37,7 +37,6 @@ import org.junit.Test
* https://github.com/EdgeApp/eosjs-node-cli/blob/paul/cleanup/app.js
*/
class SampleCodeTest {
// ///////////////////////////////////////////////////
// Seed derivation
@Ignore("This test is not implemented")
@ -71,7 +70,8 @@ class SampleCodeTest {
// ///////////////////////////////////////////////////
// Get Address
@Test
fun getAddress() = runBlocking {
fun getAddress() =
runBlocking {
val address = synchronizer.getUnifiedAddress(Account.DEFAULT)
assertFalse(address.isBlank())
log("Address: $address")
@ -87,7 +87,8 @@ class SampleCodeTest {
// Query latest block height
@Test
@OptIn(ExperimentalCoroutinesApi::class)
fun getLatestBlockHeightTest() = runTest {
fun getLatestBlockHeightTest() =
runTest {
// Test the result, only if there is no server communication problem.
runCatching {
LightWalletClient.new(context, lightwalletdHost).getLatestBlockHeight()
@ -102,9 +103,11 @@ class SampleCodeTest {
// ///////////////////////////////////////////////////
// Download compact block range
@Test
@OptIn(ExperimentalCoroutinesApi::class)
fun getBlockRange() = runTest {
val blockRange = BlockHeightUnsafe(
fun getBlockRange() =
runTest {
@Suppress("ktlint:standard:multiline-expression-wrapping")
val blockRange =
BlockHeightUnsafe(
BlockHeight.new(
ZcashNetwork.Mainnet,
500_000
@ -175,11 +178,13 @@ class SampleCodeTest {
// ///////////////////////////////////////////////////
// Create a signed transaction (with memo) and broadcast
@Test
fun submitTransaction() = runBlocking {
fun submitTransaction() =
runBlocking {
val amount = 0.123.convertZecToZatoshi()
val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
val memo = "Test Transaction"
val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(
val spendingKey =
DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed,
ZcashNetwork.Mainnet,
Account.DEFAULT
@ -196,7 +201,8 @@ class SampleCodeTest {
private val lightwalletdHost = LightWalletEndpoint.Mainnet
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val synchronizer: Synchronizer = run {
private val synchronizer: Synchronizer =
run {
val network = ZcashNetwork.fromResources(context)
Synchronizer.newBlocking(
context,

View File

@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.flowOf
*/
class MockPreferenceProvider(mutableMapFactory: () -> MutableMap<String, String?> = { mutableMapOf() }) :
PreferenceProvider {
private val map = mutableMapFactory()
override suspend fun getString(key: Key) = map[key.key]
@ -20,7 +19,10 @@ class MockPreferenceProvider(mutableMapFactory: () -> MutableMap<String, String?
override suspend fun hasKey(key: Key) = map.containsKey(key.key)
override suspend fun putString(key: Key, value: String?) {
override suspend fun putString(
key: Key,
value: String?
) {
map[key.key] = value
}
}

View File

@ -5,6 +5,8 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
object BooleanPreferenceDefaultFixture {
val KEY = Key("some_boolean_key") // $NON-NLS
fun newTrue() = BooleanPreferenceDefault(KEY, true)
fun newFalse() = BooleanPreferenceDefault(KEY, false)
}

View File

@ -6,5 +6,9 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
object IntegerPreferenceDefaultFixture {
val KEY = Key("some_string_key") // $NON-NLS
const val DEFAULT_VALUE = 123
fun new(key: Key = KEY, value: Int = DEFAULT_VALUE) = IntegerPreferenceDefault(key, value)
fun new(
key: Key = KEY,
value: Int = DEFAULT_VALUE
) = IntegerPreferenceDefault(key, value)
}

View File

@ -6,5 +6,9 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.StringPreferenceDef
object StringDefaultPreferenceFixture {
val KEY = Key("some_string_key") // $NON-NLS
const val DEFAULT_VALUE = "some_default_value" // $NON-NLS
fun new(key: Key = KEY, value: String = DEFAULT_VALUE) = StringPreferenceDefault(key, value)
fun new(
key: Key = KEY,
value: String = DEFAULT_VALUE
) = StringPreferenceDefault(key, value)
}

View File

@ -16,30 +16,36 @@ class BooleanPreferenceDefaultTest {
}
@Test
fun value_default_true() = runTest {
fun value_default_true() =
runTest {
val entry = BooleanPreferenceDefaultFixture.newTrue()
assertTrue(entry.getValue(MockPreferenceProvider()))
}
@Test
fun value_default_false() = runTest {
fun value_default_false() =
runTest {
val entry = BooleanPreferenceDefaultFixture.newFalse()
assertFalse(entry.getValue(MockPreferenceProvider()))
}
@Test
fun value_from_config_false() = runTest {
fun value_from_config_false() =
runTest {
val entry = BooleanPreferenceDefaultFixture.newTrue()
val mockPreferenceProvider = MockPreferenceProvider {
val mockPreferenceProvider =
MockPreferenceProvider {
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to false.toString())
}
assertFalse(entry.getValue(mockPreferenceProvider))
}
@Test
fun value_from_config_true() = runTest {
fun value_from_config_true() =
runTest {
val entry = BooleanPreferenceDefaultFixture.newTrue()
val mockPreferenceProvider = MockPreferenceProvider {
val mockPreferenceProvider =
MockPreferenceProvider {
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to true.toString())
}
assertTrue(entry.getValue(mockPreferenceProvider))

View File

@ -15,17 +15,20 @@ class IntegerPreferenceDefaultTest {
}
@Test
fun value_default() = runTest {
fun value_default() =
runTest {
val entry = IntegerPreferenceDefaultFixture.new()
assertEquals(IntegerPreferenceDefaultFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
}
@Test
fun value_override() = runTest {
fun value_override() =
runTest {
val expected = IntegerPreferenceDefaultFixture.DEFAULT_VALUE + 5
val entry = IntegerPreferenceDefaultFixture.new()
val mockPreferenceProvider = MockPreferenceProvider {
val mockPreferenceProvider =
MockPreferenceProvider {
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to expected.toString())
}

View File

@ -14,16 +14,19 @@ class StringPreferenceDefaultTest {
}
@Test
fun value_default() = runTest {
fun value_default() =
runTest {
val entry = StringDefaultPreferenceFixture.new()
assertEquals(StringDefaultPreferenceFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
}
@Test
fun value_override() = runTest {
fun value_override() =
runTest {
val entry = StringDefaultPreferenceFixture.new()
val mockPreferenceProvider = MockPreferenceProvider {
val mockPreferenceProvider =
MockPreferenceProvider {
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to "override")
}

View File

@ -23,7 +23,8 @@ open class UiTestPrerequisites {
}
private fun isScreenOn(): Boolean {
val powerService = ApplicationProvider.getApplicationContext<Context>()
val powerService =
ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.POWER_SERVICE) as PowerManager
return powerService.isInteractive
}

View File

@ -17,13 +17,14 @@ import kotlin.time.TimeMark
import kotlin.time.TimeSource
class FlowExtTest {
@OptIn(ExperimentalTime::class, ExperimentalCoroutinesApi::class)
@Test
@SmallTest
fun throttle_one_sec() = runTest {
fun throttle_one_sec() =
runTest {
val timer = TimeSource.Monotonic.markNow()
val flow = flow {
val flow =
flow {
while (timer.elapsedNow() <= 5.seconds) {
emit(1)
}
@ -41,7 +42,8 @@ class FlowExtTest {
}
@OptIn(ExperimentalTime::class)
private fun raceConditionTest(duration: Duration): Boolean = runBlocking {
private fun raceConditionTest(duration: Duration): Boolean =
runBlocking {
val flow = (0..1000).asFlow().throttle(duration)
val values = mutableListOf<Int>()
@ -54,7 +56,8 @@ class FlowExtTest {
@FlakyTest
@Test
fun stressTest() = runBlocking {
fun stressTest() =
runBlocking {
for (i in 0..10) {
assertTrue { raceConditionTest(0.001.seconds) }
}

View File

@ -7,9 +7,13 @@ import androidx.annotation.StringRes
import androidx.test.core.app.ApplicationProvider
import java.util.Locale
fun getStringResource(@StringRes resId: Int) = ApplicationProvider.getApplicationContext<Context>().getString(resId)
fun getStringResource(
@StringRes resId: Int
) = ApplicationProvider.getApplicationContext<Context>().getString(resId)
fun getStringResourceWithArgs(@StringRes resId: Int, formatArgs: Array<Any>) =
ApplicationProvider.getApplicationContext<Context>().getString(resId, *formatArgs)
fun getStringResourceWithArgs(
@StringRes resId: Int,
formatArgs: Array<Any>
) = ApplicationProvider.getApplicationContext<Context>().getString(resId, *formatArgs)
fun isLocaleRTL(locale: Locale) = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL

View File

@ -27,7 +27,8 @@ class ScreenTimeoutTest : UiTestPrerequisites() {
@Test
@MediumTest
fun acquireAndReleaseScreenTimeout() = runTest {
fun acquireAndReleaseScreenTimeout() =
runTest {
val testSetup = TestSetup(composeTestRule)
assertEquals(1, testSetup.getScreenTimeoutCount())
@ -38,7 +39,6 @@ class ScreenTimeoutTest : UiTestPrerequisites() {
}
private class TestSetup(composeTestRule: ComposeContentTestRule) {
val mutableScreenTimeoutFlag = MutableStateFlow(true)
private val screenTimeout = ScreenTimeout()
@ -58,6 +58,7 @@ class ScreenTimeoutTest : UiTestPrerequisites() {
}
@Composable
@Suppress("ktlint:standard:function-naming")
private fun TestView(disableScreenTimeout: Boolean) {
if (disableScreenTimeout) {
DisableScreenTimeout()

View File

@ -4,7 +4,6 @@ import androidx.multidex.MultiDexApplication
import cash.z.ecc.android.sdk.internal.Twig
class App : MultiDexApplication() {
override fun onCreate() {
super.onCreate()

View File

@ -21,7 +21,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
/**
* Since the lightwalletClient 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
@ -83,7 +82,10 @@ abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
/**
* 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)
.setPrimaryClip(ClipData.newPlainText("DemoAppClip", text))
toast(description)

View File

@ -33,6 +33,7 @@ class ComposeActivity : ComponentActivity() {
}
@Composable
@Suppress("ktlint:standard:function-naming")
private fun MainContent() {
when (walletViewModel.secretState.collectAsStateWithLifecycle().value) {
SecretState.Loading -> {

View File

@ -63,7 +63,8 @@ class MainActivity :
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
appBarConfiguration =
AppBarConfiguration(
setOf(
R.id.nav_home, R.id.nav_address, R.id.nav_balance, R.id.nav_block, R.id.nav_private_key,
R.id.nav_latest_height, R.id.nav_block_range,
@ -127,7 +128,8 @@ class MainActivity :
lightwalletClient?.shutdown()
}
val network = ZcashNetwork.fromResources(applicationContext)
lightwalletClient = LightWalletClient.new(
lightwalletClient =
LightWalletClient.new(
applicationContext,
LightWalletEndpoint.defaultForNetwork(network)
)
@ -162,7 +164,10 @@ class MainActivity :
}
@Suppress("EmptyFunctionBlock")
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
override fun onDrawerSlide(
drawerView: View,
slideOffset: Float
) {
}
override fun onDrawerOpened(drawerView: View) {
@ -176,7 +181,8 @@ class MainActivity :
private fun newBrowserIntent(url: String): Intent {
val uri = Uri.parse(url)
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
val intent =
Intent(Intent.ACTION_VIEW, uri).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}

View File

@ -36,7 +36,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@Composable
@Suppress("LongMethod")
@Suppress("LongMethod", "ktlint:standard:function-naming")
internal fun ComposeActivity.Navigation() {
val navController = rememberNavController()
@ -187,7 +187,8 @@ private fun copyToClipboard(
) {
val clipboardManager = context.getSystemService(ClipboardManager::class.java)
val data = ClipData.newPlainText(
val data =
ClipData.newPlainText(
tag,
textToCopy
)
@ -207,7 +208,8 @@ private fun copyToClipboard(
private fun newBrowserIntent(url: String): Intent {
val uri = Uri.parse(url)
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
val intent =
Intent(Intent.ACTION_VIEW, uri).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}

View File

@ -40,10 +40,10 @@ import kotlin.time.Duration.Companion.seconds
* Shared mutable state for the demo
*/
class SharedViewModel(application: Application) : AndroidViewModel(application) {
private val _seedPhrase = MutableStateFlow(DemoConstants.INITIAL_SEED_WORDS)
private val _blockHeight = MutableStateFlow<BlockHeight?>(
private val _birthdayHeight =
MutableStateFlow<BlockHeight?>(
runBlocking {
BlockHeight.ofLatestCheckpoint(
getApplication(),
@ -56,14 +56,15 @@ class SharedViewModel(application: Application) : AndroidViewModel(application)
val seedPhrase: StateFlow<String> get() = _seedPhrase
// publicly, this is read-only
val birthdayHeight: StateFlow<BlockHeight?> get() = _blockHeight
val birthdayHeight: StateFlow<BlockHeight?> get() = _birthdayHeight
private val lockoutMutex = Mutex()
private val lockoutIdFlow = MutableStateFlow<UUID?>(null)
@OptIn(ExperimentalCoroutinesApi::class)
private val synchronizerOrLockout: Flow<InternalSynchronizerStatus> = lockoutIdFlow.flatMapLatest { lockoutId ->
private val synchronizerOrLockout: Flow<InternalSynchronizerStatus> =
lockoutIdFlow.flatMapLatest { lockoutId ->
if (null != lockoutId) {
flowOf(InternalSynchronizerStatus.Lockout(lockoutId))
} else {
@ -73,12 +74,14 @@ class SharedViewModel(application: Application) : AndroidViewModel(application)
val seedBytes = Mnemonics.MnemonicCode(seedPhrase.value).toSeed()
val network = ZcashNetwork.fromResources(application)
val synchronizer = Synchronizer.new(
val synchronizer =
Synchronizer.new(
application,
network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
seed = seedBytes,
birthday = if (BenchmarkingExt.isBenchmarking()) {
birthday =
if (BenchmarkingExt.isBenchmarking()) {
BlockHeight.new(ZcashNetwork.Mainnet, BenchmarkingBlockRangeFixture.new().start)
} else {
birthdayHeight.value
@ -97,7 +100,8 @@ class SharedViewModel(application: Application) : AndroidViewModel(application)
}
// Note that seed and birthday shouldn't be changed once a synchronizer is first collected
val synchronizerFlow: StateFlow<Synchronizer?> = synchronizerOrLockout.map {
val synchronizerFlow: StateFlow<Synchronizer?> =
synchronizerOrLockout.map {
when (it) {
is InternalSynchronizerStatus.Available -> it.synchronizer
is InternalSynchronizerStatus.Lockout -> null
@ -128,7 +132,8 @@ class SharedViewModel(application: Application) : AndroidViewModel(application)
.filterIsInstance<InternalSynchronizerStatus.Lockout>()
.filter { it.id == lockoutId }
.onFirst {
val didDelete = Synchronizer.erase(
val didDelete =
Synchronizer.erase(
appContext = getApplication(),
network = ZcashNetwork.fromResources(getApplication()),
alias = OLD_UI_SYNCHRONIZER_ALIAS
@ -162,6 +167,7 @@ class SharedViewModel(application: Application) : AndroidViewModel(application)
private sealed class InternalSynchronizerStatus {
class Available(val synchronizer: Synchronizer) : InternalSynchronizerStatus()
class Lockout(val id: UUID) : InternalSynchronizerStatus()
}

View File

@ -5,7 +5,6 @@ import android.os.StrictMode
import cash.z.ecc.android.sdk.demoapp.util.AndroidApiVersion
object StrictModeHelper {
fun enableStrictMode() {
configureStrictMode()
}

View File

@ -8,11 +8,13 @@ import cash.z.ecc.android.sdk.demoapp.util.LazyWithArgument
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
private val lazy = LazyWithArgument<Context, WalletCoordinator> {
private val lazy =
LazyWithArgument<Context, WalletCoordinator> {
/*
* A flow of the user's stored wallet. Null indicates that no wallet has been stored.
*/
val persistableWalletFlow = flow {
val persistableWalletFlow =
flow {
// EncryptedPreferenceSingleton.getInstance() is a suspending function, which is why we need
// the flow builder to provide a coroutine context.
val encryptedPreferenceProvider = EncryptedPreferenceSingleton.getInstance(it)

View File

@ -23,7 +23,6 @@ import kotlinx.coroutines.launch
* that is used, update the `DemoConfig.seedWords` value.
*/
class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
private lateinit var viewingKey: UnifiedFullViewingKey
private fun displayAddress() {
@ -67,7 +66,10 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
reportTraceEvent(ProvideAddressBenchmarkTrace.Event.ADDRESS_SCREEN_START)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
displayAddress()

View File

@ -34,7 +34,6 @@ import kotlinx.coroutines.launch
*/
@Suppress("TooManyFunctions")
class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBalanceBinding =
FragmentGetBalanceBinding.inflate(layoutInflater)
@ -48,7 +47,10 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
reportTraceEvent(SyncBlockchainBenchmarkTrace.Event.BALANCE_SCREEN_END)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
val seedPhrase = sharedViewModel.seedPhrase.value
@ -110,25 +112,19 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
}
}
private fun onOrchardBalance(
orchardBalance: WalletBalance?
) {
private fun onOrchardBalance(orchardBalance: WalletBalance?) {
binding.orchardBalance.apply {
text = orchardBalance.humanString()
}
}
private fun onSaplingBalance(
saplingBalance: WalletBalance?
) {
private fun onSaplingBalance(saplingBalance: WalletBalance?) {
binding.saplingBalance.apply {
text = saplingBalance.humanString()
}
}
private fun onTransparentBalance(
transparentBalance: WalletBalance?
) {
private fun onTransparentBalance(transparentBalance: WalletBalance?) {
binding.transparentBalance.apply {
text = transparentBalance.humanString()
}
@ -136,7 +132,8 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
binding.shield.apply {
// TODO [#776]: Support variable fees
// TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776
visibility = if ((transparentBalance?.available ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) {
visibility =
if ((transparentBalance?.available ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) {
View.VISIBLE
} else {
View.GONE
@ -147,7 +144,8 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
private fun onStatus(status: Synchronizer.Status) {
Twig.debug { "Synchronizer status: $status" }
// report benchmark event
val traceEvents = when (status) {
val traceEvents =
when (status) {
Synchronizer.Status.SYNCING -> {
SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_START
}
@ -175,7 +173,8 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
}
@Suppress("MagicNumber")
private fun WalletBalance?.humanString() = if (null == this) {
private fun WalletBalance?.humanString() =
if (null == this) {
"Calculating balance"
} else {
"""

View File

@ -7,15 +7,15 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockBinding
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
// TODO [#973]: Eliminate old UI demo-app
// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973
/**
* Retrieves a compact block from the lightwalletd server and displays basic information about it.
* This demonstrates the basic ability to connect to the server, request a compact block and parse
* the response.
*/
class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
// TODO [#973]: Eliminate old UI demo-app
// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973
/*
var coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
@ -78,7 +78,10 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
// Android Lifecycle overrides
//
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
binding.buttonApply.setOnClickListener(::onApply)
binding.buttonPrevious.setOnClickListener {
@ -92,7 +95,6 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
//
// Base Fragment overrides
//
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding =
FragmentGetBlockBinding.inflate(layoutInflater)
@Suppress("MaxLineLength")
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding = FragmentGetBlockBinding.inflate(layoutInflater)
}

View File

@ -19,7 +19,6 @@ import kotlin.math.max
* block ranges for instance, to find the block with the most shielded transactions in a range.
*/
class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
// TODO [#973]: Eliminate old UI demo-app
// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973
@Suppress("MaxLineLength", "MagicNumber", "UNUSED_PARAMETER")
@ -75,12 +74,14 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
private fun onApply(unused: View) {
val network = ZcashNetwork.fromResources(requireApplicationContext())
val start = max(
val start =
max(
binding.textStartHeight.text.toString().toLongOrNull()
?: network.saplingActivationHeight.value,
network.saplingActivationHeight.value
)
val end = max(
val end =
max(
binding.textEndHeight.text.toString().toLongOrNull()
?: network.saplingActivationHeight.value,
network.saplingActivationHeight.value
@ -122,7 +123,10 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
// Android Lifecycle overrides
//
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
binding.buttonApply.setOnClickListener(::onApply)
}

View File

@ -11,7 +11,6 @@ import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetLatestHeightBinding
* instance.
*/
class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>() {
// TODO [#973]: Eliminate old UI demo-app
// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973
private fun displayLatestHeight() {

View File

@ -24,7 +24,6 @@ import kotlinx.coroutines.launch
* HomeFragment.
*/
class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
private lateinit var seedPhrase: String
private lateinit var seed: ByteArray
@ -47,14 +46,16 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@Suppress("MagicNumber")
val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(
val spendingKey =
DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed,
ZcashNetwork.fromResources(requireApplicationContext()),
Account(5)
)
// derive the key that allows you to view but not spend transactions
val viewingKey = DerivationTool.getInstance().deriveUnifiedFullViewingKey(
val viewingKey =
DerivationTool.getInstance().deriveUnifiedFullViewingKey(
spendingKey,
ZcashNetwork.fromResources(requireApplicationContext())
)
@ -79,7 +80,10 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
displayKeys()
}

View File

@ -18,11 +18,12 @@ import kotlinx.coroutines.launch
*/
@Suppress("TooManyFunctions")
class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
override fun inflateBinding(layoutInflater: LayoutInflater) = FragmentHomeBinding.inflate(layoutInflater)
override fun inflateBinding(layoutInflater: LayoutInflater) =
FragmentHomeBinding.inflate(layoutInflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
binding.textSeedPhrase.setOnClickListener(::onEditSeedPhrase)
binding.buttonPaste.setOnClickListener(::onPasteSeedPhrase)

View File

@ -111,7 +111,10 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
super.onCreate(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
initTransactionUI()
monitorChanges()

View File

@ -23,7 +23,6 @@ class TransactionAdapter : ListAdapter<TransactionOverview, TransactionViewHolde
) = oldItem == newItem
}
) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int

View File

@ -33,7 +33,12 @@ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
@Suppress("MagicNumber")
fun bindTo(isInbound: Boolean, minedHeight: BlockHeight?, time: Long?, value: Zatoshi) {
fun bindTo(
isInbound: Boolean,
minedHeight: BlockHeight?,
time: Long?,
value: Zatoshi
) {
amountText.text = value.convertZatoshiToZecString()
timeText.text = minedHeight?.let {
time?.let {

View File

@ -49,8 +49,8 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
private val isSynced get() = status == Synchronizer.Status.SYNCED
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentListUtxosBinding =
FragmentListUtxosBinding.inflate(layoutInflater)
@Suppress("MaxLineLength")
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentListUtxosBinding = FragmentListUtxosBinding.inflate(layoutInflater)
private fun initUi() {
binding.inputAddress.setText(address)
@ -131,7 +131,10 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
// private val now get() = System.currentTimeMillis()
private fun updateStatus(message: String, append: Boolean = true) {
private fun updateStatus(
message: String,
append: Boolean = true
) {
if (append) {
binding.textStatus.text = "${binding.textStatus.text} $message"
} else {
@ -140,7 +143,10 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
Twig.debug { message }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
initUi()
monitorStatus()

View File

@ -23,7 +23,6 @@ class UtxoAdapter : ListAdapter<TransactionOverview, UtxoViewHolder>(
) = oldItem == newItem
}
) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int

View File

@ -25,7 +25,11 @@ class UtxoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
@Suppress("MagicNumber")
private fun bindToHelper(amount: Zatoshi, minedHeight: BlockHeight?, time: Long?) {
private fun bindToHelper(
amount: Zatoshi,
minedHeight: BlockHeight?,
time: Long?
) {
amountText.text = amount.convertZatoshiToZecString()
timeText.text = minedHeight?.let {
time?.let {

View File

@ -34,7 +34,6 @@ import kotlinx.coroutines.launch
*/
@Suppress("TooManyFunctions")
class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
private lateinit var amountInput: TextView
private lateinit var addressInput: TextView
@ -67,10 +66,12 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
//
private fun initSendUi() {
amountInput = binding.inputAmount.apply {
amountInput =
binding.inputAmount.apply {
setText(DemoConstants.SEND_AMOUNT.toZecString())
}
addressInput = binding.inputAddress.apply {
addressInput =
binding.inputAddress.apply {
setText(DemoConstants.TO_ADDRESS)
}
binding.buttonSend.setOnClickListener(::onSend)
@ -130,7 +131,8 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
private fun onBalance(balance: WalletBalance?) {
this.balance = balance
if (!isSyncing) {
binding.textBalance.text = """
binding.textBalance.text =
"""
Available balance: ${balance?.available.convertZatoshiToZecString(12)}
Total balance: ${balance?.total.convertZatoshiToZecString(12)}
""".trimIndent()
@ -191,7 +193,10 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
initSendUi()
monitorChanges()
@ -200,7 +205,6 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
//
// BaseDemoFragment overrides
//
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding =
FragmentSendBinding.inflate(layoutInflater)
@Suppress("MaxLineLength")
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding = FragmentSendBinding.inflate(layoutInflater)
}

View File

@ -10,7 +10,6 @@ import cash.z.ecc.android.sdk.model.Zatoshi
@Suppress("MagicNumber")
object WalletSnapshotFixture {
val STATUS = Synchronizer.Status.SYNCED
val PROGRESS = PercentDecimal.ZERO_PERCENT
val TRANSPARENT_BALANCE: WalletBalance = WalletBalance(Zatoshi(8), Zatoshi(1))
@ -21,7 +20,8 @@ object WalletSnapshotFixture {
@Suppress("LongParameterList")
fun new(
status: Synchronizer.Status = STATUS,
processorInfo: CompactBlockProcessor.ProcessorInfo = CompactBlockProcessor.ProcessorInfo(
processorInfo: CompactBlockProcessor.ProcessorInfo =
CompactBlockProcessor.ProcessorInfo(
null,
null,
null

View File

@ -25,8 +25,7 @@ import java.util.concurrent.Executors
* For a given preference file, it is expected that only a single instance is constructed and that
* this instance lives for the lifetime of the application. Constructing multiple instances will
* potentially corrupt preference data and will leak resources.
*/
/*
*
* Implementation note: EncryptedSharedPreferences are not thread-safe, so this implementation
* confines them to a single background thread.
*/
@ -34,13 +33,16 @@ class AndroidPreferenceProvider(
private val sharedPreferences: SharedPreferences,
private val dispatcher: CoroutineDispatcher
) : PreferenceProvider {
override suspend fun hasKey(key: Key) = withContext(dispatcher) {
override suspend fun hasKey(key: Key) =
withContext(dispatcher) {
sharedPreferences.contains(key.key)
}
@SuppressLint("ApplySharedPref")
override suspend fun putString(key: Key, value: String?) = withContext(dispatcher) {
override suspend fun putString(
key: Key,
value: String?
) = withContext(dispatcher) {
val editor = sharedPreferences.edit()
editor.putString(key.key, value)
@ -50,13 +52,16 @@ class AndroidPreferenceProvider(
Unit
}
override suspend fun getString(key: Key) = withContext(dispatcher) {
override suspend fun getString(key: Key) =
withContext(dispatcher) {
sharedPreferences.getString(key.key, null)
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun observe(key: Key): Flow<Unit> = callbackFlow<Unit> {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
override fun observe(key: Key): Flow<Unit> =
callbackFlow<Unit> {
val listener =
SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
// Callback on main thread
trySend(Unit)
}
@ -71,35 +76,44 @@ class AndroidPreferenceProvider(
}.flowOn(dispatcher)
companion object {
suspend fun newStandard(context: Context, filename: String): PreferenceProvider {
suspend fun newStandard(
context: Context,
filename: String
): PreferenceProvider {
/*
* Because of this line, we don't want multiple instances of this object created
* because we don't clean up the thread afterwards.
*/
val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
val sharedPreferences = withContext(singleThreadedDispatcher) {
val sharedPreferences =
withContext(singleThreadedDispatcher) {
context.getSharedPreferences(filename, Context.MODE_PRIVATE)
}
return AndroidPreferenceProvider(sharedPreferences, singleThreadedDispatcher)
}
suspend fun newEncrypted(context: Context, filename: String): PreferenceProvider {
suspend fun newEncrypted(
context: Context,
filename: String
): PreferenceProvider {
/*
* Because of this line, we don't want multiple instances of this object created
* because we don't clean up the thread afterwards.
*/
val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
val mainKey = withContext(singleThreadedDispatcher) {
val mainKey =
withContext(singleThreadedDispatcher) {
@Suppress("BlockingMethodInNonBlockingContext")
MasterKey.Builder(context).apply {
setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
}.build()
}
val sharedPreferences = withContext(singleThreadedDispatcher) {
val sharedPreferences =
withContext(singleThreadedDispatcher) {
@Suppress("BlockingMethodInNonBlockingContext")
EncryptedSharedPreferences.create(
context,

View File

@ -3,6 +3,5 @@ package cash.z.ecc.android.sdk.demoapp.preference
import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
object EncryptedPreferenceKeys {
val PERSISTABLE_WALLET = PersistableWalletPreferenceDefault(Key("persistable_wallet"))
}

View File

@ -5,10 +5,10 @@ import cash.z.ecc.android.sdk.demoapp.preference.api.PreferenceProvider
import cash.z.ecc.android.sdk.demoapp.util.SuspendingLazy
object EncryptedPreferenceSingleton {
private const val PREF_FILENAME = "co.electriccoin.zcash.encrypted"
private val lazy = SuspendingLazy<Context, PreferenceProvider> {
private val lazy =
SuspendingLazy<Context, PreferenceProvider> {
AndroidPreferenceProvider.newEncrypted(it, PREF_FILENAME)
}

View File

@ -9,7 +9,6 @@ import org.json.JSONObject
data class PersistableWalletPreferenceDefault(
override val key: Key
) : PreferenceDefault<PersistableWallet?> {
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
preferenceProvider.getString(key)?.let { PersistableWallet.from(JSONObject(it)) }

View File

@ -4,10 +4,12 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
import kotlinx.coroutines.flow.Flow
interface PreferenceProvider {
suspend fun hasKey(key: Key): Boolean
suspend fun putString(key: Key, value: String?)
suspend fun putString(
key: Key,
value: String?
)
suspend fun getString(key: Key): String?

View File

@ -6,9 +6,9 @@ data class BooleanPreferenceDefault(
override val key: Key,
private val defaultValue: Boolean
) : PreferenceDefault<Boolean> {
@Suppress("SwallowedException")
override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key)?.let {
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
preferenceProvider.getString(key)?.let {
try {
it.toBooleanStrict()
} catch (e: IllegalArgumentException) {
@ -18,7 +18,10 @@ data class BooleanPreferenceDefault(
}
} ?: defaultValue
override suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: Boolean) {
override suspend fun putValue(
preferenceProvider: PreferenceProvider,
newValue: Boolean
) {
preferenceProvider.putString(key, newValue.toString())
}
}

View File

@ -6,8 +6,8 @@ data class IntegerPreferenceDefault(
override val key: Key,
private val defaultValue: Int
) : PreferenceDefault<Int> {
override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key)?.let {
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
preferenceProvider.getString(key)?.let {
try {
it.toInt()
} catch (e: NumberFormatException) {
@ -17,7 +17,10 @@ data class IntegerPreferenceDefault(
}
} ?: defaultValue
override suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: Int) {
override suspend fun putValue(
preferenceProvider: PreferenceProvider,
newValue: Int
) {
preferenceProvider.putString(key, newValue.toString())
}
}

View File

@ -10,8 +10,7 @@ import kotlinx.coroutines.flow.map
* multiple parts of the code can fetch the same preference without duplication or accidental
* variation in default value. Clients define the key and default value together, rather than just
* the key.
*/
/*
*
* API note: the default value is not available through the public interface in order to prevent
* clients from accidentally using the default value instead of the preference value.
*
@ -21,7 +20,6 @@ import kotlinx.coroutines.flow.map
* and perhaps many Integer values will also fit within the autoboxing cache.
*/
interface PreferenceDefault<T> {
val key: Key
/**
@ -34,14 +32,18 @@ interface PreferenceDefault<T> {
* @param preferenceProvider Provides actual preference values.
* @param newValue New value to write.
*/
suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: T)
suspend fun putValue(
preferenceProvider: PreferenceProvider,
newValue: T
)
/**
* @param preferenceProvider Provides actual preference values.
* @return Flow that emits preference changes. Note that implementations should emit an initial value
* indicating what was stored in the preferences, in addition to subsequent updates.
*/
fun observe(preferenceProvider: PreferenceProvider): Flow<T> = preferenceProvider.observe(key)
fun observe(preferenceProvider: PreferenceProvider): Flow<T> =
preferenceProvider.observe(key)
.map { getValue(preferenceProvider) }
.distinctUntilChanged()
}

View File

@ -6,11 +6,14 @@ data class StringPreferenceDefault(
override val key: Key,
private val defaultValue: String
) : PreferenceDefault<String> {
override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key)
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
preferenceProvider.getString(key)
?: defaultValue
override suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: String) {
override suspend fun putValue(
preferenceProvider: PreferenceProvider,
newValue: String
) {
preferenceProvider.putString(key, newValue)
}
}

View File

@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.ext.collectWith
import kotlinx.coroutines.flow.map
@Composable
@Suppress("ktlint:standard:function-naming")
fun ComponentActivity.BindCompLocalProvider(content: @Composable () -> Unit) {
val screenTimeout = ScreenTimeout()
observeScreenTimeoutFlag(screenTimeout)

View File

@ -18,7 +18,8 @@ import kotlin.time.TimeSource
fun <T> Flow<T>.throttle(
duration: Duration,
timeSource: TimeSource = TimeSource.Monotonic
): Flow<T> = flow {
): Flow<T> =
flow {
coroutineScope {
val context = coroutineContext
val mutex = Mutex()
@ -43,7 +44,8 @@ fun <T> Flow<T>.throttle(
timeMark = timeSource.markNow()
}
} else {
delayEmit = async(Dispatchers.Default) {
delayEmit =
async(Dispatchers.Default) {
mutex.withLock {
delay(duration)
withContext(context) {

View File

@ -29,6 +29,7 @@ class ScreenTimeout {
val LocalScreenTimeout = compositionLocalOf { ScreenTimeout() }
@Composable
@Suppress("ktlint:standard:function-naming")
fun DisableScreenTimeout() {
val screenTimeout = LocalScreenTimeout.current
DisposableEffect(screenTimeout) {

View File

@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.flow
@Preview(name = "Addresses")
@Composable
@Suppress("ktlint:standard:function-naming")
private fun ComposablePreview() {
MaterialTheme {
// TODO [#1090]: Demo: Add Addresses and Transactions Compose Previews
@ -43,6 +44,7 @@ private fun ComposablePreview() {
* @param copyToClipboard First string is a tag, the second string is the text to copy.
*/
@Composable
@Suppress("ktlint:standard:function-naming")
fun Addresses(
synchronizer: Synchronizer,
copyToClipboard: (String, String) -> Unit,
@ -55,7 +57,8 @@ fun Addresses(
) { paddingValues ->
// TODO [#846]: Slow addresses providing
// TODO [#846]: https://github.com/zcash/zcash-android-wallet-sdk/issues/846
val walletAddresses = flow {
val walletAddresses =
flow {
emit(WalletAddresses.new(synchronizer))
}.collectAsState(
initial = null
@ -72,6 +75,7 @@ fun Addresses(
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("ktlint:standard:function-naming")
private fun AddressesTopAppBar(onBack: () -> Unit) {
TopAppBar(
title = { Text(text = stringResource(id = R.string.menu_address)) },
@ -89,6 +93,7 @@ private fun AddressesTopAppBar(onBack: () -> Unit) {
}
@Composable
@Suppress("ktlint:standard:function-naming")
private fun AddressesMainContent(
paddingValues: PaddingValues,
addresses: WalletAddresses,

View File

@ -31,6 +31,7 @@ import cash.z.ecc.android.sdk.model.toZecString
@Preview(name = "Balance")
@Composable
@Suppress("ktlint:standard:function-naming")
private fun ComposablePreview() {
MaterialTheme {
Balance(
@ -44,6 +45,7 @@ private fun ComposablePreview() {
}
@Composable
@Suppress("ktlint:standard:function-naming")
fun Balance(
walletSnapshot: WalletSnapshot,
sendState: SendState,
@ -69,6 +71,7 @@ fun Balance(
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("ktlint:standard:function-naming")
private fun BalanceTopAppBar(
onBack: () -> Unit,
onRefresh: () -> Unit
@ -97,6 +100,7 @@ private fun BalanceTopAppBar(
}
@Composable
@Suppress("ktlint:standard:function-naming")
private fun BalanceMainContent(
paddingValues: PaddingValues,
walletSnapshot: WalletSnapshot,

View File

@ -33,6 +33,7 @@ import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot
@Preview(name = "Home")
@Composable
@Suppress("ktlint:standard:function-naming")
private fun ComposablePreviewHome() {
MaterialTheme {
Home(
@ -49,8 +50,8 @@ private fun ComposablePreviewHome() {
}
}
@Suppress("LongParameterList")
@Composable
@Suppress("LongParameterList", "ktlint:standard:function-naming")
fun Home(
walletSnapshot: WalletSnapshot,
isTestnet: Boolean,
@ -83,6 +84,7 @@ fun Home(
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("ktlint:standard:function-naming")
private fun HomeTopAppBar(
isTestnet: Boolean,
goTestnetFaucet: () -> Unit,
@ -103,6 +105,7 @@ private fun HomeTopAppBar(
}
@Composable
@Suppress("ktlint:standard:function-naming")
private fun DebugMenu(
isTestnet: Boolean,
goTestnetFaucet: () -> Unit,
@ -146,7 +149,7 @@ private fun DebugMenu(
}
@Composable
@Suppress("LongParameterList")
@Suppress("LongParameterList", "ktlint:standard:function-naming")
private fun HomeMainContent(
paddingValues: PaddingValues,
walletSnapshot: WalletSnapshot,

View File

@ -19,7 +19,8 @@ data class WalletSnapshot(
// TODO [#776]: Support variable fees
// TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776
// Note: the wallet is effectively empty if it cannot cover the miner's fee
val hasFunds = saplingBalance.available.value >
val hasFunds =
saplingBalance.available.value >
(ZcashSdk.MINERS_FEE.value.toDouble() / Zatoshi.ZATOSHI_PER_ZEC) // 0.0001
val hasSaplingBalance = saplingBalance.total.value > 0

View File

@ -66,13 +66,15 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
/**
* Synchronizer that is retained long enough to survive configuration changes.
*/
val synchronizer = walletCoordinator.synchronizer.stateIn(
val synchronizer =
walletCoordinator.synchronizer.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
null
)
val secretState: StateFlow<SecretState> = walletCoordinator.persistableWallet
val secretState: StateFlow<SecretState> =
walletCoordinator.persistableWallet
.map { persistableWallet ->
if (null == persistableWallet) {
SecretState.None
@ -85,11 +87,13 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
SecretState.Loading
)
val spendingKey = secretState
val spendingKey =
secretState
.filterIsInstance<SecretState.Ready>()
.map { it.persistableWallet }
.map {
val bip39Seed = withContext(Dispatchers.IO) {
val bip39Seed =
withContext(Dispatchers.IO) {
Mnemonics.MnemonicCode(it.seedPhrase.joinToString()).toSeed()
}
DerivationTool.getInstance().deriveUnifiedSpendingKey(
@ -104,7 +108,8 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
)
@OptIn(ExperimentalCoroutinesApi::class)
val walletSnapshot: StateFlow<WalletSnapshot?> = synchronizer
val walletSnapshot: StateFlow<WalletSnapshot?> =
synchronizer
.flatMapLatest {
if (null == it) {
flowOf(null)
@ -119,7 +124,8 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
null
)
val addresses: StateFlow<WalletAddresses?> = synchronizer
val addresses: StateFlow<WalletAddresses?> =
synchronizer
.filterNotNull()
.map {
WalletAddresses.new(it)
@ -136,8 +142,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
/**
* Creates a wallet asynchronously and then persists it. Clients observe
* [secretState] to see the side effects. This would be used for a user creating a new wallet.
*/
/*
*
* Although waiting for the wallet to be written and then read back is slower, it is probably
* safer because it 1. guarantees the wallet is written to disk and 2. has a single source of truth.
*/
@ -146,7 +151,8 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
viewModelScope.launch {
val network = ZcashNetwork.fromResources(application)
val newWallet = PersistableWallet.new(
val newWallet =
PersistableWallet.new(
application = application,
zcashNetwork = network,
endpoint = LightWalletEndpoint.defaultForNetwork(network),
@ -268,7 +274,9 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
*/
sealed class SecretState {
object Loading : SecretState()
object None : SecretState()
class Ready(val persistableWallet: PersistableWallet) : SecretState()
}
@ -276,22 +284,27 @@ sealed class SendState {
object None : SendState() {
override fun toString(): String = "None"
}
object Sending : SendState() {
override fun toString(): String = "Sending"
}
class Sent(val localTxId: Long) : SendState() {
override fun toString(): String = "Sent"
}
class Error(val error: Throwable) : SendState() {
override fun toString(): String = "Error ${error.message}"
}
}
// TODO [#529]: Localize Synchronizer Errors
// TODO [#529]: https://github.com/zcash/secant-android-wallet/issues/529
/**
* Represents all kind of Synchronizer errors
*/
// TODO [#529]: Localize Synchronizer Errors
// TODO [#529]: https://github.com/zcash/secant-android-wallet/issues/529
sealed class SynchronizerError {
abstract fun getCauseMessage(): String?
@ -316,7 +329,8 @@ sealed class SynchronizerError {
}
}
private fun Synchronizer.toCommonError(): Flow<SynchronizerError?> = callbackFlow {
private fun Synchronizer.toCommonError(): Flow<SynchronizerError?> =
callbackFlow {
// just for initial default value emit
trySend(null)
@ -354,13 +368,20 @@ private fun Synchronizer.toCommonError(): Flow<SynchronizerError?> = callbackFlo
@Suppress("MagicNumber")
private fun Synchronizer.toWalletSnapshot() =
combine(
status, // 0
processorInfo, // 1
orchardBalances, // 2
saplingBalances, // 3
transparentBalances, // 4
progress, // 5
toCommonError() // 6
// 0
status,
// 1
processorInfo,
// 2
orchardBalances,
// 3
saplingBalances,
// 4
transparentBalances,
// 5
progress,
// 6
toCommonError()
) { flows ->
val orchardBalance = flows[2] as WalletBalance?
val saplingBalance = flows[3] as WalletBalance?

View File

@ -26,6 +26,7 @@ import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
@Preview(name = "Seed")
@Composable
@Suppress("ktlint:standard:function-naming")
private fun ComposablePreview() {
MaterialTheme {
Seed(
@ -37,6 +38,7 @@ private fun ComposablePreview() {
}
@Composable
@Suppress("ktlint:standard:function-naming")
fun Seed(
zcashNetwork: ZcashNetwork,
onExistingWallet: (PersistableWallet) -> Unit,
@ -56,6 +58,7 @@ fun Seed(
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("ktlint:standard:function-naming")
private fun ConfigureSeedTopAppBar() {
TopAppBar(
title = { Text(text = stringResource(id = R.string.configure_seed)) }
@ -63,6 +66,7 @@ private fun ConfigureSeedTopAppBar() {
}
@Composable
@Suppress("ktlint:standard:function-naming")
private fun ConfigureSeedMainContent(
paddingValues: PaddingValues,
zcashNetwork: ZcashNetwork,
@ -76,7 +80,8 @@ private fun ConfigureSeedMainContent(
) {
Button(
onClick = {
val newWallet = PersistableWallet(
val newWallet =
PersistableWallet(
network = zcashNetwork,
endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork),
birthday = WalletFixture.Alice.getBirthday(zcashNetwork),
@ -90,7 +95,8 @@ private fun ConfigureSeedMainContent(
}
Button(
onClick = {
val newWallet = PersistableWallet(
val newWallet =
PersistableWallet(
network = zcashNetwork,
endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork),
birthday = WalletFixture.Ben.getBirthday(zcashNetwork),

View File

@ -52,6 +52,7 @@ import cash.z.ecc.android.sdk.model.toZecString
@Preview(name = "Send")
@Composable
@Suppress("ktlint:standard:function-naming")
private fun ComposablePreview() {
MaterialTheme {
Send(
@ -64,6 +65,7 @@ private fun ComposablePreview() {
}
@Composable
@Suppress("ktlint:standard:function-naming")
fun Send(
walletSnapshot: WalletSnapshot,
sendState: SendState,
@ -84,6 +86,7 @@ fun Send(
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("ktlint:standard:function-naming")
private fun SendTopAppBar(onBack: () -> Unit) {
TopAppBar(
title = { Text(text = stringResource(id = R.string.menu_send)) },
@ -101,7 +104,7 @@ private fun SendTopAppBar(onBack: () -> Unit) {
}
@Composable
@Suppress("LongMethod")
@Suppress("LongMethod", "ktlint:standard:function-naming")
private fun SendMainContent(
paddingValues: PaddingValues,
walletSnapshot: WalletSnapshot,
@ -227,7 +230,8 @@ private fun SendMainContent(
Button(
onClick = {
val zecSendValidation = ZecSendExt.new(
val zecSendValidation =
ZecSendExt.new(
context,
recipientAddressString,
amountZecString,
@ -240,7 +244,6 @@ private fun SendMainContent(
is ZecSendExt.ZecSendValidation.Invalid -> validation = zecSendValidation.validationErrors
}
},
// Needs actual validation
enabled = amountZecString.isNotBlank() && recipientAddressString.isNotBlank()
) {

View File

@ -39,6 +39,7 @@ import kotlinx.coroutines.launch
@Preview(name = "Transactions")
@Composable
@Suppress("ktlint:standard:function-naming")
private fun ComposablePreview() {
MaterialTheme {
// TODO [#1090]: Demo: Add Addresses and Transactions Compose Previews
@ -48,6 +49,7 @@ private fun ComposablePreview() {
}
@Composable
@Suppress("ktlint:standard:function-naming")
fun Transactions(
synchronizer: Synchronizer,
onBack: () -> Unit
@ -64,7 +66,8 @@ fun Transactions(
) { paddingValues ->
// TODO [#846]: Slow addresses providing
// TODO [#846]: https://github.com/zcash/zcash-android-wallet-sdk/issues/846
val walletAddresses = flow {
val walletAddresses =
flow {
emit(WalletAddresses.new(synchronizer))
}.collectAsState(
initial = null
@ -82,6 +85,7 @@ fun Transactions(
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("ktlint:standard:function-naming")
private fun TransactionsTopAppBar(
onBack: () -> Unit,
onRefresh: () -> Unit
@ -110,6 +114,7 @@ private fun TransactionsTopAppBar(
}
@Composable
@Suppress("ktlint:standard:function-naming")
private fun TransactionsMainContent(
paddingValues: PaddingValues,
synchronizer: Synchronizer,
@ -138,10 +143,12 @@ private fun TransactionsMainContent(
}
}
}) {
val time = tx.minedHeight?.let {
val time =
tx.minedHeight?.let {
tx.blockTimeEpochSeconds?.let { kotlinx.datetime.Instant.fromEpochSeconds(it) } ?: "Unknown"
} ?: "Pending"
val value = if (tx.isSentTransaction) {
val value =
if (tx.isSentTransaction) {
-tx.netValue.value
} else {
tx.netValue.value

View File

@ -11,7 +11,9 @@ internal object AndroidApiVersion {
* [sdk].
*/
@ChecksSdkIntAtLeast(parameter = 0)
fun isAtLeast(@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int): Boolean {
fun isAtLeast(
@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int
): Boolean {
return Build.VERSION.SDK_INT >= sdk
}

View File

@ -94,7 +94,7 @@ FOOJAY_TOOLCHAIN_RESOLVER_VERSION=0.5.0
FULLADLE_VERSION=0.17.4
GRADLE_VERSIONS_PLUGIN_VERSION=0.50.0
KSP_VERSION=1.8.20-1.0.10
KTLINT_VERSION=0.50.0
KTLINT_VERSION=1.1.0
PROTOBUF_GRADLE_PLUGIN_VERSION=0.9.4
RUST_GRADLE_PLUGIN_VERSION=0.9.3

View File

@ -7,7 +7,6 @@ import kotlin.test.assertFalse
import kotlin.test.assertTrue
class BenchmarkingExtTest {
@Test
@SmallTest
fun check_build_config() {

View File

@ -5,7 +5,6 @@ import kotlin.test.Test
import kotlin.test.assertEquals
class BlockRangeFixtureTest {
@Test
@SmallTest
fun compare_default_values() {

View File

@ -4,10 +4,7 @@ import io.grpc.Status
import io.grpc.StatusRuntimeException
object StatusExceptionFixture {
fun new(
status: Status
): StatusRuntimeException {
fun new(status: Status): StatusRuntimeException {
return StatusRuntimeException(status)
}
}

View File

@ -6,7 +6,6 @@ import kotlin.test.Ignore
import kotlin.test.Test
class ChannelFactoryTest {
private val channelFactory = AndroidChannelFactory(getAppContext())
@Test

View File

@ -10,7 +10,6 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue
class GrpcStatusResolverTest {
@Test
@SmallTest
fun resolve_explicitly_caught_server_error_test() {

View File

@ -6,7 +6,6 @@ import kotlin.test.Ignore
import kotlin.test.Test
class LightWalletClientImplTest {
private val channelFactory = AndroidChannelFactory(getAppContext())
@Test

View File

@ -6,7 +6,11 @@ import androidx.test.core.app.ApplicationProvider
fun getAppContext(): Context = ApplicationProvider.getApplicationContext()
fun getStringResource(@StringRes resId: Int) = getAppContext().getString(resId)
fun getStringResource(
@StringRes resId: Int
) = getAppContext().getString(resId)
fun getStringResourceWithArgs(@StringRes resId: Int, vararg formatArgs: String) =
getAppContext().getString(resId, *formatArgs)
fun getStringResourceWithArgs(
@StringRes resId: Int,
vararg formatArgs: String
) = getAppContext().getString(resId, *formatArgs)

View File

@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.Flow
* Client for interacting with lightwalletd.
*/
interface LightWalletClient {
/**
* @return the full transaction info.
*/
@ -116,5 +115,4 @@ interface LightWalletClient {
fun LightWalletClient.Companion.new(
context: Context,
lightWalletEndpoint: LightWalletEndpoint
): LightWalletClient =
LightWalletClientImpl.new(AndroidChannelFactory(context), lightWalletEndpoint)
): LightWalletClient = LightWalletClientImpl.new(AndroidChannelFactory(context), lightWalletEndpoint)

View File

@ -6,7 +6,6 @@ import androidx.annotation.VisibleForTesting
* Used for getting mocked blocks range for benchmarking purposes.
*/
object BenchmarkingBlockRangeFixture {
// Be aware that changing these bounds values in a broader range may result in a timeout reached in
// SyncBlockchainBenchmark. So if changing these, don't forget to align also the test timeout in
// waitForBalanceScreen() appropriately.

View File

@ -11,12 +11,10 @@ import kotlinx.coroutines.flow.asFlow
*/
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
object ListOfCompactBlocksFixture {
val DEFAULT_FILE_BLOCK_RANGE = FileBlockRangeFixture.new()
fun newSequence(
blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE
): Sequence<CompactBlockUnsafe> {
@Suppress("MaxLineLength")
fun newSequence(blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE): Sequence<CompactBlockUnsafe> {
val blocks = mutableListOf<CompactBlockUnsafe>()
for (blockHeight in blocksHeightRange.start.value..blocksHeightRange.endInclusive.value) {
@ -28,9 +26,8 @@ object ListOfCompactBlocksFixture {
return blocks.asSequence()
}
fun newFlow(
blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE
): Flow<CompactBlockUnsafe> {
@Suppress("MaxLineLength")
fun newFlow(blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE): Flow<CompactBlockUnsafe> {
return newSequence(blocksHeightRange).asFlow()
}
}

View File

@ -7,14 +7,15 @@ import java.nio.ByteBuffer
* Used for getting single mocked compact block for processing and persisting purposes.
*/
internal object SingleCompactBlockFixture {
internal const val DEFAULT_HEIGHT = 500_000L
internal const val DEFAULT_TIME = 0
internal const val DEFAULT_SAPLING_OUTPUT_COUNT = 1u
internal const val DEFAULT_ORCHARD_OUTPUT_COUNT = 2u
internal const val DEFAULT_HASH = DEFAULT_HEIGHT
internal const val DEFAULT_BLOCK_BYTES = DEFAULT_HEIGHT
internal fun heightToFixtureData(height: Long) = BytesConversionHelper.longToBytes(height)
internal fun fixtureDataToHeight(byteArray: ByteArray) = BytesConversionHelper.bytesToLong(byteArray)
@Suppress("LongParameterList")

View File

@ -3,6 +3,7 @@ package co.electriccoin.lightwallet.client.internal
internal object Constants {
const val LOG_TAG = "LightWalletClient" // NON-NLS
const val ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON = "Illegal argument provided:" // NON-NLS
const val ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY = "$ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON " +
const val ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY =
"$ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON " +
"can't be empty:" // NON-NLS
}

View File

@ -24,7 +24,6 @@ class DarksideApi private constructor(
private val channel: ManagedChannel,
private val singleRequestTimeout: Duration = 10.seconds
) {
companion object {
internal fun new(
channelFactory: ChannelFactory,
@ -38,7 +37,8 @@ class DarksideApi private constructor(
fun reset(
saplingActivationHeight: BlockHeightUnsafe,
branchId: String = "e9ff75a6", // Canopy,
// Canopy
branchId: String = "e9ff75a6",
chainName: String = "darksidemainnet"
) = apply {
Darkside.DarksideMetaState.newBuilder()
@ -50,11 +50,15 @@ class DarksideApi private constructor(
}
}
fun stageBlocks(url: String) = apply {
fun stageBlocks(url: String) =
apply {
createStub().stageBlocks(url.toUrl())
}
fun stageTransactions(url: String, targetHeight: BlockHeightUnsafe) = apply {
fun stageTransactions(
url: String,
targetHeight: BlockHeightUnsafe
) = apply {
createStub().stageTransactions(
DarksideTransactionsURL.newBuilder().setHeight(targetHeight.value.toInt()).setUrl(url).build()
)
@ -71,7 +75,10 @@ class DarksideApi private constructor(
)
}
fun stageTransactions(txs: Iterator<Service.RawTransaction>?, tipHeight: BlockHeightUnsafe) {
fun stageTransactions(
txs: Iterator<Service.RawTransaction>?,
tipHeight: BlockHeightUnsafe
) {
if (txs == null) {
return
}
@ -147,6 +154,7 @@ class DarksideApi private constructor(
var completed = false
var error: Throwable? = null
override fun onNext(value: Service.Empty?) {
// No implementation
}
@ -171,8 +179,7 @@ class DarksideApi private constructor(
}
}
private fun BlockHeightUnsafe.toHeight() =
Darkside.DarksideHeight.newBuilder().setHeight(this.value.toInt()).build()
private fun BlockHeightUnsafe.toHeight() = Darkside.DarksideHeight.newBuilder().setHeight(this.value.toInt()).build()
fun DarksideApi.Companion.new(
context: Context,

Some files were not shown because too many files have changed in this diff Show More