[#757] Adopt SDK 1.14.0 (#759)

This commit is contained in:
Carter Jernigan 2023-02-17 06:05:23 -05:00 committed by GitHub
parent 7523619afa
commit f5e0af6545
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
101 changed files with 113 additions and 1981 deletions

View File

@ -1,57 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="sdk-ext-ui-lib:connectedCheck" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-app.sdk-ext-ui-lib" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Hybrid>
<Java />
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -26,10 +26,7 @@ class ZcashApplication : CoroutineApplication() {
Twig.initialize(applicationContext)
Twig.info { "Starting application…" }
if (BuildConfig.DEBUG) {
// This is an internal API to the Zcash SDK to enable logging; it could change in the future
cash.z.ecc.android.sdk.internal.Twig.enabled(true)
} else {
if (!BuildConfig.DEBUG) {
// In release builds, logs should be stripped by R8 rules
Twig.assertLoggingStripped()
}

View File

@ -41,7 +41,6 @@ The logical components of the app are implemented as a number of Gradle modules.
* `preference-impl-android-lib` — Android-specific implementation for preference storage.
* sdk
* `sdk-ext-lib` — Contains extensions on top of the to the Zcash SDK. Some of these extensions might be migrated into the SDK eventually, while others might represent Android-centric idioms. Depending on how this module evolves, it could adopt another name such as `wallet-lib` or be split into two.
* `sdk-ext-ui` — Place for Zcash SDK components (same as `sdk-ext-lib`), which are related to the UI (e.g. depend on user locale and thus need to be translated via `strings.xml`).
* spackle — Random utilities, to fill in the cracks in the frameworks.
* `spackle-lib` — Multiplatform implementation for Kotlin and JVM
* `spackle-android-lib` — Android-specific additions.
@ -55,9 +54,8 @@ The following diagram shows a rough depiction of dependencies between the module
subgraph sdk
sdkLib[[sdk-lib]];
sdkExtLib[[sdk-ext-lib]];
sdkExtUi[[sdk-ext-ui]];
end
sdkLib[[sdk-lib]] --> sdkExtLib[[sdk-ext-lib]] --> sdkExtUi[[sdk-ext-ui]];
sdkLib[[sdk-lib]] --> sdkExtLib[[sdk-ext-lib]];
subgraph preference
preferenceApiLib[[preference-api-lib]];
preferenceImplAndroidLib[[preference-impl-android-lib]];

View File

@ -149,7 +149,7 @@ ZCASH_ANDROID_WALLET_PLUGINS_VERSION=1.0.0
ZCASH_BIP39_VERSION=1.0.4
# TODO [#279]: Revert to stable SDK before app release
# TODO [#279]: https://github.com/zcash/secant-android-wallet/issues/279
ZCASH_SDK_VERSION=1.12.0-beta01-SNAPSHOT
ZCASH_SDK_VERSION=1.14.0-beta01-SNAPSHOT
ZXING_VERSION=3.5.0

View File

@ -17,6 +17,7 @@ dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
api(libs.zcash.sdk)
api(libs.zcash.sdk.incubator)
api(libs.zcash.bip39)
androidTestImplementation(libs.bundles.androidx.test)
@ -37,4 +38,3 @@ dependencies {
}
}
}

View File

@ -1,21 +0,0 @@
package cash.z.ecc.sdk.ext
import kotlin.test.Test
import kotlin.test.assertEquals
class StringExtTest {
@Test
fun sizeInBytes_empty() {
assertEquals(0, "".sizeInUtf8Bytes())
}
@Test
fun sizeInBytes_one() {
assertEquals(1, "a".sizeInUtf8Bytes())
}
@Test
fun sizeInBytes_unicode() {
assertEquals(2, "á".sizeInUtf8Bytes())
}
}

View File

@ -1,30 +0,0 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.sdk.fixture.ZecSendFixture
import java.lang.IllegalArgumentException
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class MemoTest {
companion object {
private const val BYTE_STRING_513 = """
asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfa
"""
}
@Test
fun isWithinMaxSize_too_big() {
assertFalse(Memo.isWithinMaxLength(BYTE_STRING_513))
}
@Test
fun isWithinMaxSize_ok() {
assertTrue(Memo.isWithinMaxLength(ZecSendFixture.MEMO.value))
}
@Test(IllegalArgumentException::class)
fun init_max_size() {
Memo(BYTE_STRING_513)
}
}

View File

@ -1,19 +0,0 @@
package cash.z.ecc.sdk.model
import androidx.test.filters.SmallTest
import org.junit.Test
class PercentDecimalTest {
@Test(expected = IllegalArgumentException::class)
@SmallTest
fun require_greater_than_zero() {
PercentDecimal(-1.0f)
}
@Test(expected = IllegalArgumentException::class)
@SmallTest
fun require_less_than_one() {
PercentDecimal(1.5f)
}
}

View File

@ -1,51 +0,0 @@
package cash.z.ecc.sdk.model
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
import cash.z.ecc.sdk.test.count
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class PersistableWalletTest {
@Test
@SmallTest
fun serialize() {
val persistableWallet = PersistableWalletFixture.new()
val jsonObject = persistableWallet.toJson()
assertEquals(4, jsonObject.keys().count())
assertTrue(jsonObject.has(PersistableWallet.KEY_VERSION))
assertTrue(jsonObject.has(PersistableWallet.KEY_NETWORK_ID))
assertTrue(jsonObject.has(PersistableWallet.KEY_SEED_PHRASE))
assertTrue(jsonObject.has(PersistableWallet.KEY_BIRTHDAY))
assertEquals(1, jsonObject.getInt(PersistableWallet.KEY_VERSION))
assertEquals(ZcashNetwork.Testnet.id, jsonObject.getInt(PersistableWallet.KEY_NETWORK_ID))
assertEquals(PersistableWalletFixture.SEED_PHRASE.joinToString(), jsonObject.getString(PersistableWallet.KEY_SEED_PHRASE))
// Birthday serialization is tested in a separate file
}
@Test
@SmallTest
fun round_trip() {
val persistableWallet = PersistableWalletFixture.new()
val deserialized = PersistableWallet.from(persistableWallet.toJson())
assertEquals(persistableWallet, deserialized)
assertFalse(persistableWallet === deserialized)
}
@Test
@SmallTest
fun toString_security() {
val actual = PersistableWalletFixture.new().toString()
assertFalse(actual.contains(SeedPhraseFixture.SEED_PHRASE))
}
}

View File

@ -1,26 +0,0 @@
package cash.z.ecc.sdk.model
import androidx.test.filters.SmallTest
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Test
class SeedPhraseTest {
@Test
@SmallTest
fun split_and_join() {
val seedPhrase = SeedPhrase.new(SeedPhraseFixture.SEED_PHRASE)
assertEquals(SeedPhraseFixture.SEED_PHRASE, seedPhrase.joinToString())
}
@Test
@SmallTest
fun security() {
val seedPhrase = SeedPhraseFixture.new()
seedPhrase.split.forEach {
assertFalse(seedPhrase.toString().contains(it))
}
}
}

View File

@ -1,18 +0,0 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
class WalletAddressTest {
@Test
@ExperimentalCoroutinesApi
fun unified_equals_different_instance() = runTest {
val one = WalletAddressFixture.unified()
val two = WalletAddressFixture.unified()
assertEquals(one, two)
}
}

View File

@ -1,21 +0,0 @@
package cash.z.ecc.sdk.model
import androidx.test.filters.SmallTest
import cash.z.ecc.sdk.fixture.WalletAddressesFixture
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class WalletAddressesTest {
@Test
@SmallTest
fun security() = runTest {
val walletAddresses = WalletAddressesFixture.new()
val actual = WalletAddressesFixture.new().toString()
assertFalse(actual.contains(walletAddresses.sapling.address))
assertFalse(actual.contains(walletAddresses.transparent.address))
assertFalse(actual.contains(walletAddresses.unified.address))
}
}

View File

@ -1,6 +1,7 @@
package cash.z.ecc.sdk.model
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.fixture.Zip321UriBuildFixture
import cash.z.ecc.sdk.fixture.Zip321UriParseFixture

View File

@ -1,10 +0,0 @@
@file:Suppress("ktlint:filename")
package cash.z.ecc.sdk.test
fun <T> Iterator<T>.count(): Int {
var count = 0
forEach { count++ }
return count
}

View File

@ -4,7 +4,7 @@ package cash.z.ecc.sdk
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.sdk.model.ZecSend
import cash.z.ecc.android.sdk.model.ZecSend
suspend fun Synchronizer.send(spendingKey: UnifiedSpendingKey, send: ZecSend) = sendToAddress(
spendingKey,

View File

@ -1,9 +0,0 @@
@file:Suppress("ktlint:filename")
package cash.z.ecc.sdk.ext
import java.nio.charset.Charset
private val UTF_8 = Charset.forName("UTF-8")
fun String.sizeInUtf8Bytes() = toByteArray(UTF_8).size

View File

@ -1,18 +0,0 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.sdk.model.CurrencyConversion
import cash.z.ecc.sdk.model.FiatCurrency
import kotlinx.datetime.Instant
import kotlinx.datetime.toInstant
object CurrencyConversionFixture {
val FIAT_CURRENCY = FiatCurrencyFixture.new()
val TIMESTAMP = "2022-07-08T11:51:44Z".toInstant()
const val PRICE_OF_ZEC = 54.98
fun new(
fiatCurrency: FiatCurrency = FIAT_CURRENCY,
timestamp: Instant = TIMESTAMP,
priceOfZec: Double = PRICE_OF_ZEC
) = CurrencyConversion(fiatCurrency, timestamp, priceOfZec)
}

View File

@ -1,9 +0,0 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.sdk.model.FiatCurrency
object FiatCurrencyFixture {
const val USD = "USD"
fun new(code: String = USD) = FiatCurrency(code)
}

View File

@ -1,6 +1,6 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.sdk.model.Memo
import cash.z.ecc.android.sdk.model.Memo
object MemoFixture {
const val MEMO_STRING = "Thanks for lunch"

View File

@ -1,9 +1,9 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.android.sdk.model.SeedPhrase
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.sdk.model.PersistableWallet
import cash.z.ecc.sdk.model.SeedPhrase
object PersistableWalletFixture {

View File

@ -1,6 +1,6 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.sdk.model.SeedPhrase
import cash.z.ecc.android.sdk.model.SeedPhrase
object SeedPhraseFixture {
@Suppress("MaxLineLength")

View File

@ -1,18 +0,0 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.sdk.model.WalletAddress
object WalletAddressFixture {
// These fixture values are derived from the secret defined in PersistableWalletFixture
@Suppress("MaxLineLength")
const val UNIFIED_ADDRESS_STRING = "utest1qwerty6tj6989fcf0r57aavfnypj0krtxnmz93ty7ujr7d0spdxnz97kfdcugq9ndglvyg7v89m78dparu33q0putwaf2kvnsypsh8juzmcdvnedjlrrwhel9ldr203p7wc922luxup"
@Suppress("MaxLineLength")
const val SAPLING_ADDRESS_STRING = "zs1hf72k87gev2qnvg9228vn2xt97adfelju2hm2ap4xwrxkau5dz56mvkeseer3u8283wmy7skt4u"
const val TRANSPARENT_ADDRESS_STRING = "t1QZMTZaU1EwXppCLL5dR6U9y2M4ph3CSPK"
suspend fun unified() = WalletAddress.Unified.new(UNIFIED_ADDRESS_STRING)
suspend fun sapling() = WalletAddress.Sapling.new(SAPLING_ADDRESS_STRING)
suspend fun transparent() = WalletAddress.Transparent.new(TRANSPARENT_ADDRESS_STRING)
}

View File

@ -1,17 +0,0 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.sdk.model.WalletAddresses
object WalletAddressesFixture {
suspend fun new(
unified: String = WalletAddressFixture.UNIFIED_ADDRESS_STRING,
sapling: String = WalletAddressFixture.SAPLING_ADDRESS_STRING,
transparent: String = WalletAddressFixture.TRANSPARENT_ADDRESS_STRING
) = WalletAddresses(
WalletAddress.Unified.new(unified),
WalletAddress.Sapling.new(sapling),
WalletAddress.Transparent.new(transparent)
)
}

View File

@ -1,7 +1,8 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.sdk.model.ZecRequest
import cash.z.ecc.sdk.model.ZecRequestMessage

View File

@ -1,9 +1,10 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.Memo
import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.model.Memo
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.sdk.model.ZecSend
import cash.z.ecc.android.sdk.model.ZecSend
object ZecSendFixture {
const val ADDRESS: String = WalletAddressFixture.UNIFIED_ADDRESS_STRING

View File

@ -1,7 +1,8 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.sdk.model.ZecRequest
import cash.z.ecc.sdk.model.ZecRequestMessage
import kotlinx.coroutines.runBlocking

View File

@ -1,7 +1,8 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.sdk.model.ZecRequest
import cash.z.ecc.sdk.model.ZecRequestMessage

View File

@ -1,6 +1,7 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.model.PercentDecimal
fun CompactBlockProcessor.ProcessorInfo.downloadProgress(): PercentDecimal {
val lastDownloadRangeSnapshot = lastDownloadRange

View File

@ -1,35 +0,0 @@
package cash.z.ecc.sdk.model
import kotlinx.datetime.Instant
/**
* Represents a snapshot in time of a currency conversion rate.
*
* @param fiatCurrency The fiat currency for this conversion.
* @param timestamp The timestamp when this conversion was obtained. This value is returned by
* the server so it shouldn't have issues with client-side clock inaccuracy.
* @param priceOfZec The conversion rate of ZEC to the fiat currency.
*/
data class CurrencyConversion(
val fiatCurrency: FiatCurrency,
val timestamp: Instant,
val priceOfZec: Double
) {
init {
require(priceOfZec > 0) { "priceOfZec must be greater than 0" }
require(priceOfZec.isFinite()) { "priceOfZec must be finite" }
}
}
/**
* Represents an ISO 4217 currency code.
*/
@Suppress("MagicNumber")
data class FiatCurrency(val code: String) {
init {
require(code.length == 3) { "Fiat currency code must be 3 characters long." }
// TODO [#532] https://github.com/zcash/secant-android-wallet/issues/532
// Add another check to make sure the code is in the known ISO currency code list.
}
}

View File

@ -1,24 +0,0 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.sdk.ext.sizeInUtf8Bytes
@JvmInline
value class Memo(val value: String) {
init {
require(isWithinMaxLength(value)) {
"Memo length in bytes must be less than $MAX_MEMO_LENGTH_BYTES but " +
"actually has length ${value.sizeInUtf8Bytes()}"
}
}
companion object {
/**
* The decoded memo contents MUST NOT exceed 512 bytes.
*
* https://zips.z.cash/zip-0321
*/
private const val MAX_MEMO_LENGTH_BYTES = 512
fun isWithinMaxLength(memoString: String) = memoString.sizeInUtf8Bytes() <= MAX_MEMO_LENGTH_BYTES
}
}

View File

@ -1,25 +0,0 @@
package cash.z.ecc.sdk.model
/**
* @param decimal A percent represented as a `Double` decimal value in the range of [0, 1].
*/
@JvmInline
value class PercentDecimal(val decimal: Float) {
init {
require(decimal >= MIN)
require(decimal <= MAX)
}
companion object {
const val MIN = 0.0f
const val MAX = 1.0f
val ZERO_PERCENT = PercentDecimal(MIN)
val ONE_HUNDRED_PERCENT = PercentDecimal(MAX)
fun newLenient(decimal: Float) = PercentDecimal(
decimal
.coerceAtLeast(MIN)
.coerceAtMost(MAX)
)
}
}

View File

@ -1,89 +0,0 @@
package cash.z.ecc.sdk.model
import android.app.Application
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toEntropy
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.sdk.type.fromResources
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
/**
* Represents everything needed to save and restore a wallet.
*/
data class PersistableWallet(
val network: ZcashNetwork,
val birthday: BlockHeight?,
val seedPhrase: SeedPhrase
) {
/**
* @return Wallet serialized to JSON format, suitable for long-term encrypted storage.
*/
// Note: We're using a hand-crafted serializer so that we're less likely to have accidental
// breakage from reflection or annotation based methods, and so that we can carefully manage versioning.
fun toJson() = JSONObject().apply {
put(KEY_VERSION, VERSION_1)
put(KEY_NETWORK_ID, network.id)
birthday?.let {
put(KEY_BIRTHDAY, it.value)
}
put(KEY_SEED_PHRASE, seedPhrase.joinToString())
}
// For security, intentionally override the toString method to reduce risk of accidentally logging secrets
override fun toString() = "PersistableWallet"
companion object {
private const val VERSION_1 = 1
internal const val KEY_VERSION = "v"
internal const val KEY_NETWORK_ID = "network_ID"
internal const val KEY_BIRTHDAY = "birthday"
internal const val KEY_SEED_PHRASE = "seed_phrase"
fun from(jsonObject: JSONObject): PersistableWallet {
when (val version = jsonObject.getInt(KEY_VERSION)) {
VERSION_1 -> {
val network = run {
val networkId = jsonObject.getInt(KEY_NETWORK_ID)
ZcashNetwork.from(networkId)
}
val birthday = if (jsonObject.has(KEY_BIRTHDAY)) {
val birthdayBlockHeightLong = jsonObject.getLong(KEY_BIRTHDAY)
BlockHeight.new(network, birthdayBlockHeightLong)
} else {
null
}
val seedPhrase = jsonObject.getString(KEY_SEED_PHRASE)
return PersistableWallet(network, birthday, SeedPhrase.new(seedPhrase))
}
else -> {
throw IllegalArgumentException("Unsupported version $version")
}
}
}
/**
* @return A new PersistableWallet with a random seed phrase.
*/
suspend fun new(application: Application): PersistableWallet {
val zcashNetwork = ZcashNetwork.fromResources(application)
val birthday = BlockHeight.ofLatestCheckpoint(application, zcashNetwork)
val seedPhrase = newSeedPhrase()
return PersistableWallet(zcashNetwork, birthday, seedPhrase)
}
}
}
// Using IO context because of https://github.com/zcash/kotlin-bip39/issues/13
private suspend fun newMnemonic() = withContext(Dispatchers.IO) {
Mnemonics.MnemonicCode(cash.z.ecc.android.bip39.Mnemonics.WordCount.COUNT_24.toEntropy()).words
}
private suspend fun newSeedPhrase() = SeedPhrase(newMnemonic().map { it.concatToString() })

View File

@ -1,30 +0,0 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
// Consider using ImmutableList here
data class SeedPhrase(val split: List<String>) {
init {
require(SEED_PHRASE_SIZE == split.size) {
"Seed phrase must split into $SEED_PHRASE_SIZE words but was ${split.size}"
}
}
// For security, intentionally override the toString method to reduce risk of accidentally logging secrets
override fun toString() = "SeedPhrase"
fun joinToString() = split.joinToString(DEFAULT_DELIMITER)
suspend fun toByteArray() = withContext(Dispatchers.IO) { Mnemonics.MnemonicCode(joinToString()).toSeed() }
companion object {
const val SEED_PHRASE_SIZE = 24
const val DEFAULT_DELIMITER = " "
fun new(phrase: String) = SeedPhrase(phrase.split(DEFAULT_DELIMITER))
}
}

View File

@ -1,6 +1,7 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.sdk.model.SeedPhrase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Locale

View File

@ -1,46 +0,0 @@
package cash.z.ecc.sdk.model
sealed class WalletAddress(val address: String) {
class Unified private constructor(address: String) : WalletAddress(address) {
companion object {
suspend fun new(address: String): WalletAddress.Unified {
// https://github.com/zcash/zcash-android-wallet-sdk/issues/342
// TODO [#342]: refactor SDK to enable direct calls for address verification
return WalletAddress.Unified(address)
}
}
}
class Sapling private constructor(address: String) : WalletAddress(address) {
companion object {
suspend fun new(address: String): Sapling {
// TODO [#342]: https://github.com/zcash/zcash-android-wallet-sdk/issues/342
// TODO [#342]: refactor SDK to enable direct calls for address verification
return Sapling(address)
}
}
}
class Transparent private constructor(address: String) : WalletAddress(address) {
companion object {
suspend fun new(address: String): Transparent {
// TODO [#342]: https://github.com/zcash/zcash-android-wallet-sdk/issues/342
// TODO [#342]: refactor SDK to enable direct calls for address verification
return Transparent(address)
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as WalletAddress
if (address != other.address) return false
return true
}
override fun hashCode() = address.hashCode()
}

View File

@ -1,33 +0,0 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.model.Account
data class WalletAddresses(
val unified: WalletAddress.Unified,
val sapling: WalletAddress.Sapling,
val transparent: WalletAddress.Transparent
) {
// Override to prevent leaking details in logs
override fun toString() = "WalletAddresses"
companion object {
suspend fun new(synchronizer: Synchronizer): WalletAddresses {
val unifiedAddress = WalletAddress.Unified.new(
synchronizer.getUnifiedAddress(Account.DEFAULT)
)
val saplingAddress = WalletAddress.Sapling.new(
synchronizer.getSaplingAddress(Account.DEFAULT)
)
val transparentAddress = WalletAddress.Transparent.new(
synchronizer.getTransparentAddress(Account.DEFAULT)
)
return WalletAddresses(
unified = unifiedAddress,
sapling = saplingAddress,
transparent = transparentAddress
)
}
}
}

View File

@ -1,5 +1,6 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.fixture.Zip321UriBuildFixture
import cash.z.ecc.sdk.fixture.Zip321UriParseFixture

View File

@ -1,7 +0,0 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.sdk.model.Zatoshi
data class ZecSend(val destination: WalletAddress, val amount: Zatoshi, val memo: Memo) {
companion object
}

View File

@ -1,38 +0,0 @@
plugins {
id("com.android.library")
kotlin("android")
id("secant.android-build-conventions")
id("wtf.emulator.gradle")
id("secant.emulator-wtf-conventions")
id("secant.jacoco-conventions")
}
android {
namespace = "cash.z.ecc.sdk.ext.ui"
testNamespace = "cash.z.ecc.sdk.ext.ui.test"
resourcePrefix = "co_electriccoin_zcash_"
}
dependencies {
implementation(projects.sdkExtLib)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
androidTestImplementation(libs.bundles.androidx.test)
androidTestImplementation(libs.kotlin.test)
androidTestUtil(libs.androidx.test.services) {
artifact {
type = "apk"
}
}
if (project.property("IS_USE_TEST_ORCHESTRATOR").toString().toBoolean()) {
androidTestUtil(libs.androidx.test.orchestrator) {
artifact {
type = "apk"
}
}
}
}

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
>
<!-- For test coverage -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<!-- For test coverage on API 29 only -->
<application
android:label="sdk-ext-ui-test"
android:requestLegacyExternalStorage="true" />
</manifest>

View File

@ -1,109 +0,0 @@
package cash.z.ecc.sdk.ext.ui.model
import androidx.test.filters.SmallTest
import cash.z.ecc.sdk.ext.ui.fixture.LocaleFixture
import cash.z.ecc.sdk.ext.ui.fixture.MonetarySeparatorsFixture
import cash.z.ecc.sdk.ext.ui.toFiatCurrencyState
import cash.z.ecc.sdk.fixture.CurrencyConversionFixture
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import org.junit.Test
import kotlin.test.assertIs
import kotlin.time.Duration.Companion.seconds
class FiatCurrencyConversionRateStateTest {
@Test
@SmallTest
fun future_near() {
val zatoshi = ZatoshiFixture.new()
val frozenClock = FrozenClock(
CurrencyConversionFixture.TIMESTAMP - FiatCurrencyConversionRateState.FUTURE_CUTOFF_AGE_INCLUSIVE
)
val currencyConversion = CurrencyConversionFixture.new()
val result = zatoshi.toFiatCurrencyState(currencyConversion, LocaleFixture.new(), MonetarySeparatorsFixture.new(), frozenClock)
assertIs<FiatCurrencyConversionRateState.Current>(result)
}
@Test
@SmallTest
fun future_far() {
val zatoshi = ZatoshiFixture.new()
val frozenClock = FrozenClock(
CurrencyConversionFixture.TIMESTAMP - FiatCurrencyConversionRateState.FUTURE_CUTOFF_AGE_INCLUSIVE - 1.seconds
)
val currencyConversion = CurrencyConversionFixture.new()
val result = zatoshi.toFiatCurrencyState(currencyConversion, LocaleFixture.new(), MonetarySeparatorsFixture.new(), frozenClock)
assertIs<FiatCurrencyConversionRateState.Unavailable>(result)
}
@Test
@SmallTest
fun current() {
val zatoshi = ZatoshiFixture.new()
val frozenClock = FrozenClock(CurrencyConversionFixture.TIMESTAMP)
val currencyConversion = CurrencyConversionFixture.new(
timestamp = CurrencyConversionFixture.TIMESTAMP - 1.seconds
)
val result = zatoshi.toFiatCurrencyState(currencyConversion, LocaleFixture.new(), MonetarySeparatorsFixture.new(), frozenClock)
assertIs<FiatCurrencyConversionRateState.Current>(result)
}
@Test
@SmallTest
fun stale() {
val zatoshi = ZatoshiFixture.new()
val frozenClock = FrozenClock(CurrencyConversionFixture.TIMESTAMP)
val currencyConversion = CurrencyConversionFixture.new(
timestamp = CurrencyConversionFixture.TIMESTAMP - FiatCurrencyConversionRateState.CURRENT_CUTOFF_AGE_INCLUSIVE - 1.seconds
)
val result = zatoshi.toFiatCurrencyState(currencyConversion, LocaleFixture.new(), MonetarySeparatorsFixture.new(), frozenClock)
assertIs<FiatCurrencyConversionRateState.Stale>(result)
}
@Test
@SmallTest
fun too_stale() {
val zatoshi = ZatoshiFixture.new()
val frozenClock = FrozenClock(CurrencyConversionFixture.TIMESTAMP)
val currencyConversion = CurrencyConversionFixture.new(
timestamp = CurrencyConversionFixture.TIMESTAMP - FiatCurrencyConversionRateState.STALE_CUTOFF_AGE_INCLUSIVE - 1.seconds
)
val result = zatoshi.toFiatCurrencyState(currencyConversion, LocaleFixture.new(), MonetarySeparatorsFixture.new(), frozenClock)
assertIs<FiatCurrencyConversionRateState.Unavailable>(result)
}
@Test
@SmallTest
fun null_conversion_rate() {
val zatoshi = ZatoshiFixture.new()
val result = zatoshi.toFiatCurrencyState(null, LocaleFixture.new(), MonetarySeparatorsFixture.new())
assertIs<FiatCurrencyConversionRateState.Unavailable>(result)
}
}
private class FrozenClock(private val timestamp: Instant) : Clock {
override fun now() = timestamp
}

View File

@ -1,26 +0,0 @@
package cash.z.ecc.sdk.ext.ui.model
import androidx.test.filters.SmallTest
import org.junit.Test
import kotlin.test.assertEquals
class LocaleTest {
@Test
@SmallTest
fun toKotlinLocale() {
val javaLocale = java.util.Locale.forLanguageTag("en-US")
val kotlinLocale = javaLocale.toKotlinLocale()
assertEquals("en", kotlinLocale.language)
assertEquals("US", kotlinLocale.region)
assertEquals(null, kotlinLocale.variant)
}
@Test
@SmallTest
fun toJavaLocale() {
val kotlinLocale = cash.z.ecc.sdk.ext.ui.model.Locale("en", "US", null)
val javaLocale = kotlinLocale.toJavaLocale()
assertEquals("en-US", javaLocale.toLanguageTag())
}
}

View File

@ -1,77 +0,0 @@
package cash.z.ecc.sdk.ext.ui.model
import androidx.test.filters.SmallTest
import cash.z.ecc.sdk.ext.ui.fixture.LocaleFixture
import cash.z.ecc.sdk.ext.ui.fixture.MonetarySeparatorsFixture
import cash.z.ecc.sdk.ext.ui.toFiatString
import cash.z.ecc.sdk.fixture.CurrencyConversionFixture
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import org.junit.Test
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class ZatoshiExtTest {
companion object {
private val EN_US_SEPARATORS = MonetarySeparatorsFixture.new()
private val CURRENCY_CONVERSION = CurrencyConversionFixture.new()
}
@Test
@SmallTest
fun zero_zatoshi_to_fiat_conversion_test() {
val zatoshi = ZatoshiFixture.new(0L)
val fiatString = zatoshi.toFiatString(CURRENCY_CONVERSION, LocaleFixture.new(), EN_US_SEPARATORS)
fiatString.also {
assertNotNull(it)
assertTrue(it.isNotEmpty())
assertTrue(it.contains("0"))
assertTrue(it.isValidNumber(EN_US_SEPARATORS))
}
}
@Test
@SmallTest
fun regular_zatoshi_to_fiat_conversion_test() {
val zatoshi = ZatoshiFixture.new(123_456_789L)
val fiatString = zatoshi.toFiatString(CURRENCY_CONVERSION, LocaleFixture.new(), EN_US_SEPARATORS)
fiatString.also {
assertNotNull(it)
assertTrue(it.isNotEmpty())
assertTrue(it.isValidNumber(EN_US_SEPARATORS))
}
}
@Test
@SmallTest
fun rounded_zatoshi_to_fiat_conversion_test() {
val roundedZatoshi = ZatoshiFixture.new(100_000_000L)
val roundedCurrencyConversion = CurrencyConversionFixture.new(
priceOfZec = 100.0
)
val fiatString = roundedZatoshi.toFiatString(
roundedCurrencyConversion,
LocaleFixture.new(),
EN_US_SEPARATORS
)
fiatString.also {
assertNotNull(it)
assertTrue(it.isNotEmpty())
assertTrue(it.isValidNumber(EN_US_SEPARATORS))
assertTrue("$100${EN_US_SEPARATORS.decimal}00" == it)
}
}
}
private fun Char.isDigitOrSeparator(separators: MonetarySeparators): Boolean {
return this.isDigit() || this == separators.decimal || this == separators.grouping
}
private fun String.isValidNumber(separators: MonetarySeparators): Boolean {
return this
.drop(1) // remove currency symbol
.all { return it.isDigitOrSeparator(separators) }
}

View File

@ -1,100 +0,0 @@
package cash.z.ecc.sdk.ext.ui.model
import android.content.Context
import android.content.res.Configuration
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.ext.ui.fixture.MonetarySeparatorsFixture
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Test
import java.util.Locale
import kotlin.test.assertNotNull
import kotlin.test.assertNull
class ZecStringTest {
companion object {
private val EN_US_MONETARY_SEPARATORS = MonetarySeparatorsFixture.new()
private val context = run {
val applicationContext = ApplicationProvider.getApplicationContext<Context>()
val enUsConfiguration = Configuration(applicationContext.resources.configuration).apply {
setLocale(Locale.US)
}
applicationContext.createConfigurationContext(enUsConfiguration)
}
}
@Test
fun empty_string() {
val actual = Zatoshi.fromZecString(context, "", EN_US_MONETARY_SEPARATORS)
val expected = null
assertEquals(expected, actual)
}
@Test
fun decimal_monetary_separator() {
val actual = Zatoshi.fromZecString(context, "1.13", EN_US_MONETARY_SEPARATORS)
val expected = Zatoshi(113000000L)
assertEquals(expected, actual)
}
@Test
fun comma_grouping_separator() {
val actual = Zatoshi.fromZecString(context, "1,130", EN_US_MONETARY_SEPARATORS)
val expected = Zatoshi(113000000000L)
assertEquals(expected, actual)
}
@Test
fun decimal_monetary_and() {
val actual = Zatoshi.fromZecString(context, "1,130", EN_US_MONETARY_SEPARATORS)
val expected = Zatoshi(113000000000L)
assertEquals(expected, actual)
}
@Test
@Ignore("https://github.com/zcash/zcash-android-wallet-sdk/issues/412")
fun toZecString() {
val expected = "1.13000000"
val actual = Zatoshi(113000000).toZecString()
assertEquals(expected, actual)
}
@Test
@Ignore("https://github.com/zcash/zcash-android-wallet-sdk/issues/412")
fun round_trip() {
val expected = Zatoshi(113000000L)
val actual = Zatoshi.fromZecString(context, expected.toZecString(), EN_US_MONETARY_SEPARATORS)
assertEquals(expected, actual)
}
@Test
fun parse_bad_string() {
assertNull(Zatoshi.fromZecString(context, "", EN_US_MONETARY_SEPARATORS))
assertNull(Zatoshi.fromZecString(context, "+@#$~^&*=", EN_US_MONETARY_SEPARATORS))
assertNull(Zatoshi.fromZecString(context, "asdf", EN_US_MONETARY_SEPARATORS))
}
@Test
fun parse_invalid_numbers() {
assertNull(Zatoshi.fromZecString(context, "", EN_US_MONETARY_SEPARATORS))
assertNull(Zatoshi.fromZecString(context, "1,2", EN_US_MONETARY_SEPARATORS))
assertNull(Zatoshi.fromZecString(context, "1,23,", EN_US_MONETARY_SEPARATORS))
assertNull(Zatoshi.fromZecString(context, "1,234,", EN_US_MONETARY_SEPARATORS))
}
@Test
@SmallTest
fun overflow_number_test() {
assertNotNull(Zatoshi.fromZecString(context, "21,000,000", EN_US_MONETARY_SEPARATORS))
assertNull(Zatoshi.fromZecString(context, "21,000,001", EN_US_MONETARY_SEPARATORS))
}
}

View File

@ -1,169 +0,0 @@
package cash.z.ecc.sdk.ext.ui.regex
import androidx.test.filters.SmallTest
import cash.z.ecc.sdk.ext.ui.R
import cash.z.ecc.sdk.ext.ui.ZecStringExt
import cash.z.ecc.sdk.ext.ui.fixture.MonetarySeparatorsFixture
import cash.z.ecc.sdk.ext.ui.test.getStringResourceWithArgs
import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class ZecStringExtTest {
companion object {
private val EN_US_SEPARATORS = MonetarySeparatorsFixture.new()
}
private fun getContinuousRegex(): Regex {
return getStringResourceWithArgs(
R.string.co_electriccoin_zcash_zec_amount_regex_continuous_filter,
arrayOf(
EN_US_SEPARATORS.grouping,
EN_US_SEPARATORS.decimal
)
).toRegex()
}
private fun getConfirmRegex(): Regex {
return getStringResourceWithArgs(
R.string.co_electriccoin_zcash_zec_amount_regex_confirm_filter,
arrayOf(
EN_US_SEPARATORS.grouping,
EN_US_SEPARATORS.decimal
)
).toRegex()
}
@Test
@SmallTest
fun check_continuous_regex_validity() {
val regexString = getStringResourceWithArgs(
R.string.co_electriccoin_zcash_zec_amount_regex_continuous_filter,
arrayOf(
EN_US_SEPARATORS.grouping,
EN_US_SEPARATORS.decimal
)
)
assertNotNull(regexString)
val regexAmountChecker = regexString.toRegex()
regexAmountChecker.also {
assertNotNull(regexAmountChecker)
assertTrue(regexAmountChecker.pattern.isNotEmpty())
}
}
@Test
@SmallTest
fun check_confirm_regex_validity() {
val regexString = getStringResourceWithArgs(
R.string.co_electriccoin_zcash_zec_amount_regex_confirm_filter,
arrayOf(
EN_US_SEPARATORS.grouping,
EN_US_SEPARATORS.decimal
)
)
assertNotNull(regexString)
val regexAmountChecker = regexString.toRegex()
regexAmountChecker.also {
assertNotNull(regexAmountChecker)
assertTrue(regexAmountChecker.pattern.isNotEmpty())
}
}
@Test
@SmallTest
fun check_continuous_regex_functionality_valid_inputs() {
getContinuousRegex().also {
assertTrue(it.matches(""))
assertTrue(it.matches("123"))
assertTrue(it.matches("${EN_US_SEPARATORS.decimal}"))
assertTrue(it.matches("${EN_US_SEPARATORS.decimal}123"))
assertTrue(it.matches("123${EN_US_SEPARATORS.grouping}"))
assertTrue(it.matches("123${EN_US_SEPARATORS.grouping}456"))
assertTrue(it.matches("123${EN_US_SEPARATORS.decimal}"))
assertTrue(it.matches("123${EN_US_SEPARATORS.decimal}456"))
assertTrue(it.matches("123${EN_US_SEPARATORS.grouping}456${EN_US_SEPARATORS.decimal}"))
assertTrue(it.matches("123${EN_US_SEPARATORS.grouping}456${EN_US_SEPARATORS.decimal}789"))
assertTrue(it.matches("1${EN_US_SEPARATORS.grouping}234${EN_US_SEPARATORS.grouping}567${EN_US_SEPARATORS.decimal}00"))
}
}
@Test
@SmallTest
fun check_continuous_regex_functionality_invalid_inputs() {
getContinuousRegex().also {
assertFalse(it.matches("aaa"))
assertFalse(it.matches("123aaa"))
assertFalse(it.matches("${EN_US_SEPARATORS.grouping}"))
assertFalse(it.matches("${EN_US_SEPARATORS.grouping}123"))
assertFalse(it.matches("123${EN_US_SEPARATORS.grouping}${EN_US_SEPARATORS.grouping}"))
assertFalse(it.matches("123${EN_US_SEPARATORS.decimal}${EN_US_SEPARATORS.decimal}"))
assertFalse(it.matches("1${EN_US_SEPARATORS.grouping}2${EN_US_SEPARATORS.grouping}3"))
assertFalse(it.matches("1${EN_US_SEPARATORS.decimal}2${EN_US_SEPARATORS.decimal}3"))
assertFalse(it.matches("1${EN_US_SEPARATORS.decimal}2${EN_US_SEPARATORS.grouping}3"))
}
}
@Test
@SmallTest
fun check_confirm_regex_functionality_valid_inputs() {
getConfirmRegex().also {
assertTrue(it.matches("123"))
assertTrue(it.matches(".123"))
assertTrue(it.matches("1,234"))
assertTrue(it.matches("1,234,567,890"))
assertTrue(it.matches("1.2"))
assertTrue(it.matches("123.4"))
assertTrue(it.matches("1.234"))
assertTrue(it.matches("1,123."))
assertTrue(it.matches("1,234.567"))
assertTrue(it.matches("1,234,567.890"))
}
}
@Test
@SmallTest
fun check_confirm_regex_functionality_invalid_inputs() {
getContinuousRegex().also {
assertFalse(it.matches("+@#$~^&*="))
assertFalse(it.matches("asdf"))
assertFalse(it.matches(".."))
assertFalse(it.matches(","))
assertFalse(it.matches(",,"))
assertFalse(it.matches(",."))
assertFalse(it.matches(".,"))
assertFalse(it.matches(",123"))
assertFalse(it.matches("1,2,3"))
assertFalse(it.matches("1.2,3,4"))
assertFalse(it.matches("123,,456"))
assertFalse(it.matches("123..456"))
assertFalse(it.matches("1.234,567"))
assertFalse(it.matches("1.234,567,890"))
}
}
@Test
@SmallTest
fun check_digits_between_grouping_separators_valid_test() {
assertTrue(ZecStringExt.checkFor3Digits(EN_US_SEPARATORS, "123"))
assertTrue(ZecStringExt.checkFor3Digits(EN_US_SEPARATORS, "1${EN_US_SEPARATORS.grouping}234"))
assertTrue(ZecStringExt.checkFor3Digits(EN_US_SEPARATORS, "1${EN_US_SEPARATORS.grouping}234${EN_US_SEPARATORS.grouping}"))
assertTrue(ZecStringExt.checkFor3Digits(EN_US_SEPARATORS, "1${EN_US_SEPARATORS.grouping}234${EN_US_SEPARATORS.grouping}5"))
assertTrue(ZecStringExt.checkFor3Digits(EN_US_SEPARATORS, "1${EN_US_SEPARATORS.grouping}234${EN_US_SEPARATORS.grouping}567${EN_US_SEPARATORS.grouping}8"))
}
@Test
@SmallTest
fun check_digits_between_grouping_separators_invalid_test() {
assertFalse(ZecStringExt.checkFor3Digits(EN_US_SEPARATORS, "1${EN_US_SEPARATORS.grouping}1${EN_US_SEPARATORS.grouping}2"))
assertFalse(ZecStringExt.checkFor3Digits(EN_US_SEPARATORS, "1${EN_US_SEPARATORS.grouping}12${EN_US_SEPARATORS.grouping}3"))
assertFalse(ZecStringExt.checkFor3Digits(EN_US_SEPARATORS, "1${EN_US_SEPARATORS.grouping}1234${EN_US_SEPARATORS.grouping}"))
assertFalse(ZecStringExt.checkFor3Digits(EN_US_SEPARATORS, "1${EN_US_SEPARATORS.grouping}123${EN_US_SEPARATORS.grouping}4${EN_US_SEPARATORS.grouping}"))
}
}

View File

@ -1,14 +0,0 @@
package cash.z.ecc.sdk.ext.ui.test
import android.content.Context
import android.text.TextUtils
import android.view.View
import androidx.annotation.StringRes
import androidx.test.core.app.ApplicationProvider
import java.util.Locale
fun getStringResource(@StringRes resId: Int) = ApplicationProvider.getApplicationContext<Context>().getString(resId)
fun getStringResourceWithArgs(@StringRes resId: Int, formatArgs: Array<Any>) = ApplicationProvider.getApplicationContext<Context>().getString(resId, *formatArgs)
fun isLocaleRTL(locale: Locale) = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<application />
</manifest>

View File

@ -1,90 +0,0 @@
@file:Suppress("ktlint:filename")
package cash.z.ecc.sdk.ext.ui
import cash.z.ecc.android.sdk.ext.Conversions
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.ext.ui.model.FiatCurrencyConversionRateState
import cash.z.ecc.sdk.ext.ui.model.Locale
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
import cash.z.ecc.sdk.ext.ui.model.toJavaLocale
import cash.z.ecc.sdk.model.CurrencyConversion
import kotlinx.datetime.Clock
import java.math.BigDecimal
import java.math.MathContext
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.Currency
import kotlin.time.Duration
fun Zatoshi.toFiatCurrencyState(
currencyConversion: CurrencyConversion?,
locale: Locale,
monetarySeparators: MonetarySeparators,
clock: Clock = Clock.System
): FiatCurrencyConversionRateState {
if (currencyConversion == null) {
return FiatCurrencyConversionRateState.Unavailable
}
val fiatCurrencyConversionRate = toFiatString(currencyConversion, locale, monetarySeparators)
val currentSystemTime = clock.now()
val age = currentSystemTime - currencyConversion.timestamp
return if (age < Duration.ZERO && age.absoluteValue > FiatCurrencyConversionRateState.FUTURE_CUTOFF_AGE_INCLUSIVE) {
// Special case if the device's clock is set to the future.
// TODO [#535]: Consider using NTP requests to get the correct time instead of relying on the device's clock.
FiatCurrencyConversionRateState.Unavailable
} else if (age <= FiatCurrencyConversionRateState.CURRENT_CUTOFF_AGE_INCLUSIVE) {
FiatCurrencyConversionRateState.Current(fiatCurrencyConversionRate)
} else if (age <= FiatCurrencyConversionRateState.STALE_CUTOFF_AGE_INCLUSIVE) {
FiatCurrencyConversionRateState.Stale(fiatCurrencyConversionRate)
} else {
FiatCurrencyConversionRateState.Unavailable
}
}
fun Zatoshi.toFiatString(
currencyConversion: CurrencyConversion,
locale: Locale,
monetarySeparators: MonetarySeparators
) =
convertZatoshiToZecDecimal()
.convertZecDecimalToFiatDecimal(BigDecimal(currencyConversion.priceOfZec))
.convertFiatDecimalToFiatString(
Currency.getInstance(currencyConversion.fiatCurrency.code),
locale.toJavaLocale(),
monetarySeparators
)
private fun Zatoshi.convertZatoshiToZecDecimal(): BigDecimal {
return BigDecimal(value, MathContext.DECIMAL128).divide(
Conversions.ONE_ZEC_IN_ZATOSHI,
MathContext.DECIMAL128
).setScale(Conversions.ZEC_FORMATTER.maximumFractionDigits, RoundingMode.HALF_EVEN)
}
private fun BigDecimal.convertZecDecimalToFiatDecimal(zecPrice: BigDecimal): BigDecimal {
return multiply(zecPrice, MathContext.DECIMAL128)
}
private fun BigDecimal.convertFiatDecimalToFiatString(
fiatCurrency: Currency,
locale: java.util.Locale,
monetarySeparators: MonetarySeparators
): String {
return NumberFormat.getCurrencyInstance(locale).apply {
currency = fiatCurrency
roundingMode = RoundingMode.HALF_EVEN
if (this is DecimalFormat) {
decimalFormatSymbols.apply {
decimalSeparator = monetarySeparators.decimal
monetaryDecimalSeparator = monetarySeparators.decimal
groupingSeparator = monetarySeparators.grouping
}
}
}.format(this)
}

View File

@ -1,53 +0,0 @@
package cash.z.ecc.sdk.ext.ui
import android.content.Context
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
import cash.z.ecc.sdk.ext.ui.model.fromZecString
import cash.z.ecc.sdk.model.Memo
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.sdk.model.ZecSend
import kotlinx.coroutines.runBlocking
object ZecSendExt {
fun new(
context: Context,
destinationString: String,
zecString: String,
memoString: String,
monetarySeparators: MonetarySeparators
): ZecSendValidation {
// This runBlocking shouldn't have a performance impact, since everything needs to be loaded at this point.
// However it would be better to eliminate it entirely.
val destination = runBlocking { WalletAddress.Unified.new(destinationString) }
val amount = Zatoshi.fromZecString(context, zecString, monetarySeparators)
val memo = Memo(memoString)
val validationErrors = buildSet {
if (null == amount) {
add(ZecSendValidation.Invalid.ValidationError.INVALID_AMOUNT)
}
// TODO [#250]: Implement all validation
// TODO [#342]: https://github.com/zcash/zcash-android-wallet-sdk/issues/342
}
return if (validationErrors.isEmpty()) {
ZecSendValidation.Valid(ZecSend(destination, amount!!, memo))
} else {
ZecSendValidation.Invalid(validationErrors)
}
}
sealed class ZecSendValidation {
data class Valid(val zecSend: ZecSend) : ZecSendValidation()
data class Invalid(val validationErrors: Set<ValidationError>) : ZecSendValidation() {
enum class ValidationError {
INVALID_ADDRESS,
INVALID_AMOUNT,
INVALID_MEMO
}
}
}
}

View File

@ -1,91 +0,0 @@
package cash.z.ecc.sdk.ext.ui
import android.content.Context
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
object ZecStringExt {
private const val DIGITS_BETWEEN_GROUP_SEPARATORS = 3
/**
* Builds filter with current local monetary separators for continuous input checking. The
* solution is built upon regex validation and other common string validation checks.
*
* Regex example: ^([0-9]*([0-9]+([,]$|[,][0-9]+))*([.]$|[.][0-9]+)?)?$
* Inputs may differ according to user locale.
*
* Valid amounts: "" . | .123 | 123, | 123. | 123,456 | 123.456 | 123,456.789 | 123,456,789 | 123,456,789.123 | etc.
* Invalid amounts: 123,, | 123,. | 123.. | ,123 | 123.456.789 | etc.
*
* @param context used for loading localized pattern from strings.xml
* @param separators which consist of localized monetary separators
* @param zecString to be validated
*
* @return true in case of validation success, false otherwise
*/
fun filterContinuous(context: Context, separators: MonetarySeparators, zecString: String): Boolean {
if (!context.getString(
R.string.co_electriccoin_zcash_zec_amount_regex_continuous_filter,
separators.grouping,
separators.decimal
).toRegex().matches(zecString) || !checkFor3Digits(separators, zecString)
) {
return false
}
return true
}
/**
* Checks for at least 3 digits between grouping separators.
*
* @param separators which consist of localized monetary separators
* @param zecString to be validated
*
* @return true in case of validation success, false otherwise
*/
fun checkFor3Digits(separators: MonetarySeparators, zecString: String): Boolean {
if (zecString.count { it == separators.grouping } >= 2) {
val groups = zecString.split(separators.grouping)
for (i in 1 until (groups.size - 1)) {
if (groups[i].length != DIGITS_BETWEEN_GROUP_SEPARATORS) {
return false
}
}
}
return true
}
/**
* Builds filter with current local monetary separators for validation of entered ZEC amount
* after confirm button is pressed. The solution is built upon regex validation and other common
* string validation checks.
*
* Regex example: ^([0-9]{1,3}(?:[,]?[0-9]{3})*)*(?:[0-9]*[.][0-9]*)?$
* Inputs may differ according to user locale.
*
* Valid amounts: 123 | .123 | 123. | 123, | 123.456 | 123,456 | 123,456.789 | 123,456,789 | 123,456,789.123 | etc.
* Invalid amounts: "" | , | . | 123,, | 123,. | 123.. | ,123 | 123.456.789 | etc.
*
* @param context used for loading localized pattern from strings.xml
* @param separators which consist of localized monetary separators
* @param zecString to be validated
*
* @return true in case of validation success, false otherwise
*/
fun filterConfirm(context: Context, separators: MonetarySeparators, zecString: String): Boolean {
if (zecString.isBlank() ||
zecString == separators.grouping.toString() ||
zecString == separators.decimal.toString()
) {
return false
}
return (
context.getString(
R.string.co_electriccoin_zcash_zec_amount_regex_confirm_filter,
separators.grouping,
separators.decimal
).toRegex().matches(zecString) && checkFor3Digits(separators, zecString)
)
}
}

View File

@ -1,15 +0,0 @@
package cash.z.ecc.sdk.ext.ui.fixture
import cash.z.ecc.sdk.ext.ui.model.Locale
object LocaleFixture {
const val LANGUAGE = "en"
const val COUNTRY = "US"
val VARIANT: String? = null
fun new(
language: String = LANGUAGE,
country: String? = COUNTRY,
variant: String? = VARIANT
) = Locale(language, country, variant)
}

View File

@ -1,13 +0,0 @@
package cash.z.ecc.sdk.ext.ui.fixture
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
object MonetarySeparatorsFixture {
const val US_GROUPING_SEPARATOR = ','
const val US_DECIMAL_SEPARATOR = '.'
fun new(
grouping: Char = US_GROUPING_SEPARATOR,
decimal: Char = US_DECIMAL_SEPARATOR
) = MonetarySeparators(grouping, decimal)
}

View File

@ -1,40 +0,0 @@
package cash.z.ecc.sdk.ext.ui.model
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.minutes
/**
* Represents a state of current fiat currency conversion to ZECs.
*/
sealed class FiatCurrencyConversionRateState {
/**
* @param formattedFiatValue A fiat value formatted as a localized string. E.g. $1.00.
*/
data class Current(val formattedFiatValue: String) : FiatCurrencyConversionRateState()
/**
* @param formattedFiatValue A fiat value formatted as a localized string. E.g. $1.00.
*/
data class Stale(val formattedFiatValue: String) : FiatCurrencyConversionRateState()
object Unavailable : FiatCurrencyConversionRateState()
companion object {
/**
* Cutoff negative age. Some users may intentionally set their clock forward 10 minutes
* because they're always late to things. This allows the app to mostly work for those users,
* while still failing if the clock is way off.
*/
val FUTURE_CUTOFF_AGE_INCLUSIVE = 10.minutes
/**
* Cutoff age for next attempt to refresh the conversion rate from the API.
*/
val CURRENT_CUTOFF_AGE_INCLUSIVE = 1.minutes
/**
* Cutoff age for displaying conversion rate from prior app launches or background refresh.
*/
val STALE_CUTOFF_AGE_INCLUSIVE = 1.days
}
}

View File

@ -1,31 +0,0 @@
package cash.z.ecc.sdk.ext.ui.model
data class Locale(val language: String, val region: String?, val variant: String?) {
companion object
}
fun Locale.toJavaLocale(): java.util.Locale {
return if (!region.isNullOrEmpty() && !variant.isNullOrEmpty()) {
java.util.Locale(language, region, variant)
} else if (!region.isNullOrEmpty() && variant.isNullOrEmpty()) {
java.util.Locale(language, region)
} else {
java.util.Locale(language)
}
}
fun java.util.Locale.toKotlinLocale(): Locale {
val resultCountry = if (country.isNullOrEmpty()) {
null
} else {
country
}
val resultVariant = if (variant.isNullOrEmpty()) {
null
} else {
variant
}
return Locale(language, resultCountry, resultVariant)
}

View File

@ -1,107 +0,0 @@
package cash.z.ecc.sdk.ext.ui.model
import android.content.Context
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.ext.ui.ZecStringExt
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.text.ParseException
import java.util.Locale
object ZecString {
fun allowedCharacters(monetarySeparators: MonetarySeparators) = buildSet<Char> {
add('0')
add('1')
add('2')
add('3')
add('4')
add('5')
add('6')
add('7')
add('8')
add('9')
add(monetarySeparators.decimal)
add(monetarySeparators.grouping)
}
}
data class MonetarySeparators(val grouping: Char, val decimal: Char) {
init {
require(grouping != decimal) { "Grouping and decimal separator cannot be the same character" }
}
companion object {
/**
* @return The current localized monetary separators. Do not cache this value, as it
* can change if the system Locale changes.
*/
fun current(): MonetarySeparators {
val decimalFormatSymbols = DecimalFormatSymbols.getInstance()
return MonetarySeparators(
decimalFormatSymbols.groupingSeparator,
decimalFormatSymbols.monetaryDecimalSeparator
)
}
}
}
private const val DECIMALS = 8
// TODO [#412]: https://github.com/zcash/zcash-android-wallet-sdk/issues/412
// The SDK needs to fix the API for currency conversion
fun Zatoshi.toZecString() = convertZatoshiToZecString(DECIMALS, DECIMALS)
/*
* ZEC is our own currency, so there's not going to be an existing localization that matches it perfectly.
*
* To ensure consistent behavior regardless of user Locale, use US localization except that we swap out the
* separator characters based on the user's current Locale. This should avoid unexpected surprises
* while also localizing the separator format.
*/
/**
* @return [zecString] parsed into Zatoshi or null if parsing failed.
*/
@SuppressWarnings("ReturnCount")
fun Zatoshi.Companion.fromZecString(
context: Context,
zecString: String,
monetarySeparators: MonetarySeparators
): Zatoshi? {
if (!ZecStringExt.filterConfirm(context, monetarySeparators, zecString)) {
return null
}
val symbols = DecimalFormatSymbols.getInstance(Locale.US).apply {
this.groupingSeparator = monetarySeparators.grouping
this.decimalSeparator = monetarySeparators.decimal
}
val localizedPattern = "#${monetarySeparators.grouping}##0${monetarySeparators.decimal}0#"
// TODO [#321]: https://github.com/zcash/secant-android-wallet/issues/321
val decimalFormat = DecimalFormat(localizedPattern, symbols).apply {
isParseBigDecimal = true
roundingMode = RoundingMode.HALF_EVEN // aka Bankers rounding
}
// TODO [#343]: https://github.com/zcash/secant-android-wallet/issues/343
val bigDecimal = try {
decimalFormat.parse(zecString) as BigDecimal
} catch (e: NumberFormatException) {
null
} catch (e: ParseException) {
null
}
@Suppress("SwallowedException")
return try {
bigDecimal.convertZecToZatoshi()
} catch (e: IllegalArgumentException) {
null
}
}

View File

@ -1,4 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="co_electriccoin_zcash_zec_amount_regex_continuous_filter" formatted="true" tools:ignore="TypographyDashes">^([0-9]*([0-9]+([<xliff:g id="group" example=",">%1$s</xliff:g>]$|[<xliff:g id="group" example=",">%1$s</xliff:g>][0-9]+))*([<xliff:g id="dec" example=".">%2$s</xliff:g>]$|[<xliff:g id="dec" example=".">%2$s</xliff:g>][0-9]+)?)?$</string>
<string name="co_electriccoin_zcash_zec_amount_regex_confirm_filter" formatted="true" tools:ignore="TypographyDashes">^([0-9]{1,3}(?:[<xliff:g id="group" example=",">%1$s</xliff:g>]?[0-9]{3})*)*(?:[0-9]*[<xliff:g id="group" example=".">%2$s</xliff:g>][0-9]*)?$</string>
</resources>

View File

@ -228,6 +228,7 @@ dependencyResolutionManagement {
library("play-update", "com.google.android.play:app-update:$playAppUpdateVersion")
library("play-update-ktx", "com.google.android.play:app-update-ktx:$playAppUpdateKtxVersion")
library("zcash-sdk", "cash.z.ecc.android:zcash-android-sdk:$zcashSdkVersion")
library("zcash-sdk-incubator", "cash.z.ecc.android:zcash-android-sdk-incubator:$zcashSdkVersion")
library("zcash-bip39", "cash.z.ecc.android:kotlin-bip39:$zcashBip39Version")
library("zcash-walletplgns", "cash.z.ecc.android:zcash-android-wallet-plugins:$zcashBip39Version")
library("zxing", "com.google.zxing:core:$zxingVersion")
@ -311,7 +312,6 @@ include("crash-android-lib")
include("preference-api-lib")
include("preference-impl-android-lib")
include("sdk-ext-lib")
include("sdk-ext-ui-lib")
include("spackle-lib")
include("spackle-android-lib")
include("test-lib")

View File

@ -68,6 +68,7 @@ dependencies {
implementation(libs.kotlinx.coroutines.guava)
implementation(libs.kotlinx.datetime)
implementation(libs.zcash.sdk)
implementation(libs.zcash.sdk.incubator)
implementation(libs.zcash.bip39)
implementation(libs.zxing)
@ -76,7 +77,6 @@ dependencies {
implementation(projects.preferenceApiLib)
implementation(projects.preferenceImplAndroidLib)
implementation(projects.sdkExtLib)
implementation(projects.sdkExtUiLib)
implementation(projects.spackleAndroidLib)
api(projects.uiDesignLib)

View File

@ -6,8 +6,8 @@ import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.filters.MediumTest
import cash.z.ecc.sdk.fixture.WalletAddressesFixture
import cash.z.ecc.sdk.model.WalletAddresses
import cash.z.ecc.android.sdk.fixture.WalletAddressesFixture
import cash.z.ecc.android.sdk.model.WalletAddresses
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.theme.ZcashTheme

View File

@ -59,7 +59,6 @@ class HomeTestSetup(
onRequestCount.incrementAndGet()
},
resetSdk = {},
wipeEntireWallet = {},
isDebugMenuEnabled = false,
updateAvailable = false
)

View File

@ -9,7 +9,7 @@ import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.dp
import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.PercentDecimal
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.screen.home.HomeTag

View File

@ -2,9 +2,9 @@ package co.electriccoin.zcash.ui.screen.home.model
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.sdk.ext.ui.model.FiatCurrencyConversionRateState
import cash.z.ecc.sdk.ext.ui.model.toZecString
import cash.z.ecc.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.toZecString
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
import co.electriccoin.zcash.ui.test.getAppContext

View File

@ -4,8 +4,8 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.filters.MediumTest
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.common.LocalScreenBrightness
import co.electriccoin.zcash.ui.common.ScreenBrightness

View File

@ -4,8 +4,8 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.filters.MediumTest
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.common.LocalScreenTimeout
import co.electriccoin.zcash.ui.common.ScreenTimeout

View File

@ -7,8 +7,8 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.test.filters.MediumTest
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.screen.profile.util.ProfileConfiguration

View File

@ -9,9 +9,9 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.sdk.fixture.ZecRequestFixture
import cash.z.ecc.sdk.model.ZecRequest
import cash.z.ecc.sdk.model.ZecRequestMessage

View File

@ -1,7 +1,7 @@
package co.electriccoin.zcash.ui.screen.restore.model
import androidx.test.filters.SmallTest
import cash.z.ecc.sdk.model.SeedPhrase
import cash.z.ecc.android.sdk.model.SeedPhrase
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue

View File

@ -16,8 +16,8 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.filters.MediumTest
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.sdk.model.SeedPhrase
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
import cash.z.ecc.sdk.model.SeedPhrase
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.CommonTag

View File

@ -1,7 +1,7 @@
package co.electriccoin.zcash.ui.screen.send.ext
import androidx.test.filters.SmallTest
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import co.electriccoin.zcash.ui.test.getAppContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest

View File

@ -2,8 +2,8 @@ package co.electriccoin.zcash.ui.screen.send.ext
import androidx.compose.runtime.saveable.SaverScope
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.model.ZecSend
import cash.z.ecc.sdk.fixture.ZecSendFixture
import cash.z.ecc.sdk.model.ZecSend
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test

View File

@ -14,14 +14,14 @@ import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.ext.collectWith
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.Memo
import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.ZecSend
import cash.z.ecc.sdk.fixture.MemoFixture
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import cash.z.ecc.sdk.fixture.ZecRequestFixture
import cash.z.ecc.sdk.model.Memo
import cash.z.ecc.sdk.model.ZecSend
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.theme.ZcashTheme

View File

@ -13,7 +13,6 @@ import co.electriccoin.zcash.ui.test.getStringResource
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
@ -79,27 +78,11 @@ class SettingsViewTest : UiTestPrerequisites() {
assertEquals(1, testSetup.getAnalyticsToggleCount())
}
@Test
@MediumTest
@Ignore("Wipe has been disabled in Settings and is now a debug-only option")
fun wipe() = runTest {
val testSetup = TestSetup(composeTestRule)
assertEquals(0, testSetup.getBackupCount())
composeTestRule.onNodeWithText(getStringResource(R.string.settings_wipe)).also {
it.performClick()
}
assertEquals(1, testSetup.getWipeCount())
}
private class TestSetup(private val composeTestRule: ComposeContentTestRule) {
private val onBackCount = AtomicInteger(0)
private val onBackupCount = AtomicInteger(0)
private val onRescanCount = AtomicInteger(0)
private val onWipeCount = AtomicInteger(0)
private val onAnalyticsChangedCount = AtomicInteger(0)
fun getOnBackCount(): Int {
@ -117,11 +100,6 @@ class SettingsViewTest : UiTestPrerequisites() {
return onRescanCount.get()
}
fun getWipeCount(): Int {
composeTestRule.waitForIdle()
return onWipeCount.get()
}
fun getAnalyticsToggleCount(): Int {
composeTestRule.waitForIdle()
return onAnalyticsChangedCount.get()
@ -141,9 +119,6 @@ class SettingsViewTest : UiTestPrerequisites() {
onRescanWallet = {
onRescanCount.incrementAndGet()
},
onWipeWallet = {
onWipeCount.incrementAndGet()
},
onAnalyticsSettingsChanged = {
onAnalyticsChangedCount.incrementAndGet()
}

View File

@ -1,220 +1,26 @@
package co.electriccoin.zcash.global
import android.content.Context
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.ext.onFirst
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork
import cash.z.ecc.sdk.model.PersistableWallet
import cash.z.ecc.sdk.type.fromResources
import cash.z.ecc.android.sdk.WalletCoordinator
import co.electriccoin.zcash.spackle.LazyWithArgument
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.preference.EncryptedPreferenceKeys
import co.electriccoin.zcash.ui.preference.EncryptedPreferenceSingleton
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.UUID
class WalletCoordinator(context: Context) {
companion object {
private val lazy = LazyWithArgument<Context, WalletCoordinator> { WalletCoordinator(it) }
fun getInstance(context: Context) = lazy.getInstance(context)
}
private val applicationContext = context.applicationContext
/*
* We want a global scope that is independent of the lifecycles of either
* WorkManager or the UI.
*/
@OptIn(DelicateCoroutinesApi::class)
private val walletScope = CoroutineScope(GlobalScope.coroutineContext + Dispatchers.Main)
private val synchronizerMutex = Mutex()
private val lazy = LazyWithArgument<Context, WalletCoordinator> {
/**
* A flow of the user's stored wallet. Null indicates that no wallet has been stored.
*/
val persistableWallet = flow {
// EncryptedPreferenceSingleton.getInstance() is a suspending function, which is why we need
// the flow builder to provide a coroutine context.
val encryptedPreferenceProvider = EncryptedPreferenceSingleton.getInstance(applicationContext)
val encryptedPreferenceProvider = EncryptedPreferenceSingleton.getInstance(it)
emitAll(EncryptedPreferenceKeys.PERSISTABLE_WALLET.observe(encryptedPreferenceProvider))
}
private val lockoutMutex = Mutex()
private val synchronizerLockoutId = MutableStateFlow<UUID?>(null)
private sealed class InternalSynchronizerStatus {
object NoWallet : InternalSynchronizerStatus()
class Available(val synchronizer: Synchronizer) : InternalSynchronizerStatus()
class Lockout(val id: UUID) : InternalSynchronizerStatus()
WalletCoordinator(it, persistableWallet)
}
private val synchronizerOrLockoutId: Flow<Flow<InternalSynchronizerStatus>> = persistableWallet
.combine(synchronizerLockoutId) { persistableWallet: PersistableWallet?, lockoutId: UUID? ->
if (null != lockoutId) { // this one needs to come first
flowOf(InternalSynchronizerStatus.Lockout(lockoutId))
} else if (null == persistableWallet) {
flowOf(InternalSynchronizerStatus.NoWallet)
} else {
callbackFlow<InternalSynchronizerStatus.Available> {
val closeableSynchronizer = synchronizerMutex.withLock {
Synchronizer.new(
context = context,
zcashNetwork = persistableWallet.network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(persistableWallet.network),
birthday = persistableWallet.birthday,
seed = persistableWallet.seedPhrase.toByteArray()
)
}
trySend(InternalSynchronizerStatus.Available(closeableSynchronizer))
awaitClose {
Twig.info { "Closing flow and synchronizer" }
closeableSynchronizer.close()
}
}
}
}
/**
* Synchronizer for the Zcash SDK. Emits null until a wallet secret is persisted.
*
* Note that this synchronizer is closed as soon as it stops being collected. For UI use
* cases, see [WalletViewModel].
*/
@OptIn(ExperimentalCoroutinesApi::class)
val synchronizer: StateFlow<Synchronizer?> = synchronizerOrLockoutId
.flatMapLatest { it }
.map {
when (it) {
is InternalSynchronizerStatus.Available -> it.synchronizer
is InternalSynchronizerStatus.Lockout -> null
InternalSynchronizerStatus.NoWallet -> null
}
}
.stateIn(
walletScope,
SharingStarted.WhileSubscribed(),
null
)
/**
* Rescans the blockchain.
*
* In order for a rescan to occur, the synchronizer must be loaded already
* which would happen if the UI is collecting it.
*
* @return True if the rescan was performed and false if the rescan was not performed.
*/
suspend fun rescanBlockchain(): Boolean {
synchronizerMutex.withLock {
synchronizer.value?.let {
it.latestBirthdayHeight?.let { height ->
it.rewindToNearestHeight(height, true)
return true
}
}
}
return false
}
/**
* Resets persisted data in the SDK, but preserves the wallet secret. This will cause the
* synchronizer to emit a new instance.
*/
@OptIn(FlowPreview::class)
fun resetSdk() {
walletScope.launch {
lockoutMutex.withLock {
val lockoutId = UUID.randomUUID()
synchronizerLockoutId.value = lockoutId
synchronizerOrLockoutId
.flatMapConcat { it }
.filterIsInstance<InternalSynchronizerStatus.Lockout>()
.filter { it.id == lockoutId }
.onFirst {
synchronizerMutex.withLock {
val didDelete = Synchronizer.erase(
appContext = applicationContext,
network = ZcashNetwork.fromResources(applicationContext)
)
Twig.info { "SDK erase result: $didDelete" }
}
}
synchronizerLockoutId.value = null
}
}
}
/**
* Wipes the wallet. Will cause the app-wide synchronizer to be reset with a new instance.
*/
@OptIn(FlowPreview::class)
fun wipeEntireWallet() {
walletScope.launch {
lockoutMutex.withLock {
val lockoutId = UUID.randomUUID()
synchronizerLockoutId.value = lockoutId
synchronizerOrLockoutId
.flatMapConcat { it }
.filterIsInstance<InternalSynchronizerStatus.Lockout>()
.filter { it.id == lockoutId }
.onFirst {
// Note that clearing the data here is non-atomic since multiple files must be modified
EncryptedPreferenceSingleton.getInstance(applicationContext).also { provider ->
EncryptedPreferenceKeys.PERSISTABLE_WALLET.putValue(provider, null)
}
StandardPreferenceSingleton.getInstance(applicationContext).also { provider ->
StandardPreferenceKeys.IS_USER_BACKUP_COMPLETE.putValue(provider, false)
}
synchronizerMutex.withLock {
val didDelete = Synchronizer.erase(
appContext = applicationContext,
network = ZcashNetwork.fromResources(applicationContext)
)
Twig.info { "SDK erase result: $didDelete" }
}
synchronizerLockoutId.value = null
}
}
}
}
}
fun WalletCoordinator.Companion.getInstance(context: Context) = lazy.getInstance(context)

View File

@ -4,4 +4,4 @@ package co.electriccoin.zcash.ui.common
import androidx.compose.ui.text.intl.Locale
fun Locale.toKotlinLocale() = cash.z.ecc.sdk.ext.ui.model.Locale(language, region, script)
fun Locale.toKotlinLocale() = cash.z.ecc.android.sdk.model.Locale(language, region, script)

View File

@ -2,9 +2,9 @@ package co.electriccoin.zcash.ui.fixture
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.model.PercentDecimal
import co.electriccoin.zcash.ui.screen.home.model.WalletSnapshot
import co.electriccoin.zcash.ui.screen.home.viewmodel.SynchronizerError

View File

@ -1,6 +1,6 @@
package co.electriccoin.zcash.ui.preference
import cash.z.ecc.sdk.model.FiatCurrency
import cash.z.ecc.android.sdk.model.FiatCurrency
import co.electriccoin.zcash.preference.api.PreferenceProvider
import co.electriccoin.zcash.preference.model.entry.Key
import co.electriccoin.zcash.preference.model.entry.PreferenceDefault

View File

@ -1,6 +1,6 @@
package co.electriccoin.zcash.ui.preference
import cash.z.ecc.sdk.model.PersistableWallet
import cash.z.ecc.android.sdk.model.PersistableWallet
import co.electriccoin.zcash.preference.api.PreferenceProvider
import co.electriccoin.zcash.preference.model.entry.Key
import co.electriccoin.zcash.preference.model.entry.PreferenceDefault

View File

@ -38,8 +38,8 @@ import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import cash.z.ecc.sdk.fixture.WalletAddressesFixture
import cash.z.ecc.sdk.model.WalletAddresses
import cash.z.ecc.android.sdk.fixture.WalletAddressesFixture
import cash.z.ecc.android.sdk.model.WalletAddresses
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
import co.electriccoin.zcash.ui.design.component.Body

View File

@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import cash.z.ecc.sdk.model.PersistableWallet
import cash.z.ecc.android.sdk.model.PersistableWallet
import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.screen.backup.ext.Saver

View File

@ -38,8 +38,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
import cash.z.ecc.sdk.model.PersistableWallet
import co.electriccoin.zcash.spackle.model.Index
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.SecureScreen

View File

@ -14,7 +14,6 @@ import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.screen.home.view.Home
import co.electriccoin.zcash.ui.screen.home.viewmodel.CheckUpdateViewModel
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.screen.onboarding.viewmodel.OnboardingViewModel
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
@ -81,15 +80,6 @@ internal fun WrapHome(
resetSdk = {
walletViewModel.resetSdk()
},
wipeEntireWallet = {
// Although this is debug only, it still might be nice to show a warning dialog
// before performing this action
walletViewModel.wipeEntireWallet()
val onboardingViewModel by activity.viewModels<OnboardingViewModel>()
onboardingViewModel.onboardingState.goToBeginning()
onboardingViewModel.isImporting.value = false
},
updateAvailable = updateAvailable
)

View File

@ -3,11 +3,11 @@ package co.electriccoin.zcash.ui.screen.home.model
import android.content.Context
import androidx.compose.ui.text.intl.Locale
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.sdk.ext.ui.model.FiatCurrencyConversionRateState
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
import cash.z.ecc.sdk.ext.ui.model.toZecString
import cash.z.ecc.sdk.ext.ui.toFiatCurrencyState
import cash.z.ecc.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.toFiatCurrencyState
import cash.z.ecc.android.sdk.model.toZecString
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.toKotlinLocale
import kotlin.math.roundToInt

View File

@ -3,9 +3,9 @@ package co.electriccoin.zcash.ui.screen.home.model
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.model.PercentDecimal
import co.electriccoin.zcash.ui.screen.home.viewmodel.SynchronizerError
// TODO [#292]: Should be moved to SDK-EXT-UI module.

View File

@ -42,8 +42,8 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import cash.z.ecc.sdk.ext.ui.model.FiatCurrencyConversionRateState
import cash.z.ecc.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
import cash.z.ecc.android.sdk.model.PercentDecimal
import co.electriccoin.zcash.crash.android.GlobalCrashReporter
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
@ -73,7 +73,6 @@ fun ComposablePreview() {
goSend = {},
goRequest = {},
resetSdk = {},
wipeEntireWallet = {},
isDebugMenuEnabled = false,
updateAvailable = false
)
@ -92,12 +91,11 @@ fun Home(
goSend: () -> Unit,
goRequest: () -> Unit,
resetSdk: () -> Unit,
wipeEntireWallet: () -> Unit,
isDebugMenuEnabled: Boolean,
updateAvailable: Boolean
) {
Scaffold(topBar = {
HomeTopAppBar(isDebugMenuEnabled, resetSdk, wipeEntireWallet)
HomeTopAppBar(isDebugMenuEnabled, resetSdk)
}) { paddingValues ->
HomeMainContent(
paddingValues,
@ -117,20 +115,21 @@ fun Home(
private fun HomeTopAppBar(
isDebugMenuEnabled: Boolean,
resetSdk: () -> Unit,
wipeEntireWallet: () -> Unit
) {
TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) },
actions = {
if (isDebugMenuEnabled) {
DebugMenu(resetSdk, wipeEntireWallet)
DebugMenu(resetSdk)
}
}
)
}
@Composable
private fun DebugMenu(resetSdk: () -> Unit, wipeEntireWallet: () -> Unit) {
private fun DebugMenu(
resetSdk: () -> Unit
) {
var expanded by rememberSaveable { mutableStateOf(false) }
IconButton(onClick = { expanded = true }) {
Icon(Icons.Default.MoreVert, contentDescription = null)
@ -164,13 +163,6 @@ private fun DebugMenu(resetSdk: () -> Unit, wipeEntireWallet: () -> Unit) {
expanded = false
}
)
DropdownMenuItem(
text = { Text("Wipe entire wallet") },
onClick = {
wipeEntireWallet()
expanded = false
}
)
}
}

View File

@ -6,19 +6,23 @@ import androidx.lifecycle.viewModelScope
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.WalletCoordinator
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.FiatCurrency
import cash.z.ecc.android.sdk.model.PendingTransaction
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.android.sdk.model.WalletAddresses
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.isMined
import cash.z.ecc.android.sdk.model.isSubmitSuccess
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.sdk.model.FiatCurrency
import cash.z.ecc.sdk.model.PercentDecimal
import cash.z.ecc.sdk.model.PersistableWallet
import cash.z.ecc.sdk.model.WalletAddresses
import cash.z.ecc.sdk.type.fromResources
import co.electriccoin.zcash.global.getInstance
import co.electriccoin.zcash.spackle.Twig
import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT
import co.electriccoin.zcash.ui.common.throttle
@ -59,7 +63,7 @@ import kotlin.time.ExperimentalTime
// TODO [#292]: Should be moved to SDK-EXT-UI module.
// TODO [#292]: https://github.com/zcash/secant-android-wallet/issues/292
class WalletViewModel(application: Application) : AndroidViewModel(application) {
private val walletCoordinator = co.electriccoin.zcash.global.WalletCoordinator.getInstance(application)
private val walletCoordinator = WalletCoordinator.getInstance(application)
/*
* Using the Mutex may be overkill, but it ensures that if multiple calls are accidentally made
@ -184,7 +188,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
val application = getApplication<Application>()
viewModelScope.launch {
val newWallet = PersistableWallet.new(application)
val newWallet = PersistableWallet.new(application, ZcashNetwork.fromResources(application))
persistExistingWallet(newWallet)
}
}
@ -244,20 +248,6 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
fun resetSdk() {
walletCoordinator.resetSdk()
}
/**
* This asynchronously wipes the entire wallet state.
*
* This is destructive, as the seed phrase is deleted along with the SDK state.
*
* This could be used as part of testing, to quickly reset the app state.
*
* A more complete reset of app state can be performed in Android Settings, as this will not
* clear application state beyond the SDK and wallet secret.
*/
fun wipeEntireWallet() {
walletCoordinator.wipeEntireWallet()
}
}
/**

View File

@ -9,10 +9,10 @@ import androidx.activity.viewModels
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.android.sdk.model.SeedPhrase
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
import cash.z.ecc.sdk.model.PersistableWallet
import cash.z.ecc.sdk.model.SeedPhrase
import cash.z.ecc.sdk.type.fromResources
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil

View File

@ -6,7 +6,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.viewModels
import androidx.compose.runtime.Composable
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cash.z.ecc.sdk.model.WalletAddresses
import cash.z.ecc.android.sdk.model.WalletAddresses
import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.screen.profile.view.Profile

View File

@ -22,8 +22,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.BrightenScreen
import co.electriccoin.zcash.ui.common.DisableScreenTimeout

View File

@ -26,13 +26,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.ext.ui.ZecStringExt
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
import cash.z.ecc.sdk.ext.ui.model.ZecString
import cash.z.ecc.sdk.ext.ui.model.fromZecString
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.ZecString
import cash.z.ecc.android.sdk.model.ZecStringExt
import cash.z.ecc.android.sdk.model.fromZecString
import cash.z.ecc.sdk.model.ZecRequest
import cash.z.ecc.sdk.model.ZecRequestMessage
import co.electriccoin.zcash.ui.R

View File

@ -1,6 +1,6 @@
package co.electriccoin.zcash.ui.screen.restore.model
import cash.z.ecc.sdk.model.SeedPhrase
import cash.z.ecc.android.sdk.model.SeedPhrase
import co.electriccoin.zcash.ui.common.first
import java.util.Locale

View File

@ -17,8 +17,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.sdk.fixture.PersistableWalletFixture
import cash.z.ecc.sdk.model.PersistableWallet
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.SecureScreen
import co.electriccoin.zcash.ui.design.component.Body

View File

@ -5,7 +5,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.WalletAddress
import co.electriccoin.zcash.ui.R
/**

View File

@ -1,10 +1,10 @@
package co.electriccoin.zcash.ui.screen.send.ext
import androidx.compose.runtime.saveable.mapSaver
import cash.z.ecc.android.sdk.model.Memo
import cash.z.ecc.android.sdk.model.WalletAddress
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.model.Memo
import cash.z.ecc.sdk.model.WalletAddress
import cash.z.ecc.sdk.model.ZecSend
import cash.z.ecc.android.sdk.model.ZecSend
import kotlinx.coroutines.runBlocking
private const val KEY_ADDRESS = "address" // $NON-NLS

View File

@ -31,15 +31,15 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import cash.z.ecc.android.sdk.model.Memo
import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.sdk.ext.ui.ZecSendExt
import cash.z.ecc.sdk.ext.ui.ZecStringExt
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
import cash.z.ecc.sdk.ext.ui.model.ZecString
import cash.z.ecc.sdk.ext.ui.model.toZecString
import cash.z.ecc.android.sdk.model.ZecSend
import cash.z.ecc.android.sdk.model.ZecSendExt
import cash.z.ecc.android.sdk.model.ZecString
import cash.z.ecc.android.sdk.model.ZecStringExt
import cash.z.ecc.android.sdk.model.toZecString
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import cash.z.ecc.sdk.model.Memo
import cash.z.ecc.sdk.model.ZecSend
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
import co.electriccoin.zcash.ui.design.component.GradientSurface

View File

@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
import co.electriccoin.zcash.ui.screen.onboarding.viewmodel.OnboardingViewModel
import co.electriccoin.zcash.ui.screen.settings.view.Settings
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
@ -46,13 +45,6 @@ private fun WrapSettings(
onRescanWallet = {
walletViewModel.rescanBlockchain()
},
onWipeWallet = {
walletViewModel.wipeEntireWallet()
val onboardingViewModel by activity.viewModels<OnboardingViewModel>()
onboardingViewModel.onboardingState.goToBeginning()
onboardingViewModel.isImporting.value = false
},
onAnalyticsSettingsChanged = {
settingsViewModel.setAnalyticsEnabled(it)
}

View File

@ -42,7 +42,6 @@ fun PreviewSettings() {
isAnalyticsEnabled = true,
onBack = {},
onBackupWallet = {},
onWipeWallet = {},
onRescanWallet = {},
onAnalyticsSettingsChanged = {}
)
@ -57,7 +56,6 @@ fun Settings(
isAnalyticsEnabled: Boolean,
onBack: () -> Unit,
onBackupWallet: () -> Unit,
onWipeWallet: () -> Unit,
onRescanWallet: () -> Unit,
onAnalyticsSettingsChanged: (Boolean) -> Unit
) {
@ -68,7 +66,6 @@ fun Settings(
paddingValues,
isAnalyticsEnabled,
onBackupWallet = onBackupWallet,
onWipeWallet = onWipeWallet,
onRescanWallet = onRescanWallet,
onAnalyticsSettingsChanged = onAnalyticsSettingsChanged
)
@ -99,7 +96,6 @@ private fun SettingsMainContent(
paddingValues: PaddingValues,
isAnalyticsEnabled: Boolean,
onBackupWallet: () -> Unit,
@Suppress("UNUSED_PARAMETER") onWipeWallet: () -> Unit,
onRescanWallet: () -> Unit,
onAnalyticsSettingsChanged: (Boolean) -> Unit
) {

View File

@ -1,7 +1,7 @@
package co.electriccoin.zcash.ui.screen.support.model
import android.content.Context
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.MonetarySeparators
import co.electriccoin.zcash.global.StorageChecker
import java.util.Locale

View File

@ -9,7 +9,8 @@ import androidx.work.PeriodicWorkRequest
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkerParameters
import cash.z.ecc.android.sdk.Synchronizer
import co.electriccoin.zcash.global.WalletCoordinator
import cash.z.ecc.android.sdk.WalletCoordinator
import co.electriccoin.zcash.global.getInstance
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine

View File

@ -59,7 +59,6 @@ dependencies {
implementation(projects.testLib)
implementation(projects.spackleAndroidLib)
implementation(projects.sdkExtLib)
implementation(projects.sdkExtUiLib)
implementation(libs.bundles.androidx.test)
implementation(libs.bundles.androidx.compose.core)

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