[#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:
parent
e550cd9943
commit
73d7afc2a4
|
@ -6,7 +6,6 @@ import org.junit.Test
|
||||||
import kotlin.test.assertContentEquals
|
import kotlin.test.assertContentEquals
|
||||||
|
|
||||||
class RustDerivationToolTest {
|
class RustDerivationToolTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SEED_PHRASE =
|
private const val SEED_PHRASE =
|
||||||
"kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan" +
|
"kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan" +
|
||||||
|
@ -14,12 +13,13 @@ class RustDerivationToolTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun create_spending_key_does_not_mutate_passed_bytes() = runTest {
|
fun create_spending_key_does_not_mutate_passed_bytes() =
|
||||||
val bytesOne = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()
|
runTest {
|
||||||
val bytesTwo = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()
|
val bytesOne = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()
|
||||||
|
val bytesTwo = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()
|
||||||
|
|
||||||
RustDerivationTool.new().deriveUnifiedSpendingKey(bytesOne, networkId = 1, accountIndex = 0)
|
RustDerivationTool.new().deriveUnifiedSpendingKey(bytesOne, networkId = 1, accountIndex = 0)
|
||||||
|
|
||||||
assertContentEquals(bytesTwo, bytesOne)
|
assertContentEquals(bytesTwo, bytesOne)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import cash.z.ecc.android.sdk.internal.model.JniWalletSummary
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
interface Backend {
|
interface Backend {
|
||||||
|
|
||||||
val networkId: Int
|
val networkId: Int
|
||||||
|
|
||||||
suspend fun initBlockMetaDb(): Int
|
suspend fun initBlockMetaDb(): Int
|
||||||
|
@ -53,7 +52,11 @@ interface Backend {
|
||||||
* @throws RuntimeException as a common indicator of the operation failure
|
* @throws RuntimeException as a common indicator of the operation failure
|
||||||
*/
|
*/
|
||||||
@Throws(RuntimeException::class)
|
@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
|
fun isValidShieldedAddr(addr: String): Boolean
|
||||||
|
|
||||||
|
@ -75,7 +78,10 @@ interface Backend {
|
||||||
* @throws RuntimeException as a common indicator of the operation failure
|
* @throws RuntimeException as a common indicator of the operation failure
|
||||||
*/
|
*/
|
||||||
@Throws(RuntimeException::class)
|
@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
|
suspend fun getNearestRewindHeight(height: Long): Long
|
||||||
|
|
||||||
|
@ -139,7 +145,10 @@ interface Backend {
|
||||||
* @throws RuntimeException as a common indicator of the operation failure
|
* @throws RuntimeException as a common indicator of the operation failure
|
||||||
*/
|
*/
|
||||||
@Throws(RuntimeException::class)
|
@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
|
* @throws RuntimeException as a common indicator of the operation failure
|
||||||
|
@ -157,6 +166,7 @@ interface Backend {
|
||||||
suspend fun rewindBlockMetadataToHeight(height: Long)
|
suspend fun rewindBlockMetadataToHeight(height: Long)
|
||||||
|
|
||||||
suspend fun getVerifiedTransparentBalance(address: String): Long
|
suspend fun getVerifiedTransparentBalance(address: String): Long
|
||||||
|
|
||||||
suspend fun getTotalTransparentBalance(address: String): Long
|
suspend fun getTotalTransparentBalance(address: String): Long
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,29 +6,29 @@ import java.util.concurrent.Executors
|
||||||
internal object SdkExecutors {
|
internal object SdkExecutors {
|
||||||
/**
|
/**
|
||||||
* Executor used for database IO that's shared with the Rust native library.
|
* 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.
|
* 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
|
* We don't expect things to break, but we don't have the WAL enabled for SQLite so this
|
||||||
* is a simple solution.
|
* is a simple solution.
|
||||||
*/
|
*/
|
||||||
val DATABASE_IO = Executors.newSingleThreadExecutor {
|
val DATABASE_IO =
|
||||||
Thread(it, "zc-io").apply { isDaemon = true }
|
Executors.newSingleThreadExecutor {
|
||||||
}
|
Thread(it, "zc-io").apply { isDaemon = true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object SdkDispatchers {
|
object SdkDispatchers {
|
||||||
/**
|
/**
|
||||||
* Dispatcher used for database IO that's shared with the Rust native library.
|
* 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.
|
* 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
|
* We don't expect things to break, but we don't have the WAL enabled for SQLite so this
|
||||||
* is a simple solution.
|
* 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()
|
val DATABASE_IO = SdkExecutors.DATABASE_IO.asCoroutineDispatcher()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) =
|
||||||
System.loadLibrary(libraryName)
|
withContext(Dispatchers.IO) {
|
||||||
}
|
System.loadLibrary(libraryName)
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ class RustBackend private constructor(
|
||||||
private val saplingSpendFile: File,
|
private val saplingSpendFile: File,
|
||||||
private val saplingOutputFile: File,
|
private val saplingOutputFile: File,
|
||||||
) : Backend {
|
) : Backend {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function deletes the data database file and the cache directory (compact blocks files) if set by input
|
* This function deletes the data database file and the cache directory (compact blocks files) if set by input
|
||||||
* parameters.
|
* parameters.
|
||||||
|
@ -35,7 +34,10 @@ class RustBackend private constructor(
|
||||||
*
|
*
|
||||||
* @return false in case of any required and failed deletion, true otherwise.
|
* @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 cacheClearResult = true
|
||||||
var dataClearResult = true
|
var dataClearResult = true
|
||||||
if (clearCache) {
|
if (clearCache) {
|
||||||
|
@ -55,19 +57,21 @@ class RustBackend private constructor(
|
||||||
// Wrapper Functions
|
// Wrapper Functions
|
||||||
//
|
//
|
||||||
|
|
||||||
override suspend fun initBlockMetaDb() = withContext(SdkDispatchers.DATABASE_IO) {
|
override suspend fun initBlockMetaDb() =
|
||||||
initBlockMetaDb(
|
withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
fsBlockDbRoot.absolutePath,
|
initBlockMetaDb(
|
||||||
)
|
fsBlockDbRoot.absolutePath,
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun initDataDb(seed: ByteArray?) = withContext(SdkDispatchers.DATABASE_IO) {
|
override suspend fun initDataDb(seed: ByteArray?) =
|
||||||
initDataDb(
|
withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
dataDbFile.absolutePath,
|
initDataDb(
|
||||||
seed,
|
dataDbFile.absolutePath,
|
||||||
networkId = networkId
|
seed,
|
||||||
)
|
networkId = networkId
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun createAccount(
|
override suspend fun createAccount(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
|
@ -108,15 +112,17 @@ class RustBackend private constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int) =
|
override suspend fun getMemoAsUtf8(
|
||||||
withContext(SdkDispatchers.DATABASE_IO) {
|
txId: ByteArray,
|
||||||
getMemoAsUtf8(
|
outputIndex: Int
|
||||||
dataDbFile.absolutePath,
|
) = withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
txId,
|
getMemoAsUtf8(
|
||||||
outputIndex,
|
dataDbFile.absolutePath,
|
||||||
networkId = networkId
|
txId,
|
||||||
)
|
outputIndex,
|
||||||
}
|
networkId = networkId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun writeBlockMetadata(blockMetadata: List<JniBlockMeta>) =
|
override suspend fun writeBlockMetadata(blockMetadata: List<JniBlockMeta>) =
|
||||||
withContext(SdkDispatchers.DATABASE_IO) {
|
withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
|
@ -217,10 +223,11 @@ class RustBackend private constructor(
|
||||||
|
|
||||||
override suspend fun getFullyScannedHeight() =
|
override suspend fun getFullyScannedHeight() =
|
||||||
withContext(SdkDispatchers.DATABASE_IO) {
|
withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
val height = getFullyScannedHeight(
|
val height =
|
||||||
dataDbFile.absolutePath,
|
getFullyScannedHeight(
|
||||||
networkId = networkId
|
dataDbFile.absolutePath,
|
||||||
)
|
networkId = networkId
|
||||||
|
)
|
||||||
|
|
||||||
if (-1L == height) {
|
if (-1L == height) {
|
||||||
null
|
null
|
||||||
|
@ -231,10 +238,11 @@ class RustBackend private constructor(
|
||||||
|
|
||||||
override suspend fun getMaxScannedHeight() =
|
override suspend fun getMaxScannedHeight() =
|
||||||
withContext(SdkDispatchers.DATABASE_IO) {
|
withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
val height = getMaxScannedHeight(
|
val height =
|
||||||
dataDbFile.absolutePath,
|
getMaxScannedHeight(
|
||||||
networkId = networkId
|
dataDbFile.absolutePath,
|
||||||
)
|
networkId = networkId
|
||||||
|
)
|
||||||
|
|
||||||
if (-1L == height) {
|
if (-1L == height) {
|
||||||
null
|
null
|
||||||
|
@ -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) {
|
return withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
scanBlocks(
|
scanBlocks(
|
||||||
fsBlockDbRoot.absolutePath,
|
fsBlockDbRoot.absolutePath,
|
||||||
|
@ -287,19 +298,20 @@ class RustBackend private constructor(
|
||||||
to: String,
|
to: String,
|
||||||
value: Long,
|
value: Long,
|
||||||
memo: ByteArray?
|
memo: ByteArray?
|
||||||
): ByteArray = withContext(SdkDispatchers.DATABASE_IO) {
|
): ByteArray =
|
||||||
createToAddress(
|
withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
dataDbFile.absolutePath,
|
createToAddress(
|
||||||
unifiedSpendingKey,
|
dataDbFile.absolutePath,
|
||||||
to,
|
unifiedSpendingKey,
|
||||||
value,
|
to,
|
||||||
memo ?: ByteArray(0),
|
value,
|
||||||
spendParamsPath = saplingSpendFile.absolutePath,
|
memo ?: ByteArray(0),
|
||||||
outputParamsPath = saplingOutputFile.absolutePath,
|
spendParamsPath = saplingSpendFile.absolutePath,
|
||||||
networkId = networkId,
|
outputParamsPath = saplingOutputFile.absolutePath,
|
||||||
useZip317Fees = IS_USE_ZIP_317_FEES
|
networkId = networkId,
|
||||||
)
|
useZip317Fees = IS_USE_ZIP_317_FEES
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun shieldToAddress(
|
override suspend fun shieldToAddress(
|
||||||
account: Int,
|
account: Int,
|
||||||
|
@ -339,17 +351,13 @@ class RustBackend private constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isValidShieldedAddr(addr: String) =
|
override fun isValidShieldedAddr(addr: String) = isValidShieldedAddress(addr, networkId = networkId)
|
||||||
isValidShieldedAddress(addr, networkId = networkId)
|
|
||||||
|
|
||||||
override fun isValidTransparentAddr(addr: String) =
|
override fun isValidTransparentAddr(addr: String) = isValidTransparentAddress(addr, networkId = networkId)
|
||||||
isValidTransparentAddress(addr, networkId = networkId)
|
|
||||||
|
|
||||||
override fun isValidUnifiedAddr(addr: String) =
|
override fun isValidUnifiedAddr(addr: String) = isValidUnifiedAddress(addr, networkId = networkId)
|
||||||
isValidUnifiedAddress(addr, networkId = networkId)
|
|
||||||
|
|
||||||
override fun getBranchIdForHeight(height: Long): Long =
|
override fun getBranchIdForHeight(height: Long): Long = branchIdForHeight(height, networkId = networkId)
|
||||||
branchIdForHeight(height, networkId = networkId)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposes all of the librustzcash functions along with helpers for loading the static library.
|
* 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
|
private external fun initBlockMetaDb(fsBlockDbRoot: String): Int
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun initDataDb(dbDataPath: String, seed: ByteArray?, networkId: Int): Int
|
private external fun initDataDb(
|
||||||
|
dbDataPath: String,
|
||||||
|
seed: ByteArray?,
|
||||||
|
networkId: Int
|
||||||
|
): Int
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun createAccount(
|
private external fun createAccount(
|
||||||
|
@ -422,7 +434,11 @@ class RustBackend private constructor(
|
||||||
private external fun getSaplingReceiverForUnifiedAddress(ua: String): String?
|
private external fun getSaplingReceiverForUnifiedAddress(ua: String): String?
|
||||||
|
|
||||||
@JvmStatic
|
@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)
|
fun validateUnifiedSpendingKey(bytes: ByteArray) = isValidSpendingKey(bytes)
|
||||||
|
|
||||||
|
@ -430,13 +446,22 @@ class RustBackend private constructor(
|
||||||
private external fun isValidSpendingKey(bytes: ByteArray): Boolean
|
private external fun isValidSpendingKey(bytes: ByteArray): Boolean
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean
|
private external fun isValidShieldedAddress(
|
||||||
|
addr: String,
|
||||||
|
networkId: Int
|
||||||
|
): Boolean
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun isValidTransparentAddress(addr: String, networkId: Int): Boolean
|
private external fun isValidTransparentAddress(
|
||||||
|
addr: String,
|
||||||
|
networkId: Int
|
||||||
|
): Boolean
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun isValidUnifiedAddress(addr: String, networkId: Int): Boolean
|
private external fun isValidUnifiedAddress(
|
||||||
|
addr: String,
|
||||||
|
networkId: Int
|
||||||
|
): Boolean
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun getMemoAsUtf8(
|
private external fun getMemoAsUtf8(
|
||||||
|
@ -563,7 +588,10 @@ class RustBackend private constructor(
|
||||||
): ByteArray
|
): ByteArray
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun branchIdForHeight(height: Long, networkId: Int): Long
|
private external fun branchIdForHeight(
|
||||||
|
height: Long,
|
||||||
|
networkId: Int
|
||||||
|
): Long
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
|
|
@ -4,19 +4,16 @@ import cash.z.ecc.android.sdk.internal.Derivation
|
||||||
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
|
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
|
||||||
|
|
||||||
class RustDerivationTool private constructor() : Derivation {
|
class RustDerivationTool private constructor() : Derivation {
|
||||||
|
|
||||||
override fun deriveUnifiedFullViewingKeys(
|
override fun deriveUnifiedFullViewingKeys(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
networkId: Int,
|
networkId: Int,
|
||||||
numberOfAccounts: Int
|
numberOfAccounts: Int
|
||||||
): Array<String> =
|
): Array<String> = deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = networkId)
|
||||||
deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = networkId)
|
|
||||||
|
|
||||||
override fun deriveUnifiedFullViewingKey(
|
override fun deriveUnifiedFullViewingKey(
|
||||||
usk: JniUnifiedSpendingKey,
|
usk: JniUnifiedSpendingKey,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): String =
|
): String = deriveUnifiedFullViewingKey(usk.bytes, networkId = networkId)
|
||||||
deriveUnifiedFullViewingKey(usk.bytes, networkId = networkId)
|
|
||||||
|
|
||||||
override fun deriveUnifiedSpendingKey(
|
override fun deriveUnifiedSpendingKey(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
|
@ -24,8 +21,11 @@ class RustDerivationTool private constructor() : Derivation {
|
||||||
accountIndex: Int
|
accountIndex: Int
|
||||||
): JniUnifiedSpendingKey = deriveSpendingKey(seed, accountIndex, networkId = networkId)
|
): JniUnifiedSpendingKey = deriveSpendingKey(seed, accountIndex, networkId = networkId)
|
||||||
|
|
||||||
override fun deriveUnifiedAddress(seed: ByteArray, networkId: Int, accountIndex: Int): String =
|
override fun deriveUnifiedAddress(
|
||||||
deriveUnifiedAddressFromSeed(seed, accountIndex = accountIndex, networkId = networkId)
|
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.
|
* Given a Unified Full Viewing Key string, return the associated Unified Address.
|
||||||
|
@ -38,8 +38,7 @@ class RustDerivationTool private constructor() : Derivation {
|
||||||
override fun deriveUnifiedAddress(
|
override fun deriveUnifiedAddress(
|
||||||
viewingKey: String,
|
viewingKey: String,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): String =
|
): String = deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId)
|
||||||
deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
suspend fun new(): Derivation {
|
suspend fun new(): Derivation {
|
||||||
|
@ -63,7 +62,10 @@ class RustDerivationTool private constructor() : Derivation {
|
||||||
): Array<String>
|
): Array<String>
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun deriveUnifiedFullViewingKey(usk: ByteArray, networkId: Int): String
|
private external fun deriveUnifiedFullViewingKey(
|
||||||
|
usk: ByteArray,
|
||||||
|
networkId: Int
|
||||||
|
): String
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun deriveUnifiedAddressFromSeed(
|
private external fun deriveUnifiedAddressFromSeed(
|
||||||
|
@ -73,6 +75,9 @@ class RustDerivationTool private constructor() : Derivation {
|
||||||
): String
|
): String
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String
|
private external fun deriveUnifiedAddressFromViewingKey(
|
||||||
|
key: String,
|
||||||
|
networkId: Int
|
||||||
|
): String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,10 @@ class JniSubtreeRoot(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun new(rootHash: ByteArray, completingBlockHeight: Long): JniSubtreeRoot {
|
fun new(
|
||||||
|
rootHash: ByteArray,
|
||||||
|
completingBlockHeight: Long
|
||||||
|
): JniSubtreeRoot {
|
||||||
return JniSubtreeRoot(
|
return JniSubtreeRoot(
|
||||||
rootHash = rootHash,
|
rootHash = rootHash,
|
||||||
completingBlockHeight = completingBlockHeight
|
completingBlockHeight = completingBlockHeight
|
||||||
|
|
|
@ -24,7 +24,6 @@ class JniUnifiedSpendingKey(
|
||||||
*/
|
*/
|
||||||
val bytes: ByteArray
|
val bytes: ByteArray
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Override to prevent leaking key to logs
|
// Override to prevent leaking key to logs
|
||||||
override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)"
|
override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)"
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.internal.fixture
|
||||||
import cash.z.ecc.android.sdk.internal.model.JniAccountBalance
|
import cash.z.ecc.android.sdk.internal.model.JniAccountBalance
|
||||||
|
|
||||||
object JniAccountBalanceFixture {
|
object JniAccountBalanceFixture {
|
||||||
|
|
||||||
const val ACCOUNT_ID: Int = 0
|
const val ACCOUNT_ID: Int = 0
|
||||||
const val SAPLING_TOTAL_BALANCE: Long = 0L
|
const val SAPLING_TOTAL_BALANCE: Long = 0L
|
||||||
const val SAPLING_VERIFIED_BALANCE: Long = 0L
|
const val SAPLING_VERIFIED_BALANCE: Long = 0L
|
||||||
|
@ -12,7 +11,6 @@ object JniAccountBalanceFixture {
|
||||||
account: Int = ACCOUNT_ID,
|
account: Int = ACCOUNT_ID,
|
||||||
saplingTotalBalance: Long = SAPLING_TOTAL_BALANCE,
|
saplingTotalBalance: Long = SAPLING_TOTAL_BALANCE,
|
||||||
saplingVerifiedBalance: Long = SAPLING_VERIFIED_BALANCE,
|
saplingVerifiedBalance: Long = SAPLING_VERIFIED_BALANCE,
|
||||||
|
|
||||||
) = JniAccountBalance(
|
) = JniAccountBalance(
|
||||||
account = account,
|
account = account,
|
||||||
saplingTotalBalance = saplingTotalBalance,
|
saplingTotalBalance = saplingTotalBalance,
|
||||||
|
|
|
@ -7,13 +7,14 @@ import kotlin.test.assertIs
|
||||||
class JniBlockMetaTest {
|
class JniBlockMetaTest {
|
||||||
@Test
|
@Test
|
||||||
fun attributes_within_constraints() {
|
fun attributes_within_constraints() {
|
||||||
val instance = JniBlockMeta(
|
val instance =
|
||||||
height = UInt.MAX_VALUE.toLong(),
|
JniBlockMeta(
|
||||||
hash = byteArrayOf(),
|
height = UInt.MAX_VALUE.toLong(),
|
||||||
time = 0L,
|
hash = byteArrayOf(),
|
||||||
saplingOutputsCount = UInt.MIN_VALUE.toLong(),
|
time = 0L,
|
||||||
orchardOutputsCount = UInt.MIN_VALUE.toLong()
|
saplingOutputsCount = UInt.MIN_VALUE.toLong(),
|
||||||
)
|
orchardOutputsCount = UInt.MIN_VALUE.toLong()
|
||||||
|
)
|
||||||
assertIs<JniBlockMeta>(instance)
|
assertIs<JniBlockMeta>(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,12 @@ import kotlin.test.assertIs
|
||||||
class JniScanRangeTest {
|
class JniScanRangeTest {
|
||||||
@Test
|
@Test
|
||||||
fun attributes_within_constraints() {
|
fun attributes_within_constraints() {
|
||||||
val instance = JniScanRange(
|
val instance =
|
||||||
startHeight = UInt.MIN_VALUE.toLong(),
|
JniScanRange(
|
||||||
endHeight = UInt.MAX_VALUE.toLong(),
|
startHeight = UInt.MIN_VALUE.toLong(),
|
||||||
priority = 10
|
endHeight = UInt.MAX_VALUE.toLong(),
|
||||||
)
|
priority = 10
|
||||||
|
)
|
||||||
assertIs<JniScanRange>(instance)
|
assertIs<JniScanRange>(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,11 @@ import kotlin.test.assertIs
|
||||||
class JniSubtreeRootTest {
|
class JniSubtreeRootTest {
|
||||||
@Test
|
@Test
|
||||||
fun attributes_within_constraints() {
|
fun attributes_within_constraints() {
|
||||||
val instance = JniSubtreeRoot(
|
val instance =
|
||||||
rootHash = byteArrayOf(),
|
JniSubtreeRoot(
|
||||||
completingBlockHeight = UInt.MAX_VALUE.toLong()
|
rootHash = byteArrayOf(),
|
||||||
)
|
completingBlockHeight = UInt.MAX_VALUE.toLong()
|
||||||
|
)
|
||||||
assertIs<JniSubtreeRoot>(instance)
|
assertIs<JniSubtreeRoot>(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,12 @@ import kotlin.test.assertIs
|
||||||
class JniWalletSummaryTest {
|
class JniWalletSummaryTest {
|
||||||
@Test
|
@Test
|
||||||
fun both_attribute_within_constraints() {
|
fun both_attribute_within_constraints() {
|
||||||
val instance = JniWalletSummary(
|
val instance =
|
||||||
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
|
JniWalletSummary(
|
||||||
progressNumerator = 1L,
|
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
|
||||||
progressDenominator = 100L
|
progressNumerator = 1L,
|
||||||
)
|
progressDenominator = 100L
|
||||||
|
)
|
||||||
assertIs<JniWalletSummary>(instance)
|
assertIs<JniWalletSummary>(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ plugins {
|
||||||
val ktlint by configurations.creating
|
val ktlint by configurations.creating
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
ktlint("com.pinterest:ktlint:${project.property("KTLINT_VERSION")}") {
|
ktlint("com.pinterest.ktlint:ktlint-cli:${project.property("KTLINT_VERSION")}") {
|
||||||
attributes {
|
attributes {
|
||||||
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named<Bundling>(Bundling.EXTERNAL))
|
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named<Bundling>(Bundling.EXTERNAL))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,17 +7,19 @@ import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
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.
|
* 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)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class TransparentIntegrationTest : DarksideTest() {
|
class TransparentIntegrationTest : DarksideTest() {
|
||||||
@Before
|
@Before
|
||||||
fun setup() = runOnce {
|
fun setup() =
|
||||||
// sithLord.await()
|
runOnce {
|
||||||
}
|
// sithLord.await()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("This test is broken")
|
@Ignore("This test is broken")
|
||||||
|
|
|
@ -14,7 +14,6 @@ import org.junit.runner.RunWith
|
||||||
// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224
|
// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class InboundTxTests : ScopedTest() {
|
class InboundTxTests : ScopedTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Temporarily disabled")
|
@Ignore("Temporarily disabled")
|
||||||
fun testTargetBlock_synced() {
|
fun testTargetBlock_synced() {
|
||||||
|
@ -35,12 +34,15 @@ class InboundTxTests : ScopedTest() {
|
||||||
@Ignore("Temporarily disabled")
|
@Ignore("Temporarily disabled")
|
||||||
fun testTxCountAfter() {
|
fun testTxCountAfter() {
|
||||||
// add 2 transactions to block 663188 and 'mine' that block
|
// 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)
|
// sithLord.await(timeout = 30_000L, targetHeight = targetTxBlock)
|
||||||
validator.validateTxCount(2)
|
validator.validateTxCount(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addTransactions(targetHeight: BlockHeight, vararg txs: String) {
|
private fun addTransactions(
|
||||||
|
targetHeight: BlockHeight,
|
||||||
|
vararg txs: String
|
||||||
|
) {
|
||||||
// val overwriteBlockCount = 5
|
// val overwriteBlockCount = 5
|
||||||
chainMaker
|
chainMaker
|
||||||
// .stageEmptyBlocks(targetHeight, overwriteBlockCount)
|
// .stageEmptyBlocks(targetHeight, overwriteBlockCount)
|
||||||
|
@ -48,40 +50,42 @@ class InboundTxTests : ScopedTest() {
|
||||||
.applyTipHeight(targetHeight)
|
.applyTipHeight(targetHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength", "UnusedPrivateProperty", "ktlint:standard:max-line-length")
|
||||||
companion object {
|
companion object {
|
||||||
private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
private const val BLOCKS_URL = "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 TX_663174 = "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 TX_663188 = "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 const val TX_INDEX_REORG = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/t1.txt"
|
||||||
private val txSend = arrayOf(
|
private val txSend =
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt",
|
arrayOf(
|
||||||
"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/t-shielded-spend.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.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/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt"
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt",
|
||||||
)
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt"
|
||||||
|
)
|
||||||
|
|
||||||
private val txRecv = arrayOf(
|
private val txRecv =
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt",
|
arrayOf(
|
||||||
"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/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.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/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt",
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt",
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt"
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt",
|
||||||
)
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt"
|
||||||
|
)
|
||||||
|
|
||||||
private val firstBlock = BlockHeight.new(ZcashNetwork.Mainnet, 663150L)
|
private val firstBlock = BlockHeight.new(ZcashNetwork.Mainnet, 663150L)
|
||||||
private val targetTxBlock = BlockHeight.new(ZcashNetwork.Mainnet, 663188L)
|
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 sithLord = DarksideTestCoordinator()
|
||||||
private val validator = sithLord.validator
|
private val validator = sithLord.validator
|
||||||
private val chainMaker = sithLord.chainMaker
|
private val chainMaker = sithLord.chainMaker
|
||||||
|
@ -92,7 +96,7 @@ class InboundTxTests : ScopedTest() {
|
||||||
sithLord.enterTheDarkside()
|
sithLord.enterTheDarkside()
|
||||||
|
|
||||||
chainMaker
|
chainMaker
|
||||||
.resetBlocks(blocksUrl, startHeight = firstBlock, tipHeight = targetTxBlock)
|
.resetBlocks(BLOCKS_URL, startHeight = firstBlock, tipHeight = targetTxBlock)
|
||||||
.stageEmptyBlocks(firstBlock + 1, 100)
|
.stageEmptyBlocks(firstBlock + 1, 100)
|
||||||
.applyTipHeight(BlockHeight.new(ZcashNetwork.Mainnet, targetTxBlock.value - 1))
|
.applyTipHeight(BlockHeight.new(ZcashNetwork.Mainnet, targetTxBlock.value - 1))
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import org.junit.runner.RunWith
|
||||||
// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224
|
// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ReorgSetupTest : ScopedTest() {
|
class ReorgSetupTest : ScopedTest() {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private val birthdayHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663150)
|
private val birthdayHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663150)
|
||||||
private val targetHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663250)
|
private val targetHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663250)
|
||||||
|
@ -26,20 +25,21 @@ class ReorgSetupTest : ScopedTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Temporarily disabled")
|
@Ignore("Temporarily disabled")
|
||||||
fun testBeforeReorg_minHeight() = timeout(30_000L) {
|
fun testBeforeReorg_minHeight() =
|
||||||
// validate that we are synced, at least to the birthday height
|
timeout(30_000L) {
|
||||||
// validator.validateMinHeightSynced(birthdayHeight)
|
// validate that we are synced, at least to the birthday height
|
||||||
}
|
// validator.validateMinHeightSynced(birthdayHeight)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Temporarily disabled")
|
@Ignore("Temporarily disabled")
|
||||||
fun testBeforeReorg_maxHeight() = timeout(30_000L) {
|
fun testBeforeReorg_maxHeight() =
|
||||||
// validate that we are not synced beyond the target height
|
timeout(30_000L) {
|
||||||
// validator.validateMaxHeightSynced(targetHeight)
|
// validate that we are not synced beyond the target height
|
||||||
}
|
// validator.validateMaxHeightSynced(targetHeight)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val sithLord = DarksideTestCoordinator()
|
private val sithLord = DarksideTestCoordinator()
|
||||||
private val validator = sithLord.validator
|
private val validator = sithLord.validator
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ReorgSmallTest : ScopedTest() {
|
class ReorgSmallTest : ScopedTest() {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private val targetHeight = BlockHeight.new(
|
private val targetHeight = BlockHeight.new(
|
||||||
ZcashNetwork.Mainnet,
|
ZcashNetwork.Mainnet,
|
||||||
|
@ -29,27 +28,29 @@ class ReorgSmallTest : ScopedTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Temporarily disabled")
|
@Ignore("Temporarily disabled")
|
||||||
fun testBeforeReorg_latestBlockHash() = timeout(30_000L) {
|
fun testBeforeReorg_latestBlockHash() =
|
||||||
// validator.validateBlockHash(targetHeight, hashBeforeReorg)
|
timeout(30_000L) {
|
||||||
}
|
// validator.validateBlockHash(targetHeight, hashBeforeReorg)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Temporarily disabled")
|
@Ignore("Temporarily disabled")
|
||||||
fun testAfterReorg_callbackTriggered() = timeout(30_000L) {
|
fun testAfterReorg_callbackTriggered() =
|
||||||
hadReorg = false
|
timeout(30_000L) {
|
||||||
|
hadReorg = false
|
||||||
// sithLord.triggerSmallReorg()
|
// sithLord.triggerSmallReorg()
|
||||||
// sithLord.await()
|
// sithLord.await()
|
||||||
assertTrue(hadReorg)
|
assertTrue(hadReorg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Temporarily disabled")
|
@Ignore("Temporarily disabled")
|
||||||
fun testAfterReorg_latestBlockHash() = timeout(30_000L) {
|
fun testAfterReorg_latestBlockHash() =
|
||||||
// validator.validateBlockHash(targetHeight, hashAfterReorg)
|
timeout(30_000L) {
|
||||||
}
|
// validator.validateBlockHash(targetHeight, hashAfterReorg)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val sithLord = DarksideTestCoordinator()
|
private val sithLord = DarksideTestCoordinator()
|
||||||
private val validator = sithLord.validator
|
private val validator = sithLord.validator
|
||||||
private var hadReorg = false
|
private var hadReorg = false
|
||||||
|
|
|
@ -12,8 +12,7 @@ import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class SetupTest : ScopedTest() {
|
class SetupTest : ScopedTest() {
|
||||||
|
// @Test
|
||||||
// @Test
|
|
||||||
// fun testFirstBlockExists() {
|
// fun testFirstBlockExists() {
|
||||||
// validator.validateHasBlock(
|
// validator.validateHasBlock(
|
||||||
// firstBlock
|
// firstBlock
|
||||||
|
@ -38,8 +37,9 @@ class SetupTest : ScopedTest() {
|
||||||
@Test
|
@Test
|
||||||
@Ignore("This test is broken")
|
@Ignore("This test is broken")
|
||||||
fun tempTest() {
|
fun tempTest() {
|
||||||
val phrase = "still champion voice habit trend flight survey between bitter process artefact blind" +
|
val phrase =
|
||||||
" 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"
|
||||||
val result = SimpleMnemonics().toSeed(phrase.toCharArray()).toHex()
|
val result = SimpleMnemonics().toSeed(phrase.toCharArray()).toHex()
|
||||||
assertEquals("abc", result)
|
assertEquals("abc", result)
|
||||||
}
|
}
|
||||||
|
@ -54,12 +54,12 @@ class SetupTest : ScopedTest() {
|
||||||
assertEquals("a", "${ent.toHex()}|${String(phrase)}")
|
assertEquals("a", "${ent.toHex()}|${String(phrase)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength", "UnusedPrivateProperty", "ktlint:standard:max-line-length")
|
||||||
companion object {
|
companion object {
|
||||||
@Suppress("MaxLineLength")
|
private const val BLOCKS_URL = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
||||||
private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
private const val FIRST_BLOCK = 663150
|
||||||
private const val firstBlock = 663150
|
private const val LAST_BLOCK = 663200
|
||||||
private const val lastBlock = 663200
|
private const val LAST_BLOCK_HASH = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
|
||||||
private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
|
|
||||||
private val sithLord = DarksideTestCoordinator()
|
private val sithLord = DarksideTestCoordinator()
|
||||||
private val validator = sithLord.validator
|
private val validator = sithLord.validator
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ open class DarksideTest : ScopedTest() {
|
||||||
ranOnce = true
|
ranOnce = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var ranOnce = false
|
private var ranOnce = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,28 +49,29 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
/**
|
/**
|
||||||
* Setup dependencies, including the synchronizer and the darkside API connection
|
* Setup dependencies, including the synchronizer and the darkside API connection
|
||||||
*/
|
*/
|
||||||
fun enterTheDarkside(): DarksideTestCoordinator = runBlocking {
|
fun enterTheDarkside(): DarksideTestCoordinator =
|
||||||
// verify that we are on the darkside
|
runBlocking {
|
||||||
try {
|
// verify that we are on the darkside
|
||||||
initiate()
|
try {
|
||||||
// In the future, we may want to have the SDK internally verify being on the darkside by matching the
|
initiate()
|
||||||
// network type
|
// In the future, we may want to have the SDK internally verify being on the darkside by matching the
|
||||||
|
// network type
|
||||||
|
|
||||||
// synchronizer.getServerInfo().apply {
|
// synchronizer.getServerInfo().apply {
|
||||||
// assertTrue(
|
// assertTrue(
|
||||||
// "Error: not on the darkside",
|
// "Error: not on the darkside",
|
||||||
// vendor.contains("dark", true)
|
// vendor.contains("dark", true)
|
||||||
// or chainName.contains("dark", true)
|
// or chainName.contains("dark", true)
|
||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
} catch (error: StatusRuntimeException) {
|
} catch (error: StatusRuntimeException) {
|
||||||
Assert.fail(
|
Assert.fail(
|
||||||
"Error while fetching server status. Testing cannot begin due to:" +
|
"Error while fetching server status. Testing cannot begin due to:" +
|
||||||
" ${error.message} Caused by: ${error.cause} Verify that the server is running!"
|
" ${error.message} Caused by: ${error.cause} Verify that the server is running!"
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
this@DarksideTestCoordinator
|
||||||
}
|
}
|
||||||
this@DarksideTestCoordinator
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup the synchronizer and darksidewalletd with their initial state
|
* Setup the synchronizer and darksidewalletd with their initial state
|
||||||
|
@ -80,7 +81,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
darkside.reset(BlockHeightUnsafe(wallet.network.saplingActivationHeight.value))
|
darkside.reset(BlockHeightUnsafe(wallet.network.saplingActivationHeight.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun triggerSmallReorg() {
|
// fun triggerSmallReorg() {
|
||||||
// darkside.setBlocksUrl(smallReorg)
|
// darkside.setBlocksUrl(smallReorg)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
@ -89,6 +90,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// redo this as a call to wallet but add delay time to wallet join() function
|
// 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
|
* Waits for, at most, the given amount of time for the synchronizer to download and scan blocks
|
||||||
* and reach a 'SYNCED' status.
|
* and reach a 'SYNCED' status.
|
||||||
|
@ -125,25 +127,27 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
// wallet.send(toAddress, memo, zatoshi, fromAccountIndex)
|
// wallet.send(toAddress, memo, zatoshi, fromAccountIndex)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fun stall(delay: Long = 5000L) = runBlocking {
|
@Suppress("ktlint:standard:no-consecutive-comments")
|
||||||
delay(delay)
|
fun stall(delay: Long = 5000L) =
|
||||||
}
|
runBlocking {
|
||||||
|
delay(delay)
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Validation
|
// Validation
|
||||||
//
|
//
|
||||||
|
|
||||||
inner class DarksideTestValidator {
|
inner class DarksideTestValidator {
|
||||||
|
fun validateLatestHeight(height: BlockHeight) =
|
||||||
fun validateLatestHeight(height: BlockHeight) = runBlocking<Unit> {
|
runBlocking<Unit> {
|
||||||
val info = synchronizer.processorInfo.first()
|
val info = synchronizer.processorInfo.first()
|
||||||
val networkBlockHeight = info.networkBlockHeight
|
val networkBlockHeight = info.networkBlockHeight
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"Expected latestHeight of $height but the server last reported a height of" +
|
"Expected latestHeight of $height but the server last reported a height of" +
|
||||||
" $networkBlockHeight! Full details: $info",
|
" $networkBlockHeight! Full details: $info",
|
||||||
networkBlockHeight == height
|
networkBlockHeight == height
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
fun validateMinHeightSynced(minHeight: BlockHeight) = runBlocking<Unit> {
|
fun validateMinHeightSynced(minHeight: BlockHeight) = runBlocking<Unit> {
|
||||||
|
@ -182,7 +186,10 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
assertEquals("Expected $count transactions but found $txCount instead!", count, txCount)
|
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
|
val balance = synchronizer.saplingBalances.value
|
||||||
if (available > 0) {
|
if (available > 0) {
|
||||||
assertTrue(
|
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)
|
val balance = synchronizer.processor.getBalanceInfo(account)
|
||||||
if (available > 0) {
|
if (available > 0) {
|
||||||
assertEquals("invalid available balance", available, balance.available)
|
assertEquals("invalid available balance", available, balance.available)
|
||||||
|
@ -224,33 +235,47 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
blocksUrl: String,
|
blocksUrl: String,
|
||||||
startHeight: BlockHeight = DEFAULT_START_HEIGHT,
|
startHeight: BlockHeight = DEFAULT_START_HEIGHT,
|
||||||
tipHeight: BlockHeight = startHeight + 100
|
tipHeight: BlockHeight = startHeight + 100
|
||||||
): DarksideChainMaker = apply {
|
): DarksideChainMaker =
|
||||||
darkside
|
apply {
|
||||||
.reset(BlockHeightUnsafe(startHeight.value))
|
darkside
|
||||||
.stageBlocks(blocksUrl)
|
.reset(BlockHeightUnsafe(startHeight.value))
|
||||||
applyTipHeight(tipHeight)
|
.stageBlocks(blocksUrl)
|
||||||
}
|
applyTipHeight(tipHeight)
|
||||||
|
|
||||||
fun stageTransaction(url: String, targetHeight: BlockHeight): DarksideChainMaker = apply {
|
|
||||||
darkside.stageTransactions(url, BlockHeightUnsafe(targetHeight.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
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 stageTransaction(
|
||||||
darkside.stageEmptyBlocks(BlockHeightUnsafe(startHeight.value), count)
|
url: String,
|
||||||
}
|
targetHeight: BlockHeight
|
||||||
|
): DarksideChainMaker =
|
||||||
|
apply {
|
||||||
|
darkside.stageTransactions(url, BlockHeightUnsafe(targetHeight.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
darkside.stageEmptyBlocks(BlockHeightUnsafe(startHeight.value), count)
|
||||||
|
}
|
||||||
|
|
||||||
fun stageEmptyBlock() = stageEmptyBlocks(lastTipHeight!! + 1, 1)
|
fun stageEmptyBlock() = stageEmptyBlocks(lastTipHeight!! + 1, 1)
|
||||||
|
|
||||||
fun applyTipHeight(tipHeight: BlockHeight): DarksideChainMaker = apply {
|
fun applyTipHeight(tipHeight: BlockHeight): DarksideChainMaker =
|
||||||
darkside.applyBlocks(BlockHeightUnsafe(tipHeight.value))
|
apply {
|
||||||
lastTipHeight = tipHeight
|
darkside.applyBlocks(BlockHeightUnsafe(tipHeight.value))
|
||||||
}
|
lastTipHeight = tipHeight
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a chain with 100 blocks and a transaction in the middle.
|
* Creates a chain with 100 blocks and a transaction in the middle.
|
||||||
|
@ -281,7 +306,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength", "UnusedPrivateProperty")
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* This is a special localhost value on the Android emulator, which allows it to contact
|
* 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"
|
const val COMPUTER_LOCALHOST = "10.0.2.2"
|
||||||
|
|
||||||
// Block URLS
|
// 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"
|
"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"
|
"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"
|
"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 val DEFAULT_START_HEIGHT = BlockHeight.new(ZcashNetwork.Mainnet, 663150)
|
||||||
private const val DEFAULT_SEED_PHRASE =
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,15 +39,16 @@ open class DarksideTestPrerequisites {
|
||||||
* ApplicationInfo object (`BuildInfo` is useless for libraries.)
|
* ApplicationInfo object (`BuildInfo` is useless for libraries.)
|
||||||
*/
|
*/
|
||||||
private fun isDebuggable(context: Context): Boolean {
|
private fun isDebuggable(context: Context): Boolean {
|
||||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val packageInfo =
|
||||||
context.packageManager.getPackageInfo(
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
context.packageName,
|
context.packageManager.getPackageInfo(
|
||||||
PackageManager.PackageInfoFlags.of(0L)
|
context.packageName,
|
||||||
)
|
PackageManager.PackageInfoFlags.of(0L)
|
||||||
} else {
|
)
|
||||||
@Suppress("Deprecation")
|
} else {
|
||||||
context.packageManager.getPackageInfo(context.packageName, 0)
|
@Suppress("Deprecation")
|
||||||
}
|
context.packageManager.getPackageInfo(context.packageName, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// Normally shouldn't be null, but could be with a MockContext
|
// Normally shouldn't be null, but could be with a MockContext
|
||||||
return packageInfo.applicationInfo?.let {
|
return packageInfo.applicationInfo?.let {
|
||||||
|
|
|
@ -26,21 +26,27 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisi
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun start() {
|
fun start() {
|
||||||
testScope = CoroutineScope(
|
testScope =
|
||||||
Job(classScope.coroutineContext[Job]!!) + newFixedThreadPoolContext(
|
CoroutineScope(
|
||||||
5,
|
Job(classScope.coroutineContext[Job]!!) +
|
||||||
this.javaClass.simpleName
|
newFixedThreadPoolContext(
|
||||||
|
5,
|
||||||
|
this.javaClass.simpleName
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun end() = runBlocking<Unit> {
|
fun end() =
|
||||||
testScope.cancel()
|
runBlocking<Unit> {
|
||||||
testScope.coroutineContext[Job]?.join()
|
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 {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -49,20 +55,26 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisi
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun createScope() {
|
fun createScope() {
|
||||||
classScope = CoroutineScope(
|
classScope =
|
||||||
SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName)
|
CoroutineScope(
|
||||||
)
|
SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun destroyScope() = runBlocking<Unit> {
|
fun destroyScope() =
|
||||||
classScope.cancel()
|
runBlocking<Unit> {
|
||||||
classScope.coroutineContext[Job]?.join()
|
classScope.cancel()
|
||||||
}
|
classScope.coroutineContext[Job]?.join()
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun timeoutWith(scope: CoroutineScope, duration: Long, block: suspend () -> Unit) {
|
fun timeoutWith(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
duration: Long,
|
||||||
|
block: suspend () -> Unit
|
||||||
|
) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
delay(duration)
|
delay(duration)
|
||||||
val message = "ERROR: Test timed out after ${duration}ms"
|
val message = "ERROR: Test timed out after ${duration}ms"
|
||||||
|
|
|
@ -10,11 +10,18 @@ import java.util.Locale
|
||||||
|
|
||||||
class SimpleMnemonics : MnemonicPlugin {
|
class SimpleMnemonics : MnemonicPlugin {
|
||||||
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
||||||
|
|
||||||
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
||||||
|
|
||||||
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
||||||
|
|
||||||
override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars
|
override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars
|
||||||
|
|
||||||
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
||||||
|
|
||||||
override fun nextMnemonicList(seed: ByteArray): List<CharArray> = MnemonicCode(seed).words
|
override fun nextMnemonicList(seed: ByteArray): List<CharArray> = MnemonicCode(seed).words
|
||||||
|
|
||||||
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
||||||
|
|
||||||
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,9 +48,10 @@ class TestWallet(
|
||||||
alias = alias
|
alias = alias
|
||||||
)
|
)
|
||||||
|
|
||||||
val walletScope = CoroutineScope(
|
val walletScope =
|
||||||
SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName)
|
CoroutineScope(
|
||||||
)
|
SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName)
|
||||||
|
)
|
||||||
|
|
||||||
// Although runBlocking isn't great, this usage is OK because this is only used within the
|
// Although runBlocking isn't great, this usage is OK because this is only used within the
|
||||||
// automated tests
|
// automated tests
|
||||||
|
@ -60,16 +61,17 @@ class TestWallet(
|
||||||
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||||
private val shieldedSpendingKey =
|
private val shieldedSpendingKey =
|
||||||
runBlocking { DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, network = network, account) }
|
runBlocking { DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, network = network, account) }
|
||||||
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
|
val synchronizer: SdkSynchronizer =
|
||||||
context,
|
Synchronizer.newBlocking(
|
||||||
network,
|
context,
|
||||||
alias,
|
network,
|
||||||
endpoint,
|
alias,
|
||||||
seed,
|
endpoint,
|
||||||
startHeight,
|
seed,
|
||||||
// Using existing wallet init mode as simplification for the test
|
startHeight,
|
||||||
walletInitMode = WalletInitMode.ExistingWallet
|
// Using existing wallet init mode as simplification for the test
|
||||||
) as SdkSynchronizer
|
walletInitMode = WalletInitMode.ExistingWallet
|
||||||
|
) as SdkSynchronizer
|
||||||
|
|
||||||
val available get() = synchronizer.saplingBalances.value?.available
|
val available get() = synchronizer.saplingBalances.value?.available
|
||||||
val unifiedAddress =
|
val unifiedAddress =
|
||||||
|
@ -85,12 +87,13 @@ class TestWallet(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sync(timeout: Long = -1): TestWallet {
|
suspend fun sync(timeout: Long = -1): TestWallet {
|
||||||
val killSwitch = walletScope.launch {
|
val killSwitch =
|
||||||
if (timeout > 0) {
|
walletScope.launch {
|
||||||
delay(timeout)
|
if (timeout > 0) {
|
||||||
throw TimeoutException("Failed to sync wallet within ${timeout}ms")
|
delay(timeout)
|
||||||
|
throw TimeoutException("Failed to sync wallet within ${timeout}ms")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// block until synced
|
// block until synced
|
||||||
synchronizer.status.first { it == Synchronizer.Status.SYNCED }
|
synchronizer.status.first { it == Synchronizer.Status.SYNCED }
|
||||||
|
|
|
@ -20,6 +20,9 @@ import java.util.regex.Pattern
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.seconds
|
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
|
* 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.
|
* 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
|
* 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.
|
* 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() {
|
class StartupBenchmark : UiTestPrerequisites() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val APP_TARGET_PACKAGE_NAME = "cash.z.ecc.android.sdk.demoapp.mainnet" // NON-NLS
|
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
|
private const val APP_TARGET_ACTIVITY_NAME = "cash.z.ecc.android.sdk.demoapp.MainActivity" // NON-NLS
|
||||||
|
@ -55,18 +54,19 @@ class StartupBenchmark : UiTestPrerequisites() {
|
||||||
* This test starts the Demo-app on Home screen and measures its metrics.
|
* This test starts the Demo-app on Home screen and measures its metrics.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun appStartup() = benchmarkRule.measureRepeated(
|
fun appStartup() =
|
||||||
packageName = APP_TARGET_PACKAGE_NAME,
|
benchmarkRule.measureRepeated(
|
||||||
metrics = listOf(StartupTimingMetric()),
|
packageName = APP_TARGET_PACKAGE_NAME,
|
||||||
iterations = 5,
|
metrics = listOf(StartupTimingMetric()),
|
||||||
startupMode = StartupMode.COLD,
|
iterations = 5,
|
||||||
setupBlock = {
|
startupMode = StartupMode.COLD,
|
||||||
// Press home button before each run to ensure the starting activity isn't visible
|
setupBlock = {
|
||||||
pressHome()
|
// Press home button before each run to ensure the starting activity isn't visible
|
||||||
|
pressHome()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
startLegacyActivityAndWait()
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
startLegacyActivityAndWait()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advanced trace events startup test, which starts the Demo-app on the Home screen and then navigates to the
|
* Advanced trace events startup test, which starts the Demo-app on the Home screen and then navigates to the
|
||||||
|
@ -74,24 +74,26 @@ class StartupBenchmark : UiTestPrerequisites() {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@OptIn(ExperimentalMetricApi::class)
|
@OptIn(ExperimentalMetricApi::class)
|
||||||
fun tracesSdkStartup() = benchmarkRule.measureRepeated(
|
fun tracesSdkStartup() =
|
||||||
packageName = APP_TARGET_PACKAGE_NAME,
|
benchmarkRule.measureRepeated(
|
||||||
metrics = listOf(
|
packageName = APP_TARGET_PACKAGE_NAME,
|
||||||
TraceSectionMetric(ADDRESS_SCREEN_SECTION, TraceSectionMetric.Mode.First, false),
|
metrics =
|
||||||
TraceSectionMetric(UNIFIED_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false),
|
listOf(
|
||||||
TraceSectionMetric(SAPLING_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false),
|
TraceSectionMetric(ADDRESS_SCREEN_SECTION, TraceSectionMetric.Mode.First, false),
|
||||||
TraceSectionMetric(TRANSPARENT_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false)
|
TraceSectionMetric(UNIFIED_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false),
|
||||||
),
|
TraceSectionMetric(SAPLING_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false),
|
||||||
compilationMode = CompilationMode.Full(),
|
TraceSectionMetric(TRANSPARENT_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false)
|
||||||
startupMode = StartupMode.COLD,
|
),
|
||||||
iterations = 5,
|
compilationMode = CompilationMode.Full(),
|
||||||
measureBlock = {
|
startupMode = StartupMode.COLD,
|
||||||
startLegacyActivityAndWait()
|
iterations = 5,
|
||||||
gotoAddressScreen()
|
measureBlock = {
|
||||||
waitForAddressScreen()
|
startLegacyActivityAndWait()
|
||||||
closeAddressScreen()
|
gotoAddressScreen()
|
||||||
}
|
waitForAddressScreen()
|
||||||
)
|
closeAddressScreen()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
private fun MacrobenchmarkScope.closeAddressScreen() {
|
private fun MacrobenchmarkScope.closeAddressScreen() {
|
||||||
// To close the Address screen and disconnect from SDK Synchronizer
|
// To close the Address screen and disconnect from SDK Synchronizer
|
||||||
|
@ -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)
|
return device.waitFor(Until.hasObject(By.text(addressPattern)), timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,9 +130,10 @@ class StartupBenchmark : UiTestPrerequisites() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MacrobenchmarkScope.startLegacyActivityAndWait() {
|
private fun MacrobenchmarkScope.startLegacyActivityAndWait() {
|
||||||
val intent = Intent(Intent.ACTION_MAIN).apply {
|
val intent =
|
||||||
component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME)
|
Intent(Intent.ACTION_MAIN).apply {
|
||||||
}
|
component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
startActivityAndWait(intent)
|
startActivityAndWait(intent)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ import org.junit.Test
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
import kotlin.time.Duration.Companion.seconds
|
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
|
* 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
|
* 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
|
* 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.
|
* 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() {
|
class SyncBlockchainBenchmark : UiTestPrerequisites() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val APP_TARGET_PACKAGE_NAME = "cash.z.ecc.android.sdk.demoapp.mainnet" // NON-NLS
|
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
|
private const val APP_TARGET_ACTIVITY_NAME = "cash.z.ecc.android.sdk.demoapp.MainActivity" // NON-NLS
|
||||||
|
@ -54,26 +53,28 @@ class SyncBlockchainBenchmark : UiTestPrerequisites() {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@OptIn(ExperimentalMetricApi::class)
|
@OptIn(ExperimentalMetricApi::class)
|
||||||
fun tracesSyncBlockchain() = benchmarkRule.measureRepeated(
|
fun tracesSyncBlockchain() =
|
||||||
packageName = APP_TARGET_PACKAGE_NAME,
|
benchmarkRule.measureRepeated(
|
||||||
metrics = listOf(
|
packageName = APP_TARGET_PACKAGE_NAME,
|
||||||
TraceSectionMetric(BALANCE_SCREEN_SECTION, TraceSectionMetric.Mode.First, false),
|
metrics =
|
||||||
TraceSectionMetric(BLOCKCHAIN_SYNC_SECTION, TraceSectionMetric.Mode.First, false),
|
listOf(
|
||||||
TraceSectionMetric(DOWNLOAD_SECTION, TraceSectionMetric.Mode.First, false),
|
TraceSectionMetric(BALANCE_SCREEN_SECTION, TraceSectionMetric.Mode.First, false),
|
||||||
TraceSectionMetric(VALIDATION_SECTION, TraceSectionMetric.Mode.First, false),
|
TraceSectionMetric(BLOCKCHAIN_SYNC_SECTION, TraceSectionMetric.Mode.First, false),
|
||||||
TraceSectionMetric(SCAN_SECTION, TraceSectionMetric.Mode.First, false)
|
TraceSectionMetric(DOWNLOAD_SECTION, TraceSectionMetric.Mode.First, false),
|
||||||
),
|
TraceSectionMetric(VALIDATION_SECTION, TraceSectionMetric.Mode.First, false),
|
||||||
compilationMode = CompilationMode.Full(),
|
TraceSectionMetric(SCAN_SECTION, TraceSectionMetric.Mode.First, false)
|
||||||
startupMode = StartupMode.COLD,
|
),
|
||||||
iterations = 3,
|
compilationMode = CompilationMode.Full(),
|
||||||
measureBlock = {
|
startupMode = StartupMode.COLD,
|
||||||
startLegacyActivityAndWait()
|
iterations = 3,
|
||||||
resetSDK()
|
measureBlock = {
|
||||||
gotoBalanceScreen()
|
startLegacyActivityAndWait()
|
||||||
waitForBalanceScreen()
|
resetSDK()
|
||||||
closeBalanceScreen()
|
gotoBalanceScreen()
|
||||||
}
|
waitForBalanceScreen()
|
||||||
)
|
closeBalanceScreen()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// TODO [#808]: Add demo-ui-lib module (and reference the hardcoded texts here)
|
// TODO [#808]: Add demo-ui-lib module (and reference the hardcoded texts here)
|
||||||
// TODO [#808]: https://github.com/zcash/zcash-android-wallet-sdk/issues/808
|
// TODO [#808]: https://github.com/zcash/zcash-android-wallet-sdk/issues/808
|
||||||
|
@ -103,9 +104,10 @@ class SyncBlockchainBenchmark : UiTestPrerequisites() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MacrobenchmarkScope.startLegacyActivityAndWait() {
|
private fun MacrobenchmarkScope.startLegacyActivityAndWait() {
|
||||||
val intent = Intent(Intent.ACTION_MAIN).apply {
|
val intent =
|
||||||
component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME)
|
Intent(Intent.ACTION_MAIN).apply {
|
||||||
}
|
component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
startActivityAndWait(intent)
|
startActivityAndWait(intent)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,11 @@ import androidx.test.core.app.ApplicationProvider
|
||||||
|
|
||||||
fun getAppContext(): Context = ApplicationProvider.getApplicationContext()
|
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) =
|
fun getStringResourceWithArgs(
|
||||||
getAppContext().getString(resId, *formatArgs)
|
@StringRes resId: Int,
|
||||||
|
vararg formatArgs: String
|
||||||
|
) = getAppContext().getString(resId, *formatArgs)
|
||||||
|
|
|
@ -6,10 +6,16 @@ import androidx.test.uiautomator.UiDevice
|
||||||
import androidx.test.uiautomator.UiObject2
|
import androidx.test.uiautomator.UiObject2
|
||||||
import kotlin.time.Duration
|
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)
|
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)
|
return clickAndWait(condition, timeout.inWholeMilliseconds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,9 @@ open class UiTestPrerequisites {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isScreenOn(): Boolean {
|
private fun isScreenOn(): Boolean {
|
||||||
val powerService = ApplicationProvider.getApplicationContext<Context>()
|
val powerService =
|
||||||
.getSystemService(Context.POWER_SERVICE) as PowerManager
|
ApplicationProvider.getApplicationContext<Context>()
|
||||||
|
.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
return powerService.isInteractive
|
return powerService.isInteractive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ open class UiTestPrerequisites {
|
||||||
val keyguardService = (
|
val keyguardService = (
|
||||||
ApplicationProvider.getApplicationContext<Context>()
|
ApplicationProvider.getApplicationContext<Context>()
|
||||||
.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||||
)
|
)
|
||||||
|
|
||||||
return keyguardService.isKeyguardLocked
|
return keyguardService.isKeyguardLocked
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.junit.Test
|
||||||
* https://github.com/EdgeApp/eosjs-node-cli/blob/paul/cleanup/app.js
|
* https://github.com/EdgeApp/eosjs-node-cli/blob/paul/cleanup/app.js
|
||||||
*/
|
*/
|
||||||
class SampleCodeTest {
|
class SampleCodeTest {
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////
|
||||||
// Seed derivation
|
// Seed derivation
|
||||||
@Ignore("This test is not implemented")
|
@Ignore("This test is not implemented")
|
||||||
|
@ -71,11 +70,12 @@ class SampleCodeTest {
|
||||||
// ///////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////
|
||||||
// Get Address
|
// Get Address
|
||||||
@Test
|
@Test
|
||||||
fun getAddress() = runBlocking {
|
fun getAddress() =
|
||||||
val address = synchronizer.getUnifiedAddress(Account.DEFAULT)
|
runBlocking {
|
||||||
assertFalse(address.isBlank())
|
val address = synchronizer.getUnifiedAddress(Account.DEFAULT)
|
||||||
log("Address: $address")
|
assertFalse(address.isBlank())
|
||||||
}
|
log("Address: $address")
|
||||||
|
}
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////
|
||||||
// Derive address from Extended Full Viewing Key
|
// Derive address from Extended Full Viewing Key
|
||||||
|
@ -87,61 +87,64 @@ class SampleCodeTest {
|
||||||
// Query latest block height
|
// Query latest block height
|
||||||
@Test
|
@Test
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun getLatestBlockHeightTest() = runTest {
|
fun getLatestBlockHeightTest() =
|
||||||
// Test the result, only if there is no server communication problem.
|
runTest {
|
||||||
runCatching {
|
// Test the result, only if there is no server communication problem.
|
||||||
LightWalletClient.new(context, lightwalletdHost).getLatestBlockHeight()
|
runCatching {
|
||||||
}.onFailure {
|
LightWalletClient.new(context, lightwalletdHost).getLatestBlockHeight()
|
||||||
Twig.debug(it) { "Failed to retrieve data" }
|
}.onFailure {
|
||||||
}.onSuccess {
|
Twig.debug(it) { "Failed to retrieve data" }
|
||||||
assertTrue(it is Response.Success<BlockHeightUnsafe>)
|
}.onSuccess {
|
||||||
Twig.debug { "Latest Block: ${(it as Response.Success<BlockHeightUnsafe>).result}" }
|
assertTrue(it is Response.Success<BlockHeightUnsafe>)
|
||||||
|
Twig.debug { "Latest Block: ${(it as Response.Success<BlockHeightUnsafe>).result}" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////
|
||||||
// Download compact block range
|
// Download compact block range
|
||||||
@Test
|
@Test
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
fun getBlockRange() =
|
||||||
fun getBlockRange() = runTest {
|
runTest {
|
||||||
val blockRange = BlockHeightUnsafe(
|
@Suppress("ktlint:standard:multiline-expression-wrapping")
|
||||||
BlockHeight.new(
|
val blockRange =
|
||||||
ZcashNetwork.Mainnet,
|
BlockHeightUnsafe(
|
||||||
500_000
|
BlockHeight.new(
|
||||||
).value
|
ZcashNetwork.Mainnet,
|
||||||
)..BlockHeightUnsafe(
|
500_000
|
||||||
(
|
).value
|
||||||
BlockHeight.new(
|
)..BlockHeightUnsafe(
|
||||||
ZcashNetwork.Mainnet,
|
(
|
||||||
500_009
|
BlockHeight.new(
|
||||||
).value
|
ZcashNetwork.Mainnet,
|
||||||
|
500_009
|
||||||
|
).value
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
val lightWalletClient = LightWalletClient.new(context, lightwalletdHost)
|
val lightWalletClient = LightWalletClient.new(context, lightwalletdHost)
|
||||||
|
|
||||||
// Test the result, only if there is no server communication problem.
|
// Test the result, only if there is no server communication problem.
|
||||||
runCatching {
|
runCatching {
|
||||||
lightWalletClient.getBlockRange(blockRange)
|
lightWalletClient.getBlockRange(blockRange)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Twig.debug(it) { "Failed to retrieve data" }
|
Twig.debug(it) { "Failed to retrieve data" }
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
it.onEach { response ->
|
it.onEach { response ->
|
||||||
assert(response is Response.Success) { "Server communication failed." }
|
assert(response is Response.Success) { "Server communication failed." }
|
||||||
}
|
|
||||||
.filterIsInstance<Response.Success<CompactBlockUnsafe>>()
|
|
||||||
.map { response ->
|
|
||||||
response.result
|
|
||||||
}.toList()
|
|
||||||
.also { blocks ->
|
|
||||||
assertEquals(blockRange.endInclusive.value - blockRange.start.value, blocks.count())
|
|
||||||
|
|
||||||
blocks.forEachIndexed { i, block ->
|
|
||||||
log("Block #$i: height:${block.height} hash:${block.hash.toHex()}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.filterIsInstance<Response.Success<CompactBlockUnsafe>>()
|
||||||
|
.map { response ->
|
||||||
|
response.result
|
||||||
|
}.toList()
|
||||||
|
.also { blocks ->
|
||||||
|
assertEquals(blockRange.endInclusive.value - blockRange.start.value, blocks.count())
|
||||||
|
|
||||||
|
blocks.forEachIndexed { i, block ->
|
||||||
|
log("Block #$i: height:${block.height} hash:${block.hash.toHex()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////
|
||||||
// Query account outgoing transactions
|
// Query account outgoing transactions
|
||||||
|
@ -175,17 +178,19 @@ class SampleCodeTest {
|
||||||
// ///////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////
|
||||||
// Create a signed transaction (with memo) and broadcast
|
// Create a signed transaction (with memo) and broadcast
|
||||||
@Test
|
@Test
|
||||||
fun submitTransaction() = runBlocking {
|
fun submitTransaction() =
|
||||||
val amount = 0.123.convertZecToZatoshi()
|
runBlocking {
|
||||||
val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
|
val amount = 0.123.convertZecToZatoshi()
|
||||||
val memo = "Test Transaction"
|
val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
|
||||||
val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(
|
val memo = "Test Transaction"
|
||||||
seed,
|
val spendingKey =
|
||||||
ZcashNetwork.Mainnet,
|
DerivationTool.getInstance().deriveUnifiedSpendingKey(
|
||||||
Account.DEFAULT
|
seed,
|
||||||
)
|
ZcashNetwork.Mainnet,
|
||||||
synchronizer.sendToAddress(spendingKey, amount, address, memo)
|
Account.DEFAULT
|
||||||
}
|
)
|
||||||
|
synchronizer.sendToAddress(spendingKey, amount, address, memo)
|
||||||
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////
|
||||||
// Utility Functions
|
// Utility Functions
|
||||||
|
@ -196,18 +201,19 @@ class SampleCodeTest {
|
||||||
private val lightwalletdHost = LightWalletEndpoint.Mainnet
|
private val lightwalletdHost = LightWalletEndpoint.Mainnet
|
||||||
|
|
||||||
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
private val synchronizer: Synchronizer = run {
|
private val synchronizer: Synchronizer =
|
||||||
val network = ZcashNetwork.fromResources(context)
|
run {
|
||||||
Synchronizer.newBlocking(
|
val network = ZcashNetwork.fromResources(context)
|
||||||
context,
|
Synchronizer.newBlocking(
|
||||||
network,
|
context,
|
||||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
network,
|
||||||
seed = seed,
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||||
birthday = null,
|
seed = seed,
|
||||||
// Using existing wallet init mode as simplification for the test
|
birthday = null,
|
||||||
walletInitMode = WalletInitMode.ExistingWallet
|
// Using existing wallet init mode as simplification for the test
|
||||||
)
|
walletInitMode = WalletInitMode.ExistingWallet
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun log(message: String?) = Twig.debug { message ?: "null" }
|
fun log(message: String?) = Twig.debug { message ?: "null" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.flowOf
|
||||||
*/
|
*/
|
||||||
class MockPreferenceProvider(mutableMapFactory: () -> MutableMap<String, String?> = { mutableMapOf() }) :
|
class MockPreferenceProvider(mutableMapFactory: () -> MutableMap<String, String?> = { mutableMapOf() }) :
|
||||||
PreferenceProvider {
|
PreferenceProvider {
|
||||||
|
|
||||||
private val map = mutableMapFactory()
|
private val map = mutableMapFactory()
|
||||||
|
|
||||||
override suspend fun getString(key: Key) = map[key.key]
|
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 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
|
map[key.key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
|
||||||
|
|
||||||
object BooleanPreferenceDefaultFixture {
|
object BooleanPreferenceDefaultFixture {
|
||||||
val KEY = Key("some_boolean_key") // $NON-NLS
|
val KEY = Key("some_boolean_key") // $NON-NLS
|
||||||
|
|
||||||
fun newTrue() = BooleanPreferenceDefault(KEY, true)
|
fun newTrue() = BooleanPreferenceDefault(KEY, true)
|
||||||
|
|
||||||
fun newFalse() = BooleanPreferenceDefault(KEY, false)
|
fun newFalse() = BooleanPreferenceDefault(KEY, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,9 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
|
||||||
object IntegerPreferenceDefaultFixture {
|
object IntegerPreferenceDefaultFixture {
|
||||||
val KEY = Key("some_string_key") // $NON-NLS
|
val KEY = Key("some_string_key") // $NON-NLS
|
||||||
const val DEFAULT_VALUE = 123
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,9 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.StringPreferenceDef
|
||||||
object StringDefaultPreferenceFixture {
|
object StringDefaultPreferenceFixture {
|
||||||
val KEY = Key("some_string_key") // $NON-NLS
|
val KEY = Key("some_string_key") // $NON-NLS
|
||||||
const val DEFAULT_VALUE = "some_default_value" // $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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,32 +16,38 @@ class BooleanPreferenceDefaultTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun value_default_true() = runTest {
|
fun value_default_true() =
|
||||||
val entry = BooleanPreferenceDefaultFixture.newTrue()
|
runTest {
|
||||||
assertTrue(entry.getValue(MockPreferenceProvider()))
|
val entry = BooleanPreferenceDefaultFixture.newTrue()
|
||||||
}
|
assertTrue(entry.getValue(MockPreferenceProvider()))
|
||||||
|
|
||||||
@Test
|
|
||||||
fun value_default_false() = runTest {
|
|
||||||
val entry = BooleanPreferenceDefaultFixture.newFalse()
|
|
||||||
assertFalse(entry.getValue(MockPreferenceProvider()))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun value_from_config_false() = runTest {
|
|
||||||
val entry = BooleanPreferenceDefaultFixture.newTrue()
|
|
||||||
val mockPreferenceProvider = MockPreferenceProvider {
|
|
||||||
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to false.toString())
|
|
||||||
}
|
}
|
||||||
assertFalse(entry.getValue(mockPreferenceProvider))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun value_from_config_true() = runTest {
|
fun value_default_false() =
|
||||||
val entry = BooleanPreferenceDefaultFixture.newTrue()
|
runTest {
|
||||||
val mockPreferenceProvider = MockPreferenceProvider {
|
val entry = BooleanPreferenceDefaultFixture.newFalse()
|
||||||
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to true.toString())
|
assertFalse(entry.getValue(MockPreferenceProvider()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun value_from_config_false() =
|
||||||
|
runTest {
|
||||||
|
val entry = BooleanPreferenceDefaultFixture.newTrue()
|
||||||
|
val mockPreferenceProvider =
|
||||||
|
MockPreferenceProvider {
|
||||||
|
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to false.toString())
|
||||||
|
}
|
||||||
|
assertFalse(entry.getValue(mockPreferenceProvider))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun value_from_config_true() =
|
||||||
|
runTest {
|
||||||
|
val entry = BooleanPreferenceDefaultFixture.newTrue()
|
||||||
|
val mockPreferenceProvider =
|
||||||
|
MockPreferenceProvider {
|
||||||
|
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to true.toString())
|
||||||
|
}
|
||||||
|
assertTrue(entry.getValue(mockPreferenceProvider))
|
||||||
}
|
}
|
||||||
assertTrue(entry.getValue(mockPreferenceProvider))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,20 +15,23 @@ class IntegerPreferenceDefaultTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun value_default() = runTest {
|
fun value_default() =
|
||||||
val entry = IntegerPreferenceDefaultFixture.new()
|
runTest {
|
||||||
assertEquals(IntegerPreferenceDefaultFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
|
val entry = IntegerPreferenceDefaultFixture.new()
|
||||||
}
|
assertEquals(IntegerPreferenceDefaultFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
|
||||||
|
|
||||||
@Test
|
|
||||||
fun value_override() = runTest {
|
|
||||||
val expected = IntegerPreferenceDefaultFixture.DEFAULT_VALUE + 5
|
|
||||||
|
|
||||||
val entry = IntegerPreferenceDefaultFixture.new()
|
|
||||||
val mockPreferenceProvider = MockPreferenceProvider {
|
|
||||||
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to expected.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(expected, entry.getValue(mockPreferenceProvider))
|
@Test
|
||||||
}
|
fun value_override() =
|
||||||
|
runTest {
|
||||||
|
val expected = IntegerPreferenceDefaultFixture.DEFAULT_VALUE + 5
|
||||||
|
|
||||||
|
val entry = IntegerPreferenceDefaultFixture.new()
|
||||||
|
val mockPreferenceProvider =
|
||||||
|
MockPreferenceProvider {
|
||||||
|
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to expected.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(expected, entry.getValue(mockPreferenceProvider))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,19 +14,22 @@ class StringPreferenceDefaultTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun value_default() = runTest {
|
fun value_default() =
|
||||||
val entry = StringDefaultPreferenceFixture.new()
|
runTest {
|
||||||
assertEquals(StringDefaultPreferenceFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
|
val entry = StringDefaultPreferenceFixture.new()
|
||||||
}
|
assertEquals(StringDefaultPreferenceFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
|
||||||
|
|
||||||
@Test
|
|
||||||
fun value_override() = runTest {
|
|
||||||
val entry = StringDefaultPreferenceFixture.new()
|
|
||||||
|
|
||||||
val mockPreferenceProvider = MockPreferenceProvider {
|
|
||||||
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to "override")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals("override", entry.getValue(mockPreferenceProvider))
|
@Test
|
||||||
}
|
fun value_override() =
|
||||||
|
runTest {
|
||||||
|
val entry = StringDefaultPreferenceFixture.new()
|
||||||
|
|
||||||
|
val mockPreferenceProvider =
|
||||||
|
MockPreferenceProvider {
|
||||||
|
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to "override")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("override", entry.getValue(mockPreferenceProvider))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,9 @@ open class UiTestPrerequisites {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isScreenOn(): Boolean {
|
private fun isScreenOn(): Boolean {
|
||||||
val powerService = ApplicationProvider.getApplicationContext<Context>()
|
val powerService =
|
||||||
.getSystemService(Context.POWER_SERVICE) as PowerManager
|
ApplicationProvider.getApplicationContext<Context>()
|
||||||
|
.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
return powerService.isInteractive
|
return powerService.isInteractive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ open class UiTestPrerequisites {
|
||||||
val keyguardService = (
|
val keyguardService = (
|
||||||
ApplicationProvider.getApplicationContext<Context>()
|
ApplicationProvider.getApplicationContext<Context>()
|
||||||
.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||||
)
|
)
|
||||||
|
|
||||||
return keyguardService.isKeyguardLocked
|
return keyguardService.isKeyguardLocked
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,52 +17,55 @@ import kotlin.time.TimeMark
|
||||||
import kotlin.time.TimeSource
|
import kotlin.time.TimeSource
|
||||||
|
|
||||||
class FlowExtTest {
|
class FlowExtTest {
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class, ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalTime::class, ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
@SmallTest
|
@SmallTest
|
||||||
fun throttle_one_sec() = runTest {
|
fun throttle_one_sec() =
|
||||||
val timer = TimeSource.Monotonic.markNow()
|
runTest {
|
||||||
val flow = flow {
|
val timer = TimeSource.Monotonic.markNow()
|
||||||
while (timer.elapsedNow() <= 5.seconds) {
|
val flow =
|
||||||
emit(1)
|
flow {
|
||||||
}
|
while (timer.elapsedNow() <= 5.seconds) {
|
||||||
}.throttle(1.seconds)
|
emit(1)
|
||||||
|
}
|
||||||
|
}.throttle(1.seconds)
|
||||||
|
|
||||||
var timeMark: TimeMark? = null
|
var timeMark: TimeMark? = null
|
||||||
flow.collect {
|
flow.collect {
|
||||||
if (timeMark == null) {
|
if (timeMark == null) {
|
||||||
timeMark = TimeSource.Monotonic.markNow()
|
timeMark = TimeSource.Monotonic.markNow()
|
||||||
} else {
|
} else {
|
||||||
assert(timeMark!!.elapsedNow() >= 1.seconds)
|
assert(timeMark!!.elapsedNow() >= 1.seconds)
|
||||||
timeMark = TimeSource.Monotonic.markNow()
|
timeMark = TimeSource.Monotonic.markNow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
@OptIn(ExperimentalTime::class)
|
||||||
private fun raceConditionTest(duration: Duration): Boolean = runBlocking {
|
private fun raceConditionTest(duration: Duration): Boolean =
|
||||||
val flow = (0..1000).asFlow().throttle(duration)
|
runBlocking {
|
||||||
|
val flow = (0..1000).asFlow().throttle(duration)
|
||||||
|
|
||||||
val values = mutableListOf<Int>()
|
val values = mutableListOf<Int>()
|
||||||
flow.collect {
|
flow.collect {
|
||||||
values.add(it)
|
values.add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@runBlocking values.zipWithNext().all { it.first <= it.second }
|
||||||
}
|
}
|
||||||
|
|
||||||
return@runBlocking values.zipWithNext().all { it.first <= it.second }
|
|
||||||
}
|
|
||||||
|
|
||||||
@FlakyTest
|
@FlakyTest
|
||||||
@Test
|
@Test
|
||||||
fun stressTest() = runBlocking {
|
fun stressTest() =
|
||||||
for (i in 0..10) {
|
runBlocking {
|
||||||
assertTrue { raceConditionTest(0.001.seconds) }
|
for (i in 0..10) {
|
||||||
|
assertTrue { raceConditionTest(0.001.seconds) }
|
||||||
|
}
|
||||||
|
for (i in 0..10) {
|
||||||
|
assertTrue { raceConditionTest(0.0001.seconds) }
|
||||||
|
}
|
||||||
|
for (i in 0..10) {
|
||||||
|
assertTrue { raceConditionTest(0.00001.seconds) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (i in 0..10) {
|
|
||||||
assertTrue { raceConditionTest(0.0001.seconds) }
|
|
||||||
}
|
|
||||||
for (i in 0..10) {
|
|
||||||
assertTrue { raceConditionTest(0.00001.seconds) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,13 @@ import androidx.annotation.StringRes
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import java.util.Locale
|
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>) =
|
fun getStringResourceWithArgs(
|
||||||
ApplicationProvider.getApplicationContext<Context>().getString(resId, *formatArgs)
|
@StringRes resId: Int,
|
||||||
|
formatArgs: Array<Any>
|
||||||
|
) = ApplicationProvider.getApplicationContext<Context>().getString(resId, *formatArgs)
|
||||||
|
|
||||||
fun isLocaleRTL(locale: Locale) = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL
|
fun isLocaleRTL(locale: Locale) = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL
|
||||||
|
|
|
@ -27,18 +27,18 @@ class ScreenTimeoutTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
fun acquireAndReleaseScreenTimeout() = runTest {
|
fun acquireAndReleaseScreenTimeout() =
|
||||||
val testSetup = TestSetup(composeTestRule)
|
runTest {
|
||||||
|
val testSetup = TestSetup(composeTestRule)
|
||||||
|
|
||||||
assertEquals(1, testSetup.getScreenTimeoutCount())
|
assertEquals(1, testSetup.getScreenTimeoutCount())
|
||||||
|
|
||||||
testSetup.mutableScreenTimeoutFlag.update { false }
|
testSetup.mutableScreenTimeoutFlag.update { false }
|
||||||
composeTestRule.awaitIdle()
|
composeTestRule.awaitIdle()
|
||||||
assertEquals(0, testSetup.getScreenTimeoutCount())
|
assertEquals(0, testSetup.getScreenTimeoutCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSetup(composeTestRule: ComposeContentTestRule) {
|
private class TestSetup(composeTestRule: ComposeContentTestRule) {
|
||||||
|
|
||||||
val mutableScreenTimeoutFlag = MutableStateFlow(true)
|
val mutableScreenTimeoutFlag = MutableStateFlow(true)
|
||||||
|
|
||||||
private val screenTimeout = ScreenTimeout()
|
private val screenTimeout = ScreenTimeout()
|
||||||
|
@ -58,6 +58,7 @@ class ScreenTimeoutTest : UiTestPrerequisites() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun TestView(disableScreenTimeout: Boolean) {
|
private fun TestView(disableScreenTimeout: Boolean) {
|
||||||
if (disableScreenTimeout) {
|
if (disableScreenTimeout) {
|
||||||
DisableScreenTimeout()
|
DisableScreenTimeout()
|
||||||
|
|
|
@ -4,7 +4,6 @@ import androidx.multidex.MultiDexApplication
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
|
|
||||||
class App : MultiDexApplication() {
|
class App : MultiDexApplication() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
|
abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since the lightwalletClient is not a component that apps typically use, directly, we provide
|
* 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
|
* 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.
|
* 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)
|
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
|
||||||
.setPrimaryClip(ClipData.newPlainText("DemoAppClip", text))
|
.setPrimaryClip(ClipData.newPlainText("DemoAppClip", text))
|
||||||
toast(description)
|
toast(description)
|
||||||
|
|
|
@ -33,6 +33,7 @@ class ComposeActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun MainContent() {
|
private fun MainContent() {
|
||||||
when (walletViewModel.secretState.collectAsStateWithLifecycle().value) {
|
when (walletViewModel.secretState.collectAsStateWithLifecycle().value) {
|
||||||
SecretState.Loading -> {
|
SecretState.Loading -> {
|
||||||
|
|
|
@ -63,14 +63,15 @@ class MainActivity :
|
||||||
val navController = findNavController(R.id.nav_host_fragment)
|
val navController = findNavController(R.id.nav_host_fragment)
|
||||||
// Passing each menu ID as a set of Ids because each
|
// Passing each menu ID as a set of Ids because each
|
||||||
// menu should be considered as top level destinations.
|
// menu should be considered as top level destinations.
|
||||||
appBarConfiguration = AppBarConfiguration(
|
appBarConfiguration =
|
||||||
setOf(
|
AppBarConfiguration(
|
||||||
R.id.nav_home, R.id.nav_address, R.id.nav_balance, R.id.nav_block, R.id.nav_private_key,
|
setOf(
|
||||||
R.id.nav_latest_height, R.id.nav_block_range,
|
R.id.nav_home, R.id.nav_address, R.id.nav_balance, R.id.nav_block, R.id.nav_private_key,
|
||||||
R.id.nav_transactions, R.id.nav_utxos, R.id.nav_send
|
R.id.nav_latest_height, R.id.nav_block_range,
|
||||||
),
|
R.id.nav_transactions, R.id.nav_utxos, R.id.nav_send
|
||||||
drawerLayout
|
),
|
||||||
)
|
drawerLayout
|
||||||
|
)
|
||||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||||
navView.setupWithNavController(navController)
|
navView.setupWithNavController(navController)
|
||||||
drawerLayout.addDrawerListener(this)
|
drawerLayout.addDrawerListener(this)
|
||||||
|
@ -127,10 +128,11 @@ class MainActivity :
|
||||||
lightwalletClient?.shutdown()
|
lightwalletClient?.shutdown()
|
||||||
}
|
}
|
||||||
val network = ZcashNetwork.fromResources(applicationContext)
|
val network = ZcashNetwork.fromResources(applicationContext)
|
||||||
lightwalletClient = LightWalletClient.new(
|
lightwalletClient =
|
||||||
applicationContext,
|
LightWalletClient.new(
|
||||||
LightWalletEndpoint.defaultForNetwork(network)
|
applicationContext,
|
||||||
)
|
LightWalletEndpoint.defaultForNetwork(network)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFabClicked() {
|
private fun onFabClicked() {
|
||||||
|
@ -162,7 +164,10 @@ class MainActivity :
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("EmptyFunctionBlock")
|
@Suppress("EmptyFunctionBlock")
|
||||||
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
|
override fun onDrawerSlide(
|
||||||
|
drawerView: View,
|
||||||
|
slideOffset: Float
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDrawerOpened(drawerView: View) {
|
override fun onDrawerOpened(drawerView: View) {
|
||||||
|
@ -176,9 +181,10 @@ class MainActivity :
|
||||||
|
|
||||||
private fun newBrowserIntent(url: String): Intent {
|
private fun newBrowserIntent(url: String): Intent {
|
||||||
val uri = Uri.parse(url)
|
val uri = Uri.parse(url)
|
||||||
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
|
val intent =
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
Intent(Intent.ACTION_VIEW, uri).apply {
|
||||||
}
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod", "ktlint:standard:function-naming")
|
||||||
internal fun ComposeActivity.Navigation() {
|
internal fun ComposeActivity.Navigation() {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
|
@ -187,10 +187,11 @@ private fun copyToClipboard(
|
||||||
) {
|
) {
|
||||||
val clipboardManager = context.getSystemService(ClipboardManager::class.java)
|
val clipboardManager = context.getSystemService(ClipboardManager::class.java)
|
||||||
|
|
||||||
val data = ClipData.newPlainText(
|
val data =
|
||||||
tag,
|
ClipData.newPlainText(
|
||||||
textToCopy
|
tag,
|
||||||
)
|
textToCopy
|
||||||
|
)
|
||||||
clipboardManager.setPrimaryClip(data)
|
clipboardManager.setPrimaryClip(data)
|
||||||
|
|
||||||
// Notify users with Snackbar only on Android level 32 and lower, as 33 and higher notifies users by its own system
|
// Notify users with Snackbar only on Android level 32 and lower, as 33 and higher notifies users by its own system
|
||||||
|
@ -207,9 +208,10 @@ private fun copyToClipboard(
|
||||||
|
|
||||||
private fun newBrowserIntent(url: String): Intent {
|
private fun newBrowserIntent(url: String): Intent {
|
||||||
val uri = Uri.parse(url)
|
val uri = Uri.parse(url)
|
||||||
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
|
val intent =
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
Intent(Intent.ACTION_VIEW, uri).apply {
|
||||||
}
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,74 +40,78 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
* Shared mutable state for the demo
|
* Shared mutable state for the demo
|
||||||
*/
|
*/
|
||||||
class SharedViewModel(application: Application) : AndroidViewModel(application) {
|
class SharedViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
private val _seedPhrase = MutableStateFlow(DemoConstants.INITIAL_SEED_WORDS)
|
private val _seedPhrase = MutableStateFlow(DemoConstants.INITIAL_SEED_WORDS)
|
||||||
|
|
||||||
private val _blockHeight = MutableStateFlow<BlockHeight?>(
|
private val _birthdayHeight =
|
||||||
runBlocking {
|
MutableStateFlow<BlockHeight?>(
|
||||||
BlockHeight.ofLatestCheckpoint(
|
runBlocking {
|
||||||
getApplication(),
|
BlockHeight.ofLatestCheckpoint(
|
||||||
ZcashNetwork.fromResources(application)
|
getApplication(),
|
||||||
)
|
ZcashNetwork.fromResources(application)
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// publicly, this is read-only
|
// publicly, this is read-only
|
||||||
val seedPhrase: StateFlow<String> get() = _seedPhrase
|
val seedPhrase: StateFlow<String> get() = _seedPhrase
|
||||||
|
|
||||||
// publicly, this is read-only
|
// publicly, this is read-only
|
||||||
val birthdayHeight: StateFlow<BlockHeight?> get() = _blockHeight
|
val birthdayHeight: StateFlow<BlockHeight?> get() = _birthdayHeight
|
||||||
|
|
||||||
private val lockoutMutex = Mutex()
|
private val lockoutMutex = Mutex()
|
||||||
|
|
||||||
private val lockoutIdFlow = MutableStateFlow<UUID?>(null)
|
private val lockoutIdFlow = MutableStateFlow<UUID?>(null)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val synchronizerOrLockout: Flow<InternalSynchronizerStatus> = lockoutIdFlow.flatMapLatest { lockoutId ->
|
private val synchronizerOrLockout: Flow<InternalSynchronizerStatus> =
|
||||||
if (null != lockoutId) {
|
lockoutIdFlow.flatMapLatest { lockoutId ->
|
||||||
flowOf(InternalSynchronizerStatus.Lockout(lockoutId))
|
if (null != lockoutId) {
|
||||||
} else {
|
flowOf(InternalSynchronizerStatus.Lockout(lockoutId))
|
||||||
callbackFlow<InternalSynchronizerStatus> {
|
} else {
|
||||||
// Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already
|
callbackFlow<InternalSynchronizerStatus> {
|
||||||
// have the seed stored
|
// Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already
|
||||||
val seedBytes = Mnemonics.MnemonicCode(seedPhrase.value).toSeed()
|
// have the seed stored
|
||||||
|
val seedBytes = Mnemonics.MnemonicCode(seedPhrase.value).toSeed()
|
||||||
|
|
||||||
val network = ZcashNetwork.fromResources(application)
|
val network = ZcashNetwork.fromResources(application)
|
||||||
val synchronizer = Synchronizer.new(
|
val synchronizer =
|
||||||
application,
|
Synchronizer.new(
|
||||||
network,
|
application,
|
||||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
network,
|
||||||
seed = seedBytes,
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||||
birthday = if (BenchmarkingExt.isBenchmarking()) {
|
seed = seedBytes,
|
||||||
BlockHeight.new(ZcashNetwork.Mainnet, BenchmarkingBlockRangeFixture.new().start)
|
birthday =
|
||||||
} else {
|
if (BenchmarkingExt.isBenchmarking()) {
|
||||||
birthdayHeight.value
|
BlockHeight.new(ZcashNetwork.Mainnet, BenchmarkingBlockRangeFixture.new().start)
|
||||||
},
|
} else {
|
||||||
// We use restore mode as this is always initialization with an older seed
|
birthdayHeight.value
|
||||||
walletInitMode = WalletInitMode.RestoreWallet,
|
},
|
||||||
alias = OLD_UI_SYNCHRONIZER_ALIAS
|
// We use restore mode as this is always initialization with an older seed
|
||||||
)
|
walletInitMode = WalletInitMode.RestoreWallet,
|
||||||
|
alias = OLD_UI_SYNCHRONIZER_ALIAS
|
||||||
|
)
|
||||||
|
|
||||||
send(InternalSynchronizerStatus.Available(synchronizer))
|
send(InternalSynchronizerStatus.Available(synchronizer))
|
||||||
awaitClose {
|
awaitClose {
|
||||||
synchronizer.close()
|
synchronizer.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Note that seed and birthday shouldn't be changed once a synchronizer is first collected
|
// 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?> =
|
||||||
when (it) {
|
synchronizerOrLockout.map {
|
||||||
is InternalSynchronizerStatus.Available -> it.synchronizer
|
when (it) {
|
||||||
is InternalSynchronizerStatus.Lockout -> null
|
is InternalSynchronizerStatus.Available -> it.synchronizer
|
||||||
}
|
is InternalSynchronizerStatus.Lockout -> null
|
||||||
}.stateIn(
|
}
|
||||||
viewModelScope,
|
}.stateIn(
|
||||||
SharingStarted.WhileSubscribed(DEFAULT_ANDROID_STATE_TIMEOUT.inWholeMilliseconds, 0),
|
viewModelScope,
|
||||||
initialValue =
|
SharingStarted.WhileSubscribed(DEFAULT_ANDROID_STATE_TIMEOUT.inWholeMilliseconds, 0),
|
||||||
null
|
initialValue =
|
||||||
)
|
null
|
||||||
|
)
|
||||||
|
|
||||||
fun updateSeedPhrase(newPhrase: String?): Boolean {
|
fun updateSeedPhrase(newPhrase: String?): Boolean {
|
||||||
return if (isValidSeedPhrase(newPhrase)) {
|
return if (isValidSeedPhrase(newPhrase)) {
|
||||||
|
@ -128,11 +132,12 @@ class SharedViewModel(application: Application) : AndroidViewModel(application)
|
||||||
.filterIsInstance<InternalSynchronizerStatus.Lockout>()
|
.filterIsInstance<InternalSynchronizerStatus.Lockout>()
|
||||||
.filter { it.id == lockoutId }
|
.filter { it.id == lockoutId }
|
||||||
.onFirst {
|
.onFirst {
|
||||||
val didDelete = Synchronizer.erase(
|
val didDelete =
|
||||||
appContext = getApplication(),
|
Synchronizer.erase(
|
||||||
network = ZcashNetwork.fromResources(getApplication()),
|
appContext = getApplication(),
|
||||||
alias = OLD_UI_SYNCHRONIZER_ALIAS
|
network = ZcashNetwork.fromResources(getApplication()),
|
||||||
)
|
alias = OLD_UI_SYNCHRONIZER_ALIAS
|
||||||
|
)
|
||||||
Twig.debug { "SDK erase result: $didDelete" }
|
Twig.debug { "SDK erase result: $didDelete" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +167,7 @@ class SharedViewModel(application: Application) : AndroidViewModel(application)
|
||||||
|
|
||||||
private sealed class InternalSynchronizerStatus {
|
private sealed class InternalSynchronizerStatus {
|
||||||
class Available(val synchronizer: Synchronizer) : InternalSynchronizerStatus()
|
class Available(val synchronizer: Synchronizer) : InternalSynchronizerStatus()
|
||||||
|
|
||||||
class Lockout(val id: UUID) : InternalSynchronizerStatus()
|
class Lockout(val id: UUID) : InternalSynchronizerStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.os.StrictMode
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.AndroidApiVersion
|
import cash.z.ecc.android.sdk.demoapp.util.AndroidApiVersion
|
||||||
|
|
||||||
object StrictModeHelper {
|
object StrictModeHelper {
|
||||||
|
|
||||||
fun enableStrictMode() {
|
fun enableStrictMode() {
|
||||||
configureStrictMode()
|
configureStrictMode()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,22 +8,24 @@ import cash.z.ecc.android.sdk.demoapp.util.LazyWithArgument
|
||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.flow
|
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.
|
* A flow of the user's stored wallet. Null indicates that no wallet has been stored.
|
||||||
*/
|
*/
|
||||||
val persistableWalletFlow = flow {
|
val persistableWalletFlow =
|
||||||
// EncryptedPreferenceSingleton.getInstance() is a suspending function, which is why we need
|
flow {
|
||||||
// the flow builder to provide a coroutine context.
|
// EncryptedPreferenceSingleton.getInstance() is a suspending function, which is why we need
|
||||||
val encryptedPreferenceProvider = EncryptedPreferenceSingleton.getInstance(it)
|
// the flow builder to provide a coroutine context.
|
||||||
|
val encryptedPreferenceProvider = EncryptedPreferenceSingleton.getInstance(it)
|
||||||
|
|
||||||
emitAll(EncryptedPreferenceKeys.PERSISTABLE_WALLET.observe(encryptedPreferenceProvider))
|
emitAll(EncryptedPreferenceKeys.PERSISTABLE_WALLET.observe(encryptedPreferenceProvider))
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletCoordinator(
|
||||||
|
context = it,
|
||||||
|
persistableWallet = persistableWalletFlow
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletCoordinator(
|
|
||||||
context = it,
|
|
||||||
persistableWallet = persistableWalletFlow
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun WalletCoordinator.Companion.getInstance(context: Context) = lazy.getInstance(context)
|
fun WalletCoordinator.Companion.getInstance(context: Context) = lazy.getInstance(context)
|
||||||
|
|
|
@ -23,7 +23,6 @@ import kotlinx.coroutines.launch
|
||||||
* that is used, update the `DemoConfig.seedWords` value.
|
* that is used, update the `DemoConfig.seedWords` value.
|
||||||
*/
|
*/
|
||||||
class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
|
class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
|
||||||
|
|
||||||
private lateinit var viewingKey: UnifiedFullViewingKey
|
private lateinit var viewingKey: UnifiedFullViewingKey
|
||||||
|
|
||||||
private fun displayAddress() {
|
private fun displayAddress() {
|
||||||
|
@ -67,7 +66,10 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
|
||||||
reportTraceEvent(ProvideAddressBenchmarkTrace.Event.ADDRESS_SCREEN_START)
|
reportTraceEvent(ProvideAddressBenchmarkTrace.Event.ADDRESS_SCREEN_START)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(
|
||||||
|
view: View,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
displayAddress()
|
displayAddress()
|
||||||
|
|
|
@ -34,7 +34,6 @@ import kotlinx.coroutines.launch
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
||||||
|
|
||||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBalanceBinding =
|
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBalanceBinding =
|
||||||
FragmentGetBalanceBinding.inflate(layoutInflater)
|
FragmentGetBalanceBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
@ -48,7 +47,10 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
||||||
reportTraceEvent(SyncBlockchainBenchmarkTrace.Event.BALANCE_SCREEN_END)
|
reportTraceEvent(SyncBlockchainBenchmarkTrace.Event.BALANCE_SCREEN_END)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(
|
||||||
|
view: View,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val seedPhrase = sharedViewModel.seedPhrase.value
|
val seedPhrase = sharedViewModel.seedPhrase.value
|
||||||
|
@ -110,25 +112,19 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onOrchardBalance(
|
private fun onOrchardBalance(orchardBalance: WalletBalance?) {
|
||||||
orchardBalance: WalletBalance?
|
|
||||||
) {
|
|
||||||
binding.orchardBalance.apply {
|
binding.orchardBalance.apply {
|
||||||
text = orchardBalance.humanString()
|
text = orchardBalance.humanString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSaplingBalance(
|
private fun onSaplingBalance(saplingBalance: WalletBalance?) {
|
||||||
saplingBalance: WalletBalance?
|
|
||||||
) {
|
|
||||||
binding.saplingBalance.apply {
|
binding.saplingBalance.apply {
|
||||||
text = saplingBalance.humanString()
|
text = saplingBalance.humanString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onTransparentBalance(
|
private fun onTransparentBalance(transparentBalance: WalletBalance?) {
|
||||||
transparentBalance: WalletBalance?
|
|
||||||
) {
|
|
||||||
binding.transparentBalance.apply {
|
binding.transparentBalance.apply {
|
||||||
text = transparentBalance.humanString()
|
text = transparentBalance.humanString()
|
||||||
}
|
}
|
||||||
|
@ -136,26 +132,28 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
||||||
binding.shield.apply {
|
binding.shield.apply {
|
||||||
// TODO [#776]: Support variable fees
|
// TODO [#776]: Support variable fees
|
||||||
// TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776
|
// TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776
|
||||||
visibility = if ((transparentBalance?.available ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) {
|
visibility =
|
||||||
View.VISIBLE
|
if ((transparentBalance?.available ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) {
|
||||||
} else {
|
View.VISIBLE
|
||||||
View.GONE
|
} else {
|
||||||
}
|
View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onStatus(status: Synchronizer.Status) {
|
private fun onStatus(status: Synchronizer.Status) {
|
||||||
Twig.debug { "Synchronizer status: $status" }
|
Twig.debug { "Synchronizer status: $status" }
|
||||||
// report benchmark event
|
// report benchmark event
|
||||||
val traceEvents = when (status) {
|
val traceEvents =
|
||||||
Synchronizer.Status.SYNCING -> {
|
when (status) {
|
||||||
SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_START
|
Synchronizer.Status.SYNCING -> {
|
||||||
|
SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_START
|
||||||
|
}
|
||||||
|
Synchronizer.Status.SYNCED -> {
|
||||||
|
SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_END
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
Synchronizer.Status.SYNCED -> {
|
|
||||||
SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_END
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
traceEvents?.let { reportTraceEvent(it) }
|
traceEvents?.let { reportTraceEvent(it) }
|
||||||
|
|
||||||
binding.textStatus.text = "Status: $status"
|
binding.textStatus.text = "Status: $status"
|
||||||
|
@ -175,12 +173,13 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
private fun WalletBalance?.humanString() = if (null == this) {
|
private fun WalletBalance?.humanString() =
|
||||||
"Calculating balance"
|
if (null == this) {
|
||||||
} else {
|
"Calculating balance"
|
||||||
"""
|
} else {
|
||||||
Pending balance: ${pending.convertZatoshiToZecString(12)}
|
"""
|
||||||
Available balance: ${available.convertZatoshiToZecString(12)}
|
Pending balance: ${pending.convertZatoshiToZecString(12)}
|
||||||
Total balance: ${total.convertZatoshiToZecString(12)}
|
Available balance: ${available.convertZatoshiToZecString(12)}
|
||||||
""".trimIndent()
|
Total balance: ${total.convertZatoshiToZecString(12)}
|
||||||
}
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
|
@ -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.databinding.FragmentGetBlockBinding
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
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.
|
* 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
|
* This demonstrates the basic ability to connect to the server, request a compact block and parse
|
||||||
* the response.
|
* the response.
|
||||||
*/
|
*/
|
||||||
class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
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)
|
var coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
|
@ -78,7 +78,10 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
||||||
// Android Lifecycle overrides
|
// Android Lifecycle overrides
|
||||||
//
|
//
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(
|
||||||
|
view: View,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.buttonApply.setOnClickListener(::onApply)
|
binding.buttonApply.setOnClickListener(::onApply)
|
||||||
binding.buttonPrevious.setOnClickListener {
|
binding.buttonPrevious.setOnClickListener {
|
||||||
|
@ -92,7 +95,6 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
||||||
//
|
//
|
||||||
// Base Fragment overrides
|
// Base Fragment overrides
|
||||||
//
|
//
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding =
|
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding = FragmentGetBlockBinding.inflate(layoutInflater)
|
||||||
FragmentGetBlockBinding.inflate(layoutInflater)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import kotlin.math.max
|
||||||
* block ranges for instance, to find the block with the most shielded transactions in a range.
|
* block ranges for instance, to find the block with the most shielded transactions in a range.
|
||||||
*/
|
*/
|
||||||
class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
||||||
|
|
||||||
// TODO [#973]: Eliminate old UI demo-app
|
// TODO [#973]: Eliminate old UI demo-app
|
||||||
// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973
|
// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973
|
||||||
@Suppress("MaxLineLength", "MagicNumber", "UNUSED_PARAMETER")
|
@Suppress("MaxLineLength", "MagicNumber", "UNUSED_PARAMETER")
|
||||||
|
@ -75,16 +74,18 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
||||||
|
|
||||||
private fun onApply(unused: View) {
|
private fun onApply(unused: View) {
|
||||||
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
val start = max(
|
val start =
|
||||||
binding.textStartHeight.text.toString().toLongOrNull()
|
max(
|
||||||
?: network.saplingActivationHeight.value,
|
binding.textStartHeight.text.toString().toLongOrNull()
|
||||||
network.saplingActivationHeight.value
|
?: network.saplingActivationHeight.value,
|
||||||
)
|
network.saplingActivationHeight.value
|
||||||
val end = max(
|
)
|
||||||
binding.textEndHeight.text.toString().toLongOrNull()
|
val end =
|
||||||
?: network.saplingActivationHeight.value,
|
max(
|
||||||
network.saplingActivationHeight.value
|
binding.textEndHeight.text.toString().toLongOrNull()
|
||||||
)
|
?: network.saplingActivationHeight.value,
|
||||||
|
network.saplingActivationHeight.value
|
||||||
|
)
|
||||||
if (start <= end) {
|
if (start <= end) {
|
||||||
@Suppress("TooGenericExceptionCaught")
|
@Suppress("TooGenericExceptionCaught")
|
||||||
try {
|
try {
|
||||||
|
@ -122,7 +123,10 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
||||||
// Android Lifecycle overrides
|
// Android Lifecycle overrides
|
||||||
//
|
//
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(
|
||||||
|
view: View,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.buttonApply.setOnClickListener(::onApply)
|
binding.buttonApply.setOnClickListener(::onApply)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetLatestHeightBinding
|
||||||
* instance.
|
* instance.
|
||||||
*/
|
*/
|
||||||
class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>() {
|
class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>() {
|
||||||
|
|
||||||
// TODO [#973]: Eliminate old UI demo-app
|
// TODO [#973]: Eliminate old UI demo-app
|
||||||
// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973
|
// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973
|
||||||
private fun displayLatestHeight() {
|
private fun displayLatestHeight() {
|
||||||
|
|
|
@ -24,7 +24,6 @@ import kotlinx.coroutines.launch
|
||||||
* HomeFragment.
|
* HomeFragment.
|
||||||
*/
|
*/
|
||||||
class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
||||||
|
|
||||||
private lateinit var seedPhrase: String
|
private lateinit var seedPhrase: String
|
||||||
private lateinit var seed: ByteArray
|
private lateinit var seed: ByteArray
|
||||||
|
|
||||||
|
@ -47,17 +46,19 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(
|
val spendingKey =
|
||||||
seed,
|
DerivationTool.getInstance().deriveUnifiedSpendingKey(
|
||||||
ZcashNetwork.fromResources(requireApplicationContext()),
|
seed,
|
||||||
Account(5)
|
ZcashNetwork.fromResources(requireApplicationContext()),
|
||||||
)
|
Account(5)
|
||||||
|
)
|
||||||
|
|
||||||
// derive the key that allows you to view but not spend transactions
|
// derive the key that allows you to view but not spend transactions
|
||||||
val viewingKey = DerivationTool.getInstance().deriveUnifiedFullViewingKey(
|
val viewingKey =
|
||||||
spendingKey,
|
DerivationTool.getInstance().deriveUnifiedFullViewingKey(
|
||||||
ZcashNetwork.fromResources(requireApplicationContext())
|
spendingKey,
|
||||||
)
|
ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
|
)
|
||||||
|
|
||||||
// display the keys in the UI
|
// display the keys in the UI
|
||||||
binding.textInfo.setText("Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey")
|
binding.textInfo.setText("Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey")
|
||||||
|
@ -79,7 +80,10 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(
|
||||||
|
view: View,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
displayKeys()
|
displayKeys()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,12 @@ import kotlinx.coroutines.launch
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
|
class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
|
||||||
|
override fun inflateBinding(layoutInflater: LayoutInflater) = FragmentHomeBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
override fun inflateBinding(layoutInflater: LayoutInflater) =
|
override fun onViewCreated(
|
||||||
FragmentHomeBinding.inflate(layoutInflater)
|
view: View,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.textSeedPhrase.setOnClickListener(::onEditSeedPhrase)
|
binding.textSeedPhrase.setOnClickListener(::onEditSeedPhrase)
|
||||||
binding.buttonPaste.setOnClickListener(::onPasteSeedPhrase)
|
binding.buttonPaste.setOnClickListener(::onPasteSeedPhrase)
|
||||||
|
|
|
@ -111,7 +111,10 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(
|
||||||
|
view: View,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
initTransactionUI()
|
initTransactionUI()
|
||||||
monitorChanges()
|
monitorChanges()
|
||||||
|
|
|
@ -23,7 +23,6 @@ class TransactionAdapter : ListAdapter<TransactionOverview, TransactionViewHolde
|
||||||
) = oldItem == newItem
|
) = oldItem == newItem
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
|
|
|
@ -33,7 +33,12 @@ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@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()
|
amountText.text = value.convertZatoshiToZecString()
|
||||||
timeText.text = minedHeight?.let {
|
timeText.text = minedHeight?.let {
|
||||||
time?.let {
|
time?.let {
|
||||||
|
|
|
@ -49,8 +49,8 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
|
|
||||||
private val isSynced get() = status == Synchronizer.Status.SYNCED
|
private val isSynced get() = status == Synchronizer.Status.SYNCED
|
||||||
|
|
||||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentListUtxosBinding =
|
@Suppress("MaxLineLength")
|
||||||
FragmentListUtxosBinding.inflate(layoutInflater)
|
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentListUtxosBinding = FragmentListUtxosBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
private fun initUi() {
|
private fun initUi() {
|
||||||
binding.inputAddress.setText(address)
|
binding.inputAddress.setText(address)
|
||||||
|
@ -131,7 +131,10 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
|
|
||||||
// private val now get() = System.currentTimeMillis()
|
// private val now get() = System.currentTimeMillis()
|
||||||
|
|
||||||
private fun updateStatus(message: String, append: Boolean = true) {
|
private fun updateStatus(
|
||||||
|
message: String,
|
||||||
|
append: Boolean = true
|
||||||
|
) {
|
||||||
if (append) {
|
if (append) {
|
||||||
binding.textStatus.text = "${binding.textStatus.text} $message"
|
binding.textStatus.text = "${binding.textStatus.text} $message"
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,7 +143,10 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
||||||
Twig.debug { message }
|
Twig.debug { message }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(
|
||||||
|
view: View,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
initUi()
|
initUi()
|
||||||
monitorStatus()
|
monitorStatus()
|
||||||
|
|
|
@ -23,7 +23,6 @@ class UtxoAdapter : ListAdapter<TransactionOverview, UtxoViewHolder>(
|
||||||
) = oldItem == newItem
|
) = oldItem == newItem
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
|
|
|
@ -25,7 +25,11 @@ class UtxoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
private fun bindToHelper(amount: Zatoshi, minedHeight: BlockHeight?, time: Long?) {
|
private fun bindToHelper(
|
||||||
|
amount: Zatoshi,
|
||||||
|
minedHeight: BlockHeight?,
|
||||||
|
time: Long?
|
||||||
|
) {
|
||||||
amountText.text = amount.convertZatoshiToZecString()
|
amountText.text = amount.convertZatoshiToZecString()
|
||||||
timeText.text = minedHeight?.let {
|
timeText.text = minedHeight?.let {
|
||||||
time?.let {
|
time?.let {
|
||||||
|
|
|
@ -34,7 +34,6 @@ import kotlinx.coroutines.launch
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
|
|
||||||
private lateinit var amountInput: TextView
|
private lateinit var amountInput: TextView
|
||||||
private lateinit var addressInput: TextView
|
private lateinit var addressInput: TextView
|
||||||
|
|
||||||
|
@ -67,12 +66,14 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
//
|
//
|
||||||
|
|
||||||
private fun initSendUi() {
|
private fun initSendUi() {
|
||||||
amountInput = binding.inputAmount.apply {
|
amountInput =
|
||||||
setText(DemoConstants.SEND_AMOUNT.toZecString())
|
binding.inputAmount.apply {
|
||||||
}
|
setText(DemoConstants.SEND_AMOUNT.toZecString())
|
||||||
addressInput = binding.inputAddress.apply {
|
}
|
||||||
setText(DemoConstants.TO_ADDRESS)
|
addressInput =
|
||||||
}
|
binding.inputAddress.apply {
|
||||||
|
setText(DemoConstants.TO_ADDRESS)
|
||||||
|
}
|
||||||
binding.buttonSend.setOnClickListener(::onSend)
|
binding.buttonSend.setOnClickListener(::onSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,10 +131,11 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
private fun onBalance(balance: WalletBalance?) {
|
private fun onBalance(balance: WalletBalance?) {
|
||||||
this.balance = balance
|
this.balance = balance
|
||||||
if (!isSyncing) {
|
if (!isSyncing) {
|
||||||
binding.textBalance.text = """
|
binding.textBalance.text =
|
||||||
|
"""
|
||||||
Available balance: ${balance?.available.convertZatoshiToZecString(12)}
|
Available balance: ${balance?.available.convertZatoshiToZecString(12)}
|
||||||
Total balance: ${balance?.total.convertZatoshiToZecString(12)}
|
Total balance: ${balance?.total.convertZatoshiToZecString(12)}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +193,10 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(
|
||||||
|
view: View,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
initSendUi()
|
initSendUi()
|
||||||
monitorChanges()
|
monitorChanges()
|
||||||
|
@ -200,7 +205,6 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||||
//
|
//
|
||||||
// BaseDemoFragment overrides
|
// BaseDemoFragment overrides
|
||||||
//
|
//
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding =
|
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding = FragmentSendBinding.inflate(layoutInflater)
|
||||||
FragmentSendBinding.inflate(layoutInflater)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
object WalletSnapshotFixture {
|
object WalletSnapshotFixture {
|
||||||
|
|
||||||
val STATUS = Synchronizer.Status.SYNCED
|
val STATUS = Synchronizer.Status.SYNCED
|
||||||
val PROGRESS = PercentDecimal.ZERO_PERCENT
|
val PROGRESS = PercentDecimal.ZERO_PERCENT
|
||||||
val TRANSPARENT_BALANCE: WalletBalance = WalletBalance(Zatoshi(8), Zatoshi(1))
|
val TRANSPARENT_BALANCE: WalletBalance = WalletBalance(Zatoshi(8), Zatoshi(1))
|
||||||
|
@ -21,11 +20,12 @@ object WalletSnapshotFixture {
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
fun new(
|
fun new(
|
||||||
status: Synchronizer.Status = STATUS,
|
status: Synchronizer.Status = STATUS,
|
||||||
processorInfo: CompactBlockProcessor.ProcessorInfo = CompactBlockProcessor.ProcessorInfo(
|
processorInfo: CompactBlockProcessor.ProcessorInfo =
|
||||||
null,
|
CompactBlockProcessor.ProcessorInfo(
|
||||||
null,
|
null,
|
||||||
null
|
null,
|
||||||
),
|
null
|
||||||
|
),
|
||||||
orchardBalance: WalletBalance = ORCHARD_BALANCE,
|
orchardBalance: WalletBalance = ORCHARD_BALANCE,
|
||||||
saplingBalance: WalletBalance = SAPLING_BALANCE,
|
saplingBalance: WalletBalance = SAPLING_BALANCE,
|
||||||
transparentBalance: WalletBalance = TRANSPARENT_BALANCE,
|
transparentBalance: WalletBalance = TRANSPARENT_BALANCE,
|
||||||
|
|
|
@ -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
|
* 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
|
* this instance lives for the lifetime of the application. Constructing multiple instances will
|
||||||
* potentially corrupt preference data and will leak resources.
|
* potentially corrupt preference data and will leak resources.
|
||||||
*/
|
*
|
||||||
/*
|
|
||||||
* Implementation note: EncryptedSharedPreferences are not thread-safe, so this implementation
|
* Implementation note: EncryptedSharedPreferences are not thread-safe, so this implementation
|
||||||
* confines them to a single background thread.
|
* confines them to a single background thread.
|
||||||
*/
|
*/
|
||||||
|
@ -34,13 +33,16 @@ class AndroidPreferenceProvider(
|
||||||
private val sharedPreferences: SharedPreferences,
|
private val sharedPreferences: SharedPreferences,
|
||||||
private val dispatcher: CoroutineDispatcher
|
private val dispatcher: CoroutineDispatcher
|
||||||
) : PreferenceProvider {
|
) : PreferenceProvider {
|
||||||
|
override suspend fun hasKey(key: Key) =
|
||||||
override suspend fun hasKey(key: Key) = withContext(dispatcher) {
|
withContext(dispatcher) {
|
||||||
sharedPreferences.contains(key.key)
|
sharedPreferences.contains(key.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
@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()
|
val editor = sharedPreferences.edit()
|
||||||
|
|
||||||
editor.putString(key.key, value)
|
editor.putString(key.key, value)
|
||||||
|
@ -50,65 +52,77 @@ class AndroidPreferenceProvider(
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getString(key: Key) = withContext(dispatcher) {
|
override suspend fun getString(key: Key) =
|
||||||
sharedPreferences.getString(key.key, null)
|
withContext(dispatcher) {
|
||||||
}
|
sharedPreferences.getString(key.key, null)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
override fun observe(key: Key): Flow<Unit> = callbackFlow<Unit> {
|
override fun observe(key: Key): Flow<Unit> =
|
||||||
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
|
callbackFlow<Unit> {
|
||||||
// Callback on main thread
|
val listener =
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
|
||||||
|
// Callback on main thread
|
||||||
|
trySend(Unit)
|
||||||
|
}
|
||||||
|
sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
|
||||||
|
|
||||||
|
// Kickstart the emissions
|
||||||
trySend(Unit)
|
trySend(Unit)
|
||||||
}
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
|
|
||||||
|
|
||||||
// Kickstart the emissions
|
awaitClose {
|
||||||
trySend(Unit)
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
|
||||||
|
}
|
||||||
awaitClose {
|
}.flowOn(dispatcher)
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
|
|
||||||
}
|
|
||||||
}.flowOn(dispatcher)
|
|
||||||
|
|
||||||
companion object {
|
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 of this line, we don't want multiple instances of this object created
|
||||||
* because we don't clean up the thread afterwards.
|
* because we don't clean up the thread afterwards.
|
||||||
*/
|
*/
|
||||||
val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||||
|
|
||||||
val sharedPreferences = withContext(singleThreadedDispatcher) {
|
val sharedPreferences =
|
||||||
context.getSharedPreferences(filename, Context.MODE_PRIVATE)
|
withContext(singleThreadedDispatcher) {
|
||||||
}
|
context.getSharedPreferences(filename, Context.MODE_PRIVATE)
|
||||||
|
}
|
||||||
|
|
||||||
return AndroidPreferenceProvider(sharedPreferences, singleThreadedDispatcher)
|
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 of this line, we don't want multiple instances of this object created
|
||||||
* because we don't clean up the thread afterwards.
|
* because we don't clean up the thread afterwards.
|
||||||
*/
|
*/
|
||||||
val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||||
|
|
||||||
val mainKey = withContext(singleThreadedDispatcher) {
|
val mainKey =
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
withContext(singleThreadedDispatcher) {
|
||||||
MasterKey.Builder(context).apply {
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
MasterKey.Builder(context).apply {
|
||||||
}.build()
|
setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
}
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
val sharedPreferences = withContext(singleThreadedDispatcher) {
|
val sharedPreferences =
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
withContext(singleThreadedDispatcher) {
|
||||||
EncryptedSharedPreferences.create(
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
context,
|
EncryptedSharedPreferences.create(
|
||||||
filename,
|
context,
|
||||||
mainKey,
|
filename,
|
||||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
mainKey,
|
||||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||||
)
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return AndroidPreferenceProvider(sharedPreferences, singleThreadedDispatcher)
|
return AndroidPreferenceProvider(sharedPreferences, singleThreadedDispatcher)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,5 @@ package cash.z.ecc.android.sdk.demoapp.preference
|
||||||
import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
|
import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
|
||||||
|
|
||||||
object EncryptedPreferenceKeys {
|
object EncryptedPreferenceKeys {
|
||||||
|
|
||||||
val PERSISTABLE_WALLET = PersistableWalletPreferenceDefault(Key("persistable_wallet"))
|
val PERSISTABLE_WALLET = PersistableWalletPreferenceDefault(Key("persistable_wallet"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@ import cash.z.ecc.android.sdk.demoapp.preference.api.PreferenceProvider
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.SuspendingLazy
|
import cash.z.ecc.android.sdk.demoapp.util.SuspendingLazy
|
||||||
|
|
||||||
object EncryptedPreferenceSingleton {
|
object EncryptedPreferenceSingleton {
|
||||||
|
|
||||||
private const val PREF_FILENAME = "co.electriccoin.zcash.encrypted"
|
private const val PREF_FILENAME = "co.electriccoin.zcash.encrypted"
|
||||||
|
|
||||||
private val lazy = SuspendingLazy<Context, PreferenceProvider> {
|
private val lazy =
|
||||||
AndroidPreferenceProvider.newEncrypted(it, PREF_FILENAME)
|
SuspendingLazy<Context, PreferenceProvider> {
|
||||||
}
|
AndroidPreferenceProvider.newEncrypted(it, PREF_FILENAME)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getInstance(context: Context) = lazy.getInstance(context)
|
suspend fun getInstance(context: Context) = lazy.getInstance(context)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import org.json.JSONObject
|
||||||
data class PersistableWalletPreferenceDefault(
|
data class PersistableWalletPreferenceDefault(
|
||||||
override val key: Key
|
override val key: Key
|
||||||
) : PreferenceDefault<PersistableWallet?> {
|
) : PreferenceDefault<PersistableWallet?> {
|
||||||
|
|
||||||
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
|
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
|
||||||
preferenceProvider.getString(key)?.let { PersistableWallet.from(JSONObject(it)) }
|
preferenceProvider.getString(key)?.let { PersistableWallet.from(JSONObject(it)) }
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,12 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface PreferenceProvider {
|
interface PreferenceProvider {
|
||||||
|
|
||||||
suspend fun hasKey(key: Key): Boolean
|
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?
|
suspend fun getString(key: Key): String?
|
||||||
|
|
||||||
|
|
|
@ -6,19 +6,22 @@ data class BooleanPreferenceDefault(
|
||||||
override val key: Key,
|
override val key: Key,
|
||||||
private val defaultValue: Boolean
|
private val defaultValue: Boolean
|
||||||
) : PreferenceDefault<Boolean> {
|
) : PreferenceDefault<Boolean> {
|
||||||
|
|
||||||
@Suppress("SwallowedException")
|
@Suppress("SwallowedException")
|
||||||
override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key)?.let {
|
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
|
||||||
try {
|
preferenceProvider.getString(key)?.let {
|
||||||
it.toBooleanStrict()
|
try {
|
||||||
} catch (e: IllegalArgumentException) {
|
it.toBooleanStrict()
|
||||||
// TODO [#32]: Log coercion failure instead of just silently returning default
|
} catch (e: IllegalArgumentException) {
|
||||||
// TODO [#32]: https://github.com/zcash/zcash-android-wallet-sdk/issues/32
|
// TODO [#32]: Log coercion failure instead of just silently returning default
|
||||||
defaultValue
|
// TODO [#32]: https://github.com/zcash/zcash-android-wallet-sdk/issues/32
|
||||||
}
|
defaultValue
|
||||||
} ?: defaultValue
|
}
|
||||||
|
} ?: defaultValue
|
||||||
|
|
||||||
override suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: Boolean) {
|
override suspend fun putValue(
|
||||||
|
preferenceProvider: PreferenceProvider,
|
||||||
|
newValue: Boolean
|
||||||
|
) {
|
||||||
preferenceProvider.putString(key, newValue.toString())
|
preferenceProvider.putString(key, newValue.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,18 +6,21 @@ data class IntegerPreferenceDefault(
|
||||||
override val key: Key,
|
override val key: Key,
|
||||||
private val defaultValue: Int
|
private val defaultValue: Int
|
||||||
) : PreferenceDefault<Int> {
|
) : PreferenceDefault<Int> {
|
||||||
|
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
|
||||||
|
preferenceProvider.getString(key)?.let {
|
||||||
|
try {
|
||||||
|
it.toInt()
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
// TODO [#32]: Log coercion failure instead of just silently returning default
|
||||||
|
// TODO [#32]: https://github.com/zcash/zcash-android-wallet-sdk/issues/32
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
} ?: defaultValue
|
||||||
|
|
||||||
override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key)?.let {
|
override suspend fun putValue(
|
||||||
try {
|
preferenceProvider: PreferenceProvider,
|
||||||
it.toInt()
|
newValue: Int
|
||||||
} catch (e: NumberFormatException) {
|
) {
|
||||||
// TODO [#32]: Log coercion failure instead of just silently returning default
|
|
||||||
// TODO [#32]: https://github.com/zcash/zcash-android-wallet-sdk/issues/32
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
} ?: defaultValue
|
|
||||||
|
|
||||||
override suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: Int) {
|
|
||||||
preferenceProvider.putString(key, newValue.toString())
|
preferenceProvider.putString(key, newValue.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,7 @@ import kotlinx.coroutines.flow.map
|
||||||
* multiple parts of the code can fetch the same preference without duplication or accidental
|
* 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
|
* variation in default value. Clients define the key and default value together, rather than just
|
||||||
* the key.
|
* the key.
|
||||||
*/
|
*
|
||||||
/*
|
|
||||||
* API note: the default value is not available through the public interface in order to prevent
|
* 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.
|
* 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.
|
* and perhaps many Integer values will also fit within the autoboxing cache.
|
||||||
*/
|
*/
|
||||||
interface PreferenceDefault<T> {
|
interface PreferenceDefault<T> {
|
||||||
|
|
||||||
val key: Key
|
val key: Key
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,14 +32,18 @@ interface PreferenceDefault<T> {
|
||||||
* @param preferenceProvider Provides actual preference values.
|
* @param preferenceProvider Provides actual preference values.
|
||||||
* @param newValue New value to write.
|
* @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.
|
* @param preferenceProvider Provides actual preference values.
|
||||||
* @return Flow that emits preference changes. Note that implementations should emit an initial value
|
* @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.
|
* 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> =
|
||||||
.map { getValue(preferenceProvider) }
|
preferenceProvider.observe(key)
|
||||||
.distinctUntilChanged()
|
.map { getValue(preferenceProvider) }
|
||||||
|
.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,14 @@ data class StringPreferenceDefault(
|
||||||
override val key: Key,
|
override val key: Key,
|
||||||
private val defaultValue: String
|
private val defaultValue: String
|
||||||
) : PreferenceDefault<String> {
|
) : PreferenceDefault<String> {
|
||||||
|
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
|
||||||
|
preferenceProvider.getString(key)
|
||||||
|
?: defaultValue
|
||||||
|
|
||||||
override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key)
|
override suspend fun putValue(
|
||||||
?: defaultValue
|
preferenceProvider: PreferenceProvider,
|
||||||
|
newValue: String
|
||||||
override suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: String) {
|
) {
|
||||||
preferenceProvider.putString(key, newValue)
|
preferenceProvider.putString(key, newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.ext.collectWith
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
fun ComponentActivity.BindCompLocalProvider(content: @Composable () -> Unit) {
|
fun ComponentActivity.BindCompLocalProvider(content: @Composable () -> Unit) {
|
||||||
val screenTimeout = ScreenTimeout()
|
val screenTimeout = ScreenTimeout()
|
||||||
observeScreenTimeoutFlag(screenTimeout)
|
observeScreenTimeoutFlag(screenTimeout)
|
||||||
|
|
|
@ -18,41 +18,43 @@ import kotlin.time.TimeSource
|
||||||
fun <T> Flow<T>.throttle(
|
fun <T> Flow<T>.throttle(
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
timeSource: TimeSource = TimeSource.Monotonic
|
timeSource: TimeSource = TimeSource.Monotonic
|
||||||
): Flow<T> = flow {
|
): Flow<T> =
|
||||||
coroutineScope {
|
flow {
|
||||||
val context = coroutineContext
|
coroutineScope {
|
||||||
val mutex = Mutex()
|
val context = coroutineContext
|
||||||
|
val mutex = Mutex()
|
||||||
|
|
||||||
var timeMark = timeSource.markNow()
|
var timeMark = timeSource.markNow()
|
||||||
var delayEmit: Deferred<Unit>? = null
|
var delayEmit: Deferred<Unit>? = null
|
||||||
var firstValue = true
|
var firstValue = true
|
||||||
var valueToEmit: T
|
var valueToEmit: T
|
||||||
collect { value ->
|
collect { value ->
|
||||||
if (firstValue) {
|
if (firstValue) {
|
||||||
firstValue = false
|
firstValue = false
|
||||||
emit(value)
|
emit(value)
|
||||||
timeMark = timeSource.markNow()
|
|
||||||
return@collect
|
|
||||||
}
|
|
||||||
delayEmit?.cancel()
|
|
||||||
valueToEmit = value
|
|
||||||
|
|
||||||
if (timeMark.elapsedNow() >= duration) {
|
|
||||||
mutex.withLock {
|
|
||||||
emit(valueToEmit)
|
|
||||||
timeMark = timeSource.markNow()
|
timeMark = timeSource.markNow()
|
||||||
|
return@collect
|
||||||
}
|
}
|
||||||
} else {
|
delayEmit?.cancel()
|
||||||
delayEmit = async(Dispatchers.Default) {
|
valueToEmit = value
|
||||||
|
|
||||||
|
if (timeMark.elapsedNow() >= duration) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
delay(duration)
|
emit(valueToEmit)
|
||||||
withContext(context) {
|
|
||||||
emit(valueToEmit)
|
|
||||||
}
|
|
||||||
timeMark = timeSource.markNow()
|
timeMark = timeSource.markNow()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
delayEmit =
|
||||||
|
async(Dispatchers.Default) {
|
||||||
|
mutex.withLock {
|
||||||
|
delay(duration)
|
||||||
|
withContext(context) {
|
||||||
|
emit(valueToEmit)
|
||||||
|
}
|
||||||
|
timeMark = timeSource.markNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ class ScreenTimeout {
|
||||||
val LocalScreenTimeout = compositionLocalOf { ScreenTimeout() }
|
val LocalScreenTimeout = compositionLocalOf { ScreenTimeout() }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
fun DisableScreenTimeout() {
|
fun DisableScreenTimeout() {
|
||||||
val screenTimeout = LocalScreenTimeout.current
|
val screenTimeout = LocalScreenTimeout.current
|
||||||
DisposableEffect(screenTimeout) {
|
DisposableEffect(screenTimeout) {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
@Preview(name = "Addresses")
|
@Preview(name = "Addresses")
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun ComposablePreview() {
|
private fun ComposablePreview() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
// TODO [#1090]: Demo: Add Addresses and Transactions Compose Previews
|
// 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.
|
* @param copyToClipboard First string is a tag, the second string is the text to copy.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
fun Addresses(
|
fun Addresses(
|
||||||
synchronizer: Synchronizer,
|
synchronizer: Synchronizer,
|
||||||
copyToClipboard: (String, String) -> Unit,
|
copyToClipboard: (String, String) -> Unit,
|
||||||
|
@ -55,11 +57,12 @@ fun Addresses(
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
// TODO [#846]: Slow addresses providing
|
// TODO [#846]: Slow addresses providing
|
||||||
// TODO [#846]: https://github.com/zcash/zcash-android-wallet-sdk/issues/846
|
// TODO [#846]: https://github.com/zcash/zcash-android-wallet-sdk/issues/846
|
||||||
val walletAddresses = flow {
|
val walletAddresses =
|
||||||
emit(WalletAddresses.new(synchronizer))
|
flow {
|
||||||
}.collectAsState(
|
emit(WalletAddresses.new(synchronizer))
|
||||||
initial = null
|
}.collectAsState(
|
||||||
).value
|
initial = null
|
||||||
|
).value
|
||||||
if (null != walletAddresses) {
|
if (null != walletAddresses) {
|
||||||
AddressesMainContent(
|
AddressesMainContent(
|
||||||
paddingValues = paddingValues,
|
paddingValues = paddingValues,
|
||||||
|
@ -72,6 +75,7 @@ fun Addresses(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun AddressesTopAppBar(onBack: () -> Unit) {
|
private fun AddressesTopAppBar(onBack: () -> Unit) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(text = stringResource(id = R.string.menu_address)) },
|
title = { Text(text = stringResource(id = R.string.menu_address)) },
|
||||||
|
@ -89,6 +93,7 @@ private fun AddressesTopAppBar(onBack: () -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun AddressesMainContent(
|
private fun AddressesMainContent(
|
||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
addresses: WalletAddresses,
|
addresses: WalletAddresses,
|
||||||
|
|
|
@ -31,6 +31,7 @@ import cash.z.ecc.android.sdk.model.toZecString
|
||||||
|
|
||||||
@Preview(name = "Balance")
|
@Preview(name = "Balance")
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun ComposablePreview() {
|
private fun ComposablePreview() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
Balance(
|
Balance(
|
||||||
|
@ -44,6 +45,7 @@ private fun ComposablePreview() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
fun Balance(
|
fun Balance(
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
sendState: SendState,
|
sendState: SendState,
|
||||||
|
@ -69,6 +71,7 @@ fun Balance(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun BalanceTopAppBar(
|
private fun BalanceTopAppBar(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onRefresh: () -> Unit
|
onRefresh: () -> Unit
|
||||||
|
@ -97,6 +100,7 @@ private fun BalanceTopAppBar(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun BalanceMainContent(
|
private fun BalanceMainContent(
|
||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
|
|
|
@ -33,6 +33,7 @@ import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot
|
||||||
|
|
||||||
@Preview(name = "Home")
|
@Preview(name = "Home")
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun ComposablePreviewHome() {
|
private fun ComposablePreviewHome() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
Home(
|
Home(
|
||||||
|
@ -49,8 +50,8 @@ private fun ComposablePreviewHome() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("LongParameterList", "ktlint:standard:function-naming")
|
||||||
fun Home(
|
fun Home(
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
isTestnet: Boolean,
|
isTestnet: Boolean,
|
||||||
|
@ -83,6 +84,7 @@ fun Home(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun HomeTopAppBar(
|
private fun HomeTopAppBar(
|
||||||
isTestnet: Boolean,
|
isTestnet: Boolean,
|
||||||
goTestnetFaucet: () -> Unit,
|
goTestnetFaucet: () -> Unit,
|
||||||
|
@ -103,6 +105,7 @@ private fun HomeTopAppBar(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun DebugMenu(
|
private fun DebugMenu(
|
||||||
isTestnet: Boolean,
|
isTestnet: Boolean,
|
||||||
goTestnetFaucet: () -> Unit,
|
goTestnetFaucet: () -> Unit,
|
||||||
|
@ -146,7 +149,7 @@ private fun DebugMenu(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList", "ktlint:standard:function-naming")
|
||||||
private fun HomeMainContent(
|
private fun HomeMainContent(
|
||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
|
|
|
@ -19,8 +19,9 @@ data class WalletSnapshot(
|
||||||
// TODO [#776]: Support variable fees
|
// TODO [#776]: Support variable fees
|
||||||
// TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776
|
// 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
|
// Note: the wallet is effectively empty if it cannot cover the miner's fee
|
||||||
val hasFunds = saplingBalance.available.value >
|
val hasFunds =
|
||||||
(ZcashSdk.MINERS_FEE.value.toDouble() / Zatoshi.ZATOSHI_PER_ZEC) // 0.0001
|
saplingBalance.available.value >
|
||||||
|
(ZcashSdk.MINERS_FEE.value.toDouble() / Zatoshi.ZATOSHI_PER_ZEC) // 0.0001
|
||||||
val hasSaplingBalance = saplingBalance.total.value > 0
|
val hasSaplingBalance = saplingBalance.total.value > 0
|
||||||
|
|
||||||
val isSendEnabled: Boolean get() = status == Synchronizer.Status.SYNCED && hasFunds
|
val isSendEnabled: Boolean get() = status == Synchronizer.Status.SYNCED && hasFunds
|
||||||
|
|
|
@ -66,68 +66,74 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
||||||
/**
|
/**
|
||||||
* Synchronizer that is retained long enough to survive configuration changes.
|
* Synchronizer that is retained long enough to survive configuration changes.
|
||||||
*/
|
*/
|
||||||
val synchronizer = walletCoordinator.synchronizer.stateIn(
|
val synchronizer =
|
||||||
viewModelScope,
|
walletCoordinator.synchronizer.stateIn(
|
||||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
|
||||||
null
|
|
||||||
)
|
|
||||||
|
|
||||||
val secretState: StateFlow<SecretState> = walletCoordinator.persistableWallet
|
|
||||||
.map { persistableWallet ->
|
|
||||||
if (null == persistableWallet) {
|
|
||||||
SecretState.None
|
|
||||||
} else {
|
|
||||||
SecretState.Ready(persistableWallet)
|
|
||||||
}
|
|
||||||
}.stateIn(
|
|
||||||
viewModelScope,
|
|
||||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
|
||||||
SecretState.Loading
|
|
||||||
)
|
|
||||||
|
|
||||||
val spendingKey = secretState
|
|
||||||
.filterIsInstance<SecretState.Ready>()
|
|
||||||
.map { it.persistableWallet }
|
|
||||||
.map {
|
|
||||||
val bip39Seed = withContext(Dispatchers.IO) {
|
|
||||||
Mnemonics.MnemonicCode(it.seedPhrase.joinToString()).toSeed()
|
|
||||||
}
|
|
||||||
DerivationTool.getInstance().deriveUnifiedSpendingKey(
|
|
||||||
seed = bip39Seed,
|
|
||||||
network = it.network,
|
|
||||||
account = Account.DEFAULT
|
|
||||||
)
|
|
||||||
}.stateIn(
|
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val secretState: StateFlow<SecretState> =
|
||||||
|
walletCoordinator.persistableWallet
|
||||||
|
.map { persistableWallet ->
|
||||||
|
if (null == persistableWallet) {
|
||||||
|
SecretState.None
|
||||||
|
} else {
|
||||||
|
SecretState.Ready(persistableWallet)
|
||||||
|
}
|
||||||
|
}.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||||
|
SecretState.Loading
|
||||||
|
)
|
||||||
|
|
||||||
|
val spendingKey =
|
||||||
|
secretState
|
||||||
|
.filterIsInstance<SecretState.Ready>()
|
||||||
|
.map { it.persistableWallet }
|
||||||
|
.map {
|
||||||
|
val bip39Seed =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Mnemonics.MnemonicCode(it.seedPhrase.joinToString()).toSeed()
|
||||||
|
}
|
||||||
|
DerivationTool.getInstance().deriveUnifiedSpendingKey(
|
||||||
|
seed = bip39Seed,
|
||||||
|
network = it.network,
|
||||||
|
account = Account.DEFAULT
|
||||||
|
)
|
||||||
|
}.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
val walletSnapshot: StateFlow<WalletSnapshot?> = synchronizer
|
val walletSnapshot: StateFlow<WalletSnapshot?> =
|
||||||
.flatMapLatest {
|
synchronizer
|
||||||
if (null == it) {
|
.flatMapLatest {
|
||||||
flowOf(null)
|
if (null == it) {
|
||||||
} else {
|
flowOf(null)
|
||||||
it.toWalletSnapshot()
|
} else {
|
||||||
|
it.toWalletSnapshot()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.throttle(1.seconds)
|
||||||
.throttle(1.seconds)
|
.stateIn(
|
||||||
.stateIn(
|
viewModelScope,
|
||||||
viewModelScope,
|
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
null
|
||||||
null
|
)
|
||||||
)
|
|
||||||
|
|
||||||
val addresses: StateFlow<WalletAddresses?> = synchronizer
|
val addresses: StateFlow<WalletAddresses?> =
|
||||||
.filterNotNull()
|
synchronizer
|
||||||
.map {
|
.filterNotNull()
|
||||||
WalletAddresses.new(it)
|
.map {
|
||||||
}.stateIn(
|
WalletAddresses.new(it)
|
||||||
viewModelScope,
|
}.stateIn(
|
||||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
viewModelScope,
|
||||||
null
|
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||||
)
|
null
|
||||||
|
)
|
||||||
|
|
||||||
private val mutableSendState = MutableStateFlow<SendState>(SendState.None)
|
private val mutableSendState = MutableStateFlow<SendState>(SendState.None)
|
||||||
|
|
||||||
|
@ -136,8 +142,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
||||||
/**
|
/**
|
||||||
* Creates a wallet asynchronously and then persists it. Clients observe
|
* 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.
|
* [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
|
* 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.
|
* safer because it 1. guarantees the wallet is written to disk and 2. has a single source of truth.
|
||||||
*/
|
*/
|
||||||
|
@ -146,12 +151,13 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val network = ZcashNetwork.fromResources(application)
|
val network = ZcashNetwork.fromResources(application)
|
||||||
val newWallet = PersistableWallet.new(
|
val newWallet =
|
||||||
application = application,
|
PersistableWallet.new(
|
||||||
zcashNetwork = network,
|
application = application,
|
||||||
endpoint = LightWalletEndpoint.defaultForNetwork(network),
|
zcashNetwork = network,
|
||||||
walletInitMode = WalletInitMode.NewWallet
|
endpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||||
)
|
walletInitMode = WalletInitMode.NewWallet
|
||||||
|
)
|
||||||
persistWallet(newWallet)
|
persistWallet(newWallet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,7 +274,9 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
||||||
*/
|
*/
|
||||||
sealed class SecretState {
|
sealed class SecretState {
|
||||||
object Loading : SecretState()
|
object Loading : SecretState()
|
||||||
|
|
||||||
object None : SecretState()
|
object None : SecretState()
|
||||||
|
|
||||||
class Ready(val persistableWallet: PersistableWallet) : SecretState()
|
class Ready(val persistableWallet: PersistableWallet) : SecretState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,22 +284,27 @@ sealed class SendState {
|
||||||
object None : SendState() {
|
object None : SendState() {
|
||||||
override fun toString(): String = "None"
|
override fun toString(): String = "None"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Sending : SendState() {
|
object Sending : SendState() {
|
||||||
override fun toString(): String = "Sending"
|
override fun toString(): String = "Sending"
|
||||||
}
|
}
|
||||||
|
|
||||||
class Sent(val localTxId: Long) : SendState() {
|
class Sent(val localTxId: Long) : SendState() {
|
||||||
override fun toString(): String = "Sent"
|
override fun toString(): String = "Sent"
|
||||||
}
|
}
|
||||||
|
|
||||||
class Error(val error: Throwable) : SendState() {
|
class Error(val error: Throwable) : SendState() {
|
||||||
override fun toString(): String = "Error ${error.message}"
|
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
|
* 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 {
|
sealed class SynchronizerError {
|
||||||
abstract fun getCauseMessage(): String?
|
abstract fun getCauseMessage(): String?
|
||||||
|
|
||||||
|
@ -316,51 +329,59 @@ sealed class SynchronizerError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Synchronizer.toCommonError(): Flow<SynchronizerError?> = callbackFlow {
|
private fun Synchronizer.toCommonError(): Flow<SynchronizerError?> =
|
||||||
// just for initial default value emit
|
callbackFlow {
|
||||||
trySend(null)
|
// just for initial default value emit
|
||||||
|
trySend(null)
|
||||||
|
|
||||||
onCriticalErrorHandler = {
|
onCriticalErrorHandler = {
|
||||||
Twig.error { "WALLET - Error Critical: $it" }
|
Twig.error { "WALLET - Error Critical: $it" }
|
||||||
trySend(SynchronizerError.Critical(it))
|
trySend(SynchronizerError.Critical(it))
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
onProcessorErrorHandler = {
|
onProcessorErrorHandler = {
|
||||||
Twig.error { "WALLET - Error Processor: $it" }
|
Twig.error { "WALLET - Error Processor: $it" }
|
||||||
trySend(SynchronizerError.Processor(it))
|
trySend(SynchronizerError.Processor(it))
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
onSubmissionErrorHandler = {
|
onSubmissionErrorHandler = {
|
||||||
Twig.error { "WALLET - Error Submission: $it" }
|
Twig.error { "WALLET - Error Submission: $it" }
|
||||||
trySend(SynchronizerError.Submission(it))
|
trySend(SynchronizerError.Submission(it))
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
onSetupErrorHandler = {
|
onSetupErrorHandler = {
|
||||||
Twig.error { "WALLET - Error Setup: $it" }
|
Twig.error { "WALLET - Error Setup: $it" }
|
||||||
trySend(SynchronizerError.Setup(it))
|
trySend(SynchronizerError.Setup(it))
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
onChainErrorHandler = { x, y ->
|
onChainErrorHandler = { x, y ->
|
||||||
Twig.error { "WALLET - Error Chain: $x, $y" }
|
Twig.error { "WALLET - Error Chain: $x, $y" }
|
||||||
trySend(SynchronizerError.Chain(x, y))
|
trySend(SynchronizerError.Chain(x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitClose {
|
awaitClose {
|
||||||
// nothing to close here
|
// nothing to close here
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// No good way around needing magic numbers for the indices
|
// No good way around needing magic numbers for the indices
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
private fun Synchronizer.toWalletSnapshot() =
|
private fun Synchronizer.toWalletSnapshot() =
|
||||||
combine(
|
combine(
|
||||||
status, // 0
|
// 0
|
||||||
processorInfo, // 1
|
status,
|
||||||
orchardBalances, // 2
|
// 1
|
||||||
saplingBalances, // 3
|
processorInfo,
|
||||||
transparentBalances, // 4
|
// 2
|
||||||
progress, // 5
|
orchardBalances,
|
||||||
toCommonError() // 6
|
// 3
|
||||||
|
saplingBalances,
|
||||||
|
// 4
|
||||||
|
transparentBalances,
|
||||||
|
// 5
|
||||||
|
progress,
|
||||||
|
// 6
|
||||||
|
toCommonError()
|
||||||
) { flows ->
|
) { flows ->
|
||||||
val orchardBalance = flows[2] as WalletBalance?
|
val orchardBalance = flows[2] as WalletBalance?
|
||||||
val saplingBalance = flows[3] as WalletBalance?
|
val saplingBalance = flows[3] as WalletBalance?
|
||||||
|
|
|
@ -26,6 +26,7 @@ import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
|
||||||
|
|
||||||
@Preview(name = "Seed")
|
@Preview(name = "Seed")
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun ComposablePreview() {
|
private fun ComposablePreview() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
Seed(
|
Seed(
|
||||||
|
@ -37,6 +38,7 @@ private fun ComposablePreview() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
fun Seed(
|
fun Seed(
|
||||||
zcashNetwork: ZcashNetwork,
|
zcashNetwork: ZcashNetwork,
|
||||||
onExistingWallet: (PersistableWallet) -> Unit,
|
onExistingWallet: (PersistableWallet) -> Unit,
|
||||||
|
@ -56,6 +58,7 @@ fun Seed(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun ConfigureSeedTopAppBar() {
|
private fun ConfigureSeedTopAppBar() {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(text = stringResource(id = R.string.configure_seed)) }
|
title = { Text(text = stringResource(id = R.string.configure_seed)) }
|
||||||
|
@ -63,6 +66,7 @@ private fun ConfigureSeedTopAppBar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun ConfigureSeedMainContent(
|
private fun ConfigureSeedMainContent(
|
||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
zcashNetwork: ZcashNetwork,
|
zcashNetwork: ZcashNetwork,
|
||||||
|
@ -76,13 +80,14 @@ private fun ConfigureSeedMainContent(
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val newWallet = PersistableWallet(
|
val newWallet =
|
||||||
network = zcashNetwork,
|
PersistableWallet(
|
||||||
endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork),
|
network = zcashNetwork,
|
||||||
birthday = WalletFixture.Alice.getBirthday(zcashNetwork),
|
endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork),
|
||||||
seedPhrase = SeedPhrase.new(WalletFixture.Alice.seedPhrase),
|
birthday = WalletFixture.Alice.getBirthday(zcashNetwork),
|
||||||
walletInitMode = WalletInitMode.RestoreWallet
|
seedPhrase = SeedPhrase.new(WalletFixture.Alice.seedPhrase),
|
||||||
)
|
walletInitMode = WalletInitMode.RestoreWallet
|
||||||
|
)
|
||||||
onExistingWallet(newWallet)
|
onExistingWallet(newWallet)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -90,13 +95,14 @@ private fun ConfigureSeedMainContent(
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val newWallet = PersistableWallet(
|
val newWallet =
|
||||||
network = zcashNetwork,
|
PersistableWallet(
|
||||||
endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork),
|
network = zcashNetwork,
|
||||||
birthday = WalletFixture.Ben.getBirthday(zcashNetwork),
|
endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork),
|
||||||
seedPhrase = SeedPhrase.new(WalletFixture.Ben.seedPhrase),
|
birthday = WalletFixture.Ben.getBirthday(zcashNetwork),
|
||||||
walletInitMode = WalletInitMode.RestoreWallet
|
seedPhrase = SeedPhrase.new(WalletFixture.Ben.seedPhrase),
|
||||||
)
|
walletInitMode = WalletInitMode.RestoreWallet
|
||||||
|
)
|
||||||
onExistingWallet(newWallet)
|
onExistingWallet(newWallet)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -52,6 +52,7 @@ import cash.z.ecc.android.sdk.model.toZecString
|
||||||
|
|
||||||
@Preview(name = "Send")
|
@Preview(name = "Send")
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun ComposablePreview() {
|
private fun ComposablePreview() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
Send(
|
Send(
|
||||||
|
@ -64,6 +65,7 @@ private fun ComposablePreview() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
fun Send(
|
fun Send(
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
sendState: SendState,
|
sendState: SendState,
|
||||||
|
@ -84,6 +86,7 @@ fun Send(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun SendTopAppBar(onBack: () -> Unit) {
|
private fun SendTopAppBar(onBack: () -> Unit) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(text = stringResource(id = R.string.menu_send)) },
|
title = { Text(text = stringResource(id = R.string.menu_send)) },
|
||||||
|
@ -101,7 +104,7 @@ private fun SendTopAppBar(onBack: () -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod", "ktlint:standard:function-naming")
|
||||||
private fun SendMainContent(
|
private fun SendMainContent(
|
||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
walletSnapshot: WalletSnapshot,
|
walletSnapshot: WalletSnapshot,
|
||||||
|
@ -227,20 +230,20 @@ private fun SendMainContent(
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val zecSendValidation = ZecSendExt.new(
|
val zecSendValidation =
|
||||||
context,
|
ZecSendExt.new(
|
||||||
recipientAddressString,
|
context,
|
||||||
amountZecString,
|
recipientAddressString,
|
||||||
memoString,
|
amountZecString,
|
||||||
monetarySeparators
|
memoString,
|
||||||
)
|
monetarySeparators
|
||||||
|
)
|
||||||
|
|
||||||
when (zecSendValidation) {
|
when (zecSendValidation) {
|
||||||
is ZecSendExt.ZecSendValidation.Valid -> onSend(zecSendValidation.zecSend)
|
is ZecSendExt.ZecSendValidation.Valid -> onSend(zecSendValidation.zecSend)
|
||||||
is ZecSendExt.ZecSendValidation.Invalid -> validation = zecSendValidation.validationErrors
|
is ZecSendExt.ZecSendValidation.Invalid -> validation = zecSendValidation.validationErrors
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Needs actual validation
|
// Needs actual validation
|
||||||
enabled = amountZecString.isNotBlank() && recipientAddressString.isNotBlank()
|
enabled = amountZecString.isNotBlank() && recipientAddressString.isNotBlank()
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -39,6 +39,7 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Preview(name = "Transactions")
|
@Preview(name = "Transactions")
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun ComposablePreview() {
|
private fun ComposablePreview() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
// TODO [#1090]: Demo: Add Addresses and Transactions Compose Previews
|
// TODO [#1090]: Demo: Add Addresses and Transactions Compose Previews
|
||||||
|
@ -48,6 +49,7 @@ private fun ComposablePreview() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
fun Transactions(
|
fun Transactions(
|
||||||
synchronizer: Synchronizer,
|
synchronizer: Synchronizer,
|
||||||
onBack: () -> Unit
|
onBack: () -> Unit
|
||||||
|
@ -64,11 +66,12 @@ fun Transactions(
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
// TODO [#846]: Slow addresses providing
|
// TODO [#846]: Slow addresses providing
|
||||||
// TODO [#846]: https://github.com/zcash/zcash-android-wallet-sdk/issues/846
|
// TODO [#846]: https://github.com/zcash/zcash-android-wallet-sdk/issues/846
|
||||||
val walletAddresses = flow {
|
val walletAddresses =
|
||||||
emit(WalletAddresses.new(synchronizer))
|
flow {
|
||||||
}.collectAsState(
|
emit(WalletAddresses.new(synchronizer))
|
||||||
initial = null
|
}.collectAsState(
|
||||||
).value
|
initial = null
|
||||||
|
).value
|
||||||
if (null != walletAddresses) {
|
if (null != walletAddresses) {
|
||||||
TransactionsMainContent(
|
TransactionsMainContent(
|
||||||
paddingValues = paddingValues,
|
paddingValues = paddingValues,
|
||||||
|
@ -82,6 +85,7 @@ fun Transactions(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun TransactionsTopAppBar(
|
private fun TransactionsTopAppBar(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onRefresh: () -> Unit
|
onRefresh: () -> Unit
|
||||||
|
@ -110,6 +114,7 @@ private fun TransactionsTopAppBar(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@Suppress("ktlint:standard:function-naming")
|
||||||
private fun TransactionsMainContent(
|
private fun TransactionsMainContent(
|
||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
synchronizer: Synchronizer,
|
synchronizer: Synchronizer,
|
||||||
|
@ -138,14 +143,16 @@ private fun TransactionsMainContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
val time = tx.minedHeight?.let {
|
val time =
|
||||||
tx.blockTimeEpochSeconds?.let { kotlinx.datetime.Instant.fromEpochSeconds(it) } ?: "Unknown"
|
tx.minedHeight?.let {
|
||||||
} ?: "Pending"
|
tx.blockTimeEpochSeconds?.let { kotlinx.datetime.Instant.fromEpochSeconds(it) } ?: "Unknown"
|
||||||
val value = if (tx.isSentTransaction) {
|
} ?: "Pending"
|
||||||
-tx.netValue.value
|
val value =
|
||||||
} else {
|
if (tx.isSentTransaction) {
|
||||||
tx.netValue.value
|
-tx.netValue.value
|
||||||
}
|
} else {
|
||||||
|
tx.netValue.value
|
||||||
|
}
|
||||||
Text("$time, $value")
|
Text("$time, $value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,9 @@ internal object AndroidApiVersion {
|
||||||
* [sdk].
|
* [sdk].
|
||||||
*/
|
*/
|
||||||
@ChecksSdkIntAtLeast(parameter = 0)
|
@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
|
return Build.VERSION.SDK_INT >= sdk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ FOOJAY_TOOLCHAIN_RESOLVER_VERSION=0.5.0
|
||||||
FULLADLE_VERSION=0.17.4
|
FULLADLE_VERSION=0.17.4
|
||||||
GRADLE_VERSIONS_PLUGIN_VERSION=0.50.0
|
GRADLE_VERSIONS_PLUGIN_VERSION=0.50.0
|
||||||
KSP_VERSION=1.8.20-1.0.10
|
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
|
PROTOBUF_GRADLE_PLUGIN_VERSION=0.9.4
|
||||||
RUST_GRADLE_PLUGIN_VERSION=0.9.3
|
RUST_GRADLE_PLUGIN_VERSION=0.9.3
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class BenchmarkingExtTest {
|
class BenchmarkingExtTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SmallTest
|
@SmallTest
|
||||||
fun check_build_config() {
|
fun check_build_config() {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class BlockRangeFixtureTest {
|
class BlockRangeFixtureTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SmallTest
|
@SmallTest
|
||||||
fun compare_default_values() {
|
fun compare_default_values() {
|
||||||
|
|
|
@ -4,10 +4,7 @@ import io.grpc.Status
|
||||||
import io.grpc.StatusRuntimeException
|
import io.grpc.StatusRuntimeException
|
||||||
|
|
||||||
object StatusExceptionFixture {
|
object StatusExceptionFixture {
|
||||||
|
fun new(status: Status): StatusRuntimeException {
|
||||||
fun new(
|
|
||||||
status: Status
|
|
||||||
): StatusRuntimeException {
|
|
||||||
return StatusRuntimeException(status)
|
return StatusRuntimeException(status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import kotlin.test.Ignore
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
|
||||||
class ChannelFactoryTest {
|
class ChannelFactoryTest {
|
||||||
|
|
||||||
private val channelFactory = AndroidChannelFactory(getAppContext())
|
private val channelFactory = AndroidChannelFactory(getAppContext())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -10,7 +10,6 @@ import kotlin.test.assertNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class GrpcStatusResolverTest {
|
class GrpcStatusResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SmallTest
|
@SmallTest
|
||||||
fun resolve_explicitly_caught_server_error_test() {
|
fun resolve_explicitly_caught_server_error_test() {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import kotlin.test.Ignore
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
|
||||||
class LightWalletClientImplTest {
|
class LightWalletClientImplTest {
|
||||||
|
|
||||||
private val channelFactory = AndroidChannelFactory(getAppContext())
|
private val channelFactory = AndroidChannelFactory(getAppContext())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -6,7 +6,11 @@ import androidx.test.core.app.ApplicationProvider
|
||||||
|
|
||||||
fun getAppContext(): Context = ApplicationProvider.getApplicationContext()
|
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) =
|
fun getStringResourceWithArgs(
|
||||||
getAppContext().getString(resId, *formatArgs)
|
@StringRes resId: Int,
|
||||||
|
vararg formatArgs: String
|
||||||
|
) = getAppContext().getString(resId, *formatArgs)
|
||||||
|
|
|
@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.Flow
|
||||||
* Client for interacting with lightwalletd.
|
* Client for interacting with lightwalletd.
|
||||||
*/
|
*/
|
||||||
interface LightWalletClient {
|
interface LightWalletClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the full transaction info.
|
* @return the full transaction info.
|
||||||
*/
|
*/
|
||||||
|
@ -116,5 +115,4 @@ interface LightWalletClient {
|
||||||
fun LightWalletClient.Companion.new(
|
fun LightWalletClient.Companion.new(
|
||||||
context: Context,
|
context: Context,
|
||||||
lightWalletEndpoint: LightWalletEndpoint
|
lightWalletEndpoint: LightWalletEndpoint
|
||||||
): LightWalletClient =
|
): LightWalletClient = LightWalletClientImpl.new(AndroidChannelFactory(context), lightWalletEndpoint)
|
||||||
LightWalletClientImpl.new(AndroidChannelFactory(context), lightWalletEndpoint)
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import androidx.annotation.VisibleForTesting
|
||||||
* Used for getting mocked blocks range for benchmarking purposes.
|
* Used for getting mocked blocks range for benchmarking purposes.
|
||||||
*/
|
*/
|
||||||
object BenchmarkingBlockRangeFixture {
|
object BenchmarkingBlockRangeFixture {
|
||||||
|
|
||||||
// Be aware that changing these bounds values in a broader range may result in a timeout reached in
|
// 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
|
// SyncBlockchainBenchmark. So if changing these, don't forget to align also the test timeout in
|
||||||
// waitForBalanceScreen() appropriately.
|
// waitForBalanceScreen() appropriately.
|
||||||
|
|
|
@ -11,12 +11,10 @@ import kotlinx.coroutines.flow.asFlow
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||||
object ListOfCompactBlocksFixture {
|
object ListOfCompactBlocksFixture {
|
||||||
|
|
||||||
val DEFAULT_FILE_BLOCK_RANGE = FileBlockRangeFixture.new()
|
val DEFAULT_FILE_BLOCK_RANGE = FileBlockRangeFixture.new()
|
||||||
|
|
||||||
fun newSequence(
|
@Suppress("MaxLineLength")
|
||||||
blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE
|
fun newSequence(blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE): Sequence<CompactBlockUnsafe> {
|
||||||
): Sequence<CompactBlockUnsafe> {
|
|
||||||
val blocks = mutableListOf<CompactBlockUnsafe>()
|
val blocks = mutableListOf<CompactBlockUnsafe>()
|
||||||
|
|
||||||
for (blockHeight in blocksHeightRange.start.value..blocksHeightRange.endInclusive.value) {
|
for (blockHeight in blocksHeightRange.start.value..blocksHeightRange.endInclusive.value) {
|
||||||
|
@ -28,9 +26,8 @@ object ListOfCompactBlocksFixture {
|
||||||
return blocks.asSequence()
|
return blocks.asSequence()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun newFlow(
|
@Suppress("MaxLineLength")
|
||||||
blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE
|
fun newFlow(blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE): Flow<CompactBlockUnsafe> {
|
||||||
): Flow<CompactBlockUnsafe> {
|
|
||||||
return newSequence(blocksHeightRange).asFlow()
|
return newSequence(blocksHeightRange).asFlow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,15 @@ import java.nio.ByteBuffer
|
||||||
* Used for getting single mocked compact block for processing and persisting purposes.
|
* Used for getting single mocked compact block for processing and persisting purposes.
|
||||||
*/
|
*/
|
||||||
internal object SingleCompactBlockFixture {
|
internal object SingleCompactBlockFixture {
|
||||||
|
|
||||||
internal const val DEFAULT_HEIGHT = 500_000L
|
internal const val DEFAULT_HEIGHT = 500_000L
|
||||||
internal const val DEFAULT_TIME = 0
|
internal const val DEFAULT_TIME = 0
|
||||||
internal const val DEFAULT_SAPLING_OUTPUT_COUNT = 1u
|
internal const val DEFAULT_SAPLING_OUTPUT_COUNT = 1u
|
||||||
internal const val DEFAULT_ORCHARD_OUTPUT_COUNT = 2u
|
internal const val DEFAULT_ORCHARD_OUTPUT_COUNT = 2u
|
||||||
internal const val DEFAULT_HASH = DEFAULT_HEIGHT
|
internal const val DEFAULT_HASH = DEFAULT_HEIGHT
|
||||||
internal const val DEFAULT_BLOCK_BYTES = DEFAULT_HEIGHT
|
internal const val DEFAULT_BLOCK_BYTES = DEFAULT_HEIGHT
|
||||||
|
|
||||||
internal fun heightToFixtureData(height: Long) = BytesConversionHelper.longToBytes(height)
|
internal fun heightToFixtureData(height: Long) = BytesConversionHelper.longToBytes(height)
|
||||||
|
|
||||||
internal fun fixtureDataToHeight(byteArray: ByteArray) = BytesConversionHelper.bytesToLong(byteArray)
|
internal fun fixtureDataToHeight(byteArray: ByteArray) = BytesConversionHelper.bytesToLong(byteArray)
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
|
|
@ -3,6 +3,7 @@ package co.electriccoin.lightwallet.client.internal
|
||||||
internal object Constants {
|
internal object Constants {
|
||||||
const val LOG_TAG = "LightWalletClient" // NON-NLS
|
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_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 =
|
||||||
"can't be empty:" // NON-NLS
|
"$ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON " +
|
||||||
|
"can't be empty:" // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ class DarksideApi private constructor(
|
||||||
private val channel: ManagedChannel,
|
private val channel: ManagedChannel,
|
||||||
private val singleRequestTimeout: Duration = 10.seconds
|
private val singleRequestTimeout: Duration = 10.seconds
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal fun new(
|
internal fun new(
|
||||||
channelFactory: ChannelFactory,
|
channelFactory: ChannelFactory,
|
||||||
|
@ -38,7 +37,8 @@ class DarksideApi private constructor(
|
||||||
|
|
||||||
fun reset(
|
fun reset(
|
||||||
saplingActivationHeight: BlockHeightUnsafe,
|
saplingActivationHeight: BlockHeightUnsafe,
|
||||||
branchId: String = "e9ff75a6", // Canopy,
|
// Canopy
|
||||||
|
branchId: String = "e9ff75a6",
|
||||||
chainName: String = "darksidemainnet"
|
chainName: String = "darksidemainnet"
|
||||||
) = apply {
|
) = apply {
|
||||||
Darkside.DarksideMetaState.newBuilder()
|
Darkside.DarksideMetaState.newBuilder()
|
||||||
|
@ -50,11 +50,15 @@ class DarksideApi private constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageBlocks(url: String) = apply {
|
fun stageBlocks(url: String) =
|
||||||
createStub().stageBlocks(url.toUrl())
|
apply {
|
||||||
}
|
createStub().stageBlocks(url.toUrl())
|
||||||
|
}
|
||||||
|
|
||||||
fun stageTransactions(url: String, targetHeight: BlockHeightUnsafe) = apply {
|
fun stageTransactions(
|
||||||
|
url: String,
|
||||||
|
targetHeight: BlockHeightUnsafe
|
||||||
|
) = apply {
|
||||||
createStub().stageTransactions(
|
createStub().stageTransactions(
|
||||||
DarksideTransactionsURL.newBuilder().setHeight(targetHeight.value.toInt()).setUrl(url).build()
|
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) {
|
if (txs == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -147,6 +154,7 @@ class DarksideApi private constructor(
|
||||||
|
|
||||||
var completed = false
|
var completed = false
|
||||||
var error: Throwable? = null
|
var error: Throwable? = null
|
||||||
|
|
||||||
override fun onNext(value: Service.Empty?) {
|
override fun onNext(value: Service.Empty?) {
|
||||||
// No implementation
|
// No implementation
|
||||||
}
|
}
|
||||||
|
@ -171,8 +179,7 @@ class DarksideApi private constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun BlockHeightUnsafe.toHeight() =
|
private fun BlockHeightUnsafe.toHeight() = Darkside.DarksideHeight.newBuilder().setHeight(this.value.toInt()).build()
|
||||||
Darkside.DarksideHeight.newBuilder().setHeight(this.value.toInt()).build()
|
|
||||||
|
|
||||||
fun DarksideApi.Companion.new(
|
fun DarksideApi.Companion.new(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue